diff --git a/.eslintignore b/.eslintignore index 7bbe41136..4af79d129 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,4 +2,6 @@ apps/animclk/V29.LBM.js apps/banglerun/rollup.config.js apps/schoolCalendar/fullcalendar/main.js apps/authentiwatch/qr_packed.js +apps/qrcode/qr-scanner.umd.min.js +apps/gipy/pkg/gpconv.js *.test.js diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..345bce54f --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: espruino +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: ['http://www.espruino.com/Donate']# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 000000000..7c0cfca3a --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,27 @@ +name: build + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository and submodules + uses: actions/checkout@v3 + with: + submodules: recursive + - name: Use Node.js 16.x + uses: actions/setup-node@v3 + with: + node-version: 16.x + - name: Install testing dependencies + run: npm ci + - name: Test all apps and widgets + run: npm test + - name: Install typescript dependencies + working-directory: ./typescript + run: npm ci + - name: Build all TS apps and widgets + working-directory: ./typescript + run: npm run build diff --git a/.gitignore b/.gitignore index 523dc5f20..f4588ac6f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ .htaccess node_modules -package-lock.json .DS_Store *.js.bak appdates.csv @@ -9,4 +8,9 @@ appdates.csv _config.yml tests/Layout/bin/tmp.* tests/Layout/testresult.bmp -apps.local.json \ No newline at end of file +apps.local.json +_site +.jekyll-cache +.owncloudsync.log +Desktop.ini +.sync_*.db* diff --git a/.gitmodules b/.gitmodules index fd6663d2a..c2c1104c2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "EspruinoAppLoaderCore"] path = core url = https://github.com/espruino/EspruinoAppLoaderCore.git +[submodule "webtools"] + path = webtools + url = https://github.com/espruino/EspruinoWebTools.git diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f3d0d2159..000000000 --- a/.travis.yml +++ /dev/null @@ -1,3 +0,0 @@ -language: node_js -node_js: - - "node" diff --git a/README.md b/README.md index 9cf30065a..fed13a358 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Bangle.js App Loader (and Apps) ================================ -[![Build Status](https://app.travis-ci.com/espruino/BangleApps.svg?branch=master)](https://app.travis-ci.com/github/espruino/BangleApps) +[![Build Status](https://github.com/espruino/BangleApps/actions/workflows/nodejs.yml/badge.svg)](https://github.com/espruino/BangleApps/actions/workflows/nodejs.yml) * 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/) @@ -72,6 +72,18 @@ try and keep filenames short to avoid overflowing the buffer. }, ``` +### Screenshots + +In the app `metadata.json` file you can add a list of screenshots with a line like: `"screenshots" : [ { "url":"screenshot.png" } ],` + +To get a screenshot you can: + +* 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. + + ## Testing ### Online @@ -174,12 +186,12 @@ The widget example is available in [`apps/_example_widget`](apps/_example_widget 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 +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) + 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 @@ -214,10 +226,8 @@ 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" - // if this is 'widget' then it's not displayed in the menu - // if it's 'clock' then it'll be loaded by default at boot time - // if this is 'bootloader' then it's code that is run at boot time, but is not in a menu + "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", @@ -240,18 +250,43 @@ and which gives information about the app for the 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 + "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 - // 'launch' - replacement launcher app - // 'bootloader' - code that runs at startup only + // '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 (see modules/clock_info.js) "supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2 - "dependencies" : { "notify":"type" } // optional, app 'types' we depend on + "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 @@ -306,7 +341,7 @@ and which gives information about the app for the Launcher. ``` * 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` or empty. +* 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 @@ -402,7 +437,7 @@ Example `settings.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 + if (typeof settings.monkeys !== "number") settings.monkeys = 12; // default value function save(key, value) { settings[key] = value; require('Storage').write('myappid.json', settings); @@ -436,7 +471,10 @@ It should also add `myappid.json` to `data`, to make sure it is cleaned up when ## Modules You can include any of [Espruino's modules](https://www.espruino.com/Modules) as -normal with `require("modulename")`. If you want to develop your own module for your +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. diff --git a/_config.yml b/_config.yml index 2f7efbeab..c74188174 100644 --- a/_config.yml +++ b/_config.yml @@ -1 +1 @@ -theme: jekyll-theme-minimal \ No newline at end of file +theme: jekyll-theme-slate \ No newline at end of file diff --git a/android.html b/android.html new file mode 100644 index 000000000..8a70a46e9 --- /dev/null +++ b/android.html @@ -0,0 +1,352 @@ + + + + + + + + + + + + + + + + + + + + Bangle.js App Loader + + + + + + +
+ +
+ + + + +
+
+ +
+ + +
+
+
+ + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/2047pp/2047pp.app.js b/apps/2047pp/2047pp.app.js new file mode 100644 index 000000000..9163aaf3a --- /dev/null +++ b/apps/2047pp/2047pp.app.js @@ -0,0 +1,140 @@ +class TwoK { + constructor() { + this.b = Array(4).fill().map(() => Array(4).fill(0)); + this.score = 0; + this.cmap = {0: "#caa", 2:"#ccc", 4: "#bcc", 8: "#ba6", 16: "#e61", 32: "#d20", 64: "#d00", 128: "#da0", 256: "#ec0", 512: "#dd0"}; + } + drawBRect(x1, y1, x2, y2, th, c, cf, fill) { + g.setColor(c); + for (i=0; i 4) g.setColor(1, 1, 1); + else g.setColor(0, 0, 0); + g.setFont("Vector", bh*(b>8 ? (b>64 ? (b>512 ? 0.32 : 0.4) : 0.6) : 0.7)); + if (b>0) g.drawString(b.toString(), xo+(x+0.5)*bw+1, yo+(y+0.5)*bh); + } + } + shift(d) { // +/-1: shift x, +/- 2: shift y + var crc = E.CRC32(this.b.toString()); + if (d==-1) { // shift x left + for (y=0; y<4; ++y) { + for (x=2; x>=0; x--) + if (this.b[y][x]==0) { + for (i=x; i<3; i++) this.b[y][i] = this.b[y][i+1]; + this.b[y][3] = 0; + } + for (x=0; x<3; ++x) + if (this.b[y][x]==this.b[y][x+1]) { + this.score += 2*this.b[y][x]; + this.b[y][x] += this.b[y][x+1]; + for (j=x+1; j<3; ++j) this.b[y][j] = this.b[y][j+1]; + this.b[y][3] = 0; + } + } + } + else if (d==1) { // shift x right + for (y=0; y<4; ++y) { + for (x=1; x<4; x++) + if (this.b[y][x]==0) { + for (i=x; i>0; i--) this.b[y][i] = this.b[y][i-1]; + this.b[y][0] = 0; + } + for (x=3; x>0; --x) + if (this.b[y][x]==this.b[y][x-1]) { + this.score += 2*this.b[y][x]; + this.b[y][x] += this.b[y][x-1] ; + for (j=x-1; j>0; j--) this.b[y][j] = this.b[y][j-1]; + this.b[y][0] = 0; + } + } + } + else if (d==-2) { // shift y down + for (x=0; x<4; ++x) { + for (y=1; y<4; y++) + if (this.b[y][x]==0) { + for (i=y; i>0; i--) this.b[i][x] = this.b[i-1][x]; + this.b[0][x] = 0; + } + for (y=3; y>0; y--) + if (this.b[y][x]==this.b[y-1][x] || this.b[y][x]==0) { + this.score += 2*this.b[y][x]; + this.b[y][x] += this.b[y-1][x]; + for (j=y-1; j>0; j--) this.b[j][x] = this.b[j-1][x]; + this.b[0][x] = 0; + } + } + } + else if (d==2) { // shift y up + for (x=0; x<4; ++x) { + for (y=2; y>=0; y--) + if (this.b[y][x]==0) { + for (i=y; i<3; i++) this.b[i][x] = this.b[i+1][x]; + this.b[3][x] = 0; + } + for (y=0; y<3; ++y) + if (this.b[y][x]==this.b[y+1][x] || this.b[y][x]==0) { + this.score += 2*this.b[y][x]; + this.b[y][x] += this.b[y+1][x]; + for (j=y+1; j<3; ++j) this.b[j][x] = this.b[j+1][x]; + this.b[3][x] = 0; + } + } + } + return (E.CRC32(this.b.toString())!=crc); + } + addDigit() { + var d = Math.random()>0.9 ? 4 : 2; + var id = Math.floor(Math.random()*16); + while (this.b[Math.floor(id/4)][id%4] > 0) id = Math.floor(Math.random()*16); + this.b[Math.floor(id/4)][id%4] = d; + } +} + +function dragHandler(e) { + if (e.b && (Math.abs(e.dx)>7 || Math.abs(e.dy)>7)) { + var res = false; + if (Math.abs(e.dx)>Math.abs(e.dy)) { + if (e.dx>0) res = twok.shift(1); + if (e.dx<0) res = twok.shift(-1); + } + else { + if (e.dy>0) res = twok.shift(-2); + if (e.dy<0) res = twok.shift(2); + } + if (res) twok.addDigit(); + twok.render(); + } +} + +function swipeHandler() { + +} + +function buttonHandler() { + +} + +var twok = new TwoK(); +twok.addDigit(); twok.addDigit(); +twok.render(); +if (process.env.HWVERSION==2) Bangle.on("drag", dragHandler); +if (process.env.HWVERSION==1) { + Bangle.on("swipe", (e) => { res = twok.shift(e); if (res) twok.addDigit(); twok.render(); }); + setWatch(() => { res = twok.shift(2); if (res) twok.addDigit(); twok.render(); }, BTN1, {repeat: true}); + setWatch(() => { res = twok.shift(-2); if (res) twok.addDigit(); twok.render(); }, BTN3, {repeat: true}); +} diff --git a/apps/2047pp/2047pp_screenshot.png b/apps/2047pp/2047pp_screenshot.png new file mode 100644 index 000000000..8c407fb6f Binary files /dev/null and b/apps/2047pp/2047pp_screenshot.png differ diff --git a/apps/2047pp/ChangeLog b/apps/2047pp/ChangeLog new file mode 100644 index 000000000..a1f88e5ec --- /dev/null +++ b/apps/2047pp/ChangeLog @@ -0,0 +1,2 @@ +0.01: New app! +0.02: Better support for watch themes diff --git a/apps/2047pp/README.md b/apps/2047pp/README.md new file mode 100644 index 000000000..cac3323a6 --- /dev/null +++ b/apps/2047pp/README.md @@ -0,0 +1,9 @@ + +# Game of 2047pp (2047++) + +Tile shifting game inspired by the well known 2048 game. Also very similar to another Bangle game, Game1024. + +Attempt to combine equal numbers by swiping left, right, up or down (on Bangle 2) or swiping left/right and using +the top/bottom button (Bangle 1). + +![Screenshot](./2047pp_screenshot.png) diff --git a/apps/2047pp/app-icon.js b/apps/2047pp/app-icon.js new file mode 100644 index 000000000..4086d1879 --- /dev/null +++ b/apps/2047pp/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A31gAeFtoxPF9wujGBYQG1YAWF6ur5gAYGIovOFzIABF6ReaMAwv/F/4v/F7ejv9/0Yvq1Eylksv4vqvIuBF9ZeDF9ZeBqovr1AsB0YvrLwXMF9ReDF9ZeBq1/v4vBqowKF7lWFYIAFF/7vXAAa/qF+jxB0YvsABov/F/4v/F6WsF7YgEF5xgaLwgvPGIQAWDwwvQADwvJGEguKF+AxhFpoA/AH4A/AFI=")) \ No newline at end of file diff --git a/apps/2047pp/app.png b/apps/2047pp/app.png new file mode 100644 index 000000000..d1fb4a5e5 Binary files /dev/null and b/apps/2047pp/app.png differ diff --git a/apps/2047pp/metadata.json b/apps/2047pp/metadata.json new file mode 100644 index 000000000..033354ac6 --- /dev/null +++ b/apps/2047pp/metadata.json @@ -0,0 +1,15 @@ +{ "id": "2047pp", + "name": "2047pp", + "shortName":"2047pp", + "icon": "app.png", + "version":"0.02", + "description": "Bangle version of a tile shifting game", + "supports" : ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "readme": "README.md", + "tags": "game", + "storage": [ + {"name":"2047pp.app.js","url":"2047pp.app.js"}, + {"name":"2047pp.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/2ofthemclk/ChangeLog b/apps/2ofthemclk/ChangeLog new file mode 100644 index 000000000..2286a7f70 --- /dev/null +++ b/apps/2ofthemclk/ChangeLog @@ -0,0 +1 @@ +0.01: New App! \ No newline at end of file diff --git a/apps/2ofthemclk/README.md b/apps/2ofthemclk/README.md new file mode 100644 index 000000000..7ac2cf779 --- /dev/null +++ b/apps/2ofthemclk/README.md @@ -0,0 +1,11 @@ +# two of them clock + +You can now wear teh memez on your wrist. + +![](screenshot.png) + +Also serves as an example of displaying seconds only when unlocked or charging and only refreshing on the minute otherwise. +Widgets not supported + +## Creator +- [Kilrah](https://github.com/kilrah) diff --git a/apps/2ofthemclk/app-icon.js b/apps/2ofthemclk/app-icon.js new file mode 100644 index 000000000..9bfb8a550 --- /dev/null +++ b/apps/2ofthemclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgZC/AH4ADkAPOgVJkgEBAQQAJiQRByEJgmQCJWSpMEAQMkyQJCpASHhAOBpAmBJJgjBCIUJCRg4CCIJxFMQ2SoARCkmACI0EBAJHCCIMLj4RFiUBskAgIXBEAU5A4P34CtCiEJsEJ/AHBCgOBAoQAEi0H////HciQsBwywICIXWzkG4A+BEY0gif46dt6/cgnIgkWnHfLIP/MoUWwHbpvC/kAjEEj0HNYQCCkEfGgP/64RB2EAifHLwMAjg1CCIMD/0H/0B8EAh+HgeAkARCE4IjC/4jBYIMPLIcIAYUPB4OBCIQABhu/AoShCHYIRBx6QBDgUw2//8OHPwcJ39//ILBCIU9LgMBSQgsBJAYRBkE/CIIABgRHD3wRFkk/2zBDAYU//3b/oRB8ARBj6ABgEE7YREEYf+oMkSwINCyClCn//z//+4RBgMkgU3EgUcwFJgEeboOXCIP2EYJCDAAVJkkGWoIuBgf2EYQPDkECCIOGd4ffyEJkgFBAAcSoEkwQCBhw+BwQaByVAkGAKwIFBBANLkEQgAyBCIVIkBpBgmSBYOQoApBgcgiQRCAQIyCCgsSjIFBCIcgRgJNCCgQyBpAgDAQT2BCgIOBBAQUCCIpfBCIwCKP4QRNpCSDCLyJBCIbjBTwYRLboJ0BCI4QD")) diff --git a/apps/2ofthemclk/app.js b/apps/2ofthemclk/app.js new file mode 100644 index 000000000..78415fba7 --- /dev/null +++ b/apps/2ofthemclk/app.js @@ -0,0 +1,90 @@ +const img = require("heatshrink").decompress(atob("2GwgZC/ADEIAQMBgEQAgMChEEAoQA4wACEAHKDBAQYA9X/YACgQFGkBH1HAQFCwEIgkAAQVAH2GAAwtBQwcQgMAwQ/vGQI4CAQRHCwFAkACCQYTRFAEqzBAopHCgMEAQiSHAEsQAYT1ChBEEPoMQBAMQAoQRCX9BEDOgR6BHwkgwVBQw4aEIlKGCPoWCI4REBBAMEQYUCH856CNwJ9CO4YCBHwOSgCGBRIREBJQICBAAOAH0UQGQJ0CHYTCDAQOQpKABZAICCZAREBQccgIgJHBoJrBXIQFBIISDFwALBKwTEkd4RrBPobIBHAMkIgICBQYoODSoIfBRIYmEACjjBMoIgBwAsEF4YFDkmSAoUBBATgDDQIjEADSqBEwI4BgJxCPoUEiVIkmCpI7BoLCBpBTEC4MChAjEfy6hCL4J3EAQMJHwLCDBwJEBQYMkiQODI4JcBQAICDEwKMCRK4dBYoJrCHYMgyB3BHYWShKGCBweAJoLXDQAJiBZAQAVC4ZZBU4KADPQR3BQYLCByA+BBAWCJoabCUIR6BcwQIFaIQAPC4KABwC8CoC/DPQSDDoICBHwREBJQabBHwRHBNYSqDZYQAMCIQCBQYT7DAQKADQwVJAQY7BAoJEBHAJZCQwYmBEANANATXBI4IvBPpJQFiEAPoYCBWwg7CBYIFCAQIFGcAYCBX4ImBIgQaBBwLDPwCDCoMAEYS/BfwJECoJEDAQuCSQLOBBwJ9DQYY7BHwaDDABYRBQwIgBDQMSVoMSHwK5BIgKDDIIyACKwMEyDLChKGEZwckIgWCIhwXBYQY7BEwJHBHZICHSoSeCMogCCQYYOBYho+BTwQmBQYQvCQAUSIJyACZAKDCMobsDEANAH5hTDTYYmDF4ICBQaICCLgSeBEAKACJohKCRgJBIwCeBC4IdBEALsBQAYCCHx4XBLIRlBkkQRgRBBdgMEJoSDMgJEBKwRcCU4J9SAQYaCGoI4BAoUQVobFBJQL4BGoICBAA4UDL4QsCiRBVAQjdBiS8CAQMShLRDfAUQH44+BC4sgyAjBHzC/CGoKnCySqCagJuCiEIQYNAQZIRCyAXBQALFXQxBHEaIQIBQYZNBH4sSQYVAKwIgBIgQ+ZMQYFBHwMBVQiGCGoILBIAsEgBNBTwWCZASAdMQkSVQaDBKAOCXIVBZALLBIIY+BwBWBQYbCfQYbCBEwRHBRIRxBQwMCQgiYDCIOShBcCQbxEBEYJHChIIBAQMQQYK5CwFAH4JHBiCYCQYg+eUgaDBAQYLCeoR6BAQUBQYcCQYJNCCgRBgQAZrBBAZ0BYoUQAQMAQYaJBKwLaDDQgCdQA6MCBYI4BiEJIgKDCYoVAHwIOBCgRBggTvChLCDRIMEGQTFBAQKDCgAOBhI+kyVIj4mBgkCRgzFGgRHBQYRKBKAJcCIkP/+UIJpB3BJoMQAoOQoBBBgCYDQclJgF+YQZEIkkQKAKDBgGAQAKSBQYNBIMfgh0BHY2SXgI1BQANIgjLCQYKPBLJZBcgPBggIDoI+BHYL4CgUBBAI/BwDIBKYUSIMsAnApFfYQABHwOQoEEiACCZYKPBQctIGwSDEfYIAEX4L7BiQLCQYTFmpEDwAuBgSAByVAIIoABhMAYoKDBH0xBHGoJ6BABMkYoJTBAoTFqgf+H5QABkGQYQOAQdNPGQUcvxBMgGAoMEiVBkmQExYOBILkfwP4jhAKgmShEEYRxNBCIJEBiVJkBKSo4zDv//8CDLFgTdBkmCExWCC4gFBIIM5IK8fwDHMEx0EDBEB/5BQoED+EAgbFBQZIjBAYK2CVozRBAoMSLRUPDoJBPvEAvw1FEZJNBgI4DAQa2CAoKeKgf+g/kIJ84//+IIX4AYMHgfx4Ech0HjgqFiUJDol/8AFBkANBUJDsC8ECIJuT/H/MQ0H8f+n//x/AFQwmFyE4j4FBL4PHKwwAB/gmBJoNBTAYCIyP4CgJBG//wn/j+CvIgLFFgFwIIMAnAUHHwU4AQMIQZmf/5BIIgjyJII0OAQMPTA8B/4CBBYjILv//cZAIBMQRBNyAHCn41B/AXEMQMP/AcHhJBIk//KwIAJQwRBRKwN/4BBC8Fx4Hj/5BBJoJBNpEDCgQAWII8AMgP4j+PAgIADYQMPOIsBQZEfwE4IMEfHooAD/EDwE/dIqDI/g4QEYJBLg//+ARBIJKDBAA8EII1HcAZBN/kBEwxBFMQSDKDQSzEuACBIIySDIJsHgEOQZkcIJeOn4XCGQhHBiRBGOgZALuAaBIJeSg4vCSoJBHwP48F/45xDvxBKDoKCMTwQAHEAeRaIccIIuP/5rBg///x0DgeB44EBIIpKBv//8EPfAwAGQZeTDQN/DoU///x44+Bn6tCj/+cwJBCwAFCIImRQAMPIIIaBH5UHgf+IJf4jggDg59C8EOAoKhBj47DEwYCBhIgE/4RBn4gEIJKnBIJkfXwL4C/kBJQJBB8fwCwSJDAAK8BIIrmBIIIdBBgP/DQYAGBZDmIF4XgIIMD+E4XgIKBSQJuFHAMBIIn4/4UBAQf/45oCAAb1BVQRBJyCtCNwQCBuE48eABAQgCFYJHCAAMcgBBEyE/OIUfWwIVBLIxiBWwOOYpZZBhwJBgPAgF+JQU/IIopDMoK8CEAeAagUHgf/4BBCGw0B/5BNDAIODHwN+AgNxLgI1B8CDJUgoLDMoZ6BGwxoBj6VBIJORLYXAfwMHHAPAjgjBHYMHB4RBFEAwLFh6kBgJBDZAhBSNwMH/l/HwP/QYKtEIIk/KwIgEiE4BYM4YoP48DXBEwM/8Y7Dn//IJ74BhxBBh4IDAAPHHwIgBIIUB47RCgiDDHwIvCUIIvCL4P/YoqJEIJn/+I+FAAPwZwMBQwK5EIIxxCx0AU4Q0HQYjmLyAOBIJXgZwILBZwIgEBYJBDkGAg68BgDFBIIMB/BBIQZhBEbod/IIZfDAARBEgYFBkCDDg6/EHwMBYQhHDeQJBLwYwC4ED/wMBh4ICEwYOCIIg4DQYYvCa4JfB/CGBUIQmCIIU4IJmBDAP+O4SkCIIhHDIIs4IJH/xyDCMoP/46YD+IpBQZ2AHwPxX5H4AQofHIIcEIIQgBHwKGCHYQABuBZCn6MDII4gBIIahC//wC4KhBJYZBBMQomBgIgCQYMAUgQPDgLIBTYbgGQZJBCx40BRIQmCZAYAEFIyDDEALCC/8cgLmCQYhBQBwRBDHwLIHAAakGh0BIIQHCIIX/wLaDI4IFD8FwSQhBGpBBC8ZBB/kOHYSkCIIccv4CBDokcuADBQYjjDMQP8QY4IBI4ZBHSQZiEfwaDD+BfCFgIgHkCDEgEPIIUAbRDOKIIOQAofjHoWOAwJ6BYov8uLFHAALFCQYL1Bh/4L4Uf+A7Hv5BPL4UAv0HfwcBAgQADgEONgxBCAgI7BUIYADLIrjFIIyYDnEfQALpDPoStCNYTODcxCDBeohBFboKPBAQLpBIJRnD/ED/wUB+IgDjkOn7RDQYTwHIIQgC8FwIIOP45BEgCGBFgJ3DIJQoDDYPwC4OPHAIICJoStHIIsgCgPgagMfGoV/Rgf8v4eBIKK8Cx/AHAUD+E/JQM4BAXjU4yDFC4LXCQwIQEcYRfIB4cgA4pWCEYImCIIKtBwBEBJQaDKgE4GoPHGQ6hDIBIABIIk/gfxfYl+BQNx4DIDQZNJII6hBwLCDUghALQY3jGgZZCL4OPEwKPEjggGhJBEn4gBHwaDFDQ4gGIIcOYooAB+ChD/gLEj+AIJVwa4SbBACpfCIIkPEQLIEAA/HVQ7FDkkOQYP+n/gEwRBZcYI+LR4ofEgRBDkBBB8EB//gHR1wAomCIIgODX4YAJR4TFGQYihB/hBBYipiCA4hfBnBALF4SDCOgcSQYhBCAQPj+P4HRcfMQkEL4QOEOgPAgF/IJMDAYWAnDFIQYRQBg/jx4jBABUDIJAIEGIRxC8YmBIAnAAgaDBAAYgCAQSSEgeOXxn8AohBDyQHCHAYRFI4ILBPoMfx4PBwPAYpBlBn6kDABscAogdDyVIA4IgBMQICBn7sB444B45KBIgPxYoU/EIZBHa4JBEuA/HBAV+AwUBIIlJgg4C/yGD8F/YgjLFwP4IJTCB/4OEIJFxEALUDII0gh4wC/ACCEwIABX4MfIwgOCageCIImQnEcLgMcHxDCCv4vBWwIABhJBFkkBF4SGBGQI7Dd4JAGAAR6BgAgFyUHgBlCGQYAHBwMcAwYdFpIIBWwaADAAWOn4FD+ILFgEEIK8AnAEDgRBGQYLCD8AjC/w+BYoIAC/jXDAAXAQY9ADoZBMAAhBHgDjBAQSzBRIYAFeoQADGQKDHgH8gBcCMoI9KgbjCwRBGkBfBUgX8uEHj+BX4f8v0AJAxBJpBBBL4JBBQZ8SII0EgIsDAQUDX4fHRgMAj5BF/0AcwyDBAAQUBIJ5fHQYKhBO4j1BXgXwnEcNwRBHkAjGo62DIIPjMoLFJ+EBL45BD/+BHwfgZYLpCjkP/DFIEY8/GYYXBfYYAJIJMkBgLmBEAQ0DIITCGIIcBU42TX4kBMoIAMhJBLEAg0DJQM4j/wYoXHB4IFBQZGBIIgAOiA+HAQSbBj6eEIQfx/8AuEHRIUAhwOBIJGRIKcCIJWQg7FDX4wsBv5EBJouAU5E/KYYAOHxBBDn5BFgb7DgEcPoJNGIJMAIIsggFAHw0B4EEIJeSQYx9C/+OnAIE/BNCCgIjJHYL7FpCMGgP4HxSkD//+QY/x4AJDh7IBJoPgiQjKiAVCBAVIYhCDOgeAII4IFwPH/+P/DpMHYRQDIJDgIAQtATYx6BjgHEIIP4SoRlNI45BVpDyBAAf8boLgBX4IADg8f/0BHyBrDIA0CLJ5BEg4FDh/AEQrPB5JBbgBBPYocOgEfwAFDII3+HyICCDgsHgESCJFBBApBEg8cAgMgQY0H//yIKl4IQvgIJICGHYUAgPH/gXBglwQY0cdJ4CFo5fF8C/KIIy8Dg/kIIMn+CnHHyQCCC4IdFwTFPn+OC4U/GoUjII0AghBbnAdKoAIGxwaBv/4JQZBHHyYCCp/gd4fACJOQYo0kh//gfwgIICng/FgPBIK1AIIcAhIRKNY+R/EfwAXDn+AEIccuPJIK1INAIeBuBBJkGQYo4aBkECBAZBBnCEEHygmDgFx4BrBBxFBkkAIJACBiQFDkaDCh/AjgLEASsCgEgHAQLFyCDBYpCVIP4RBdyDCJOgNBgiDLAQkncwQABjg+XaJpNBfYMQQaH+H4MB4HgIMgsBiUJgGAQZ8kxxBBh0AghBjQAICBoDFBQZ8kx/Aj/+gKAjkmCoKABAQMAhAXPyZBB+DCkAQMJkkCgEgwSDQyVIkA+kpMEiEIgACBkCDRAVB9BgMEYYMAQwJB4QYNAQYUEgTLBQfGAQIQABwA+2AQNIkiDCgEIZYMIIO8JQIkBgjC3AQNBgkQIISDByBB3HAKDEoKA3AQOCgEgIAWChJB6hCDEhBB5iFAQf6DDgMAgSD7YYcEiVJkBB3kGAIIaDBYvMgH4cIkGCaIpBypBBEAAI7zAQtBH4kBgkSYocgIOdIQQrFBoIOEI4YCuoBAEoKDBpMEBwTLzQY5BBHeICFIAqDByAODkBBywRAEgUBgi/zAQmQoBBDiFAPosEIOaDFhI7zAQpAEgkSH26DIBw4JBhEEiFIkGQAQRBmpA/EAAVAkGAhEAwUAA==")); + +var battery = E.getBattery(); + +// Positions on screen +const timeX = 88, timeY = 52; +const dateX = 5, dateY = 180; +const battX = 172, battY = 175; + +// Draw on every second if unlocked or charging, minute otherwise, start at with seconds on load +var drawTimeout; +var drawInterval = 1000; + +// schedule a draw for the next interval +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, drawInterval - (Date.now() % drawInterval)); +} + +// Update display and timeout on lock/unlock and charge state change +Bangle.on('lock',on=>{ + draw(); +}); + +Bangle.on('charging',charging=>{ + draw(); +}); + +function draw() { + // work out how to display the current time + var d = new Date(); + var h = d.getHours(), m = d.getMinutes(); + var time = (" "+h).substr(-2) + ":" + ("0"+m).substr(-2); + var seconds = ("0"+d.getSeconds()).substr(-2); + + // g.clear(); // Unneeded if background image takes the whole screen + + // Draw background + g.drawImage(img); + g.setColor(1, 1, 1); + + // draw the current time + g.setFontAlign(1,1); // align right bottom + g.setFont("6x15",3); + g.drawString(time, timeX, timeY, false); + + // Draw battery % + g.setFont("6x15",1); + var battStr = ""; + if(Bangle.isCharging()) { + battStr = "+"; + } + g.drawString(battStr + battery + "%", battX, battY, false); + + // Draw date + g.setFontAlign(-1,1); // align left bottom + g.setFont("6x15",2); + var dateStr = require("locale").date(d)+" "; + g.drawString(dateStr, dateX, dateY, false); + + // draw the seconds only if unlocked, set next timeout + if(!Bangle.isLocked() || Bangle.isCharging()) { + drawInterval = 1000; + g.setFont("6x15",2); + g.drawString(seconds, timeX+2, timeY-4, false); + } + else + drawInterval = 60000; + + // Schedule next draw + queueDraw(); + // console.log("Draw " + time + ":" + seconds); +} + +function refreshBattery() { + battery = E.getBattery(); +} + +// Only update displayed battery level every minute as it fluctuates a lot +var batteryInterval = setInterval(refreshBattery, 60000); + +Bangle.setUI("clock"); +Bangle.setLocked(false); +// Clear the screen once, at startup +g.clear(); +// draw immediately at first +draw(); diff --git a/apps/2ofthemclk/app.png b/apps/2ofthemclk/app.png new file mode 100644 index 000000000..d304f27d9 Binary files /dev/null and b/apps/2ofthemclk/app.png differ diff --git a/apps/2ofthemclk/bg.png b/apps/2ofthemclk/bg.png new file mode 100644 index 000000000..5f65ca3c7 Binary files /dev/null and b/apps/2ofthemclk/bg.png differ diff --git a/apps/2ofthemclk/metadata.json b/apps/2ofthemclk/metadata.json new file mode 100644 index 000000000..fa02b3e2f --- /dev/null +++ b/apps/2ofthemclk/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "2ofthemclk", + "name": "two of them clock", + "version": "0.01", + "description": "You can now wear teh memez on your wrist.", + "readme": "README.md", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"2ofthemclk.app.js","url":"app.js"}, + {"name":"2ofthemclk.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/2ofthemclk/screenshot.png b/apps/2ofthemclk/screenshot.png new file mode 100644 index 000000000..b9a80a2c5 Binary files /dev/null and b/apps/2ofthemclk/screenshot.png differ diff --git a/apps/7x7dotsclock/7x7dotsclock.app.js b/apps/7x7dotsclock/7x7dotsclock.app.js new file mode 100644 index 000000000..aa6672a4f --- /dev/null +++ b/apps/7x7dotsclock/7x7dotsclock.app.js @@ -0,0 +1,394 @@ +/* +7x7DotsClock + +by Peter Kuppelwieser + +*/ + +let settings = Object.assign({ swupApp: "",swdownApp: "", swleftApp: "", swrightApp: "", ColorMinutes: ""}, require("Storage").readJSON("7x7dotsclock.json", true) || {}); + +// position on screen +var Xs = 0, Ys = 30,Xe = 175, Ye=175; +//const Xs = 0, Ys = 0,Xe = 175, Ye=175; +var SegH = (Ye-Ys)/2,SegW = (Xe-Xs)/2; +var Dx = SegW/14, Dy = SegH/16; + +switch(settings.ColorMinutes) { +case "blue": + var mColor = [0.3,0.3,1]; + var sColor = [0,0,1]; + var sbColor = [1,1,1]; + break; +case "pink": + var mColor = [1,0.3,1]; + var sColor = [1,0,1]; + var sbColor = [1,1,1]; + break; +case "green": + var mColor = [0.3,1,0.3]; + var sColor = [0,1,0]; + var sbColor = [1,1,1]; + break; +case "yellow": + var mColor = [1,1,0.3]; + var sColor = [1,1,0]; + var sbColor = [0,0,0]; + break; +default: + var sColor = [0,0,1]; + var mColor = [0.3,0.3,1]; + var sbColor = [1,1,1]; +} +const bColor = [0.3,0.3,0.3]; + +const Font = [ + [ + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1], + [1,1,0,0,0,1,1], + [1,1,0,0,0,1,1], + [1,1,0,0,0,1,1], + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1] + ], + [ + [0,0,0,1,1,0,0], + [0,0,0,1,1,0,0], + [0,0,0,1,1,0,0], + [0,0,0,1,1,0,0], + [0,0,0,1,1,0,0], + [0,0,0,1,1,0,0], + [0,0,0,1,1,0,0], + ], + [ + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1], + [0,0,0,0,0,1,1], + [1,1,1,1,1,1,1], + [1,1,0,0,0,0,0], + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1] + ], + [ + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1], + [0,0,0,0,0,1,1], + [0,0,0,1,1,1,1], + [0,0,0,0,0,1,1], + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1] + ], + [ + [1,1,0,0,0,0,0], + [1,1,0,0,0,0,0], + [1,1,0,1,1,0,0], + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1], + [0,0,0,1,1,0,0], + [0,0,0,1,1,0,0] + ], + [ + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1], + [1,1,0,0,0,0,0], + [1,1,1,1,1,1,1], + [0,0,0,0,0,1,1], + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1] + ], + [ + [1,1,0,0,0,0,0], + [1,1,0,0,0,0,0], + [1,1,0,0,0,0,0], + [1,1,1,1,1,1,1], + [1,1,0,0,0,1,1], + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1] + ], + [ + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1], + [0,0,0,0,0,1,1], + [0,0,0,0,0,1,1], + [0,0,0,0,0,1,1], + [0,0,0,0,0,1,1], + [0,0,0,0,0,1,1] + ], + [ + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1], + [1,1,0,0,0,1,1], + [1,1,1,1,1,1,1], + [1,1,0,0,0,1,1], + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1] + ], + [ + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1], + [1,1,0,0,0,1,1], + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1], + [0,0,0,0,0,1,1], + [0,0,0,0,0,1,1] + ], + ]; + +// Global Vars +var dho = -1, eho = -1, dmo = -1, emo = -1; + + +function drawHSeg(x1,y1,x2,y2,Num,Color,Size) { + + + g.setColor(g.theme.bg); + g.fillRect(x1, y1, x2, y2); + for (let i = 1; i < 8; i++) { + for (let j = 1; j < 8; j++) { + if (Font[Num][j-1][i-1] == 1) { + if (Color == "fg") { + g.setColor(g.theme.fg); + } else { + g.setColor(mColor[0],mColor[1],mColor[2]); + } + g.fillCircle(x1+Dx+(i-1)*(x2-x1)/7,y1+Dy+(j-1)*(y2-y1)/7,Size); + } else { + g.setColor(bColor[0],bColor[1],bColor[2]); + g.fillCircle(x1+Dx+(i-1)*(x2-x1)/7,y1+Dy+(j-1)*(y2-y1)/7,1); + } + } + } +} + + +function drawSSeg(x1,y1,x2,y2,Num,Color,Size) { + for (let i = 1; i < 8; i++) { + for (let j = 1; j < 8; j++) { + if (Font[Num][j-1][i-1] == 1) { + if (Color == "fg") { + g.setColor(sColor[0],sColor[1],sColor[2]); + } else { + g.setColor(g.theme.fg); + //g.setColor(0.7,0.7,0.7); + } + g.fillCircle(x1+(i-1)*(x2-x1)/7,y1+(j-1)*(y2-y1)/7,Size); + } + } + } +} + + +function ShowSeconds() { + + g.setColor(sbColor[0],sbColor[1],sbColor[2]); + + g.fillRect((Xe-Xs) / 2 - 14 + Xs -4, + (Ye-Ys) / 2 - 7 + Ys -4, + (Xe-Xs) / 2 + 14 + Xs +4, + (Ye-Ys) / 2 + 7 + Ys +4); + + + drawSSeg( (Xe-Xs) / 2 - 14 + Xs -1, + (Ye-Ys) / 2 - 7 + Ys +1, + (Xe-Xs) / 2 + Xs -1, + (Ye-Ys) / 2 + 7 + Ys +1, + ds,"fg",1); + + drawSSeg( (Xe-Xs) / 2 + Xs +2, + (Ye-Ys) / 2 - 7 + Ys +1, + (Xe-Xs) / 2 + 14 + Xs +2, + (Ye-Ys) / 2 + 7 + Ys +1, + es,"fg",1); + +} + +function draw() { + // work out how to display the current time + var d = new Date(); + var h = d.getHours(), m = d.getMinutes(), s = d.getSeconds(); + + + dh = Math.floor(h/10); + eh = h - dh * 10; + + dm = Math.floor(m/10); + em = m - dm * 10; + + ds = Math.floor(s/10); + es = s - ds * 10; + + + // Reset the state of the graphics library + g.reset(); + if (dh != dho) { + g.setColor(1,1,1); + drawHSeg(Xs, Ys, Xs+SegW, Ys+SegH,dh,"fg",4); + dho = dh; + } + + if (eh != eho) { + g.setColor(1,1,1); + drawHSeg(Xs+SegW+Dx, Ys, Xs+SegW*2, Ys+SegH,eh,"fg",4); + eho = eh; + } + + if (dm != dmo) { + g.setColor(0.3,0.3,1); + drawHSeg(Xs, Ys+SegH+Dy, Xs+SegW, Ys+SegH*2,dm,"",4); + dmo = dm; + } + + if (em != emo) { + g.setColor(0.3,0.3,1); + drawHSeg(Xs+SegW+Dx, Ys+SegH+Dy, Xs+SegW*2, Ys+SegH*2,em,"",4); + emo = em; + } + + if (!Bangle.isLocked()) ShowSeconds(); + +} + + +function actions(v){ + if(BTN1.read() === true) { + print("BTN pressed"); + Bangle.showLauncher(); + } + + if(v==-1){ + print("up swipe event"); + if(settings.swupApp != "") load(settings.swupApp); + print(settings.swupApp); + } else if(v==1) { + print("down swipe event"); + if(settings.swdownApp != "") load(settings.swdownApp); + print(settings.swdownApp); + } else { + print("touch event"); + } +} + +// Get Messages status +var messages_installed = require("Storage").read("messages") !== undefined; + +//var BTconnected = NRF.getSecurityStatus().connected; +//NRF.on('connect',BTconnected = NRF.getSecurityStatus().connected); +//NRF.on('disconnect',BTconnected = NRF.getSecurityStatus().connected); + + +function drawWidgeds() { + + //Bluetooth + //print(BluetoothDevice.connected); + var x1Bt = 160; + var y1Bt = 0; + var x2Bt = x1Bt + 30; + var y2Bt = y2Bt; + + if (NRF.getSecurityStatus().connected) + g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f")); + else + g.setColor(g.theme.dark ? "#666" : "#999"); + g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),x1Bt,y1Bt); + + + //Battery + //print(E.getBattery()); + //print(Bangle.isCharging()); + + var x1B = 130; + var y1B = 2; + var x2B = x1B + 20; + var y2B = y1B + 15; + + g.setColor(g.theme.bg); + g.clearRect(x1B,y1B,x2B,y2B); + + g.setColor(g.theme.fg); + g.drawRect(x1B,y1B,x2B,y2B); + g.fillRect(x1B,y1B,x1B+(E.getBattery()*(x2B-x1B)/100),y2B); + g.fillRect(x2B,y1B+(y2B-y1B)/2-3,x2B+4,y1B+(y2B-y1B)/2+3); + + + + //Messages + + var x1M = 100; + var y1M = y1B; + var x2M = x1M + 25; + var y2M = y2B; + + if (messages_installed && require("messages").status() == "new") { + g.setColor(g.theme.fg); + g.fillRect(x1M,y1M,x2M,y2M); + g.setColor(g.theme.bg); + g.drawLine(x1M,y1M,x1M+(x2M-x1M)/2,y1M+(y2M-y1M)/2); + g.drawLine(x1M+(x2M-x1M)/2,y1M+(y2M-y1M)/2,x2M,y1M); + } + + var strDow = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']; + var d = new Date(); + var dow = d.getDay(),day = d.getDate(), month = d.getMonth() + 1, year = d.getFullYear(); + + print(strDow[dow] + ' ' + day + '.' + month + ' ' + year); + + g.setColor(g.theme.fg); + g.setFontAlign(-1, -1,0); + g.setFont("Vector", 20); + g.drawString(strDow[dow] + ' ' + day, 0, 0, true); + +} + + + + +function SetFull(on) { + dho = -1; eho = -1; dmo = -1; emo = -1; + g.clear(); + + if (on === true) { + Ys = 0; + Bangle.setUI("clock"); + Bangle.on('swipe', function(direction) { }); + + } else { + Ys = 30; + Bangle.setUI("updown",actions); + Bangle.on('swipe', function(direction) { + switch (direction) { + case 1: + print("swipe left event"); + if(settings.swleftApp != "") load(settings.swleftApp); + print(settings.swleftApp); + break; + case -1: + print("swipe right event"); + if(settings.swrightApp != "") load(settings.swrightApp); + print(settings.swrightApp); + break; + default: + print("swipe undefined event"); + } + }); + } + + SegH = (Ye-Ys)/2; + Dy = SegH/16; + + draw(); + + if (on != true) { + //Bangle.loadWidgets(); + //Bangle.drawWidgets(); + drawWidgeds(); + } +} + +Bangle.on('lock', function(on) { + SetFull(on); +}); + + +SetFull(Bangle.isLocked()); + +var secondInterval = setInterval(draw, 1000); diff --git a/apps/7x7dotsclock/7x7dotsclock.img.js b/apps/7x7dotsclock/7x7dotsclock.img.js new file mode 100644 index 000000000..b1f91c0bb --- /dev/null +++ b/apps/7x7dotsclock/7x7dotsclock.img.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkEBAkTmEzkAHDmcjmQBBmcTmICCgMAiMAkE/+P/mEQgMQgH/n/zAIP/l/yA4QvXC4kDkEjFgIACkcSmMTkMyBoQHBI4kvI6wXBn8wA4c/mfzl8y+cfEoIaBVa5HBAAMQF4UgIoIBBBgJNBAwQ3BkfygSnJSQIUBkECiBoCL48DmCPFAA6PCX40jX4hYEU4LNBX4JHIkBHCBgJHBianKj8wO4IvHgSnBmJ3CHYqGCABcRcYTXLAA5KCFAJfCC4KnDX4anNgUgiSnMkQQBO5hvCl8yO4pHEd4oyBH4QBBU5TXHkcimUTkLXFL44HEiTbBO4MhBoQHBI4KECR45HGBoIFBU4y/BC4c/mYXGMQJHFiBHLEAIHCf5gAKhWg1UB0IEBjUA0MB0EAjQKCiANCCQOg0cxmcSmWjU4MqmcDmSnDBASkBmejCQIXFmYXEmYXHicyhRLC0AEBAIJFBAIIFCBAYHDF65fXR66vImUCnS8IkeinUBgERgEgcIMBgRHDBgLvCBYMQmcjBYIAHfwL7JiQLBichkcSnUSO4MhI4MxI5MSmMjPgMinCnCkRHGIgJHFiUgkUalUCAgMRkUCkIvIkUSkMC0EiBxAAI0UKkBHCkCPDgA+CI5Z3BmYPBAB53CV4MSEgcSiCnOR4cyR5JQEgBHCC4I0BC4UjC4MCxQXGF4IlBxRHB0UAlUK0BMBkIEBI5ILB0ZHBF4czlTXHI4mjCQIXOH4KnDC4MKgGqgGgAgIBBIoJHJBoQ=")) diff --git a/apps/7x7dotsclock/7x7dotsclock.settings.js b/apps/7x7dotsclock/7x7dotsclock.settings.js new file mode 100644 index 000000000..34935d668 --- /dev/null +++ b/apps/7x7dotsclock/7x7dotsclock.settings.js @@ -0,0 +1,88 @@ +(function(back) { + +let settings = Object.assign({ swupApp: "",swdownApp: "", swleftApp: "", swrightApp: "",ColorMinutes: ""}, require("Storage").readJSON("7x7dotsclock.json", true) || {}); + + + +function setSetting(key,value) { + print("call " + key + " = " + value); + settings[key] = value; + + print("storing settings 7x7dotsclock.json"); + storage.write('7x7dotsclock.json', settings); +} + + + // Helper method which uses int-based menu item for set of string values + function stringItems(key, startvalue, values) { + return { + value: (startvalue === undefined ? 0 : values.indexOf(startvalue)), + format: v => values[v], + min: 0, + max: values.length - 1, + wrap: true, + step: 1, + onchange: v => { + setSetting(key,values[v]); + } + }; + } + + // Helper method which breaks string set settings down to local settings object + function stringInSettings(name, values) { + return stringItems(name,settings[name], values); + } + +function showMainMenu() { + const mainMenu = { + "": {"title": "7x7 Dots Clock Settings"}, + "< Back": ()=>load(), + "Minutes": stringInSettings("ColorMinutes", ["blue","pink","green","yellow"]), + "swipe-up": ()=>showSelAppMenu("swupApp"), + "swipe-down": ()=>showSelAppMenu("swdownApp"), + "swipe-left": ()=>showSelAppMenu("swleftApp"), + "swipe-right": ()=>showSelAppMenu("swrightApp") + + }; + + E.showMenu(mainMenu); +} + + +function showSelAppMenu(key) { + var Apps = require("Storage").list(/\.info$/) + .map(app => {var a=storage.readJSON(app, 1);return ( + a&&a.name != "Launcher" + && a&&a.name != "Bootloader" + && a&&a.type != "clock" + && a&&a.type !="widget" + )?a:undefined}) + .filter(app => app) // filter out any undefined apps + .sort((a, b) => a.sortorder - b.sortorder); + const SelAppMenu = { + '': { + 'title': /*LANG*/'Select App', + }, + '< Back': ()=>showMainMenu(), + }; + Apps.forEach((app, index) => { + var label = app.name; + if (settings[key] === app.src) { + label = "* " + label; + } + SelAppMenu[label] = () => { + if (settings[key] !== app.src) { + setSetting(key,app.src); + showMainMenu(); + } + }; + }); + if (Apps.length === 0) { + SelAppMenu[/*LANG*/"No Apps Found"] = () => { }; + } + return E.showMenu(SelAppMenu); +} + +showMainMenu(); + +}) diff --git a/apps/7x7dotsclock/ChangeLog b/apps/7x7dotsclock/ChangeLog new file mode 100644 index 000000000..5e8e48b0b --- /dev/null +++ b/apps/7x7dotsclock/ChangeLog @@ -0,0 +1,3 @@ +0.01: Initial version for upload +0.02: Better theme support, configurable colors, small improvements +0.03: Use `messages` library to check for new messages \ No newline at end of file diff --git a/apps/7x7dotsclock/README.md b/apps/7x7dotsclock/README.md new file mode 100644 index 000000000..28fcac1b1 --- /dev/null +++ b/apps/7x7dotsclock/README.md @@ -0,0 +1,15 @@ +# 7x7 dots clock + +![](dotsfontclock.png) + +* A Clock with big numbers made of 7x7 dots +* system widgeds ar not (yet) supported +* when screen is locked it shows hours and minutes in full screen mode +* adjustable color for minutes and seconds + +![](dotsfontclock-scr1.png) + +* when screen is unlocked it shows additional info: bluetooth, battery, new message state, date and seconds +* you can configure an app per swipe direction +* when swiping the configured apps are launched +* button press opens launcher diff --git a/apps/7x7dotsclock/dotsfontclock-scr1.png b/apps/7x7dotsclock/dotsfontclock-scr1.png new file mode 100644 index 000000000..5ab2e4863 Binary files /dev/null and b/apps/7x7dotsclock/dotsfontclock-scr1.png differ diff --git a/apps/7x7dotsclock/dotsfontclock-scr2.png b/apps/7x7dotsclock/dotsfontclock-scr2.png new file mode 100644 index 000000000..f301bb50c Binary files /dev/null and b/apps/7x7dotsclock/dotsfontclock-scr2.png differ diff --git a/apps/7x7dotsclock/dotsfontclock.png b/apps/7x7dotsclock/dotsfontclock.png new file mode 100644 index 000000000..af8fa61ba Binary files /dev/null and b/apps/7x7dotsclock/dotsfontclock.png differ diff --git a/apps/7x7dotsclock/metadata.json b/apps/7x7dotsclock/metadata.json new file mode 100644 index 000000000..ba1996544 --- /dev/null +++ b/apps/7x7dotsclock/metadata.json @@ -0,0 +1,19 @@ +{ "id": "7x7dotsclock", + "name": "7x7 Dots Clock", + "shortName":"7x7 Dots Clock", + "version":"0.03", + "description": "A clock with a big 7x7 dots Font", + "icon": "dotsfontclock.png", + "tags": "clock", + "type": "clock", + "supports" : ["BANGLEJS2"], + "allow_emulator": true, + "readme": "README.md", + "storage": [ + {"name":"7x7dotsclock.app.js","url":"7x7dotsclock.app.js"}, + {"name":"7x7dotsclock.settings.js","url":"7x7dotsclock.settings.js"}, + {"name":"7x7dotsclock.img","url":"7x7dotsclock.img.js","evaluate":true} + ], + "data": [{"name":"7x7dotsclock.json"}], + "screenshots": [{"url":"dotsfontclock.png"},{"url":"dotsfontclock-scr1.png"},{"url":"dotsfontclock-scr2.png"}] +} diff --git a/apps/90sclk/ChangeLog b/apps/90sclk/ChangeLog new file mode 100644 index 000000000..057d6ff73 --- /dev/null +++ b/apps/90sclk/ChangeLog @@ -0,0 +1,3 @@ +0.01: New App! +0.02: Fullscreen settings. +0.03: Tell clock widgets to hide. diff --git a/apps/90sclk/README.md b/apps/90sclk/README.md new file mode 100644 index 000000000..c09c6fe23 --- /dev/null +++ b/apps/90sclk/README.md @@ -0,0 +1,13 @@ +# 90s Clock + +A watch face in 90s style: + +![](screenshot_2.png) + +Fullscreen mode can be enabled in the settings: + +![](screenshot.png) + + +## Creator +- [David Peer](https://github.com/peerdavid) diff --git a/apps/90sclk/app-icon.js b/apps/90sclk/app-icon.js new file mode 100644 index 000000000..28f75c4e6 --- /dev/null +++ b/apps/90sclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgc8+fAgEgwAMDvPnz99BYdl2weHtu27ft2AGBiEcuEAhAPDg4jGgECIRMN23fthUNgP374vBAB3gAgc/gAXNjlx4EDxwJEpAjG/6IBjkBL4UAjVgBAJuCgPHBQMFEIkkyQjFhwEClgXBEYNBwkQJoibCBwNFBAUCEAVAQZAjC/8euPHDon//hKB//xEYMP//jBYP/+ARDNYM///+EYIgBj1B/8fCIUhEYQRB//FUIM/EZU4EYMkEYP/8VhEYUH/gRBWAUfI4MD+AjBoAsBwEH8EB/EDwE4HwYjCuEHWAOHgExEYKbBCIZNB8fAEYQHByE/EwPABAY+BgRHDBANyJQXHNwIjD8CSBj/+BwMSTwOOBYK2D/4CCNYZQB/iJBQwYjCCIcAgeBSoOAWYQjEVoIRCNAIjKAQKJBgAFC8ZoCWwJbDABMHGQPAAoMQB5EDx/4A4gqBZwIGCWwIABuBWC4EBZwPgv/AcwS/EAAcIU4IRBVQIRKEwIjBv0ARIUDCJIjD//x/ARK/5HC/+BCJkcI45uDgECUgQjCWAM4WwUBWYanEAA8cTARWBEYUC5RAHw1YgEOFQXADQPHIIkAhgICuARBh0A23blhHBagIKBsOGjNswhHDEYUUAoTUBhkxEYMwKwU503bvuwXILmCEYMYsumWYYjB85lDEYovBEYXm7fs25EBI4kYtOWNwIjD4+8NYsw4YjGz9/2hrEoOGjVBwE4NYdzNYSwBuEDEYcxaIUA8+atugGogjBiVgWAI")) diff --git a/apps/90sclk/app.js b/apps/90sclk/app.js new file mode 100644 index 000000000..351c235e0 --- /dev/null +++ b/apps/90sclk/app.js @@ -0,0 +1,145 @@ +const SETTINGS_FILE = "90sclk.setting.json"; +const locale = require('locale'); +const storage = require('Storage'); + + +/* + * Load settings + */ +let settings = { + fullscreen: false, +}; + +let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; +for (const key in saved_settings) { + settings[key] = saved_settings[key] +} + + +function getImg() { + return require("heatshrink").decompress(atob("2Gwgc8+fPAQnACY+ShAmQj9/8+evICF//evv3799FguCpMgAwdly1ZAQNbtu2II8EyRoR///23bEIICE//7AoewC4tJkmAAoSDPCggANg//QAwCBvv/QAKDIFgRuDQYZeDCY0BkiCRn53EAQlv/4IEMpGSpKDI+ASGhraEABkB/8eQY+fQZ0AgVJkGAQYTZKCIKCRv/4gaDJ/wFDtgcJhMkyCDGmAQFwVIICEcuJxBQY+ev6ACAQNxDxUgycly1bKpWSoBBQj7gCQZF/QZ4ABiUMuaDDggNFkmCICEH/gECQY+fQYtwEBcCgEGKoXYBgsBkmAIKE/8AEChqDH/6DEEZ/HjlwdIIAEhMgIKEB/4FEQYp9BQYncMyAuJCSMP/AGEmyDHAoaxGAE1/TwsHQYZ9B7/9QYcYIFcD/wHFgiDF76DEQVkAuBBGjyDBz6DG4xBtRhCDE//9AoWAIOsAQASDFSowAxhqDE9oFBIG4ABg6DC/4CBjAWOuPHIVMDtu///sCh//AAKFqgOH/iAPIARBrgE/IJ0fH4WOIFcAg5BMgPHH4PHH9iDC8ANLh5AB/xNI4D4WwAONeRkcv//SQkFAYUDJgJcMABFJKBwmMGgP4A4kEboUfBgLgMAA0EjVpQbILB//xXIkBQYU/ZwM4/4rNAAkkyVICBr4BBZLCBGQ8sBYSACj/wICFB02atLFPBRC2CjgUG4sDZwhBRgVJQaL4FAAS2BGogADhcsj7OEIJ8Bg0atKDBsB6NVoIIGuJABF5FxorOFIJ8JQAKDJn/gQZq2BfAYAGpcsZwpBOiCACQZPHQZsHIAKMHAAMUqNFBApBOQAYCCoCYNQYsPH4P+CZMC5csIKUBwyADAQVwIJsBPQccRIP8C5UcQaiAFQaX/8EAn7CCAoIAJ2SDSgkaQAoCBz1gQZ4AE4ASKgsUQaSAHAQRANAAKAD/+ACJckQaVhQAwCB0+cIJ8Hj//46VNoqDILJECQBFNmnSIJ4AQgMsQZ8BgyAFz1584CCQaBBRQaEJQA9JmnTQYPQIMELQZEH/gGEjCAEAQOnQc6ABQY8HQYqAGPoKACQccCpaDNgJ9BvKACPoaDmQASDIIIUCQAtJQAwCC4BBfiSABQZUEjVpQYaAIAQRAfQZc/8EAQB4CCwBBfQASDGgP/+FhOgNpPpKDlgqDJIIPzQAR9KAQpBfkiDK8+atOnQByDMkDQTgKADQY0JmmSQYKAOQZcBkhBUQAaDFiF5QCSDLhKDVsqDIgRuByaAQAQMwFZFJH6QABhaDJgFxQCQCB4gqHwVIIKiAEQYsAv6ARQZUEyVAICcCpaDKv/HQafAFQ0kwSCUPoWUQZPkQaYpGhMkICgYCQZfEjyDYgUJkA6PjgGFgNFiyDHgf/4EDQaHz54nFiVIIB8///wBI2SQY5BCgCDXkGShAtFiFBggIFv//AAKeFCYKDI/wDBhqDN588+fOEgkkyBvHpMkwAHDj4/B8YvCAA0kQYoUBKYMBQaFxEIcJkA2EDwMMmZUB+FABAMH//xFgN/QYwABgqDJgE8QBHzQAQCC9ghDpMgFIsJkiDCyRBCn//KQRBJgECpaDZuAZCwVIE4sBE4JUD4Y7B/8cBwRBKgFBQY0DboPzQBX79++/fgDAMEOoZpEQAQCDkf//AODvxAJAAxZBQwP/jyDRkmCEA1y5bYE58f+J9EQZZBHAAP4gVPQYKAE+aACAQTZCkggHQAgCBrNnwANDWYQAPuJAB+AbBuKDOgUJkAfGgmyLQoCBBwkf+BBQgE4egRyBEoqAEAQWAiVJDw8EQYuWrNkQYkfQaIAFgMHQZecsGShAZHjiAFAQUQQa5qGEQM+QAwCC0mQDBKDEkqDBsqDEeQJBXgEeQZUdkgtEQZ2wBoUHQbMAgaAIAQNJkAXJQAQCBQAKDILZIAQQZPCrBBKQZJ9Dg/8IDMAh6DH92SpAWKQYaACQY0/ILcBQY2cumXjgWKQZMwQb8AnyDF9Mk33gCpUEQYMlQYltQYgaLACEHQYnHpN1QwKDUU4f/IDYAB7aDD2VLt/+NBiDBQAQCBraDDh5BejyDCuPSrgFBChcEuaDKuJBegF/QYOkySGB/7xDABCADQYdgBYUP/BBeuPnjsl4+eQZsAQY8EMQfAGJzvMAAUB31JlqGB//+CZcD+yDDtu27ILD/5BPACGGpKABQZ0f+PHQYnDBYUcQaAAQyVL9/+///dhn/wEJQYNbQYKPETpgACiBAPkGCjyDPg/8G4McQYVwR4nwOJ4POgMkwDpBAASDLn/gbgdZsCPFIJ0EIJ0ChMgwEAQYbsKgP/EJaDLjl/DRgAEiVJAgUPQYQnKh6PLQYJhBTZAnCcALUPhB0DQZt/BZUAg4yJuI/B+PHIJ7UGLgQ1KvwhLIJMDEgPgSRgADhEkHAsHQZccuBBUg5AB44dDQRtJkjsH/wUJj/wERc/O4QADgJABC4iDNgVIkB3HQZRBMHAKDGv4IFahIAEiVAfaZlNQYyBB/APFIJkgyQLJfZJlNQYIFE4//+KkFDpkBkmQSJgAUIIKDDh6CBTA0HSQoAEgUJkCuMIK4ECjl//8cUI4bKgVJH8JBFg6BB/APHn6DKwUIIMscuJAB+PABw/HDRMEyRAjOgQ/BAALuJn4JIhEkwRAkOgI/BO5TUDAA1JkiClAAP//xQL/AJHgVIkBBnSRvABI8SoBA0gEPII8gyRA1QZEBkmQIO0OA40JkGAIOwAGgVJH/oAByVIIH0EyVAIHsIkmCQX0JkhA+QYMgC6scuPHIM1IC63/AAPgBhF/4CZwuI/B+PH/hB6gaAE/+AIPEHQAIyDj/wSRBBugKBBPokfQZAAvv//jgHEQZIAuj//WYyJFAGMH/56HRIoAxn/8BI6DRsBAjv//RhBKIABEcmAwRgQPOgf//BBasOGIMEHj/wBZJBQgccuPBIKGCBxs/fZU/8CDSjATQiQNMgP/jgLKFiCDBuHHEBIAGyQNMh/4JpbzRQYVlUhIAEgmQBpccv/AILkHhlxQYNwEZQACkhRM//wR6yDKAQMIe5kkMRn8BpaPLQZYCBCRcIkoOKWwPgDRUD/yCRQYtYsARK9MvdhV/LhkfaJaDNjkwOhHHpKSKOhxBTQYsYsuWB48P/dJGR8HMQUUQbUxQYlx4APFji2BpAdK/+AAgMKlmy5cs0QICAAIODQbEYBwv//0SoAbIgP/jgEBijmFMQcH/hASQYMwEAvHhgyD4/8uGSDhMP/AEC23btoCBQwUoBQM/8BBSQY1hy1ZsqhCh//EYJBKv53DL4oCCiiSBICaDJjlwoEcv/HCIPCDZED/wEClKDEtiDBQwOBIKkQQZOWgH/GQUEyAbIj/wAYNRL44CCvZBUgaDB4cMEY98uAQBkjMCAA38AYUKtKDJ//4IKaAEAQaDCrNsB4MBkjgJjiRCQBACCv/AG5MBeoSDSjkwgEJkAjIWYcCzSDJ2//oBBJh//eQaDMjKDCtuSIIJlJOIaAJAQMf/PFDhCPBIIP/SQuDQYcxExHChCDDg4ZDhwED0yDB0yDDQwf//coIA8/H4XjIIyDIjCDCAQPapCoJJQaDL//x44WGuI/B+I+Bv5BFQAKDNpg4EZJHmQYWbQYp3B5cs0AVEgZAB8AGCv/4QZ1hQYeSpGAIJaDL9/8AoMUCgkf/6MEQalw6YCBIJc586DCQAYCC//2QYyMB/wcEIIyDKwyDB22SQwIjDYg6AIAQX/AoYUCBAP8MoZBHgKDEmAmHmmHAoPDCgP/QZOeQYOaQYnf/+yQYOwCQMD///8AbEBAKDT0mZQYNZsCDUv/vQYkHj//44dGQZnDEw0kAoiDJvKDBvKDBtKDC2///qDC2WAn///wbGgYIGQZlpkiDDsuHII6DKPQQIDoP//lwDg0f+AjFQZkNmIvF76DR3//QAKDB2f///gDY8fQaUapMmQAKDCy1YQaXxAoceIIJAHQZ8MPoVw4dIQY3HQZfmQYOmQYP/7aDCAoP/4BBI/+AQaOapKADQav8Aofv/53FAAcHBY6DHmAgC4VMF4xoIQYfnQYXav6DBtuy76CBO4yDXyVJwyDEzxBHQZP/QYZGBjhAJg/8BAyDIAQWTF41/QaHb//27dsQIP92SDJII8EQZWkyQFBjKDCt/+Eo6DKAoX//YIBQbkcmgvGj/wExE5QYeeQYOf/+276CB7ct2BBJn/gQY0QQZMkzCDDw0A/5BIQZF/QYICB3gICQbkNkgsEuEHQaF503//qBB//27dt0CDctMkAodgCYP/wBoJQZAABBAcUQaMgQZUxEYcMTxLGDQYvm75BCtu2QaEDNYSDBoMGQYsapMmBAZcCQaVz/+8BAhAJNAQRCA4SAHAQVMuARBmHATxIAEQYvnzd982bQYVoIJaVB/AHDQZOSpIFCPoc/IJaDGAQ3FIJYOBNwSDL6QCBSokB/5BLgaDFzVp0yDC7QYKABCDNCQk/8B6CuAgHQZh0EAByDJycMmPDCIaDBAgX//wgHjyDHzSDZgiDE0mQAoIREIIKDCA==")); +} + +Graphics.prototype.setFontTime = function(scale) { + // Actual height 54 (56 - 3) + this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAAAPwAAAAAAAA/gAAAAAAAF/AAAAAAAA/8AAAAAAAD/wAAAAAAAP/AAAAAAAA/wAAAAAAAB/AAAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAB+AAAAAAAAf4AAAAAAAH/wAAAAAAB//AAAAAAAf/+AAAAAAH//8AAAAAB///wAAAAAf///gAAAAH///+AAAAB////4AAAAf///+AAAAH////gAAAB////8AAAA/////AAAAP////wAAAD////8AAAA/////AAAAP////gAAAD////4AAAA////+AAAAD////gAAAAP///4AAAAAf//+AAAAAB///gAAAAAD//4AAAAAAP/+AAAAAAAf/gAAAAAAB/wAAAAAAAH8AAAAAAAAPAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/wAAAAAAAf/wAAAAAAH//gAAAAAD///gAAAAAf///AAAAAB///+AAAAgP///8AAAHg////wAAAfn////gAAD/t////AAAP/j///8AAA//D///4AAH//D///gAAf//H//+AAB///H//4AAH//+P//gAAP//+P/+AAA///8f/4AAD///8f/gAAP///8f+AAAf///8/wAAB////w/AAAD////A4AAAH///4BgAAAP///gAAAAAf//8AAAAAA///AAAAAAA//wAAAAAAA/+AAAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAD+AAAAAAAAP4AAAAAAAB/gAAAAAAAP+AAAAAAAA/5//////4D/v//////gP+//////+A/7//////4D/v//////gP+//////+A/7//////4D/v//////gP+//////+Af7//////4A/v//////gA+//////+AA7///hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAD/4AAfwAAAf/gAD/AAAH/+AA/8AAA//4AH/wAAP//gA//AAD//+AH/8AA///4Af/wAP///gD//AD///+AP/8Af///4A//wH////gD//B////+AP/8f//7/4A//3///P/gD/////w/+AP////8D/4A/////AP/gD////4A/+AP///+AD/4Af///gAH/gB///4AAf/AD///AAB/8AH//wAAH/wAP/8AAAf/AAf/gAAB/4AA/4AAAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//gAAAf/wD/+AAAB//AP/4AAAH/8A//gAAAf/wD/+AAAB//AP/4AAAH/8A//gAAAf/wB/+AAAAAAAAAB/4A//8A////////wH////////Af///////8A////////wD///+////AP///7///8A////P///wB///8f//+AH///h///4AP//+D///AAf//wH//4AA//+AP//AAB//wAf/4AAD/+AAf/AAAD/gAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf+AAAAAAAH//AAAAAAA//+AAAAAAP//8AAAAAB///4AAAAAP///wAAAAA////gAAAAH////AAAAA////8AAAAD////4AAAAf////j//gB////+//+AP///////4A////////gD///////+Af///////4B////////gH///////+Af///////gB//////AAAH8AAAHAAAAAAA//wAAAAAAD//4AAAAAAP//gAAAAAA//+AAAAAAD//4AAAAAAP//gAAAAAA//+AAAAAAD//4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH////AB/AAf///+AP/+B////4A//4H////AD//gf///8AP/+B////4A//4H////gD//gf/////+AAAAAAAP///wP//gA////A//+AD///8D//4AP///wP//gAf//+A//+AB///4D//4AD///gAB/gAH//8AAAAAAP//gAAAAAAf/8AAAAAAA//gAAAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAAP/8AAAAAAB//8AAAAAAf//8AAAAAD///4AAAAAf///wAAAAD////wAAAAf////AAAAD////+AAAAP////8AAAB/////wAAAP/////gAAA/////+AAAH/////8AAAf/////wAAB//////gAAP/////+AAA//////4AAD//////gAAP/////+AAA//////4AAD//////gAAP//////AAA//////8AAAAf////wAAAAAAP//gAAAAB///+AAAAAH///4AAAAAf///gAAAAB///+AAAAAH///wAAAAAf///AAAAAA///8AAAAAD///gAAAAAH//+AAAAAAP//wAAAAAAf/+AAAAAAA//wAAAAAAB/+AAAAAAAB/gAAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/wAAAAAH///AAAAAAf//8AAAAAB///wAAAAAH///AAAAAAf//8AAAAAB///wAAAAAH///AAAAAAf//8AAAAAA//AAAAAAAAAAAAAAAAwAAP//////A////////8D////////wP////////A////////8D////////wP////////A////////8D////////wP////////A////////8D////////wP////////A////0cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAAD/gAH//AAA//gA//+AAP//AH//8AB//+A///4AP//8H///wA///4f///AH///j///+Af///P///4D///9////wP////////A////////8D////////wP///3///+AAAAAAAAAAB/wAAAAAAAP///x4AAAA////P///4D///8////gH///z///+Af///H///4B///8f///gD///g///8AH//8D///wAf//wH//+AA//+AP//wAA//gA//+AAA/4AA//gAAAEAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP4AAAAAAAH/8AAAAAAB//8AAAAAAP//4AAAAAB///wAAAAAP///gAAAAB////AAAAAP///8AAAAA////4AAAAH////gAAAAf////AAAAD////8AAAAP////wAAAA/////AAAAD////8AAAAH////wAAAAAAAAAAAAAD///+AAAAAP///////AA///////8AD///////wAP///////AA///////8AB///////wAH//////+AAf//////4AA///////gAD//////8AAH//////wAAf/////+AAA//////4AAB//////AAAH/////4AAAP/////AAAAf////4AAAA////+AAAAB////wAAAAB///8AAAAAB///AAAAAAB//wAAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AB8AAAAAH8AP4AAAAA/4B/wAAAAH/gO/AAAAAf+A/8AAAAB/4D/wAAAAH/AP/AAAAAP4AfwAAAAAfAA+AAAAAAQAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="), 46, atob("FionHyIiJiIyJScyFA=="), 58+(scale<<8)+(1<<16)); + return this; +}; + + +Graphics.prototype.setFontDate = function(scale) { + // Actual height 28 (27 - 0) + this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH///wf///B///8H///wf//+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAfwAAB+AAAAAAAAfwAAB/AAAH8AAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAADg8AAfH+AB//4A///gD//+AP//AA/w/gBPf+AB//4A///wB//8AH//AAfw8AAPDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAD4AAAfwAAD/h4B/+HwH/4/Af//+AP5/4A/n/gB8f8ADg/gAAD8AAADgAAAAAAAAAAAAAAAAAAAAAAAAB8AAAP4CAB/gcAH+D4Af4/wB/n+AH9/wAHv+AAB/wAAf8AAD/gAA/54AH/PwAf5/gB+H+ADw/4AEB/gAAH8AAAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAP4AB9/wAP//gB///AP//8A///4D///gH//+Af3/wAfP/AAAf8AAB/gAAP/AAB/8AAP/wAAfOAAAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+AAAP4AAA/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBgAH///g////n////f///9/////8AB/fgAH8AAAPAAAAAAAAAAAAAAAAAAAAAAD9/AAf38AB/f///9////z///+H///wP//8AAAAAAAAAAAAAAAAAAAAAAAAFAAAA+AAAH8AAAfwAAB/AAAD8AAAEAAAAAAAAAAAAAAAAAAAAAAAAfAAAB8AAAHwAAAfAAA//wAD//AAP/8AA//wAD//AAAfAAAB8AAAHwAAAfAAAAAAAAAAAAAAAAAAAAAAABkAAAPwAAA/AAAB4AAACAAAAAAAAAAAAAAAAAAAAAD4AAAPgAAA+AAAD4AAAPgAAA+AAAD4AAAPgAAA+AAAD4AAAPgAAA+AAAAAAAAAAAAAAAAAAAAAAACAAAAeAAAD4AAAPgAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAPAAAD+AAA/4AAP/wAD//AB//wAf/8AH//AB//wAf/8AA//AAD/wAAH8AAAeAAAAgAAAAAAAAAAAAAAAAAAAAAAAA8AAAP8AAD/4AEP/wAd//gD9//AP9/8A/9/wD/7/AP/78Af/7wA//CAB/8AAD/AAADwAAAAAAAAAAAAAAAAAAAABwAAAPAAAB8AAAH3//+ff//59///n3//+Pf//4d///gAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAD8B4AfwfgH/B+A/8P4P/w/j//D+//8P//vw//4/D/+B8H/gHwf8AfAfAA8AAAAAAAAAAAAAAfgAPx/AB/H8AH8fwAPwH/f/H///8f///x/+//H/7/8P/H/gf8P8A/APgAAAAAAAAAAAAAAAAAAAAAAAAD8AAA/8AAH/4AA//gAD//AAf/9/h///+H///4f///h//8AAD/gAAP+AAA/4AAD/gAAAAAAAAAAAAAAAAAAAAAAAAf/4PB//g/n/+D+f/4P5/gf/n/B/+f8H/5/wP/AAAf4AAA/AAAAAAAAAAAAAAAAAAAAAAAAAA8AAAP8AAD/8AAf/4AD//wAP//gB//+AH//8A///wD///AP//8A///wAAP/AAD/+AAH/4AAf/AAB/8AAD/gAAH+AAAPgAAAAAAAAAAAAAAAAAAAAeAAD/4AAP/gAA/+AAB/4AAAAAM+f///5////n///+f///5////n///+f5AAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AP4H+B/w/8P/n/4////n///+f///4AAAAH/9/+P///4/+f/j/5/+H/D/wH4H+AAAHgAAAAAAAAAAAAAAAAAAAA/AAAP/AAB/+AAP/4AA//wAH//AAf/8AB//wAH///wf///B///8H///wP//+A///4B///gD//8AP//gAP/4AAf+AAAPAAAAAAAAAAAAAAAAAAAAAAGAwAA8HgAH4+AAfD4AA4HAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAB4+AAPn4AA+PgABwcAAAAAAAAAAAAAAAAAAAAABgAAAOAAAB8AAAf4AAD/wAAf/AAD/+AAfz8AA8HwADgfgAEA+AAABgAAAAAAAAAAAAAAAAAAAAHj4AAfPgAB8+AAHz4AAfPgAB8+AAHz4AAfPgAB8+AAHz4AAfPgAB8+AAHj4AAAAAAAAAAAAAAAAAAAAAAAAAAAYAAQDwADgfgAfD8AB/fgAD/8AAH/wAAP+AAAfwAAA+AAABwAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAf8AAD/wAAf/gAB/+AAH/4AgA//3B///cH//9wf//3A//8YD/+AAH/4AAP+AAAPgAAAAAAAAAAAAAAAAAD8AAA/8AAH/4AA//wAH8fgAfu+AAf98AOf7wA///ADn/8AO//wA///AD//YAP/8AA/gMAB//wAD//AAH/4AAP/AAAPwAAAAAAAAAAAAAAAAAAAAP///h///+P///5////n///+f//AB+D8AH///+f///4////h///+B///4AAAAAAAAAAAAAAAAAAAAAAAf///j////P///8////z////P///8////z///AP///+f///5////D/5/8H/H/gPwH8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/gAA//gAH//AA//+AH//8A///4D///gf///B///8H///wf///B/gH8H+Afwf4B/BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH//8A////D///8P///w////B///8H///gP//+A///wB//+AD//wAD/8AAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAB////n///+f///5////n///+f///5////n/fP+f9+/5/37/n/AP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAf///5////n///+f///5////n///+f///5/z8AH/PwAf8AAAAAAAAAAAAAAAAAAAAAAA/gAAP/wAD//wAf//gD///Af//+B///4P///w////H///8f///x////H///8f4H/x/gf/D+B/8AAHAAAAcAAABwAAAHAAAAAAAAAAAAAAAAAAAAAAABgH///gf//+B///4H///gf//+B///4AB+AAf/9+B///4H///gf//+B///4H///gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8P///w////D///8P///w////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgAAAfAAAB+AAAH4AAAfgAAB+f///5////n///+f///x///8AAAAAAAAAAAAAAAAAAAAAAAAAAANH///8f///x////H///8f///x////H//+AAH/8AA//4AH//wA///gD//+Af//8B///wH///Af//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAX5////n///+f///5////v///+////5//7/gAAP+AAA/4AAD/gAAAAAAAAAAAAAAAAAAAAAAAAAAvh///+P///5////n///+f///5/wAAD+AAAP8AAAfwAAD/AAAP4AAB////n///+f///5////j///+H///4AAAAAAAAAAAAAAAAAAAAAAAH///gf//+B///4H///gf//+B///4B//4AB//4B///4H///gf//+B///4H///gf//+B///4AAAAAAAAAAAAAAAD/AAA//gAH//AA//+AH//8Af//wD///gf//+B///8H///wf///B/z/8H//wAf///B///8H///wf///A///8D///gH//8AP//wAf/+AA//gAA/4AAAAAAAAAAAAAAAAAA/x////H///8f///x////H///8f///x//gAH/+AAf/4AA//gAB/8AAD/gAAH8AAAAAAAAAAAAAAAAAAAAAAAcAAAP8AAD/8AA//8AH//4Af//wD///AP//+B///4H///wf///B///8H//+Af///B///8H///wf///A///8D///gH//+Af//4A///gB//+AB//4AB/PgAAAYAAAAAAAAAAAAAAAAAAAP///5////n///+f///5////n///+P///4////D///+P///4////h/9/+D/gAAD4AAAAAAAAAAAAAAAAAAAAAAAAAADwAAA/wAAH/gAA//AfH/8D8f/4f5////n+P/+f4P/5/g//j8D/8DgH/wAAP8AAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwAAAfAAAB8//8H///wf///B///8H///wf//AB8AAAHwAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAB///8H///8f///5////n///+AAAH5////n///+f///x////H///wf/gAAAAAAAAAAAAAAAAQAAAD8AAAP/AAA//wAH//8Af//+B////gf//+AP//4AP//gP//+P///5////n///gP//gA//AAD/gAAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///+D///8P///4////j///+PAAfwAAB/AAAH4AAAfw////j//++P//74///vj///8AAB7AAAAAAAAAAAAAAAAAAAAAAAAAAABgeAA+B/AP8P/n/w////j///+H///gH//4D///8P///4////h/4/8H8AfwPAAOAgAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAHwAAAfgAAB/AAAH8H/wf///B///8H///wf///B///8H/8cAfwAAB+AAAHwAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAABwAAAHAAD8cAD/xwD//HH//+f///5////n///+P//w4f/wDh/gAOHgAA4AAADgAAAOAAAAAAAAAAAAAA///+////////////////////////+AAA/wAAD/gAAP+AAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAA/AAAD/AAAf/AAB//gAD//gAD//gAD//gAD//gAD//gAD//AAD/4AAB/AAAB8AAABgAAAAAAAAAAAAAAAAAAOAAAe4AAB7gAAHuAAAe////7////v///+////7////v///YwAAAAAAAA="), 32, atob("HA4NFhEaFwoNDQsRCRALFBMPEBESEBgSExgKCRARERIXEhQVExEPGBMOERYRFhQaExwUExARFRYTFhMPFA4="), 28+(scale<<8)+(1<<16)); + return this; +} + + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + + +function drawBorderString(str, x, y, b, fc){ + g.setColor("#000"); + g.drawString(str, x, y+b); + g.drawString(str, x, y-b); + g.drawString(str, x+b, y); + g.drawString(str, x-b, y); + + g.setColor(fc); + g.drawString(str,x+1,y); +} + + +function getSteps() { + try{ + if (WIDGETS.wpedom !== undefined) { + return WIDGETS.wpedom.getSteps(); + } else if (WIDGETS.activepedom !== undefined) { + return WIDGETS.activepedom.getSteps(); + } + } catch(ex) { + // In case we failed, we can only show 0 steps. + } + + return 0; +} + + +function draw() { + // queue draw in one minute + queueDraw(); + + var x = g.getWidth()/2; + var y_offset = settings.fullscreen ? 0 : 10; + var y = g.getHeight()/2-20 + y_offset; + + g.reset().clearRect(0,24,g.getWidth(),g.getHeight()); + g.drawImage(getImg(),0,0); + + // Draw time + var date = new Date(); + var timeStr = locale.time(date,1); + g.setFontAlign(0,0); + g.setFontTime(); + drawBorderString(timeStr, x, y, 5, "#fff"); + + // Draw date + y += 50; + x = x - g.stringWidth(timeStr) / 2 + 5; + g.setFontDate(); + g.setFontAlign(-1,0); + var dateStr = locale.dow(date, true).toUpperCase() + date.getDate(); + var fc = Bangle.isLocked() ? "#0ff" :"#fff"; + fc = E.getBattery() < 50 ? "#f00" : fc; + drawBorderString(dateStr, x, y, 3, fc); + + // Draw steps + g.setFontAlign(1,1); + var steps = parseInt(getSteps() / 1000); + drawBorderString(steps, g.getWidth()-10, g.getHeight()-10, 3, "#f0f"); + + // Draw widgets if not fullscreen + if(settings.fullscreen){ + for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} + } else { + Bangle.drawWidgets(); + } +} + +// Show launcher when middle button pressed +Bangle.setUI("clock"); + +Bangle.loadWidgets(); + +// Clear the screen once, at startup +g.setTheme({bg:"#000",fg:"#fff",dark:false}).clear(); +// draw immediately at first, queue update +draw(); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + + +Bangle.on('lock', function(isLocked) { + print("LOCK"); + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + draw(); +}); + + diff --git a/apps/90sclk/app.png b/apps/90sclk/app.png new file mode 100644 index 000000000..29875b1dc Binary files /dev/null and b/apps/90sclk/app.png differ diff --git a/apps/90sclk/bg.png b/apps/90sclk/bg.png new file mode 100644 index 000000000..4ebf755ad Binary files /dev/null and b/apps/90sclk/bg.png differ diff --git a/apps/90sclk/metadata.json b/apps/90sclk/metadata.json new file mode 100644 index 000000000..59b627427 --- /dev/null +++ b/apps/90sclk/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "90sclk", + "name": "90s Clock", + "version": "0.03", + "description": "A 90s style watch-face", + "readme": "README.md", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"},{"url":"screenshot_2.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"90sclk.app.js","url":"app.js"}, + {"name":"90sclk.img","url":"app-icon.js","evaluate":true}, + {"name":"90sclk.settings.js","url":"settings.js"} + ] +} diff --git a/apps/90sclk/screenshot.png b/apps/90sclk/screenshot.png new file mode 100644 index 000000000..182a85321 Binary files /dev/null and b/apps/90sclk/screenshot.png differ diff --git a/apps/90sclk/screenshot_2.png b/apps/90sclk/screenshot_2.png new file mode 100644 index 000000000..9646b1168 Binary files /dev/null and b/apps/90sclk/screenshot_2.png differ diff --git a/apps/90sclk/settings.js b/apps/90sclk/settings.js new file mode 100644 index 000000000..8f97cd317 --- /dev/null +++ b/apps/90sclk/settings.js @@ -0,0 +1,31 @@ +(function(back) { + const SETTINGS_FILE = "90sclk.setting.json"; + + // initialize with default settings... + const storage = require('Storage') + let settings = { + fullscreen: false, + }; + let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; + for (const key in saved_settings) { + settings[key] = saved_settings[key] + } + + function save() { + storage.write(SETTINGS_FILE, settings) + } + + + E.showMenu({ + '': { 'title': '90s Clock' }, + '< Back': back, + 'Full Screen': { + value: settings.fullscreen, + format: () => (settings.fullscreen ? 'Yes' : 'No'), + onchange: () => { + settings.fullscreen = !settings.fullscreen; + save(); + }, + } + }); + }) diff --git a/apps/UI4swatch/Changelog b/apps/UI4swatch/ChangeLog similarity index 100% rename from apps/UI4swatch/Changelog rename to apps/UI4swatch/ChangeLog diff --git a/apps/_example_app/app.js b/apps/_example_app/app.js index af367779a..06c254a36 100644 --- a/apps/_example_app/app.js +++ b/apps/_example_app/app.js @@ -1,12 +1,34 @@ // 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 - // If you clear the screen, do Bangle.drawWidgets(); +// clear the screen +g.clear(); + +var n = 0; + +// redraw the screen +function draw() { + g.reset().clearRect(Bangle.appRect); + g.setFont("6x8").setFontAlign(0,0).drawString("Up / Down",g.getWidth()/2,g.getHeight()/2 - 20); + g.setFont("Vector",60).setFontAlign(0,0).drawString(n,g.getWidth()/2,g.getHeight()/2 + 30); +} + +// Respond to user input +Bangle.setUI({mode: "updown"}, function(dir) { + if (dir<0) { + n--; + draw(); + } else if (dir>0) { + n++; + draw(); + } else { + n = 0; + draw(); } }); -g.clear(); -// call your app function here +// First draw... +draw(); + +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/_example_widget/widget.js b/apps/_example_widget/widget.js index f7aed6991..226aea589 100644 --- a/apps/_example_widget/widget.js +++ b/apps/_example_widget/widget.js @@ -9,7 +9,7 @@ currently-running apps */ // add your widget WIDGETS["mywidget"]={ - area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right) + area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right), be aware that not all apps support widgets at the bottom of the screen width: 28, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout draw:draw // called to draw the widget }; diff --git a/apps/a_dndtoggle/ChangeLog b/apps/a_dndtoggle/ChangeLog new file mode 100644 index 000000000..ec66c5568 --- /dev/null +++ b/apps/a_dndtoggle/ChangeLog @@ -0,0 +1 @@ +0.01: Initial version diff --git a/apps/a_dndtoggle/README.md b/apps/a_dndtoggle/README.md new file mode 100644 index 000000000..bd0981c5b --- /dev/null +++ b/apps/a_dndtoggle/README.md @@ -0,0 +1,13 @@ +# a_dndtoggle - Toggle Quiet Mode of the watch + +When Quiet mode is off, just start this app to set quiet mode. Start it again to turn off quiet mode. +Work in progress. + +#ToDo +Settings page, current status indicator. + +## Creator + +Hank - contact at http://forum.espruino.com + + diff --git a/apps/a_dndtoggle/a_dndtoggle.app.js b/apps/a_dndtoggle/a_dndtoggle.app.js new file mode 100644 index 000000000..c0b968f2c --- /dev/null +++ b/apps/a_dndtoggle/a_dndtoggle.app.js @@ -0,0 +1,43 @@ + +const modeNames = [/*LANG*/"Noisy", /*LANG*/"Alarms", /*LANG*/"Silent"]; +let bSettings = require('Storage').readJSON('setting.json',true)||{}; +let current = 0|bSettings.quiet; +//0 off +//1 alarms +//2 silent + +console.log("old: " + current); + +switch (current) { + case 0: + bSettings.quiet = 2; + Bangle.buzz(); + setTimeout('Bangle.buzz();',500); + break; + case 1: + bSettings.quiet = 0; + Bangle.buzz(); + break; + case 2: + bSettings.quiet = 0; + Bangle.buzz(); + break; + default: + bSettings.quiet = 0; + Bangle.buzz(); +} + +console.log("new: " + bSettings.quiet); + +E.showMessage(modeNames[current] + " -> " + modeNames[bSettings.quiet]); +setTimeout('exitApp();', 2000); + + +function exitApp(){ + +require("Storage").writeJSON("setting.json", bSettings); +// reload clocks with new theme, otherwise just wait for user to switch apps + +load() + +} \ No newline at end of file diff --git a/apps/a_dndtoggle/a_dndtoggle.png b/apps/a_dndtoggle/a_dndtoggle.png new file mode 100644 index 000000000..4c8b74c0c Binary files /dev/null and b/apps/a_dndtoggle/a_dndtoggle.png differ diff --git a/apps/a_dndtoggle/app-icon.js b/apps/a_dndtoggle/app-icon.js new file mode 100644 index 000000000..0b08cc65b --- /dev/null +++ b/apps/a_dndtoggle/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwJC/AAl/Agf/AAUAgIFDwEHAofgh/g/0Ag/wj+AnwVB/EegEfEIN4nkAh+AgE8vgVBAoV4Aoce/EAgfADQIFcjwpFHYIFCnxBFJopZBn5ZCMopxFPoqJFSowA/gA=")) \ No newline at end of file diff --git a/apps/a_dndtoggle/metadata.json b/apps/a_dndtoggle/metadata.json new file mode 100644 index 000000000..f5ae9cc31 --- /dev/null +++ b/apps/a_dndtoggle/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "a_dndtoggle", + "name": "a_dndtoggle - Toggle Quiet Mode of the watch", + "shortName": "A_DND Toggle", + "version": "0.01", + "description": "Toggle Quiet Mode of the watch just by starting this app.", + "icon": "a_dndtoggle.png", + "type": "app", + "tags": "tool", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"a_dndtoggle.app.js","url":"a_dndtoggle.app.js"}, + {"name":"a_dndtoggle.img","url":"app-icon.js","evaluate":true} + ], + "readme": "README.md" +} diff --git a/apps/about/ChangeLog b/apps/about/ChangeLog index f5638fdd2..e236e4b34 100644 --- a/apps/about/ChangeLog +++ b/apps/about/ChangeLog @@ -10,3 +10,5 @@ 0.10: Added separate Bangle.js 2 file with Bangle.js 2 kickstarter pixels (as of 28 Oct 2021) 0.11: Bangle.js2: New pixels, btn1 to exit 0.12: Actual pixels as of 29th Nov 2021 +0.13: Bangle.js 2: Use setUI to add software back button +0.14: Add automatic translation of more strings diff --git a/apps/about/app-bangle1.js b/apps/about/app-bangle1.js index 28a292376..dd94c1e84 100644 --- a/apps/about/app-bangle1.js +++ b/apps/about/app-bangle1.js @@ -11,8 +11,8 @@ g.drawString("BANGLEJS.COM",120,y-4); } else { y=-(4+h); // small screen, start right at top } -g.drawString("Powered by Espruino",0,y+=4+h); -g.drawString("Version "+ENV.VERSION,0,y+=h); +g.drawString(/*LANG*/"Powered by Espruino",0,y+=4+h); +g.drawString(/*LANG*/"Version "+ENV.VERSION,0,y+=h); g.drawString("Commit "+ENV.GIT_COMMIT,0,y+=h); function getVersion(name,file) { var j = s.readJSON(file,1); @@ -24,9 +24,9 @@ getVersion("Launcher","launch.info"); getVersion("Settings","setting.info"); y+=h; -g.drawString(MEM.total+" JS Variables available",0,y+=h); -g.drawString("Storage: "+(require("Storage").getFree()>>10)+"k free",0,y+=h); -if (ENV.STORAGE) g.drawString(" "+(ENV.STORAGE>>10)+"k total",0,y+=h); +g.drawString(MEM.total+/*LANG*/" JS Variables available",0,y+=h); +g.drawString("Storage: "+(require("Storage").getFree()>>10)+/*LANG*/"k free",0,y+=h); +if (ENV.STORAGE) g.drawString(" "+(ENV.STORAGE>>10)+/*LANG*/"k total",0,y+=h); if (ENV.SPIFLASH) g.drawString("SPI Flash: "+(ENV.SPIFLASH>>10)+"k",0,y+=h); g.setFontAlign(0,-1); g.flip(); diff --git a/apps/about/app-bangle2.js b/apps/about/app-bangle2.js index 978d36193..ccffd183f 100644 --- a/apps/about/app-bangle2.js +++ b/apps/about/app-bangle2.js @@ -10,7 +10,7 @@ var img = atob("sIwDkm2S66DYwA2AAAAAHAHGSRxJEkAAgmGGBxDIADIdAFJIbAHF9HP00kBUC6Dt var imgHeight = g.imageMetrics(img).height; var imgScroll = Math.floor(Math.random()*imgHeight); -g.reset().setFont("6x15").setFontAlign(0,0); +g.clear(1).setFont("6x15").setFontAlign(0,0); g.drawString(ENV.VERSION + " " + NRF.getAddress(), g.getWidth()/2, 171); g.drawImage(img,0,24); @@ -35,17 +35,17 @@ function drawInfo() { g.setFont("4x6").setFontAlign(0,0).drawString("BANGLEJS.COM",W-30,56); var h=8, y = 24-h; g.setFont("6x8").setFontAlign(-1,-1); - g.drawString("Powered by Espruino",0,y+=4+h); - g.drawString("Version "+ENV.VERSION,0,y+=h); + g.drawString(/*LANG*/"Powered by Espruino",0,y+=4+h); + g.drawString(/*LANG*/"Version "+ENV.VERSION,0,y+=h); g.drawString("Commit "+ENV.GIT_COMMIT,0,y+=h); getVersion("Bootloader","boot.info"); getVersion("Launcher","launch.info"); getVersion("Settings","setting.info"); - g.drawString(MEM.total+" JS Vars",0,y+=h); - g.drawString("Storage: "+(require("Storage").getFree()>>10)+"k free",0,y+=h); - if (ENV.STORAGE) g.drawString(" "+(ENV.STORAGE>>10)+"k total",0,y+=h); + g.drawString(MEM.total+/*LANG*/" JS Vars",0,y+=h); + g.drawString("Storage: "+(require("Storage").getFree()>>10)+/*LANG*/"k free",0,y+=h); + if (ENV.STORAGE) g.drawString(" "+(ENV.STORAGE>>10)+/*LANG*/"k total",0,y+=h); if (ENV.SPIFLASH) g.drawString("SPI Flash: "+(ENV.SPIFLASH>>10)+"k",0,y+=h); imageTop = y+h; imgScroll = imgHeight-imageTop; @@ -69,4 +69,7 @@ function drawImage() { // TODO: a nice little animation before setTimeout(drawInfo, 1000); -setWatch(_=>load(), BTN1); +Bangle.setUI({ + mode : "custom", + back : load +}); diff --git a/apps/about/metadata.json b/apps/about/metadata.json index 6c22bdc56..52cd37b7d 100644 --- a/apps/about/metadata.json +++ b/apps/about/metadata.json @@ -1,7 +1,7 @@ { "id": "about", "name": "About", - "version": "0.12", + "version": "0.14", "description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers", "icon": "app.png", "tags": "tool,system", diff --git a/apps/accellog/ChangeLog b/apps/accellog/ChangeLog index c0097db80..94241c7a7 100644 --- a/apps/accellog/ChangeLog +++ b/apps/accellog/ChangeLog @@ -1,3 +1,5 @@ 0.01: New App! 0.02: Use the new multiplatform 'Layout' library 0.03: Exit as first menu option, dont show decimal places for seconds +0.04: Localisation, change Exit->Back to allow back-arrow to appear on 2v13 firmware +0.05: Add max G values during recording, record actual G values and magnitude to CSV diff --git a/apps/accellog/app.js b/apps/accellog/app.js index c54c5002b..147f7503f 100644 --- a/apps/accellog/app.js +++ b/apps/accellog/app.js @@ -1,5 +1,6 @@ var fileNumber = 0; var MAXLOGS = 9; +var logRawData = false; function getFileName(n) { return "accellog."+n+".csv"; @@ -7,29 +8,34 @@ function getFileName(n) { function showMenu() { var menu = { - "" : { title : "Accel Logger" }, - "Exit" : function() { + "" : { title : /*LANG*/"Accel Logger" }, + "< Back" : function() { load(); }, - "File No" : { + /*LANG*/"File No" : { value : fileNumber, min : 0, max : MAXLOGS, onchange : v => { fileNumber=v; } }, - "Start" : function() { + /*LANG*/"Start" : function() { E.showMenu(); startRecord(); }, - "View Logs" : function() { + /*LANG*/"View Logs" : function() { viewLogs(); }, + /*LANG*/"Log raw data" : { + value : logRawData, + format : v => v?/*LANG*/"Yes":/*LANG*/"No", + onchange : v => { logRawData=v; } + }, }; E.showMenu(menu); } function viewLog(n) { - E.showMessage("Loading..."); + E.showMessage(/*LANG*/"Loading..."); var f = require("Storage").open(getFileName(n), "r"); var records = 0, l = "", ll=""; while ((l=f.readLine())!==undefined) {records++;ll=l;} @@ -37,29 +43,29 @@ function viewLog(n) { if (ll) length = Math.round( (ll.split(",")[0]|0)/1000 ); var menu = { - "" : { title : "Log "+n } + "" : { title : "Log "+n }, + "< Back" : () => { viewLogs(); } }; - menu[records+" Records"] = ""; - menu[length+" Seconds"] = ""; - menu["DELETE"] = function() { - E.showPrompt("Delete Log "+n).then(ok=>{ + menu[records+/*LANG*/" Records"] = ""; + menu[length+/*LANG*/" Seconds"] = ""; + menu[/*LANG*/"DELETE"] = function() { + E.showPrompt(/*LANG*/"Delete Log "+n).then(ok=>{ if (ok) { - E.showMessage("Erasing..."); + E.showMessage(/*LANG*/"Erasing..."); f.erase(); viewLogs(); } else viewLog(n); }); }; - menu["< Back"] = function() { - viewLogs(); - }; + E.showMenu(menu); } function viewLogs() { var menu = { - "" : { title : "Logs" } + "" : { title : /*LANG*/"Logs" }, + "< Back" : () => { showMenu(); } }; var hadLogs = false; @@ -67,23 +73,23 @@ function viewLogs() { var f = require("Storage").open(getFileName(i), "r"); if (f.readLine()!==undefined) { (function(i) { - menu["Log "+i] = () => viewLog(i); + menu[/*LANG*/"Log "+i] = () => viewLog(i); })(i); hadLogs = true; } } if (!hadLogs) - menu["No Logs Found"] = function(){}; - menu["< Back"] = function() { showMenu(); }; + menu[/*LANG*/"No Logs Found"] = function(){}; E.showMenu(menu); } function startRecord(force) { + var stopped = false; if (!force) { // check for existing file var f = require("Storage").open(getFileName(fileNumber), "r"); if (f.readLine()!==undefined) - return E.showPrompt("Overwrite Log "+fileNumber+"?").then(ok=>{ + return E.showPrompt(/*LANG*/"Overwrite Log "+fileNumber+"?").then(ok=>{ if (ok) startRecord(true); else showMenu(); }); } @@ -93,39 +99,101 @@ function startRecord(force) { var Layout = require("Layout"); var layout = new Layout({ type: "v", c: [ - {type:"txt", font:"6x8", label:"Samples", pad:2}, - {type:"txt", id:"samples", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg}, - {type:"txt", font:"6x8", label:"Time", pad:2}, - {type:"txt", id:"time", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg}, - {type:"txt", font:"6x8:2", label:"RECORDING", bgCol:"#f00", pad:5, fillx:1}, - ] - },{btns:[ // Buttons... - {label:"STOP", cb:()=>{ - Bangle.removeListener('accel', accelHandler); - showMenu(); + { type: "h", c: [ + { type: "v", c: [ + {type:"txt", font:"6x8", label:/*LANG*/"Samples", pad:2}, + {type:"txt", id:"samples", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg}, + ]}, + { type: "v", c: [ + {type:"txt", font:"6x8", label:/*LANG*/"Time", pad:2}, + {type:"txt", id:"time", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg}, + ]}, + ]}, + { type: "h", c: [ + { type: "v", c: [ + {type:"txt", font:"6x8", label:/*LANG*/"Max X", pad:2}, + {type:"txt", id:"maxX", font:"6x8", label:" - ", pad:5, bgCol:g.theme.bg}, + ]}, + { type: "v", c: [ + {type:"txt", font:"6x8", label:/*LANG*/"Max Y", pad:2}, + {type:"txt", id:"maxY", font:"6x8", label:" - ", pad:5, bgCol:g.theme.bg}, + ]}, + { type: "v", c: [ + {type:"txt", font:"6x8", label:/*LANG*/"Max Z", pad:2}, + {type:"txt", id:"maxZ", font:"6x8", label:" - ", pad:5, bgCol:g.theme.bg}, + ]}, + ]}, + {type:"txt", font:"6x8", label:/*LANG*/"Max G", pad:2}, + {type:"txt", id:"maxMag", font:"6x8:4", label:" - ", pad:5, bgCol:g.theme.bg}, + {type:"txt", id:"state", font:"6x8:2", label:/*LANG*/"RECORDING", bgCol:"#f00", pad:5, fillx:1}, + ]}, + { + btns:[ // Buttons... + {id: "btnStop", label:/*LANG*/"STOP", cb:()=>{ + if (stopped) { + showMenu(); + } + else { + Bangle.removeListener('accel', accelHandler); + layout.state.label = /*LANG*/"STOPPED"; + layout.state.bgCol = /*LANG*/"#0f0"; + stopped = true; + layout.render(); + } }} ]}); layout.render(); // now start writing var f = require("Storage").open(getFileName(fileNumber), "w"); - f.write("Time (ms),X,Y,Z\n"); + f.write("Time (ms),X,Y,Z,Total\n"); var start = getTime(); var sampleCount = 0; + var maxMag = 0; + var maxX = 0; + var maxY = 0; + var maxZ = 0; function accelHandler(accel) { var t = getTime()-start; - f.write([ - t*1000, - accel.x*8192, - accel.y*8192, - accel.z*8192].map(n=>Math.round(n)).join(",")+"\n"); + if (logRawData) { + f.write([ + t*1000, + accel.x*8192, + accel.y*8192, + accel.z*8192, + accel.mag*8192, + ].map(n=>Math.round(n)).join(",")+"\n"); + } else { + f.write([ + Math.round(t*1000), + accel.x, + accel.y, + accel.z, + accel.mag, + ].join(",")+"\n"); + } + if (accel.mag > maxMag) { + maxMag = accel.mag.toFixed(2); + } + if (accel.x > maxX) { + maxX = accel.x.toFixed(2); + } + if (accel.y > maxY) { + maxY = accel.y.toFixed(2); + } + if (accel.z > maxZ) { + maxZ = accel.z.toFixed(2); + } sampleCount++; layout.samples.label = sampleCount; layout.time.label = Math.round(t)+"s"; - layout.render(layout.samples); - layout.render(layout.time); + layout.maxX.label = maxX; + layout.maxY.label = maxY; + layout.maxZ.label = maxZ; + layout.maxMag.label = maxMag; + layout.render(); } Bangle.setPollInterval(80); // 12.5 Hz - the default diff --git a/apps/accellog/metadata.json b/apps/accellog/metadata.json index a30c9a6fc..903c57903 100644 --- a/apps/accellog/metadata.json +++ b/apps/accellog/metadata.json @@ -2,7 +2,7 @@ "id": "accellog", "name": "Acceleration Logger", "shortName": "Accel Log", - "version": "0.03", + "version": "0.05", "description": "Logs XYZ acceleration data to a CSV file that can be downloaded to your PC", "icon": "app.png", "tags": "outdoor", diff --git a/apps/aclock/metadata.json b/apps/aclock/metadata.json index c483a4e8c..5e4b4b680 100644 --- a/apps/aclock/metadata.json +++ b/apps/aclock/metadata.json @@ -8,6 +8,7 @@ "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"aclock.app.js","url":"clock-analog.js"}, diff --git a/apps/activepedom/README.md b/apps/activepedom/README.md index ac32a1dd6..06ad280ee 100644 --- a/apps/activepedom/README.md +++ b/apps/activepedom/README.md @@ -1,6 +1,11 @@ # Active Pedometer + Pedometer that filters out arm movement and displays a step goal progress. +**Note:** Since creation of this app, Bangle.js's step counting algorithm has +improved significantly - and as a result the algorithm in this app (which + runs *on top* of Bangle.js's algorithm) may no longer be accurate. + I changed the step counting algorithm completely. Now every step is counted when in status 'active', if the time difference between two steps is not too short or too long. To get in 'active' mode, you have to reach the step threshold before the active timer runs out. @@ -9,6 +14,7 @@ When you reach the step threshold, the steps needed to reach the threshold are c Steps are saved to a datafile every 5 minutes. You can watch a graph using the app. ## Screenshots + * 600 steps ![](600.png) @@ -70,4 +76,4 @@ Steps are saved to a datafile every 5 minutes. You can watch a graph using the a ## Requests -If you have any feature requests, please post in this forum thread: http://forum.espruino.com/conversations/345754/ \ No newline at end of file +If you have any feature requests, please post in this forum thread: http://forum.espruino.com/conversations/345754/ diff --git a/apps/activepedom/metadata.json b/apps/activepedom/metadata.json index 4deb7006d..81bafb573 100644 --- a/apps/activepedom/metadata.json +++ b/apps/activepedom/metadata.json @@ -3,7 +3,7 @@ "name": "Active Pedometer", "shortName": "Active Pedometer", "version": "0.09", - "description": "Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph.", + "description": "(NOT RECOMMENDED) Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph. The `Health` app now provides step logging and graphs.", "icon": "app.png", "tags": "outdoors,widget", "supports": ["BANGLEJS"], diff --git a/apps/activityreminder/ChangeLog b/apps/activityreminder/ChangeLog new file mode 100644 index 000000000..3811425ac --- /dev/null +++ b/apps/activityreminder/ChangeLog @@ -0,0 +1,10 @@ +0.01: New App! +0.02: Fix the settings bug and some tweaking +0.03: Do not alarm while charging +0.04: Obey system quiet mode +0.05: Battery optimisation, add the pause option, bug fixes +0.06: Add a temperature threshold to detect (and not alert) if the BJS isn't worn. Better support for the peoples using the app at night +0.07: Fix bug on the cutting edge firmware +0.08: Use default Bangle formatter for booleans +0.09: New app screen (instead of showing settings or the alert) and some optimisations +0.10: Add software back button via setUI diff --git a/apps/activityreminder/README.md b/apps/activityreminder/README.md new file mode 100644 index 000000000..0c79b4141 --- /dev/null +++ b/apps/activityreminder/README.md @@ -0,0 +1,15 @@ +# Activity reminder + +A reminder to take short walks for the ones with a sedentary lifestyle. +The alert will popup only if you didn't take your short walk yet. + +Different settings can be personalized: +- Enable : Enable/Disable the app +- Start hour: Hour to start the reminder +- End hour: Hour to end the reminder +- Max inactivity: Maximum inactivity time to allow before the alert. From 15 to 120 min +- Dismiss delay: Delay added before the next alert if the alert is dismissed. From 5 to 60 min +- Pause delay: Same as Dismiss delay but longer (usefull for meetings and such). From 30 to 240 min +- Min steps: Minimal amount of steps to count as an activity +- Temp Threshold: Temperature threshold to determine if the watch is worn + diff --git a/apps/activityreminder/alert.js b/apps/activityreminder/alert.js new file mode 100644 index 000000000..96a9b76c4 --- /dev/null +++ b/apps/activityreminder/alert.js @@ -0,0 +1,37 @@ +(function () { + // load variable before defining functions cause it can trigger a ReferenceError + const activityreminder = require("activityreminder"); + const storage = require("Storage"); + let activityreminder_data = activityreminder.loadData(); + + function run() { + E.showPrompt("Inactivity detected", { + title: "Activity reminder", + buttons: { "Ok": 1, "Dismiss": 2, "Pause": 3 } + }).then(function (v) { + if (v == 1) { + activityreminder_data.okDate = new Date(); + } + if (v == 2) { + activityreminder_data.dismissDate = new Date(); + } + if (v == 3) { + activityreminder_data.pauseDate = new Date(); + } + activityreminder.saveData(activityreminder_data); + load(); + }); + + // Obey system quiet mode: + if (!(storage.readJSON('setting.json', 1) || {}).quiet) { + Bangle.buzz(400); + } + setTimeout(load, 20000); + } + + g.clear(); + Bangle.loadWidgets(); + Bangle.drawWidgets(); + run(); + +})(); \ No newline at end of file diff --git a/apps/activityreminder/app-icon.js b/apps/activityreminder/app-icon.js new file mode 100644 index 000000000..418657961 --- /dev/null +++ b/apps/activityreminder/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwYda7dtwAQNmwRB2wQMgO2CIXACJcNCIfYCJYOCCgQRNJQYRM2ADBgwR/CKprRWAKPQWZ0DCIjXLjYREGpYODAQVgCBB3Btj+EAoQAGO4IdCgImDCAwLCAoo4IF4J3DCIPDCIQ4FO4VtwARCAoIRGRgQCBa4IRCKAQRERgOwIIIRDAoOACIoIBwwRHLIqMCFgIRCGQQRIWAYRLYQoREWwTmHO4IRCFgLXHPoi/CbogAFEAIRCWwTpKEwZBCHwK5BCJZEBCJZcCGQTLDCJK/BAQIRKMoaSDOIYAFeQYRMcYRWBXIUAWYPACIq8DagfACJQLCCIYsBU4QRF7B9CAogRGI4QLCAoprIMoZKER5C/DAoShMAo4AGfAQFIACQ=")) diff --git a/apps/activityreminder/app.js b/apps/activityreminder/app.js new file mode 100644 index 000000000..81e10d8dd --- /dev/null +++ b/apps/activityreminder/app.js @@ -0,0 +1,58 @@ +(function () { + // load variable before defining functions cause it can trigger a ReferenceError + const activityreminder = require("activityreminder"); + let activityreminder_data = activityreminder.loadData(); + let W = g.getWidth(); + // let H = g.getHeight(); + + function getHoursMins(date){ + var h = date.getHours(); + var m = date.getMinutes(); + return (""+h).substr(-2) + ":" + ("0"+m).substr(-2); + } + + function drawData(name, value, y){ + g.drawString(name, 10, y); + g.drawString(value, 100, y); + } + + function drawInfo() { + var h=18, y = h; + g.setColor(g.theme.fg); + g.setFont("Vector",h).setFontAlign(-1,-1); + + // Header + g.drawLine(0,25,W,25); + g.drawLine(0,26,W,26); + + g.drawString("Current Cycle", 10, y+=h); + drawData("Start", getHoursMins(activityreminder_data.stepsDate), y+=h); + drawData("Steps", getCurrentSteps(), y+=h); + + /* + g.drawString("Button Press", 10, y+=h*2); + drawData("Ok", getHoursMins(activityreminder_data.okDate), y+=h); + drawData("Dismiss", getHoursMins(activityreminder_data.dismissDate), y+=h); + drawData("Pause", getHoursMins(activityreminder_data.pauseDate), y+=h); + */ + } + + function getCurrentSteps(){ + let health = Bangle.getHealthStatus("day"); + return health.steps - activityreminder_data.stepsOnDate; + } + + function run() { + g.clear(); + Bangle.loadWidgets(); + Bangle.drawWidgets(); + drawInfo(); + Bangle.setUI({ + mode : "custom", + back : load + }) + } + + run(); + +})(); diff --git a/apps/activityreminder/app.png b/apps/activityreminder/app.png new file mode 100644 index 000000000..91073c444 Binary files /dev/null and b/apps/activityreminder/app.png differ diff --git a/apps/activityreminder/boot.js b/apps/activityreminder/boot.js new file mode 100644 index 000000000..5a11d73b8 --- /dev/null +++ b/apps/activityreminder/boot.js @@ -0,0 +1,81 @@ +(function () { + // load variable before defining functions cause it can trigger a ReferenceError + const activityreminder = require("activityreminder"); + const activityreminder_settings = activityreminder.loadSettings(); + let activityreminder_data = activityreminder.loadData(); + + if (activityreminder_data.firstLoad) { + activityreminder_data.firstLoad = false; + activityreminder.saveData(activityreminder_data); + } + + function run() { + if (isNotWorn()) return; + let now = new Date(); + let h = now.getHours(); + + if (isDuringAlertHours(h)) { + let health = Bangle.getHealthStatus("day"); + if (health.steps - activityreminder_data.stepsOnDate >= activityreminder_settings.minSteps // more steps made than needed + || health.steps < activityreminder_data.stepsOnDate) { // new day or reboot of the watch + activityreminder_data.stepsOnDate = health.steps; + activityreminder_data.stepsDate = now; + activityreminder.saveData(activityreminder_data); + /* todo in a futur release + Add settimer to trigger like 30 secs after going in this part cause the person have been walking + (pass some argument to run() to handle long walks and not triggering so often) + */ + } + + if (mustAlert(now)) { + load('activityreminder.alert.js'); + } + } + + } + + function isNotWorn() { + return (Bangle.isCharging() || activityreminder_settings.tempThreshold >= E.getTemperature()); + } + + function isDuringAlertHours(h) { + if (activityreminder_settings.startHour < activityreminder_settings.endHour) { // not passing through midnight + return (h >= activityreminder_settings.startHour && h < activityreminder_settings.endHour); + } else { // passing through midnight + return (h >= activityreminder_settings.startHour || h < activityreminder_settings.endHour); + } + } + + function mustAlert(now) { + if ((now - activityreminder_data.stepsDate) / 60000 > activityreminder_settings.maxInnactivityMin) { // inactivity detected + if ((now - activityreminder_data.okDate) / 60000 > 3 && // last alert anwsered with ok was more than 3 min ago + (now - activityreminder_data.dismissDate) / 60000 > activityreminder_settings.dismissDelayMin && // last alert was more than dismissDelayMin ago + (now - activityreminder_data.pauseDate) / 60000 > activityreminder_settings.pauseDelayMin) { // last alert was more than pauseDelayMin ago + return true; + } + } + return false; + } + + Bangle.on('midnight', function () { + /* + Usefull trick to have the app working smothly for people using it at night + */ + let now = new Date(); + let h = now.getHours(); + if (activityreminder_settings.enabled && isDuringAlertHours(h)) { + // updating only the steps and keeping the original stepsDate on purpose + activityreminder_data.stepsOnDate = 0; + activityreminder.saveData(activityreminder_data); + } + }); + + + if (activityreminder_settings.enabled) { + setInterval(run, 60000); + /* todo in a futur release + increase setInterval time to something that is still sensible (5 mins ?) + when we added a settimer + */ + } +})(); diff --git a/apps/activityreminder/lib.js b/apps/activityreminder/lib.js new file mode 100644 index 000000000..a5c35190c --- /dev/null +++ b/apps/activityreminder/lib.js @@ -0,0 +1,44 @@ +exports.loadSettings = function () { + return Object.assign({ + enabled: true, + startHour: 9, + endHour: 20, + maxInnactivityMin: 30, + dismissDelayMin: 15, + pauseDelayMin: 120, + minSteps: 50, + tempThreshold: 27 + }, require("Storage").readJSON("activityreminder.s.json", true) || {}); +}; + +exports.writeSettings = function (settings) { + require("Storage").writeJSON("activityreminder.s.json", settings); +}; + +exports.saveData = function (data) { + require("Storage").writeJSON("activityreminder.data.json", data); +}; + +exports.loadData = function () { + let health = Bangle.getHealthStatus("day"); + let data = Object.assign({ + firstLoad: true, + stepsDate: new Date(), + stepsOnDate: health.steps, + okDate: new Date(1970), + dismissDate: new Date(1970), + pauseDate: new Date(1970), + }, + require("Storage").readJSON("activityreminder.data.json") || {}); + + if (typeof (data.stepsDate) == "string") + data.stepsDate = new Date(data.stepsDate); + if (typeof (data.okDate) == "string") + data.okDate = new Date(data.okDate); + if (typeof (data.dismissDate) == "string") + data.dismissDate = new Date(data.dismissDate); + if (typeof (data.pauseDate) == "string") + data.pauseDate = new Date(data.pauseDate); + + return data; +}; diff --git a/apps/activityreminder/metadata.json b/apps/activityreminder/metadata.json new file mode 100644 index 000000000..a7fb0c487 --- /dev/null +++ b/apps/activityreminder/metadata.json @@ -0,0 +1,24 @@ +{ + "id": "activityreminder", + "name": "Activity Reminder", + "shortName":"Activity Reminder", + "description": "A reminder to take short walks for the ones with a sedentary lifestyle", + "version":"0.10", + "icon": "app.png", + "type": "app", + "tags": "tool,activity", + "supports": ["BANGLEJS", "BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name": "activityreminder.app.js", "url":"app.js"}, + {"name": "activityreminder.boot.js", "url": "boot.js"}, + {"name": "activityreminder.settings.js", "url": "settings.js"}, + {"name": "activityreminder.alert.js", "url": "alert.js"}, + {"name": "activityreminder", "url": "lib.js"}, + {"name": "activityreminder.img", "url": "app-icon.js", "evaluate": true} + ], + "data": [ + {"name": "activityreminder.s.json"}, + {"name": "activityreminder.data.json", "storageFile": true} + ] +} diff --git a/apps/activityreminder/settings.js b/apps/activityreminder/settings.js new file mode 100644 index 000000000..051c0dcd8 --- /dev/null +++ b/apps/activityreminder/settings.js @@ -0,0 +1,86 @@ +(function (back) { + // Load settings + const activityreminder = require("activityreminder"); + let settings = activityreminder.loadSettings(); + + function getMainMenu(){ + var mainMenu = { + "": { "title": "Activity Reminder" }, + "< Back": () => back(), + 'Enable': { + value: settings.enabled, + onchange: v => { + settings.enabled = v; + activityreminder.writeSettings(settings); + } + }, + 'Start hour': { + value: settings.startHour, + min: 0, max: 24, + onchange: v => { + settings.startHour = v; + activityreminder.writeSettings(settings); + } + }, + 'End hour': { + value: settings.endHour, + min: 0, max: 24, + onchange: v => { + settings.endHour = v; + activityreminder.writeSettings(settings); + } + }, + 'Max inactivity': { + value: settings.maxInnactivityMin, + min: 15, max: 120, + onchange: v => { + settings.maxInnactivityMin = v; + activityreminder.writeSettings(settings); + }, + format: x => x + "m" + }, + 'Dismiss delay': { + value: settings.dismissDelayMin, + min: 5, max: 60, + onchange: v => { + settings.dismissDelayMin = v; + activityreminder.writeSettings(settings); + }, + format: x => x + "m" + }, + 'Pause delay': { + value: settings.pauseDelayMin, + min: 30, max: 240, step: 5, + onchange: v => { + settings.pauseDelayMin = v; + activityreminder.writeSettings(settings); + }, + format: x => { + return x + "m"; + } + }, + 'Min steps': { + value: settings.minSteps, + min: 10, max: 500, step: 10, + onchange: v => { + settings.minSteps = v; + activityreminder.writeSettings(settings); + } + }, + 'Temp Threshold': { + value: settings.tempThreshold, + min: 20, max: 40, step: 0.5, + format: v => v + "°C", + onchange: v => { + settings.tempThreshold = v; + activityreminder.writeSettings(settings); + } + } + }; + + return mainMenu; + } + + // Show the menu + E.showMenu(getMainMenu()); +}) diff --git a/apps/advcasio/ChangeLog b/apps/advcasio/ChangeLog new file mode 100644 index 000000000..fd37c324e --- /dev/null +++ b/apps/advcasio/ChangeLog @@ -0,0 +1,4 @@ +0.01: AdvCasio first version +0.02: Remove un-needed fonts to improve memory usage +0.03: Tell clock widgets to hide. +0.04: Swipe down to see widgets, step counter now just uses getHealthStatus diff --git a/apps/advcasio/README.md b/apps/advcasio/README.md new file mode 100644 index 000000000..3ce771497 --- /dev/null +++ b/apps/advcasio/README.md @@ -0,0 +1,62 @@ +# Adv Casio Clock + + + +An over-engineered clock inspired by Casio watches.
+It has a dedicated timer, a scratchpad and can display the weather condition 4 days ahead.
+It uses a custom web app to update its content.
+Forked from the awesome Cassio Watch.
+ +## Todo + +- Improving quality of the background images, right now it is quite blurry. +- Improving screenshots quality. +- Improving web app look. +- Improving bangle app performances (using functions for images and specialized array). + +## Functionalities + +- Current time +- Current day and month +- Footsteps +- Battery +- Simple Timer embedded +- Weather forecast (7 days) +- Scratchpad + +## Screenshots +Clock:
+ + + + +Web interface to update weather & scratchpad
+https://dotgreg.github.io/advCasioBangleClock + + + +## Usage +### How to update the tasks list / weather +- you will need a free openweathermap.org api key. +- go to https://dotgreg.github.io/advCasioBangleClock/ + - Alternatively you can install it on your own server/heroku/service/github pages, the web-app code is here +- fill the location and the api key (it will be saved on your browser, no need to do it each time) +- edit the scratchpad with what you want +- click on sync +- reload your clock! + +### How to start/stop the timer +- swipe up : add time (+5min) +- swipe down : remove time (-5min) +- swipe right : start timer +- swipe left : stop timer + +## Links +### Issues, suggestions and bugtracker +https://github.com/dotgreg/advCasioBangleClock/issues + +### Code repository (bangle app and web app) +https://github.com/dotgreg/advCasioBangleClock + +### Creator +https://github.com/dotgreg diff --git a/apps/advcasio/app-icon.js b/apps/advcasio/app-icon.js new file mode 100644 index 000000000..2471ceac7 --- /dev/null +++ b/apps/advcasio/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwghC/AH4A/AGsCmUQC6kf/8wC6k///wgEv//zD4PxAQIJBABP//4QBC4IcBh/yEQIXKgP/l4rBl/yGAMP/4iBKJUC/5gBIAQVBBAMR/8gC5IQBAAMQC4IVBFoMjAYIXNmAXBgYXCPgQAJl/xHwPwj/yn5kC/55BUxSlC+JiBVgQ5BUxiDBUIIXBIQQXBcCoA/AH4ADXAUgUAUQBAkPeoTDFgIHBAALQEA4XwC4IOEAAQRBbAQBBCAIgBEYMQC4TnEC4XyeQgBDAAMwC4pIDC4kDAgJLD//xC5QIBNQISCFYIZCC4aEBAQRCDAAPyl4hBOIh3Cn53GNgMRiKxGBAR5BAoYA/AH4A/AH4A5A")) diff --git a/apps/advcasio/app.js b/apps/advcasio/app.js new file mode 100644 index 000000000..9d246b7ef --- /dev/null +++ b/apps/advcasio/app.js @@ -0,0 +1,160 @@ +const storage = require('Storage'); + +require("Font6x12").add(Graphics); +require("Font8x12").add(Graphics); +require("Font7x11Numeric7Seg").add(Graphics); + +function bigThenSmall(big, small, x, y) { + g.setFont("7x11Numeric7Seg", 2); + g.drawString(big, x, y); + x += g.stringWidth(big); + g.setFont("8x12"); + g.drawString(small, x, y); +} + +function getBackgroundImage() { + return require("heatshrink").decompress(atob("2GwwkGIf4AfgMRkUiiIHCiMRiAMDAwYCCBAYVDAHMv/4ACkBIBAgPxBgM/BYXyAoICBCowA5gRADKQUDKAYMCmYCBiBXBCo4A5J4MxiMSKQUf+YBBBgSiBgc/kBXBBAMyCoK2CK/btCiUhfAJLCkBkDiMQgBXDCoUvNAJX+AAU/+MB/8wAQIAC+cQK5hoDgIEBBIQFEAYIPHBIgBBAQQIDBwZXSKIMxgJaBgEjmZYCmBXLgLBBkkAgUhiMxBIM0iMSCoMRkZECkQJEichBINDiETAgISBiQTDK6MvJAXzVIQrBBYMCK5E/K4kwGIJXFgdAMgQQBiYiCDgU0HQSlCgMikIEBEAMTDYJXQ+UikYDBj6nCAAMTWoJ6BK4oVEK4c0oQ+BK4MjAgMDJoJXHNYJXHBwa0BohcDY4QAKgJQE+LzBNwJVBkQMEkBXBCoyvFJAVAKISaBiMiHQRIDkVBoSyCK5CvBAgavNDAJAC+cQn5DCgSpBl4MDgBXBgCsBCoYoMLAKREgIKDBJIdKK5oA/AH4A/AH4A/ADUBIH4APiAFEi1mAGUADrkRKwUGK2ZXes1gK2xXfD8A3/K/4AWgxX/ACtga2AwIHLkAgCwvJw6RcDgIABK+w4cK/I4dsEGP5BXtSAQ6BV/5XSG4RX/K6Y3fK+42CK/5XTGwcGK/5XSVwY5cK+o1DAAayYsAhDsCv4K7BTBK4YeYK7CyFVzJXFFIpXtVwYiYK/rmZKYYDDELJXXG4YiaK/Y0aKgQAEK+gkdKt5XGKzqv5GTpX6ETlgK4xWrKTyxKVthXmAGRX/K/5X/AH5X/K/4gBAH4A/AFz/uAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHNggEGHfEAgAEHKyQXVK0qTCAggbUK+6SDAApzXK/5BRDYZX3KxBBSYqxXngyvaV25XEd4ZCSsAcBAoRZ2dQZXBLwgaQCIYeCAGirCS4YGCDSJXCC6ZaodYICBZzSw4S4I+XDgSv4K4rzCK/47RAQTMaWHI9YV3TscV3aVagByBK3SwCSqyt8AAQ+XK/4A/AH4A/AH4A3gAA/AH4AuZbdggwc3ADpX/K/5XxsEAgA+XK/o8BgBX/K64/WK/4/XK/5X/K/5XvgBX/K64cYHrw4CSTFggCuXK4oDCEQJXYDS6ScDgg4CPKyRCAAZX0HAgBDK+LlYK4oeBAwZ9aK+lgAoQGBgyvzDIIDBK66sCG4JXYCwIBDK7ADCK+xZCHwJXzGoQ8BK7DpBAAaSXSgRXZO4okCK+IaXV4oABEILSWSYjRCHSo3BDSxXEAAIcBAISvyKawcIAYIGCK/4cUH4YlaHS0AHgI1XOg5YBPrY6WHgRXfAGRXDHzBX8VoJX/K68ADjRX6sBX/K/5X/K8wdcK/UAG7B0iKzZYbK/BWDAH4A/hWpzWhIf4ASgOpzIAB0EAhhH/AB8ZzGJ1WazMA4pH/AB+pxOZxOpzVMqA2ugUzmcgD7cKVYOqzGqpnRFw8ykchK8kviEBmQFBgMiFocSCAcSkUQAgMikRsHhWqxOq0Ut4mqBw0DC4IxBD4wpBHAQMCA4cCGJIAFj8hDIQuBkMTCwU/AYQJBiUxFoPxiIVDK4kyxUz4cxl+KK5MfDQXyD4UCmMSmAEBAQQHDgMTmIxHAAqpBmaqCFwMDEYZRBgEjCQQBB+USK5E/ns/0Uzwc6K48ykYkCK4IfCc4I4CK4QHEBAYAMiICBmYuDmQEBh8iAgRXCLISvJO4MqwcklEiK5CADV4oaBV4oHEK6Eve4JNCbwRfCiMTFoMDkMRSAJXCD49azWp0UqzWayJXIQwcAO4cCkMCFIJOCA4XxK6KPBkR6DTwYyBAwYPEAggfFzORpWK1OZyAOHJ4QfERAUSEgQxIIIgAr1URWIOZzOgGtwAhgMZzWq1OaIv4ASKgOqzTkvAEmq1WgFtQA==")); +} + +function getRocketSequences() { + return { + 1: require("heatshrink").decompress(atob("qFGwkCkQAiiEBEkUgKQhPhE8ogCE8YhCiQoEE7pKEPIgncTQ4neEwpQCPoh1eJYYwCJ7QmHKAh1hZIpOjPAUBJ0ZQCTzEhExZ1lPAZ1kKDQmOJ65O2E65OPOy5O2E64mPOyxO/J2wnPJyx2QJ35O/J2khE0p2POq52PEy4nOiQnlOrEhiSfMJrEggQnLJzB1CPBQmZkInMEzBQDPBImbPBR1ZEoRMCZYImhgQgEE0BzFKAgmaDwLDFKAbqdYQwHBOrcgDgLBFJrsiiRNGYbpLBY4Ymhd4omkkUhE0pQEEwUBJjrHBd4QmCdzoiBDwYrCPLyZHF4QnagQeCE8UgJwYniJwgnIOzwfFO0wJCJzMQE4gyFEzR2FBQombkInDQI4AakAnBTYS+ZE5BMDE0LEES7YnLE0R3FAEQA=")), + 2: require("heatshrink").decompress(atob("qFGwkCkQAikMAgIliKYon/AA0gEAQniEwIhCAgYndEIjqBE8CaGKogmgKAp1fKAgncExBQBBQR1gKAp7BJ0IndExR4CE0idaOpYnbExqeYJxxPYEx0BJ0x2XExx2XJ20QE6xONJi5OPGwJOlBwLFkLoLFlBwJOkOwJOlE4JOkTjBOOE/52Pdi5OPEy7FnE5wmXE5xOZT5gmYEoMiiB1lgR4KTLAkDPBJ1WIAYDDKA4mWJwchDwYEDTjQiDJQh4GYLAhHFosSJy6OCTIxaEEywbBKYwjEEzMgUQxQFBogAURwZOGOjTKJdTYnOEryfHE0JQEfIpQgYQMAgJLeAgrtfTI4ndgSaFE4h0bdQkSZQpOfEAgIBO0AnEdrh2FJAb1EdbInEBIpObOwhOEEzYnFXzZ2HE4QlhE4QlDFMKcDYooniO0QnDT0YnCE0ciA")), + 3: require("heatshrink").decompress(atob("qFGwkEogAjiMUEkVAKYgnhPYolgOQIniOYZ4FOcLqBE8CaGKojpgKAomhEYUQE7gmHKAIxCE0QkCPYR1gZIgnZExR4CJ0idmE7ZONYzImNgEUJ0p3YJRh2ZJJwnXOpQhBdkpaETsMEGQhOhE7jFLUYpOfTzgmKE4hOiE4hOigEUJ0rvCEywnPEqx2OTjBOOE7ImOTsqeZE5zFYoJOmT5kBJzEAih4LdK5mBAQInKOqoYDEgR4JEypHDEYbxJOq5ABdgZ7CEzZOEJQgnGihOYEIzJFTionCKYxWGEy9ADAYnGUIYmWog/EdBFAEy7KIKAwnjKwLqWE5pMeT48CVQpQfgMjKEtEiAnfEQJQCgJSCTcB6FJzkEdYcUE8FAdQghDOzonKTjh2EZAidcDoInHJzodBOwx/BE8JxcOwsAOwQmhJgSXDObwnFEwUUO0LFGE8aeiE4YmiokQE0tE")), + 4: require("heatshrink").decompress(atob("qFGwkCkQAjiMSEkRTFE/4AGkMAgQCBE8MgEIYEDE7whDdQIngTQxVEE0ChFTjxQFE7jnFKAgxCOsBQFZgJ1gE7wmKPAROkTrTEHGAwnYiBHJFAaeXOoyXBEQZPac5AsFgJOhAoh2XJwwnFKoROdE4J9GJzwnIiQmVkInPAC0QE5AJFE64mHY5DFdE4SBEYr5JDJ0hKDJ0jCZJxoACgInmKLAmOTq5OOEy5OPTsxOYE5wmXO5wlYkAnMOqshiRNCgR4LOC8CkJCCEzxHDAgYnJOqpAEDoZ4HEyodDEQpQHdCsQOwwFHEyzoCPYzJGEy0gEwaZGA4acVEQSjHKAomXkQYEYAwlZeRKYDE8gjCYa7zJEwcCkImfKAb4FAD0hdTh4LgRSBOcR0CJz0gYYrrgN4QnEYrxOEE4bEeiAnGF4J2idL6VDE8ohBE0gnFE0J0BE4QGBiROgdIQABgJ2hJoTtjYgZSEE8ScgE4omikUQTcQADA=")), + 5: require("heatshrink").decompress(atob("qFGwkCkQAikMAgIliKYonhiAnjkEATIIniEwIhCAgYndEIhQFYUZVEE0BQFOr5QEeQQmiKAL1DOr5QEE7ROCDgZVEAoInZDwchFQQoDPAJOdEQYrBdrZFDOYwncEJDsDVIpOXgJxEE4pObEAgGFgJOaE48BaIhOZJ5ZObY5ROcE441CE6xOGPAwtCJzpGCJ0hHDkI1DJzwoEJzInLFg52dUo5O/J35OzE54mWOx4mXJxx1XE54mXkUhExkSJzCfMOrAlBPBiZXgQDBAQQmgJgh4JOqoYEFYwmaDoZzEFgh1YDgkiiAFEKAroXJJAGFiQmVkCNDTIz5EJy57HKAomXkQYEJoqaYeRadEJrAnJEQUAgJPiAoYmeT4cCkAnBE0BKCJkT1EkDCeJYYiDOkLDFFL5wBE4guCPDhEBEwQiDY70CkInDiQnCJzkhOwhKDdzp2Idb4nEE0B0Bdo4niE0J0CeYhOhgESUYYnidsgnEE0KeCE0gnDE0ciA")), + 6: require("heatshrink").decompress(atob("qFGwkCkQA/ABEgKQZPhEwgABEsAoGJkBxBE8JKEAowAbJIhQEgLDiPooAdKA4ncTZAndSwhQEFoInaJQkSKAwlZdgwnfSgYADE4h1ZDwInlcggnIOzAdCE8i7EY5J3XDgYhGd4pOZEI52bSYwGCOAJ2bYIodEOzZOFFAjFcEwwAIE6xOHABBO/J34ndEyx2PJ00BJ00SJ0p1XE54mXOxxO/J5wmYgQnMOrB2BPBgkWiJ1CPBbBYAYR4KiTAXRwIrFTjgZDJYZ4IEyoiEIwrDcEJJQFOqwiBDARxFFwgmXkAYDEogsBF4QmXEQJ7GUYYkBEzDKJAgYmdEQbKFEzonEKYgngJwgmfZggmjKQghgiBRGkBzeTgUikJRgc47LDErTnDEAkQJzkCJwYnEJzonEJIaddOwhJEJzgdBE4hYEJzieJADgnEE0KUCXzoAGkJLEiB2hOgQDBT0TsDT0YmlE4YmjkQ=")), + 7: require("heatshrink").decompress(atob("qFGwkCkQAhkIpBiQlhkBSEJ8InlEIIoFE7whEE8pQFE7giBJQoneI4MCTYhQDE7YdCYYondEQYnEPwZ1bE5BQCJzonHkR2ZEAkBE4pNBE7zHFYrYhFUgonaXAQeEEwruZEYcgiROHJ7AfDAwxOeAAURiAmHE65HIOzwmOJ35OPE6xOPO35O/J35O/J1gnPEyx2PEy5OOOq5OnE5xOYO5omZgJQMJrQnLiQnagR4JOq5nCDgZ1fEYRLDE5DoZkUQNoZ4GOrJKGAoomXOw7lCAwYmYDgJSEAAUBA4QDBJzB6FOQrDXJwTJFdLjJKE9jDYZRAmkKAwmhKAgmiKAYmBkApdJIgjCKYIncOQYvJYTovGE84lagR2DE4xOakBOEgJXFOjYnEJAbtdOwggEkAmbDgInDE0B0BE4QgcE5AkiXYbpCOLonGYo4nhPMYnCUEgnBY0kiA==")), + 8: require("heatshrink").decompress(atob("qFGwkCkQA/ABBSEJ8MgE4kBEsBPFE7xMCOIJ3hOYgFEE7rCGE70gE4pQBiAndYQwjBUohOZD4ZQFE7YkBE5AICYbZ2GE7sggJRCAA8iYzZOITroALE7EhExh4CAC0QExpPXOponZExx2XJ24nWdh52XdhzF/Yu5O/J35O0E55OXOx5O/J2omXE5x1XO54mYgQnMJrR4LOrciiAmiJgR4KEzIjDPBAlYiAiEeI51YkEBE4J5CD4KceTQQcBJgRQFdTZDCJIjDcNIqhGdTQmCkByFTTInDKgoAEE7ZEEJwhPdE1R1FE0InEE0R3DEwTGcDwomEE7hKFPYqafE8ROCE5DJbE5B/IEqh2ED4gnCJrMCJwgnEiB2bE4qeFEzUggQmIBQLEaEQImHLIImaE4YfcOw4lEFMLECS7onJO8wmkE4QljAAIA==")), + }; +} + +let rocketSequence = 1; +let settings = storage.readJSON("cassioWatch.settings.json", true) || {}; +let rocketSpeed = settings.rocketSpeed || 700; +delete settings; + +// schedule a draw for the next minute +let rocketInterval; +var drawTimeout; +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + + +function clearIntervals() { + if (rocketInterval) clearInterval(rocketInterval); + rocketInterval = undefined; + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; +} + +function drawClock() { + g.setFont("7x11Numeric7Seg", 3); + g.clearRect(80, 57, 170, 96); + g.setColor(0, 255, 255); + g.drawRect(80, 57, 170, 96); + g.fillRect(80, 57, 170, 96); + g.setColor(0, 0, 0); + g.drawString(require("locale").time(new Date(), 1), 70, 60); + g.setFont("8x12", 2); + g.drawString(require("locale").dow(new Date(), 2).toUpperCase(), 18, 130); + g.setFont("8x12"); + g.drawString(require("locale").month(new Date(), 2).toUpperCase(), 80, 126); + g.setFont("8x12", 2); + const time = new Date().getDate(); + g.drawString(time < 10 ? "0" + time : time, 78, 137); +} + +function drawBattery() { + bigThenSmall(E.getBattery(), "%", 135, 21); +} + +function drawRocket() { + let Rocket = getRocketSequences(); + g.clearRect(5, 62, 63, 115); + g.setColor(0, 255, 255); + g.drawRect(5, 62, 63, 115); + g.fillRect(5, 62, 63, 115); + g.drawImage(Rocket[rocketSequence], 5, 65, { scale: 0.7 }); + g.setColor(0, 0, 0); + rocketSequence = rocketSequence + 1; + if(rocketSequence > 8) rocketSequence = 1; +} + +function getTemperature(){ + try { + var weatherJson = storage.readJSON('weather.json'); + var weather = weatherJson.weather; + return Math.round(weather.temp-273.15); + + } catch(ex) { + print(ex) + return "?" + } +} + +function getSteps() { + var steps = Bangle.getHealthStatus("day").steps; + steps = Math.round(steps/1000); + return steps + "k"; +} + + +function draw() { + queueDraw(); + + g.clear(1); + g.setColor(0, 255, 255); + g.fillRect(0, 0, g.getWidth(), g.getHeight()); + let background = getBackgroundImage(); + g.drawImage(background, 0, 0, { scale: 1 }); + g.setColor(0, 0, 0); + g.setFont("6x12"); + g.drawString("Launching Process", 30, 20); + g.setFont("8x12"); + g.drawString("ACTIVATE", 40, 35); + + g.setFontAlign(0,-1); + g.setFont("8x12", 2); + g.drawString(getTemperature(), 155, 132); + g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), 109, 98); + g.drawString(getSteps(), 158, 98); + + g.setFontAlign(-1,-1); + drawClock(); + drawRocket(); + drawBattery(); + + // Hide widgets + for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} +} + +Bangle.on("lcdPower", (on) => { + if (on) { + draw(); + } else { + clearIntervals(); + } +}); + + +Bangle.on("lock", (locked) => { + clearIntervals(); + draw(); + if (!locked) { + rocketInterval = setInterval(drawRocket, rocketSpeed); + } +}); + +Bangle.setUI("clock"); + +// Load widgets, but don't show them +Bangle.loadWidgets(); +require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe +g.clear(1); +draw(); diff --git a/apps/advcasio/app.png b/apps/advcasio/app.png new file mode 100644 index 000000000..a7c1b2736 Binary files /dev/null and b/apps/advcasio/app.png differ diff --git a/apps/advcasio/data.json b/apps/advcasio/data.json new file mode 100644 index 000000000..d986fa09c --- /dev/null +++ b/apps/advcasio/data.json @@ -0,0 +1 @@ +{"tasks":"", "weather":[]}; diff --git a/apps/advcasio/metadata.json b/apps/advcasio/metadata.json new file mode 100644 index 000000000..25dc1243a --- /dev/null +++ b/apps/advcasio/metadata.json @@ -0,0 +1,25 @@ +{ "id": "advcasio", + "name": "Advanced Casio Clock", + "shortName":"advcasio", + "version":"0.04", + "description": "An over-engineered clock inspired by Casio watches. It has a 4 days weather, a timer using swipe and a scratchpad. Can be updated using a dedicated webapp.", + "icon": "app.png", + "tags": "clock", + "type": "clock", + "screenshots": [ + { "url": "screenshot-clock-1.jpg" }, + { "url": "screenshot-clock-2.jpg" }, + { "url": "screenshot-clock-3.jpg" }, + { "url": "screenshot-webapp.jpg" } + ], + "supports" : ["BANGLEJS", "BANGLEJS2"], + "readme": "README.md", + "allow_emulator":true, + "storage": [ + {"name":"advcasio.app.js","url":"app.js"}, + {"name":"advcasio.img","url":"app-icon.js","evaluate":true} + ], + "data": [ + { "name": "advcasio.data.json", "url": "data.json", "storageFile": true } + ] +} diff --git a/apps/advcasio/screenshot-clock-1.jpg b/apps/advcasio/screenshot-clock-1.jpg new file mode 100644 index 000000000..7f6f042c9 Binary files /dev/null and b/apps/advcasio/screenshot-clock-1.jpg differ diff --git a/apps/advcasio/screenshot-clock-2.jpg b/apps/advcasio/screenshot-clock-2.jpg new file mode 100644 index 000000000..b5f1e38af Binary files /dev/null and b/apps/advcasio/screenshot-clock-2.jpg differ diff --git a/apps/advcasio/screenshot-clock-3.jpg b/apps/advcasio/screenshot-clock-3.jpg new file mode 100644 index 000000000..59389eb31 Binary files /dev/null and b/apps/advcasio/screenshot-clock-3.jpg differ diff --git a/apps/advcasio/screenshot-webapp.jpg b/apps/advcasio/screenshot-webapp.jpg new file mode 100644 index 000000000..d67bdba91 Binary files /dev/null and b/apps/advcasio/screenshot-webapp.jpg differ diff --git a/apps/agenda/ChangeLog b/apps/agenda/ChangeLog new file mode 100644 index 000000000..7f749ff25 --- /dev/null +++ b/apps/agenda/ChangeLog @@ -0,0 +1,9 @@ +0.01: Basic agenda with events from GB +0.02: Added settings page to force calendar sync +0.03: Disable past events display from settings +0.04: Added awareness of allDay field +0.05: Displaying calendar colour and name +0.06: Added clkinfo for clocks. +0.07: Clkinfo improvements. +0.08: Fix error in clkinfo (didn't require Storage & locale) + Fix clkinfo icon diff --git a/apps/agenda/README.md b/apps/agenda/README.md new file mode 100644 index 000000000..1a0ec9264 --- /dev/null +++ b/apps/agenda/README.md @@ -0,0 +1,30 @@ +# Agenda + +Basic agenda reading the events synchronised from GadgetBridge. + +### Functionalities + +* List all events in the next week (or whatever is synchronized) +* Optionally view past events (until GB removes them) +* Show start time and location of the events in the list +* Show the colour of the calendar in the list +* Display description, location and calendar name after tapping on events + +### Troubleshooting + +For the events sync to work, GadgetBridge needs to have the calendar permission and calendar sync should be enabled in the devices settings (gear sign in GB, also check the blacklisted calendars there, if events are missing). +Keep in mind that GadgetBridge won't synchronize all events on your calendar, just the ones in a time window of 7 days (you don't want your watch to explode), ideally every day old events get deleted since they appear out of such window. + +#### Force Sync + +If for any reason events still cannot sync or some are missing, you can try any of the following (just one, you normally don't need to do this): +1. from GB open the burger menu (side), tap debug and set time. +2. from the bangle, open settings > apps > agenda > Force calendar sync, then select not to delete the local events (this is equivalent to option 1). +3. do like option 2 but delete events, GB will synchronize a fresh database instead of patching the old one (good in case you somehow cannot get rid of older events) + +After any of the options, you may need to disconnect/force close Gadgetbridge before reconnecting and let it sync (give it some time for that too), restart the agenda app on the bangle after a while to see the changes. + +### Report a bug + +You can easily open an issue in the espruino repo, but I won't be notified and it might take time. +If you want a (hopefully) quicker response, just report [on my fork](https://github.com/glemco/BangleApps). diff --git a/apps/agenda/agenda-icon.js b/apps/agenda/agenda-icon.js new file mode 100644 index 000000000..891543955 --- /dev/null +++ b/apps/agenda/agenda-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwg1yhGIxAPMBwIPFhH//GAC5n/C4oHBC5/IGwoXBHQQAKC4OIFAWOxHv9GO9wAKI4XoC4foEIIWLC4IABC4gIBFxnuE4IqBC4gARC4ZzNAAwXaxe7ACO4C625C4m4xIJBzAeCxGbCAOIFgQOBC4pOBxe4AYIPBAYQKCAYYXE3GL/ADBx/oxb3BC4X+xG4xwOBC4uP/YDB54MBf4Po3eM/4XBx/+C4pTBGIIkBLgOYAYIvB9GJBwI6BL45zCL4aCCL4h3GU64ALdYS1CI55bBAAgXFO4mMO4QDBDIO/////YxBU53IxIVB/GfDAWYa5wtC/GPAYWIL4wXBL4oSBC4jcBC4m4QIWYSwWIIQIAG/CnMMAIAC/JLCMIIvMIwZHFJAJfLC5yPHAYIRDAoy/KCIi7BMon4d4+Od4IXBxAZBEQLtB/+YxIXDL4SLCL4WPzAXCNgRFBLIKnKLIrcEI4gXNAAp3CxGZAAzCBC5KnCKAIAICxBlBC4IAJxG/C4/4wAXLhBgD/IcD3AXMGAIqDDgRGNGAoXDFxxhEI4W4FxwwCaoYWBFx4YDAAQWRAEQ")) diff --git a/apps/agenda/agenda.clkinfo.js b/apps/agenda/agenda.clkinfo.js new file mode 100644 index 000000000..baa8b9516 --- /dev/null +++ b/apps/agenda/agenda.clkinfo.js @@ -0,0 +1,29 @@ +(function() { + var agendaItems = { + name: "Agenda", + img: atob("GBiBAAAAAAAAAADGMA///w///wf//wAAAA///w///w///w///x///h///h///j///D///X//+f//8wAABwAADw///w///wf//gAAAA=="), + items: [] + }; + var locale = require("locale"); + var now = new Date(); + var agenda = require("Storage").readJSON("android.calendar.json") + .filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000) + .sort((a,b)=>a.timestamp - b.timestamp); + + agenda.forEach((entry, i) => { + + var title = entry.title.slice(0,12); + var date = new Date(entry.timestamp*1000); + var dateStr = locale.date(date).replace(/\d\d\d\d/,""); + dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : ""; + + agendaItems.items.push({ + name: "Agenda "+i, + get: () => ({ text: title + "\n" + dateStr, img: null}), + show: function() { agendaItems.items[i].emit("redraw"); }, + hide: function () {} + }); + }); + + return agendaItems; +}) diff --git a/apps/agenda/agenda.js b/apps/agenda/agenda.js new file mode 100644 index 000000000..9cffe0265 --- /dev/null +++ b/apps/agenda/agenda.js @@ -0,0 +1,138 @@ +/* CALENDAR is a list of: + {id:int, + type, + timestamp, + durationInSeconds, + title, + description, + location, + color:int, + calName, + allDay: bool, + } +*/ + +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +var FILE = "android.calendar.json"; + +var Locale = require("locale"); + +var fontSmall = "6x8"; +var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2"; +var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2"; +var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4"; + +//FIXME maybe write the end from GB already? Not durationInSeconds here (or do while receiving?) +var CALENDAR = require("Storage").readJSON("android.calendar.json",true)||[]; +var settings = require("Storage").readJSON("agenda.settings.json",true)||{}; + +CALENDAR=CALENDAR.sort((a,b)=>a.timestamp - b.timestamp); + +function getDate(timestamp) { + return new Date(timestamp*1000); +} +function formatDateLong(date, includeDay, allDay) { + let shortTime = Locale.time(date,1)+Locale.meridian(date); + if(allDay) shortTime = ""; + if(includeDay || allDay) + return Locale.date(date)+" "+shortTime; + return shortTime; +} +function formatDateShort(date, allDay) { + return Locale.date(date).replace(/\d\d\d\d/,"")+(allDay? + "" : Locale.time(date,1)+Locale.meridian(date)); +} + +var lines = []; +function showEvent(ev) { + var bodyFont = fontBig; + if(!ev) return; + g.setFont(bodyFont); + //var lines = []; + if (ev.title) lines = g.wrapString(ev.title, g.getWidth()-10); + var titleCnt = lines.length; + var start = getDate(ev.timestamp); + var end = getDate((+ev.timestamp) + (+ev.durationInSeconds)); + var includeDay = true; + if (titleCnt) lines.push(""); // add blank line after title + if(start.getDay() == end.getDay() && start.getMonth() == end.getMonth()) + includeDay = false; + if(includeDay || ev.allDay) { + lines = lines.concat( + /*LANG*/"Start:", + g.wrapString(formatDateLong(start, includeDay, ev.allDay), g.getWidth()-10), + /*LANG*/"End:", + g.wrapString(formatDateLong(end, includeDay, ev.allDay), g.getWidth()-10)); + } else { + lines = lines.concat( + g.wrapString(Locale.date(start), g.getWidth()-10), + g.wrapString(/*LANG*/"Start"+": "+formatDateLong(start, includeDay, ev.allDay), g.getWidth()-10), + g.wrapString(/*LANG*/"End"+": "+formatDateLong(end, includeDay, ev.allDay), g.getWidth()-10)); + } + if(ev.location) + lines = lines.concat(/*LANG*/"Location"+": ", g.wrapString(ev.location, g.getWidth()-10)); + if(ev.description) + lines = lines.concat("",g.wrapString(ev.description, g.getWidth()-10)); + if(ev.calName) + lines = lines.concat(/*LANG*/"Calendar"+": ", g.wrapString(ev.calName, g.getWidth()-10)); + lines = lines.concat(["",/*LANG*/"< Back"]); + E.showScroller({ + h : g.getFontHeight(), // height of each menu item in pixels + c : lines.length, // number of menu items + // a function to draw a menu item + draw : function(idx, r) { + // FIXME: in 2v13 onwards, clearRect(r) will work fine. There's a bug in 2v12 + g.setBgColor(idx=lines.length-2) + showList(); + }, + back : () => showList() + }); +} + +function showList() { + //it might take time for GB to delete old events, decide whether to show them grayed out or hide entirely + if(!settings.pastEvents) { + let now = new Date(); + //TODO add threshold here? + CALENDAR = CALENDAR.filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000); + } + if(CALENDAR.length == 0) { + E.showMessage("No events"); + return; + } + E.showScroller({ + h : 52, + c : Math.max(CALENDAR.length,3), // workaround for 2v10.219 firmware (min 3 not needed for 2v11) + draw : function(idx, r) {"ram" + var ev = CALENDAR[idx]; + g.setColor(g.theme.fg); + g.clearRect(r.x,r.y,r.x+r.w, r.y+r.h); + if (!ev) return; + var isPast = false; + var x = r.x+2, title = ev.title; + var body = formatDateShort(getDate(ev.timestamp),ev.allDay)+"\n"+(ev.location?ev.location:/*LANG*/"No location"); + if(settings.pastEvents) isPast = ev.timestamp + ev.durationInSeconds < (new Date())/1000; + if (title) g.setFontAlign(-1,-1).setFont(fontBig) + .setColor(isPast ? "#888" : g.theme.fg).drawString(title, x+4,r.y+2); + if (body) { + g.setFontAlign(-1,-1).setFont(fontMedium).setColor(isPast ? "#888" : g.theme.fg); + g.drawString(body, x+10,r.y+20); + } + g.setColor("#888").fillRect(r.x,r.y+r.h-1,r.x+r.w-1,r.y+r.h-1); // dividing line between items + if(ev.color) { + g.setColor("#"+(0x1000000+Number(ev.color)).toString(16).padStart(6,"0")); + g.fillRect(r.x,r.y+4,r.x+3, r.y+r.h-4); + } + }, + select : idx => showEvent(CALENDAR[idx]), + back : () => load() + }); +} +showList(); diff --git a/apps/agenda/agenda.png b/apps/agenda/agenda.png new file mode 100644 index 000000000..c850b0e5d Binary files /dev/null and b/apps/agenda/agenda.png differ diff --git a/apps/agenda/metadata.json b/apps/agenda/metadata.json new file mode 100644 index 000000000..7e49e3f96 --- /dev/null +++ b/apps/agenda/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "agenda", + "name": "Agenda", + "version": "0.08", + "description": "Simple agenda", + "icon": "agenda.png", + "screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}], + "tags": "agenda,clkinfo", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"agenda.app.js","url":"agenda.js"}, + {"name":"agenda.settings.js","url":"settings.js"}, + {"name":"agenda.clkinfo.js","url":"agenda.clkinfo.js"}, + {"name":"agenda.img","url":"agenda-icon.js","evaluate":true} + ], + "data": [{"name":"agenda.settings.json"}] +} diff --git a/apps/agenda/screenshot_agenda_event1.png b/apps/agenda/screenshot_agenda_event1.png new file mode 100644 index 000000000..581da286b Binary files /dev/null and b/apps/agenda/screenshot_agenda_event1.png differ diff --git a/apps/agenda/screenshot_agenda_event2.png b/apps/agenda/screenshot_agenda_event2.png new file mode 100644 index 000000000..f5edcaae8 Binary files /dev/null and b/apps/agenda/screenshot_agenda_event2.png differ diff --git a/apps/agenda/screenshot_agenda_overview.png b/apps/agenda/screenshot_agenda_overview.png new file mode 100644 index 000000000..a2030d05f Binary files /dev/null and b/apps/agenda/screenshot_agenda_overview.png differ diff --git a/apps/agenda/settings.js b/apps/agenda/settings.js new file mode 100644 index 000000000..4220fcb63 --- /dev/null +++ b/apps/agenda/settings.js @@ -0,0 +1,48 @@ +(function(back) { + function gbSend(message) { + Bluetooth.println(""); + Bluetooth.println(JSON.stringify(message)); + } + var settings = require("Storage").readJSON("agenda.settings.json",1)||{}; + function updateSettings() { + require("Storage").writeJSON("agenda.settings.json", settings); + } + var CALENDAR = require("Storage").readJSON("android.calendar.json",true)||[]; + var mainmenu = { + "" : { "title" : "Agenda" }, + "< Back" : back, + /*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?/*LANG*/"Yes":/*LANG*/"No" }, + /*LANG*/"Force calendar sync" : () => { + if(NRF.getSecurityStatus().connected) { + E.showPrompt(/*LANG*/"Do you want to also clear the internal database first?", { + buttons: {/*LANG*/"Yes": 1, /*LANG*/"No": 2, /*LANG*/"Cancel": 3} + }).then((v)=>{ + switch(v) { + case 1: + require("Storage").writeJSON("android.calendar.json",[]); + CALENDAR = []; + /* falls through */ + case 2: + gbSend({t:"force_calendar_sync", ids: CALENDAR.map(e=>e.id)}); + E.showAlert(/*LANG*/"Request sent to the phone").then(()=>E.showMenu(mainmenu)); + break; + case 3: + default: + E.showMenu(mainmenu); + return; + } + }); + } else { + E.showAlert(/*LANG*/"You are not connected").then(()=>E.showMenu(mainmenu)); + } + }, + /*LANG*/"Show past events" : { + value : !!settings.pastEvents, + onchange: v => { + settings.pastEvents = v; + updateSettings(); + } + }, + }; + E.showMenu(mainmenu); +}) diff --git a/apps/agpsdata/ChangeLog b/apps/agpsdata/ChangeLog new file mode 100644 index 000000000..8ada244d7 --- /dev/null +++ b/apps/agpsdata/ChangeLog @@ -0,0 +1,5 @@ +0.01: First, proof of concept +0.02: Load AGPS data on app start and automatically in background +0.03: Do not load AGPS data on boot + Increase minimum interval to 6 hours +0.04: Write AGPS data chunks with delay to improve reliability diff --git a/apps/agpsdata/README.md b/apps/agpsdata/README.md new file mode 100644 index 000000000..57bb055a1 --- /dev/null +++ b/apps/agpsdata/README.md @@ -0,0 +1,19 @@ +# A-GPS Data + +Load assisted GPS (A-GPS) data directly to your Bangle.js using the new http requests on Android GadgetBridge. + +Will download A-GPS data in background (if enabled in settings). + +The GNSS type can be configured in the settings. + +Make sure: +* your GadgetBridge version supports http requests +* turn on internet access in GadgetBridge settings + +Currently proof of concept on Bangle.js 2 only. + +## Creator +[@pidajo](https://github.com/pidajo) + +## Contributor +[@myxor](https://github.com/myxor) diff --git a/apps/agpsdata/agpsdata-icon.js b/apps/agpsdata/agpsdata-icon.js new file mode 100644 index 000000000..1677a2177 --- /dev/null +++ b/apps/agpsdata/agpsdata-icon.js @@ -0,0 +1 @@ +atob("MDCEAAAAAAAAAAAAAAAAiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAIiIiAAAAAAAAAAAAAAAAAAAAAAAAAAAAIiIiAAAAAAAAAAAAAAAAAAAAAAAAAAACIiIiIAAAAAAAAAAAAAAAAAAAAAAAAAACIiIiIAAAAAAAAAAAAAAAAAAAAAAAAAACIiIiIAAAAAAAAAAAAAAAAAAAAAAAAAAiIiIiIgAAAAAAAAAAAAAAAAAAAAAAAAAiIiIiIgAAAAAAAAAAAAAAAAAAAAAAAAIiIOIiIiAAAAAAAAAAAAAAAAAAAAAAAAIiDOIiIiAAAAAAAAAAAAAAAAAAAAAAAAIiDOIiIiIAAAAAAAAAAAAAAAAAAAAAACIiPOIiIiIAAAAAAAAAAAAAAAAAAAAAAiIj/OIiIiIgAAAAAAAAAAAAAAAAAAAAAiI//OIiIiIgAAAAAAAAAAAAAAAAAAAAAiI//OIiIiIiAAAAAAAAAAAAAAAAAAAAAiD//OIiIiIiAAAAAAAAAAAAAAAAAAAAIiP//OIiIiIiAAAAAAAAAAAAAAAAAAAAIg///OIiIiIiIAAAAAAAAAAAAAAAAAACIj///OIiIiIiIAAAAAAAAAAAAAAAAAACIP///OIiIiIiIgAAAAAAAAAAAAAAAAACI////OIiIiIiIgAAAAAAAAAAAAAAAAAiD////OIiIiIiIiAAAAAAAAAAAAAAAAAiP////OIiIiIiIiAAAAAAAAAAAAAAAAIiP////OIiIiIiIiIAAAAAAAAAAAAAAAIj/////OIiIiIiIiIAAAAAAAAAAAAAACIj/////OIiIiIiIiIgAAAAAAAAAAAAACI//////OIiIiIiIiIgAAAAAAAAAAAAAiI//////OIiIiIiIiIgAAAAAAAAAAAAAiIiIiIiIgzMzMzMziIiAAAAAAAAAAAAAiIiIiIiIj///////+IiAAAAAAAAAAAAIiIiIiIiIj////////4iIAAAAAAAAAAAIiIiIiIiIgzMzMzMzM4iIAAAAAAAAAACIP///////OIiIiIiIiIiIgAAAAAAAAACI////////OIiIiIiIiIiIgAAAAAAAAAiI////////OIiIiIiIiIiIiAAAAAAAAAiP////////OIiIiIiIiIiIiAAAAAAAAIiP////////OIiIiIiIiIiIiIAAAAAAAIj/////////OIiIiIiIiIiIiIAAAAAACIj////////ziIiIiIiIiIiIiIgAAAAACIP///////+IiIiIiIiIiIiIiIgAAAAACI//////84gYiIiIiIiIiIiIiIgAAAAAiD////84iIiAAAiIiIiIiIiIiIiAAAAAiP///ziIiIAAAAAIiIiIiIiIiIiAAAAIg/8ziIiIAAAAAAAAAIiIiIiIiIiIAAAIj/iIiIAAAAAAAAAAAAAIiIiIiIiIAACIiIiBgAAAAAAAAAAAAAAACIiIiIiIgACIiIgAAAAAAAAAAAAAAAAAAACIiIiIgACIgAAAAAAAAAAAAAAAAAAAAAAAiIiIgA==") diff --git a/apps/agpsdata/agpsdata.png b/apps/agpsdata/agpsdata.png new file mode 100644 index 000000000..a0f4de4cb Binary files /dev/null and b/apps/agpsdata/agpsdata.png differ diff --git a/apps/agpsdata/app.js b/apps/agpsdata/app.js new file mode 100644 index 000000000..4a6d2ba5c --- /dev/null +++ b/apps/agpsdata/app.js @@ -0,0 +1,54 @@ +function display(text1, text2) { + g.reset(); + g.clear(); + var img = require("Storage").read("agpsdata.img"); + if (img) { + g.drawImage(img, g.getWidth() - 48, g.getHeight() - 48 - 24); + } + g.setFont("Vector", 18); + g.setFontAlign(0, 1); + g.drawString(text1, g.getWidth() / 2, g.getHeight() / 3 + 24); + if (text2 != undefined) { + g.setFont("Vector", 12); + g.setFontAlign(-1, -1); + g.drawString(text2, 5, g.getHeight() / 3 + 29); + } + Bangle.drawWidgets(); +} + +// Show launcher when middle button pressed +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +let waiting = false; + +function start() { + g.reset(); + g.clear(); + waiting = false; + display("Retry?", "touch to retry"); + Bangle.on("touch", () => { updateAgps(); }); +} + +function updateAgps() { + g.reset(); + g.clear(); + if (!waiting) { + waiting = true; + display("Updating A-GPS...", "takes ~ 10 seconds"); + require("agpsdata").pull(function() { + waiting = false; + display("A-GPS updated.", "touch to close"); + Bangle.on("touch", () => { load(); }); + }, + function(error) { + waiting = false; + E.showAlert(error, "Error") + .then(() => { start(); }); + }); + } else { + display("Waiting..."); + } +} +updateAgps(); diff --git a/apps/agpsdata/boot.js b/apps/agpsdata/boot.js new file mode 100644 index 000000000..2b1e6819c --- /dev/null +++ b/apps/agpsdata/boot.js @@ -0,0 +1,26 @@ +(function() { + let waiting = false; + let settings = require("Storage").readJSON("agpsdata.settings.json", 1) || { + enabled: true, + refresh: 1440 + }; + + if (settings.refresh == undefined) settings.refresh = 1440; + + function successCallback(){ + waiting = false; + } + + function errorCallback(){ + waiting = false; + } + + if (settings.enabled) { + setInterval(() => { + if (!waiting && NRF.getSecurityStatus().connected){ + waiting = true; + require("agpsdata").pull(successCallback, errorCallback); + } + }, settings.refresh * 1000 * 60); + } +})(); diff --git a/apps/agpsdata/default.json b/apps/agpsdata/default.json new file mode 100644 index 000000000..0b6e0cecf --- /dev/null +++ b/apps/agpsdata/default.json @@ -0,0 +1 @@ +{"enabled":true,"refresh":1440,"gnsstype":1} diff --git a/apps/agpsdata/lib.js b/apps/agpsdata/lib.js new file mode 100644 index 000000000..34608a5c6 --- /dev/null +++ b/apps/agpsdata/lib.js @@ -0,0 +1,93 @@ +function readSettings() { + settings = Object.assign( + require('Storage').readJSON("agpsdata.default.json", true) || {}, + require('Storage').readJSON(FILE, true) || {}); +} + +var FILE = "agpsdata.settings.json"; +var settings; +readSettings(); + +function setAGPS(b64) { + return new Promise(function(resolve, reject) { + var initCommands = "Bangle.setGPSPower(1);\n"; // turn GPS on + const gnsstype = settings.gnsstype || 1; // default GPS + initCommands += `Serial1.println("${CASIC_CHECKSUM("$PCAS04," + gnsstype)}")\n`; // set GNSS mode + // What about: + // NAV-TIMEUTC (0x01 0x10) + // NAV-PV (0x01 0x03) + // or AGPS.zip uses AID-INI (0x0B 0x01) + + eval(initCommands); + + try { + writeChunks(atob(b64), resolve); + } catch (e) { + console.log("error:", e); + reject(); + } + }); +} + +var chunkI = 0; +function writeChunks(bin, resolve) { + return new Promise(function(resolve2) { + const chunkSize = 128; + setTimeout(function() { + if (chunkI < bin.length) { + var chunk = bin.substr(chunkI, chunkSize); + js = `Serial1.write(atob("${btoa(chunk)}"))\n`; + eval(js); + + chunkI += chunkSize; + writeChunks(bin, resolve); + } else { + if (resolve) + resolve(); // call outer resolve + } + }, 200); + }); +} + +function CASIC_CHECKSUM(cmd) { + var cs = 0; + for (var i = 1; i < cmd.length; i++) + cs = cs ^ cmd.charCodeAt(i); + return cmd + "*" + cs.toString(16).toUpperCase().padStart(2, '0'); +} + +function updateLastUpdate() { + const file = "agpsdata.json"; + let data = require("Storage").readJSON(file, 1) || {}; + data.lastUpdate = Math.round(Date.now()); + require("Storage").writeJSON(file, data); +} + +exports.pull = function(successCallback, failureCallback) { + const uri = "https://www.espruino.com/agps/casic.base64"; + if (Bangle.http) { + Bangle.http(uri, {timeout : 10000}) + .then(event => { + setAGPS(event.resp) + .then(r => { + updateLastUpdate(); + if (successCallback) + successCallback(); + }) + .catch((e) => { + console.log("error", e); + if (failureCallback) + failureCallback(e); + }); + }) + .catch((e) => { + console.log("error", e); + if (failureCallback) + failureCallback(e); + }); + } else { + console.log("error: No http method found"); + if (failureCallback) + failureCallback(/*LANG*/ "No http method"); + } +}; diff --git a/apps/agpsdata/metadata.json b/apps/agpsdata/metadata.json new file mode 100644 index 000000000..203a00f72 --- /dev/null +++ b/apps/agpsdata/metadata.json @@ -0,0 +1,24 @@ +{ "id": "agpsdata", + "name": "A-GPS Data Downloader App", + "shortName":"A-GPS Data", + "icon": "agpsdata.png", + "version":"0.04", + "description": "Once installed, this app allows you to download assisted GPS (A-GPS) data directly to your Bangle.js **via Gadgetbridge on an Android phone** when you run the app. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.", + "tags": "boot,tool,assisted,gps,agps,http", + "allow_emulator":true, + "supports": ["BANGLEJS2"], + "readme":"README.md", + "screenshots" : [ { "url":"screenshot.png" }, { "url":"screenshot2.png" } ], + "storage": [ + {"name":"agpsdata.app.js","url":"app.js"}, + {"name":"agpsdata.img","url":"agpsdata-icon.js","evaluate":true}, + {"name":"agpsdata.default.json","url":"default.json"}, + {"name":"agpsdata.boot.js","url":"boot.js"}, + {"name":"agpsdata","url":"lib.js"}, + {"name":"agpsdata.settings.js","url":"settings.js"} + ], + "data": [ + {"name": "agpsdata.json"}, + {"name": "agpsdata.settings.json"} + ] +} diff --git a/apps/agpsdata/screenshot.png b/apps/agpsdata/screenshot.png new file mode 100644 index 000000000..1fcb2d8ee Binary files /dev/null and b/apps/agpsdata/screenshot.png differ diff --git a/apps/agpsdata/screenshot2.png b/apps/agpsdata/screenshot2.png new file mode 100644 index 000000000..7c546e4b5 Binary files /dev/null and b/apps/agpsdata/screenshot2.png differ diff --git a/apps/agpsdata/settings.js b/apps/agpsdata/settings.js new file mode 100644 index 000000000..64fa25330 --- /dev/null +++ b/apps/agpsdata/settings.js @@ -0,0 +1,71 @@ +(function(back) { +function writeSettings(key, value) { + var s = Object.assign( + require('Storage').readJSON(settingsDefaultFile, true) || {}, + require('Storage').readJSON(settingsFile, true) || {}); + s[key] = value; + require('Storage').writeJSON(settingsFile, s); + readSettings(); +} + +function readSettings() { + settings = Object.assign( + require('Storage').readJSON(settingsDefaultFile, true) || {}, + require('Storage').readJSON(settingsFile, true) || {}); +} + +var settingsFile = "agpsdata.settings.json"; +var settingsDefaultFile = "agpsdata.default.json"; + +var settings; +readSettings(); + +const gnsstypes = [ + "", "GPS", "BDS", "GPS+BDS", "GLONASS", "GPS+GLONASS", "BDS+GLONASS", + "GPS+BDS+GLON." +]; + +function buildMainMenu() { + var mainmenu = { + '' : {'title' : 'AGPS download'}, + '< Back' : back, + "Enabled" : { + value : !!settings.enabled, + onchange : v => { writeSettings("enabled", v); } + }, + "Refresh every" : { + value : settings.refresh / 60, + min : 6, + max : 168, + step : 1, + format : v => v + "h", + onchange : v => { writeSettings("refresh", Math.round(v * 60)); } + }, + "GNSS type" : { + value : settings.gnsstype, + min : 1, + max : 7, + step : 1, + format : v => gnsstypes[v], + onchange : x => writeSettings('gnsstype', x) + }, + "Force refresh" : () => { + E.showMessage("Loading A-GPS data"); + require("agpsdata") + .pull( + function() { + E.showAlert("Success").then( + () => { E.showMenu(buildMainMenu()); }); + }, + function(error) { + E.showAlert(error, "Error") + .then(() => { E.showMenu(buildMainMenu()); }); + }); + } + }; + + return mainmenu; +} + +E.showMenu(buildMainMenu()); +}); diff --git a/apps/aiclock/ChangeLog b/apps/aiclock/ChangeLog new file mode 100644 index 000000000..fb5aed3e3 --- /dev/null +++ b/apps/aiclock/ChangeLog @@ -0,0 +1,5 @@ +0.01: New app! +0.02: Design improvements and fixes. +0.03: Indicate battery level through line occurrence. +0.04: Use widget_utils module. +0.05: Support for clkinfo. \ No newline at end of file diff --git a/apps/aiclock/README.md b/apps/aiclock/README.md new file mode 100644 index 000000000..31dd5aa29 --- /dev/null +++ b/apps/aiclock/README.md @@ -0,0 +1,25 @@ +# AI Clock +This clock was designed by stable diffusion ([paper](https://arxiv.org/abs/2112.10752)) using the following prompt: + +`A rectangle banglejs watchface` + + +The original output of stable diffusion is shown here: + +![](orig.png) + +My implementation is shown below. Note that horizontal lines occur randomly, but the +probability is correlated with the battery level. So if your screen contains only +a few lines its time to charge your bangle again ;) Also note that the upper text +implementes the clkinfo module and can be configured via touch left/right/up/down. +Touch at the center to trigger the selected action. + +![](impl.png) + + +# Thanks to +The great open-source community: I used an open-source diffusion model (https://github.com/CompVis/stable-diffusion) +to generate a watch face for the open-source smartwatch BangleJs. + +## Creator +- [David Peer](https://github.com/peerdavid). \ No newline at end of file diff --git a/apps/aiclock/aiclock.app.js b/apps/aiclock/aiclock.app.js new file mode 100644 index 000000000..b5bb30b9d --- /dev/null +++ b/apps/aiclock/aiclock.app.js @@ -0,0 +1,437 @@ +/************************************************ + * AI Clock + */ + const storage = require('Storage'); + const clock_info = require("clock_info"); + + + + /************************************************ + * Assets + */ +require("Font7x11Numeric7Seg").add(Graphics); +Graphics.prototype.setFontGochiHand = function(scale) { + // Actual height 27 (29 - 3) + this.setFontCustom( + atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAA8AAAAADwAAAAAPAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAA/+AAAB//4AAH///gAH///gAAf//AAAB/+AAAAH8AAAAAAAAAAAAAAAAAAAAH8AAAAB/8AAAAP/4AAAB//wAAAPx/AAAB8B+AAAHgD4AAA+AHgAADwAeAAAPAB4AAA8AHgAAD4AeAAAPgB4AAAeAPgAAB8A8AAAH4HwAAAP/+AAAAf/wAAAA/+AAAAB/wAAAAB8AAAAAAAAAAADgAAAAAfAAAAAB4AAAAAPAAAAAB8AAAAAHgAAAAA8AAAAADwAAAAAf4AAAAB//8AAAD//4AAAH//gAAAD/+AAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AHgAAHgA+AAA/AD4AAD4AfgAAfAD+AAB4Af4AAHgD/gAAeAfeAAB4D54AAHw/HgAAf/4fAAA//B8AAD/4DwAAH+APAAAHgA8AAAAADwAAAAAOAAAAAAAAABgAAAAAPAAAAAB8AAAAAHwAYAAAeAD4AAD4APwAAPA4fgAA8Hw+AADwfB4AAPh4HwAA+HgPAAB/+A8AAH/4DwAAP/weAAAf/j4AAAc//gAAAB/8AAAAD/gAAAAD8AAAAAAAAAAAAAAAAAADAAAAAA+AAAAAP4AAAAB/wAAAAP/AAAAD+8AAAAfzwAAAf8HAAAB/gcAAAH/hwAAAf//gAAA//+AAAAf//gAAAP//gAAAD/+AAAAB/4AAAAH/AAAAAeAAAAAAgAAAAAAAAAAAAcAAAB8H8AAAP4f4AAA/x/wAAD/H/gAAf+A+AAB74B4AAHnwHgAAefAfAAB58A8AAHj4DwAAePgPAAB4fA8AAHh+HgAAeD8+AAB4P/4AAHgf/AAAeA/4AAAAA+AAAAAAAAAAAAAAAAAAHgAAAAD/wAAAA//gAAAH//AAAA//+AAAD4H8AAAfA/wAAB4D/AAAHgP+AAAeB54AAB4HngAAHweeAAAfB54AAA4HngAAAAeeAAAAB/4AAAAH/AAAAAP4AAAAAfAAADwAAAAAPAAAAAA8HgAAADweAAAAPB4AAAA8HgAAADweAAAAPh4AAAA+HgAAAB4eAAAAHx4AAAAf//8AAA///wAAD//+AAAH//4AAAAeAAAAAB4AAAAAHgAAAAAeAAAAAB4AAAAAHgAAAAAAAAAAAAAAAAAAD+AAAA+f+AAAH//8AAA///wAAH/4fgAAePgeAAB4+B4AAHj4HwAAePgPAAB4+A8AAHz4DwAAfngeAAA//B4AAD/+HgAAH//8AAAP//wAAAAf+AAAAA/wAAAAAYAAAAAAAAAAA/gAAAAH/AAAAA/8AAAAD34AAAAeHgAAAB4eAAAAHh4AAAA8HgAAADweAAAAPDwAAAA8PAAAADx4AAAAPvgAAAAf///AAB///8AAH///wAAP///AAA/wA4AABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOA4AAAA8DwAAADwPAAAAPA8AAAAYBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='), + 46, + atob("DQoXEBQVExUUFRYUDQ=="), + 40+(scale<<8)+(1<<16) + ); + return this; +} + +/************************************************ + * Set some important constants such as width, height and center + */ +var W = g.getWidth(),R=W/2; +var H = g.getHeight(); +var cx = W/2; +var cy = H/2; +var drawTimeout; +var lock_input = false; + + +/************************************************ + * SETTINGS + */ +const SETTINGS_FILE = "aiclock.setting.json"; +let settings = { + menuPosX: 0, + menuPosY: 0, +}; +let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; +for (const key in saved_settings) { + settings[key] = saved_settings[key] +} + + +/************************************************ + * Menu + */ +function getDate(){ + var date = new Date(); + return ("0"+date.getDate()).substr(-2) + "/" + ("0"+(date.getMonth()+1)).substr(-2) +} + + +// Custom clockItems menu - therefore, its added here and not in a clkinfo.js file. +var clockItems = { + name: getDate(), + img: null, + items: [ + { name: "Week", + get: () => ({ text: "Week " + weekOfYear(), img: null}), + show: function() { clockItems.items[0].emit("redraw"); }, + hide: function () {} + }, + ] + }; + +function weekOfYear() { + var date = new Date(); + date.setHours(0, 0, 0, 0); + // Thursday in current week decides the year. + date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7); + // January 4 is always in week 1. + var week1 = new Date(date.getFullYear(), 0, 4); + // Adjust to Thursday in week 1 and count number of weeks from date to week1. + return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000 + - 3 + (week1.getDay() + 6) % 7) / 7); +} + + + +// Load menu +var menu = clock_info.load(); +menu = menu.concat(clockItems); + + + // Ensure that our settings are still in range (e.g. app uninstall). Otherwise reset the position it. + if(settings.menuPosX >= menu.length || settings.menuPosY > menu[settings.menuPosX].items.length ){ + settings.menuPosX = 0; + settings.menuPosY = 0; + } + + // Set draw functions for each item + menu.forEach((menuItm, x) => { + menuItm.items.forEach((item, y) => { + function drawItem() { + // For the clock, we have a special case, as we don't wanna redraw + // immediately when something changes. Instead, we update data each minute + // to save some battery etc. Therefore, we hide (and disable the listener) + // immedeately after redraw... + item.hide(); + + // After drawing the item, we enable inputs again... + lock_input = false; + + var info = item.get(); + drawMenuItem(info.text, info.img); + } + + item.on('redraw', drawItem); + }) + }); + + + function canRunMenuItem(){ + if(settings.menuPosY == 0){ + return false; + } + + var menuEntry = menu[settings.menuPosX]; + var item = menuEntry.items[settings.menuPosY-1]; + return item.run !== undefined; + } + + + function runMenuItem(){ + if(settings.menuPosY == 0){ + return; + } + + var menuEntry = menu[settings.menuPosX]; + var item = menuEntry.items[settings.menuPosY-1]; + try{ + var ret = item.run(); + if(ret){ + Bangle.buzz(300, 0.6); + } + } catch (ex) { + // Simply ignore it... + } + } + + +/* + * Based on the great multi clock from https://github.com/jeffmer/BangleApps/ + */ +Graphics.prototype.drawRotRect = function(w, r1, r2, angle) { + angle = angle % 360; + var w2=w/2, h=r2-r1, theta=angle*Math.PI/180; + return this.fillPoly(this.transformVertices([-w2,0,-w2,-h,w2,-h,w2,0], + {x:cx+r1*Math.sin(theta),y:cy-r1*Math.cos(theta),rotate:theta})); +}; + + +function drawBackground() { + g.setFontAlign(0,0); + g.setColor(g.theme.fg); + + var bat = E.getBattery() / 100.0; + var y = 0; + while(y < H){ + // Show less lines in case of small battery level. + if(Math.random() > bat){ + y += 5; + continue; + } + + y += 3 + Math.floor(Math.random() * 10); + g.drawLine(0, y, W, y); + g.drawLine(0, y+1, W, y+1); + g.drawLine(0, y+2, W, y+2); + y += 2; + } +} + + +function drawCircle(isLocked){ + g.setColor(g.theme.fg); + g.fillCircle(cx, cy, 12); + + var c = isLocked ? "#f00" : g.theme.bg; + g.setColor(c); + g.fillCircle(cx, cy, 6); +} + +function toAngle(a){ + if (a < 0){ + return 360 + a; + } + + if(a > 360) { + return 360 - a; + } + + return a +} + + +function drawMenuItem(text, image){ + if(text == null){ + drawTime(); + return + } + // image = atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA=="); + + text = String(text); + + g.reset().setBgColor("#fff").setColor("#000"); + g.setFontAlign(0,0); + g.setFont("Vector", 20); + + var imgWidth = image == null ? 0 : 24; + var strWidth = g.stringWidth(text); + var strHeight = text.split('\n').length > 1 ? 40 : Math.max(24, imgWidth+2); + var w = imgWidth + strWidth; + + g.clearRect(cx-w/2-8, 40-strHeight/2-1, cx+w/2+4, 40+strHeight/2) + + // Draw right line as designed by stable diffusion + g.drawLine(cx+w/2+5, 40-strHeight/2-1, cx+w/2+5, 40+strHeight/2); + g.drawLine(cx+w/2+6, 40-strHeight/2-1, cx+w/2+6, 40+strHeight/2); + g.drawLine(cx+w/2+7, 40-strHeight/2-1, cx+w/2+7, 40+strHeight/2); + + // And finally the text + g.drawString(text, cx+imgWidth/2, 42); + g.drawString(text, cx+1+imgWidth/2, 41); + + if(image != null) { + var scale = image.width ? imgWidth / image.width : 1; + g.drawImage(image, W/2 + -strWidth/2-4 - parseInt(imgWidth/2), 41-12, {scale: scale}); + } + + drawTime(); +} + + +function drawTime(){ + // Draw digital time first + drawDigits(); + + // And now the analog time + var drawHourHand = g.drawRotRect.bind(g,8,12,R-38); + var drawMinuteHand = g.drawRotRect.bind(g,6,12,R-12 ); + + g.setFontAlign(0,0); + + // Compute angles + var date = new Date(); + var m = parseInt(date.getMinutes() * 360 / 60); + var h = date.getHours(); + h = h > 12 ? h-12 : h; + h += date.getMinutes()/60.0; + h = parseInt(h*360/12); + + // Draw minute and hour fg + g.setColor(g.theme.fg); + drawHourHand(h); + drawMinuteHand(m); +} + + +function drawDigits(){ + var date = new Date(); + + g.setFontAlign(0,0); + g.setFont("7x11Numeric7Seg",3); + + var text = ("0"+date.getHours()).substr(-2) + ":" + ("0"+date.getMinutes()).substr(-2); //Bangle.getHealthStatus("day").steps; + var w = g.stringWidth(text); + g.setColor(g.theme.bg); + g.fillRect(cx-w/2-4, 120, cx+w/2+4, 140+20); + + // Draw right line as designed by stable diffusion + g.setColor(g.theme.fg); + g.drawLine(cx+w/2+5, 120, cx+w/2+5, 140+20); + g.drawLine(cx+w/2+6, 120, cx+w/2+6, 140+20); + g.drawLine(cx+w/2+7, 120, cx+w/2+7, 140+20); + + // And the 7set text + g.setColor("#BBB"); + g.drawString("88:88", cx, 140); + g.drawString("88:88", cx+1, 140); + g.drawString("88:88", cx, 141); + + g.setColor(g.theme.fg); + g.drawString(text, cx, 140); + g.drawString(text, cx+1, 140); + g.drawString(text, cx, 141); +} + + +function drawDate(){ + var menuEntry = menu[settings.menuPosX]; + + // The first entry is the overview... + if(settings.menuPosY == 0){ + drawMenuItem(menuEntry.name, menuEntry.img); + return; + } + + // Draw item if needed + lock_input = true; + var item = menuEntry.items[settings.menuPosY-1]; + item.show(); +} + + + + + +function draw(){ + // Queue draw in one minute + queueDraw(); + + g.reset(); + g.clearRect(0, 0, g.getWidth(), g.getHeight()); + g.setColor(1,1,1); + + drawBackground(); + drawDate(); + drawCircle(Bangle.isLocked()); +} + + +/* + * Listeners + */ +Bangle.on('lcdPower',on=>{ + if (on) { + draw(true); + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +Bangle.on('lock', function(isLocked) { + drawCircle(isLocked); +}); + +Bangle.on('touch', function(btn, e){ + var left = parseInt(g.getWidth() * 0.22); + var right = g.getWidth() - left; + var upper = parseInt(g.getHeight() * 0.22); + var lower = g.getHeight() - upper; + + var is_upper = e.y < upper; + var is_lower = e.y > lower; + var is_left = e.x < left && !is_upper && !is_lower; + var is_right = e.x > right && !is_upper && !is_lower; + var is_center = !is_upper && !is_lower && !is_left && !is_right; + + if(lock_input){ + return; + } + + if(is_lower){ + Bangle.buzz(40, 0.6); + settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1); + + draw(); + } + + if(is_upper){ + Bangle.buzz(40, 0.6); + settings.menuPosY = settings.menuPosY-1; + settings.menuPosY = settings.menuPosY < 0 ? menu[settings.menuPosX].items.length : settings.menuPosY; + + draw(); + } + + if(is_right){ + Bangle.buzz(40, 0.6); + settings.menuPosX = (settings.menuPosX+1) % menu.length; + settings.menuPosY = 0; + draw(); + } + + if(is_left){ + Bangle.buzz(40, 0.6); + settings.menuPosY = 0; + settings.menuPosX = settings.menuPosX-1; + settings.menuPosX = settings.menuPosX < 0 ? menu.length-1 : settings.menuPosX; + draw(); + } + + if(is_center){ + if(canRunMenuItem()){ + runMenuItem(); + } + } +}); + + +E.on("kill", function(){ + try{ + storage.write(SETTINGS_FILE, settings); + } catch(ex){ + // If this fails, we still kill the app... + } +}); + + +/* + * Some helpers + */ +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + + +/* + * Lets start widgets, listen for btn etc. + */ +// Show launcher when middle button pressed +Bangle.setUI("clock"); +Bangle.loadWidgets(); +/* + * we are not drawing the widgets as we are taking over the whole screen + * so we will blank out the draw() functions of each widget and change the + * area to the top bar doesn't get cleared. + */ +require('widget_utils').hide(); + +// Clear the screen once, at startup and draw clock +g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear(); +draw(); + +// After drawing the watch face, we can draw the widgets +// Bangle.drawWidgets(); diff --git a/apps/aiclock/aiclock.icon.js b/apps/aiclock/aiclock.icon.js new file mode 100644 index 000000000..0033b3848 --- /dev/null +++ b/apps/aiclock/aiclock.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgP/ACfAEZU/ECZELIKhSR/+PAoWAv4FDhk/x/ggP+j0fx/AgP8n8PCIX8CwIFC/F/w4FBgP4gEHC4QFE//w//DC4QFB8YFC+P/8IdCAoYdBAoPxDoQAd+CiKh4dQwDhfAA4A=")) \ No newline at end of file diff --git a/apps/aiclock/aiclock.png b/apps/aiclock/aiclock.png new file mode 100644 index 000000000..104261254 Binary files /dev/null and b/apps/aiclock/aiclock.png differ diff --git a/apps/aiclock/impl.png b/apps/aiclock/impl.png new file mode 100644 index 000000000..8a9e43e2d Binary files /dev/null and b/apps/aiclock/impl.png differ diff --git a/apps/aiclock/impl_2.png b/apps/aiclock/impl_2.png new file mode 100644 index 000000000..be3519a4b Binary files /dev/null and b/apps/aiclock/impl_2.png differ diff --git a/apps/aiclock/impl_3.png b/apps/aiclock/impl_3.png new file mode 100644 index 000000000..c2a036d14 Binary files /dev/null and b/apps/aiclock/impl_3.png differ diff --git a/apps/aiclock/metadata.json b/apps/aiclock/metadata.json new file mode 100644 index 000000000..1dcda427f --- /dev/null +++ b/apps/aiclock/metadata.json @@ -0,0 +1,22 @@ +{ + "id": "aiclock", + "name": "AI Clock", + "shortName":"AI Clock", + "icon": "aiclock.png", + "version":"0.05", + "readme": "README.md", + "supports": ["BANGLEJS2"], + "description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.", + "type": "clock", + "tags": "clock", + "screenshots": [ + {"url":"orig.png"}, + {"url":"impl.png"}, + {"url":"impl_2.png"}, + {"url":"impl_3.png"} + ], + "storage": [ + {"name":"aiclock.app.js","url":"aiclock.app.js"}, + {"name":"aiclock.img","url":"aiclock.icon.js","evaluate":true} + ] +} diff --git a/apps/aiclock/orig.png b/apps/aiclock/orig.png new file mode 100644 index 000000000..009826454 Binary files /dev/null and b/apps/aiclock/orig.png differ diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog index d129e9f9f..9994d33d9 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -13,3 +13,27 @@ Widgets now shown on Alarm screen 0.13: Alarm widget state now updates when setting/resetting an alarm 0.14: Order of 'back' menu item +0.15: Fix hour/minute wrapping code for new menu system +0.16: Adding alarm library +0.17: Moving alarm internals to 'sched' library +0.18: Cope with >1 identical alarm at once (#1667) +0.19: Ensure rescheduled alarms that already fired have 'last' reset +0.20: Use the new 'sched' factories to initialize new alarms/timers +0.21: Fix time reset after a day of week change (#1676) +0.22: Refactor some methods to scheduling library +0.23: Fix regression with Days of Week (#1735) +0.24: Automatically save the alarm/timer when the user returns to the main menu using the back arrow + Add "Enable All", "Disable All" and "Remove All" actions +0.25: Fix redrawing selected Alarm/Timer entry inside edit submenu +0.26: Add support for Monday as first day of the week (#1780) +0.27: New UI! +0.28: Fix bug with alarms not firing when configured to fire only once +0.29: Fix wrong 'dow' handling in new timer if first day of week is Monday +0.30: Fix "Enable All" +0.31: Add seconds to timers +0.32: Fix wrong hidden filter + Add option for auto-delete a timer after it expires +0.33: Allow hiding timers&alarms +0.34: Add "Confirm" option to alarm/timer edit menus +0.35: Add automatic translation of more strings +0.36: alarm widget moved out of app diff --git a/apps/alarm/README.md b/apps/alarm/README.md new file mode 100644 index 000000000..741946b0c --- /dev/null +++ b/apps/alarm/README.md @@ -0,0 +1,31 @@ +# Alarms & Timers + +This app allows you to add/modify any alarms and timers. + +It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) to handle the alarm scheduling in an efficient way that can work alongside other apps. + +## Menu overview + +- `New...` + - `New Alarm` → Configure a new alarm + - `Repeat` → Select when the alarm will fire. You can select a predefined option (_Once_, _Every Day_, _Workdays_ or _Weekends_ or you can configure the days freely) + - `New Timer` → Configure a new timer +- `Advanced` + - `Scheduler settings` → Open the [Scheduler](https://github.com/espruino/BangleApps/tree/master/apps/sched) settings page, see its [README](https://github.com/espruino/BangleApps/blob/master/apps/sched/README.md) for details + - `Enable All` → Enable _all_ disabled alarms & timers + - `Disable All` → Disable _all_ enabled alarms & timers + - `Delete All` → Delete _all_ alarms & timers + +## Creator + +- [Gordon Williams](https://github.com/gfwilliams) + +## Main Contributors + +- [Alessandro Cocco](https://github.com/alessandrococco) - New UI, full rewrite, new features +- [Sabin Iacob](https://github.com/m0n5t3r) - Auto snooze support +- [storm64](https://github.com/storm64) - Fix redrawing in submenus + +## Attributions + +All icons used in this app are from [icons8](https://icons8.com). diff --git a/apps/alarm/alarm.js b/apps/alarm/alarm.js deleted file mode 100644 index a655dad1e..000000000 --- a/apps/alarm/alarm.js +++ /dev/null @@ -1,72 +0,0 @@ -// Chances are boot0.js got run already and scheduled *another* -// 'load(alarm.js)' - so let's remove it first! -clearInterval(); - -function formatTime(t) { - var hrs = 0|t; - var mins = Math.round((t-hrs)*60); - return hrs+":"+("0"+mins).substr(-2); -} - -function getCurrentHr() { - var time = new Date(); - return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600); -} - -function showAlarm(alarm) { - var msg = formatTime(alarm.hr); - var buzzCount = 10; - if (alarm.msg) - msg += "\n"+alarm.msg; - Bangle.loadWidgets(); - Bangle.drawWidgets(); - E.showPrompt(msg,{ - title:alarm.timer ? /*LANG*/"TIMER!" : /*LANG*/"ALARM!", - buttons : {/*LANG*/"Sleep":true,/*LANG*/"Ok":false} // default is sleep so it'll come back in 10 mins - }).then(function(sleep) { - buzzCount = 0; - if (sleep) { - if(alarm.ohr===undefined) alarm.ohr = alarm.hr; - alarm.hr += 10/60; // 10 minutes - } else { - alarm.last = (new Date()).getDate(); - if (alarm.ohr!==undefined) { - alarm.hr = alarm.ohr; - delete alarm.ohr; - } - if (!alarm.rp) alarm.on = false; - } - require("Storage").write("alarm.json",JSON.stringify(alarms)); - load(); - }); - function buzz() { - if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence - Bangle.buzz(100).then(()=>{ - setTimeout(()=>{ - Bangle.buzz(100).then(function() { - if (buzzCount--) - setTimeout(buzz, 3000); - else if(alarm.as) { // auto-snooze - buzzCount = 10; - setTimeout(buzz, 600000); - } - }); - },100); - }); - } - buzz(); -} - -// Check for alarms -var day = (new Date()).getDate(); -var hr = getCurrentHr()+10000; // get current time - 10s in future to ensure we alarm if we've started the app a tad early -var alarms = require("Storage").readJSON("alarm.json",1)||[]; -var active = alarms.filter(a=>a.on&&(a.hra.hr-b.hr); - showAlarm(active[0]); -} else { - // otherwise just go back to default app - setTimeout(load, 100); -} diff --git a/apps/alarm/app.js b/apps/alarm/app.js index 17062d44a..1414c0b90 100644 --- a/apps/alarm/app.js +++ b/apps/alarm/app.js @@ -1,181 +1,396 @@ Bangle.loadWidgets(); Bangle.drawWidgets(); -var alarms = require("Storage").readJSON("alarm.json",1)||[]; -/*alarms = [ - { on : true, - hr : 6.5, // hours + minutes/60 - msg : "Eat chocolate", - last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day! - rp : true, // repeat - as : false, // auto snooze - timer : 5, // OPTIONAL - if set, this is a timer and it's the time in minutes +// 0 = Sunday (default), 1 = Monday +const firstDayOfWeek = (require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0; +const WORKDAYS = 62 +const WEEKEND = firstDayOfWeek ? 192 : 65; +const EVERY_DAY = firstDayOfWeek ? 254 : 127; + +const iconAlarmOn = "\0" + atob("GBiBAAAAAAAAAAYAYA4AcBx+ODn/nAP/wAf/4A/n8A/n8B/n+B/n+B/n+B/n+B/h+B/4+A/+8A//8Af/4AP/wAH/gAB+AAAAAAAAAA=="); +const iconAlarmOff = "\0" + (g.theme.dark + ? atob("GBjBAP////8AAAAAAAAGAGAOAHAcfjg5/5wD/8AH/+AP5/AP5/Af5/gf5/gf5wAf5gAf4Hgf+f4P+bYP8wMH84cD84cB8wMAebYAAf4AAHg=") + : atob("GBjBAP//AAAAAAAAAAAGAGAOAHAcfjg5/5wD/8AH/+AP5/AP5/Af5/gf5/gf5wAf5gAf4Hgf+f4P+bYP8wMH84cD84cB8wMAebYAAf4AAHg=")); + +const iconTimerOn = "\0" + (g.theme.dark + ? atob("GBjBAP////8AAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5wAAwwABgYABgYABgYAH/+AH/+AAAAAAAAAAAAA=") + : atob("GBjBAP//AAAAAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5wAAwwABgYABgYABgYAH/+AH/+AAAAAAAAAAAAA=")); +const iconTimerOff = "\0" + (g.theme.dark + ? atob("GBjBAP////8AAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5HgAwf4BgbYBgwMBg4cH84cH8wMAAbYAAf4AAHg=") + : atob("GBjBAP//AAAAAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5HgAwf4BgbYBgwMBg4cH84cH8wMAAbYAAf4AAHg=")); + +// An array of alarm objects (see sched/README.md) +var alarms = require("sched").getAlarms(); + +function handleFirstDayOfWeek(dow) { + if (firstDayOfWeek == 1) { + if ((dow & 1) == 1) { + // In the scheduler API Sunday is 1. + // Here the week starts on Monday and Sunday is ON so + // when I read the dow I need to move Sunday to 128... + dow += 127; + } else if ((dow & 128) == 128) { + // ... and then when I write the dow I need to move Sunday back to 1. + dow -= 127; + } } -];*/ - -function formatTime(t) { - var hrs = 0|t; - var mins = Math.round((t-hrs)*60); - return hrs+":"+("0"+mins).substr(-2); + return dow; } -function formatMins(t) { - mins = (0|t)%60; - hrs = 0|(t/60); - return hrs+":"+("0"+mins).substr(-2); -} - -function getCurrentHr() { - var time = new Date(); - return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600); -} +// Check the first day of week and update the dow field accordingly (alarms only!) +alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow)); function showMainMenu() { const menu = { - '': { 'title': 'Alarm/Timer' }, - /*LANG*/'< Back' : ()=>{load();}, - /*LANG*/'New Alarm': ()=>editAlarm(-1), - /*LANG*/'New Timer': ()=>editTimer(-1) + "": { "title": /*LANG*/"Alarms & Timers" }, + "< Back": () => load(), + /*LANG*/"New...": () => showNewMenu() }; - alarms.forEach((alarm,idx)=>{ - if (alarm.timer) { - txt = /*LANG*/"TIMER "+(alarm.on?/*LANG*/"on ":/*LANG*/"off ")+formatMins(alarm.timer); - } else { - txt = /*LANG*/"ALARM "+(alarm.on?/*LANG*/"on ":/*LANG*/"off ")+formatTime(alarm.hr); - if (alarm.rp) txt += /*LANG*/" (repeat)"; - } - menu[txt] = function() { - if (alarm.timer) editTimer(idx); - else editAlarm(idx); + + alarms.forEach((e, index) => { + var label = e.timer + ? require("time_utils").formatDuration(e.timer) + : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : ""); + menu[label] = { + value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff), + onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index) }; }); - if (WIDGETS["alarm"]) WIDGETS["alarm"].reload(); - return E.showMenu(menu); + menu[/*LANG*/"Advanced"] = () => showAdvancedMenu(); + + E.showMenu(menu); } -function editAlarm(alarmIndex) { - var newAlarm = alarmIndex<0; - var hrs = 12; - var mins = 0; - var en = true; - var repeat = true; - var as = false; - if (!newAlarm) { - var a = alarms[alarmIndex]; - hrs = 0|a.hr; - mins = Math.round((a.hr-hrs)*60); - en = a.on; - repeat = a.rp; - as = a.as; - } - const menu = { - '': { 'title': /*LANG*/'Alarm' }, - /*LANG*/'< Back' : showMainMenu, - /*LANG*/'Hours': { - value: hrs, - onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this' - }, - /*LANG*/'Minutes': { - value: mins, - onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this' - }, - /*LANG*/'Enabled': { - value: en, - format: v=>v?"On":"Off", - onchange: v=>en=v - }, - /*LANG*/'Repeat': { - value: en, - format: v=>v?"Yes":"No", - onchange: v=>repeat=v - }, - /*LANG*/'Auto snooze': { - value: as, - format: v=>v?"Yes":"No", - onchange: v=>as=v - } - }; - function getAlarm() { - var hr = hrs+(mins/60); - var day = 0; - // If alarm is for tomorrow not today (eg, in the past), set day - if (hr < getCurrentHr()) - day = (new Date()).getDate(); - // Save alarm - return { - on : en, hr : hr, - last : day, rp : repeat, as: as - }; - } - menu[/*LANG*/"> Save"] = function() { - if (newAlarm) alarms.push(getAlarm()); - else alarms[alarmIndex] = getAlarm(); - require("Storage").write("alarm.json",JSON.stringify(alarms)); - showMainMenu(); - }; - if (!newAlarm) { - menu[/*LANG*/"> Delete"] = function() { - alarms.splice(alarmIndex,1); - require("Storage").write("alarm.json",JSON.stringify(alarms)); - showMainMenu(); - }; - } - return E.showMenu(menu); +function showNewMenu() { + E.showMenu({ + "": { "title": /*LANG*/"New..." }, + "< Back": () => showMainMenu(), + /*LANG*/"Alarm": () => showEditAlarmMenu(undefined, undefined), + /*LANG*/"Timer": () => showEditTimerMenu(undefined, undefined) + }); } -function editTimer(alarmIndex) { - var newAlarm = alarmIndex<0; - var hrs = 0; - var mins = 5; - var en = true; - if (!newAlarm) { - var a = alarms[alarmIndex]; - mins = (0|a.timer)%60; - hrs = 0|(a.timer/60); - en = a.on; +function showEditAlarmMenu(selectedAlarm, alarmIndex) { + var isNew = alarmIndex === undefined; + + var alarm = require("sched").newDefaultAlarm(); + alarm.dow = handleFirstDayOfWeek(alarm.dow); + + if (selectedAlarm) { + Object.assign(alarm, selectedAlarm); } + + var time = require("time_utils").decodeTime(alarm.t); + const menu = { - '': { 'title': /*LANG*/'Timer' }, - /*LANG*/'Hours': { - value: hrs, - onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this' + "": { "title": isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm" }, + "< Back": () => { + prepareAlarmForSave(alarm, alarmIndex, time); + saveAndReload(); + showMainMenu(); }, - /*LANG*/'Minutes': { - value: mins, - onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this' + /*LANG*/"Hour": { + value: time.h, + format: v => ("0" + v).substr(-2), + min: 0, + max: 23, + wrap: true, + onchange: v => time.h = v }, - /*LANG*/'Enabled': { - value: en, - format: v=>v?/*LANG*/"On":/*LANG*/"Off", - onchange: v=>en=v + /*LANG*/"Minute": { + value: time.m, + format: v => ("0" + v).substr(-2), + min: 0, + max: 59, + wrap: true, + onchange: v => time.m = v + }, + /*LANG*/"Enabled": { + value: alarm.on, + onchange: v => alarm.on = v + }, + /*LANG*/"Repeat": { + value: decodeDOW(alarm), + onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, alarm.dow, (repeat, dow) => { + alarm.rp = repeat; + alarm.dow = dow; + alarm.t = require("time_utils").encodeTime(time); + setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex); + }) + }, + /*LANG*/"Vibrate": require("buzz_menu").pattern(alarm.vibrate, v => alarm.vibrate = v), + /*LANG*/"Auto Snooze": { + value: alarm.as, + onchange: v => alarm.as = v + }, + /*LANG*/"Hidden": { + value: alarm.hidden || false, + onchange: v => alarm.hidden = v + }, + /*LANG*/"Cancel": () => showMainMenu(), + /*LANG*/"Confirm": () => { + prepareAlarmForSave(alarm, alarmIndex, time); + saveAndReload(); + showMainMenu(); } }; - function getTimer() { - var d = new Date(Date.now() + ((hrs*60)+mins)*60000); - var hr = d.getHours() + (d.getMinutes()/60) + (d.getSeconds()/3600); - // Save alarm - return { - on : en, - timer : (hrs*60)+mins, - hr : hr, - rp : false, as: false + + if (!isNew) { + menu[/*LANG*/"Delete"] = () => { + E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Alarm" }).then((confirm) => { + if (confirm) { + alarms.splice(alarmIndex, 1); + saveAndReload(); + showMainMenu(); + } else { + alarm.t = require("time_utils").encodeTime(time); + setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex); + } + }); }; } - menu["> Save"] = function() { - if (newAlarm) alarms.push(getTimer()); - else alarms[alarmIndex] = getTimer(); - require("Storage").write("alarm.json",JSON.stringify(alarms)); - showMainMenu(); + + E.showMenu(menu); +} + +function prepareAlarmForSave(alarm, alarmIndex, time) { + alarm.t = require("time_utils").encodeTime(time); + alarm.last = alarm.t < require("time_utils").getCurrentTimeMillis() ? new Date().getDate() : 0; + + if (alarmIndex === undefined) { + alarms.push(alarm); + } else { + alarms[alarmIndex] = alarm; + } +} + +function saveAndReload() { + // Before saving revert the dow to the standard format (alarms only!) + alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow)); + + require("sched").setAlarms(alarms); + require("sched").reload(); + + // Fix after save + alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow)); +} + +function decodeDOW(alarm) { + return alarm.rp + ? require("date_utils") + .dows(firstDayOfWeek, 2) + .map((day, index) => alarm.dow & (1 << (index + firstDayOfWeek)) ? day : "_") + .join("") + .toLowerCase() + : /*LANG*/"Once" +} + +function showEditRepeatMenu(repeat, dow, dowChangeCallback) { + var originalRepeat = repeat; + var originalDow = dow; + var isCustom = repeat && dow != WORKDAYS && dow != WEEKEND && dow != EVERY_DAY; + + const menu = { + "": { "title": /*LANG*/"Repeat Alarm" }, + "< Back": () => dowChangeCallback(repeat, dow), + /*LANG*/"Once": { + // The alarm will fire once. Internally it will be saved + // as "fire every days" BUT the repeat flag is false so + // we avoid messing up with the scheduler. + value: !repeat, + onchange: () => dowChangeCallback(false, EVERY_DAY) + }, + /*LANG*/"Workdays": { + value: repeat && dow == WORKDAYS, + onchange: () => dowChangeCallback(true, WORKDAYS) + }, + /*LANG*/"Weekends": { + value: repeat && dow == WEEKEND, + onchange: () => dowChangeCallback(true, WEEKEND) + }, + /*LANG*/"Every Day": { + value: repeat && dow == EVERY_DAY, + onchange: () => dowChangeCallback(true, EVERY_DAY) + }, + /*LANG*/"Custom": { + value: isCustom ? decodeDOW({ rp: true, dow: dow }) : false, + onchange: () => setTimeout(showCustomDaysMenu, 10, isCustom ? dow : EVERY_DAY, dowChangeCallback, originalRepeat, originalDow) + } }; - if (!newAlarm) { - menu["> Delete"] = function() { - alarms.splice(alarmIndex,1); - require("Storage").write("alarm.json",JSON.stringify(alarms)); + + E.showMenu(menu); +} + +function showCustomDaysMenu(dow, dowChangeCallback, originalRepeat, originalDow) { + const menu = { + "": { "title": /*LANG*/"Custom Days" }, + "< Back": () => { + // If the user unchecks all the days then we assume repeat = once + // and we force the dow to every day. + var repeat = dow > 0; + dowChangeCallback(repeat, repeat ? dow : EVERY_DAY) + } + }; + + require("date_utils").dows(firstDayOfWeek).forEach((day, i) => { + menu[day] = { + value: !!(dow & (1 << (i + firstDayOfWeek))), + onchange: v => v ? (dow |= 1 << (i + firstDayOfWeek)) : (dow &= ~(1 << (i + firstDayOfWeek))) + }; + }); + + menu[/*LANG*/"Cancel"] = () => setTimeout(showEditRepeatMenu, 10, originalRepeat, originalDow, dowChangeCallback) + + E.showMenu(menu); +} + +function showEditTimerMenu(selectedTimer, timerIndex) { + var isNew = timerIndex === undefined; + + var timer = require("sched").newDefaultTimer(); + + if (selectedTimer) { + Object.assign(timer, selectedTimer); + } + + var time = require("time_utils").decodeTime(timer.timer); + + const menu = { + "": { "title": isNew ? /*LANG*/"New Timer" : /*LANG*/"Edit Timer" }, + "< Back": () => { + prepareTimerForSave(timer, timerIndex, time); + saveAndReload(); showMainMenu(); + }, + /*LANG*/"Hours": { + value: time.h, + min: 0, + max: 23, + wrap: true, + onchange: v => time.h = v + }, + /*LANG*/"Minutes": { + value: time.m, + min: 0, + max: 59, + wrap: true, + onchange: v => time.m = v + }, + /*LANG*/"Seconds": { + value: time.s, + min: 0, + max: 59, + step: 1, + wrap: true, + onchange: v => time.s = v + }, + /*LANG*/"Enabled": { + value: timer.on, + onchange: v => timer.on = v + }, + /*LANG*/"Delete After Expiration": { + value: timer.del, + onchange: v => timer.del = v + }, + /*LANG*/"Hidden": { + value: timer.hidden || false, + onchange: v => timer.hidden = v + }, + /*LANG*/"Vibrate": require("buzz_menu").pattern(timer.vibrate, v => timer.vibrate = v), + /*LANG*/"Cancel": () => showMainMenu(), + /*LANG*/"Confirm": () => { + prepareTimerForSave(timer, timerIndex, time); + saveAndReload(); + showMainMenu(); + } + }; + + if (!isNew) { + menu[/*LANG*/"Delete"] = () => { + E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Timer" }).then((confirm) => { + if (confirm) { + alarms.splice(timerIndex, 1); + saveAndReload(); + showMainMenu(); + } else { + timer.timer = require("time_utils").encodeTime(time); + setTimeout(showEditTimerMenu, 10, timer, timerIndex) + } + }); }; } - return E.showMenu(menu); + + E.showMenu(menu); +} + +function prepareTimerForSave(timer, timerIndex, time) { + timer.timer = require("time_utils").encodeTime(time); + timer.t = require("time_utils").getCurrentTimeMillis() + timer.timer; + timer.last = 0; + + if (timerIndex === undefined) { + alarms.push(timer); + } else { + alarms[timerIndex] = timer; + } +} + +function showAdvancedMenu() { + E.showMenu({ + "": { "title": /*LANG*/"Advanced" }, + "< Back": () => showMainMenu(), + /*LANG*/"Scheduler Settings": () => eval(require("Storage").read("sched.settings.js"))(() => showAdvancedMenu()), + /*LANG*/"Enable All": () => enableAll(true), + /*LANG*/"Disable All": () => enableAll(false), + /*LANG*/"Delete All": () => deleteAll() + }); +} + +function enableAll(on) { + if (alarms.filter(e => e.on == !on).length == 0) { + E.showAlert( + on ? /*LANG*/"Nothing to Enable" : /*LANG*/"Nothing to Disable", + on ? /*LANG*/"Enable All" : /*LANG*/"Disable All" + ).then(() => showAdvancedMenu()); + } else { + E.showPrompt(/*LANG*/"Are you sure?", { title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All" }).then((confirm) => { + if (confirm) { + alarms.forEach((alarm, i) => { + alarm.on = on; + if (on) { + if (alarm.timer) { + prepareTimerForSave(alarm, i, require("time_utils").decodeTime(alarm.timer)) + } else { + prepareAlarmForSave(alarm, i, require("time_utils").decodeTime(alarm.t)) + } + } + }); + saveAndReload(); + showMainMenu(); + } else { + showAdvancedMenu(); + } + }); + } +} + +function deleteAll() { + if (alarms.length == 0) { + E.showAlert(/*LANG*/"Nothing to delete", /*LANG*/"Delete All").then(() => showAdvancedMenu()); + } else { + E.showPrompt(/*LANG*/"Are you sure?", { + title: /*LANG*/"Delete All" + }).then((confirm) => { + if (confirm) { + alarms = []; + saveAndReload(); + showMainMenu(); + } else { + showAdvancedMenu(); + } + }); + } } showMainMenu(); diff --git a/apps/alarm/boot.js b/apps/alarm/boot.js deleted file mode 100644 index 47dae5361..000000000 --- a/apps/alarm/boot.js +++ /dev/null @@ -1,25 +0,0 @@ -// check for alarms -(function() { - var alarms = require('Storage').readJSON('alarm.json',1)||[]; - var time = new Date(); - var active = alarms.filter(a=>a.on); - if (active.length) { - active = active.sort((a,b)=>(a.hr-b.hr)+(a.last-b.last)*24); - var hr = time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600); - if (!require('Storage').read("alarm.js")) { - console.log("No alarm app!"); - require('Storage').write('alarm.json',"[]"); - } else { - var t = 3600000*(active[0].hr-hr); - if (active[0].last == time.getDate() || t < 0) t += 86400000; - if (t<1000) t=1000; - /* execute alarm at the correct time. We avoid execing immediately - since this code will get called AGAIN when alarm.js is loaded. alarm.js - will then clearInterval() to get rid of this call so it can proceed - normally. */ - setTimeout(function() { - load("alarm.js"); - },t); - } - } -})(); diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json index 3e109bda9..dbf090774 100644 --- a/apps/alarm/metadata.json +++ b/apps/alarm/metadata.json @@ -1,18 +1,29 @@ { "id": "alarm", - "name": "Default Alarm & Timer", + "name": "Alarms & Timers", "shortName": "Alarms", - "version": "0.14", - "description": "Set and respond to alarms and timers", + "version": "0.36", + "description": "Set alarms and timers on your Bangle", "icon": "app.png", - "tags": "tool,alarm,widget", - "supports": ["BANGLEJS","BANGLEJS2"], + "tags": "tool,alarm", + "supports": [ "BANGLEJS", "BANGLEJS2" ], + "readme": "README.md", + "dependencies": { "scheduler":"type", "alarm":"widget" }, "storage": [ - {"name":"alarm.app.js","url":"app.js"}, - {"name":"alarm.boot.js","url":"boot.js"}, - {"name":"alarm.js","url":"alarm.js"}, - {"name":"alarm.img","url":"app-icon.js","evaluate":true}, - {"name":"alarm.wid.js","url":"widget.js"} + { "name": "alarm.app.js", "url": "app.js" }, + { "name": "alarm.img", "url": "app-icon.js", "evaluate": true } ], - "data": [{"name":"alarm.json"}] + "screenshots": [ + { "url": "screenshot-1.png" }, + { "url": "screenshot-2.png" }, + { "url": "screenshot-3.png" }, + { "url": "screenshot-4.png" }, + { "url": "screenshot-5.png" }, + { "url": "screenshot-6.png" }, + { "url": "screenshot-7.png" }, + { "url": "screenshot-8.png" }, + { "url": "screenshot-9.png" }, + { "url": "screenshot-10.png" }, + { "url": "screenshot-11.png" } + ] } diff --git a/apps/alarm/screenshot-1.png b/apps/alarm/screenshot-1.png new file mode 100644 index 000000000..d2bd3a409 Binary files /dev/null and b/apps/alarm/screenshot-1.png differ diff --git a/apps/alarm/screenshot-10.png b/apps/alarm/screenshot-10.png new file mode 100644 index 000000000..1e6e516c3 Binary files /dev/null and b/apps/alarm/screenshot-10.png differ diff --git a/apps/alarm/screenshot-11.png b/apps/alarm/screenshot-11.png new file mode 100644 index 000000000..197c84194 Binary files /dev/null and b/apps/alarm/screenshot-11.png differ diff --git a/apps/alarm/screenshot-2.png b/apps/alarm/screenshot-2.png new file mode 100644 index 000000000..1cbc255a9 Binary files /dev/null and b/apps/alarm/screenshot-2.png differ diff --git a/apps/alarm/screenshot-3.png b/apps/alarm/screenshot-3.png new file mode 100644 index 000000000..a165d3594 Binary files /dev/null and b/apps/alarm/screenshot-3.png differ diff --git a/apps/alarm/screenshot-4.png b/apps/alarm/screenshot-4.png new file mode 100644 index 000000000..7fd7e99b6 Binary files /dev/null and b/apps/alarm/screenshot-4.png differ diff --git a/apps/alarm/screenshot-5.png b/apps/alarm/screenshot-5.png new file mode 100644 index 000000000..4174c5670 Binary files /dev/null and b/apps/alarm/screenshot-5.png differ diff --git a/apps/alarm/screenshot-6.png b/apps/alarm/screenshot-6.png new file mode 100644 index 000000000..dc579ca5c Binary files /dev/null and b/apps/alarm/screenshot-6.png differ diff --git a/apps/alarm/screenshot-7.png b/apps/alarm/screenshot-7.png new file mode 100644 index 000000000..49da44710 Binary files /dev/null and b/apps/alarm/screenshot-7.png differ diff --git a/apps/alarm/screenshot-8.png b/apps/alarm/screenshot-8.png new file mode 100644 index 000000000..86d69cd93 Binary files /dev/null and b/apps/alarm/screenshot-8.png differ diff --git a/apps/alarm/screenshot-9.png b/apps/alarm/screenshot-9.png new file mode 100644 index 000000000..2d8c7fc83 Binary files /dev/null and b/apps/alarm/screenshot-9.png differ diff --git a/apps/alpinenav/ChangeLog b/apps/alpinenav/ChangeLog new file mode 100644 index 000000000..b3d1e0874 --- /dev/null +++ b/apps/alpinenav/ChangeLog @@ -0,0 +1,2 @@ +0.01: New App! +0.02: Added adjustment for Bangle.js magnetometer heading fix diff --git a/apps/alpinenav/app-icon.js b/apps/alpinenav/app-icon.js index dba084202..6708ee67f 100644 --- a/apps/alpinenav/app-icon.js +++ b/apps/alpinenav/app-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mUywkEIf4A/AHUBiAYWgcwDC0v+IYW///C6sC+c/kAYUj/xj/wDCgvBgfyVihhBAQQASh6TCMikvYoRkU/73CMicD+ZnFViJFBj5MBMiU/+IuBJoJkRCoUvfIPy/5kQVgM//7gBC4KCDFxSsDgTHCl8QWgaRKmBJBFIzmDSJXzYBECWobbJAAKNIMhYlBOoK/IMhZXCmYMLABAkCS4RkSXZoNJRBo/CgK6UBwTWBBIs/SJBAGl7UFegIXMaogHEehAAHj/yIYsfehAAGMQISFMRxbCiEDU4ZiQZY5iQZYpiSbQ8/cwzLOCiQA/AH4A1A")) \ No newline at end of file +require("heatshrink").decompress(atob("mEkgIRO4AFJgPgAocDAoswAocHAokGjAFDhgFFhgFDjEOAoc4gxSE44FDuPjAod//+AAoXfn4FCgPMjJUCmIJBAoU7AoJUCv4CBsACBtwCBuACB4w3CEQIaCKgMBFgQFBgYFCLQMDMIfAg55D4BcDg/gNAcD+B0DSIMcOgiGEjCYEjgFEhhVCUgQ")) diff --git a/apps/alpinenav/app.js b/apps/alpinenav/app.js index 29eeab0c9..7cffc39c3 100644 --- a/apps/alpinenav/app.js +++ b/apps/alpinenav/app.js @@ -224,7 +224,7 @@ Bangle.on('mag', function (m) { if (isNaN(m.heading)) compass_heading = "---"; else - compass_heading = 360 - Math.round(m.heading); + compass_heading = Math.round(m.heading); current_colour = g.getColor(); g.reset(); g.setColor(background_colour); diff --git a/apps/alpinenav/metadata.json b/apps/alpinenav/metadata.json index dcb56e912..c5a0e0611 100644 --- a/apps/alpinenav/metadata.json +++ b/apps/alpinenav/metadata.json @@ -1,7 +1,7 @@ { "id": "alpinenav", "name": "Alpine Nav", - "version": "0.01", + "version": "0.02", "description": "App that performs GPS monitoring to track and display position relative to a given origin in realtime", "icon": "app-icon.png", "tags": "outdoors,gps", diff --git a/apps/altimeter/ChangeLog b/apps/altimeter/ChangeLog new file mode 100644 index 000000000..29388520e --- /dev/null +++ b/apps/altimeter/ChangeLog @@ -0,0 +1,2 @@ +0.01: New App! +0.02: Actually upload correct code diff --git a/apps/altimeter/app-icon.js b/apps/altimeter/app-icon.js new file mode 100644 index 000000000..1f8dfb637 --- /dev/null +++ b/apps/altimeter/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///t9TmuV3+GJf4AN+ALVgf8BasP/4LVn//4ALUWgJUJBZUDBYJUIBZcP3/nKhEOt/WBZE5r+VKg0KgEVr9V3wLHqtaqt9sALElWAqoABt1QBZNeBYuq0ILCrVUBYulBYVWBYkCBYgABBZ8K1WVBYlABZegKQWqBQlVqALKqWoKQWpBYtWBZeqKRAAB1WABZZSHAANq0ALLKQ6qC1ALLKQ5UEAH4AG")) diff --git a/apps/altimeter/app.js b/apps/altimeter/app.js new file mode 100644 index 000000000..cac4e80fd --- /dev/null +++ b/apps/altimeter/app.js @@ -0,0 +1,30 @@ +Bangle.setBarometerPower(true, "app"); + +g.clear(1); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +var zero = 0; +var R = Bangle.appRect; +var y = R.y + R.h/2; +var MEDIANLENGTH = 20; +var avr = [], median; +var value = 0; + +Bangle.on('pressure', function(e) { + while (avr.length>MEDIANLENGTH) avr.pop(); + avr.unshift(e.altitude); + median = avr.slice().sort(); + g.reset().clearRect(0,y-30,g.getWidth()-10,y+30); + if (median.length>10) { + var mid = median.length>>1; + value = E.sum(median.slice(mid-4,mid+5)) / 9; + g.setFont("Vector",50).setFontAlign(0,0).drawString((value-zero).toFixed(1), g.getWidth()/2, y); + } +}); + +g.reset(); +g.setFont("6x8").setFontAlign(0,0).drawString(/*LANG*/"ALTITUDE (m)", g.getWidth()/2, y-40); +g.setFont("6x8").setFontAlign(0,0,3).drawString(/*LANG*/"ZERO", g.getWidth()-5, g.getHeight()/2); +setWatch(function() { + zero = value; +}, (process.env.HWVERSION==2) ? BTN1 : BTN2, {repeat:true}); diff --git a/apps/altimeter/app.png b/apps/altimeter/app.png new file mode 100644 index 000000000..9c9d69077 Binary files /dev/null and b/apps/altimeter/app.png differ diff --git a/apps/altimeter/metadata.json b/apps/altimeter/metadata.json new file mode 100644 index 000000000..8bdbf3022 --- /dev/null +++ b/apps/altimeter/metadata.json @@ -0,0 +1,12 @@ +{ "id": "altimeter", + "name": "Altimeter", + "version":"0.02", + "description": "Simple altimeter that can display height changed using Bangle.js 2's built in pressure sensor.", + "icon": "app.png", + "tags": "tool,outdoors", + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"altimeter.app.js","url":"app.js"}, + {"name":"altimeter.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index 59cb23a46..86dbdb649 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -6,3 +6,15 @@ 0.05: Fix handling of message actions 0.06: Option to keep messages after a disconnect (default false) (fix #1186) 0.07: Include charging state in battery updates to phone +0.08: Handling of alarms +0.09: Alarm vibration, repeat, and auto-snooze now handled by sched +0.10: Fix SMS bug +0.12: Use default Bangle formatter for booleans +0.13: Added Bangle.http function (see Readme file for more info) +0.14: Fix timeout of http function not being cleaned up +0.15: Allow method/body/headers to be specified for `http` (needs Gadgetbridge 0.68.0b or later) +0.16: Bangle.http now fails immediately if there is no Bluetooth connection (fix #2152) +0.17: Now kick off Calendar sync as soon as connected to Gadgetbridge +0.18: Use new message library + If connected to Gadgetbridge, allow GPS forwarding from phone (Gadgetbridge code still not merged) +0.19: Add automatic translation for a couple of strings. diff --git a/apps/android/README.md b/apps/android/README.md index c10718aac..c76e6e528 100644 --- a/apps/android/README.md +++ b/apps/android/README.md @@ -20,6 +20,8 @@ It contains: of Gadgetbridge - making your phone make noise so you can find it. * `Keep Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js keep any messages it has received, or should it delete them? +* `Overwrite GPS` - when GPS is requested by an app, this doesn't use Bangle.js's GPS +but instead asks Gadgetbridge on the phone to use the phone's GPS * `Messages` - launches the messages app, showing a list of messages ## How it works @@ -32,6 +34,25 @@ Responses are sent back to Gadgetbridge simply as one line of JSON. More info on message formats on http://www.espruino.com/Gadgetbridge +## Functions provided + +The boot code also provides some useful functions: + +* `Bangle.messageResponse = function(msg,response)` - send a yes/no response to a message. `msg` is a message object, and `response` is a boolean. +* `Bangle.musicControl = function(cmd)` - control music, cmd = `play/pause/next/previous/volumeup/volumedown` +* `Bangle.http = function(url,options)` - make an HTTPS request to a URL and return a promise with the data. Requires the [internet enabled `Bangle.js Gadgetbridge` app](http://www.espruino.com/Gadgetbridge#http-requests). `options` can contain: + * `id` - a custom (string) ID + * `timeout` - a timeout for the request in milliseconds (default 30000ms) + * `xpath` an xPath query to run on the request (but right now the URL requested must be XML - HTML is rarely XML compliant) + +eg: + +``` +Bangle.http("https://pur3.co.uk/hello.txt").then(data=>{ + console.log("Got ",data); +}); +``` + ## Testing Bangle.js can only hold one connection open at a time, so it's hard to see diff --git a/apps/android/boot.js b/apps/android/boot.js index eb3d26c6e..e1e5b028b 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -3,8 +3,14 @@ Bluetooth.println(""); Bluetooth.println(JSON.stringify(message)); } + var lastMsg; var settings = require("Storage").readJSON("android.settings.json",1)||{}; + //default alarm settings + if (settings.rp == undefined) settings.rp = true; + if (settings.as == undefined) settings.as = true; + if (settings.vibrate == undefined) settings.vibrate = ".."; + require('Storage').writeJSON("android.settings.json", settings); var _GB = global.GB; global.GB = (event) => { // feed a copy to other handlers if there were any @@ -13,7 +19,17 @@ /* TODO: Call handling, fitness */ var HANDLERS = { // {t:"notify",id:int, src,title,subject,body,sender,tel:string} add - "notify" : function() { Object.assign(event,{t:"add",positive:true, negative:true});require("messages").pushMessage(event); }, + "notify" : function() { + Object.assign(event,{t:"add",positive:true, negative:true}); + // Detect a weird GadgetBridge bug and fix it + // For some reason SMS messages send two GB notifications, with different sets of info + if (lastMsg && event.body == lastMsg.body && lastMsg.src == undefined && event.src == "Messages") { + // Mutate the other message + event.id = lastMsg.id; + } + lastMsg = event; + require("messages").pushMessage(event); + }, // {t:"notify~",id:int, title:string} // modified "notify~" : function() { event.t="modify";require("messages").pushMessage(event); }, // {t:"notify-",id:int} // remove @@ -41,17 +57,131 @@ t:event.cmd=="incoming"?"add":"remove", id:"call", src:"Phone", positive:true, negative:true, - title:event.name||"Call", body:"Incoming call\n"+event.number}); + title:event.name||/*LANG*/"Call", body:/*LANG*/"Incoming call\n"+event.number}); require("messages").pushMessage(event); }, + "alarm" : function() { + //wipe existing GB alarms + var sched; + try { sched = require("sched"); } catch (e) {} + if (!sched) return; // alarms may not be installed + var gbalarms = sched.getAlarms().filter(a=>a.appid=="gbalarms"); + for (var i = 0; i < gbalarms.length; i++) + sched.setAlarm(gbalarms[i].id, undefined); + var alarms = sched.getAlarms(); + var time = new Date(); + var currentTime = time.getHours() * 3600000 + + time.getMinutes() * 60000 + + time.getSeconds() * 1000; + for (var j = 0; j < event.d.length; j++) { + // prevents all alarms from going off at once?? + var dow = event.d[j].rep; + if (!dow) dow = 127; //if no DOW selected, set alarm to all DOW + var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0; + var a = require("sched").newDefaultAlarm(); + a.id = "gb"+j; + a.appid = "gbalarms"; + a.on = true; + a.t = event.d[j].h * 3600000 + event.d[j].m * 60000; + a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format + a.last = last; + alarms.push(a); + } + sched.setAlarms(alarms); + sched.reload(); + }, + //TODO perhaps move those in a library (like messages), used also for viewing events? + //add and remove events based on activity on phone (pebble-like) + "calendar" : function() { + var cal = require("Storage").readJSON("android.calendar.json",true); + if (!cal || !Array.isArray(cal)) cal = []; + var i = cal.findIndex(e=>e.id==event.id); + if(i<0) + cal.push(event); + else + cal[i] = event; + require("Storage").writeJSON("android.calendar.json", cal); + }, + "calendar-" : function() { + var cal = require("Storage").readJSON("android.calendar.json",true); + //if any of those happen we are out of sync! + if (!cal || !Array.isArray(cal)) cal = []; + cal = cal.filter(e=>e.id!=event.id); + require("Storage").writeJSON("android.calendar.json", cal); + }, + //triggered by GB, send all ids + "force_calendar_sync_start" : function() { + var cal = require("Storage").readJSON("android.calendar.json",true); + if (!cal || !Array.isArray(cal)) cal = []; + gbSend({t:"force_calendar_sync", ids: cal.map(e=>e.id)}); + }, + "http":function() { + //get the promise and call the promise resolve + if (Bangle.httpRequest === undefined) return; + var request=Bangle.httpRequest[event.id]; + if (request === undefined) return; //already timedout or wrong id + delete Bangle.httpRequest[event.id]; + clearTimeout(request.t); //t = timeout variable + if(event.err!==undefined) //if is error + request.j(event.err); //r = reJect function + else + request.r(event); //r = resolve function + }, + "gps": function() { + const settings = require("Storage").readJSON("android.settings.json",1)||{}; + if (!settings.overwriteGps) return; + delete event.t; + event.satellites = NaN; + event.course = NaN; + event.fix = 1; + Bangle.emit('gps', event); + }, + "is_gps_active": function() { + gbSend({ t: "gps_power", status: Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0 }); + } }; var h = HANDLERS[event.t]; if (h) h(); else console.log("GB Unknown",event); }; + // HTTP request handling - see the readme + // options = {id,timeout,xpath} + Bangle.http = (url,options)=>{ + options = options||{}; + if (!NRF.getSecurityStatus().connected) + return Promise.reject(/*LANG*/"Not connected to Bluetooth"); + if (Bangle.httpRequest === undefined) + Bangle.httpRequest={}; + if (options.id === undefined) { + // try and create a unique ID + do { + options.id = Math.random().toString().substr(2); + } while( Bangle.httpRequest[options.id]!==undefined); + } + //send the request + var req = {t: "http", url:url, id:options.id}; + if (options.xpath) req.xpath = options.xpath; + if (options.method) req.method = options.method; + if (options.body) req.body = options.body; + if (options.headers) req.headers = options.headers; + gbSend(req); + //create the promise + var promise = new Promise(function(resolve,reject) { + //save the resolve function in the dictionary and create a timeout (30 seconds default) + Bangle.httpRequest[options.id]={r:resolve,j:reject,t:setTimeout(()=>{ + //if after "timeoutMillisec" it still hasn't answered -> reject + delete Bangle.httpRequest[options.id]; + reject("Timeout"); + },options.timeout||30000)}; + }); + return promise; + } // Battery monitor function sendBattery() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); } - NRF.on("connect", () => setTimeout(sendBattery, 2000)); + NRF.on("connect", () => setTimeout(function() { + sendBattery(); + GB({t:"force_calendar_sync_start"}); // send a list of our calendar entries to start off the sync process + }, 2000)); Bangle.on("charging", sendBattery); if (!settings.keep) NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect @@ -71,6 +201,30 @@ if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id }); // error/warn here? }; + // GPS overwrite logic + if (settings.overwriteGps) { // if the overwrite option is set../ + // Save current logic + const originalSetGpsPower = Bangle.setGPSPower; + // Replace set GPS power logic to suppress activation of gps (and instead request it from the phone) + Bangle.setGPSPower = (isOn, appID) => { + // if not connected, use old logic + if (!NRF.getSecurityStatus().connected) return originalSetGpsPower(isOn, appID); + // Emulate old GPS power logic + if (!Bangle._PWR) Bangle._PWR={}; + if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[]; + if (!appID) appID="?"; + if (isOn && !Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.push(appID); + if (!isOn && Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.splice(Bangle._PWR.GPS.indexOf(appID),1); + let pwr = Bangle._PWR.GPS.length>0; + gbSend({ t: "gps_power", status: pwr }); + return pwr; + } + // Replace check if the GPS is on to check the _PWR variable + Bangle.isGPSOn = () => { + return Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0; + } + } + // remove settings object so it's not taking up RAM delete settings; })(); diff --git a/apps/android/metadata.json b/apps/android/metadata.json index d126b869a..d5a45edb7 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -2,11 +2,11 @@ "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.07", + "version": "0.19", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", "icon": "app.png", "tags": "tool,system,messages,notifications,gadgetbridge", - "dependencies": {"messages":"app"}, + "dependencies": {"messages":"module"}, "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ @@ -15,6 +15,6 @@ {"name":"android.img","url":"app-icon.js","evaluate":true}, {"name":"android.boot.js","url":"boot.js"} ], - "data": [{"name":"android.settings.json"}], + "data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}], "sortorder": -8 } diff --git a/apps/android/settings.js b/apps/android/settings.js index 7c46a1fc0..3e04e0f9d 100644 --- a/apps/android/settings.js +++ b/apps/android/settings.js @@ -1,4 +1,7 @@ (function(back) { + + + function gb(j) { Bluetooth.println(JSON.stringify(j)); } @@ -10,21 +13,30 @@ "" : { "title" : "Android" }, "< Back" : back, /*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" }, - "Find Phone" : () => E.showMenu({ - "" : { "title" : "Find Phone" }, + /*LANG*/"Find Phone" : () => E.showMenu({ + "" : { "title" : /*LANG*/"Find Phone" }, "< Back" : ()=>E.showMenu(mainmenu), /*LANG*/"On" : _=>gb({t:"findPhone",n:true}), /*LANG*/"Off" : _=>gb({t:"findPhone",n:false}), }), /*LANG*/"Keep Msgs" : { value : !!settings.keep, - format : v=>v?/*LANG*/"Yes":/*LANG*/"No", onchange: v => { settings.keep = v; updateSettings(); } }, - /*LANG*/"Messages" : ()=>load("messages.app.js") + /*LANG*/"Overwrite GPS" : { + value : !!settings.overwriteGps, + onchange: newValue => { + if (newValue) { + Bangle.setGPSPower(false, 'android'); + } + settings.overwriteGps = newValue; + updateSettings(); + } + }, + /*LANG*/"Messages" : ()=>require("message").openGUI(), }; E.showMenu(mainmenu); }) diff --git a/apps/animclk/ChangeLog b/apps/animclk/ChangeLog index 348448c34..76d15bdb1 100644 --- a/apps/animclk/ChangeLog +++ b/apps/animclk/ChangeLog @@ -1,3 +1,5 @@ 0.01: New App! 0.02: Fix bug if image clock wasn't installed 0.03: Update to use setUI +0.04: Tell clock widgets to hide. Move loadWidgets() so it only runs on +startup and not on every draw. diff --git a/apps/animclk/app.js b/apps/animclk/app.js index 4bf63daf6..bdc399fbe 100644 --- a/apps/animclk/app.js +++ b/apps/animclk/app.js @@ -87,7 +87,6 @@ if (g.drawImages) { draw(); var secondInterval = setInterval(draw,100); // load widgets - Bangle.loadWidgets(); Bangle.drawWidgets(); // Stop when LCD goes off Bangle.on('lcdPower',on=>{ @@ -104,3 +103,5 @@ if (g.drawImages) { } // Show launcher when button pressed Bangle.setUI("clock"); + +Bangle.loadWidgets(); diff --git a/apps/animclk/metadata.json b/apps/animclk/metadata.json index 31dfe453f..0b426a37d 100644 --- a/apps/animclk/metadata.json +++ b/apps/animclk/metadata.json @@ -2,7 +2,7 @@ "id": "animclk", "name": "Animated Clock", "shortName": "Anim Clock", - "version": "0.03", + "version": "0.04", "description": "An animated clock face using Mark Ferrari's amazing 8 bit game art and palette cycling: http://www.markferrari.com/art/8bit-game-art", "icon": "app.png", "type": "clock", diff --git a/apps/antonclk/ChangeLog b/apps/antonclk/ChangeLog index 4dca8053e..4ef0cee75 100644 --- a/apps/antonclk/ChangeLog +++ b/apps/antonclk/ChangeLog @@ -7,4 +7,10 @@ when weekday name "On": weekday name is cut at 6th position and .# is added 0.06: fixes #1271 - wrong settings name when weekday name and calendar weeknumber are on then display is # - week is buffered until date or timezone changes \ No newline at end of file + week is buffered until date or timezone changes +0.07: align default settings with app.js (otherwise the initial displayed settings will be confusing to users) +0.08: fixed calendar weeknumber not shortened to two digits +0.09: Use default Bangle formatter for booleans +0.10: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16 +0.11: Moved enhanced Anton clock to 'Anton Clock Plus' and stripped this clock back down to make it faster for new users (270ms -> 170ms) + Modified to avoid leaving functions defined when using setUI({remove:...}) diff --git a/apps/antonclk/app.js b/apps/antonclk/app.js index 7b40d8eb5..528866588 100644 --- a/apps/antonclk/app.js +++ b/apps/antonclk/app.js @@ -1,230 +1,45 @@ // Clock with large digits using the "Anton" bold font - -const SETTINGSFILE = "antonclk.json"; - 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)); }; -Graphics.prototype.setFontAntonSmall = function(scale) { - // Actual height 53 (52 - 0) - g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAAAAAAAAAAAAAMAAAAAAAAD8AAAAAAAA/8AAAAAAAf/8AAAAAAH//8AAAAAB///8AAAAA////8AAAAP////8AAAD/////8AAB//////8AAf//////8AH///////4A///////+AA///////AAA//////wAAA/////8AAAA////+AAAAA////gAAAAA///4AAAAAA//8AAAAAAA//AAAAAAAA/wAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/////wAAA//////8AAB//////+AAH///////gAH///////gAP///////wAf///////4Af///////4A////////8A////////8A////////8A//AAAAD/8A/8AAAAA/8A/8AAAAA/8A/8AAAAA/8A/+AAAAB/8A////////8A////////8A////////8Af///////4Af///////4AP///////wAP///////wAH///////gAD///////AAA//////8AAAP/////wAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAAA/4AAAAAAAA/4AAAAAAAB/wAAAAAAAB/wAAAAAAAD/wAAAAAAAD/gAAAAAAAH///////8AP///////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/4AAAP8AA//4AAA/8AB//4AAH/8AH//4AAP/8AP//4AA//8AP//4AB//8Af//4AD//8Af//4AP//8A///4Af//8A///4A///8A///4D///8A//AAH///8A/8AAP///8A/8AA//+/8A/8AD//8/8A/+Af//w/8A//////g/8A/////+A/8A/////8A/8Af////4A/8Af////wA/8AP////AA/8AP///+AA/8AH///8AA/8AD///wAA/8AA///AAA/8AAP/4AAA/8AAAAAAAAAAAAAAAAAAAAAAH4AAf/gAAA/4AAf/8AAD/4AAf//AAH/4AAf//gAP/4AAf//wAP/4AAf//wAf/4AAf//4Af/4AAf//4A//4AAf//8A//4AAf//8A//4AAP//8A//A/8AB/8A/8A/8AA/8A/8B/8AA/8A/8B/8AA/8A/+D//AB/8A////////8A////////8A////////8Af///////4Af///////4Af///////wAP///////gAH//9////gAD//4///+AAB//wf//4AAAP/AH//gAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAAAB//wAAAAAAP//wAAAAAD///wAAAAA////wAAAAH////wAAAB/////wAAAf/////wAAD//////wAA///////wAA/////h/wAA////wB/wAA///8AB/wAA///AAB/wAA//gAAB/wAA////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8AAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAAAAAAAAAAAAAAAAAAAAAP/4AA////4P/+AA////4P//AA////4P//gA////4P//wA////4P//wA////4P//4A////4P//4A////4P//8A////4P//8A////4P//8A/8H/AAB/8A/8H+AAA/8A/8P+AAA/8A/8P+AAA/8A/8P/gAD/8A/8P/////8A/8P/////8A/8P/////8A/8P/////4A/8H/////4A/8H/////wA/8D/////wA/8B/////gA/8A////+AA/8AP///4AAAAAB///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////wAAAf/////8AAB///////AAH///////gAP///////wAP///////wAf///////4Af///////4A////////8A////////8A////////8A/+AH/AB/8A/8AP+AA/8A/4Af+AA/8A/8Af+AA/8A/8Af/gH/8A//4f////8A//4f////8A//4f////8Af/4f////4Af/4f////4AP/4P////wAP/4P////gAH/4H////AAD/4D///+AAB/4B///4AAAP4AP//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAAAAAAA/8AAAAAAAA/8AAAAAB8A/8AAAAB/8A/8AAAAf/8A/8AAAH//8A/8AAA///8A/8AAH///8A/8AA////8A/8AD////8A/8Af////8A/8B/////8A/8P/////8A/8//////8A////////AA///////AAA//////gAAA/////4AAAA/////AAAAA////4AAAAA////AAAAAA///8AAAAAA///gAAAAAA//+AAAAAAA//wAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/gD//gAAA//4P//8AAD//8f///AAH//+////gAH///////wAP///////4AP///////8Af///////8Af///////+Af///////+A////////+A//B//AB/+A/+A/+AA/+A/8Af+AA/+A/+Af+AA/+A//A//AB/+A////////+Af///////+Af///////+Af///////8Af///////8AP///////4AH///////4AH//+////wAD//+////AAA//4P//+AAAP/gH//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH//gAfgAAA///8A/8AAB///+A//AAH////A//gAH////g//wAP////g//wAf////w//4Af////w//4A/////w//8A/////w//8A/////w//8A//gP/wA/8A/8AD/wA/8A/8AD/wAf8A/8AD/gA/8A/+AH/AB/8A////////8A////////8A////////8Af///////4Af///////4Af///////wAP///////wAH///////gAD//////+AAA//////4AAAP/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DhgeFB4eHh4eHh4eDw=="), 60 + (scale << 8) + (1 << 16)); -}; +{ // must be inside our own scope here so that when we are unloaded everything disappears + // we also define functions using 'let fn = function() {..}' for the same reason. function decls are global +let drawTimeout; -// variables defined from settings -var secondsMode; -var secondsColoured; -var secondsWithColon; -var dateOnMain; -var dateOnSecs; -var weekDay; -var calWeek; -var upperCase; -var vectorFont; +// Actually draw the watch face +let draw = function() { + var x = g.getWidth() / 2; + var y = g.getHeight() / 2; + g.reset().clearRect(Bangle.appRect); // clear whole background (w/o widgets) + var date = new Date(); + var timeStr = require("locale").time(date, 1); // Hour and minute + g.setFontAlign(0, 0).setFont("Anton").drawString(timeStr, x, y); + // Show date and day of week + var dateStr = require("locale").date(date, 0).toUpperCase()+"\n"+ + require("locale").dow(date, 0).toUpperCase(); + g.setFontAlign(0, 0).setFont("6x8", 2).drawString(dateStr, x, y+48); -// dynamic variables -var drawTimeout; -var queueMillis = 1000; -var secondsScreen = true; - -var isBangle1 = (process.env.HWVERSION == 1); - -//For development purposes -/* -require('Storage').writeJSON(SETTINGSFILE, { - secondsMode: "Unlocked", // "Never", "Unlocked", "Always" - secondsColoured: true, - secondsWithColon: true, - dateOnMain: "Long", // "Short", "Long", "ISO8601" - dateOnSecs: "Year", // "No", "Year", "Weekday", LEGACY: true/false - weekDay: true, - calWeek: true, - upperCase: true, - vectorFont: true, -}); -*/ - -// OR (also for development purposes) -/* -require('Storage').erase(SETTINGSFILE); -*/ - -// Load settings -function loadSettings() { - // Helper function default setting - function def (value, def) {return value !== undefined ? value : def;} - - var settings = require('Storage').readJSON(SETTINGSFILE, true) || {}; - secondsMode = def(settings.secondsMode, "Never"); - secondsColoured = def(settings.secondsColoured, true); - secondsWithColon = def(settings.secondsWithColon, true); - dateOnMain = def(settings.dateOnMain, "Long"); - dateOnSecs = def(settings.dateOnSecs, "Year"); - weekDay = def(settings.weekDay, true); - calWeek = def(settings.calWeek, false); - upperCase = def(settings.upperCase, true); - vectorFont = def(settings.vectorFont, false); - - // Legacy - if (dateOnSecs === true) - dateOnSecs = "Year"; - if (dateOnSecs === false) - dateOnSecs = "No"; -} - -// schedule a draw for the next second or minute -function queueDraw() { + // queue next draw if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = setTimeout(function() { drawTimeout = undefined; draw(); - }, queueMillis - (Date.now() % queueMillis)); -} + }, 60000 - (Date.now() % 60000)); +}; -function updateState() { - if (Bangle.isLCDOn()) { - if ((secondsMode === "Unlocked" && !Bangle.isLocked()) || secondsMode === "Always") { - secondsScreen = true; - queueMillis = 1000; - } else { - secondsScreen = false; - queueMillis = 60000; - } - draw(); // draw immediately, queue redraw - } else { // stop draw timer +// Show launcher when middle button pressed +Bangle.setUI({ + mode : "clock", + remove : function() { + // Called to unload all of the clock app if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = undefined; - } -} - -function isoStr(date) { - return date.getFullYear() + "-" + ("0" + (date.getMonth() + 1)).substr(-2) + "-" + ("0" + date.getDate()).substr(-2); -} - -var calWeekBuffer = [false,false,false]; //buffer tz, date, week no (once calculated until other tz or date is requested) -function ISO8601calWeek(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480 - dateNoTime = date; dateNoTime.setHours(0,0,0,0); - if (calWeekBuffer[0] === date.getTimezoneOffset() && calWeekBuffer[1] === dateNoTime) return calWeekBuffer[2]; - calWeekBuffer[0] = date.getTimezoneOffset(); - calWeekBuffer[1] = dateNoTime; - var tdt = new Date(date.valueOf()); - var dayn = (date.getDay() + 6) % 7; - tdt.setDate(tdt.getDate() - dayn + 3); - var firstThursday = tdt.valueOf(); - tdt.setMonth(0, 1); - if (tdt.getDay() !== 4) { - tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7); - } - calWeekBuffer[2] = 1 + Math.ceil((firstThursday - tdt) / 604800000); - return calWeekBuffer[2]; -} - -function doColor() { - return !isBangle1 && !Bangle.isLocked() && secondsColoured; -} - -// Actually draw the watch face -function draw() { - var x = g.getWidth() / 2; - var y = g.getHeight() / 2 - (secondsMode !== "Never" ? 24 : (vectorFont ? 12 : 0)); - g.reset(); - /* This is to mark the widget areas during development. - g.setColor("#888") - .fillRect(0, 0, g.getWidth(), 23) - .fillRect(0, g.getHeight() - 23, g.getWidth(), g.getHeight()).reset(); - /* */ - g.clearRect(0, 24, g.getWidth(), g.getHeight() - 24); // clear whole background (w/o widgets) - var date = new Date(); // Actually the current date, this one is shown - var timeStr = require("locale").time(date, 1); // Hour and minute - g.setFontAlign(0, 0).setFont("Anton").drawString(timeStr, x, y); // draw time - if (secondsScreen) { - y += 65; - var secStr = (secondsWithColon ? ":" : "") + ("0" + date.getSeconds()).substr(-2); - if (doColor()) - g.setColor(0, 0, 1); - g.setFont("AntonSmall"); - if (dateOnSecs !== "No") { // A bit of a complex drawing with seconds on the right and date on the left - g.setFontAlign(1, 0).drawString(secStr, g.getWidth() - (isBangle1 ? 32 : 2), y); // seconds - y -= (vectorFont ? 15 : 13); - x = g.getWidth() / 4 + (isBangle1 ? 12 : 4) + (secondsWithColon ? 0 : g.stringWidth(":") / 2); - var dateStr2 = (dateOnMain === "ISO8601" ? isoStr(date) : require("locale").date(date, 1)); - var year; - var md; - var yearfirst; - if (dateStr2.match(/\d\d\d\d$/)) { // formatted date ends with year - year = (dateOnSecs === "Year" ? dateStr2.slice(-4) : require("locale").dow(date, 1)); - md = dateStr2.slice(0, -4); - if (!md.endsWith(".")) // keep separator before the year only if it is a dot (31.12. but 31/12) - md = md.slice(0, -1); - yearfirst = false; - } else { // formatted date begins with year - if (!dateStr2.match(/^\d\d\d\d/)) // if year position cannot be detected... - dateStr2 = isoStr(date); // ...use ISO date format instead - year = (dateOnSecs === "Year" ? dateStr2.slice(0, 4) : require("locale").dow(date, 1)); - md = dateStr2.slice(5); // never keep separator directly after year - yearfirst = true; - } - if (dateOnSecs === "Weekday" && upperCase) - year = year.toUpperCase(); - g.setFontAlign(0, 0); - if (vectorFont) - g.setFont("Vector", 24); - else - g.setFont("6x8", 2); - if (doColor()) - g.setColor(1, 0, 0); - g.drawString(md, x, (yearfirst ? y + (vectorFont ? 26 : 16) : y)); - g.drawString(year, x, (yearfirst ? y : y + (vectorFont ? 26 : 16))); - } else { - g.setFontAlign(0, 0).drawString(secStr, x, y); // Just the seconds centered - } - } else { // No seconds screen: Show date and optionally day of week - y += (vectorFont ? 50 : (secondsMode !== "Never") ? 52 : 40); - var dateStr = (dateOnMain === "ISO8601" ? isoStr(date) : require("locale").date(date, (dateOnMain === "Long" ? 0 : 1))); - if (upperCase) - dateStr = dateStr.toUpperCase(); - g.setFontAlign(0, 0); - if (vectorFont) - g.setFont("Vector", 24); - else - g.setFont("6x8", 2); - g.drawString(dateStr, x, y); - if (calWeek || weekDay) { - var dowcwStr = ""; - if (calWeek) - dowcwStr = " #" + ("0" + ISO8601calWeek(date)).substring(-2); - if (weekDay) - dowcwStr = require("locale").dow(date, calWeek ? 1 : 0) + dowcwStr; //weekDay e.g. Monday or weekDayShort # e.g. Mon #01 - else //week #01 - dowcwStr = /*LANG*/"week" + dowcwStr; - if (upperCase) - dowcwStr = dowcwStr.toUpperCase(); - g.drawString(dowcwStr, x, y + (vectorFont ? 26 : 16)); - } - } - - // queue next draw - queueDraw(); -} - -// Init the settings of the app -loadSettings(); -// Clear the screen once, at startup -g.clear(); -// Set dynamic state and perform initial drawing -updateState(); -// Register hooks for LCD on/off event and screen lock on/off event -Bangle.on('lcdPower', on => { - updateState(); -}); -Bangle.on('lock', on => { - updateState(); -}); -// Show launcher when middle button pressed -Bangle.setUI("clock"); + delete Graphics.prototype.setFontAnton; + }}); // Load widgets Bangle.loadWidgets(); -Bangle.drawWidgets(); - -// end of file \ No newline at end of file +draw(); +setTimeout(Bangle.drawWidgets,0); +} diff --git a/apps/antonclk/app.png b/apps/antonclk/app.png index a38093c5f..bb764d2a1 100644 Binary files a/apps/antonclk/app.png and b/apps/antonclk/app.png differ diff --git a/apps/antonclk/metadata.json b/apps/antonclk/metadata.json index def5d3b48..b8242f11a 100644 --- a/apps/antonclk/metadata.json +++ b/apps/antonclk/metadata.json @@ -1,9 +1,8 @@ { "id": "antonclk", "name": "Anton Clock", - "version": "0.06", - "description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.", - "readme":"README.md", + "version": "0.11", + "description": "A simple clock using the bold Anton font. See `Anton Clock Plus` for an enhanced version", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], "type": "clock", @@ -12,8 +11,6 @@ "allow_emulator": true, "storage": [ {"name":"antonclk.app.js","url":"app.js"}, - {"name":"antonclk.settings.js","url":"settings.js"}, {"name":"antonclk.img","url":"app-icon.js","evaluate":true} - ], - "data": [{"name":"antonclk.json"}] + ] } diff --git a/apps/antonclk/screenshot.png b/apps/antonclk/screenshot.png index e949b8a24..9b38e90d5 100644 Binary files a/apps/antonclk/screenshot.png and b/apps/antonclk/screenshot.png differ diff --git a/apps/antonclkplus/ChangeLog b/apps/antonclkplus/ChangeLog new file mode 100644 index 000000000..3b0a3d8b8 --- /dev/null +++ b/apps/antonclkplus/ChangeLog @@ -0,0 +1,15 @@ +0.01: New App! +0.02: Load widgets after setUI so widclk knows when to hide +0.03: Clock now shows day of week under date. +0.04: Clock can optionally show seconds, date optionally in ISO-8601 format, weekdays and uppercase configurable, too. +0.05: Clock can optionally show ISO-8601 calendar weeknumber (default: Off) + when weekday name "Off": week #: + when weekday name "On": weekday name is cut at 6th position and .# is added +0.06: fixes #1271 - wrong settings name + when weekday name and calendar weeknumber are on then display is # + week is buffered until date or timezone changes +0.07: align default settings with app.js (otherwise the initial displayed settings will be confusing to users) +0.08: fixed calendar weeknumber not shortened to two digits +0.09: Use default Bangle formatter for booleans +0.10: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16 + Modified to avoid leaving functions defined when using setUI({remove:...}) diff --git a/apps/antonclk/README.md b/apps/antonclkplus/README.md similarity index 87% rename from apps/antonclk/README.md rename to apps/antonclkplus/README.md index 28a38f5fd..25b478dd9 100644 --- a/apps/antonclk/README.md +++ b/apps/antonclkplus/README.md @@ -1,6 +1,6 @@ -# Anton Clock - Large font digital watch with seconds and date +# Anton Clock Plus - Large font digital watch with seconds and date -Anton clock uses the "Anton" bold font to show the time in a clear, easily readable manner. On the Bangle.js 2, the time can be read easily even if the screen is locked and unlit. +Anton Clock Plus uses the "Anton" bold font to show the time in a clear, easily readable manner. On the Bangle.js 2, the time can be read easily even if the screen is locked and unlit. ## Features @@ -16,16 +16,16 @@ The basic time representation only shows hours and minutes of the current time. ## Usage -Install Anton clock through the Bangle.js app loader. -Configure it through the default Bangle.js configuration mechanism +* Install Anton Clock Plus through the Bangle.js app loader. +* Configure it through the default Bangle.js configuration mechanism (Settings app, "Apps" menu, "Anton clock" submenu). -If you like it, make it your default watch face +* If you like it, make it your default watch face (Settings app, "System" menu, "Clock" submenu, select "Anton clock"). ## Configuration -Anton clock is configured by the standard settings mechanism of Bangle.js's operating system: -Open the "Settings" app, then the "Apps" submenu and below it the "Anton clock" menu. +Anton Clock is configured by the standard settings mechanism of Bangle.js's operating system: +Open the `Settings` app, then the `Apps` submenu and below it the `Anton Clock+` menu. You configure Anton clock through several "on/off" switches in two menus. ### The main menu diff --git a/apps/antonclkplus/app-icon.js b/apps/antonclkplus/app-icon.js new file mode 100644 index 000000000..0c3aeb210 --- /dev/null +++ b/apps/antonclkplus/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgf/AH4At/l/Aofgh4DB+EAj4REQoM/AgP4AoeACIoLCg4FB4AFDCIwLCgAROgYIB8EBAoUH/gVBCIxQBCKYHBCJp9DI4ICBLJYRCn4RQEYMOR5ARDIgIRMYQZZBgARGZwZBDCKQrCgEDR5AdBUIQRJDoLXFCJD7J/xrICIQFCn4RH/4LDAoTaCCI4Ar/LLDCBfypMkCgMkyV/CJOSCIOf5IRGFwOfCJNP//JnmT588z/+pM/BYIRCk4RC/88+f/n4RCngRCz1JCIf5/nzGoQRIHwXPCIPJI4f8CJHJGQJKCCI59LCI5ZCCJ/+v/kBoM/+V/HIJrHBYJWB/JKB5x9JEYP8AQKdBpwRL841Dp41KZoTxBHYTXBWY77PCKKhJ/4/CcgMkXoQAiA=")) diff --git a/apps/antonclkplus/app.js b/apps/antonclkplus/app.js new file mode 100644 index 000000000..409d7d487 --- /dev/null +++ b/apps/antonclkplus/app.js @@ -0,0 +1,238 @@ +// Clock with large digits using the "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)); +}; + +Graphics.prototype.setFontAntonSmall = function(scale) { + // Actual height 53 (52 - 0) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAAAAAAAAAAAAAMAAAAAAAAD8AAAAAAAA/8AAAAAAAf/8AAAAAAH//8AAAAAB///8AAAAA////8AAAAP////8AAAD/////8AAB//////8AAf//////8AH///////4A///////+AA///////AAA//////wAAA/////8AAAA////+AAAAA////gAAAAA///4AAAAAA//8AAAAAAA//AAAAAAAA/wAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/////wAAA//////8AAB//////+AAH///////gAH///////gAP///////wAf///////4Af///////4A////////8A////////8A////////8A//AAAAD/8A/8AAAAA/8A/8AAAAA/8A/8AAAAA/8A/+AAAAB/8A////////8A////////8A////////8Af///////4Af///////4AP///////wAP///////wAH///////gAD///////AAA//////8AAAP/////wAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAAA/4AAAAAAAA/4AAAAAAAB/wAAAAAAAB/wAAAAAAAD/wAAAAAAAD/gAAAAAAAH///////8AP///////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/4AAAP8AA//4AAA/8AB//4AAH/8AH//4AAP/8AP//4AA//8AP//4AB//8Af//4AD//8Af//4AP//8A///4Af//8A///4A///8A///4D///8A//AAH///8A/8AAP///8A/8AA//+/8A/8AD//8/8A/+Af//w/8A//////g/8A/////+A/8A/////8A/8Af////4A/8Af////wA/8AP////AA/8AP///+AA/8AH///8AA/8AD///wAA/8AA///AAA/8AAP/4AAA/8AAAAAAAAAAAAAAAAAAAAAAH4AAf/gAAA/4AAf/8AAD/4AAf//AAH/4AAf//gAP/4AAf//wAP/4AAf//wAf/4AAf//4Af/4AAf//4A//4AAf//8A//4AAf//8A//4AAP//8A//A/8AB/8A/8A/8AA/8A/8B/8AA/8A/8B/8AA/8A/+D//AB/8A////////8A////////8A////////8Af///////4Af///////4Af///////wAP///////gAH//9////gAD//4///+AAB//wf//4AAAP/AH//gAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAAAB//wAAAAAAP//wAAAAAD///wAAAAA////wAAAAH////wAAAB/////wAAAf/////wAAD//////wAA///////wAA/////h/wAA////wB/wAA///8AB/wAA///AAB/wAA//gAAB/wAA////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8AAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAAAAAAAAAAAAAAAAAAAAAP/4AA////4P/+AA////4P//AA////4P//gA////4P//wA////4P//wA////4P//4A////4P//4A////4P//8A////4P//8A////4P//8A/8H/AAB/8A/8H+AAA/8A/8P+AAA/8A/8P+AAA/8A/8P/gAD/8A/8P/////8A/8P/////8A/8P/////8A/8P/////4A/8H/////4A/8H/////wA/8D/////wA/8B/////gA/8A////+AA/8AP///4AAAAAB///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////wAAAf/////8AAB///////AAH///////gAP///////wAP///////wAf///////4Af///////4A////////8A////////8A////////8A/+AH/AB/8A/8AP+AA/8A/4Af+AA/8A/8Af+AA/8A/8Af/gH/8A//4f////8A//4f////8A//4f////8Af/4f////4Af/4f////4AP/4P////wAP/4P////gAH/4H////AAD/4D///+AAB/4B///4AAAP4AP//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAAAAAAA/8AAAAAAAA/8AAAAAB8A/8AAAAB/8A/8AAAAf/8A/8AAAH//8A/8AAA///8A/8AAH///8A/8AA////8A/8AD////8A/8Af////8A/8B/////8A/8P/////8A/8//////8A////////AA///////AAA//////gAAA/////4AAAA/////AAAAA////4AAAAA////AAAAAA///8AAAAAA///gAAAAAA//+AAAAAAA//wAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/gD//gAAA//4P//8AAD//8f///AAH//+////gAH///////wAP///////4AP///////8Af///////8Af///////+Af///////+A////////+A//B//AB/+A/+A/+AA/+A/8Af+AA/+A/+Af+AA/+A//A//AB/+A////////+Af///////+Af///////+Af///////8Af///////8AP///////4AH///////4AH//+////wAD//+////AAA//4P//+AAAP/gH//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH//gAfgAAA///8A/8AAB///+A//AAH////A//gAH////g//wAP////g//wAf////w//4Af////w//4A/////w//8A/////w//8A/////w//8A//gP/wA/8A/8AD/wA/8A/8AD/wAf8A/8AD/gA/8A/+AH/AB/8A////////8A////////8A////////8Af///////4Af///////4Af///////wAP///////wAH///////gAD//////+AAA//////4AAAP/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DhgeFB4eHh4eHh4eDw=="), 60 + (scale << 8) + (1 << 16)); +}; + +{ // must be inside our own scope here so that when we are unloaded everything disappears + // we also define functions using 'let fn = function() {..}' for the same reason. function decls are global + +const SETTINGSFILE = "antonclk.json"; +const isBangle1 = (process.env.HWVERSION == 1); + +// variables defined from settings +let secondsMode; +let secondsColoured; +let secondsWithColon; +let dateOnMain; +let dateOnSecs; +let weekDay; +let calWeek; +let upperCase; +let vectorFont; + +// dynamic variables +let drawTimeout; +let queueMillis = 1000; +let secondsScreen = true; + + + +//For development purposes +/* +require('Storage').writeJSON(SETTINGSFILE, { + secondsMode: "Unlocked", // "Never", "Unlocked", "Always" + secondsColoured: true, + secondsWithColon: true, + dateOnMain: "Long", // "Short", "Long", "ISO8601" + dateOnSecs: "Year", // "No", "Year", "Weekday", LEGACY: true/false + weekDay: true, + calWeek: true, + upperCase: true, + vectorFont: true, +}); +*/ + +// OR (also for development purposes) +/* +require('Storage').erase(SETTINGSFILE); +*/ + +// Load settings +let loadSettings = function() { + // Helper function default setting + function def (value, def) {return value !== undefined ? value : def;} + + var settings = require('Storage').readJSON(SETTINGSFILE, true) || {}; + secondsMode = def(settings.secondsMode, "Never"); + secondsColoured = def(settings.secondsColoured, true); + secondsWithColon = def(settings.secondsWithColon, true); + dateOnMain = def(settings.dateOnMain, "Long"); + dateOnSecs = def(settings.dateOnSecs, "Year"); + weekDay = def(settings.weekDay, true); + calWeek = def(settings.calWeek, false); + upperCase = def(settings.upperCase, true); + vectorFont = def(settings.vectorFont, false); + + // Legacy + if (dateOnSecs === true) + dateOnSecs = "Year"; + if (dateOnSecs === false) + dateOnSecs = "No"; +} + +// schedule a draw for the next second or minute +let queueDraw = function() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, queueMillis - (Date.now() % queueMillis)); +} + +let updateState = function() { + if (Bangle.isLCDOn()) { + if ((secondsMode === "Unlocked" && !Bangle.isLocked()) || secondsMode === "Always") { + secondsScreen = true; + queueMillis = 1000; + } else { + secondsScreen = false; + queueMillis = 60000; + } + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +} + +let isoStr = function(date) { + return date.getFullYear() + "-" + ("0" + (date.getMonth() + 1)).slice(-2) + "-" + ("0" + date.getDate()).slice(-2); +} + +let calWeekBuffer = [false,false,false]; //buffer tz, date, week no (once calculated until other tz or date is requested) +let ISO8601calWeek = function(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480 + dateNoTime = date; dateNoTime.setHours(0,0,0,0); + if (calWeekBuffer[0] === date.getTimezoneOffset() && calWeekBuffer[1] === dateNoTime) return calWeekBuffer[2]; + calWeekBuffer[0] = date.getTimezoneOffset(); + calWeekBuffer[1] = dateNoTime; + var tdt = new Date(date.valueOf()); + var dayn = (date.getDay() + 6) % 7; + tdt.setDate(tdt.getDate() - dayn + 3); + var firstThursday = tdt.valueOf(); + tdt.setMonth(0, 1); + if (tdt.getDay() !== 4) { + tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7); + } + calWeekBuffer[2] = 1 + Math.ceil((firstThursday - tdt) / 604800000); + return calWeekBuffer[2]; +} + +let doColor = function() { + return !isBangle1 && !Bangle.isLocked() && secondsColoured; +} + +// Actually draw the watch face +let draw = function() { + var x = g.getWidth() / 2; + var y = g.getHeight() / 2 - (secondsMode !== "Never" ? 24 : (vectorFont ? 12 : 0)); + g.reset(); + /* This is to mark the widget areas during development. + g.setColor("#888") + .fillRect(0, 0, g.getWidth(), 23) + .fillRect(0, g.getHeight() - 23, g.getWidth(), g.getHeight()).reset(); + /* */ + g.clearRect(0, 24, g.getWidth(), g.getHeight() - 24); // clear whole background (w/o widgets) + var date = new Date(); // Actually the current date, this one is shown + var timeStr = require("locale").time(date, 1); // Hour and minute + g.setFontAlign(0, 0).setFont("Anton").drawString(timeStr, x, y); // draw time + if (secondsScreen) { + y += 65; + var secStr = (secondsWithColon ? ":" : "") + ("0" + date.getSeconds()).slice(-2); + if (doColor()) + g.setColor(0, 0, 1); + g.setFont("AntonSmall"); + if (dateOnSecs !== "No") { // A bit of a complex drawing with seconds on the right and date on the left + g.setFontAlign(1, 0).drawString(secStr, g.getWidth() - (isBangle1 ? 32 : 2), y); // seconds + y -= (vectorFont ? 15 : 13); + x = g.getWidth() / 4 + (isBangle1 ? 12 : 4) + (secondsWithColon ? 0 : g.stringWidth(":") / 2); + var dateStr2 = (dateOnMain === "ISO8601" ? isoStr(date) : require("locale").date(date, 1)); + var year; + var md; + var yearfirst; + if (dateStr2.match(/\d\d\d\d$/)) { // formatted date ends with year + year = (dateOnSecs === "Year" ? dateStr2.slice(-4) : require("locale").dow(date, 1)); + md = dateStr2.slice(0, -4); + if (!md.endsWith(".")) // keep separator before the year only if it is a dot (31.12. but 31/12) + md = md.slice(0, -1); + yearfirst = false; + } else { // formatted date begins with year + if (!dateStr2.match(/^\d\d\d\d/)) // if year position cannot be detected... + dateStr2 = isoStr(date); // ...use ISO date format instead + year = (dateOnSecs === "Year" ? dateStr2.slice(0, 4) : require("locale").dow(date, 1)); + md = dateStr2.slice(5); // never keep separator directly after year + yearfirst = true; + } + if (dateOnSecs === "Weekday" && upperCase) + year = year.toUpperCase(); + g.setFontAlign(0, 0); + if (vectorFont) + g.setFont("Vector", 24); + else + g.setFont("6x8", 2); + if (doColor()) + g.setColor(1, 0, 0); + g.drawString(md, x, (yearfirst ? y + (vectorFont ? 26 : 16) : y)); + g.drawString(year, x, (yearfirst ? y : y + (vectorFont ? 26 : 16))); + } else { + g.setFontAlign(0, 0).drawString(secStr, x, y); // Just the seconds centered + } + } else { // No seconds screen: Show date and optionally day of week + y += (vectorFont ? 50 : (secondsMode !== "Never") ? 52 : 40); + var dateStr = (dateOnMain === "ISO8601" ? isoStr(date) : require("locale").date(date, (dateOnMain === "Long" ? 0 : 1))); + if (upperCase) + dateStr = dateStr.toUpperCase(); + g.setFontAlign(0, 0); + if (vectorFont) + g.setFont("Vector", 24); + else + g.setFont("6x8", 2); + g.drawString(dateStr, x, y); + if (calWeek || weekDay) { + var dowcwStr = ""; + if (calWeek) + dowcwStr = " #" + ("0" + ISO8601calWeek(date)).slice(-2); + if (weekDay) + dowcwStr = require("locale").dow(date, calWeek ? 1 : 0) + dowcwStr; //weekDay e.g. Monday or weekDayShort # e.g. Mon #01 + else //week #01 + dowcwStr = /*LANG*/"week" + dowcwStr; + if (upperCase) + dowcwStr = dowcwStr.toUpperCase(); + g.drawString(dowcwStr, x, y + (vectorFont ? 26 : 16)); + } + } + + // queue next draw + queueDraw(); +} + +// Init the settings of the app +loadSettings(); +// Clear the screen once, at startup +g.clear(); +// Set dynamic state and perform initial drawing +updateState(); +// Register hooks for LCD on/off event and screen lock on/off event +Bangle.on('lcdPower', updateState); +Bangle.on('lock', updateState); +// Show launcher when middle button pressed +Bangle.setUI({ + mode : "clock", + remove : function() { + // Called to unload all of the clock app + Bangle.removeListener('lcdPower', updateState); + Bangle.removeListener('lock', updateState); + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + delete Graphics.prototype.setFontAnton; + delete Graphics.prototype.setFontAntonSmall; + }}); +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); +} diff --git a/apps/antonclkplus/app.png b/apps/antonclkplus/app.png new file mode 100644 index 000000000..a38093c5f Binary files /dev/null and b/apps/antonclkplus/app.png differ diff --git a/apps/antonclkplus/metadata.json b/apps/antonclkplus/metadata.json new file mode 100644 index 000000000..05c59a4fb --- /dev/null +++ b/apps/antonclkplus/metadata.json @@ -0,0 +1,20 @@ +{ + "id": "antonclkplus", + "name": "Anton Clock Plus", + "shortName": "Anton Clock+", + "version": "0.10", + "description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.", + "readme":"README.md", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"antonclkplus.app.js","url":"app.js"}, + {"name":"antonclkplus.settings.js","url":"settings.js"}, + {"name":"antonclkplus.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"antonclkplus.json"}] +} diff --git a/apps/antonclkplus/screenshot.png b/apps/antonclkplus/screenshot.png new file mode 100644 index 000000000..e949b8a24 Binary files /dev/null and b/apps/antonclkplus/screenshot.png differ diff --git a/apps/antonclk/settings.js b/apps/antonclkplus/settings.js similarity index 83% rename from apps/antonclk/settings.js rename to apps/antonclkplus/settings.js index e452b02c7..4448c00ed 100644 --- a/apps/antonclk/settings.js +++ b/apps/antonclkplus/settings.js @@ -2,7 +2,6 @@ (function(back) { var FILE = "antonclk.json"; - // Load settings var settings = Object.assign({ secondsOnUnlock: false, }, require('Storage').readJSON(FILE, true) || {}); @@ -38,10 +37,9 @@ }, "< Back": () => back(), "Seconds...": () => E.showMenu(secmenu), - "Date": stringInSettings("dateOnMain", ["Short", "Long", "ISO8601"]), + "Date": stringInSettings("dateOnMain", ["Long", "Short", "ISO8601"]), "Show Weekday": { value: (settings.weekDay !== undefined ? settings.weekDay : true), - format: v => v ? "On" : "Off", onchange: v => { settings.weekDay = v; writeSettings(); @@ -49,15 +47,13 @@ }, "Show CalWeek": { value: (settings.calWeek !== undefined ? settings.calWeek : false), - format: v => v ? "On" : "Off", onchange: v => { settings.calWeek = v; writeSettings(); } }, "Uppercase": { - value: (settings.upperCase !== undefined ? settings.upperCase : false), - format: v => v ? "On" : "Off", + value: (settings.upperCase !== undefined ? settings.upperCase : true), onchange: v => { settings.upperCase = v; writeSettings(); @@ -65,7 +61,6 @@ }, "Vector font": { value: (settings.vectorFont !== undefined ? settings.vectorFont : false), - format: v => v ? "On" : "Off", onchange: v => { settings.vectorFont = v; writeSettings(); @@ -81,27 +76,22 @@ "< Back": () => E.showMenu(mainmenu), "Show": stringInSettings("secondsMode", ["Never", "Unlocked", "Always"]), "With \":\"": { - value: (settings.secondsWithColon !== undefined ? settings.secondsWithColon : false), - format: v => v ? "On" : "Off", + value: (settings.secondsWithColon !== undefined ? settings.secondsWithColon : true), onchange: v => { settings.secondsWithColon = v; writeSettings(); } }, "Color": { - value: (settings.secondsColoured !== undefined ? settings.secondsColoured : false), - format: v => v ? "On" : "Off", + value: (settings.secondsColoured !== undefined ? settings.secondsColoured : true), onchange: v => { settings.secondsColoured = v; writeSettings(); } }, - "Date": stringInSettings("dateOnSecs", ["No", "Year", "Weekday"]) + "Date": stringInSettings("dateOnSecs", ["Year", "Weekday", "No"]) }; - // Actually display the menu E.showMenu(mainmenu); }); - -// end of file diff --git a/apps/aptsciclk/ChangeLog b/apps/aptsciclk/ChangeLog new file mode 100644 index 000000000..ed32a45a2 --- /dev/null +++ b/apps/aptsciclk/ChangeLog @@ -0,0 +1,8 @@ +0.01: New App! +0.02: Icons, loading screen +0.03: Random icon, Shorter "loading" screen +0.04: Support for light and dark Themes +0.05: Small bugfix +0.06: Formatting +0.07: Added potato GLaDOS and quote functionality when you tap her +0.08: Fixed drawing issues with the quotes and added more diff --git a/apps/aptsciclk/README.md b/apps/aptsciclk/README.md new file mode 100644 index 000000000..718e3d408 --- /dev/null +++ b/apps/aptsciclk/README.md @@ -0,0 +1,11 @@ +# Description + +This is a simple clock based on the Portal Series. + +# Features + +The button in the center of the screen is interactable and the warning image will change when it is pressed. + +Potato GLaDOS in the bottom left corner is interactable and will display a quote when tapped. (You can add more quotes by editing the `aptsciclkquotes.txt` file seperating each quote with a `^`) + +When the app loads the Apeture Science Logo is displayed. diff --git a/apps/aptsciclk/app-icon.js b/apps/aptsciclk/app-icon.js new file mode 100644 index 000000000..d2a2dbbd6 --- /dev/null +++ b/apps/aptsciclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwgNKxAACEaIVDDKWAhAXGwAtODA4HBLR4YFD4QWICIhABGAoMBJRBZHC4wwHOQ4IFAgQwGUQ4YBAg4uMJIwDDGAjRLIgYLHc5gXJIwbKLC4hICb4gZKfAhgETJKHJLwwXRUooWKCImAJogXRMopGMNwkIC4oWLYYqtHC5rFJC5h0GIxwsGFyD8CC4wwOIxBIQFwoeBCxrwEFwYXTFgTXReI4uQC4apPC4xNERqBlGFx4XCeJ4nHD4kIIxY3KPoIxNBwYXEJRInEP44iGOgwXFBYYcDChCHHC4wMBC5BnJEoouMGAYXEJJCCJC4pOEcpYKBFIpJFZRQXGD4gWKXBUICxjdFIhwyJOJMAA=")) diff --git a/apps/aptsciclk/app.js b/apps/aptsciclk/app.js new file mode 100644 index 000000000..c2903cf37 --- /dev/null +++ b/apps/aptsciclk/app.js @@ -0,0 +1,368 @@ +const big = g.getWidth()>200; +const timeFontSize = big?5:4; +const dateFontSize = big?3:2; +const gmtFontSize = 2; +const font = "6x8"; + +const xyCenter = g.getWidth() / 2; +const yposTime = xyCenter*0.73; +const yposDate = xyCenter*0.48; +const yposYear = xyCenter*1.8; + +const buttonTolerance = 20; +const buttonX = 88; +const buttonY = 104; + +var pause = false; //set to true to pause any sort of drawing (except for quotes) + +function getImg(img){ + if (img == "w0"){//drink + return { + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOZFIQOD4EABwnwgEDBwf8g/4h4ODwYQBv4OC+AbDAIP+j/HAQIOC4Hwj4RBBwP8o8B/+PBwWOkEP/l/BwP4+JCB44OCj+Ih/+n4OB+PEoP38YOB/0YkUXGgIOB8cBi9f+IOCkEI+XvBwXigFG64OEg0/t4OEuP7BwkHx/PBwWigF8voOC+Uwg/ig4OCkMgv8QsIOB+cfSoOGLIUR/E/4ljBwPxx/B/0kO4UI/0P+J3C/HHVQOISoWEn+D/iPBBwIwC8IOCwcP84IBBwU4TAMHBwfAv+AcARBBgD3CBwX8gDnBBwfwewIODAgIABBwYHDB3oAEBwIHFByyDBABg")) +} + } + else if (img == "w1"){//cube dispenser + return { + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOI/3/+fvBwYEBnwO/By3APgN/O6IeBh4OF8AOcwADCBwX8g4dM/8fBwt774OE+/9Bwt/BxodH3oOcFgyVG8BhCBwX8hRwCBwXA0C6BBwc/w4OE41MBwtEo6VF84sE/1/54OLDo4sHHYxKHLIxoGO44AD/kAABo")) + } + } + else if (img == "w2"){//acid + return { + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOF+IOGngOF8/D8YGD/wdBB4nv4fzAwf4BwOfGQd/4f7/+//+f74OB4PwHIJKDx8P/4BBBwP8BwIBBBwXvh+Hw5ZD+Pwnl/NAcegJOBBwfgj0fBwvhBxcPgYEBBwXw/F+FghIB84OC/BfBOYQOBk/w/0f4f4nkGgFgh0hwED4H4jOBuF8hk/v/Hzlnx/zFgQZBGYLCD4EHaIn8gAOF8EDBwn+dgQOK/8AN4IOD+EABww0BBwqGEBwIWBBwk8CwIODg/gv4OEv4OD+4OBBAIOBRYIFBh+PcAQdC+gOCDoN+h/vBwPP/wOB/wOBwJCBBwP2oa3BLALgBiA7BOwIvB/+DQoV/d4hPBBwQsB/wJB8ZoEAAZoDAAQOPRQIAM")) + } + } + else if (img == "w3"){//turret + return { + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOi+IOGh4OF8AOF/UNBwthx4OE+0YBwtBh4OE6mQBwn7rEfBwl22IOE99gBwn99UzBwUc/+90YsC8HH+++n98n/+g0++2Z+4OB4Fz73T74OCg877d8/YdC+d7u/v3gsBjEvt/+O4X+gvtIgI7CwG934OD8E326kD/0A+yzEwEO74OD/EArYOEgEDv4OD+PAl4OEnkBaInz0EPBwk3iAdE+XwSIYDBj2Oj4OD/fYvIOEvdHz4OD99unIOD/vt44OE3u4Dou3h4OE+3x/IOE70/Bwn78/9Bwl4LAQ7Dx75DBwP4Awb+EBwgAEBz0AABo=")) + } + } + else if (img == "w4"){//falling cube + return { + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOC+YOF/0PBwvgv4OE/kFBwvAyIdFnYeBBwYeDDofng4OE8vYDonx7uPBwkf/+/Bwfh+czBwf+g/5z4OD+FevIdEhMDDon/0E3BwgeBJQgeB+5ZFvAEBBwfzgYOEw/XLInwn3BBwf8gH4LYIOCwUHDonwmE4HYkHwKkE8P4XYQOCv7dCYQkBWYsAWYvAiAsE/EDJQn/wF+CwJZDg/gBwgrBXYIOC8D+FNAL+F4eDBwn4nh2BBweHFYJ3EFYQOC/0P/AOECgIOE/E/BwsHBwvACAIODWAQOEJAIOFAgIOEQ4QsEAAOfBwoACBwgACBw8AABo")) +} + } + else if (img == "w5"){//ball + return { + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOiv4OF8YOFAgQOEyYdGBw3zBw0BBwv4j4OB+EAgOD84OE+/ev4dD/3+BwvcugsE/u7t0f4aRC7e2sF8Bwlxg4dEu8YBwYsB/HDHYsMngOB8EDweHDon//PADoYABz0PBwfwnJKE/0OjZZC/kB4Hxz4OCwEYh+wBwXwgeA/+HBwUP8EP/0/BwPj/0DCQIOB/l/4DQBw4OBDIMPUoJKB+H/wY+B44OBj/4CoJKC+P/g7+FBAL+Fj4OFbwIOEI4IOF8YO6JQwAEaIgORgAANA")) + } + } + else if (img == "w6"){//ball recviver + return { + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOR/YOG34Ob/e7Bwu7CwQOhGgQOD34OF/0LBwvfv4dMuPfBwn29oOFtwONDowsHHY3+h7CNj4OF+IOc4A7NDo7gGJQ4ACBwX+//vBwnvBAIOK8EH/kBBwd+v/PSwIOB/fnjiWBBwXesHPLQIOB/2AgEvBwfgh0AFgf8gAuBLKQObgAANA==")) + } + } + else if (img == "w7"){//falling portals + return { + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AB0//4AE4YFE/H8BwtvBwvvvgOE/33Bwvf3gOE/v7Bxn5Bw2fHYv7/oOF3/cB118JQQOC4ODJQn8jEfLInBjBoE/0jO4pjD953CwCVF/EH5//+ykCwA8Cp4OB/MDz4DBEQUYjPzaIfn5k/74xC/l44f+BwePz1595ADDYPvv7vDMAN3Bwf4CAIOE4//BYIOB/0On47E8AFCBwcPTwYOCAgPAgE8Bwf8gEDBwOAGIJZDBwX9DofhUYRKDKIIOEAAQOD8EABwgcB+IODnoKB84OD37tCBwUzZ4QODZ4QdDnIFB/YODZwP+v47DJIIBBJQcAAwZyBABoA==")) + } + } + else if (img == "w8"){//flying portals + return { + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AB0//4AE4YFE/H8BwtvBwvvvgOE/33Bwvf3gdF/YOF/4OF/IOGgA7F8ENBwn8gHcBw/5AoOAg4OCh4sD/vD+AFB45KBBwfwv//BwMJgEIFAXcnvggF4kEBBwPMSIIYBz/8nAEBw5ZD4IhBO48AhpoG953FSo/2Ugv/p4OF/LCGaIyIBB34OH4EAngODbAMDBwnfDoqeCBy7RBBwnh//xBwc9BQPnBwe/AYO/BwUzFYQODGgYOCnIFB/YOD57WBv47Dj//AIJKDgAGDOQIANA")) + } + } + else if (img == "w9"){//cake + return { + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOY/oOG94OF/1/Bwv3FgwKCBwfnFhn8HY0LAQPwvgOB8EP/5uBBwP2gF4j+PBwP+sEEj/x44OB90Ao/8Dodwg8/nkH4ZXBgHnx8ABwPv/k98+ABwZEB+EAJQPj/3+nkAv4OB5+fz0Aj4OB98Ag+Ah/nBwJXB4EDHYSTB/EA/wsCSoJfBwAODNIPgBwgcBHYQOCC4QODn8Ah4ODGgMH+47D8EB/A7KTYMf4A7Eg/wHYgcBHZx3DcAPggbRBFgQcBcAQOB/iUBBwgcBBwgcCd4V/HYL+D/YOBDgIOC8/+DgIOC/+HfwIOD/4cCBwYAEBwQADBz0AABoA=")) + } + } + else if (img == "butPress"){ + return { + width : 176, height : 176, bpp : 4, + transparent : 1, + buffer : require("heatshrink").decompress(atob("iIA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AFEc5kM5hD/ACXMAAJXB5nBI35WSK4ZY/AB8cK4/MJP5WRK4pY/ABhPD5e7he7A4fBJn5XNKwJXCLAZX/ABUcKwhXDLAZN/VxhSCK4m8WH5XNVwZXEWARX/ABEcK4sAgBYDXYRP/K5RQC2ACE3e8K/5XPVgYDCK/4AKJIPLVYoEEBoPBIWPd6ICPK46uDAohXzjvd7oCMCAJX/K7cAAAZXFBQkBK/6v/ABPd6ICPK/4AaK4mwKwYEDV+4ARjhKBVQoDD3gMBK2MdAIRXXVYSuDK/5XN5ZRCgEAWQYLBK/4AJK4u7KwZXC4JXxiPd6JXV5hXH3hX1ACscWApXDMQRN/WBpYCK4QICV35XOLARXBA4ZX/ABccKAfMhgFEJf5YRK4hJ/ABxXH4JI/LCRXCK34ASjhXCIf4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/ACIA==")) + } + } + else if (img == "butUnpress"){ + return { + width : 176, height : 176, bpp : 4, + transparent : 1, + buffer : require("heatshrink").decompress(atob("iIA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AFEc5kM5hD/ACXMAAJXB5nBI35WSK4ZY/AB8cK4/MJP5WRK4pY/ABhQEK43BJn5X/AEMcK5fMJv6uPK46w/K/4AgjhPFgEALAxP/K5vAAQhX/K6KsDWApP/AA6uHWA/BIWOIwICPK46qFAohXxjGIxACMCAJX/K7cAAAZXFBQkBK/6v/ABOIwICPK/4AaKInAAhCv2ACMcVRC0FK2MYAIRXXVYSuFK/5XO5kAgCuFK/4AJJwvMKw3BK+MRxGBK/4ArjhXMJv6wQK4qu/K/4AjjhXKJf5YRK4hJ/ABxXH4JI/LCRXCK34ASjhXCIf4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/ACI=")) + } + } + + else if (img == "apetureLaboratories"){ + return { + width : 173, height : 43, bpp : 4, + transparent : 0, + buffer : require("heatshrink").decompress(atob("AA+IAAeAIv5UTK34APhABBKwpeBJX5VLJgJWGAwKv/ABL8EKomABIYA/KpJWHAoRW/KpZQCfwJSCAgRW/KphWFBgZW/KphNCAgSxEKP4ADJAatFKwWAL4oA/KpALGXQhS/Ko4MIwAOMAHREOKv5ZVVgcAh///4LDAwQAB+AIHCQYHMDQQYBDwQEGDIoaGTx4MCwAiCJgYhFBIYIHLw4HFBARjGESJUDehZVFVhJNLRI5VGDIIdEAgwiL+DzDJAJVJB4IMBCoKsHAYpFCDgr2IApYEIBpJFCKpqsCHgQbFIhBVHBhJoGTwyBRKp5mBDoY6GKoYqEXYg7JApKeHQJysEKpRmBDoasHQBAACM4qMGEAr0JAgZjGFYhVPgGAEIpVEAAaAEA4oyEW4qyKFZBoFKoisDJIJWLfIp3IQBJeJfgysMERpVCwEIVpijFBA6ZJdA4JHJYgEKQIx1EVgRVBVhj5IEIyZHHYoUGNw4MCQIwIKAARVDxBVRQo6ZJHZZoGU4yBGBAiBGVgRZBVhYlIfJBoFHZZoHfJRwGBAQrEVISvCKpwrEUZAqFVhzYKB4yKGQIpVDAYJVKEoYFDBIpVIb4ysMIgoPHOgoRFhGAVwKsLAFzQHACBVCVhQA/KpawBCJa76IhRWDVxIODAwTaFAocP///dhALGBQYJBCIYQBDYgKEBBAEDVgeIAgKgFBYZVEEYY+CH4YDBBgYFBBYoFEOYhmEDYgFFLIwEDKw2AKwhTFBoSsHIgglFAQo/HKIoDECBBZGc46eEAYa2EKox2Ga5LjLDoq8FKAgVDBAv/EghWGVgRWJKoaOFD4IgCViAWFVjKTGJIZOFKpKOHAQj3JAogCFPApcGKQziGLopKCU4hWFKojsEHYrXFCAJFId45CEDYh4EB4Y1DCYSzFJQT+FgAGCKooA/AAZMBfopWDKv5WLVgxT/AB+AKopW/ACBU/ABwA==")) + } + } + +else if (img == "apetureLaboratoriesLight"){ + return { + width : 173, height : 43, bpp : 4, + transparent : 1, + buffer : require("heatshrink").decompress(atob("iIAGxAADwINHAH5ULK34APjABBKwpeBJX5VLJgJWGAwKv/ABL8EKomBBIYA/KpJWHAoRW/KpZQCfwJSCAgRW/KphWFBgZW/KphNCAgSxEKP4ADJAatFKwWBL4oA/KpALGXQhS/Ko4MIwIOMAHREOKv5ZVVgcRiEAgALDAwQABgIIHCQYHMDQQYBDwQEGDIoaGTx4MCwIiCJgYhFBIYIHLw4HFBARjGESJUDehZVFVhJNLRI5VGDIIdEAgwiLgLzDJAJVJB4IMBCoKsHAYpFCDgr2IApYEIBpJFCKpqsCHgQbFIhBVHBhJoGTwyBRKp5mBDoY6GKoYqEXYg7JApKeHQJysEKpRmBDoasHQBAACM4qMGEAr0JAgZjGFYhVPiOBEIpVEAAaAEA4oyEW4qyKFZBoFKoisDJIJWLfIp3IQBJeJfgysMERpVCwMYVpijFBA6ZJdA4JHJYgEKQIx1EVgRVBVhj5IEIyZHHYoUGNw4MCQIwIKAARVDxBVRQo6ZJHZZoGU4yBGBAiBGVgRZBVhYlIfJBoFHZZoHfJRwGBAQrEVISvCKpwrEUZAqFVhzYKB4yKGQIpVDAYJVKEoYFDBIpVIb4ysMIgoPHOgoRFjGBVwKsLAFzQHACBVCVhQA/KpawBCJa76IhRWDVxIODAwTaFAocQgEAdhALGBQYJBCIYQBDYgKEBBAEDVgeIAgKgFBYZVEEYY+CH4YDBBgYFBBYoFEOYhmEDYgFFLIwEDKw2BKwhTFBoSsHIgglFAQo/HKIoDECBBZGc46eEAYa2EKox2Ga5LjLDoq8FKAgVDBAsAEghWGVgRWJKoaOFD4IgCViAWFVjKTGJIZOFKpKOHAQj3JAogCFPApcGKQziGLopKCU4hWFKojsEHYrXFCAJFId45CEDYh4EB4Y1DCYSzFJQT+FiIGCKooA/AAZMBfopWDKv5WLVgxT/AB+BKopW/ACBU/ABsQA=")) + } + } + + else if (img == "potato"){ + return { + width : 54, height : 55, bpp : 4, + transparent : 6, + buffer : require("heatshrink").decompress(atob("swAEsEGA4oASEQ4ARGgNgDa8QsA3BKStowxTDDalikxTCRKsSNwY2BDSYvDGoI2TsoTDgwEBGx5IBs1VHIgaBGx4qCDQZOBUiUGstQDQ4FBKYwHGDQNWDRA3BCYsABotgJ4dkogABowcGAAUGOwogDDAVCAYScKOooFBooWCAAjqNAoQYHKYQ8ELQZzFsAMCiIeJAAdFMwiCEoIaNoICBoAaMiMUDA0ikMiigaIAAgaGDIIaBkcyoCHEMxqIBmdEkMzmcxDSVBkYXBGoMzmUQDSMSDIURiIbBkDBCDRtBiYwBDIMREAMiDR6qBiI0BkQEBKQKkBUJQAEoCfBC4MVgMSDYMkGwQaBeBMUiC3BGYNN8kSHgM0DQrsGAAMQQAMyCwIaBgK/BmIaKM4IGCiMTDQXd9waCmlENZIaEgESKAMhpxQEDQSiEoJTGiEimaGCqIaBmKABUQwyBUIzRBkIXBDoI8BkAaDGwgaFEIMCeQUSYAKNBmLYDGwJ/DDYlFqAaBGwILBGgLyBUIRSFQghrCgEjDYMiiTdCggaFDYgaFKIKIBmcTkciiA1GNwpsFgI4BmZsBkVEDRAbJiBTCNQMABIIZHfBCPBDQKSCoFEGhA2IKIMQgJ1CoCFHGxVBeASQDgAZJDQ8RQIMyDQkGDZQ1GDILtBAwNGsAaLs1hokECYNCaQMxDIVmDRoOBDQifCBggaNKgMRqUhiovFGxoMEsCADAQQaMsUWJBi+LsMRqtWDSwUBqvFqpVFQ54UCstVqEGsDbBQx4lFgAABotRAgQABT54ADqtRM5jjQAAQ")) + } + } + + else if (img == "apetureWatch"){ + return { + width : 176, height : 176, bpp : 4, + transparent : 2, + buffer : require("heatshrink").decompress(atob("kQA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A+/4A/AH4A/AH4A/AD0RgAASgMQCqgrqiJX/K/5X/K/5XR7vQK/8IxBX/K/5X/K/5X/K/5X/K/5X/K/5X/K/5Xah////wK/5XTKwIABK4YAG//x7vRBhAA2jGIAYMfK4fxCRAOCK/5XFKwYABBgTBEh4LC7vQbabxLFYoVPFZMIxADBK4sAK4wLDK/5XSBYhX/K6MPBIQDBK/5XD+BXMBAQQCK/5XDKAJXKVwRWBAoJX/K4j3CUohXDL4YACK/5XF+CuEK4yuCK/5XHWAZOCK4cPKwhX/K45MFK4YAGK/5XGLApX/K6X/K5sPK/5XIWAZXJ/5X/K6sPK/5XPAoauDK/5XJ/5XDj5eEVwRX/K5y2FVwRX/K4/wK44GDVwRX/K50fWAi9DK/5XGUYRQCiKwCAwKuDK/5XGJgZXGMQJWDK/5XGAoKkCK4arEK/5XIVQRQDK/5XQAwZLC+BXBAwYACLwJX/K4auCWApXHCAJX/K5JYFAoi/Ch5X/K4ZHCAAZXEAoZnCK/5XLVQRXFBgZX/K4igCLAnxFYRdBBohX/K5cAiIrJK/5XEfIhX/K/5Xr+BX/K/5Xu/5X/K/5WKFhRXWl5XB+BX/K6ywFK9XxK4SMFK7JWCK58PK7sP/5XDGoxXr/5Xc//4xBXEHIKyPK54fFK5CPBK7cPxAyBK4oHBK7wKFK5AQBK7UP/GPD4JWCiMfK4IJBK7jOGFgYwEK4XRBg4AQ/CtFAAZXCBQ4AQjBXCCRxXcUoJLDjnMhnMBgTqCK7RzJYYxXC6DbTAAf4UYInB5gABK4PM4KBDdYwrQhBXBBQ5XIAAJXYh6GDKwRXDLASwCK7BxIK8auDjhXH5iwPK5gKIK8iuBKwhXFLAJX/AA0PxCuBKAhXG4KwCK/6uEx/xK5qwNK/KuBjhXL5kRWAJXrbapXEJ4pXH4JXXABRXhh+I+JXPiP//5X/K4WP+McJ4oLBLAxX/K5vAAQhX/ABBDBiJXFVgawFiMf//wK/4gBAAKuHWA/BCYQhIK8uIwACOK5CqFAohXxhGIxACMCAJX/K7YaEK4pKFK/6v/ABOIwACOK/4AMFZRXI4AEIV+wrO///iMcVRC0FiMf//wQaZXZhABCFZ0P//xK4qrCVwpXB/+PbahX15gLBVwpX/K5ERJwvMKw3BiP4K98AxGAFaH//5XPj/4+BXvFaRXCjhXMiMfxBXhGoIAcK4nxWAxXF4MR/GPK4Q4e+UiADcvK4UPWARXMj/4NwayKACUPK8KwDjhXKcQOIKYZX/eIkRLAhXEiKuBx5XEY4QAYDgJXiIAXxiJXH4KuC/4VDK/6wGLAZXCKwKuGK/6wIiMcK4QFBVw5X/WA2IwJSCAAYJBVwpX/WA2IJwJVDj///GPVwpX/LA34K4PxVoYHBKwxX/AAynCK4ZWC+BX/K5ixB/6vEVo5X/IxAABK4asHK/5XLgJXCBxRX/K/5XgLAQNLK/5X/K6r7CACxX/K/5XVfJcBiINLK/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K5o6bK/YaZK/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K6o6bK/cAABUBiINLK/5X/K/5X/K/5X/K/AMLACBX/K7DMaAAcRADA4eA==")) + } + } + + else if (img == "apetureWatchLight"){ + return { + width : 176, height : 176, bpp : 4, + transparent : 2, + buffer : require("heatshrink").decompress(atob("kQA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A+gAA/AH4A/AH4A/AD0R/4AS+MfCqgrqiJX/K/5X/K/5XR7vfK//4xBX/K/5X/K/5X/K/5X/K/5X/K/5X/K/5Xa+EAgEPK/5XTKwIABK4YAGgEB7vRBhAA2jGIAYMQK4cBCRAOCK/5XFKwYABBgTBE+ALC7vfbabxLFYoVPFZP4xADBK4v/K4wLDK/5XSBYhX/K6PwBIQDBK/5XDh5XMBAQQCK/5XDKAJXKVwRWBAoJX/K4j3CUohXDL4YACK/5XFh6uEK4yuCK/5XHWAZOCK4fwKwhX/K45MFK4YAGK/5XGLApX/K6UAK5vwK/5XIWAZXJgBX/K6vwK/5XPAoauDK/5XJgBXDiBeEVwRX/K5y2FVwRX/K48PK44GDVwRX/K50QWAi9DK/5XGUYRQCiKwCAwKuDK/5XGJgZXGMQJWDK/5XGAoKkCK4arEK/5XIVQRQDK/5XQAwZLCh5XBAwYACLwJX/K4auCWApXHCAJX/K5JYFAoi/C+BX/K4ZHCAAZXEAoZnCK/5XLVQRXFBgZX/K4igCLAkBFYRdBBohX/K5f/iIrJK/5XEfIhX/K/5Xrh5X/K/5XugBX/K/5WKFhRXWkBXBh5XvgJXCGgpXcWApXoF4KvD+COGK65WCK5/wK7gtCK4YmCLB5XfgBXbFgIzBK4mILCBXPDwpXIcIJXa+BPBKAJXFLARXdBQpXICAJXah/4x4qDAAMfK4IJBWBpXODgwsDAAcQK4XRBg4APgAwBVogADK4XwgInWjBXCCRxXbiD9BKwcc5kM5gNCRgXwK7JyJYYxXC77bTIwf4UYInB5gABK4PM4MRDQXwDpYrKawMABQ5XIAAJXYh6uDKwRXDLAQRDK64YIK8SuEjhXH5iwPK5gKIK8UP/APBKwhXFLAKwNK/ItBiJQEK43BDgRX/AAXw/GP+JXNGQXwK/5XDEgMcK5fMiIdBK9YAKK5cPK4RPFK4/BDoUPFagAJK8WI+JXPGYRX/IIWP+McJ4sAgBYGK/5XN4ACEK/4AH+AjCK4qsDWAsRDwIWCK/ogBAAKuHWA/BCYQhIK8uIx4COK5CqFAohXx/GIxACMCAJX/K7cAAAZXFBQkBK/6v/ABOIx4COK/4AMFZRXI4AEIV+wrN+AjCjiqIWgpUCCwRXr/ABCFZ0PBoJXFVYSuFK4P/x4VBK/5XI5kAgCuFK/5XIiJOF5hWG4MR/BXv/+Ix5XREgJXOj58BK94rR+AkCjhXMiMfxAUCK70AADpXE+KwGK4vBiP4x5XhgUiADcgEQTyCK5sf/ATDK/5DD+McK5UR/+IK/5XEeYcRLAhXEiKuBx4SDK/6wFiJXH4KuOK/SwELAZXCKwKuOK/ywBiMcK4QFBVwZX/K43/gACBxGBKQQADBIKuBh5X/K43/JAOIJwJVDIYP4x4NCK/5XHfAP4K4PxVoYHBBgRX/K5H/gCnCK4ZWDVxpX9LARABV4ZWQK/xYBgBXD+EAKx5X/AAMBK4RVQK/5ADK4RBSK/5YDIKZX/K/5XNfYQA1K/5X2eJkReKfxj4VTK/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5XvgAAYh5X8DTJX/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5XVgAAYK/orL+MRIKZX/K/5X/K/5X/K/5X/K/5XmgAAdiIA3")) + } + } +} + + +function drawStart(){ + g.clear(); + g.reset(); + if (g.theme.dark){apSciLab = getImg("apetureLaboratories");} + else {apSciLab = getImg("apetureLaboratoriesLight");} + g.drawImage(apSciLab, xyCenter-apSciLab.width/2, xyCenter-apSciLab.height/2); +} + +// Check settings for what type our clock should be +var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; + +// timeout used to update every minute +var drawTimeout; + +//warnings +var maxWarning = 9; +var curWarning = Math.floor(Math.random() * (maxWarning+1)); + +function unPause(delay, quote){ + if (pause){ + setTimeout(function() { + if (quote == undefined || quoteNum == quote){ + pause = false; + draw(); + } + }, delay); + } +} + +var quoteNum; + +function quote(fontsize, width, height, specificQuote){ + pause = true; + var finalString = ""; + var quotesFile; + var finalFontSize; + quotesFile = require("Storage").read("aptsciclkquotes.txt", 0, 0); //opens the quotes file + //console.log(quotesFile); + var quotes = quotesFile.split("^"); + var numQuotes = quotes.length;//number of quotes + var curQuote; + + if (specificQuote == undefined){ + quoteNum = Math.round(Math.random()*numQuotes)-1; + curQuote = quotes[quoteNum]; //quote to be displayed + } + else{ + quoteNum = specificQuote; + curQuote = quotes[quoteNum]; + } + + unPause(10000, quoteNum); + + var curWords = curQuote.split(" "); //individual words + //console.log(numQuotes); + + var maxChar = width/6/fontsize; + var maxLines = height/10/fontsize; + var curLines = 0; + var curLength = 0; + + + for (var i = 0; i < curWords.length; i++){ + //console.log(curLength+curWords[i].length); + if (curLength + curWords[i].length <= maxChar){ + finalString += " "+curWords[i]; + curLength += curWords[i].length+1; + //console.log("next"); + } + else{ + //console.log("break"); + curLines++; + if (curLines > maxLines){ + curLength = 0; + finalString = ""; + i = -1; + if (fontsize > 1){fontsize--;} + maxChar = width/6/fontsize; + maxLines = height/10/fontsize; + console.log(maxLines); + console.log(maxChar); + + } + else{ + curLength = 0; + finalString += "\n"; + i--; + } + } + finalFontSize = fontsize; + } + + + //drawing actual stuff + g.setColor(g.getBgColor()); + g.fillRect(10, 10+28, g.getWidth()-10,g.getWidth()-10); + g.reset(); + g.setFont(font, finalFontSize); + g.setFontAlign(0, 0); + g.drawString(finalString, xyCenter, xyCenter+14); + //quote length*pixels per character = pixel width + //height ~120 width ~160 +} + +function buttonPressed(){ + if (curWarning < maxWarning) curWarning += 1; + else curWarning = 0; + g.reset(); + buttonImg = getImg("butPress"); + g.drawImage(buttonImg, 0, 0); + + warningImg = getImg("w"+String(curWarning)); + g.drawImage(warningImg, 1, g.getWidth()-61); + + setTimeout(buttonUnpressed, 500); +} +function buttonUnpressed(){ + if (!pause){ + buttonImg = getImg("butUnpress"); + g.drawImage(buttonImg, 0, 0); + } + else{ + setTimeout(buttonUnpressed, 500); + } +} + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + + +function draw() { + if (pause){} + else{ + // get date + var d = new Date(); + var da = d.toString().split(" "); + + g.reset(); // default draw styles + //draw watchface + if (g.theme.dark){apSciWatch = getImg("apetureWatch");} + else {apSciWatch = getImg("apetureWatchLight");} + g.drawImage(apSciWatch, xyCenter-apSciWatch.width/2, xyCenter-apSciWatch.height/2); + + potato = getImg("potato"); + g.drawImage(potato, 118, 118); + + g.drawImage(warningImg, 1, g.getWidth()-61);//update warning + + // drawString centered + g.setFontAlign(0, 0); + + // draw time + var time = da[4].substr(0, 5).split(":"); + var hours = time[0], + minutes = time[1]; + var meridian = ""; + if (is12Hour) { + hours = parseInt(hours,10); + meridian = "AM"; + if (hours == 0) { + hours = 12; + meridian = "AM"; + } else if (hours >= 12) { + meridian = "PM"; + if (hours>12) hours -= 12; + } + hours = (" "+hours).substr(-2); + } + + g.setFont(font, timeFontSize); + g.drawString(`${hours}:${minutes}`, xyCenter+2, yposTime, false); + g.setFont(font, gmtFontSize); + g.drawString(meridian, xyCenter + 102, yposTime + 10, true); + + // draw Day, name of month, Date + var date = [da[0], da[1], da[2]].join(" "); + g.setFont(font, dateFontSize); + g.drawString(String(date), xyCenter, yposDate, false); + + + // draw year + g.setFont(font, dateFontSize); + g.drawString(d.getFullYear(), xyCenter+1, yposYear, true); + } + queueDraw(); +} + + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +Bangle.on('touch',(n,e)=>{ + //button is 88 104 + if (!pause && buttonX-buttonTolerance < e.x && e.x < buttonX+buttonTolerance && buttonY-buttonTolerance < e.y && e.y < buttonY+buttonTolerance){ + buttonPressed(); + } + //Potato GLaDOS + else if (!pause && 117 < e.x && e.x < 172 && 117 < e.y && e.y < 172){ + quote(2, 150, 140); + } + else{ + unPause(0); + } +}); + +//show Apeture laboritories +drawStart(); + +setTimeout(function() { + // clean app screen + g.clear(); + // Show launcher when button pressed + Bangle.setUI("clock"); + Bangle.loadWidgets(); + Bangle.drawWidgets(); + //update warning image + buttonPressed(); + // draw now + draw(); + }, 500); diff --git a/apps/aptsciclk/app.png b/apps/aptsciclk/app.png new file mode 100644 index 000000000..b37efdaf8 Binary files /dev/null and b/apps/aptsciclk/app.png differ diff --git a/apps/aptsciclk/metadata.json b/apps/aptsciclk/metadata.json new file mode 100644 index 000000000..77e40f843 --- /dev/null +++ b/apps/aptsciclk/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "aptsciclk", + "name": "Apeture Science Clock", + "shortName":"AptSci Clock", + "version": "0.08", + "description": "A clock based on the portal series", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "allow_emulator": false, + "readme":"README.md", + "storage": [ + {"name":"aptsciclkquotes.txt","url":"quotes.txt"}, + {"name":"aptsciclk.app.js","url":"app.js"}, + {"name":"aptsciclk.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/aptsciclk/quotes.txt b/apps/aptsciclk/quotes.txt new file mode 100644 index 000000000..bc7a04867 --- /dev/null +++ b/apps/aptsciclk/quotes.txt @@ -0,0 +1 @@ +Well here we are again^You euthanized your faithful Companion Cube more quickly than any test subject on record. Congratulations.^So get comfortable while I warm up the neurotoxin emitters^This isn't brave. It's murder. What did I ever do to you?^The difference between us is that I can feel pain.^Who's gonna make the cake when I'm gone? You?^Oh... It's you.^I've been really busy being dead. You know, after you MURDERED ME.^So. How are you holding up? BECAUSE I'M A POTATO.^You really do have brain damage, don't you?^You like revenge, right? Everybody likes revenge. Well, let's go get some.^It's been fun. Don't come back.^And then you showed up. You dangerous, mute lunatic.^Unbelievable. You, [subject name here] must be the pride of [subject hometown here.]^You are not a good person. You know that, right? Good people don't get up here.^Cake, and grief counseling, will be available at the conclusion of the test.^This is your fault. I'm going to kill you. And all the cake is gone. You don't even care, do you?^Momentum, a function of mass and velocity, is conserved between portals. In layman's terms, speedy thing goes in, speedy thing comes out. diff --git a/apps/aptsciclk/screenshot.png b/apps/aptsciclk/screenshot.png new file mode 100644 index 000000000..4803e4b13 Binary files /dev/null and b/apps/aptsciclk/screenshot.png differ diff --git a/apps/arrow/icon.js b/apps/arrow/icon.js index 380728484..917a5c979 100644 --- a/apps/arrow/icon.js +++ b/apps/arrow/icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mUywIebg/4AocP//AAoUf//+BYgMDh/+j/8Dol/wEAgYFBg/wgEBFIV+AQIVCh4fBnwFBgISBj8AhgJCh+Ag4BB4ED8ED+ASCAYJDBnkAvkAIYIWBjw8B/EB8AcBn//gF4DwJdBAQMA/EP738FYM8g/nz+A+EPgHx8YKBgfAjF4sAKBHIItBBQJMBFoJEBHII1BIQIDCvAUCAYYUBHIIDBMIXACgQpBRAIUBMIIrBDAIWCVYaiBTYQJCn4FBQgIIBEYKrDQ4MBVYUf8CQCCoP/w6DBAAKIBAocHAoIwBBgb5DDoYAZA=")) +require("heatshrink").decompress(atob("kkkwIEBgf8AYMB//4AgN///ggEf4E/wED+EACQN8C4Pgh4TBh8BCYMAvEcEoWD4AEBnk4gFggPHwAXBj1wgIwB88An/Ah3gg/+gF+gH/+EH8Ef/+ABAPvuAIBgnyCIQjBBAMAJAIIEuAICFgIIBh14BAMB8eAg0Ajk8KAXBKAU4jwDBg+ADoIXBg4NBnxPBEgPAgP8gZaBg//KoKLBKAIEBMQMAA")) diff --git a/apps/assistedgps/metadata.json b/apps/assistedgps/metadata.json index 1dbc42c87..4c91dcd35 100644 --- a/apps/assistedgps/metadata.json +++ b/apps/assistedgps/metadata.json @@ -1,11 +1,12 @@ { "id": "assistedgps", - "name": "Assisted GPS Update (AGPS)", + "name": "Assisted GPS Updater (AGPS)", "version": "0.03", - "description": "Downloads assisted GPS (AGPS) data to Bangle.js 1 or 2 for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.", + "description": "Downloads assisted GPS (AGPS) data to Bangle.js for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.", + "sortorder": -1, "icon": "app.png", "type": "RAM", - "tags": "tool,outdoors,agps", + "tags": "tool,outdoors,agps,gps,a-gps", "supports": ["BANGLEJS","BANGLEJS2"], "custom": "custom.html", "customConnect": true, diff --git a/apps/astral/ChangeLog b/apps/astral/ChangeLog index a51c96760..747e5ac2e 100644 --- a/apps/astral/ChangeLog +++ b/apps/astral/ChangeLog @@ -1,3 +1,5 @@ 0.01: Create astral clock app 0.02: Fixed Whirlpool galaxy RA/DA, larger compass display, fixed moonphase overlapping battery widget 0.03: Update to use Bangle.setUI instead of setWatch +0.04: Tell clock widgets to hide. +0.05: Added adjustment for Bangle.js magnetometer heading fix diff --git a/apps/astral/app-icon.js b/apps/astral/app-icon.js index 19d0998ff..d10e7a498 100644 --- a/apps/astral/app-icon.js +++ b/apps/astral/app-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mUyxH+AH4AG3YAGF1w0oExYykEZwyhEIyRJGUAfEYpgxjLxQNEGEajMGTohPGMBTQOZwwTGKoyXDASVWGSwtHKYYAJZbYVEGR7bSGKQWkDRQbOCAoxYRI4wMCIYxXXpQSYP6L4NCRLGXLZwdVMJwAWGKgwbD6aUTSzoRKfCAxbAogcJBxQx/GP4x/GP4xNAAoKKBxwxaGRQZPSqwZmGOZ7VY8oxnPZoJPGP57TBJavWGL7gRRaiPVGJxRGBJgxcACYxfHJIRLSrTHxGODHvGSgwcAEY=")) \ No newline at end of file +require("heatshrink").decompress(atob("kUw4MA///xP5gEH/AMBh//4AHBwF4gEDwEHgEB4fw8EAsf/jEAjPh80AhngjnAgcwAIMB5kA50A+cAmfAtnAhnYmc//8zhln/+c4YjBg0w440Bxk38EB/cP/0B//Dwf/+FxwEf8EGIAJGB2BkCnhiB4EPgF//EDFQIpB+HGgOMnkxwFjh8MsEY4YQHn/x//j//8n/wHYItBCAKFBhgKBKAIQBBgIQC4AQCmAQChkD/v8gcA/wCBBoMA7+39kAPwP/WIMP4aYBCAYhCCAkHAYOAA=")) diff --git a/apps/astral/app.js b/apps/astral/app.js index c445463f2..a435ca9e3 100644 --- a/apps/astral/app.js +++ b/apps/astral/app.js @@ -767,6 +767,24 @@ function draw() { g.clear(); current_moonphase = getMoonPhase(); +Bangle.setUI("clockupdown", btn => { + if (btn==0) { + if (!processing) { + if (!modeswitch) { + modeswitch = true; + if (mode == "planetary") mode = "extras"; + else mode = "planetary"; + } + else + modeswitch = false; + } + } else { + if (!processing) + ready_to_compute = true; + } +}); + + // Load widgets Bangle.loadWidgets(); Bangle.drawWidgets(); @@ -799,23 +817,6 @@ Bangle.setGPSPower(1); // Show launcher when button pressed Bangle.setClockMode(); -Bangle.setUI("clockupdown", btn => { - if (btn==0) { - if (!processing) { - if (!modeswitch) { - modeswitch = true; - if (mode == "planetary") mode = "extras"; - else mode = "planetary"; - } - else - modeswitch = false; - } - } else { - if (!processing) - ready_to_compute = true; - } -}); - setWatch(function () { if (!astral_settings.astral_default) { colours_switched = true; @@ -833,7 +834,7 @@ Bangle.on('mag', function (m) { if (isNaN(m.heading)) compass_heading = "---"; else - compass_heading = 360 - Math.round(m.heading); + compass_heading = Math.round(m.heading); // g.setColor("#000000"); // g.fillRect(160, 10, 160, 20); g.setColor(display_colour); diff --git a/apps/astral/metadata.json b/apps/astral/metadata.json index 3317092db..647066a13 100644 --- a/apps/astral/metadata.json +++ b/apps/astral/metadata.json @@ -1,7 +1,7 @@ { "id": "astral", "name": "Astral Clock", - "version": "0.03", + "version": "0.05", "description": "Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting. See Readme before using.", "icon": "app-icon.png", "type": "clock", diff --git a/apps/astrocalc/ChangeLog b/apps/astrocalc/ChangeLog index 60ef5da0a..746ab2162 100644 --- a/apps/astrocalc/ChangeLog +++ b/apps/astrocalc/ChangeLog @@ -1,2 +1,3 @@ 0.01: Create astrocalc app 0.02: Store last GPS lock, can be used instead of waiting for new GPS on start +0.03: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps diff --git a/apps/astrocalc/astrocalc-app.js b/apps/astrocalc/astrocalc-app.js index 4e7aa0b40..46fb855ec 100644 --- a/apps/astrocalc/astrocalc-app.js +++ b/apps/astrocalc/astrocalc-app.js @@ -9,7 +9,7 @@ * Calculate the Sun and Moon positions based on watch GPS and display graphically */ -const SunCalc = require("suncalc.js"); +const SunCalc = require("suncalc"); // from modules folder const storage = require("Storage"); const LAST_GPS_FILE = "astrocalc.gps.json"; let lastGPS = (storage.readJSON(LAST_GPS_FILE, 1) || null); @@ -385,4 +385,4 @@ function init() { } let m; -init(); \ No newline at end of file +init(); diff --git a/apps/astrocalc/metadata.json b/apps/astrocalc/metadata.json index 384c7fa1e..d77474700 100644 --- a/apps/astrocalc/metadata.json +++ b/apps/astrocalc/metadata.json @@ -1,7 +1,7 @@ { "id": "astrocalc", "name": "Astrocalc", - "version": "0.02", + "version": "0.03", "description": "Calculates interesting information on the sun and moon cycles for the current day based on your location.", "icon": "astrocalc.png", "tags": "app,sun,moon,cycles,tool,outdoors", @@ -9,7 +9,6 @@ "allow_emulator": true, "storage": [ {"name":"astrocalc.app.js","url":"astrocalc-app.js"}, - {"name":"suncalc.js","url":"suncalc.js"}, {"name":"astrocalc.img","url":"astrocalc-icon.js","evaluate":true}, {"name":"first-quarter.img","url":"first-quarter-icon.js","evaluate":true}, {"name":"last-quarter.img","url":"last-quarter-icon.js","evaluate":true}, diff --git a/apps/astrocalc/suncalc.js b/apps/astrocalc/suncalc.js deleted file mode 100644 index e2beaedca..000000000 --- a/apps/astrocalc/suncalc.js +++ /dev/null @@ -1,328 +0,0 @@ -/* - (c) 2011-2015, Vladimir Agafonkin - SunCalc is a JavaScript library for calculating sun/moon position and light phases. - https://github.com/mourner/suncalc -*/ - -(function () { 'use strict'; - - // shortcuts for easier to read formulas - - var PI = Math.PI, - sin = Math.sin, - cos = Math.cos, - tan = Math.tan, - asin = Math.asin, - atan = Math.atan2, - acos = Math.acos, - rad = PI / 180; - - // sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas - - - // date/time constants and conversions - - var dayMs = 1000 * 60 * 60 * 24, - J1970 = 2440588, - J2000 = 2451545; - - function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; } - function fromJulian(j) { return (j + 0.5 - J1970) * dayMs; } - function toDays(date) { return toJulian(date) - J2000; } - - - // general calculations for position - - var e = rad * 23.4397; // obliquity of the Earth - - function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); } - function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); } - - function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); } - function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); } - - function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; } - - function astroRefraction(h) { - if (h < 0) // the following formula works for positive altitudes only. - h = 0; // if h = -0.08901179 a div/0 would occur. - - // formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. - // 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad: - return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179)); - } - - // general sun calculations - - function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); } - - function eclipticLongitude(M) { - - var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center - P = rad * 102.9372; // perihelion of the Earth - - return M + C + P + PI; - } - - function sunCoords(d) { - - var M = solarMeanAnomaly(d), - L = eclipticLongitude(M); - - return { - dec: declination(L, 0), - ra: rightAscension(L, 0) - }; - } - - - var SunCalc = {}; - - - // calculates sun position for a given date and latitude/longitude - - SunCalc.getPosition = function (date, lat, lng) { - - var lw = rad * -lng, - phi = rad * lat, - d = toDays(date), - - c = sunCoords(d), - H = siderealTime(d, lw) - c.ra; - - return { - azimuth: azimuth(H, phi, c.dec), - altitude: altitude(H, phi, c.dec) - }; - }; - - - // sun times configuration (angle, morning name, evening name) - - var times = SunCalc.times = [ - [-0.833, 'sunrise', 'sunset' ], - [ -0.3, 'sunriseEnd', 'sunsetStart' ], - [ -6, 'dawn', 'dusk' ], - [ -12, 'nauticalDawn', 'nauticalDusk'], - [ -18, 'nightEnd', 'night' ], - [ 6, 'goldenHourEnd', 'goldenHour' ] - ]; - - // adds a custom time to the times config - - SunCalc.addTime = function (angle, riseName, setName) { - times.push([angle, riseName, setName]); - }; - - - // calculations for sun times - - var J0 = 0.0009; - - function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); } - - function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; } - function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); } - - function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); } - function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; } - - // returns set time for the given sun altitude - function getSetJ(h, lw, phi, dec, n, M, L) { - - var w = hourAngle(h, phi, dec), - a = approxTransit(w, lw, n); - return solarTransitJ(a, M, L); - } - - - // calculates sun times for a given date, latitude/longitude, and, optionally, - // the observer height (in meters) relative to the horizon - - SunCalc.getTimes = function (date, lat, lng, height) { - - height = height || 0; - - var lw = rad * -lng, - phi = rad * lat, - - dh = observerAngle(height), - - d = toDays(date), - n = julianCycle(d, lw), - ds = approxTransit(0, lw, n), - - M = solarMeanAnomaly(ds), - L = eclipticLongitude(M), - dec = declination(L, 0), - - Jnoon = solarTransitJ(ds, M, L), - - i, len, time, h0, Jset, Jrise; - - - var result = { - solarNoon: new Date(fromJulian(Jnoon)), - nadir: new Date(fromJulian(Jnoon - 0.5)) - }; - - for (i = 0, len = times.length; i < len; i += 1) { - time = times[i]; - h0 = (time[0] + dh) * rad; - - Jset = getSetJ(h0, lw, phi, dec, n, M, L); - Jrise = Jnoon - (Jset - Jnoon); - - result[time[1]] = new Date(fromJulian(Jrise) - (dayMs / 2)); - result[time[2]] = new Date(fromJulian(Jset) + (dayMs / 2)); - } - - return result; - }; - - - // moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas - - function moonCoords(d) { // geocentric ecliptic coordinates of the moon - - var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude - M = rad * (134.963 + 13.064993 * d), // mean anomaly - F = rad * (93.272 + 13.229350 * d), // mean distance - - l = L + rad * 6.289 * sin(M), // longitude - b = rad * 5.128 * sin(F), // latitude - dt = 385001 - 20905 * cos(M); // distance to the moon in km - - return { - ra: rightAscension(l, b), - dec: declination(l, b), - dist: dt - }; - } - - SunCalc.getMoonPosition = function (date, lat, lng) { - - var lw = rad * -lng, - phi = rad * lat, - d = toDays(date), - - c = moonCoords(d), - H = siderealTime(d, lw) - c.ra, - h = altitude(H, phi, c.dec), - // formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. - pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H)); - - h = h + astroRefraction(h); // altitude correction for refraction - - return { - azimuth: azimuth(H, phi, c.dec), - altitude: h, - distance: c.dist, - parallacticAngle: pa - }; - }; - - - // calculations for illumination parameters of the moon, - // based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and - // Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. - - // Function updated from gist: https://gist.github.com/endel/dfe6bb2fbe679781948c - - SunCalc.getMoonIllumination = function (date) { - let month = date.getMonth(); - let year = date.getFullYear(); - let day = date.getDate(); - - let c = 0; - let e = 0; - let jd = 0; - let b = 0; - - if (month < 3) { - year--; - month += 12; - } - - ++month; - c = 365.25 * year; - e = 30.6 * month; - jd = c + e + day - 694039.09; // jd is total days elapsed - jd /= 29.5305882; // divide by the moon cycle - b = parseInt(jd); // int(jd) -> b, take integer part of jd - jd -= b; // subtract integer part to leave fractional part of original jd - b = Math.round(jd * 8); // scale fraction from 0-8 and round - - if (b >= 8) b = 0; // 0 and 8 are the same so turn 8 into 0 - - return {phase: b}; - }; - - - function hoursLater(date, h) { - return new Date(date.valueOf() + h * dayMs / 24); - } - - // calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article - - SunCalc.getMoonTimes = function (date, lat, lng, inUTC) { - var t = date; - if (inUTC) t.setUTCHours(0, 0, 0, 0); - else t.setHours(0, 0, 0, 0); - - var hc = 0.133 * rad, - h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc, - h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx; - - // go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set) - for (var i = 1; i <= 24; i += 2) { - h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc; - h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc; - - a = (h0 + h2) / 2 - h1; - b = (h2 - h0) / 2; - xe = -b / (2 * a); - ye = (a * xe + b) * xe + h1; - d = b * b - 4 * a * h1; - roots = 0; - - if (d >= 0) { - dx = Math.sqrt(d) / (Math.abs(a) * 2); - x1 = xe - dx; - x2 = xe + dx; - if (Math.abs(x1) <= 1) roots++; - if (Math.abs(x2) <= 1) roots++; - if (x1 < -1) x1 = x2; - } - - if (roots === 1) { - if (h0 < 0) rise = i + x1; - else set = i + x1; - - } else if (roots === 2) { - rise = i + (ye < 0 ? x2 : x1); - set = i + (ye < 0 ? x1 : x2); - } - - if (rise && set) break; - - h0 = h2; - } - - var result = {}; - - if (rise) result.rise = hoursLater(t, rise); - if (set) result.set = hoursLater(t, set); - - if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true; - - return result; - }; - - - // export as Node module / AMD module / browser variable - if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc; - else if (typeof define === 'function' && define.amd) define(SunCalc); - else global.SunCalc = SunCalc; - -}()); diff --git a/apps/authentiwatch/ChangeLog b/apps/authentiwatch/ChangeLog index 7d6f96026..655916170 100644 --- a/apps/authentiwatch/ChangeLog +++ b/apps/authentiwatch/ChangeLog @@ -3,3 +3,5 @@ 0.03: Add "Calculating" placeholder, update JSON save format 0.04: Fix tapping at very bottom of list, exit on inactivity 0.05: Add support for bulk importing and exporting tokens +0.06: Add spaces to codes for improved readability (thanks @BartS23) +0.07: Bangle 2: Improve drag responsiveness and exit on button press diff --git a/apps/authentiwatch/README.md b/apps/authentiwatch/README.md index cc25e9604..a957cf93a 100644 --- a/apps/authentiwatch/README.md +++ b/apps/authentiwatch/README.md @@ -33,7 +33,7 @@ Keep those copies safe and secure. * Swipe right to exit to the app launcher. * Swipe left on selected counter token to advance the counter to the next value. -![Screenshot](screenshot.png) +![Screenshot](screenshot1.png) ![Screenshot](screenshot2.png) ![Screenshot](screenshot3.png) ![Screenshot](screenshot4.png) ## Creator diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index 640183230..05d94fc46 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -1,5 +1,10 @@ -const tokenextraheight = 16; -var tokendigitsheight = 30; +const COUNTER_TRIANGLE_SIZE = 10; +const TOKEN_EXTRA_HEIGHT = 16; +var TOKEN_DIGITS_HEIGHT = 30; +var TOKEN_HEIGHT = TOKEN_DIGITS_HEIGHT + TOKEN_EXTRA_HEIGHT; +const PROGRESSBAR_HEIGHT = 3; +const IDLE_REPEATS = 1; // when idle, the number of extra timed periods to show before hiding +const SETTINGS = "authentiwatch.json"; // Hash functions const crypto = require("crypto"); const algos = { @@ -7,33 +12,24 @@ const algos = { "SHA256":{sha:crypto.SHA256,retsz:32,blksz:64 }, "SHA1" :{sha:crypto.SHA1 ,retsz:20,blksz:64 }, }; -const calculating = "Calculating"; -const notokens = "No tokens"; -const notsupported = "Not supported"; +const CALCULATING = /*LANG*/"Calculating"; +const NO_TOKENS = /*LANG*/"No tokens"; +const NOT_SUPPORTED = /*LANG*/"Not supported"; // sample settings: // {tokens:[{"algorithm":"SHA1","digits":6,"period":30,"issuer":"","account":"","secret":"Bbb","label":"Aaa"}],misc:{}} -var settings = require("Storage").readJSON("authentiwatch.json", true) || {tokens:[],misc:{}}; +var settings = require("Storage").readJSON(SETTINGS, true) || {tokens:[], misc:{}}; if (settings.data ) tokens = settings.data ; /* v0.02 settings */ if (settings.tokens) tokens = settings.tokens; /* v0.03+ settings */ -// QR Code Text -// -// Example: -// -// otpauth://totp/${url}:AA_${algorithm}_${digits}dig_${period}s@${url}?algorithm=${algorithm}&digits=${digits}&issuer=${url}&period=${period}&secret=${secret} -// -// ${algorithm} : one of SHA1 / SHA256 / SHA512 -// ${digits} : one of 6 / 8 -// ${period} : one of 30 / 60 -// ${url} : a domain name "example.com" -// ${secret} : the seed code - function b32decode(seedstr) { - // RFC4648 - var i, buf = 0, bitcount = 0, retstr = ""; - for (i in seedstr) { - var c = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".indexOf(seedstr.charAt(i).toUpperCase(), 0); + // RFC4648 Base16/32/64 Data Encodings + let buf = 0, bitcount = 0, retstr = ""; + for (let c of seedstr.toUpperCase()) { + if (c == '0') c = 'O'; + if (c == '1') c = 'I'; + if (c == '8') c = 'B'; + c = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".indexOf(c); if (c != -1) { buf <<= 5; buf |= c; @@ -45,192 +41,127 @@ function b32decode(seedstr) { } } } - var retbuf = new Uint8Array(retstr.length); - for (i in retstr) { + let retbuf = new Uint8Array(retstr.length); + for (let i in retstr) { retbuf[i] = retstr.charCodeAt(i); } return retbuf; } -function do_hmac(key, message, algo) { - var a = algos[algo]; - // RFC2104 + +function hmac(key, message, algo) { + let a = algos[algo.toUpperCase()]; + // RFC2104 HMAC if (key.length > a.blksz) { key = a.sha(key); } - var istr = new Uint8Array(a.blksz + message.length); - var ostr = new Uint8Array(a.blksz + a.retsz); - for (var i = 0; i < a.blksz; ++i) { - var c = (i < key.length) ? key[i] : 0; + let istr = new Uint8Array(a.blksz + message.length); + let ostr = new Uint8Array(a.blksz + a.retsz); + for (let i = 0; i < a.blksz; ++i) { + let c = (i < key.length) ? key[i] : 0; istr[i] = c ^ 0x36; ostr[i] = c ^ 0x5C; } istr.set(message, a.blksz); ostr.set(a.sha(istr), a.blksz); - var ret = a.sha(ostr); - // RFC4226 dynamic truncation - var v = new DataView(ret, ret[ret.length - 1] & 0x0F, 4); + let ret = a.sha(ostr); + // RFC4226 HOTP (dynamic truncation) + let v = new DataView(ret, ret[ret.length - 1] & 0x0F, 4); return v.getUint32(0) & 0x7FFFFFFF; } -function hotp(d, token, dohmac) { - var tick; + +function formatOtp(otp, digits) { + // add 0 padding + let ret = "" + otp % Math.pow(10, digits); + while (ret.length < digits) { + ret = "0" + ret; + } + // add a space after every 3rd or 4th digit + let re = (digits % 3 == 0 || (digits % 3 >= digits % 4 && digits % 4 != 0)) ? "" : "."; + return ret.replace(new RegExp("(..." + re + ")", "g"), "$1 ").trim(); +} + +function hotp(token) { + let d = Date.now(); + let tick, next; if (token.period > 0) { // RFC6238 - timed - var seconds = Math.floor(d.getTime() / 1000); - tick = Math.floor(seconds / token.period); + tick = Math.floor(Math.floor(d / 1000) / token.period); + next = (tick + 1) * token.period * 1000; } else { // RFC4226 - counter tick = -token.period; + next = d + 30000; } - var msg = new Uint8Array(8); - var v = new DataView(msg.buffer); + let msg = new Uint8Array(8); + let v = new DataView(msg.buffer); v.setUint32(0, tick >> 16 >> 16); v.setUint32(4, tick & 0xFFFFFFFF); - var ret = calculating; - if (dohmac) { - try { - var hash = do_hmac(b32decode(token.secret), msg, token.algorithm.toUpperCase()); - ret = "" + hash % Math.pow(10, token.digits); - while (ret.length < token.digits) { - ret = "0" + ret; - } - } catch(err) { - ret = notsupported; - } + let ret; + try { + ret = hmac(b32decode(token.secret), msg, token.algorithm); + ret = formatOtp(ret, token.digits); + } catch(err) { + ret = NOT_SUPPORTED; } - return {hotp:ret, next:((token.period > 0) ? ((tick + 1) * token.period * 1000) : d.getTime() + 30000)}; + return {hotp:ret, next:next}; } +// Tokens are displayed in three states: +// 1. Unselected (state.id<0) +// 2. Selected, inactive (no code) (state.id>=0,state.hotp.hotp=="") +// 3. Selected, active (code showing) (state.id>=0,state.hotp.hotp!="") +var fontszCache = {}; var state = { - listy: 0, - prevcur:0, - curtoken:-1, - nextTime:0, - otp:"", - rem:0, - hide:0 + listy:0, // list scroll position + id:-1, // current token ID + hotp:{hotp:"",next:0} }; -function drawToken(id, r) { - var x1 = r.x; - var y1 = r.y; - var x2 = r.x + r.w - 1; - var y2 = r.y + r.h - 1; - var adj, lbl, sz; - g.setClipRect(Math.max(x1, Bangle.appRect.x ), Math.max(y1, Bangle.appRect.y ), - Math.min(x2, Bangle.appRect.x2), Math.min(y2, Bangle.appRect.y2)); - lbl = tokens[id].label.substr(0, 10); - if (id == state.curtoken) { - // current token - g.setColor(g.theme.fgH); - g.setBgColor(g.theme.bgH); - g.setFont("Vector", tokenextraheight); - // center just below top line - g.setFontAlign(0, -1, 0); - adj = y1; - } else { - g.setColor(g.theme.fg); - g.setBgColor(g.theme.bg); - sz = tokendigitsheight; +function sizeFont(id, txt, w) { + let sz = fontszCache[id]; + if (!sz) { + sz = TOKEN_DIGITS_HEIGHT; do { g.setFont("Vector", sz--); - } while (g.stringWidth(lbl) > r.w); - // center in box - g.setFontAlign(0, 0, 0); - adj = (y1 + y2) / 2; + } while (g.stringWidth(txt) > w); + fontszCache[id] = ++sz; } - g.clearRect(x1, y1, x2, y2); - g.drawString(lbl, (x1 + x2) / 2, adj, false); - if (id == state.curtoken) { - if (tokens[id].period > 0) { - // timed - draw progress bar - let xr = Math.floor(Bangle.appRect.w * state.rem / tokens[id].period); - g.fillRect(x1, y2 - 4, xr, y2 - 1); - adj = 0; - } else { - // counter - draw triangle as swipe hint - let yc = (y1 + y2) / 2; - g.fillPoly([0, yc, 10, yc - 10, 10, yc + 10, 0, yc]); - adj = 12; - } - // digits just below label - sz = tokendigitsheight; - do { - g.setFont("Vector", sz--); - } while (g.stringWidth(state.otp) > (r.w - adj)); - g.drawString(state.otp, (x1 + adj + x2) / 2, y1 + tokenextraheight, false); - } - // shaded lines top and bottom - g.setColor(0.5, 0.5, 0.5); - g.drawLine(x1, y1, x2, y1); - g.drawLine(x1, y2, x2, y2); - g.setClipRect(0, 0, g.getWidth(), g.getHeight()); + g.setFont("Vector", sz); } -function draw() { - var timerfn = exitApp; - var timerdly = 10000; - var d = new Date(); - if (state.curtoken != -1) { - var t = tokens[state.curtoken]; - if (state.otp == calculating) { - state.otp = hotp(d, t, true).hotp; - } - if (d.getTime() > state.nextTime) { - if (state.hide == 0) { - // auto-hide the current token - if (state.curtoken != -1) { - state.prevcur = state.curtoken; - state.curtoken = -1; +tokenY = id => id * TOKEN_HEIGHT + AR.y - state.listy; +half = n => Math.floor(n / 2); + +function timerCalc() { + let timerfn = exitApp; + let timerdly = 10000; + if (state.id >= 0 && state.hotp.hotp != "") { + if (tokens[state.id].period > 0) { + // timed HOTP + if (state.hotp.next < Date.now()) { + if (state.cnt > 0) { + state.cnt--; + state.hotp = hotp(tokens[state.id]); + } else { + state.hotp.hotp = ""; } - state.nextTime = 0; + timerdly = 1; + timerfn = updateCurrentToken; } else { - // time to generate a new token - var r = hotp(d, t, state.otp != ""); - state.nextTime = r.next; - state.otp = r.hotp; - if (t.period <= 0) { - state.hide = 1; - } - state.hide--; - } - } - state.rem = Math.max(0, Math.floor((state.nextTime - d.getTime()) / 1000)); - } - if (tokens.length > 0) { - var drewcur = false; - var id = Math.floor(state.listy / (tokendigitsheight + tokenextraheight)); - var y = id * (tokendigitsheight + tokenextraheight) + Bangle.appRect.y - state.listy; - while (id < tokens.length && y < Bangle.appRect.y2) { - drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:(tokendigitsheight + tokenextraheight)}); - if (id == state.curtoken && (tokens[id].period <= 0 || state.nextTime != 0)) { - drewcur = true; - } - id += 1; - y += (tokendigitsheight + tokenextraheight); - } - if (drewcur) { - // the current token has been drawn - schedule a redraw - if (tokens[state.curtoken].period > 0) { - timerdly = (state.otp == calculating) ? 1 : 1000; // timed - } else { - timerdly = state.nexttime - d.getTime(); // counter - } - timerfn = draw; - if (tokens[state.curtoken].period <= 0) { - state.hide = 0; + timerdly = 1000; + timerfn = updateProgressBar; } } else { - // de-select the current token if it is scrolled out of view - if (state.curtoken != -1) { - state.prevcur = state.curtoken; - state.curtoken = -1; + // counter HOTP + if (state.cnt > 0) { + state.cnt--; + timerdly = 30000; + } else { + state.hotp.hotp = ""; + timerdly = 1; } - state.nexttime = 0; + timerfn = updateCurrentToken; } - } else { - g.setFont("Vector", tokendigitsheight); - g.setFontAlign(0, 0, 0); - g.drawString(notokens, Bangle.appRect.x + Bangle.appRect.w / 2, Bangle.appRect.y + Bangle.appRect.h / 2, false); } if (state.drawtimer) { clearTimeout(state.drawtimer); @@ -238,93 +169,236 @@ function draw() { state.drawtimer = setTimeout(timerfn, timerdly); } -function onTouch(zone, e) { - if (e) { - var id = Math.floor((state.listy + (e.y - Bangle.appRect.y)) / (tokendigitsheight + tokenextraheight)); - if (id == state.curtoken || tokens.length == 0 || id >= tokens.length) { - id = -1; - } - if (state.curtoken != id) { - if (id != -1) { - var y = id * (tokendigitsheight + tokenextraheight) - state.listy; - if (y < 0) { - state.listy += y; - y = 0; +function updateCurrentToken() { + drawToken(state.id); + timerCalc(); +} + +function updateProgressBar() { + drawProgressBar(); + timerCalc(); +} + +function drawProgressBar() { + let id = state.id; + if (id >= 0 && tokens[id].period > 0) { + let rem = Math.min(tokens[id].period, Math.floor((state.hotp.next - Date.now()) / 1000)); + if (rem >= 0) { + let y1 = tokenY(id); + let y2 = y1 + TOKEN_HEIGHT - 1; + if (y2 >= AR.y && y1 <= AR.y2) { + // token visible + y1 = y2 - PROGRESSBAR_HEIGHT; + if (y1 <= AR.y2) + { + // progress bar visible + y2 = Math.min(y2, AR.y2); + let xr = Math.floor(AR.w * rem / tokens[id].period) + AR.x; + g.setColor(g.theme.fgH) + .setBgColor(g.theme.bgH) + .fillRect(AR.x, y1, xr, y2) + .clearRect(xr + 1, y1, AR.x2, y2); } - y += (tokendigitsheight + tokenextraheight); - if (y > Bangle.appRect.h) { - state.listy += (y - Bangle.appRect.h); - } - state.otp = ""; + } else { + // token not visible + state.id = -1; } - state.nextTime = 0; - state.curtoken = id; - state.hide = 2; } } - draw(); +} + +// id = token ID number (0...) +function drawToken(id) { + let x1 = AR.x; + let y1 = tokenY(id); + let x2 = AR.x2; + let y2 = y1 + TOKEN_HEIGHT - 1; + let lbl = (id >= 0 && id < tokens.length) ? tokens[id].label.substr(0, 10) : ""; + let adj; + g.setClipRect(x1, Math.max(y1, AR.y), x2, Math.min(y2, AR.y2)); + if (id === state.id) { + g.setColor(g.theme.fgH) + .setBgColor(g.theme.bgH); + } else { + g.setColor(g.theme.fg) + .setBgColor(g.theme.bg); + } + if (id == state.id && state.hotp.hotp != "") { + // small label centered just below top line + g.setFont("Vector", TOKEN_EXTRA_HEIGHT) + .setFontAlign(0, -1, 0); + adj = y1; + } else { + // large label centered in box + sizeFont("l" + id, lbl, AR.w); + g.setFontAlign(0, 0, 0); + adj = half(y1 + y2); + } + g.clearRect(x1, y1, x2, y2) + .drawString(lbl, half(x1 + x2), adj, false); + if (id == state.id && state.hotp.hotp != "") { + adj = 0; + if (tokens[id].period <= 0) { + // counter - draw triangle as swipe hint + let yc = half(y1 + y2); + adj = COUNTER_TRIANGLE_SIZE; + g.fillPoly([AR.x, yc, AR.x + adj, yc - adj, AR.x + adj, yc + adj]); + adj += 2; + } + // digits just below label + x1 = half(x1 + adj + x2); + y1 += TOKEN_EXTRA_HEIGHT; + if (state.hotp.hotp == CALCULATING) { + sizeFont("c", CALCULATING, AR.w - adj); + g.drawString(CALCULATING, x1, y1, false) + .flip(); + state.hotp = hotp(tokens[id]); + g.clearRect(AR.x + adj, y1, AR.x2, y2); + } + sizeFont("d" + id, state.hotp.hotp, AR.w - adj); + g.drawString(state.hotp.hotp, x1, y1, false); + if (tokens[id].period > 0) { + drawProgressBar(); + } + } + g.setClipRect(0, 0, g.getWidth() - 1, g.getHeight() - 1); +} + +function changeId(id) { + if (id != state.id) { + state.hotp.hotp = CALCULATING; + let pid = state.id; + state.id = id; + if (pid >= 0) { + drawToken(pid); + } + if (id >= 0) { + drawToken( id); + } + } } function onDrag(e) { - if (e.x > g.getWidth() || e.y > g.getHeight()) return; - if (e.dx == 0 && e.dy == 0) return; - var newy = Math.min(state.listy - e.dy, tokens.length * (tokendigitsheight + tokenextraheight) - Bangle.appRect.h); - state.listy = Math.max(0, newy); - draw(); + state.cnt = IDLE_REPEATS; + if (e.b != 0 && e.dy != 0) { + let y = E.clip(state.listy - E.clip(e.dy, -AR.h, AR.h), 0, Math.max(0, tokens.length * TOKEN_HEIGHT - AR.h)); + if (state.listy != y) { + let id, dy = state.listy - y; + state.listy = y; + g.setClipRect(AR.x, AR.y, AR.x2, AR.y2) + .scroll(0, dy); + if (dy > 0) { + id = Math.floor((state.listy + dy) / TOKEN_HEIGHT); + y = tokenY(id + 1); + do { + drawToken(id); + id--; + y -= TOKEN_HEIGHT; + } while (y > AR.y); + } + if (dy < 0) { + id = Math.floor((state.listy + dy + AR.h) / TOKEN_HEIGHT); + y = tokenY(id); + while (y < AR.y2) { + drawToken(id); + id++; + y += TOKEN_HEIGHT; + } + } + } + } + if (e.b == 0) { + timerCalc(); + } +} + +function onTouch(zone, e) { + state.cnt = IDLE_REPEATS; + if (e) { + let id = Math.floor((state.listy + e.y - AR.y) / TOKEN_HEIGHT); + if (id == state.id || tokens.length == 0 || id >= tokens.length) { + id = -1; + } + if (state.id != id) { + if (id >= 0) { + // scroll token into view if necessary + let dy = 0; + let y = id * TOKEN_HEIGHT - state.listy; + if (y < 0) { + dy -= y; + y = 0; + } + y += TOKEN_HEIGHT; + if (y > AR.h) { + dy -= (y - AR.h); + } + onDrag({b:1, dy:dy}); + } + changeId(id); + } + } + timerCalc(); } function onSwipe(e) { - if (e == 1) { + state.cnt = IDLE_REPEATS; + switch (e) { + case 1: exitApp(); + break; + case -1: + if (state.id >= 0 && tokens[state.id].period <= 0) { + tokens[state.id].period--; + require("Storage").writeJSON(SETTINGS, {tokens:tokens, misc:settings.misc}); + state.hotp.hotp = CALCULATING; + drawToken(state.id); + } + break; } - if (e == -1 && state.curtoken != -1 && tokens[state.curtoken].period <= 0) { - tokens[state.curtoken].period--; - let newsettings={tokens:tokens,misc:settings.misc}; - require("Storage").writeJSON("authentiwatch.json", newsettings); - state.nextTime = 0; - state.otp = ""; - state.hide = 2; - } - draw(); + timerCalc(); } -function bangle1Btn(e) { +function bangleBtn(e) { + state.cnt = IDLE_REPEATS; if (tokens.length > 0) { - if (state.curtoken == -1) { - state.curtoken = state.prevcur; - } else { - switch (e) { - case -1: state.curtoken--; break; - case 1: state.curtoken++; break; - } - } - state.curtoken = Math.max(state.curtoken, 0); - state.curtoken = Math.min(state.curtoken, tokens.length - 1); - var fakee = {}; - fakee.y = state.curtoken * (tokendigitsheight + tokenextraheight) - state.listy + Bangle.appRect.y; - state.curtoken = -1; - state.nextTime = 0; - onTouch(0, fakee); - } else { - draw(); // resets idle timer + let id = E.clip(state.id + e, 0, tokens.length - 1); + onDrag({b:1, dy:state.listy - E.clip(id * TOKEN_HEIGHT - half(AR.h - TOKEN_HEIGHT), 0, Math.max(0, tokens.length * TOKEN_HEIGHT - AR.h))}); + changeId(id); + drawProgressBar(); } + timerCalc(); } function exitApp() { + if (state.drawtimer) { + clearTimeout(state.drawtimer); + } Bangle.showLauncher(); } Bangle.on('touch', onTouch); Bangle.on('drag' , onDrag ); Bangle.on('swipe', onSwipe); -if (typeof BTN2 == 'number') { - setWatch(function(){bangle1Btn(-1);}, BTN1, {edge:"rising", debounce:50, repeat:true}); - setWatch(function(){exitApp(); }, BTN2, {edge:"rising", debounce:50, repeat:true}); - setWatch(function(){bangle1Btn( 1);}, BTN3, {edge:"rising", debounce:50, repeat:true}); +if (typeof BTN1 == 'number') { + if (typeof BTN2 == 'number' && typeof BTN3 == 'number') { + setWatch(()=>bangleBtn(-1), BTN1, {edge:"rising" , debounce:50, repeat:true}); + setWatch(()=>exitApp() , BTN2, {edge:"falling", debounce:50}); + setWatch(()=>bangleBtn( 1), BTN3, {edge:"rising" , debounce:50, repeat:true}); + } else { + setWatch(()=>exitApp() , BTN1, {edge:"falling", debounce:50}); + } } Bangle.loadWidgets(); - -// Clear the screen once, at startup +const AR = Bangle.appRect; +// draw the initial display g.clear(); -draw(); +if (tokens.length > 0) { + state.listy = AR.h; + onDrag({b:1, dy:AR.h}); +} else { + g.setFont("Vector", TOKEN_DIGITS_HEIGHT) + .setFontAlign(0, 0, 0) + .drawString(NO_TOKENS, AR.x + half(AR.w), AR.y + half(AR.h), false); +} +timerCalc(); Bangle.drawWidgets(); diff --git a/apps/authentiwatch/interface.html b/apps/authentiwatch/interface.html index d2213b819..d7cd59f1a 100644 --- a/apps/authentiwatch/interface.html +++ b/apps/authentiwatch/interface.html @@ -12,8 +12,8 @@ body.select div.select,body.export div.export{display:block} body.select div.export,body.export div.select{display:none} body.select div#tokens,body.editing div#edit,body.scanning div#scan,body.showqr div#showqr,body.export div#tokens{display:block} #tokens th,#tokens td{padding:5px} -#tokens tr:nth-child(odd){background-color:#ccc} -#tokens tr:nth-child(even){background-color:#eee} +#tokens tr:nth-child(odd){background-color:#f1f1fc} +#tokens tr:nth-child(even){background-color:#fff} #qr-canvas{margin:auto;width:calc(100%-20px);max-width:400px} #advbtn,#scan,#tokenqr table{text-align:center} #edittoken tbody#adv{display:none} @@ -54,8 +54,9 @@ var tokens = settings.tokens; */ function base32clean(val, nows) { var ret = val.replaceAll(/\s+/g, ' '); - ret = ret.replaceAll(/0/g, 'O'); - ret = ret.replaceAll(/1/g, 'I'); + ret = ret.replaceAll('0', 'O'); + ret = ret.replaceAll('1', 'I'); + ret = ret.replaceAll('8', 'B'); ret = ret.replaceAll(/[^A-Za-z2-7 ]/g, ''); if (nows) { ret = ret.replaceAll(/\s+/g, ''); @@ -80,9 +81,9 @@ function b32encode(str) { function b32decode(seedstr) { // RFC4648 - var i, buf = 0, bitcount = 0, ret = ''; - for (i in seedstr) { - var c = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'.indexOf(seedstr.charAt(i).toUpperCase(), 0); + var buf = 0, bitcount = 0, ret = ''; + for (var c of seedstr.toUpperCase()) { + c = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'.indexOf(c); if (c != -1) { buf <<= 5; buf |= c; @@ -225,15 +226,18 @@ function editToken(id) { markup += selectMarkup('algorithm', otpAlgos, tokens[id].algorithm); markup += ''; markup += ''; - markup += ''; + markup += ''; markup += ''; - markup += ''; - markup += ''; + markup += ''; + markup += ' '; + markup += ''; + markup += ' '; if (tokens[id].isnew) { - markup += ''; + markup += ''; } else { - markup += ''; - markup += ''; + markup += ''; + markup += ' '; + markup += ''; } document.getElementById('edit').innerHTML = markup; document.body.className = 'editing'; @@ -303,9 +307,23 @@ function updateTokens() { return ''; }; const tokenButton = function(fn, id, label, dir) { - return ''; + return ''; }; - var markup = '
'; + var markup = ''; + markup += '
'; + markup += ''; + markup += ' '; + markup += ''; + markup += ' '; + markup += ''; + markup += ' '; + markup += ''; + markup += '
'; + markup += ''; + markup += ' '; + markup += ''; + markup += '
'; + markup += ''; /* any tokens marked new are cancelled new additions and must be removed */ @@ -330,15 +348,6 @@ function updateTokens() { markup += ''; } markup += '
'; markup += tokenSelect('all'); markup += 'TokenOrder
'; - markup += '
'; - markup += ''; - markup += ''; - markup += ''; - markup += ''; - markup += '
'; - markup += ''; - markup += ''; - markup += '
'; document.getElementById('tokens').innerHTML = markup; document.body.className = 'select'; } @@ -404,7 +413,7 @@ class proto3decoder { constructor(str) { this.buf = []; for (let i in str) { - this.buf = this.buf.concat(str.charCodeAt(i)); + this.buf.push(str.charCodeAt(i)); } } getVarint() { @@ -486,7 +495,7 @@ function startScan(handler,cancel) { document.body.className = 'scanning'; navigator.mediaDevices .getUserMedia({video:{facingMode:'environment'}}) - .then(function(stream){ + .then(stream => { scanning=true; video.setAttribute('playsinline',true); video.srcObject = stream; @@ -603,7 +612,7 @@ function qrBack() {
- +
@@ -612,7 +621,7 @@ function qrBack() {
- +
diff --git a/apps/authentiwatch/metadata.json b/apps/authentiwatch/metadata.json index 676d8da9f..36e1ea34d 100644 --- a/apps/authentiwatch/metadata.json +++ b/apps/authentiwatch/metadata.json @@ -3,8 +3,8 @@ "name": "2FA Authenticator", "shortName": "AuthWatch", "icon": "app.png", - "screenshots": [{"url":"screenshot.png"}], - "version": "0.05", + "screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot4.png"}], + "version": "0.07", "description": "Google Authenticator compatible tool.", "tags": "tool", "interface": "interface.html", diff --git a/apps/authentiwatch/screenshot.png b/apps/authentiwatch/screenshot.png deleted file mode 100644 index 2a7bcbd9a..000000000 Binary files a/apps/authentiwatch/screenshot.png and /dev/null differ diff --git a/apps/authentiwatch/screenshot1.png b/apps/authentiwatch/screenshot1.png new file mode 100644 index 000000000..c3ac0e3b7 Binary files /dev/null and b/apps/authentiwatch/screenshot1.png differ diff --git a/apps/authentiwatch/screenshot2.png b/apps/authentiwatch/screenshot2.png new file mode 100644 index 000000000..26bafdbb2 Binary files /dev/null and b/apps/authentiwatch/screenshot2.png differ diff --git a/apps/authentiwatch/screenshot3.png b/apps/authentiwatch/screenshot3.png new file mode 100644 index 000000000..80f2fb172 Binary files /dev/null and b/apps/authentiwatch/screenshot3.png differ diff --git a/apps/authentiwatch/screenshot4.png b/apps/authentiwatch/screenshot4.png new file mode 100644 index 000000000..00756228d Binary files /dev/null and b/apps/authentiwatch/screenshot4.png differ diff --git a/apps/banglerun/.gitignore b/apps/banglerun/.gitignore deleted file mode 100644 index c2658d7d1..000000000 --- a/apps/banglerun/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/apps/banglerun/ChangeLog b/apps/banglerun/ChangeLog deleted file mode 100644 index c778588cc..000000000 --- a/apps/banglerun/ChangeLog +++ /dev/null @@ -1,13 +0,0 @@ -0.01: First release -0.02: Bugfix time: Reset minutes to 0 when hitting 60 -0.03: Fix distance >=10 km (fix #529) -0.04: Use offscreen buffer for flickerless updates -0.05: Complete rewrite. New UI, GPS & HRM Kalman filters, activity logging -0.06: Reading HDOP directly from the GPS event (needs Espruino 2v07 or above) -0.07: Fixed GPS update, added guards against NaN values -0.08: Fix issue with GPS coordinates being wrong after the first one -0.09: Another GPS fix (log raw coordinates - not filtered ones) -0.10: Removed kalman filtering to allow distance log to work - Only log data every 5 seconds (not 1 sec) - Don't create a file until the first log entry is ready - Add labels for buttons diff --git a/apps/banglerun/README.md b/apps/banglerun/README.md deleted file mode 100644 index 80e984bfa..000000000 --- a/apps/banglerun/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# BangleRun - -An app for running sessions. Displays info and logs your run for later viewing. - -## Compilation - -The app is written in Typescript, and needs to be transpiled in order to be -run on the BangleJS. The easiest way to perform this step is by using the -ubiquitous [NPM package manager](https://www.npmjs.com/get-npm). - -After having installed NPM for your platform, checkout the `BangleApps` repo, -open a terminal, and navigate into the `apps/banglerun` folder. Then issue: - -``` -npm i -``` - -to install the project's build tools, and: - -``` -npm run build -``` - -To build the app. The last command will generate the `app.js` file, containing -the transpiled code for the BangleJS. diff --git a/apps/banglerun/app-icon.js b/apps/banglerun/app-icon.js deleted file mode 100644 index 9eeaced6e..000000000 --- a/apps/banglerun/app-icon.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("mEwwIHEuAEDgP8ApMDAqAXBjAGD/E8AgUcgF8CAX/BgIFBn//wAFCv//8PwAoP///5Aon/8AcB+IFB4AFB8P/34FBgfj/8fwAFB4f+g4cBg/H/w/Cg+HKQcPx4FEh4/CAoMfAocOj4/CKYRwELIIFDLII6BAoZSBLIYeCgP+v4FD/k/GAQFBHgcD/ABBIIX4gIFBSYPwAoUPAog/B8AFEwAFDDQQCBQoQFCZYYFigCKEgFwgAA==")) diff --git a/apps/banglerun/app.js b/apps/banglerun/app.js deleted file mode 100644 index b79255171..000000000 --- a/apps/banglerun/app.js +++ /dev/null @@ -1 +0,0 @@ -!function(){"use strict";var t;!function(t){t.Stopped="STOP",t.Paused="PAUSE",t.Running="RUN"}(t||(t={}));const n={STOP:63488,PAUSE:65504,RUN:2016};function e(t,n,e){g.setColor(0),g.fillRect(n-60,e,n+60,e+30),g.setColor(65535),g.drawString(t,n,e)}function i(i){var s;g.setFontVector(30),g.setFontAlign(0,-1,0),e((i.distance/1e3).toFixed(2),60,55),e(function(t){const n=Math.round(t),e=Math.floor(n/3600),i=Math.floor(n/60)%60,s=n%60;return(e?e+":":"")+("0"+i).substr(-2)+":"+("0"+s).substr(-2)}(i.duration),172,55),e(function(t){if(t<.1667)return"__'__\"";const n=Math.round(1e3/t),e=Math.floor(n/60),i=n%60;return("0"+e).substr(-2)+"'"+("0"+i).substr(-2)+'"'}(i.speed),60,115),e(i.hr.toFixed(0),172,115),e(i.steps.toFixed(0),60,175),e(i.cadence.toFixed(0),172,175),g.setFont("6x8",2),g.setColor(i.gpsValid?2016:63488),g.fillRect(0,216,80,240),g.setColor(0),g.drawString("GPS",40,220),g.setColor(65535),g.fillRect(80,216,160,240),g.setColor(0),g.drawString(("0"+(s=new Date).getHours()).substr(-2)+":"+("0"+s.getMinutes()).substr(-2),120,220),g.setColor(n[i.status]),g.fillRect(160,216,230,240),g.setColor(0),g.drawString(i.status,200,220),g.setFont("6x8").setFontAlign(0,0,1).setColor(-1),i.status===t.Paused?g.drawString("START",236,60,1).drawString(" CLEAR ",236,180,1):i.status===t.Running?g.drawString(" PAUSE ",236,60,1).drawString(" PAUSE ",236,180,1):g.drawString("START",236,60,1).drawString(" ",236,180,1)}function s(t){g.clear(),g.setColor(50712),g.setFont("6x8",2),g.setFontAlign(0,-1,0),g.drawString("DIST (KM)",60,32),g.drawString("TIME",180,32),g.drawString("PACE",60,92),g.drawString("HEART",180,92),g.drawString("STEPS",60,152),g.drawString("CADENCE",180,152),i(t),Bangle.drawWidgets()}function a(n){n.status===t.Stopped&&function(t){const n=(new Date).toISOString().replace(/[-:]/g,""),e=`banglerun_${n.substr(2,6)}_${n.substr(9,6)}`;t.file=require("Storage").open(e,"w"),t.fileWritten=!1}(n),n.status===t.Running?n.status=t.Paused:n.status=t.Running,i(n)}const r={fix:NaN,lat:NaN,lon:NaN,alt:NaN,vel:NaN,dop:NaN,gpsValid:!1,x:NaN,y:NaN,z:NaN,t:NaN,timeSinceLog:0,hr:60,hrError:100,file:null,fileWritten:!1,drawing:!1,status:t.Stopped,duration:0,distance:0,speed:0,steps:0,cadence:0};var o;o=r,Bangle.on("GPS",n=>function(n,e){n.lat=e.lat,n.lon=e.lon,n.alt=e.alt,n.vel=e.speed/3.6,n.fix=e.fix,n.dop=e.hdop,n.gpsValid=n.fix>0,function(n){const e=Date.now();let i=(e-n.t)/1e3;if(isFinite(i)||(i=0),n.t=e,n.timeSinceLog+=i,n.status===t.Running&&(n.duration+=i),!n.gpsValid)return;const s=6371008.8+n.alt,a=n.lat*Math.PI/180,r=n.lon*Math.PI/180,o=s*Math.cos(a)*Math.cos(r),g=s*Math.cos(a)*Math.sin(r),d=s*Math.sin(a);if(!n.x)return n.x=o,n.y=g,void(n.z=d);const u=o-n.x,l=g-n.y,c=d-n.z,f=Math.sqrt(u*u+l*l+c*c);n.x=o,n.y=g,n.z=d,n.status===t.Running&&(n.distance+=f,n.speed=n.distance/n.duration||0,n.cadence=60*n.steps/n.duration||0)}(n),i(n),n.gpsValid&&n.status===t.Running&&n.timeSinceLog>5&&(n.timeSinceLog=0,function(t){t.fileWritten||(t.file.write(["timestamp","latitude","longitude","altitude","duration","distance","heartrate","steps"].join(",")+"\n"),t.fileWritten=!0),t.file.write([Date.now().toFixed(0),t.lat.toFixed(6),t.lon.toFixed(6),t.alt.toFixed(2),t.duration.toFixed(0),t.distance.toFixed(2),t.hr.toFixed(0),t.steps.toFixed(0)].join(",")+"\n")}(n))}(o,n)),Bangle.setGPSPower(1),function(t){Bangle.on("HRM",n=>function(t,n){if(0===n.confidence)return;const e=n.bpm-t.hr,i=Math.abs(e)+101-n.confidence,s=t.hrError/(t.hrError+i)||0;t.hr+=e*s,t.hrError+=(i-t.hrError)*s}(t,n)),Bangle.setHRMPower(1)}(r),function(n){Bangle.on("step",()=>function(n){n.status===t.Running&&(n.steps+=1)}(n))}(r),function(t){Bangle.loadWidgets(),Bangle.on("lcdPower",n=>{t.drawing=n,n&&s(t)}),s(t)}(r),setWatch(()=>a(r),BTN1,{repeat:!0,edge:"falling"}),setWatch(()=>function(n){n.status===t.Paused&&function(t){t.duration=0,t.distance=0,t.speed=0,t.steps=0,t.cadence=0}(n),n.status===t.Running?n.status=t.Paused:n.status=t.Stopped,i(n)}(r),BTN3,{repeat:!0,edge:"falling"})}(); diff --git a/apps/banglerun/banglerun.png b/apps/banglerun/banglerun.png deleted file mode 100644 index bf2cd8af3..000000000 Binary files a/apps/banglerun/banglerun.png and /dev/null differ diff --git a/apps/banglerun/interface.html b/apps/banglerun/interface.html deleted file mode 100644 index 6388d3b65..000000000 --- a/apps/banglerun/interface.html +++ /dev/null @@ -1,217 +0,0 @@ - - - - - -
- - - - - diff --git a/apps/banglerun/jasmine.json b/apps/banglerun/jasmine.json deleted file mode 100644 index 813363b27..000000000 --- a/apps/banglerun/jasmine.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "spec_dir": "test", - "spec_files": [ - "**/*.spec.ts" - ] -} \ No newline at end of file diff --git a/apps/banglerun/metadata.json b/apps/banglerun/metadata.json deleted file mode 100644 index d66441c8d..000000000 --- a/apps/banglerun/metadata.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "banglerun", - "name": "BangleRun", - "shortName": "BangleRun", - "version": "0.10", - "description": "An app for running sessions. Displays info and logs your run for later viewing.", - "icon": "banglerun.png", - "tags": "run,running,fitness,outdoors", - "supports": ["BANGLEJS"], - "interface": "interface.html", - "allow_emulator": false, - "storage": [ - {"name":"banglerun.app.js","url":"app.js"}, - {"name":"banglerun.img","url":"app-icon.js","evaluate":true} - ] -} diff --git a/apps/banglerun/package.json b/apps/banglerun/package.json deleted file mode 100644 index 1f5cc677b..000000000 --- a/apps/banglerun/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "banglerun", - "version": "0.5.0", - "description": "Bangle.js app for running sessions", - "main": "app.js", - "types": "app.d.ts", - "scripts": { - "build": "rollup -c", - "test": "ts-node -P tsconfig.spec.json node_modules/jasmine/bin/jasmine --config=jasmine.json" - }, - "author": { - "name": "Stefano Baldan", - "email": "singintime@gmail.com" - }, - "license": "ISC", - "devDependencies": { - "@rollup/plugin-typescript": "^4.1.1", - "@types/jasmine": "^3.5.10", - "jasmine": "^3.5.0", - "rollup": "^2.10.2", - "rollup-plugin-terser": "^5.3.0", - "terser": "^4.7.0", - "ts-node": "^8.10.2", - "tslib": "^2.0.0", - "typescript": "^3.9.2" - } -} diff --git a/apps/banglerun/rollup.config.js b/apps/banglerun/rollup.config.js deleted file mode 100644 index f7027eb2b..000000000 --- a/apps/banglerun/rollup.config.js +++ /dev/null @@ -1,15 +0,0 @@ -import typescript from '@rollup/plugin-typescript'; -import { terser } from 'rollup-plugin-terser'; - -export default { - input: './src/app.ts', - output: { - dir: '.', - format: 'iife', - name: 'banglerun' - }, - plugins: [ - typescript(), - terser(), - ] -}; diff --git a/apps/banglerun/src/activity.ts b/apps/banglerun/src/activity.ts deleted file mode 100644 index c1a01f30b..000000000 --- a/apps/banglerun/src/activity.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { draw } from './display'; -import { initLog } from './log'; -import { ActivityStatus, AppState } from './state'; - -function startActivity(state: AppState): void { - if (state.status === ActivityStatus.Stopped) { - initLog(state); - } - - if (state.status === ActivityStatus.Running) { - state.status = ActivityStatus.Paused; - } else { - state.status = ActivityStatus.Running; - } - - draw(state); -} - -function stopActivity(state: AppState): void { - if (state.status === ActivityStatus.Paused) { - clearActivity(state); - } - - if (state.status === ActivityStatus.Running) { - state.status = ActivityStatus.Paused; - } else { - state.status = ActivityStatus.Stopped; - } - - draw(state); -} - -function clearActivity(state: AppState): void { - state.duration = 0; - state.distance = 0; - state.speed = 0; - state.steps = 0; - state.cadence = 0; -} - -export { clearActivity, startActivity, stopActivity }; diff --git a/apps/banglerun/src/app.ts b/apps/banglerun/src/app.ts deleted file mode 100644 index 7093e24e0..000000000 --- a/apps/banglerun/src/app.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { startActivity, stopActivity } from './activity'; -import { initDisplay } from './display'; -import { initGps } from './gps'; -import { initHrm } from './hrm'; -import { initState } from './state'; -import { initStep } from './step'; - -declare var BTN1: any; -declare var BTN3: any; -declare var setWatch: any; - -const appState = initState(); - -initGps(appState); -initHrm(appState); -initStep(appState); -initDisplay(appState); - -setWatch(() => startActivity(appState), BTN1, { repeat: true, edge: 'falling' }); -setWatch(() => stopActivity(appState), BTN3, { repeat: true, edge: 'falling' }); diff --git a/apps/banglerun/src/display.ts b/apps/banglerun/src/display.ts deleted file mode 100644 index 528890c35..000000000 --- a/apps/banglerun/src/display.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { ActivityStatus, AppState } from './state'; - -declare var Bangle: any; -declare var g: any; - -const STATUS_COLORS = { - 'STOP': 0xF800, - 'PAUSE': 0xFFE0, - 'RUN': 0x07E0, -} - -function initDisplay(state: AppState): void { - Bangle.loadWidgets(); - Bangle.on('lcdPower', (on: boolean) => { - state.drawing = on; - if (on) { - drawAll(state); - } - }); - drawAll(state); -} - -function drawBackground(): void { - g.clear(); - g.setColor(0xC618); - g.setFont('6x8', 2); - g.setFontAlign(0, -1, 0); - g.drawString('DIST (KM)', 60, 32); - g.drawString('TIME', 172, 32); - g.drawString('PACE', 60, 92); - g.drawString('HEART', 172, 92); - g.drawString('STEPS', 60, 152); - g.drawString('CADENCE', 172, 152); -} - -function drawValue(value: string, x: number, y: number) { - g.setColor(0x0000); - g.fillRect(x - 60, y, x + 60, y + 30); - g.setColor(0xFFFF); - g.drawString(value, x, y); -} - -function draw(state: AppState): void { - g.setFontVector(30); - g.setFontAlign(0, -1, 0); - - drawValue(formatDistance(state.distance), 60, 55); - drawValue(formatTime(state.duration), 172, 55); - drawValue(formatPace(state.speed), 60, 115); - drawValue(state.hr.toFixed(0), 172, 115); - drawValue(state.steps.toFixed(0), 60, 175); - drawValue(state.cadence.toFixed(0), 172, 175); - - g.setFont('6x8', 2); - - g.setColor(state.gpsValid ? 0x07E0 : 0xF800); - g.fillRect(0, 216, 80, 240); - g.setColor(0x0000); - g.drawString('GPS', 40, 220); - - g.setColor(0xFFFF); - g.fillRect(80, 216, 160, 240); - g.setColor(0x0000); - g.drawString(formatClock(new Date()), 120, 220); - - g.setColor(STATUS_COLORS[state.status]); - g.fillRect(160, 216, 230, 240); - g.setColor(0x0000); - g.drawString(state.status, 200, 220); - - g.setFont("6x8").setFontAlign(0,0,1).setColor(-1); - if (state.status === ActivityStatus.Paused) { - g.drawString("START",236,60,1).drawString(" CLEAR ",236,180,1); - } else if (state.status === ActivityStatus.Running) { - g.drawString(" PAUSE ",236,60,1).drawString(" PAUSE ",236,180,1); - } else { - g.drawString("START",236,60,1).drawString(" ",236,180,1); - } -} - -function drawAll(state: AppState) { - drawBackground(); - draw(state); - Bangle.drawWidgets(); -} - -function formatClock(date: Date): string { - return ('0' + date.getHours()).substr(-2) + ':' + ('0' + date.getMinutes()).substr(-2); -} - -function formatDistance(meters: number): string { - return (meters / 1000).toFixed(2); -} - -function formatPace(speed: number): string { - if (speed < 0.1667) { - return `__'__"`; - } - const pace = Math.round(1000 / speed); - const min = Math.floor(pace / 60); - const sec = pace % 60; - return ('0' + min).substr(-2) + `'` + ('0' + sec).substr(-2) + `"`; -} - -function formatTime(time: number): string { - const seconds = Math.round(time); - const hrs = Math.floor(seconds / 3600); - const min = Math.floor(seconds / 60) % 60; - const sec = seconds % 60; - return (hrs ? hrs + ':' : '') + ('0' + min).substr(-2) + `:` + ('0' + sec).substr(-2); -} - -export { - draw, - drawAll, - drawBackground, - drawValue, - formatClock, - formatDistance, - formatPace, - formatTime, - initDisplay, -}; diff --git a/apps/banglerun/src/gps.ts b/apps/banglerun/src/gps.ts deleted file mode 100644 index 1886ecfb2..000000000 --- a/apps/banglerun/src/gps.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { draw } from './display'; -import { updateLog } from './log'; -import { ActivityStatus, AppState } from './state'; - -declare var Bangle: any; - -interface GpsEvent { - lat: number; - lon: number; - alt: number; - speed: number; - hdop: number; - fix: number; -} - -const EARTH_RADIUS = 6371008.8; - -function initGps(state: AppState): void { - Bangle.on('GPS', (gps: GpsEvent) => readGps(state, gps)); - Bangle.setGPSPower(1); -} - -function readGps(state: AppState, gps: GpsEvent): void { - state.lat = gps.lat; - state.lon = gps.lon; - state.alt = gps.alt; - state.vel = gps.speed / 3.6; - state.fix = gps.fix; - state.dop = gps.hdop; - state.gpsValid = state.fix > 0; - - updateGps(state); - draw(state); - - /* Only log GPS data every 5 secs if we - have a fix and we're running. */ - if (state.gpsValid && - state.status === ActivityStatus.Running && - state.timeSinceLog > 5) { - state.timeSinceLog = 0; - updateLog(state); - } -} - -function updateGps(state: AppState): void { - const t = Date.now(); - let dt = (t - state.t) / 1000; - if (!isFinite(dt)) dt=0; - state.t = t; - state.timeSinceLog += dt; - - if (state.status === ActivityStatus.Running) { - state.duration += dt; - } - - if (!state.gpsValid) { - return; - } - - const r = EARTH_RADIUS + state.alt; - const lat = state.lat * Math.PI / 180; - const lon = state.lon * Math.PI / 180; - const x = r * Math.cos(lat) * Math.cos(lon); - const y = r * Math.cos(lat) * Math.sin(lon); - const z = r * Math.sin(lat); - - if (!state.x) { - state.x = x; - state.y = y; - state.z = z; - return; - } - - const dx = x - state.x; - const dy = y - state.y; - const dz = z - state.z; - const dpMag = Math.sqrt(dx * dx + dy * dy + dz * dz); - - state.x = x; - state.y = y; - state.z = z; - - if (state.status === ActivityStatus.Running) { - state.distance += dpMag; - state.speed = (state.distance / state.duration) || 0; - state.cadence = (60 * state.steps / state.duration) || 0; - } -} - -export { initGps, readGps, updateGps }; diff --git a/apps/banglerun/src/hrm.ts b/apps/banglerun/src/hrm.ts deleted file mode 100644 index 08dd237a7..000000000 --- a/apps/banglerun/src/hrm.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { AppState } from './state'; - -interface HrmData { - bpm: number; - confidence: number; - raw: string; -} - -declare var Bangle: any; - -function initHrm(state: AppState) { - Bangle.on('HRM', (hrm: HrmData) => updateHrm(state, hrm)); - Bangle.setHRMPower(1); -} - -function updateHrm(state: AppState, hrm: HrmData) { - if (hrm.confidence === 0) { - return; - } - - const dHr = hrm.bpm - state.hr; - const hrError = Math.abs(dHr) + 101 - hrm.confidence; - const hrGain = (state.hrError / (state.hrError + hrError)) || 0; - - state.hr += dHr * hrGain; - state.hrError += (hrError - state.hrError) * hrGain; -} - -export { initHrm, updateHrm }; diff --git a/apps/banglerun/src/log.ts b/apps/banglerun/src/log.ts deleted file mode 100644 index b6714e407..000000000 --- a/apps/banglerun/src/log.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { AppState } from './state'; - -declare var require: any; - -function initLog(state: AppState): void { - const datetime = new Date().toISOString().replace(/[-:]/g, ''); - const date = datetime.substr(2, 6); - const time = datetime.substr(9, 6); - const filename = `banglerun_${date}_${time}`; - state.file = require('Storage').open(filename, 'w'); - state.fileWritten = false; -} - -function updateLog(state: AppState): void { - if (!state.fileWritten) { - state.file.write([ - 'timestamp', - 'latitude', - 'longitude', - 'altitude', - 'duration', - 'distance', - 'heartrate', - 'steps', - ].join(',') + '\n'); - state.fileWritten = true; - } - state.file.write([ - Date.now().toFixed(0), - state.lat.toFixed(6), - state.lon.toFixed(6), - state.alt.toFixed(2), - state.duration.toFixed(0), - state.distance.toFixed(2), - state.hr.toFixed(0), - state.steps.toFixed(0), - ].join(',') + '\n'); -} - -export { initLog, updateLog }; diff --git a/apps/banglerun/src/state.ts b/apps/banglerun/src/state.ts deleted file mode 100644 index 14ef2dc5d..000000000 --- a/apps/banglerun/src/state.ts +++ /dev/null @@ -1,85 +0,0 @@ -enum ActivityStatus { - Stopped = 'STOP', - Paused = 'PAUSE', - Running = 'RUN', -} - -interface AppState { - // GPS NMEA data - fix: number; - lat: number; - lon: number; - alt: number; - vel: number; - dop: number; - gpsValid: boolean; - - // Absolute position data - x: number; - y: number; - z: number; - // Last fix time - t: number; - // Last time we saved log info - timeSinceLog : number; - - // HRM data - hr: number, - hrError: number, - - // Logger data - file: File; - fileWritten: boolean; - - // Drawing data - drawing: boolean; - - // Activity data - status: ActivityStatus; - duration: number; - distance: number; - speed: number; - steps: number; - cadence: number; -} - -interface File { - read: Function; - write: Function; - erase: Function; -} - -function initState(): AppState { - return { - fix: NaN, - lat: NaN, - lon: NaN, - alt: NaN, - vel: NaN, - dop: NaN, - gpsValid: false, - - x: NaN, - y: NaN, - z: NaN, - t: NaN, - timeSinceLog : 0, - - hr: 60, - hrError: 100, - - file: null, - fileWritten: false, - - drawing: false, - - status: ActivityStatus.Stopped, - duration: 0, - distance: 0, - speed: 0, - steps: 0, - cadence: 0, - } -} - -export { ActivityStatus, AppState, File, initState }; diff --git a/apps/banglerun/src/step.ts b/apps/banglerun/src/step.ts deleted file mode 100644 index c7fcb61ea..000000000 --- a/apps/banglerun/src/step.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ActivityStatus, AppState } from './state'; - -declare var Bangle: any; - -function initStep(state: AppState) { - Bangle.on('step', () => updateStep(state)); -} - -function updateStep(state: AppState) { - if (state.status === ActivityStatus.Running) { - state.steps += 1; - } -} - -export { initStep, updateStep }; diff --git a/apps/banglerun/tsconfig.json b/apps/banglerun/tsconfig.json deleted file mode 100644 index a341a5a5e..000000000 --- a/apps/banglerun/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "module": "es2015", - "noImplicitAny": true, - "target": "es2015" - }, - "include": [ - "src" - ] -} diff --git a/apps/banglerun/tsconfig.spec.json b/apps/banglerun/tsconfig.spec.json deleted file mode 100644 index 136ae137b..000000000 --- a/apps/banglerun/tsconfig.spec.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "noImplicitAny": true, - "target": "es2015" - }, - "include": [ - "test" - ] -} diff --git a/apps/banglexercise/ChangeLog b/apps/banglexercise/ChangeLog index 5f1d3bd7d..6cf589541 100644 --- a/apps/banglexercise/ChangeLog +++ b/apps/banglexercise/ChangeLog @@ -2,3 +2,4 @@ 0.02: Add sit ups Add more feedback to the user about the exercises Clean up code +0.03: Add software back button on main menu diff --git a/apps/banglexercise/app.js b/apps/banglexercise/app.js index bc6e35f07..9659ee81f 100644 --- a/apps/banglexercise/app.js +++ b/apps/banglexercise/app.js @@ -71,7 +71,8 @@ function showMainMenu() { let menu; menu = { "": { - title: "BanglExercise" + title: "BanglExercise", + back: load } }; @@ -381,4 +382,5 @@ Bangle.on('HRM', function(hrm) { }); g.clear(1); +Bangle.loadWidgets(); showMainMenu(); diff --git a/apps/banglexercise/metadata.json b/apps/banglexercise/metadata.json index 9bb93f112..f4ce1894b 100644 --- a/apps/banglexercise/metadata.json +++ b/apps/banglexercise/metadata.json @@ -1,7 +1,7 @@ { "id": "banglexercise", "name": "BanglExercise", "shortName":"BanglExercise", - "version":"0.02", + "version":"0.03", "description": "Can automatically track exercises while wearing the Bangle.js watch.", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], diff --git a/apps/barclock/ChangeLog b/apps/barclock/ChangeLog index 316660fc6..88f4eaf00 100644 --- a/apps/barclock/ChangeLog +++ b/apps/barclock/ChangeLog @@ -7,3 +7,10 @@ 0.07: Update to use Bangle.setUI instead of setWatch 0.08: Use theme colors, Layout library 0.09: Fix time/date disappearing after fullscreen notification +0.10: Use ClockFace library +0.11: Use ClockFace.is12Hour +0.12: Add settings to hide date,widgets +0.13: Add font setting +0.14: Use ClockFace_menu.addItems +0.15: Add Power saving option +0.16: Support Fast Loading diff --git a/apps/barclock/README.md b/apps/barclock/README.md index 4b92313c5..28572e37c 100644 --- a/apps/barclock/README.md +++ b/apps/barclock/README.md @@ -4,3 +4,8 @@ A simple digital clock showing seconds as a horizontal bar. | 24hr style | 12hr style | | --- | --- | | ![24-hour bar clock](screenshot.png) | ![12-hour bar clock with meridian](screenshot_pm.png) | + +## Settings +* `Show date`: display date at the bottom of screen +* `Font`: choose between bitmap or vector fonts +* `Power saving`: (Bangle.js 2 only) don't draw the seconds bar while the watch is locked \ No newline at end of file diff --git a/apps/barclock/clock-bar.js b/apps/barclock/clock-bar.js index 5d46a1cb4..f2499189b 100644 --- a/apps/barclock/clock-bar.js +++ b/apps/barclock/clock-bar.js @@ -1,108 +1,128 @@ /* jshint esversion: 6 */ -/** - * A simple digital clock showing seconds as a bar - **/ +{ + /** + * A simple digital clock showing seconds as a bar + **/ // Check settings for what type our clock should be -const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; -let locale = require("locale"); -{ // add some more info to locale - let date = new Date(); - date.setFullYear(1111); - date.setMonth(1, 3); // februari: months are zero-indexed - const localized = locale.date(date, true); - locale.dayFirst = /3.*2/.test(localized); - - locale.hasMeridian = false; - if (typeof locale.meridian==="function") { // function does not exist if languages app is not installed + let locale = require("locale"); + { // add some more info to locale + let date = new Date(); + date.setFullYear(1111); + date.setMonth(1, 3); // februari: months are zero-indexed + const localized = locale.date(date, true); + locale.dayFirst = /3.*2/.test(localized); locale.hasMeridian = (locale.meridian(date)!==""); } -} -Bangle.loadWidgets(); -function renderBar(l) { - if (!this.fraction) { - // zero-size fillRect stills draws one line of pixels, we don't want that - return; - } - const width = this.fraction*l.w; - g.fillRect(l.x, l.y, l.x+width-1, l.y+l.height-1); -} -const Layout = require("Layout"); -const layout = new Layout({ - type: "v", c: [ - { - type: "h", c: [ - {id: "time", label: "88:88", type: "txt", font: "6x8:5", bgCol: g.theme.bg}, // size updated below - {id: "ampm", label: " ", type: "txt", font: "6x8:2", bgCol: g.theme.bg}, - ], - }, - {id: "bar", type: "custom", fraction: 0, fillx: 1, height: 6, col: g.theme.fg2, render: renderBar}, - {height: 40}, - {id: "date", type: "txt", font: "10%", valign: 1}, - ], -}, {lazy: true}); -// adjustments based on screen size and whether we display am/pm -let thickness; // bar thickness, same as time font "pixel block" size -if (is12Hour) { - // Maximum font size = ( - ) / (5chars * 6px) - thickness = Math.floor((g.getWidth()-24)/(5*6)); -} else { - layout.ampm.label = ""; - thickness = Math.floor(g.getWidth()/(5*6)); -} -layout.bar.height = thickness+1; -layout.time.font = "6x8:"+thickness; -layout.update(); + let barW = 0, prevX = 0; + const renderBar = function (l) { + "ram"; + if (l) prevX = 0; // called from Layout: drawing area was cleared + else l = clock.layout.bar; + let x2 = l.x+barW; + if (clock.powerSave && Bangle.isLocked()) x2 = 0; // hide bar + if (x2===prevX) return; // nothing to do + if (x2===0) x2--; // don't leave 1px line + if (x212) { + date12.setHours(hours-12); + } + return locale.time(date12, true); } - const date12 = new Date(date.getTime()); - const hours = date12.getHours(); - if (hours===0) { - date12.setHours(12); - } else if (hours>12) { - date12.setHours(hours-12); - } - return locale.time(date12, true); -} -function ampmText(date) { - return (is12Hour && locale.hasMeridian)? locale.meridian(date) : ""; -} -function dateText(date) { - const dayName = locale.dow(date, true), - month = locale.month(date, true), - day = date.getDate(); - const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}`; - return `${dayName} ${dayMonth}`; -} + const ampmText = date => (clock.is12Hour && locale.hasMeridian) ? locale.meridian(date) : ""; + const dateText = date => { + const dayName = locale.dow(date, true), + month = locale.month(date, true), + day = date.getDate(); + const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}`; + return `${dayName} ${dayMonth}`; + }; -draw = function draw(force) { - if (!Bangle.isLCDOn()) {return;} // no drawing, also no new update scheduled - const date = new Date(); - layout.time.label = timeText(date); - layout.ampm.label = ampmText(date); - layout.date.label = dateText(date); - const SECONDS_PER_MINUTE = 60; - layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE; - if (force) { - Bangle.drawWidgets(); - layout.forgetLazyState(); - } - layout.render(); - // schedule update at start of next second - const millis = date.getMilliseconds(); - setTimeout(draw, 1000-millis); -}; + const ClockFace = require("ClockFace"), + clock = new ClockFace({ + precision: 1, + settingsFile: "barclock.settings.json", + init: function() { + const Layout = require("Layout"); + this.layout = new Layout({ + type: "v", c: [ + { + type: "h", c: [ + {id: "time", label: "88:88", type: "txt", font: "6x8:5", col: g.theme.fg, bgCol: g.theme.bg}, // updated below + {id: "ampm", label: " ", type: "txt", font: "6x8:2", col: g.theme.fg, bgCol: g.theme.bg}, + ], + }, + {id: "bar", type: "custom", fillx: 1, height: 6, col: g.theme.fg2, render: renderBar}, + this.showDate ? {height: 40} : {}, + this.showDate ? {id: "date", type: "txt", font: "10%", valign: 1} : {}, + ], + }, {lazy: true}); + // adjustments based on screen size and whether we display am/pm + let thickness; // bar thickness, same as time font "pixel block" size + if (this.is12Hour && locale.hasMeridian) { + // Maximum font size = ( - ) / (5chars * 6px) + thickness = Math.floor((Bangle.appRect.w-24)/(5*6)); + } else { + this.layout.ampm.label = ""; + thickness = Math.floor(Bangle.appRect.w/(5*6)); + } + let bar = this.layout.bar; + bar.height = thickness+1; + if (this.font===1) { // vector + const B2 = process.env.HWVERSION>1; + if (this.is12Hour && locale.hasMeridian) { + this.layout.time.font = "Vector:"+(B2 ? 50 : 60); + this.layout.ampm.font = "Vector:"+(B2 ? 20 : 40); + } else { + this.layout.time.font = "Vector:"+(B2 ? 60 : 80); + } + } else { + this.layout.time.font = "6x8:"+thickness; + } + this.layout.update(); + bar.y2 = bar.y+bar.height-1; + }, + update: function(date, c) { + "ram"; + if (c.m) this.layout.time.label = timeText(date); + if (c.h) this.layout.ampm.label = ampmText(date); + if (c.d && this.showDate) this.layout.date.label = dateText(date); + if (c.m) this.layout.render(); + if (c.s) { + barW = Math.round(date.getSeconds()/60*this.layout.bar.w); + renderBar(); + } + }, + resume: function() { + prevX = 0; // force redraw of bar + this.layout.forgetLazyState(); + }, + remove: function() { + if (this.onLock) Bangle.removeListener("lock", this.onLock); + }, + }); -// Show launcher when button pressed -Bangle.setUI("clock"); -Bangle.on("lcdPower", function(on) { - if (on) { - draw(true); + // power saving: only update once a minute while locked, hide bar + if (clock.powerSave) { + clock.onLock = lock => { + clock.precision = lock ? 60 : 1; + clock.tick(); + renderBar(); // hide/redraw bar right away + } + Bangle.on("lock", clock.onLock); } -}); -g.reset().clear(); -Bangle.drawWidgets(); -draw(); + + clock.start(); +} \ No newline at end of file diff --git a/apps/barclock/metadata.json b/apps/barclock/metadata.json index 2b7be355f..785c228b0 100644 --- a/apps/barclock/metadata.json +++ b/apps/barclock/metadata.json @@ -1,7 +1,7 @@ { "id": "barclock", "name": "Bar Clock", - "version": "0.09", + "version": "0.16", "description": "A simple digital clock showing seconds as a bar", "icon": "clock-bar.png", "screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}], @@ -12,6 +12,10 @@ "allow_emulator": true, "storage": [ {"name":"barclock.app.js","url":"clock-bar.js"}, + {"name":"barclock.settings.js","url":"settings.js"}, {"name":"barclock.img","url":"clock-bar-icon.js","evaluate":true} + ], + "data": [ + {"name":"barclock.settings.json"} ] } diff --git a/apps/barclock/settings.js b/apps/barclock/settings.js new file mode 100644 index 000000000..7b88b7021 --- /dev/null +++ b/apps/barclock/settings.js @@ -0,0 +1,30 @@ +(function(back) { + let s = require("Storage").readJSON("barclock.settings.json", true) || {}; + + function save(key, value) { + s[key] = value; + require("Storage").writeJSON("barclock.settings.json", s); + } + + const fonts = [/*LANG*/"Bitmap",/*LANG*/"Vector"]; + let menu = { + "": {"title": /*LANG*/"Bar Clock"}, + /*LANG*/"< Back": back, + /*LANG*/"Font": { + value: s.font|0, + min: 0, max: 1, wrap: true, + format: v => fonts[v], + onchange: v => save("font", v), + }, + }; + let items = { + showDate: s.showDate, + loadWidgets: s.loadWidgets, + }; + // Power saving for Bangle.js 1 doesn't make sense (no updates while screen is off anyway) + if (process.env.HWVERSION>1) { + items.powerSave = s.powerSave; + } + require("ClockFace_menu").addItems(menu, save, items); + E.showMenu(menu); +}); diff --git a/apps/barcode/ChangeLog b/apps/barcode/ChangeLog new file mode 100644 index 000000000..7ab5d8587 --- /dev/null +++ b/apps/barcode/ChangeLog @@ -0,0 +1,10 @@ +0.01: Please forgive me +0.02: Now tells time! +0.03: Interaction +0.04: Shows day of week +0.05: Shows day of month +0.06: Updates every 5 minutes when locked, or when unlock occurs. Also shows nr of steps. +0.07: Step count resets at midnight +0.08: Step count stored in memory to survive reloads. Now shows step count daily and since last reboot. +0.09: NOW it really should reset daily (instead of every other day...) +0.10: Tell clock widgets to hide. diff --git a/apps/barcode/README.md b/apps/barcode/README.md new file mode 100644 index 000000000..17b365d45 --- /dev/null +++ b/apps/barcode/README.md @@ -0,0 +1,23 @@ +# Barcode clockwatchface + +A scannable EAN-8 compatible clockwatchface for your Bangle 2 + +The format of the bars are + +`||HHmm||MMwc||` + +* Left section: HHmm + * H: Hours + * m: Minutes +* Right section: MM9c + * M: Day of month + * w: Day of week + * c: Calculated EAN-8 digit checksum + +Apart from that + +* The upper left section displays total number of steps per day +* The upper right section displays total number of steps from last boot ("stepuptime") +* The face updates every 5 minutes or on demant by pressing the hardware button + +This clockwathface is aware of theme choice, so it will adapt to Light/Dark themes. diff --git a/apps/barcode/barcode.app.js b/apps/barcode/barcode.app.js new file mode 100644 index 000000000..0d9df78d5 --- /dev/null +++ b/apps/barcode/barcode.app.js @@ -0,0 +1,428 @@ +/* Sizes */ +let checkBarWidth = 10; +let checkBarHeight = 140; + +let digitBarWidth = 14; +let digitBarHeight = 100; + +let textBarWidth = 56; +let textBarHeight = 20; + +let textWidth = 14; +let textHeight = 20; + +/* Offsets */ +var startOffsetX = 17; +var startOffsetY = 30; + +let startBarOffsetX = startOffsetX; +let startBarOffsetY = startOffsetY; + +let upperTextBarLeftOffsetX = startBarOffsetX + checkBarWidth; +let upperTextBarLeftOffsetY = startOffsetY; + +let midBarOffsetX = upperTextBarLeftOffsetX + textBarWidth; +let midBarOffsetY = startOffsetY; + +let upperTextBarRightOffsetX = midBarOffsetX + checkBarWidth; +let upperTextBarRightOffsetY = startOffsetY; + +let endBarOffsetX = upperTextBarRightOffsetX + textBarWidth; +let endBarOffsetY = startOffsetY; + +let leftBarsStartX = startBarOffsetX + checkBarWidth; +let leftBarsStartY = upperTextBarLeftOffsetY + textBarHeight; + +let rightBarsStartX = midBarOffsetX + checkBarWidth; +let rightBarsStartY = upperTextBarRightOffsetY + textBarHeight; + +/* Utilities */ +let stepCount = require("Storage").readJSON("stepCount",1); +if(stepCount === undefined) stepCount = 0; +let intCaster = num => Number(num); + +var drawTimeout; + +function renderWatch(l) { + g.setFont("4x6",2); + + // work out how to display the current time + + var d = new Date(); + var h = d.getHours(), m = d.getMinutes(); + var time = h + ":" + ("0"+m).substr(-2); + //var month = ("0" + (d.getMonth()+1)).slice(-2); + var dayOfMonth = ('0' + d.getDate()).slice(-2); + var dayOfWeek = d.getDay() || 7; + var concatTime = ("0"+h).substr(-2) + ("0"+m).substr(-2) + dayOfMonth + dayOfWeek; + + const chars = String(concatTime).split("").map((concatTime) => { + return Number(concatTime); + }); + const checkSum = calculateChecksum(chars); + concatTime += checkSum; + + drawCheckBar(startBarOffsetX, startBarOffsetY); + + drawLDigit(chars[0], 0, leftBarsStartY); + drawLDigit(chars[1], 1, leftBarsStartY); + drawLDigit(chars[2], 2, leftBarsStartY); + drawLDigit(chars[3], 3, leftBarsStartY); + + g.drawString(getStepCount(), startOffsetX + checkBarWidth + 3, startOffsetY + 4); + g.drawString(concatTime.substring(0,4), startOffsetX + checkBarWidth + 3, startOffsetY + textBarHeight + digitBarHeight + 6); + + drawCheckBar(midBarOffsetX, midBarOffsetY); + + drawRDigit(chars[4], 0, rightBarsStartY); + drawRDigit(chars[5], 1, rightBarsStartY); + drawRDigit(chars[6], 2, rightBarsStartY); + drawRDigit(checkSum, 3, rightBarsStartY); + + g.drawString(Bangle.getStepCount(), midBarOffsetX + checkBarWidth + 3, startOffsetY + 4); + g.drawString(concatTime.substring(4), midBarOffsetX + checkBarWidth + 3, startOffsetY + textBarHeight + digitBarHeight + 6); + + drawCheckBar(endBarOffsetX, endBarOffsetY); + + // schedule a draw for the next minute + if (drawTimeout) { + clearTimeout(drawTimeout); + } + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + layout.render(layout.watch); + }, (1000 * 60 * 5) - (Date.now() % (1000 * 60 * 5))); +} + +function drawLDigit(digit, index, offsetY) { + switch(digit) { + case 0: + drawLZeroWithOffset(leftBarsStartX+(digitBarWidth*index), offsetY); + break; + case 1: + drawLOneWithOffset(leftBarsStartX+(digitBarWidth*index), offsetY); + break; + case 2: + drawLTwoWithOffset(leftBarsStartX+(digitBarWidth*index), offsetY); + break; + case 3: + drawLThreeWithOffset(leftBarsStartX+(digitBarWidth*index), offsetY); + break; + case 4: + drawLFourWithOffset(leftBarsStartX+(digitBarWidth*index), offsetY); + break; + case 5: + drawLFiveWithOffset(leftBarsStartX+(digitBarWidth*index), offsetY); + break; + case 6: + drawLSixWithOffset(leftBarsStartX+(digitBarWidth*index), offsetY); + break; + case 7: + drawLSevenWithOffset(leftBarsStartX+(digitBarWidth*index), offsetY); + break; + case 8: + drawLEightWithOffset(leftBarsStartX+(digitBarWidth*index), offsetY); + break; + case 9: + drawLNineWithOffset(leftBarsStartX+(digitBarWidth*index), offsetY); + break; + } +} + +function drawRDigit(digit, index, offsetY) { + switch(digit) { + case 0: + drawRZeroWithOffset(rightBarsStartX+(digitBarWidth*index), offsetY); + break; + case 1: + drawROneWithOffset(rightBarsStartX+(digitBarWidth*index), offsetY); + break; + case 2: + drawRTwoWithOffset(rightBarsStartX+(digitBarWidth*index), offsetY); + break; + case 3: + drawRThreeWithOffset(rightBarsStartX+(digitBarWidth*index), offsetY); + break; + case 4: + drawRFourWithOffset(rightBarsStartX+(digitBarWidth*index), offsetY); + break; + case 5: + drawRFiveWithOffset(rightBarsStartX+(digitBarWidth*index), offsetY); + break; + case 6: + drawRSixWithOffset(rightBarsStartX+(digitBarWidth*index), offsetY); + break; + case 7: + drawRSevenWithOffset(rightBarsStartX+(digitBarWidth*index), offsetY); + break; + case 8: + drawREightWithOffset(rightBarsStartX+(digitBarWidth*index), offsetY); + break; + case 9: + drawRNineWithOffset(rightBarsStartX+(digitBarWidth*index), offsetY); + break; + } +} + +/* +LEAN + +01234567890123 + xxxx xx + xx xxxx + xxxxxxxx xx + xx xxxx + xxxx xx + xx xxxxxxxx + xxxxxx xxxx + xxxx xxxxxx + xx xxxx + xxxx xx +*/ +function drawLOneWithOffset(offset, offsetY) { + let barOneX = 4; + let barTwoX = 12; + g.fillRect(barOneX+offset,offsetY+0,barOneX+3+offset,offsetY+digitBarHeight); + g.fillRect(barTwoX+offset,offsetY+0,barTwoX+1+offset,offsetY+digitBarHeight); + //g.drawString("1",offset+3,offsetY+digitHeight+5); +} + +function drawLTwoWithOffset(offset, offsetY) { + let barOneX = 4; + let barTwoX = 10; + g.fillRect(barOneX+offset,offsetY+0,barOneX+1+offset,offsetY+digitBarHeight); + g.fillRect(barTwoX+offset,offsetY+0,barTwoX+3+offset,offsetY+digitBarHeight); + //g.drawString("2",offset+3,offsetY+digitHeight+5); +} + +function drawLThreeWithOffset(offset, offsetY) { + let barOneX = 2; + let barTwoX = 12; + g.fillRect(barOneX+offset,offsetY+0,barOneX+7+offset,offsetY+digitBarHeight); + g.fillRect(barTwoX+offset,offsetY+0,barTwoX+1+offset,offsetY+digitBarHeight); + //g.drawString("3",offset+3,offsetY+digitHeight+5); +} + +function drawLFourWithOffset(offset, offsetY) { + let barOneX = 2; + let barTwoX = 10; + g.fillRect(barOneX+offset,offsetY+0,barOneX+1+offset,offsetY+digitBarHeight); + g.fillRect(barTwoX+offset,offsetY+0,barTwoX+3+offset,offsetY+digitBarHeight); + //g.drawString("4",offset+3,offsetY+digitHeight+5); +} + +function drawLFiveWithOffset(offset, offsetY) { + let barOneX = 2; + let barTwoX = 12; + g.fillRect(barOneX+offset,offsetY+0,barOneX+3+offset,offsetY+digitBarHeight); + g.fillRect(barTwoX+offset,offsetY+0,barTwoX+1+offset,offsetY+digitBarHeight); + //g.drawString("5",offset+3,offsetY+digitHeight+5); +} + +function drawLSixWithOffset(offset, offsetY) { + let barOneX = 2; + let barTwoX = 6; + g.fillRect(barOneX+offset,offsetY+0,barOneX+1+offset,offsetY+digitBarHeight); + g.fillRect(barTwoX+offset,offsetY+0,barTwoX+7+offset,offsetY+digitBarHeight); + //g.drawString("6",offset+3,offsetY+digitHeight+5); +} + +function drawLSevenWithOffset(offset, offsetY) { + let barOneX = 2; + let barTwoX = 10; + g.fillRect(barOneX+offset,offsetY+0,barOneX+5+offset,offsetY+digitBarHeight); + g.fillRect(barTwoX+offset,offsetY+0,barTwoX+3+offset,offsetY+digitBarHeight); + //g.drawString("7",offset+3,offsetY+digitHeight+5); +} + +function drawLEightWithOffset(offset, offsetY) { + let barOneX = 2; + let barTwoX = 8; + g.fillRect(barOneX+offset,offsetY+0,barOneX+3+offset,offsetY+digitBarHeight); + g.fillRect(barTwoX+offset,offsetY+0,barTwoX+5+offset,offsetY+digitBarHeight); + //g.drawString("8",offset+3,offsetY+digitHeight+5); +} + +function drawLNineWithOffset(offset, offsetY) { + let barOneX = 6; + let barTwoX = 10; + g.fillRect(barOneX+offset,offsetY+0,barOneX+1+offset,offsetY+digitBarHeight); + g.fillRect(barTwoX+offset,offsetY+0,barTwoX+3+offset,offsetY+digitBarHeight); + //g.drawString("9",offset+3,offsetY+digitHeight+5); +} + +function drawLZeroWithOffset(offset, offsetY) { + let barOneX = 6; + let barTwoX = 12; + g.fillRect(barOneX+offset,offsetY+0,barOneX+3+offset,offsetY+digitBarHeight); + g.fillRect(barTwoX+offset,offsetY+0,barTwoX+1+offset,offsetY+digitBarHeight); + //g.drawString("0",offset+3,offsetY+digitHeight+5); +} + + + +/* +REAN + +01234567890123 +xxxx xxxx +xxxx xxxx +xx xx +xx xxxxxx +xx xxxxxx +xx xx +xx xx +xx xx +xxxxxx xx +xxxxxx xx + +*/ +function drawROneWithOffset(offset, offsetY) { + let barOneX = 0; + let barTwoX = 8; + g.fillRect(offset+barOneX,offsetY+0,offset+barOneX+3,offsetY+digitBarHeight); + g.fillRect(offset+barTwoX,offsetY+0,offset+barTwoX+3,offsetY+digitBarHeight); + //g.drawString("1",offset+2,offsetY+textHeight+5); +} + +function drawRTwoWithOffset(offset, offsetY) { + let barOneX = 0; + let barTwoX = 6; + g.fillRect(offset+barOneX,offsetY+0,offset+barOneX+3,offsetY+digitBarHeight); + g.fillRect(offset+barTwoX,offsetY+0,offset+barTwoX+3,offsetY+digitBarHeight); + //g.drawString("2",offset+2,offsetY+textHeight+5); +} + +function drawRThreeWithOffset(offset, offsetY) { + let barOneX = 0; + let barTwoX = 10; + g.fillRect(barOneX+offset,offsetY+0,barOneX+1+offset,offsetY+digitBarHeight); + g.fillRect(barTwoX+offset,offsetY+0,barTwoX+1+offset,offsetY+digitBarHeight); + //g.drawString("3",offset+2,offsetY+textHeight+5); +} + +function drawRFourWithOffset(offset, offsetY) { + let barOneX = 0; + let barTwoX = 4; + g.fillRect(barOneX+offset,offsetY+0,barOneX+1+offset,offsetY+digitBarHeight); + g.fillRect(barTwoX+offset,offsetY+0,barTwoX+5+offset,offsetY+digitBarHeight); + //g.drawString("4",offset+2,offsetY+textHeight+5); +} + +function drawRFiveWithOffset(offset, offsetY) { + let barOneX = 0; + let barTwoX = 6; + g.fillRect(barOneX+offset,offsetY+0,barOneX+1+offset,offsetY+digitBarHeight); + g.fillRect(barTwoX+offset,offsetY+0,barTwoX+5+offset,offsetY+digitBarHeight); + //g.drawString("5",offset+2,offsetY+textHeight+5); +} + +function drawRSixWithOffset(offset, offsetY) { + let barOneX = 0; + let barTwoX = 4; + g.fillRect(barOneX+offset,offsetY+0,barOneX+1+offset,offsetY+digitBarHeight); + g.fillRect(barTwoX+offset,offsetY+0,barTwoX+1+offset,offsetY+digitBarHeight); + //g.drawString("6",offset+2,offsetY+textHeight+5); +} + +function drawRSevenWithOffset(offset, offsetY) { + let barOneX = 0; + let barTwoX = 8; + g.fillRect(barOneX+offset,offsetY+0,barOneX+1+offset,offsetY+digitBarHeight); + g.fillRect(barTwoX+offset,offsetY+0,barTwoX+1+offset,offsetY+digitBarHeight); + //g.drawString("7",offset+2,offsetY+textHeight+5); +} + +function drawREightWithOffset(offset, offsetY) { + let barOneX = 0; + let barTwoX = 6; + g.fillRect(offset+barOneX,offsetY+0,offset+barOneX+1,offsetY+digitBarHeight); + g.fillRect(offset+barTwoX,offsetY+0,offset+barTwoX+1,offsetY+digitBarHeight); + //g.drawString("8",offset+2,offsetY+textHeight+5); +} + +function drawRNineWithOffset(offset, offsetY) { + let barOneX = 0; + let barTwoX = 8; + g.fillRect(barOneX+offset,offsetY+0,barOneX+5+offset,offsetY+digitBarHeight); + g.fillRect(barTwoX+offset,offsetY+0,barTwoX+1+offset,offsetY+digitBarHeight); + //g.drawString("9",offset+2,offsetY+textHeight+5); +} + +function drawRZeroWithOffset(offset, offsetY) { + let barOneX = 0; + let barTwoX = 10; + g.fillRect(barOneX+offset,offsetY+0,barOneX+5+offset,offsetY+digitBarHeight); + g.fillRect(barTwoX+offset,offsetY+0,barTwoX+1+offset,offsetY+digitBarHeight); + //g.drawString("0",offset+2,offsetY+textHeight+5); +} + +function drawCheckBar(offsetX, offsetY) { + const barOneX = offsetX+2; + const barOneWidth = 1; + const barTwoX = offsetX+6; + const barTwoWidth = 1; + g.fillRect(barOneX,offsetY,barOneX+barOneWidth,offsetY+checkBarHeight); + g.fillRect(barTwoX,offsetY,barTwoX+barTwoWidth,offsetY+checkBarHeight); +} + +function calculateChecksum(digits) { + let oddSum = digits[6] + digits[4] + digits[2] + digits[0]; + let evenSum = digits[5] + digits[3] + digits[1]; + + let checkSum = (10 - ((3 * oddSum + evenSum) % 10)) % 10; + + return checkSum; +} + +function storeStepCount() { + stepCount = Bangle.getStepCount(); + require("Storage").writeJSON("stepCount",stepCount); +} + +function getStepCount() { + let accumulatedSteps = Bangle.getStepCount(); + if(accumulatedSteps <= stepCount) { + return 0; + } + return accumulatedSteps - stepCount; +} + +function resetAtMidnight() { + let now = new Date(); + let night = new Date( + now.getFullYear(), + now.getMonth(), + now.getDate(), // the next day, ... + 23, 58, 0 // ...at 00:00:00 hours +); + let msToMidnight = night.getTime() - now.getTime(); + + setTimeout(function() { + storeStepCount(); // <-- This is the function being called at midnight. + resetAtMidnight(); // Then, reset again next midnight. + }, msToMidnight); +} + +resetAtMidnight(); + +// The layout, referencing the custom renderer +var Layout = require("Layout"); +var layout = new Layout( { + type:"v", c: [ + {type:"custom", render:renderWatch, id:"watch", bgCol:g.theme.bg, fillx:1, filly:1 } + ] +}); + +// Clear the screen once, at startup +g.clear(); +Bangle.setUI("clock"); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +layout.render(); + +Bangle.on('lock', function(locked) { + if(!locked) { + layout.render(); + } +}); diff --git a/apps/barcode/barcode.clock.png b/apps/barcode/barcode.clock.png new file mode 100644 index 000000000..7d249cdeb Binary files /dev/null and b/apps/barcode/barcode.clock.png differ diff --git a/apps/barcode/barcode.icon.js b/apps/barcode/barcode.icon.js new file mode 100644 index 000000000..969943e0e --- /dev/null +++ b/apps/barcode/barcode.icon.js @@ -0,0 +1 @@ +E.toArrayBuffer(atob("MDAE///+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifnD6khY+bTIiDTIn8gWq8n+if/////+ifrV6mlp+rXYiFXZr8k4q8r+if/////+if7+///u//79ie7/7//u///+if/////+ifnP6t+378r+ienvyf/K/7v+if/////+ifrfx6/I78j+ibe/+e/W75n+if/////+iee/+t/Z77f+iervyv/r/7v+if//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////")) diff --git a/apps/barcode/barcode.icon.png b/apps/barcode/barcode.icon.png new file mode 100644 index 000000000..43fa77a6f Binary files /dev/null and b/apps/barcode/barcode.icon.png differ diff --git a/apps/barcode/metadata.json b/apps/barcode/metadata.json new file mode 100644 index 000000000..3f6bf06e6 --- /dev/null +++ b/apps/barcode/metadata.json @@ -0,0 +1,16 @@ +{ "id": "barcode", + "name": "Barcode clock", + "shortName":"Barcode clock", + "icon": "barcode.icon.png", + "version":"0.10", + "description": "EAN-8 compatible barcode clock.", + "tags": "barcode,ean,ean-8,watchface,clock,clockface", + "type": "clock", + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"barcode.app.js","url":"barcode.app.js"}, + {"name":"barcode.img","url":"barcode.icon.js","evaluate":true} + ], + "readme":"README.md", + "screenshots": [{"url":"barcode.clock.png"}] +} diff --git a/apps/barometer/ChangeLog b/apps/barometer/ChangeLog index de3a5cb96..b429dda17 100644 --- a/apps/barometer/ChangeLog +++ b/apps/barometer/ChangeLog @@ -1,3 +1,4 @@ 0.01: Display pressure as number and hand 0.02: Use theme color 0.03: workaround for some firmwares that return 'undefined' for first call to barometer +0.04: Update every second, go back with short button press diff --git a/apps/barometer/app.js b/apps/barometer/app.js index 77d4c974f..7e793af4f 100644 --- a/apps/barometer/app.js +++ b/apps/barometer/app.js @@ -59,6 +59,7 @@ function drawTicks(){ function drawScaleLabels(){ g.setColor(g.theme.fg); g.setFont("Vector",12); + g.setFontAlign(-1,-1); let label = MIN; for (let i=0;i <= NUMBER_OF_LABELS; i++){ @@ -103,22 +104,29 @@ function drawIcons() { } g.setBgColor(g.theme.bg); -g.clear(); - -drawTicks(); -drawScaleLabels(); -drawIcons(); try { function baroHandler(data) { - if (data===undefined) // workaround for https://github.com/espruino/BangleApps/issues/1429 - setTimeout(() => Bangle.getPressure().then(baroHandler), 500); - else + g.clear(); + + drawTicks(); + drawScaleLabels(); + drawIcons(); + if (data!==undefined) { drawHand(Math.round(data.pressure)); + } } Bangle.getPressure().then(baroHandler); + setInterval(() => Bangle.getPressure().then(baroHandler), 1000); } catch(e) { - print(e.message); - print("barometer not supporter, show a demo value"); + if (e !== undefined) { + print(e.message); + } + print("barometer not supported, show a demo value"); drawHand(MIN); } + +Bangle.setUI({ + mode : "custom", + back : function() {load();} +}); diff --git a/apps/barometer/metadata.json b/apps/barometer/metadata.json index a385f2be2..767fa630b 100644 --- a/apps/barometer/metadata.json +++ b/apps/barometer/metadata.json @@ -1,7 +1,7 @@ { "id": "barometer", "name": "Barometer", "shortName":"Barometer", - "version":"0.03", + "version":"0.04", "description": "A simple barometer that displays the current air pressure", "icon": "barometer.png", "tags": "tool,outdoors", diff --git a/apps/barwatch/ChangeLog b/apps/barwatch/ChangeLog new file mode 100644 index 000000000..7f837e50e --- /dev/null +++ b/apps/barwatch/ChangeLog @@ -0,0 +1 @@ +0.01: First version diff --git a/apps/barwatch/README.md b/apps/barwatch/README.md new file mode 100644 index 000000000..c37caa6e4 --- /dev/null +++ b/apps/barwatch/README.md @@ -0,0 +1,5 @@ +# BarWatch - an experimental watch + +For too long the watches have shown the time with digits or hands. No more! +With this stylish watch the time is represented by bars. Up to 24 as the day goes by. +Practical? Not really, but a different look! \ No newline at end of file diff --git a/apps/barwatch/app-icon.js b/apps/barwatch/app-icon.js new file mode 100644 index 000000000..82416ee28 --- /dev/null +++ b/apps/barwatch/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("l0uwkE/4A/AH4A/AB0gicQmUB+EPgEigExh8gj8A+ECAgMQn4WCgcACyotWC34W/C34W/CycACw0wgYWFBYIWCAAc/+YGHCAgNFACkxl8hGYwAMLYUvCykQC34WycoIW/C34W0gAWTmUjkUzkbmSAFY=")) \ No newline at end of file diff --git a/apps/barwatch/app.js b/apps/barwatch/app.js new file mode 100644 index 000000000..e0ed15ce6 --- /dev/null +++ b/apps/barwatch/app.js @@ -0,0 +1,76 @@ +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + + +function draw() { + g.reset(); + + if(g.theme.dark){ + g.setColor(1,1,1); + }else{ + g.setColor(0,0,0); + } + + // work out how to display the current time + var d = new Date(); + var h = d.getHours(), m = d.getMinutes(); + + // hour bars + var bx_offset = 10, by_offset = 35; + var b_width = 8, b_height = 60; + var b_space = 5; + + for(var i=0; i 11){ + by_offset = 105; + } + var iter = i % 12; + //console.log(iter); + g.fillRect(bx_offset+(b_width*(iter+1))+(b_space*iter), + by_offset, + bx_offset+(b_width*iter)+(b_space*iter), + by_offset+b_height); + } + + // minute bar + if(h > 11){ + by_offset = 105; + } + var m_bar = h % 12; + if(m != 0){ + g.fillRect(bx_offset+(b_width*(m_bar+1))+(b_space*m_bar), + by_offset+b_height-m, + bx_offset+(b_width*m_bar)+(b_space*m_bar), + by_offset+b_height); + } + + // queue draw in one minute + queueDraw(); +} + +// Clear the screen once, at startup +g.clear(); +// draw immediately at first +draw(); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +Bangle.setUI("clock"); +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/barwatch/app.png b/apps/barwatch/app.png new file mode 100644 index 000000000..134de9424 Binary files /dev/null and b/apps/barwatch/app.png differ diff --git a/apps/barwatch/metadata.json b/apps/barwatch/metadata.json new file mode 100644 index 000000000..adcd44107 --- /dev/null +++ b/apps/barwatch/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "barwatch", + "name": "BarWatch", + "shortName":"BarWatch", + "version":"0.01", + "description": "A watch that displays the time using bars. One bar for each hour.", + "readme": "README.md", + "icon": "screenshot.png", + "tags": "clock", + "type": "clock", + "allow_emulator":true, + "screenshots" : [ { "url": "screenshot.png" } ], + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"barwatch.app.js","url":"app.js"}, + {"name":"barwatch.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/barwatch/screenshot.png b/apps/barwatch/screenshot.png new file mode 100644 index 000000000..305138252 Binary files /dev/null and b/apps/barwatch/screenshot.png differ diff --git a/apps/batclock/ChangeLog b/apps/batclock/ChangeLog index e6e21b146..2a2d91b74 100644 --- a/apps/batclock/ChangeLog +++ b/apps/batclock/ChangeLog @@ -1,2 +1,3 @@ 0.01: App Created! 0.02: Update to use Bangle.setUI instead of setWatch +0.03: Tell clock widgets to hide. diff --git a/apps/batclock/bat-clock.app.js b/apps/batclock/bat-clock.app.js index 31b8f5b9b..dc649160f 100644 --- a/apps/batclock/bat-clock.app.js +++ b/apps/batclock/bat-clock.app.js @@ -249,6 +249,9 @@ g.clear(); g.setColor(0, 0.5, 0).drawImage(bg_crack); g.setColor(1, 1, 1).drawImage(batman); +// Show launcher when button pressed +Bangle.setUI("clock"); + Bangle.loadWidgets(); Bangle.drawWidgets(); @@ -256,5 +259,3 @@ Bangle.drawWidgets(); timeInterval = setInterval(showTime, 1000); showTime(); -// Show launcher when button pressed -Bangle.setUI("clock"); diff --git a/apps/batclock/metadata.json b/apps/batclock/metadata.json index 8aa115780..e6520cb90 100644 --- a/apps/batclock/metadata.json +++ b/apps/batclock/metadata.json @@ -2,7 +2,7 @@ "id": "batclock", "name": "Bat Clock", "shortName": "Bat Clock", - "version": "0.02", + "version": "0.03", "description": "Morphing Clock, with an awesome \"The Dark Knight\" themed logo.", "icon": "bat-clock.png", "screenshots": [{"url":"screenshot.png"}], diff --git a/apps/bclock/ChangeLog b/apps/bclock/ChangeLog index 5b2cf598c..79c198431 100644 --- a/apps/bclock/ChangeLog +++ b/apps/bclock/ChangeLog @@ -1,2 +1,3 @@ 0.02: Modified for use with new bootloader and firmware 0.03: Update to use Bangle.setUI instead of setWatch +0.04: Tell clock widgets to hide. diff --git a/apps/bclock/clock-binary.js b/apps/bclock/clock-binary.js index fdf945ee6..c08a7abe6 100644 --- a/apps/bclock/clock-binary.js +++ b/apps/bclock/clock-binary.js @@ -100,10 +100,12 @@ Bangle.on('lcdPower', on => { if (on) drawClock(); }); +// Show launcher when button pressed +Bangle.setUI("clock"); + g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); setInterval(() => { drawClock(); }, 1000); drawClock(); -// Show launcher when button pressed -Bangle.setUI("clock"); + diff --git a/apps/bclock/metadata.json b/apps/bclock/metadata.json index 94219a30b..c6a24d89f 100644 --- a/apps/bclock/metadata.json +++ b/apps/bclock/metadata.json @@ -1,7 +1,7 @@ { "id": "bclock", "name": "Binary Clock", - "version": "0.03", + "version": "0.04", "description": "A simple binary clock watch face", "icon": "clock-binary.png", "type": "clock", diff --git a/apps/bee/ChangeLog b/apps/bee/ChangeLog new file mode 100644 index 000000000..cfb44c8e9 --- /dev/null +++ b/apps/bee/ChangeLog @@ -0,0 +1,3 @@ +0.01: New app! +0.02: Fix bug with regenerating index, fix bug in word lookups +0.03: Improve word search performance diff --git a/apps/bee/README.md b/apps/bee/README.md new file mode 100644 index 000000000..3d0a4c14a --- /dev/null +++ b/apps/bee/README.md @@ -0,0 +1,33 @@ + +# Spelling bee game + +Word finding game inspired by the NYT spelling bee. Find as many words with 4 or more letters (must include the +letter at the center of the 'hive') as you can. + + +## Usage + + - tap on letters to type out word + - swipe left to delete last letter + - swipe right to enter; the word will turn blue while it is being checked against the internal dictionary; once + checked, it will turn red if the word is invalid, does not contain the central letter or has been guessed before or + will turn green if it is a valid word; in the latter case, points will be awarded + - swipe down to shuffle the 6 outer letters + - swipe up to view a list of already guessed words; tap on any of them to return to the regular game. + + +## Scoring + +The number of correctly guessed words is displayed on the bottom left, the score on the bottom right. A single point +is awarded for a 4 letter word, or the number of letters if longer. A pangram is a word that contains all 7 letters at +least once and yields an additional 7 points. Each game contains at least one pangram. + + +## Technical remarks +The game uses an internal dictionary consisting of a newline separated list of English words ('bee.words', using the '2of12inf' word list). +The dictionary is fairly large (~700kB of flash space) and thus requires appropriate space on the watch and will make installing the app somewhat +slow. Because of its size it cannot be compressed (heatshrink needs to hold the compressed/uncompressed data in memory). +This file can be replaced with a custom dictionary, an ASCII file containing a newline-separated (single "\n", not DOS-style "\r\n") alphabetically +sorted (sorting is important for the word lookup algorithm) list of words. + +![Screenshot](./bee_screenshot.png) diff --git a/apps/bee/app-icon.js b/apps/bee/app-icon.js new file mode 100644 index 000000000..f3bf5dbb2 --- /dev/null +++ b/apps/bee/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AE2JAAIKHnc7DyNPp4vRGAwuBGB4sBAAQvSGIovPFqYvHGAYvDGBYsGGhwvGGIQvEGBQnDMYhkNGBAvOvQABqyRTF5GJr4wLFwQACX6IwLsowJLYMrldVGAQvTsoADGBITD0YvDldPF6n+F4gyGGAdP5nMF4KKBGDJZDGI7EBcoOiGAK7DGAQvYRogxEr1Pp9VMAiSBBILBWeJIxCromBMAQwDAAZfTGBQyCxOCGAIvBGIV/F7AwMAAOIp95GAYACFqoyQMAIwGF7QADEQd5FgIADqvGF8DnEAAIvFGIWjF8CFE0QwHAAQudAAK0EGBQuecw3GqpemYIxiCGIa8cF4wwHdTwvJp9/F82jGA9VMQovf5jkHGIwvg4wvIAAgvg5miF9wwNF8QABF9QwF0YuoF4oxCqoulGBAAB42i0QvjGBPMF0gwIFswwHF1IA/AH4A/AH4AL")) diff --git a/apps/bee/app.png b/apps/bee/app.png new file mode 100644 index 000000000..ed16c44b1 Binary files /dev/null and b/apps/bee/app.png differ diff --git a/apps/bee/bee.app.js b/apps/bee/bee.app.js new file mode 100644 index 000000000..878e9763c --- /dev/null +++ b/apps/bee/bee.app.js @@ -0,0 +1,181 @@ + +const S = require("Storage"); +const words = S.read("bee.words"); +var letters = []; + +var centers = []; + +var word = ''; + +var foundWords = []; +var score = 0; + +var intervalID = -1; + +function biSearch(w, ws, start, end, count) { + "compile" + if (start>end-w.legnth || count--<=0) return ws.substr(start, end-start).indexOf("\n"+w+"\n"); + var mid = (end+start)>>1; + if (ws[mid-1]==="\n") --mid; + else while (midws[mid+i+1]) return biSearch(w, ws, mid+1, end, count); +} + +function isPangram(w) { + var ltrs = ''; + for (var i=0; i=0) return false; // already found + if (biSearch(w, words, 0, words.length, 20)>-1) { + foundWords.push(w); + foundWords.sort(); + if (w.length==4) score++; + else score += w.length; + if (isPangram(w)) score += 7; + return true; + } + return false; +} + +function getHexPoly(cx, cy, r, a) { + var p = []; + for (var i=0; i<6; ++i) p.push(cx+r*Math.sin((i+a)/3*Math.PI), cy+r*Math.cos((i+a)/3*Math.PI)); + return p; +} + +function drawHive() { + w = g.getWidth(); + h = g.getHeight(); + const R = w/3.3; + centers = getHexPoly(w/2, h/2+10, R, 0); + centers.push(w/2, h/2+10); + g.clear(); + g.setFont("Vector", w/7).setFontAlign(0, 0, 0); + g.setColor(g.theme.fg); + for (var i=0; i<6; ++i) { + g.drawPoly(getHexPoly(centers[2*i], centers[2*i+1], 0.9*R/Math.sqrt(3), 0.5), {closed:true}); + g.drawString(String.fromCharCode(65+letters[i+1]), centers[2*i]+2, centers[2*i+1]+2); + } + g.setColor(1, 1, 0).fillPoly(getHexPoly(w/2, h/2+10, 0.9*R/Math.sqrt(3), 0.5)); + g.setColor(0).drawString(String.fromCharCode(65+letters[0]), w/2+2, h/2+10+2); +} + +function shuffleLetters(qAll) { + for (var i=letters.length-1; i > 0; i--) { + var j = (1-qAll) + Math.floor(Math.random()*(i+qAll)); + var temp = letters[i]; + letters[i] = letters[j]; + letters[j] = temp; + } +} + +function pickLetters() { + var ltrs = ""; + while (ltrs.length!==7) { + ltrs = []; + var i = Math.floor((words.length-10)*Math.random()); + while (words[i]!="\n" && i0) { + word = word.slice(0, -1); + drawWord(g.theme.fg); + } + if (d==1 && word.length>=4) { + drawWord("#00f"); + drawWord((checkWord(word.toLowerCase()) ? "#0f0" : "#f00")); + if (intervalID===-1) intervalID = setInterval(wordFound, 800); + } + if (e===1) { + shuffleLetters(0); + drawHive(); + drawScore(); + drawWord(g.theme.fg); + } + if (e===-1 && foundWords.length>0) showWordList(); +} + +function showWordList() { + Bangle.removeListener("touch", touchHandler); + Bangle.removeListener("swipe", swipeHandler); + E.showScroller({ + h : 20, c : foundWords.length, + draw : (idx, r) => { + g.clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1).setFont("6x8:2"); + g.setColor(isPangram(foundWords[idx])?'#0f0':g.theme.fg).drawString(foundWords[idx].toUpperCase(),r.x+10,r.y+4); + }, + select : (idx) => { + setInterval(()=> { + E.showScroller(); + drawHive(); + drawScore(); + drawWord(g.theme.fg); + Bangle.on("touch", touchHandler); + Bangle.on("swipe", swipeHandler); + clearInterval(); + }, 100); + } + }); +} + +pickLetters(); +drawHive(); +drawScore(); +Bangle.on("touch", touchHandler); +Bangle.on("swipe", swipeHandler); diff --git a/apps/bee/bee_screenshot.png b/apps/bee/bee_screenshot.png new file mode 100644 index 000000000..cd173b997 Binary files /dev/null and b/apps/bee/bee_screenshot.png differ diff --git a/apps/bee/bee_words_2of12 b/apps/bee/bee_words_2of12 new file mode 100644 index 000000000..3bab44251 --- /dev/null +++ b/apps/bee/bee_words_2of12 @@ -0,0 +1,74578 @@ +aardvark +aardvarks +abaci +aback +abacus +abacuses +abaft +abalone +abalones +abandon +abandoned +abandoning +abandonment +abandons +abase +abased +abasement +abases +abash +abashed +abashedly +abashes +abashing +abashment +abasing +abate +abated +abatement +abates +abating +abattoir +abattoirs +abbe +abbes +abbess +abbesses +abbey +abbeys +abbot +abbots +abbreviate +abbreviated +abbreviates +abbreviating +abbreviation +abbreviations +abdicate +abdicated +abdicates +abdicating +abdication +abdications +abdomen +abdomens +abdominal +abdominals +abduct +abducted +abducting +abduction +abductions +abductor +abductors +abducts +abeam +abed +aberrant +aberration +aberrational +aberrations +abet +abets +abetted +abetter +abetters +abetting +abettor +abettors +abeyance +abhor +abhorred +abhorrence +abhorrent +abhorrently +abhorring +abhors +abidance +abide +abided +abides +abiding +abidingly +abilities +ability +abject +abjection +abjectly +abjectness +abjuration +abjurations +abjuratory +abjure +abjured +abjurer +abjurers +abjures +abjuring +ablate +ablated +ablates +ablating +ablation +ablations +ablative +ablatives +ablaze +able +abler +ablest +abloom +ablution +ablutions +ably +abnegate +abnegated +abnegates +abnegating +abnegation +abnormal +abnormalities +abnormality +abnormally +aboard +abode +abodes +abolish +abolished +abolishes +abolishing +abolition +abolitionism +abolitionist +abolitionists +abominable +abominably +abominate +abominated +abominates +abominating +abomination +abominations +aboriginal +aboriginals +aborigine +aborigines +aborning +abort +aborted +aborting +abortion +abortionist +abortionists +abortions +abortive +abortively +aborts +abound +abounded +abounding +abounds +about +above +aboveboard +abracadabra +abrade +abraded +abrades +abrading +abrasion +abrasions +abrasive +abrasively +abrasiveness +abrasives +abreast +abridge +abridged +abridgement +abridgements +abridges +abridging +abridgment +abridgments +abroad +abrogate +abrogated +abrogates +abrogating +abrogation +abrogations +abrogator +abrogators +abrupt +abrupter +abruptest +abruptly +abruptness +abscess +abscessed +abscesses +abscessing +abscissa +abscissae +abscissas +abscission +abscond +absconded +absconder +absconders +absconding +absconds +abseil +abseiled +abseiling +abseils +absence +absences +absent +absented +absentee +absenteeism +absentees +absenting +absently +absentminded +absentmindedly +absentmindedness +absents +absinth +absinthe +absolute +absolutely +absoluteness +absolutes +absolutest +absolution +absolutism +absolutist +absolutists +absolve +absolved +absolves +absolving +absorb +absorbed +absorbency +absorbent +absorbents +absorbing +absorbingly +absorbs +absorption +absorptive +abstain +abstained +abstainer +abstainers +abstaining +abstains +abstemious +abstemiously +abstemiousness +abstention +abstentions +abstinence +abstinent +abstract +abstracted +abstractedly +abstractedness +abstracting +abstraction +abstractions +abstractly +abstractness +abstracts +abstruse +abstrusely +abstruseness +absurd +absurder +absurdest +absurdities +absurdity +absurdly +absurdness +abundance +abundances +abundant +abundantly +abuse +abused +abuser +abusers +abuses +abusing +abusive +abusively +abusiveness +abut +abutment +abutments +abuts +abutted +abutting +abuzz +abysmal +abysmally +abyss +abyssal +abysses +acacia +acacias +academe +academia +academic +academically +academician +academicians +academics +academies +academy +acanthi +acanthus +acanthuses +accede +acceded +accedes +acceding +accelerate +accelerated +accelerates +accelerating +acceleration +accelerator +accelerators +accent +accented +accenting +accents +accentual +accentuate +accentuated +accentuates +accentuating +accentuation +accept +acceptability +acceptable +acceptableness +acceptably +acceptance +acceptances +acceptation +acceptations +accepted +accepting +accepts +access +accessed +accesses +accessibility +accessible +accessibly +accessing +accession +accessions +accessories +accessory +accident +accidental +accidentally +accidentals +accidents +acclaim +acclaimed +acclaiming +acclaims +acclamation +acclimate +acclimated +acclimates +acclimating +acclimation +acclimatization +acclimatize +acclimatized +acclimatizes +acclimatizing +acclivities +acclivity +accolade +accolades +accommodate +accommodated +accommodates +accommodating +accommodatingly +accommodation +accommodations +accompanied +accompanies +accompaniment +accompaniments +accompanist +accompanists +accompany +accompanying +accomplice +accomplices +accomplish +accomplished +accomplishes +accomplishing +accomplishment +accomplishments +accord +accordance +accordant +accorded +according +accordingly +accordion +accordionist +accordionists +accordions +accords +accost +accosted +accosting +accosts +account +accountability +accountable +accountancy +accountant +accountants +accounted +accounting +accounts +accouter +accoutered +accoutering +accouterments +accouters +accoutre +accoutred +accoutrements +accoutres +accoutring +accredit +accreditation +accredited +accrediting +accredits +accretion +accretions +accrual +accruals +accrue +accrued +accrues +accruing +acculturate +acculturated +acculturates +acculturating +acculturation +accumulate +accumulated +accumulates +accumulating +accumulation +accumulations +accumulative +accumulator +accumulators +accuracy +accurate +accurately +accurateness +accursed +accursedness +accurst +accusation +accusations +accusative +accusatives +accusatory +accuse +accused +accuser +accusers +accuses +accusing +accusingly +accustom +accustomed +accustoming +accustoms +aced +acerbate +acerbated +acerbates +acerbating +acerbic +acerbically +acerbity +aces +acetaminophen +acetate +acetates +acetic +acetone +acetonic +acetylene +ache +ached +achene +achenes +aches +achier +achiest +achievable +achieve +achieved +achievement +achievements +achiever +achievers +achieves +achieving +aching +achoo +achoos +achromatic +achy +acid +acidic +acidified +acidifies +acidify +acidifying +acidity +acidly +acidosis +acids +acidulous +acing +acknowledge +acknowledged +acknowledgement +acknowledgements +acknowledges +acknowledging +acknowledgment +acknowledgments +acme +acmes +acne +acolyte +acolytes +aconite +aconites +acorn +acorns +acoustic +acoustical +acoustically +acoustics +acquaint +acquaintance +acquaintances +acquaintanceship +acquainted +acquainting +acquaints +acquiesce +acquiesced +acquiescence +acquiescent +acquiescently +acquiesces +acquiescing +acquirable +acquire +acquired +acquirement +acquires +acquiring +acquisition +acquisitions +acquisitive +acquisitively +acquisitiveness +acquit +acquits +acquittal +acquittals +acquitted +acquitting +acre +acreage +acreages +acres +acrid +acrider +acridest +acridity +acridly +acridness +acrimonious +acrimoniously +acrimoniousness +acrimony +acrobat +acrobatic +acrobatically +acrobatics +acrobats +acronym +acronyms +acrophobia +acropolis +acropolises +across +acrostic +acrostics +acrylic +acrylics +acted +acting +actinium +action +actionable +actions +activate +activated +activates +activating +activation +activator +activators +active +actively +activeness +actives +activism +activist +activists +activities +activity +actor +actors +actress +actresses +acts +actual +actualities +actuality +actualization +actualize +actualized +actualizes +actualizing +actually +actuarial +actuaries +actuary +actuate +actuated +actuates +actuating +actuation +actuator +actuators +acuity +acumen +acupressure +acupuncture +acupuncturist +acupuncturists +acute +acutely +acuteness +acuter +acutes +acutest +acyclovir +adage +adages +adagio +adagios +adamant +adamantly +adapt +adaptability +adaptable +adaptation +adaptations +adapted +adapter +adapters +adapting +adaptive +adaptor +adaptors +adapts +addable +added +addend +addenda +addends +addendum +adder +adders +addible +addict +addicted +addicting +addiction +addictions +addictive +addicts +adding +addition +additional +additionally +additions +additive +additives +addle +addled +addles +addling +address +addressed +addressee +addressees +addresses +addressing +adds +adduce +adduced +adduces +adducing +adenine +adenoid +adenoidal +adenoids +adept +adeptly +adeptness +adepts +adequacy +adequate +adequately +adequateness +adhere +adhered +adherence +adherent +adherents +adheres +adhering +adhesion +adhesive +adhesiveness +adhesives +adieu +adieus +adieux +adios +adipose +adjacency +adjacent +adjacently +adjectival +adjectivally +adjective +adjectives +adjoin +adjoined +adjoining +adjoins +adjourn +adjourned +adjourning +adjournment +adjournments +adjourns +adjudge +adjudged +adjudges +adjudging +adjudicate +adjudicated +adjudicates +adjudicating +adjudication +adjudicative +adjudicator +adjudicators +adjudicatory +adjunct +adjuncts +adjuration +adjurations +adjure +adjured +adjures +adjuring +adjust +adjustable +adjusted +adjuster +adjusters +adjusting +adjustment +adjustments +adjustor +adjustors +adjusts +adjutant +adjutants +adman +admen +administer +administered +administering +administers +administrate +administrated +administrates +administrating +administration +administrations +administrative +administratively +administrator +administrators +admirable +admirably +admiral +admirals +admiralty +admiration +admire +admired +admirer +admirers +admires +admiring +admiringly +admissibility +admissible +admissibly +admission +admissions +admit +admits +admittance +admitted +admittedly +admitting +admix +admixed +admixes +admixing +admixture +admixtures +admonish +admonished +admonishes +admonishing +admonishment +admonishments +admonition +admonitions +admonitory +adobe +adobes +adolescence +adolescences +adolescent +adolescents +adopt +adoptable +adopted +adopter +adopters +adopting +adoption +adoptions +adoptive +adopts +adorable +adorableness +adorably +adoration +adore +adored +adorer +adorers +adores +adoring +adoringly +adorn +adorned +adorning +adornment +adornments +adorns +adrenal +adrenalin +adrenaline +adrenals +adrift +adroit +adroitly +adroitness +adsorb +adsorbed +adsorbent +adsorbents +adsorbing +adsorbs +adsorption +adsorptions +adulate +adulated +adulates +adulating +adulation +adulator +adulators +adulatory +adult +adulterant +adulterants +adulterate +adulterated +adulterates +adulterating +adulteration +adulterer +adulterers +adulteress +adulteresses +adulteries +adulterous +adultery +adulthood +adults +adumbrate +adumbrated +adumbrates +adumbrating +adumbration +advance +advanced +advancement +advancements +advances +advancing +advantage +advantaged +advantageous +advantageously +advantages +advantaging +advent +adventitious +adventitiously +advents +adventure +adventured +adventurer +adventurers +adventures +adventuresome +adventuress +adventuresses +adventuring +adventurous +adventurously +adventurousness +adverb +adverbial +adverbially +adverbials +adverbs +adversarial +adversaries +adversary +adverse +adversely +adverseness +adverser +adversest +adversities +adversity +advert +adverted +adverting +advertise +advertised +advertisement +advertisements +advertiser +advertisers +advertises +advertising +advertize +advertized +advertizement +advertizements +advertizes +advertizing +advertorial +advertorials +adverts +advice +advisability +advisable +advisably +advise +advised +advisedly +advisement +adviser +advisers +advises +advising +advisor +advisories +advisors +advisory +advocacy +advocate +advocated +advocates +advocating +adze +adzes +aegis +aeon +aeons +aerate +aerated +aerates +aerating +aeration +aerator +aerators +aerial +aerialist +aerialists +aerially +aerials +aerie +aeries +aerobatic +aerobatics +aerobic +aerobically +aerobics +aerodrome +aerodromes +aerodynamic +aerodynamically +aerodynamics +aeronautic +aeronautical +aeronautics +aeroplane +aeroplanes +aerosol +aerosols +aerospace +aery +aesthete +aesthetes +aesthetic +aesthetically +aestheticism +aesthetics +afar +affability +affable +affably +affair +affairs +affect +affectation +affectations +affected +affectedly +affecting +affectingly +affection +affectionate +affectionately +affections +affects +afferent +affiance +affianced +affiances +affiancing +affidavit +affidavits +affiliate +affiliated +affiliates +affiliating +affiliation +affiliations +affinities +affinity +affirm +affirmation +affirmations +affirmative +affirmatively +affirmatives +affirmed +affirming +affirms +affix +affixed +affixes +affixing +afflatus +afflict +afflicted +afflicting +affliction +afflictions +afflicts +affluence +affluent +affluently +afford +affordable +afforded +affording +affords +afforest +afforestation +afforested +afforesting +afforests +affray +affrays +affront +affronted +affronting +affronts +afghan +afghans +aficionado +aficionados +afield +afire +aflame +afloat +aflutter +afoot +aforementioned +aforesaid +aforethought +afoul +afraid +afresh +after +afterbirth +afterbirths +afterburner +afterburners +aftercare +aftereffect +aftereffects +afterglow +afterglows +afterimage +afterimages +afterlife +afterlives +aftermarket +aftermarkets +aftermath +aftermaths +afternoon +afternoons +aftershave +aftershaves +aftershock +aftershocks +aftertaste +aftertastes +afterthought +afterthoughts +afterward +afterwards +afterword +afterwords +again +against +agape +agar +agate +agates +agave +aged +ageing +ageism +ageist +ageists +ageless +agelessly +agelessness +agencies +agency +agenda +agendas +agent +agents +ageratum +ageratums +ages +agglomerate +agglomerated +agglomerates +agglomerating +agglomeration +agglomerations +agglutinate +agglutinated +agglutinates +agglutinating +agglutination +agglutinations +aggrandize +aggrandized +aggrandizement +aggrandizes +aggrandizing +aggravate +aggravated +aggravates +aggravating +aggravatingly +aggravation +aggravations +aggregate +aggregated +aggregates +aggregating +aggregation +aggregations +aggression +aggressive +aggressively +aggressiveness +aggressor +aggressors +aggrieve +aggrieved +aggrieves +aggrieving +aghast +agile +agilely +agility +aging +agitate +agitated +agitates +agitating +agitation +agitations +agitator +agitators +agitprop +agleam +aglitter +aglow +agnostic +agnosticism +agnostics +agog +agonies +agonize +agonized +agonizes +agonizing +agonizingly +agony +agoraphobia +agoraphobic +agoraphobics +agrarian +agrarianism +agrarians +agree +agreeable +agreeableness +agreeably +agreed +agreeing +agreement +agreements +agrees +agribusiness +agribusinesses +agricultural +agriculturalist +agriculturalists +agriculturally +agriculture +agriculturist +agriculturists +agronomic +agronomics +agronomist +agronomists +agronomy +aground +ague +ahead +ahem +ahoy +aide +aided +aides +aiding +aids +aigrette +aigrettes +ailed +aileron +ailerons +ailing +ailment +ailments +ails +aimed +aiming +aimless +aimlessly +aimlessness +aims +airbag +airbags +airbase +airbases +airborne +airbrush +airbrushed +airbrushes +airbrushing +airbus +airbuses +airbusses +aircraft +airdrop +airdropped +airdropping +airdrops +aired +airfare +airfares +airfield +airfields +airflow +airfoil +airfoils +airfreight +airhead +airheads +airier +airiest +airily +airiness +airing +airings +airless +airlessness +airlift +airlifted +airlifting +airlifts +airline +airliner +airliners +airlines +airlock +airlocks +airmail +airmailed +airmailing +airmails +airman +airmen +airplane +airplanes +airplay +airport +airports +airs +airship +airships +airsick +airsickness +airspace +airstrike +airstrikes +airstrip +airstrips +airtight +airtime +airwaves +airway +airways +airworthier +airworthiest +airworthiness +airworthy +airy +aisle +aisles +aitch +aitches +ajar +akimbo +akin +alabaster +alack +alacrity +alarm +alarmed +alarming +alarmingly +alarmist +alarmists +alarms +alas +albacore +albacores +albatross +albatrosses +albeit +albinism +albino +albinos +albs +album +albumen +albumin +albuminous +albums +alchemist +alchemists +alchemy +alcohol +alcoholic +alcoholically +alcoholics +alcoholism +alcohols +alcove +alcoves +alder +alderman +aldermen +alders +alderwoman +alderwomen +aleatory +alehouse +alehouses +alembic +alembics +alert +alerted +alerting +alertly +alertness +alerts +ales +alewife +alewives +alfalfa +alfresco +alga +algae +algal +algebra +algebraic +algebraically +algorithm +algorithmic +algorithms +alias +aliased +aliases +aliasing +alibi +alibied +alibiing +alibis +alien +alienable +alienate +alienated +alienates +alienating +alienation +alienist +alienists +aliens +alight +alighted +alighting +alights +align +aligned +aligner +aligners +aligning +alignment +alignments +aligns +alike +aliment +alimentary +alimented +alimenting +aliments +alimony +aline +alined +alinement +alinements +alines +alining +alit +alive +aliveness +aliyah +aliyahs +alkali +alkalies +alkaline +alkalinity +alkalis +alkalize +alkalized +alkalizes +alkalizing +alkaloid +alkaloids +alkyd +alkyds +allay +allayed +allaying +allays +allegation +allegations +allege +alleged +allegedly +alleges +allegiance +allegiances +alleging +allegoric +allegorical +allegorically +allegories +allegorist +allegorists +allegory +allegretto +allegrettos +allegro +allegros +allele +alleles +alleluia +alleluias +allergen +allergenic +allergens +allergic +allergically +allergies +allergist +allergists +allergy +alleviate +alleviated +alleviates +alleviating +alleviation +alley +alleys +alleyway +alleyways +alliance +alliances +allied +allies +alligator +alligators +alliterate +alliterated +alliterates +alliterating +alliteration +alliterations +alliterative +alliteratively +allocate +allocated +allocates +allocating +allocation +allocations +allot +allotment +allotments +allots +allotted +allotting +allover +allow +allowable +allowably +allowance +allowances +allowed +allowing +allows +alloy +alloyed +alloying +alloys +allspice +allude +alluded +alludes +alluding +allure +allured +allurement +allurements +allures +alluring +alluringly +allusion +allusions +allusive +allusively +allusiveness +alluvia +alluvial +alluvium +alluviums +ally +allying +almanac +almanacs +almighty +almond +almonds +almoner +almoners +almost +alms +almshouse +almshouses +aloe +aloes +aloft +aloha +alohas +alone +along +alongshore +alongside +aloof +aloofly +aloofness +aloud +alpaca +alpacas +alpha +alphabet +alphabetic +alphabetical +alphabetically +alphabetization +alphabetizations +alphabetize +alphabetized +alphabetizer +alphabetizers +alphabetizes +alphabetizing +alphabets +alphanumeric +alphanumerical +alphanumerically +alphas +alpine +alps +already +alright +also +altar +altarpiece +altarpieces +altars +alter +alterable +alteration +alterations +altercation +altercations +altered +altering +alternate +alternated +alternately +alternates +alternating +alternation +alternations +alternative +alternatively +alternatives +alternator +alternators +alters +altho +although +altimeter +altimeters +altitude +altitudes +alto +altogether +altos +altruism +altruist +altruistic +altruistically +altruists +alum +alumina +aluminium +aluminum +alumna +alumnae +alumni +alumnus +alums +always +amalgam +amalgamate +amalgamated +amalgamates +amalgamating +amalgamation +amalgamations +amalgams +amanuenses +amanuensis +amaranth +amaranths +amaretto +amaryllis +amaryllises +amass +amassed +amasses +amassing +amateur +amateurish +amateurishly +amateurishness +amateurism +amateurs +amatory +amaze +amazed +amazement +amazes +amazing +amazingly +amazon +amazonian +amazons +ambassador +ambassadorial +ambassadors +ambassadorship +ambassadorships +ambassadress +ambassadresses +amber +ambergris +ambiance +ambiances +ambidexterity +ambidextrous +ambidextrously +ambience +ambiences +ambient +ambiguities +ambiguity +ambiguous +ambiguously +ambition +ambitions +ambitious +ambitiously +ambitiousness +ambivalence +ambivalent +ambivalently +amble +ambled +ambler +amblers +ambles +ambling +ambrosia +ambrosial +ambulance +ambulances +ambulant +ambulate +ambulated +ambulates +ambulating +ambulation +ambulations +ambulatories +ambulatory +ambuscade +ambuscaded +ambuscades +ambuscading +ambush +ambushed +ambushes +ambushing +ameba +amebae +amebas +amebic +ameboid +ameliorate +ameliorated +ameliorates +ameliorating +amelioration +ameliorative +amen +amenability +amenable +amenably +amend +amendable +amended +amending +amendment +amendments +amends +amenities +amenity +amerce +amerced +amercement +amercements +amerces +amercing +americium +amethyst +amethysts +amiability +amiable +amiably +amicability +amicable +amicably +amid +amide +amides +amidship +amidships +amidst +amigo +amigos +amir +amirs +amiss +amity +ammeter +ammeters +ammo +ammonia +ammunition +amnesia +amnesiac +amnesiacs +amnesic +amnesics +amnestied +amnesties +amnesty +amnestying +amnia +amniocenteses +amniocentesis +amnion +amnions +amniotic +amoeba +amoebae +amoebas +amoebic +amok +among +amongst +amontillado +amontillados +amoral +amorality +amorally +amorous +amorously +amorousness +amorphous +amorphously +amorphousness +amortization +amortizations +amortize +amortized +amortizes +amortizing +amount +amounted +amounting +amounts +amour +amours +amperage +ampere +amperes +ampersand +ampersands +amphetamine +amphetamines +amphibian +amphibians +amphibious +amphibiously +amphitheater +amphitheaters +amphitheatre +amphitheatres +amphora +amphorae +amphoras +ample +ampler +amplest +amplification +amplifications +amplified +amplifier +amplifiers +amplifies +amplify +amplifying +amplitude +amplitudes +amply +ampoule +ampoules +amps +ampul +ampule +ampules +ampuls +amputate +amputated +amputates +amputating +amputation +amputations +amputee +amputees +amuck +amulet +amulets +amuse +amused +amusement +amusements +amuses +amusing +amusingly +amylase +anabolism +anachronism +anachronisms +anachronistic +anachronistically +anaconda +anacondas +anaemia +anaemic +anaerobe +anaerobes +anaerobic +anaerobically +anaesthesia +anaesthetic +anaesthetics +anaesthetist +anaesthetists +anaesthetize +anaesthetized +anaesthetizes +anaesthetizing +anagram +anagrams +anal +analgesia +analgesic +analgesics +anally +analog +analogical +analogically +analogies +analogize +analogized +analogizes +analogizing +analogous +analogously +analogousness +analogs +analogue +analogues +analogy +analysand +analysands +analyse +analysed +analyses +analysing +analysis +analyst +analysts +analytic +analytical +analytically +analyzable +analyze +analyzed +analyzer +analyzers +analyzes +analyzing +anapaest +anapaests +anapest +anapestic +anapestics +anapests +anarchic +anarchically +anarchism +anarchist +anarchistic +anarchists +anarchy +anathema +anathemas +anathematize +anathematized +anathematizes +anathematizing +anatomic +anatomical +anatomically +anatomies +anatomist +anatomists +anatomize +anatomized +anatomizes +anatomizing +anatomy +ancestor +ancestors +ancestral +ancestrally +ancestress +ancestresses +ancestries +ancestry +anchor +anchorage +anchorages +anchored +anchoring +anchorite +anchorites +anchorman +anchormen +anchorperson +anchorpersons +anchors +anchorwoman +anchorwomen +anchovies +anchovy +ancient +ancienter +ancientest +anciently +ancientness +ancients +ancillaries +ancillary +andante +andantes +andiron +andirons +androgen +androgenic +androgynous +androgyny +android +androids +anecdota +anecdotal +anecdote +anecdotes +anemia +anemic +anemically +anemometer +anemometers +anemone +anemones +anent +anesthesia +anesthesiologist +anesthesiologists +anesthesiology +anesthetic +anesthetics +anesthetist +anesthetists +anesthetization +anesthetize +anesthetized +anesthetizes +anesthetizing +aneurism +aneurisms +aneurysm +aneurysms +anew +angel +angelfish +angelfishes +angelic +angelica +angelical +angelically +angels +anger +angered +angering +angers +angina +angioplasties +angioplasty +angiosperm +angiosperms +angle +angled +angler +anglers +angles +angleworm +angleworms +anglicize +anglicized +anglicizes +anglicizing +angling +angora +angoras +angrier +angriest +angrily +angry +angst +angstrom +angstroms +anguish +anguished +anguishes +anguishing +angular +angularities +angularity +anhydrous +aniline +animadversion +animadversions +animadvert +animadverted +animadverting +animadverts +animal +animalcule +animalcules +animals +animate +animated +animatedly +animates +animating +animation +animations +animator +animators +animism +animist +animistic +animists +animosities +animosity +animus +anion +anionic +anions +anise +aniseed +anisette +ankh +ankhs +ankle +anklebone +anklebones +ankles +anklet +anklets +annalist +annalists +annals +anneal +annealed +annealing +anneals +annelid +annelids +annex +annexation +annexations +annexe +annexed +annexes +annexing +annihilate +annihilated +annihilates +annihilating +annihilation +annihilator +annihilators +anniversaries +anniversary +annotate +annotated +annotates +annotating +annotation +annotations +annotative +annotator +annotators +announce +announced +announcement +announcements +announcer +announcers +announces +announcing +annoy +annoyance +annoyances +annoyed +annoying +annoyingly +annoys +annual +annually +annuals +annuitant +annuitants +annuities +annuity +annul +annular +annulled +annulling +annulment +annulments +annuls +annunciation +annunciations +anode +anodes +anodize +anodized +anodizes +anodizing +anodyne +anodynes +anoint +anointed +anointing +anointment +anoints +anomalies +anomalous +anomalously +anomaly +anon +anonymity +anonymous +anonymously +anopheles +anorak +anoraks +anorectic +anorectics +anorexia +anorexic +anorexics +another +answer +answerable +answered +answering +answers +antacid +antacids +antagonism +antagonisms +antagonist +antagonistic +antagonistically +antagonists +antagonize +antagonized +antagonizes +antagonizing +antarctic +ante +anteater +anteaters +antebellum +antecedence +antecedent +antecedents +antechamber +antechambers +anted +antedate +antedated +antedates +antedating +antediluvian +anteed +anteing +antelope +antelopes +antenatal +antenna +antennae +antennas +anterior +anteroom +anterooms +antes +anthem +anthems +anther +anthers +anthill +anthills +anthologies +anthologist +anthologists +anthologize +anthologized +anthologizes +anthologizing +anthology +anthracite +anthrax +anthropocentric +anthropoid +anthropoids +anthropological +anthropologically +anthropologist +anthropologists +anthropology +anthropomorphic +anthropomorphically +anthropomorphism +anthropomorphous +anti +antiabortion +antiabortionist +antiabortionists +antiaircraft +antibacterial +antibacterials +antibiotic +antibiotics +antibodies +antibody +antic +anticancer +antichrist +antichrists +anticipate +anticipated +anticipates +anticipating +anticipation +anticipations +anticipatory +anticlerical +anticlimactic +anticlimactically +anticlimax +anticlimaxes +anticline +anticlines +anticlockwise +anticoagulant +anticoagulants +anticommunism +anticommunist +anticommunists +antics +anticyclone +anticyclones +anticyclonic +antidemocratic +antidepressant +antidepressants +antidote +antidotes +antifascist +antifascists +antifreeze +antigen +antigenic +antigenicity +antigens +antihero +antiheroes +antihistamine +antihistamines +antiknock +antilabor +antilogarithm +antilogarithms +antimacassar +antimacassars +antimalarial +antimalarials +antimatter +antimicrobial +antimissile +antimony +antinuclear +antioxidant +antioxidants +antiparticle +antiparticles +antipasti +antipasto +antipastos +antipathetic +antipathies +antipathy +antipersonnel +antiperspirant +antiperspirants +antiphon +antiphonal +antiphonally +antiphonals +antiphons +antipodal +antipodean +antipodeans +antipodes +antipollution +antipoverty +antiquarian +antiquarianism +antiquarians +antiquaries +antiquary +antiquate +antiquated +antiquates +antiquating +antique +antiqued +antiques +antiquing +antiquities +antiquity +antis +antisemitic +antisemitism +antisepsis +antiseptic +antiseptically +antiseptics +antisera +antiserum +antiserums +antislavery +antisocial +antisocially +antispasmodic +antispasmodics +antisubmarine +antitank +antitheses +antithesis +antithetic +antithetical +antithetically +antitoxin +antitoxins +antitrust +antivenin +antivenins +antiviral +antivirals +antivivisectionist +antivivisectionists +antiwar +antler +antlered +antlers +antonym +antonymous +antonyms +ants +antsier +antsiest +antsy +anus +anuses +anvil +anvils +anxieties +anxiety +anxious +anxiously +anxiousness +anybodies +anybody +anyhow +anymore +anyone +anyplace +anything +anytime +anyway +anywhere +anywise +aorta +aortae +aortas +aortic +apace +apart +apartheid +apartment +apartments +apathetic +apathetically +apathy +apatite +aped +apelike +aperitif +aperitifs +aperture +apertures +apes +apex +apexes +aphasia +aphasic +aphasics +aphelia +aphelion +aphelions +aphid +aphids +aphorism +aphorisms +aphoristic +aphoristically +aphrodisiac +aphrodisiacs +apiaries +apiarist +apiarists +apiary +apical +apically +apices +apiece +aping +apish +apishly +aplenty +aplomb +apocalypse +apocalypses +apocalyptic +apocrypha +apocryphal +apocryphally +apogee +apogees +apolitical +apolitically +apologetic +apologetically +apologia +apologias +apologies +apologist +apologists +apologize +apologized +apologizes +apologizing +apology +apoplectic +apoplexies +apoplexy +apostasies +apostasy +apostate +apostates +apostatize +apostatized +apostatizes +apostatizing +apostle +apostles +apostleship +apostolic +apostrophe +apostrophes +apothecaries +apothecary +apothegm +apothegms +apotheoses +apotheosis +appal +appall +appalled +appalling +appallingly +appalls +appaloosa +appaloosas +appals +apparatus +apparatuses +apparel +appareled +appareling +apparelled +apparelling +apparels +apparent +apparently +apparition +apparitions +appeal +appealed +appealing +appealingly +appeals +appear +appearance +appearances +appeared +appearing +appears +appease +appeased +appeasement +appeasements +appeaser +appeasers +appeases +appeasing +appellant +appellants +appellate +appellation +appellations +append +appendage +appendages +appendectomies +appendectomy +appended +appendices +appendicitis +appending +appendix +appendixes +appends +appertain +appertained +appertaining +appertains +appetite +appetites +appetizer +appetizers +appetizing +appetizingly +applaud +applauded +applauder +applauders +applauding +applauds +applause +apple +applejack +apples +applesauce +applet +applets +appliance +appliances +applicability +applicable +applicably +applicant +applicants +application +applications +applicator +applicators +applied +applier +appliers +applies +applique +appliqued +appliqueing +appliques +apply +applying +appoint +appointed +appointee +appointees +appointing +appointive +appointment +appointments +appoints +apportion +apportioned +apportioning +apportionment +apportions +appose +apposed +apposes +apposing +apposite +appositely +appositeness +apposition +appositive +appositives +appraisal +appraisals +appraise +appraised +appraiser +appraisers +appraises +appraising +appreciable +appreciably +appreciate +appreciated +appreciates +appreciating +appreciation +appreciations +appreciative +appreciatively +appreciator +appreciators +appreciatory +apprehend +apprehended +apprehending +apprehends +apprehension +apprehensions +apprehensive +apprehensively +apprehensiveness +apprentice +apprenticed +apprentices +apprenticeship +apprenticeships +apprenticing +apprise +apprised +apprises +apprising +apprize +apprized +apprizes +apprizing +approach +approachable +approached +approaches +approaching +approbation +approbations +appropriate +appropriated +appropriately +appropriateness +appropriates +appropriating +appropriation +appropriations +appropriator +appropriators +approval +approvals +approve +approved +approves +approving +approvingly +approximate +approximated +approximately +approximates +approximating +approximation +approximations +appurtenance +appurtenances +appurtenant +apricot +apricots +apron +aprons +apropos +apse +apses +apter +aptest +aptitude +aptitudes +aptly +aptness +aqua +aquaculture +aquae +aqualung +aqualungs +aquamarine +aquamarines +aquanaut +aquanauts +aquaplane +aquaplaned +aquaplanes +aquaplaning +aquaria +aquarium +aquariums +aquas +aquatic +aquatically +aquatics +aquavit +aqueduct +aqueducts +aqueous +aquiculture +aquifer +aquifers +aquiline +arabesque +arabesques +arability +arable +arachnid +arachnids +arbiter +arbiters +arbitrage +arbitraged +arbitrager +arbitragers +arbitrages +arbitrageur +arbitrageurs +arbitraging +arbitrament +arbitraments +arbitrarily +arbitrariness +arbitrary +arbitrate +arbitrated +arbitrates +arbitrating +arbitration +arbitrator +arbitrators +arbor +arboreal +arboreta +arboretum +arboretums +arbors +arborvitae +arborvitaes +arbour +arbours +arbutus +arbutuses +arcade +arcades +arcane +arced +arch +archaeological +archaeologically +archaeologist +archaeologists +archaeology +archaic +archaically +archaism +archaisms +archaist +archaists +archangel +archangels +archbishop +archbishopric +archbishoprics +archbishops +archdeacon +archdeacons +archdiocesan +archdiocese +archdioceses +archduchess +archduchesses +archduke +archdukes +arched +archenemies +archenemy +archeological +archeologist +archeologists +archeology +archer +archers +archery +arches +archest +archetypal +archetype +archetypes +archetypical +archfiend +archfiends +archiepiscopal +arching +archipelago +archipelagoes +archipelagos +architect +architectonic +architectonics +architects +architectural +architecturally +architecture +architectures +architrave +architraves +archival +archive +archived +archives +archiving +archivist +archivists +archly +archness +archway +archways +arcing +arcked +arcking +arcs +arctic +arctics +ardent +ardently +ardor +ardors +ardour +ardours +arduous +arduously +arduousness +area +areal +areas +arena +arenas +ares +argent +argon +argosies +argosy +argot +argots +arguable +arguably +argue +argued +arguer +arguers +argues +arguing +argument +argumentation +argumentative +argumentatively +argumentativeness +arguments +argyle +argyles +aria +arias +arid +aridity +aridly +aright +arise +arisen +arises +arising +aristocracies +aristocracy +aristocrat +aristocratic +aristocratically +aristocrats +arithmetic +arithmetical +arithmetically +arithmetician +arithmeticians +arks +armada +armadas +armadillo +armadillos +armament +armaments +armature +armatures +armband +armbands +armchair +armchairs +armed +armful +armfuls +armhole +armholes +armies +arming +armistice +armistices +armlet +armlets +armor +armored +armorer +armorers +armorial +armories +armoring +armors +armory +armour +armoured +armouries +armouring +armours +armoury +armpit +armpits +armrest +armrests +arms +armsful +army +aroma +aromas +aromatherapist +aromatherapists +aromatherapy +aromatic +aromatically +aromatics +arose +around +arousal +arouse +aroused +arouses +arousing +arpeggio +arpeggios +arraign +arraigned +arraigning +arraignment +arraignments +arraigns +arrange +arranged +arrangement +arrangements +arranger +arrangers +arranges +arranging +arrant +arras +arrases +array +arrayed +arraying +arrays +arrears +arrest +arrested +arresting +arrests +arrhythmia +arrhythmic +arrhythmical +arrival +arrivals +arrive +arrived +arrives +arriving +arrogance +arrogant +arrogantly +arrogate +arrogated +arrogates +arrogating +arrogation +arrow +arrowhead +arrowheads +arrowroot +arrows +arroyo +arroyos +arsenal +arsenals +arsenic +arson +arsonist +arsonists +artefact +artefacts +arterial +arteries +arteriole +arterioles +arteriosclerosis +artery +artful +artfully +artfulness +arthritic +arthritics +arthritis +arthropod +arthropods +arthroscope +arthroscopes +arthroscopic +artichoke +artichokes +article +articles +articular +articulate +articulated +articulately +articulateness +articulates +articulating +articulation +articulations +artier +artiest +artifact +artifacts +artifice +artificer +artificers +artifices +artificial +artificiality +artificially +artillery +artilleryman +artillerymen +artiness +artisan +artisans +artist +artiste +artistes +artistic +artistically +artistry +artists +artless +artlessly +artlessness +arts +artsier +artsiest +artsy +artwork +artworks +arty +arum +arums +asbestos +ascend +ascendance +ascendancy +ascendant +ascendants +ascended +ascendency +ascendent +ascendents +ascending +ascends +ascension +ascensions +ascent +ascents +ascertain +ascertainable +ascertained +ascertaining +ascertainment +ascertains +ascetic +ascetically +asceticism +ascetics +ascot +ascots +ascribable +ascribe +ascribed +ascribes +ascribing +ascription +aseptic +aseptically +asexual +asexuality +asexually +ashamed +ashamedly +ashcan +ashcans +ashen +ashes +ashier +ashiest +ashlar +ashlars +ashore +ashram +ashrams +ashtray +ashtrays +ashy +aside +asides +asinine +asininely +asininities +asininity +askance +asked +askew +asking +asks +aslant +asleep +asocial +asparagus +aspartame +aspect +aspects +aspen +aspens +asperities +asperity +aspersion +aspersions +asphalt +asphalted +asphalting +asphalts +asphodel +asphodels +asphyxia +asphyxiate +asphyxiated +asphyxiates +asphyxiating +asphyxiation +asphyxiations +aspic +aspics +aspidistra +aspidistras +aspirant +aspirants +aspirate +aspirated +aspirates +aspirating +aspiration +aspirations +aspirator +aspirators +aspire +aspired +aspires +aspirin +aspiring +aspirins +asps +assail +assailable +assailant +assailants +assailed +assailing +assails +assassin +assassinate +assassinated +assassinates +assassinating +assassination +assassinations +assassins +assault +assaulted +assaulting +assaults +assay +assayed +assayer +assayers +assaying +assays +assemblage +assemblages +assemble +assembled +assembler +assemblers +assembles +assemblies +assembling +assembly +assemblyman +assemblymen +assemblywoman +assemblywomen +assent +assented +assenting +assents +assert +asserted +asserting +assertion +assertions +assertive +assertively +assertiveness +asserts +asses +assess +assessed +assesses +assessing +assessment +assessments +assessor +assessors +asset +assets +asseverate +asseverated +asseverates +asseverating +asseveration +asshole +assholes +assiduity +assiduous +assiduously +assiduousness +assign +assignable +assignation +assignations +assigned +assigner +assigners +assigning +assignment +assignments +assignor +assignors +assigns +assimilate +assimilated +assimilates +assimilating +assimilation +assist +assistance +assistant +assistants +assisted +assisting +assists +assize +assizes +associate +associated +associates +associating +association +associations +associative +assonance +assonant +assonants +assort +assorted +assorting +assortment +assortments +assorts +assuage +assuaged +assuages +assuaging +assumable +assume +assumed +assumes +assuming +assumption +assumptions +assumptive +assurance +assurances +assure +assured +assuredly +assureds +assures +assuring +astatine +aster +asterisk +asterisked +asterisking +asterisks +astern +asteroid +asteroids +asters +asthma +asthmatic +asthmatics +astigmatic +astigmatism +astigmatisms +astir +astonish +astonished +astonishes +astonishing +astonishingly +astonishment +astound +astounded +astounding +astoundingly +astounds +astraddle +astrakhan +astral +astray +astride +astringency +astringent +astringently +astringents +astrolabe +astrolabes +astrologer +astrologers +astrological +astrologically +astrologist +astrologists +astrology +astronaut +astronautic +astronautical +astronautics +astronauts +astronomer +astronomers +astronomic +astronomical +astronomically +astronomy +astrophysical +astrophysicist +astrophysicists +astrophysics +astute +astutely +astuteness +astuter +astutest +asunder +asylum +asylums +asymmetric +asymmetrical +asymmetrically +asymmetry +asymptomatic +atavism +atavist +atavistic +atavists +ataxia +ataxic +ataxics +atelier +ateliers +atheism +atheist +atheistic +atheists +atherosclerosis +athirst +athlete +athletes +athletic +athletically +athletics +athwart +atilt +atlas +atlases +atmosphere +atmospheres +atmospheric +atmospherically +atmospherics +atoll +atolls +atom +atomic +atomically +atomize +atomized +atomizer +atomizers +atomizes +atomizing +atoms +atonal +atonality +atonally +atone +atoned +atonement +atones +atoning +atop +atria +atrial +atrium +atriums +atrocious +atrociously +atrociousness +atrocities +atrocity +atrophied +atrophies +atrophy +atrophying +atropine +attach +attachable +attache +attached +attaches +attaching +attachment +attachments +attack +attacked +attacker +attackers +attacking +attacks +attain +attainability +attainable +attainder +attained +attaining +attainment +attainments +attains +attar +attempt +attempted +attempting +attempts +attend +attendance +attendances +attendant +attendants +attended +attendee +attendees +attending +attends +attention +attentions +attentive +attentively +attentiveness +attenuate +attenuated +attenuates +attenuating +attenuation +attest +attestation +attestations +attested +attesting +attests +attic +attics +attire +attired +attires +attiring +attitude +attitudes +attitudinal +attitudinize +attitudinized +attitudinizes +attitudinizing +attorney +attorneys +attract +attractable +attractant +attractants +attracted +attracting +attraction +attractions +attractive +attractively +attractiveness +attracts +attributable +attribute +attributed +attributes +attributing +attribution +attributions +attributive +attributively +attributives +attrition +attune +attuned +attunes +attuning +atwitter +atypical +atypically +auburn +auction +auctioned +auctioneer +auctioneers +auctioning +auctions +audacious +audaciously +audaciousness +audacity +audibility +audible +audibles +audibly +audience +audiences +audio +audiobook +audiobooks +audiological +audiologist +audiologists +audiology +audiometer +audiometers +audiophile +audiophiles +audios +audiotape +audiotapes +audiovisual +audiovisuals +audit +audited +auditing +audition +auditioned +auditioning +auditions +auditor +auditoria +auditorium +auditoriums +auditors +auditory +audits +auger +augers +aught +aughts +augment +augmentation +augmentations +augmentative +augmented +augmenter +augmenters +augmenting +augments +augur +augured +auguries +auguring +augurs +augury +august +auguster +augustest +augustly +augustness +auks +aunt +auntie +aunties +aunts +aunty +aura +aurae +aural +aurally +auras +aureola +aureolas +aureole +aureoles +auricle +auricles +auricular +aurora +aurorae +auroras +auscultate +auscultated +auscultates +auscultating +auscultation +auscultations +auspice +auspices +auspicious +auspiciously +auspiciousness +austere +austerely +austerer +austerest +austerities +austerity +austral +authentic +authentically +authenticate +authenticated +authenticates +authenticating +authentication +authentications +authenticity +author +authored +authoress +authoresses +authoring +authoritarian +authoritarianism +authoritarians +authoritative +authoritatively +authoritativeness +authorities +authority +authorization +authorizations +authorize +authorized +authorizes +authorizing +authors +authorship +autism +autistic +auto +autobahn +autobahns +autobiographer +autobiographers +autobiographic +autobiographical +autobiographically +autobiographies +autobiography +autoclave +autoclaves +autocracies +autocracy +autocrat +autocratic +autocratically +autocrats +autodidact +autodidacts +autograph +autographed +autographing +autographs +autoimmune +autoimmunity +automaker +automakers +automata +automate +automated +automates +automatic +automatically +automatics +automating +automation +automatism +automatize +automatized +automatizes +automatizing +automaton +automatons +automobile +automobiles +automotive +autonomic +autonomous +autonomously +autonomy +autopilot +autopilots +autopsied +autopsies +autopsy +autopsying +autos +autoworker +autoworkers +autumn +autumnal +autumns +auxiliaries +auxiliary +auxin +avail +availability +available +availed +availing +avails +avalanche +avalanches +avarice +avaricious +avariciously +avast +avatar +avatars +avaunt +avenge +avenged +avenger +avengers +avenges +avenging +avenue +avenues +aver +average +averaged +averages +averaging +averred +averring +avers +averse +aversion +aversions +avert +averted +averting +averts +avian +aviaries +aviary +aviation +aviator +aviators +aviatrices +aviatrix +aviatrixes +avid +avidity +avidly +avionic +avionics +avitaminosis +avocado +avocadoes +avocados +avocation +avocational +avocations +avoid +avoidable +avoidably +avoidance +avoided +avoiding +avoids +avoirdupois +avouch +avouched +avouches +avouching +avow +avowal +avowals +avowed +avowedly +avowing +avows +avuncular +await +awaited +awaiting +awaits +awake +awaked +awaken +awakened +awakening +awakenings +awakens +awakes +awaking +award +awarded +awarding +awards +aware +awareness +awash +away +awed +aweigh +awes +awesome +awesomely +awesomeness +awestricken +awestruck +awful +awfuller +awfullest +awfully +awfulness +awhile +awing +awkward +awkwarder +awkwardest +awkwardly +awkwardness +awls +awning +awnings +awns +awoke +awoken +awol +awry +axed +axes +axial +axially +axing +axiom +axiomatic +axiomatically +axioms +axis +axle +axles +axletree +axletrees +axolotl +axolotls +axon +axons +ayah +ayahs +ayatollah +ayatollahs +ayes +azalea +azaleas +azimuth +azimuths +azure +baaed +baaing +baas +babble +babbled +babbler +babblers +babbles +babbling +babe +babel +babels +babes +babied +babier +babies +babiest +baboon +baboons +babushka +babushkas +baby +babyhood +babying +babyish +babysat +babysit +babysits +babysitter +babysitters +babysitting +baccalaureate +baccalaureates +baccarat +bacchanal +bacchanalia +bacchanalian +bacchanalians +bacchanalias +bacchanals +bachelor +bachelorhood +bachelors +bacillary +bacilli +bacillus +back +backache +backaches +backbencher +backbenchers +backbit +backbite +backbiter +backbiters +backbites +backbiting +backbitten +backboard +backboards +backbone +backbones +backbreaking +backdate +backdated +backdates +backdating +backdrop +backdrops +backed +backer +backers +backfield +backfields +backfire +backfired +backfires +backfiring +backgammon +background +backgrounder +backgrounders +backgrounds +backhand +backhanded +backhandedly +backhander +backhanders +backhanding +backhands +backhoe +backhoes +backing +backings +backlash +backlashes +backless +backlog +backlogged +backlogging +backlogs +backpack +backpacked +backpacker +backpackers +backpacking +backpacks +backpedal +backpedaled +backpedaling +backpedalled +backpedalling +backpedals +backrest +backrests +backroom +backs +backscratching +backseat +backseats +backside +backsides +backslapper +backslappers +backslapping +backslash +backslashes +backslid +backslidden +backslide +backslider +backsliders +backslides +backsliding +backspace +backspaced +backspaces +backspacing +backspin +backstabber +backstabbers +backstage +backstair +backstairs +backstop +backstopped +backstopping +backstops +backstretch +backstretches +backstroke +backstroked +backstrokes +backstroking +backtalk +backtrack +backtracked +backtracking +backtracks +backup +backups +backward +backwardly +backwardness +backwards +backwash +backwater +backwaters +backwoods +backwoodsman +backwoodsmen +backyard +backyards +bacon +bacteria +bacterial +bactericidal +bactericide +bactericides +bacteriologic +bacteriological +bacteriologist +bacteriologists +bacteriology +bacterium +badder +baddest +baddie +baddies +baddy +bade +badge +badger +badgered +badgering +badgers +badges +badinage +badlands +badly +badman +badmen +badminton +badmouth +badmouthed +badmouthing +badmouths +badness +baffle +baffled +bafflement +baffler +bafflers +baffles +baffling +bagatelle +bagatelles +bagel +bagels +bagful +bagfuls +baggage +bagged +baggie +baggier +baggies +baggiest +baggily +bagginess +bagging +baggy +bagpipe +bagpiper +bagpipers +bagpipes +bags +bagsful +baguette +baguettes +baht +bahts +bail +bailable +bailed +bailiff +bailiffs +bailing +bailiwick +bailiwicks +bailout +bailouts +bails +bailsman +bailsmen +bairn +bairns +bait +baited +baiting +baits +baize +bake +baked +baker +bakeries +bakers +bakery +bakes +bakeshop +bakeshops +baking +baklava +baksheesh +balaclava +balaclavas +balalaika +balalaikas +balance +balanced +balances +balancing +balboa +balboas +balconies +balcony +bald +balded +balder +balderdash +baldest +baldfaced +balding +baldly +baldness +baldric +baldrics +balds +bale +baled +baleen +baleful +balefully +balefulness +baler +balers +bales +baling +balk +balked +balkier +balkiest +balking +balks +balky +ball +ballad +balladeer +balladeers +balladry +ballads +ballast +ballasted +ballasting +ballasts +ballcock +ballcocks +balled +ballerina +ballerinas +ballet +balletic +ballets +ballgame +ballgames +balling +ballistic +ballistics +balloon +ballooned +ballooning +balloonist +balloonists +balloons +ballot +balloted +balloting +ballots +ballpark +ballparks +ballplayer +ballplayers +ballpoint +ballpoints +ballroom +ballrooms +balls +ballsier +ballsiest +ballsy +ballyhoo +ballyhooed +ballyhooing +ballyhoos +balm +balmier +balmiest +balminess +balms +balmy +baloney +balsa +balsam +balsamic +balsams +balsas +baluster +balusters +balustrade +balustrades +bamboo +bamboos +bamboozle +bamboozled +bamboozles +bamboozling +banal +banalities +banality +banally +banana +bananas +band +bandage +bandaged +bandages +bandaging +bandana +bandanas +bandanna +bandannas +bandbox +bandboxes +bandeau +bandeaux +banded +bandied +bandier +bandies +bandiest +banding +bandit +banditry +bandits +banditti +bandmaster +bandmasters +bandoleer +bandoleers +bandolier +bandoliers +bands +bandsman +bandsmen +bandstand +bandstands +bandwagon +bandwagons +bandy +bandying +bane +baneful +banes +bang +banged +banging +bangle +bangles +bangs +bani +banish +banished +banishes +banishing +banishment +banister +banisters +banjo +banjoes +banjoist +banjoists +banjos +bank +bankable +bankbook +bankbooks +bankcard +bankcards +banked +banker +bankers +banking +banknote +banknotes +bankroll +bankrolled +bankrolling +bankrolls +bankrupt +bankruptcies +bankruptcy +bankrupted +bankrupting +bankrupts +banks +banned +banner +banners +banning +bannister +bannisters +bannock +bannocks +banns +banquet +banqueted +banqueter +banqueters +banqueting +banquets +banquette +banquettes +bans +banshee +banshees +banshie +banshies +bantam +bantams +bantamweight +bantamweights +banter +bantered +bantering +banteringly +banters +banyan +banyans +banzai +banzais +baobab +baobabs +baptism +baptismal +baptisms +baptisteries +baptistery +baptistries +baptistry +baptize +baptized +baptizer +baptizers +baptizes +baptizing +barb +barbarian +barbarianism +barbarianisms +barbarians +barbaric +barbarically +barbarism +barbarisms +barbarities +barbarity +barbarize +barbarized +barbarizes +barbarizing +barbarous +barbarously +barbecue +barbecued +barbecues +barbecuing +barbed +barbel +barbell +barbells +barbels +barbeque +barbequed +barbeques +barbequing +barber +barbered +barbering +barberries +barberry +barbers +barbershop +barbershops +barbing +barbiturate +barbiturates +barbs +barbwire +barcarole +barcaroles +barcarolle +barcarolles +bard +bardic +bards +bare +bareback +barebacked +bared +barefaced +barefacedly +barefoot +barefooted +barehanded +bareheaded +barelegged +barely +bareness +barer +bares +barest +barf +barfed +barfing +barflies +barfly +barfs +bargain +bargained +bargainer +bargainers +bargaining +bargains +barge +barged +bargeman +bargemen +barges +barging +barhop +barhopped +barhopping +barhops +baring +baritone +baritones +barium +bark +barked +barkeep +barkeeper +barkeepers +barkeeps +barker +barkers +barking +barks +barley +barmaid +barmaids +barman +barmen +barn +barnacle +barnacled +barnacles +barns +barnstorm +barnstormed +barnstormer +barnstormers +barnstorming +barnstorms +barnyard +barnyards +barometer +barometers +barometric +barometrically +baron +baronage +baronages +baroness +baronesses +baronet +baronetcies +baronetcy +baronets +baronial +baronies +barons +barony +baroque +barque +barques +barrack +barracked +barracking +barracks +barracuda +barracudas +barrage +barraged +barrages +barraging +barre +barred +barrel +barreled +barreling +barrelled +barrelling +barrels +barren +barrener +barrenest +barrenness +barrens +barres +barrette +barrettes +barricade +barricaded +barricades +barricading +barrier +barriers +barring +barrio +barrios +barrister +barristers +barroom +barrooms +barrow +barrows +bars +bartender +bartenders +barter +bartered +barterer +barterers +bartering +barters +baryon +baryons +basal +basally +basalt +basaltic +base +baseball +baseballs +baseboard +baseboards +based +baseless +baseline +baselines +basely +baseman +basemen +basement +basements +baseness +baser +bases +basest +bash +bashed +bashes +bashful +bashfully +bashfulness +bashing +basic +basically +basics +basil +basilica +basilicas +basilisk +basilisks +basin +basinful +basinfuls +basing +basins +basis +bask +basked +basket +basketball +basketballs +basketry +baskets +basketwork +basking +basks +bass +basses +basset +bassets +bassi +bassinet +bassinets +bassist +bassists +basso +bassoon +bassoonist +bassoonists +bassoons +bassos +basswood +basswoods +bast +bastard +bastardization +bastardizations +bastardize +bastardized +bastardizes +bastardizing +bastards +bastardy +baste +basted +baster +basters +bastes +basting +bastion +bastions +batch +batched +batches +batching +bate +bated +bates +bath +bathe +bathed +bather +bathers +bathes +bathetic +bathhouse +bathhouses +bathing +bathmat +bathmats +bathos +bathrobe +bathrobes +bathroom +bathrooms +baths +bathtub +bathtubs +bathyscaph +bathyscaphe +bathyscaphes +bathyscaphs +bathysphere +bathyspheres +batik +batiks +bating +batiste +batman +batmen +baton +batons +bats +batsman +batsmen +battalion +battalions +batted +batten +battened +battening +battens +batter +battered +batterer +batterers +batteries +battering +batters +battery +battier +battiest +batting +battle +battleax +battleaxe +battleaxes +battled +battledore +battledores +battlefield +battlefields +battlefront +battlefronts +battleground +battlegrounds +battlement +battlements +battler +battlers +battles +battleship +battleships +battling +batty +bauble +baubles +baud +bauds +baulk +baulked +baulking +baulks +bauxite +bawd +bawdier +bawdiest +bawdily +bawdiness +bawds +bawdy +bawl +bawled +bawling +bawls +bayberries +bayberry +bayed +baying +bayonet +bayoneted +bayoneting +bayonets +bayonetted +bayonetting +bayou +bayous +bays +bazaar +bazaars +bazooka +bazookas +beach +beachcomber +beachcombers +beached +beaches +beachhead +beachheads +beaching +beachwear +beacon +beacons +bead +beaded +beadier +beadiest +beading +beadle +beadles +beads +beady +beagle +beagles +beak +beaked +beaker +beakers +beaks +beam +beamed +beaming +beams +bean +beanbag +beanbags +beaned +beanie +beanies +beaning +beanpole +beanpoles +beans +beanstalk +beanstalks +bear +bearable +bearably +beard +bearded +bearding +beardless +beards +bearer +bearers +bearing +bearings +bearish +bearishly +bearishness +bearlike +bears +bearskin +bearskins +beast +beastlier +beastliest +beastliness +beastly +beasts +beat +beatable +beaten +beater +beaters +beatific +beatifically +beatification +beatifications +beatified +beatifies +beatify +beatifying +beating +beatings +beatitude +beatitudes +beatnik +beatniks +beats +beau +beaus +beaut +beauteous +beauteously +beautician +beauticians +beauties +beautification +beautified +beautifier +beautifiers +beautifies +beautiful +beautifully +beautify +beautifying +beauts +beauty +beaux +beaver +beavered +beavering +beavers +bebop +becalm +becalmed +becalming +becalms +became +because +beck +beckon +beckoned +beckoning +beckons +becks +becloud +beclouded +beclouding +beclouds +become +becomes +becoming +becomingly +bedaub +bedaubed +bedaubing +bedaubs +bedazzle +bedazzled +bedazzlement +bedazzles +bedazzling +bedbug +bedbugs +bedclothes +bedded +bedding +bedeck +bedecked +bedecking +bedecks +bedevil +bedeviled +bedeviling +bedevilled +bedevilling +bedevilment +bedevils +bedfellow +bedfellows +bedim +bedimmed +bedimming +bedims +bedizen +bedizened +bedizening +bedizens +bedlam +bedlams +bedpan +bedpans +bedpost +bedposts +bedraggle +bedraggled +bedraggles +bedraggling +bedridden +bedrock +bedrocks +bedroll +bedrolls +bedroom +bedrooms +beds +bedside +bedsides +bedsore +bedsores +bedspread +bedspreads +bedstead +bedsteads +bedtime +bedtimes +beebread +beech +beeches +beechnut +beechnuts +beef +beefburger +beefburgers +beefcake +beefcakes +beefed +beefier +beefiest +beefiness +beefing +beefs +beefsteak +beefsteaks +beefy +beehive +beehives +beekeeper +beekeepers +beekeeping +beeline +beelines +been +beep +beeped +beeper +beepers +beeping +beeps +beer +beerier +beeriest +beers +beery +bees +beeswax +beet +beetle +beetled +beetles +beetling +beets +beeves +befall +befallen +befalling +befalls +befell +befit +befits +befitted +befitting +befittingly +befog +befogged +befogging +befogs +before +beforehand +befoul +befouled +befouling +befouls +befriend +befriended +befriending +befriends +befuddle +befuddled +befuddlement +befuddles +befuddling +began +begat +beget +begets +begetting +beggar +beggared +beggaring +beggarly +beggars +beggary +begged +begging +begin +beginner +beginners +beginning +beginnings +begins +begone +begonia +begonias +begot +begotten +begrime +begrimed +begrimes +begriming +begrudge +begrudged +begrudges +begrudging +begrudgingly +begs +beguile +beguiled +beguilement +beguiler +beguilers +beguiles +beguiling +beguilingly +beguine +beguines +begum +begums +begun +behalf +behalves +behave +behaved +behaves +behaving +behavior +behavioral +behaviorally +behaviorism +behaviorist +behaviorists +behaviour +behead +beheaded +beheading +beheads +beheld +behemoth +behemoths +behest +behests +behind +behindhand +behinds +behold +beholden +beholder +beholders +beholding +beholds +behoove +behooved +behooves +behooving +beige +being +beings +bejewel +bejeweled +bejeweling +bejewelled +bejewelling +bejewels +belabor +belabored +belaboring +belabors +belabour +belaboured +belabouring +belabours +belated +belatedly +belay +belayed +belaying +belays +belch +belched +belches +belching +beleaguer +beleaguered +beleaguering +beleaguers +belfries +belfry +belie +belied +belief +beliefs +belies +believable +believably +believe +believed +believer +believers +believes +believing +belittle +belittled +belittlement +belittles +belittling +bell +belladonna +bellboy +bellboys +belle +belled +belles +belletrist +belletristic +belletrists +bellhop +bellhops +bellicose +bellicosity +bellied +bellies +belligerence +belligerency +belligerent +belligerently +belligerents +belling +bellman +bellmen +bellow +bellowed +bellowing +bellows +bells +bellwether +bellwethers +belly +bellyache +bellyached +bellyaches +bellyaching +bellybutton +bellybuttons +bellyful +bellyfuls +bellying +belong +belonged +belonging +belongings +belongs +beloved +beloveds +below +belt +belted +belting +belts +beltway +beltways +beluga +belugas +belying +bemire +bemired +bemires +bemiring +bemoan +bemoaned +bemoaning +bemoans +bemuse +bemused +bemusedly +bemusement +bemuses +bemusing +bench +benched +benches +benching +benchmark +benchmarks +bend +bendable +bender +benders +bending +bends +beneath +benediction +benedictions +benedictory +benefaction +benefactions +benefactor +benefactors +benefactress +benefactresses +benefice +beneficence +beneficent +beneficently +benefices +beneficial +beneficially +beneficiaries +beneficiary +benefit +benefited +benefiting +benefits +benefitted +benefitting +benevolence +benevolences +benevolent +benevolently +benighted +benightedly +benign +benignant +benigner +benignest +benignity +benignly +bent +bents +bentwood +benumb +benumbed +benumbing +benumbs +benzene +benzine +bequeath +bequeathed +bequeathing +bequeaths +bequest +bequests +berate +berated +berates +berating +bereave +bereaved +bereavement +bereavements +bereaves +bereaving +bereft +beret +berets +berg +bergs +beriberi +berkelium +berm +berms +berried +berries +berry +berrying +berrylike +berserk +berth +berthed +berthing +berths +beryl +beryllium +beryls +beseech +beseeched +beseecher +beseechers +beseeches +beseeching +beseechingly +beseem +beseemed +beseeming +beseems +beset +besets +besetting +beside +besides +besiege +besieged +besieger +besiegers +besieges +besieging +besmear +besmeared +besmearing +besmears +besmirch +besmirched +besmirches +besmirching +besom +besoms +besot +besots +besotted +besotting +besought +bespangle +bespangled +bespangles +bespangling +bespatter +bespattered +bespattering +bespatters +bespeak +bespeaking +bespeaks +bespectacled +bespoke +bespoken +best +bested +bestial +bestiality +bestially +bestiaries +bestiary +besting +bestir +bestirred +bestirring +bestirs +bestow +bestowal +bestowals +bestowed +bestowing +bestows +bestrew +bestrewed +bestrewing +bestrewn +bestrews +bestrid +bestridden +bestride +bestrides +bestriding +bestrode +bests +bestseller +bestsellers +beta +betake +betaken +betakes +betaking +betas +betcha +betel +bethink +bethinking +bethinks +bethought +betide +betided +betides +betiding +betimes +betoken +betokened +betokening +betokens +betook +betray +betrayal +betrayals +betrayed +betrayer +betrayers +betraying +betrays +betroth +betrothal +betrothals +betrothed +betrothing +betroths +bets +betted +better +bettered +bettering +betterment +betters +betting +bettor +bettors +between +betwixt +bevel +beveled +beveling +bevelled +bevelling +bevels +beverage +beverages +bevies +bevy +bewail +bewailed +bewailing +bewails +beware +bewared +bewares +bewaring +bewhiskered +bewigged +bewilder +bewildered +bewildering +bewilderingly +bewilderment +bewilders +bewitch +bewitched +bewitches +bewitching +bewitchingly +bewitchment +beyond +beys +bezel +bezels +biannual +biannually +bias +biased +biases +biasing +biassed +biassing +biathlon +biathlons +bible +bibles +biblical +bibliographer +bibliographers +bibliographic +bibliographical +bibliographically +bibliographies +bibliography +bibliophile +bibliophiles +bibs +bibulous +bicameral +bicameralism +bicarb +bicarbonate +bicarbonates +bicarbs +bicentenaries +bicentenary +bicentennial +bicentennials +bicep +biceps +bicepses +bicker +bickered +bickerer +bickerers +bickering +bickers +biconcave +biconvex +bicuspid +bicuspids +bicycle +bicycled +bicycler +bicyclers +bicycles +bicycling +bicyclist +bicyclists +biddable +bidden +bidder +bidders +biddies +bidding +biddy +bide +bided +bides +bidet +bidets +biding +bidirectional +bidirectionally +bids +biennia +biennial +biennially +biennials +biennium +bienniums +bier +biers +bifocal +bifocals +bifurcate +bifurcated +bifurcates +bifurcating +bifurcation +bifurcations +bigamist +bigamists +bigamous +bigamy +bigger +biggest +biggie +biggies +biggish +bighead +bigheads +bighearted +bigheartedness +bighorn +bighorns +bight +bights +bigmouth +bigmouths +bigness +bigot +bigoted +bigotries +bigotry +bigots +bigwig +bigwigs +bijou +bijoux +bike +biked +biker +bikers +bikes +biking +bikini +bikinis +bilabial +bilabials +bilateral +bilaterally +bile +bilge +bilges +bilingual +bilingualism +bilingually +bilinguals +bilious +biliousness +bilk +bilked +bilker +bilkers +bilking +bilks +bill +billable +billboard +billboards +billed +billet +billeted +billeting +billets +billfold +billfolds +billiard +billiards +billies +billing +billings +billingsgate +billion +billionaire +billionaires +billions +billionth +billionths +billow +billowed +billowing +billows +billowy +bills +billy +bimbo +bimboes +bimbos +bimetallic +bimetallics +bimetallism +bimonthlies +bimonthly +binaries +binary +bind +binder +binderies +binders +bindery +binding +bindings +binds +bindweed +binge +binged +bingeing +binges +binging +bingo +binnacle +binnacles +binned +binning +binocular +binoculars +binomial +binomials +bins +biochemical +biochemically +biochemicals +biochemist +biochemistry +biochemists +biodegradability +biodegradable +biodegrade +biodegraded +biodegrades +biodegrading +biodiversity +bioethics +biofeedback +biographer +biographers +biographic +biographical +biographically +biographies +biography +biologic +biological +biologically +biologist +biologists +biology +biomass +bionic +bionically +bionics +biophysical +biophysicist +biophysicists +biophysics +biopic +biopics +biopsied +biopsies +biopsy +biopsying +biorhythm +biorhythms +bios +biosphere +biospheres +biotechnological +biotechnology +biotin +bipartisan +bipartisanship +bipartite +biped +bipedal +bipeds +biplane +biplanes +bipolar +bipolarity +biracial +birch +birched +birches +birching +bird +birdbath +birdbaths +birdbrain +birdbrained +birdbrains +birded +birder +birders +birdhouse +birdhouses +birdie +birdied +birdieing +birdies +birding +birdlime +birds +birdseed +birdwatcher +birdwatchers +biretta +birettas +birth +birthday +birthdays +birthed +birthing +birthmark +birthmarks +birthplace +birthplaces +birthrate +birthrates +birthright +birthrights +births +birthstone +birthstones +biscuit +biscuits +bisect +bisected +bisecting +bisection +bisections +bisector +bisectors +bisects +bisexual +bisexuality +bisexually +bisexuals +bishop +bishopric +bishoprics +bishops +bismuth +bison +bisons +bisque +bistro +bistros +bitch +bitched +bitches +bitchier +bitchiest +bitchily +bitchiness +bitching +bitchy +bite +biter +biters +bites +biting +bitingly +bits +bitten +bitter +bitterer +bitterest +bitterly +bittern +bitterness +bitterns +bitters +bittersweet +bittersweets +bittier +bittiest +bitty +bitumen +bituminous +bivalent +bivalve +bivalves +bivouac +bivouacked +bivouacking +bivouacs +biweeklies +biweekly +biyearly +bizarre +bizarrely +blab +blabbed +blabber +blabbered +blabbering +blabbermouth +blabbermouths +blabbers +blabbing +blabs +black +blackamoor +blackamoors +blackball +blackballed +blackballing +blackballs +blackberries +blackberry +blackbird +blackbirds +blackboard +blackboards +blacked +blacken +blackened +blackening +blackens +blacker +blackest +blackguard +blackguards +blackhead +blackheads +blacking +blackish +blackjack +blackjacked +blackjacking +blackjacks +blacklist +blacklisted +blacklisting +blacklists +blackly +blackmail +blackmailed +blackmailer +blackmailers +blackmailing +blackmails +blackness +blackout +blackouts +blacks +blacksmith +blacksmiths +blacksnake +blacksnakes +blackthorn +blackthorns +blacktop +blacktopped +blacktopping +blacktops +bladder +bladders +blade +bladed +blades +blah +blahs +blamable +blame +blameable +blamed +blameless +blamelessly +blamelessness +blames +blameworthiness +blameworthy +blaming +blanch +blanched +blanches +blanching +blancmange +blancmanges +bland +blander +blandest +blandish +blandished +blandishes +blandishing +blandishment +blandishments +blandly +blandness +blank +blanked +blanker +blankest +blanket +blanketed +blanketing +blankets +blanking +blankly +blankness +blanks +blare +blared +blares +blaring +blarney +blarneyed +blarneying +blarneys +blase +blaspheme +blasphemed +blasphemer +blasphemers +blasphemes +blasphemies +blaspheming +blasphemous +blasphemously +blasphemy +blast +blasted +blaster +blasters +blasting +blastoff +blastoffs +blasts +blatancies +blatancy +blatant +blatantly +blather +blathered +blathering +blathers +blaze +blazed +blazer +blazers +blazes +blazing +blazon +blazoned +blazoning +blazons +bleach +bleached +bleacher +bleachers +bleaches +bleaching +bleak +bleaker +bleakest +bleakly +bleakness +blear +blearier +bleariest +blearily +bleariness +bleary +bleat +bleated +bleating +bleats +bled +bleed +bleeder +bleeders +bleeding +bleeds +bleep +bleeped +bleeper +bleepers +bleeping +bleeps +blemish +blemished +blemishes +blemishing +blench +blenched +blenches +blenching +blend +blended +blender +blenders +blending +blends +blent +bless +blessed +blessedly +blessedness +blesses +blessing +blessings +blest +blew +blight +blighted +blighting +blights +blimey +blimp +blimps +blind +blinded +blinder +blinders +blindest +blindfold +blindfolded +blindfolding +blindfolds +blinding +blindingly +blindly +blindness +blinds +blindside +blindsided +blindsides +blindsiding +blini +blinis +blink +blinked +blinker +blinkered +blinkering +blinkers +blinking +blinks +blintz +blintze +blintzes +blip +blips +bliss +blissful +blissfully +blissfulness +blister +blistered +blistering +blisteringly +blisters +blistery +blithe +blithely +blitheness +blither +blithering +blithesome +blithest +blitz +blitzed +blitzes +blitzing +blitzkrieg +blitzkriegs +blizzard +blizzards +bloat +bloated +bloating +bloats +blob +blobbed +blobbing +blobs +bloc +block +blockade +blockaded +blockader +blockaders +blockades +blockading +blockage +blockages +blockbuster +blockbusters +blockbusting +blocked +blocker +blockers +blockhead +blockheads +blockhouse +blockhouses +blocking +blocks +blocs +bloke +blokes +blond +blonde +blonder +blondes +blondest +blondish +blondness +blonds +blood +bloodbath +bloodbaths +bloodcurdling +blooded +bloodhound +bloodhounds +bloodied +bloodier +bloodies +bloodiest +bloodiness +blooding +bloodless +bloodlessly +bloodlessness +bloodletting +bloodline +bloodlines +bloodmobile +bloodmobiles +bloods +bloodshed +bloodshot +bloodstain +bloodstained +bloodstains +bloodstock +bloodstream +bloodstreams +bloodsucker +bloodsuckers +bloodsucking +bloodthirstier +bloodthirstiest +bloodthirstily +bloodthirstiness +bloodthirsty +bloody +bloodying +bloom +bloomed +bloomer +bloomers +blooming +blooms +bloop +blooped +blooper +bloopers +blooping +bloops +blossom +blossomed +blossoming +blossoms +blossomy +blot +blotch +blotched +blotches +blotchier +blotchiest +blotching +blotchy +blots +blotted +blotter +blotters +blotting +blotto +blouse +bloused +blouses +blousing +blow +blower +blowers +blowflies +blowfly +blowgun +blowguns +blowhard +blowhards +blowier +blowiest +blowing +blown +blowout +blowouts +blowpipe +blowpipes +blows +blowsier +blowsiest +blowsy +blowtorch +blowtorched +blowtorches +blowtorching +blowup +blowups +blowy +blowzier +blowziest +blowzy +blubber +blubbered +blubbering +blubbers +blubbery +bludgeon +bludgeoned +bludgeoning +bludgeons +blue +bluebell +bluebells +blueberries +blueberry +bluebird +bluebirds +bluebonnet +bluebonnets +bluebottle +bluebottles +blued +bluefish +bluefishes +bluegill +bluegills +bluegrass +blueing +blueish +bluejacket +bluejackets +bluejay +bluejays +bluejeans +blueness +bluenose +bluenoses +bluepoint +bluepoints +blueprint +blueprinted +blueprinting +blueprints +bluer +blues +bluesier +bluesiest +bluest +bluestocking +bluestockings +bluesy +bluet +bluets +bluff +bluffed +bluffer +bluffers +bluffest +bluffing +bluffly +bluffness +bluffs +bluing +bluish +blunder +blunderbuss +blunderbusses +blundered +blunderer +blunderers +blundering +blunders +blunt +blunted +blunter +bluntest +blunting +bluntly +bluntness +blunts +blur +blurb +blurbs +blurred +blurrier +blurriest +blurriness +blurring +blurry +blurs +blurt +blurted +blurting +blurts +blush +blushed +blusher +blushers +blushes +blushing +bluster +blustered +blusterer +blusterers +blustering +blusterous +blusters +blustery +boar +board +boarded +boarder +boarders +boarding +boardinghouse +boardinghouses +boardroom +boardrooms +boards +boardwalk +boardwalks +boars +boas +boast +boasted +boaster +boasters +boastful +boastfully +boastfulness +boasting +boasts +boat +boated +boater +boaters +boathouse +boathouses +boating +boatman +boatmen +boats +boatswain +boatswains +bobbed +bobbies +bobbin +bobbing +bobbins +bobble +bobbled +bobbles +bobbling +bobby +bobbysoxer +bobbysoxers +bobcat +bobcats +bobolink +bobolinks +bobs +bobsled +bobsledded +bobsledder +bobsledders +bobsledding +bobsleds +bobsleigh +bobsleighed +bobsleighing +bobsleighs +bobtail +bobtails +bobwhite +bobwhites +bocce +bocci +boccie +bock +bodacious +bode +boded +bodega +bodegas +bodes +bodice +bodices +bodied +bodies +bodily +boding +bodkin +bodkins +bods +body +bodybuilder +bodybuilders +bodybuilding +bodyguard +bodyguards +bodysuit +bodysuits +bodywork +boffo +bogey +bogeyed +bogeying +bogeyman +bogeymen +bogeys +bogged +boggier +boggiest +bogging +boggle +boggled +boggles +boggling +boggy +bogie +bogied +bogieing +bogies +bogs +bogus +bogy +bogyman +bogymen +bohemian +bohemianism +bohemians +boil +boiled +boiler +boilermaker +boilermakers +boilerplate +boilers +boiling +boils +boisterous +boisterously +boisterousness +bola +bolas +bold +bolder +boldest +boldface +boldfaced +boldly +boldness +bole +bolero +boleros +boles +bolivar +bolivares +bolivars +boll +bollix +bollixed +bollixes +bollixing +bolls +bologna +boloney +bolshevik +bolsheviki +bolsheviks +bolster +bolstered +bolstering +bolsters +bolt +bolted +bolting +bolts +bolus +boluses +bomb +bombard +bombarded +bombardier +bombardiers +bombarding +bombardment +bombardments +bombards +bombast +bombastic +bombastically +bombed +bomber +bombers +bombing +bombproof +bombproofed +bombproofing +bombproofs +bombs +bombshell +bombshells +bonanza +bonanzas +bonbon +bonbons +bond +bondage +bonded +bondholder +bondholders +bonding +bondman +bondmen +bonds +bondsman +bondsmen +bondwoman +bondwomen +bone +boned +bonehead +boneheaded +boneheads +boneless +boner +boners +bones +boney +boneyer +boneyest +bonfire +bonfires +bong +bonged +bonging +bongo +bongoes +bongos +bongs +bonhomie +bonier +boniest +boniness +boning +bonito +bonitoes +bonitos +bonkers +bonnet +bonnets +bonnie +bonnier +bonniest +bonny +bonsai +bonus +bonuses +bony +boob +boobed +boobies +boobing +booboo +booboos +boobs +booby +boodle +boodles +booed +boogeyman +boogeymen +boogie +boogied +boogieing +boogieman +boogiemen +boogies +boohoo +boohooed +boohooing +boohoos +booing +book +bookbinder +bookbinderies +bookbinders +bookbindery +bookbinding +bookcase +bookcases +booked +bookend +bookended +bookending +bookends +bookie +bookies +booking +bookings +bookish +bookkeeper +bookkeepers +bookkeeping +booklet +booklets +bookmaker +bookmakers +bookmaking +bookmark +bookmarked +bookmarking +bookmarks +bookmobile +bookmobiles +bookplate +bookplates +books +bookseller +booksellers +bookshelf +bookshelves +bookshop +bookshops +bookstore +bookstores +bookworm +bookworms +boom +boombox +boomboxes +boomed +boomerang +boomeranged +boomeranging +boomerangs +booming +booms +boon +boondocks +boondoggle +boondoggled +boondoggler +boondogglers +boondoggles +boondoggling +boonies +boons +boor +boorish +boorishly +boorishness +boorishnesses +boors +boos +boost +boosted +booster +boosters +boosting +boosts +boot +bootblack +bootblacks +booted +bootee +bootees +booth +booths +bootie +booties +booting +bootleg +bootlegged +bootlegger +bootleggers +bootlegging +bootlegs +bootless +boots +bootstrap +bootstrapped +bootstrapping +bootstraps +booty +booze +boozed +boozer +boozers +boozes +boozier +booziest +boozing +boozy +bopped +bopping +bops +borax +bordello +bordellos +border +bordered +bordering +borderland +borderlands +borderline +borderlines +borders +bore +bored +boredom +borer +borers +bores +boring +born +borne +boron +borough +boroughs +borrow +borrowed +borrower +borrowers +borrowing +borrowings +borrows +borsch +borscht +borzoi +borzois +bosh +bosom +bosoms +bosomy +boss +bossed +bosses +bossier +bossiest +bossily +bossiness +bossing +bossism +bossy +bosun +bosuns +botanic +botanical +botanically +botanist +botanists +botany +botch +botched +botcher +botchers +botches +botching +both +bother +bothered +bothering +bothers +bothersome +bottle +bottled +bottleneck +bottlenecks +bottler +bottlers +bottles +bottling +bottom +bottomed +bottoming +bottomless +bottoms +botulism +boudoir +boudoirs +bouffant +bouffants +bougainvillea +bougainvilleas +bough +boughs +bought +bouillabaisse +bouillabaisses +bouillon +bouillons +boulder +boulders +boulevard +boulevards +bounce +bounced +bouncer +bouncers +bounces +bouncier +bounciest +bouncily +bounciness +bouncing +bouncy +bound +boundaries +boundary +bounded +bounden +bounder +bounders +bounding +boundless +boundlessly +boundlessness +bounds +bounteous +bounteously +bounteousness +bounties +bountiful +bountifully +bountifulness +bounty +bouquet +bouquets +bourbon +bourgeois +bourgeoisie +bout +boutique +boutiques +boutonniere +boutonnieres +bouts +bouzouki +bouzoukis +bovine +bovines +bowdlerization +bowdlerizations +bowdlerize +bowdlerized +bowdlerizes +bowdlerizing +bowed +bowel +bowels +bower +bowers +bowing +bowl +bowlder +bowlders +bowled +bowleg +bowlegged +bowlegs +bowler +bowlers +bowlful +bowlfuls +bowline +bowlines +bowling +bowls +bowman +bowmen +bows +bowsprit +bowsprits +bowstring +bowstrings +bowwow +bowwows +boxcar +boxcars +boxed +boxer +boxers +boxes +boxier +boxiest +boxing +boxlike +boxwood +boxy +boycott +boycotted +boycotting +boycotts +boyfriend +boyfriends +boyhood +boyhoods +boyish +boyishly +boyishness +boys +boysenberries +boysenberry +bozo +bozos +brace +braced +bracelet +bracelets +bracer +bracero +braceros +bracers +braces +bracing +bracken +bracket +bracketed +bracketing +brackets +brackish +brackishness +bract +bracts +brad +brads +brae +braes +brag +braggadocio +braggadocios +braggart +braggarts +bragged +bragger +braggers +bragging +brags +braid +braided +braiding +braids +braille +brain +brainchild +brainchildren +brained +brainier +brainiest +braininess +braining +brainless +brainlessly +brains +brainstorm +brainstormed +brainstorming +brainstorms +brainteaser +brainteasers +brainwash +brainwashed +brainwashes +brainwashing +brainy +braise +braised +braises +braising +brake +braked +brakeman +brakemen +brakes +braking +bramble +brambles +bramblier +brambliest +brambly +bran +branch +branched +branches +branching +branchlike +brand +branded +brander +branders +brandied +brandies +branding +brandish +brandished +brandishes +brandishing +brands +brandy +brandying +bras +brash +brasher +brashest +brashly +brashness +brass +brasserie +brasseries +brasses +brassier +brassiere +brassieres +brassiest +brassily +brassiness +brassy +brat +brats +brattier +brattiest +bratty +bratwurst +bratwursts +bravado +brave +braved +bravely +braveness +braver +bravery +braves +bravest +braving +bravo +bravos +bravura +bravuras +brawl +brawled +brawler +brawlers +brawling +brawls +brawn +brawnier +brawniest +brawniness +brawny +bray +brayed +braying +brays +braze +brazed +brazen +brazened +brazening +brazenly +brazenness +brazens +brazer +brazers +brazes +brazier +braziers +brazing +breach +breached +breaches +breaching +bread +breadbasket +breadbaskets +breadboard +breadboards +breadbox +breadboxes +breadcrumb +breadcrumbs +breaded +breadfruit +breadfruits +breading +breadline +breadlines +breads +breadth +breadths +breadwinner +breadwinners +break +breakable +breakables +breakage +breakages +breakaway +breakaways +breakdown +breakdowns +breaker +breakers +breakeven +breakfast +breakfasted +breakfasting +breakfasts +breakfront +breakfronts +breaking +breakneck +breakout +breakouts +breaks +breakthrough +breakthroughs +breakup +breakups +breakwater +breakwaters +bream +breams +breast +breastbone +breastbones +breasted +breasting +breastplate +breastplates +breasts +breaststroke +breaststrokes +breastwork +breastworks +breath +breathable +breathalyze +breathalyzed +breathalyzes +breathalyzing +breathe +breathed +breather +breathers +breathes +breathier +breathiest +breathing +breathless +breathlessly +breathlessness +breaths +breathtaking +breathtakingly +breathy +bred +breech +breeches +breed +breeder +breeders +breeding +breeds +breeze +breezed +breezes +breezeway +breezeways +breezier +breeziest +breezily +breeziness +breezing +breezy +brethren +breve +breves +brevet +breveted +breveting +brevets +brevetted +brevetting +breviaries +breviary +brevity +brew +brewed +brewer +breweries +brewers +brewery +brewing +brewpub +brewpubs +brews +briar +briars +bribe +bribed +briber +bribers +bribery +bribes +bribing +brick +brickbat +brickbats +bricked +bricking +bricklayer +bricklayers +bricklaying +bricks +brickwork +bridal +bridals +bride +bridegroom +bridegrooms +brides +bridesmaid +bridesmaids +bridge +bridgeable +bridged +bridgehead +bridgeheads +bridges +bridgework +bridging +bridle +bridled +bridles +bridling +brie +brief +briefcase +briefcases +briefed +briefer +briefest +briefing +briefings +briefly +briefness +briefs +brier +briers +brig +brigade +brigades +brigadier +brigadiers +brigand +brigandage +brigands +brigantine +brigantines +bright +brighten +brightened +brightener +brighteners +brightening +brightens +brighter +brightest +brightly +brightness +brights +brigs +brilliance +brilliancy +brilliant +brilliantine +brilliantly +brilliants +brim +brimful +brimfull +brimless +brimmed +brimming +brims +brimstone +brindle +brindled +brine +bring +bringer +bringers +bringing +brings +brinier +briniest +brininess +brink +brinkmanship +brinks +brinksmanship +briny +brioche +brioches +briquet +briquets +briquette +briquettes +brisk +brisked +brisker +briskest +brisket +briskets +brisking +briskly +briskness +brisks +bristle +bristled +bristles +bristlier +bristliest +bristling +bristly +britches +brittle +brittleness +brittler +brittlest +broach +broached +broaches +broaching +broad +broadband +broadcast +broadcasted +broadcaster +broadcasters +broadcasting +broadcasts +broadcloth +broaden +broadened +broadening +broadens +broader +broadest +broadloom +broadly +broadminded +broadness +broads +broadsheet +broadsheets +broadside +broadsided +broadsides +broadsiding +broadsword +broadswords +brocade +brocaded +brocades +brocading +broccoli +brochette +brochettes +brochure +brochures +brogan +brogans +brogue +brogues +broil +broiled +broiler +broilers +broiling +broils +broke +broken +brokenhearted +brokenheartedly +brokenly +brokenness +broker +brokerage +brokerages +brokered +brokering +brokers +bromide +bromides +bromidic +bromine +bronc +bronchi +bronchial +bronchitic +bronchitis +broncho +bronchos +bronchus +bronco +broncobuster +broncobusters +broncos +broncs +brontosaur +brontosauri +brontosaurs +brontosaurus +brontosauruses +bronze +bronzed +bronzes +bronzing +brooch +brooches +brood +brooded +brooder +brooders +broodier +broodiest +brooding +broodingly +broodmare +broodmares +broods +broody +brook +brooked +brooking +brooklet +brooklets +brooks +broom +brooms +broomstick +broomsticks +bros +broth +brothel +brothels +brother +brotherhood +brotherhoods +brotherliness +brotherly +brothers +broths +brougham +broughams +brought +brouhaha +brouhahas +brow +browbeat +browbeaten +browbeating +browbeats +brown +browned +browner +brownest +brownie +brownies +browning +brownish +brownness +brownout +brownouts +browns +brownstone +brownstones +brows +browse +browsed +browser +browsers +browses +browsing +bruin +bruins +bruise +bruised +bruiser +bruisers +bruises +bruising +bruit +bruited +bruiting +bruits +brunch +brunched +brunches +brunching +brunet +brunets +brunette +brunettes +brunt +brush +brushed +brushes +brushing +brushoff +brushoffs +brushwood +brushwork +brusk +brusker +bruskest +brusque +brusquely +brusqueness +brusquer +brusquest +brutal +brutalities +brutality +brutalization +brutalize +brutalized +brutalizes +brutalizing +brutally +brute +brutes +brutish +brutishly +brutishness +bubble +bubbled +bubblegum +bubbles +bubblier +bubbliest +bubbling +bubbly +bubo +buboes +bubs +buccaneer +buccaneers +buck +buckaroo +buckaroos +buckboard +buckboards +bucked +bucket +bucketed +bucketful +bucketfuls +bucketing +buckets +buckeye +buckeyes +bucking +buckle +buckled +buckler +bucklers +buckles +buckling +buckram +bucks +bucksaw +bucksaws +buckshot +buckskin +buckskins +buckteeth +bucktooth +bucktoothed +buckwheat +bucolic +bucolically +bucolics +budded +buddies +budding +buddy +budge +budged +budgerigar +budgerigars +budges +budget +budgetary +budgeted +budgeting +budgets +budgie +budgies +budging +buds +buff +buffalo +buffaloed +buffaloes +buffaloing +buffalos +buffed +buffer +buffered +buffering +buffers +buffet +buffeted +buffeting +buffets +buffing +buffoon +buffoonery +buffoonish +buffoons +buffs +bugaboo +bugaboos +bugbear +bugbears +bugged +bugger +buggered +buggering +buggers +buggier +buggies +buggiest +bugging +buggy +bugle +bugled +bugler +buglers +bugles +bugling +bugs +build +builder +builders +building +buildings +builds +buildup +buildups +built +bulb +bulbous +bulbs +bulge +bulged +bulges +bulgier +bulgiest +bulging +bulgy +bulimarexia +bulimia +bulimic +bulimics +bulk +bulked +bulkhead +bulkheads +bulkier +bulkiest +bulkiness +bulking +bulks +bulky +bull +bulldog +bulldogged +bulldogging +bulldogs +bulldoze +bulldozed +bulldozer +bulldozers +bulldozes +bulldozing +bulled +bullet +bulletin +bulletined +bulletining +bulletins +bulletproof +bulletproofed +bulletproofing +bulletproofs +bullets +bullfight +bullfighter +bullfighters +bullfighting +bullfights +bullfinch +bullfinches +bullfrog +bullfrogs +bullhead +bullheaded +bullheadedly +bullheadedness +bullheads +bullhorn +bullhorns +bullied +bullies +bulling +bullion +bullish +bullishly +bullishness +bullock +bullocks +bullpen +bullpens +bullring +bullrings +bulls +bullshit +bullshits +bullshitted +bullshitter +bullshitters +bullshitting +bully +bullying +bulrush +bulrushes +bulwark +bulwarks +bumble +bumblebee +bumblebees +bumbled +bumbler +bumblers +bumbles +bumbling +bummed +bummer +bummers +bummest +bumming +bump +bumped +bumper +bumpers +bumpier +bumpiest +bumpiness +bumping +bumpkin +bumpkins +bumps +bumptious +bumptiously +bumptiousness +bumpy +bums +bunch +bunched +bunches +bunchier +bunchiest +bunching +bunchy +bunco +buncoed +buncoing +buncombe +buncos +bundle +bundled +bundles +bundling +bung +bungalow +bungalows +bunged +bungee +bungees +bunghole +bungholes +bunging +bungle +bungled +bungler +bunglers +bungles +bungling +bungs +bunion +bunions +bunk +bunked +bunker +bunkers +bunkhouse +bunkhouses +bunking +bunko +bunkos +bunks +bunkum +bunnies +bunny +buns +bunt +bunted +bunting +buntings +bunts +buoy +buoyancy +buoyant +buoyantly +buoyed +buoying +buoys +burble +burbled +burbles +burbling +burbs +burden +burdened +burdening +burdens +burdensome +burdock +bureau +bureaucracies +bureaucracy +bureaucrat +bureaucratic +bureaucratically +bureaucratization +bureaucratize +bureaucratized +bureaucratizes +bureaucratizing +bureaucrats +bureaus +bureaux +burg +burgeon +burgeoned +burgeoning +burgeons +burger +burgers +burgh +burgher +burghers +burghs +burglar +burglaries +burglarize +burglarized +burglarizes +burglarizing +burglarproof +burglarproofed +burglarproofing +burglarproofs +burglars +burglary +burgle +burgled +burgles +burgling +burgomaster +burgomasters +burgs +burgundies +burgundy +burial +burials +buried +buries +burl +burlap +burled +burlesque +burlesqued +burlesques +burlesquing +burlier +burliest +burliness +burls +burly +burn +burnable +burnables +burned +burner +burners +burning +burnish +burnished +burnisher +burnishers +burnishes +burnishing +burnoose +burnooses +burnous +burnouses +burnout +burnouts +burns +burnt +burp +burped +burping +burps +burr +burred +burring +burrito +burritos +burro +burros +burrow +burrowed +burrower +burrowers +burrowing +burrows +burrs +burs +bursa +bursae +bursar +bursaries +bursars +bursary +bursas +bursitis +burst +bursted +bursting +bursts +bury +burying +busbies +busboy +busboys +busby +bused +buses +busgirl +busgirls +bush +bushed +bushel +busheled +busheling +bushelled +bushelling +bushels +bushes +bushier +bushiest +bushiness +bushing +bushings +bushman +bushmaster +bushmasters +bushmen +bushwhack +bushwhacked +bushwhacker +bushwhackers +bushwhacking +bushwhacks +bushy +busied +busier +busies +busiest +busily +business +businesses +businesslike +businessman +businessmen +businessperson +businesspersons +businesswoman +businesswomen +busing +buskin +buskins +buss +bussed +busses +bussing +bust +busted +buster +busters +bustier +bustiest +busting +bustle +bustled +bustles +bustling +busts +busty +busy +busybodies +busybody +busying +busyness +busywork +butane +butch +butcher +butchered +butcheries +butchering +butchers +butchery +butches +butler +butlers +buts +butt +butte +butted +butter +butterball +butterballs +buttercup +buttercups +buttered +butterfat +butterfingered +butterfingers +butterflied +butterflies +butterfly +butterflying +butterier +butteries +butteriest +buttering +buttermilk +butternut +butternuts +butters +butterscotch +buttery +buttes +butting +buttock +buttocks +button +buttoned +buttonhole +buttonholed +buttonholes +buttonholing +buttoning +buttons +buttonwood +buttonwoods +buttress +buttressed +buttresses +buttressing +butts +buxom +buyback +buybacks +buyer +buyers +buying +buyout +buyouts +buys +buzz +buzzard +buzzards +buzzed +buzzer +buzzers +buzzes +buzzing +buzzword +buzzwords +byelaw +byelaws +byes +bygone +bygones +bylaw +bylaws +byline +bylines +bypass +bypassed +bypasses +bypassing +bypast +bypath +bypaths +byplay +byproduct +byproducts +byroad +byroads +bystander +bystanders +byte +bytes +byway +byways +byword +bywords +byzantine +cabal +cabala +caballero +caballeros +cabals +cabana +cabanas +cabaret +cabarets +cabbage +cabbages +cabbed +cabbie +cabbies +cabbing +cabby +cabdriver +cabdrivers +cabin +cabinet +cabinetmaker +cabinetmakers +cabinetmaking +cabinetry +cabinets +cabinetwork +cabins +cable +cablecast +cablecasted +cablecasting +cablecasts +cabled +cablegram +cablegrams +cables +cabling +cabochon +cabochons +caboodle +caboose +cabooses +cabriolet +cabriolets +cabs +cabstand +cabstands +cacao +cacaos +cache +cached +cachepot +cachepots +caches +cachet +cachets +caching +cackle +cackled +cackler +cacklers +cackles +cackling +cacophonies +cacophonous +cacophony +cacti +cactus +cactuses +cadaver +cadaverous +cadavers +caddie +caddied +caddies +caddish +caddishly +caddishness +caddy +caddying +cadence +cadenced +cadences +cadenza +cadenzas +cadet +cadets +cadge +cadged +cadger +cadgers +cadges +cadging +cadmium +cadre +cadres +cads +caducei +caduceus +caesarean +caesareans +caesarian +caesarians +caesura +caesurae +caesuras +cafe +cafes +cafeteria +cafeterias +caffeine +caftan +caftans +cage +caged +cages +cagey +cagier +cagiest +cagily +caginess +caging +cagy +cahoot +cahoots +caiman +caimans +cairn +cairns +caisson +caissons +caitiff +caitiffs +cajole +cajoled +cajolement +cajoler +cajolers +cajolery +cajoles +cajoling +cake +caked +cakes +cakewalk +cakewalks +caking +calabash +calabashes +calaboose +calabooses +calamari +calamaris +calamine +calamities +calamitous +calamitously +calamity +calcareous +calciferous +calcification +calcified +calcifies +calcify +calcifying +calcimine +calcimined +calcimines +calcimining +calcine +calcined +calcines +calcining +calcite +calcium +calculable +calculate +calculated +calculatedly +calculates +calculating +calculatingly +calculation +calculations +calculative +calculator +calculators +calculi +calculus +calculuses +caldera +calderas +caldron +caldrons +calendar +calendared +calendaring +calendars +calender +calendered +calendering +calenders +calf +calfs +calfskin +caliber +calibers +calibrate +calibrated +calibrates +calibrating +calibration +calibrations +calibrator +calibrators +calibre +calibres +calico +calicoes +calicos +calif +californium +califs +caliper +calipered +calipering +calipers +caliph +caliphate +caliphates +caliphs +calisthenic +calisthenics +calk +calked +calking +calks +call +calla +callas +callback +callbacks +called +caller +callers +calligrapher +calligraphers +calligraphic +calligraphist +calligraphists +calligraphy +calling +callings +calliope +calliopes +calliper +callipered +callipering +callipers +callisthenics +callosities +callosity +callous +calloused +callouses +callousing +callously +callousness +callow +callower +callowest +callowness +calls +callus +callused +calluses +callusing +calm +calmed +calmer +calmest +calming +calmly +calmness +calms +caloric +calorie +calories +calorific +calumet +calumets +calumniate +calumniated +calumniates +calumniating +calumniation +calumniator +calumniators +calumnies +calumnious +calumny +calve +calved +calves +calving +calyces +calypso +calypsos +calyx +calyxes +camaraderie +camber +cambered +cambering +cambers +cambia +cambial +cambium +cambiums +cambric +camcorder +camcorders +came +camel +camelhair +camellia +camellias +camels +cameo +cameos +camera +cameraman +cameramen +cameras +camerawoman +camerawomen +camisole +camisoles +camomile +camomiles +camouflage +camouflaged +camouflager +camouflagers +camouflages +camouflaging +camp +campaign +campaigned +campaigner +campaigners +campaigning +campaigns +campanile +campaniles +campanili +campanologist +campanologists +campanology +camped +camper +campers +campfire +campfires +campground +campgrounds +camphor +campier +campiest +camping +camps +campsite +campsites +campus +campuses +campy +cams +camshaft +camshafts +canal +canalization +canalize +canalized +canalizes +canalizing +canals +canape +canapes +canard +canards +canaries +canary +canasta +cancan +cancans +cancel +canceled +canceler +cancelers +canceling +cancellation +cancellations +cancelled +canceller +cancellers +cancelling +cancels +cancer +cancerous +cancers +candelabra +candelabras +candelabrum +candelabrums +candid +candidacies +candidacy +candidate +candidates +candidature +candidatures +candidly +candidness +candied +candies +candle +candled +candlelight +candlepower +candler +candlers +candles +candlestick +candlesticks +candlewick +candlewicks +candling +candor +candour +candy +candying +cane +canebrake +canebrakes +caned +caner +caners +canes +canine +canines +caning +canister +canisters +canker +cankered +cankering +cankerous +cankers +cannabis +cannabises +canned +cannelloni +canneries +cannery +cannibal +cannibalism +cannibalistic +cannibalization +cannibalize +cannibalized +cannibalizes +cannibalizing +cannibals +cannier +canniest +cannily +canniness +canning +cannon +cannonade +cannonaded +cannonades +cannonading +cannonball +cannonballs +cannoned +cannoning +cannons +cannot +canny +canoe +canoed +canoeing +canoeist +canoeists +canoes +canola +canon +canonical +canonically +canonization +canonizations +canonize +canonized +canonizes +canonizing +canons +canopied +canopies +canopy +canopying +cans +canst +cant +cantabile +cantaloup +cantaloupe +cantaloupes +cantaloups +cantankerous +cantankerously +cantankerousness +cantata +cantatas +canted +canteen +canteens +canter +cantered +cantering +canters +canticle +canticles +cantilever +cantilevered +cantilevering +cantilevers +canting +canto +canton +cantonal +cantonment +cantonments +cantons +cantor +cantors +cantos +cants +canvas +canvasback +canvasbacks +canvased +canvases +canvasing +canvass +canvassed +canvasser +canvassers +canvasses +canvassing +canyon +canyons +capabilities +capability +capable +capably +capacious +capaciously +capaciousness +capacitance +capacities +capacitor +capacitors +capacity +caparison +caparisoned +caparisoning +caparisons +cape +caped +caper +capered +capering +capers +capes +capeskin +capillaries +capillarity +capillary +capital +capitalism +capitalist +capitalistic +capitalistically +capitalists +capitalization +capitalize +capitalized +capitalizes +capitalizing +capitally +capitals +capitation +capitations +capitol +capitols +capitulate +capitulated +capitulates +capitulating +capitulation +capitulations +caplet +caplets +capo +capon +capons +capos +capped +capping +cappuccino +cappuccinos +caprice +caprices +capricious +capriciously +capriciousness +caps +capsicum +capsicums +capsize +capsized +capsizes +capsizing +capstan +capstans +capstone +capstones +capsular +capsule +capsuled +capsules +capsuling +capsulize +capsulized +capsulizes +capsulizing +captain +captaincies +captaincy +captained +captaining +captains +caption +captioned +captioning +captions +captious +captiously +captiousness +captivate +captivated +captivates +captivating +captivation +captivator +captivators +captive +captives +captivities +captivity +captor +captors +capture +captured +captures +capturing +caracul +carafe +carafes +caramel +caramelize +caramelized +caramelizes +caramelizing +caramels +carapace +carapaces +carat +carats +caravan +caravans +caravansaries +caravansary +caravanserai +caravanserais +caravel +caravels +caraway +caraways +carbide +carbides +carbine +carbines +carbohydrate +carbohydrates +carbon +carbonaceous +carbonate +carbonated +carbonates +carbonating +carbonation +carboniferous +carbonize +carbonized +carbonizes +carbonizing +carbons +carborundum +carboy +carboys +carbuncle +carbuncles +carbuncular +carburetor +carburetors +carburetter +carburetters +carburettor +carburettors +carcass +carcasses +carcinogen +carcinogenic +carcinogenicity +carcinogenics +carcinogens +carcinoma +carcinomas +carcinomata +card +cardamom +cardamoms +cardboard +carded +carder +carders +cardiac +cardigan +cardigans +cardinal +cardinally +cardinals +carding +cardiogram +cardiograms +cardiograph +cardiographs +cardiologist +cardiologists +cardiology +cardiopulmonary +cardiovascular +cards +cardsharp +cardsharper +cardsharpers +cardsharps +care +cared +careen +careened +careening +careens +career +careered +careering +careerist +careerists +careers +carefree +careful +carefuller +carefullest +carefully +carefulness +caregiver +caregivers +careless +carelessly +carelessness +carer +carers +cares +caress +caressed +caresses +caressing +caret +caretaker +caretakers +carets +careworn +carfare +cargo +cargoes +cargos +carhop +carhops +caribou +caribous +caricature +caricatured +caricatures +caricaturing +caricaturist +caricaturists +caries +carillon +carillons +caring +carious +carjack +carjacked +carjacker +carjackers +carjacking +carjackings +carjacks +carload +carloads +carmine +carnage +carnal +carnality +carnally +carnation +carnations +carnelian +carnelians +carney +carneys +carnies +carnival +carnivals +carnivore +carnivores +carnivorous +carnivorously +carnivorousness +carny +carob +carol +caroled +caroler +carolers +caroling +carolled +caroller +carollers +carolling +carols +carom +caromed +caroming +caroms +carotene +carotid +carotids +carousal +carousals +carouse +caroused +carousel +carousels +carouser +carousers +carouses +carousing +carp +carpal +carpals +carped +carpel +carpels +carpenter +carpentered +carpentering +carpenters +carpentry +carper +carpers +carpet +carpetbag +carpetbagged +carpetbagger +carpetbaggers +carpetbagging +carpetbags +carpeted +carpeting +carpets +carpi +carping +carpool +carpooled +carpooling +carpools +carport +carports +carps +carpus +carrel +carrell +carrells +carrels +carriage +carriages +carried +carrier +carriers +carries +carrion +carrot +carrots +carroty +carrousel +carrousels +carry +carryall +carryalls +carrying +carryout +carryover +carryovers +cars +carsick +carsickness +cart +cartage +carted +cartel +cartels +carter +carters +carthorse +carthorses +cartilage +cartilages +cartilaginous +carting +cartload +cartloads +cartographer +cartographers +cartographic +cartography +carton +cartons +cartoon +cartooned +cartooning +cartoonist +cartoonists +cartoons +cartridge +cartridges +carts +cartwheel +cartwheeled +cartwheeling +cartwheels +carve +carved +carver +carvers +carves +carving +carvings +caryatid +caryatides +caryatids +casaba +casabas +cascade +cascaded +cascades +cascading +cascara +cascaras +case +cased +caseharden +casehardened +casehardening +casehardens +casein +caseload +caseloads +casement +casements +cases +casework +caseworker +caseworkers +cash +cashbook +cashbooks +cashed +cashes +cashew +cashews +cashier +cashiered +cashiering +cashiers +cashing +cashless +cashmere +casing +casings +casino +casinos +cask +casket +caskets +casks +cassava +cassavas +casserole +casseroled +casseroles +casseroling +cassette +cassettes +cassia +cassias +cassino +cassock +cassocks +cassowaries +cassowary +cast +castanet +castanets +castaway +castaways +caste +castellated +caster +casters +castes +castigate +castigated +castigates +castigating +castigation +castigator +castigators +casting +castings +castle +castled +castles +castling +castoff +castoffs +castor +castors +castrate +castrated +castrates +castrating +castration +castrations +casts +casual +casually +casualness +casuals +casualties +casualty +casuist +casuistic +casuistry +casuists +cataclysm +cataclysmal +cataclysmic +cataclysms +catacomb +catacombs +catafalque +catafalques +catalepsy +cataleptic +cataleptics +catalog +cataloged +cataloger +catalogers +cataloging +catalogs +catalogue +catalogued +cataloguer +cataloguers +catalogues +cataloguing +catalpa +catalpas +catalysis +catalyst +catalysts +catalytic +catalytics +catalyze +catalyzed +catalyzes +catalyzing +catamaran +catamarans +catapult +catapulted +catapulting +catapults +cataract +cataracts +catarrh +catastrophe +catastrophes +catastrophic +catastrophically +catatonia +catatonic +catatonics +catbird +catbirds +catboat +catboats +catcall +catcalled +catcalling +catcalls +catch +catchall +catchalls +catcher +catchers +catches +catchier +catchiest +catching +catchment +catchments +catchpenny +catchphrase +catchphrases +catchup +catchword +catchwords +catchy +catechism +catechisms +catechist +catechists +catechize +catechized +catechizes +catechizing +categorical +categorically +categories +categorization +categorizations +categorize +categorized +categorizes +categorizing +category +cater +catercorner +catered +caterer +caterers +catering +caterpillar +caterpillars +caters +caterwaul +caterwauled +caterwauling +caterwauls +catfish +catfishes +catgut +catharses +catharsis +cathartic +cathartics +cathedral +cathedrals +catheter +catheterize +catheterized +catheterizes +catheterizing +catheters +cathode +cathodes +cathodic +catholic +catholicity +cation +cations +catkin +catkins +catlike +catnap +catnapped +catnapping +catnaps +catnip +cats +catsup +cattail +cattails +cattier +cattiest +cattily +cattiness +cattle +cattleman +cattlemen +catty +catwalk +catwalks +caucus +caucused +caucuses +caucusing +caucussed +caucussing +caudal +caudally +caught +cauldron +cauldrons +cauliflower +cauliflowers +caulk +caulked +caulker +caulkers +caulking +caulks +causal +causalities +causality +causally +causation +causative +cause +caused +causeless +causer +causerie +causeries +causers +causes +causeway +causeways +causing +caustic +caustically +causticity +caustics +cauterization +cauterize +cauterized +cauterizes +cauterizing +caution +cautionary +cautioned +cautioning +cautions +cautious +cautiously +cautiousness +cavalcade +cavalcades +cavalier +cavalierly +cavaliers +cavalries +cavalry +cavalryman +cavalrymen +cave +caveat +caveats +caved +caveman +cavemen +cavern +cavernous +cavernously +caverns +caves +caviar +caviare +cavil +caviled +caviler +cavilers +caviling +cavilled +caviller +cavillers +cavilling +cavils +caving +cavities +cavity +cavort +cavorted +cavorting +cavorts +cawed +cawing +caws +cayenne +cayman +caymans +cays +cayuse +cayuses +cease +ceased +ceasefire +ceasefires +ceaseless +ceaselessly +ceaselessness +ceases +ceasing +ceca +cecal +cecum +cedar +cedars +cede +ceded +ceder +ceders +cedes +cedilla +cedillas +ceding +ceiling +ceilings +celandine +celebrant +celebrants +celebrate +celebrated +celebrates +celebrating +celebration +celebrations +celebrator +celebrators +celebratory +celebrities +celebrity +celerity +celery +celesta +celestas +celestial +celestially +celibacy +celibate +celibates +cell +cellar +cellars +celled +celli +cellist +cellists +cellmate +cellmates +cello +cellophane +cellos +cellphone +cellphones +cells +cellular +cellulite +celluloid +cellulose +cement +cemented +cementer +cementers +cementing +cements +cementum +cemeteries +cemetery +cenobite +cenobites +cenobitic +cenotaph +cenotaphs +censer +censers +censor +censored +censorial +censoring +censorious +censoriously +censoriousness +censors +censorship +censurable +censure +censured +censurer +censurers +censures +censuring +census +censused +censuses +censusing +cent +centaur +centaurs +centavo +centavos +centenarian +centenarians +centenaries +centenary +centennial +centennially +centennials +center +centerboard +centerboards +centered +centerfold +centerfolds +centering +centerpiece +centerpieces +centers +centigrade +centigram +centigramme +centigrammes +centigrams +centiliter +centiliters +centime +centimes +centimeter +centimeters +centimetre +centimetres +centipede +centipedes +central +centrality +centralization +centralize +centralized +centralizer +centralizers +centralizes +centralizing +centrally +centrals +centre +centred +centres +centrifugal +centrifugally +centrifuge +centrifuged +centrifuges +centrifuging +centring +centripetal +centripetally +centrism +centrist +centrists +cents +centuries +centurion +centurions +century +cephalic +ceramic +ceramicist +ceramicists +ceramics +ceramist +ceramists +cereal +cereals +cerebella +cerebellar +cerebellum +cerebellums +cerebra +cerebral +cerebrate +cerebrated +cerebrates +cerebrating +cerebration +cerebrum +cerebrums +cerement +cerements +ceremonial +ceremonially +ceremonials +ceremonies +ceremonious +ceremoniously +ceremoniousness +ceremony +cerise +cerium +cermet +certain +certainly +certainties +certainty +certifiable +certifiably +certificate +certificated +certificates +certificating +certification +certifications +certified +certifies +certify +certifying +certitude +cerulean +cervical +cervices +cervix +cervixes +cesarean +cesareans +cesium +cessation +cessations +cession +cessions +cesspool +cesspools +cetacean +cetaceans +chafe +chafed +chafes +chaff +chaffed +chaffinch +chaffinches +chaffing +chaffs +chafing +chagrin +chagrined +chagrining +chagrinned +chagrinning +chagrins +chain +chained +chaining +chains +chainsaw +chainsawed +chainsawing +chainsaws +chair +chaired +chairing +chairlift +chairlifts +chairman +chairmanship +chairmen +chairperson +chairpersons +chairs +chairwoman +chairwomen +chaise +chaises +chalcedony +chalet +chalets +chalice +chalices +chalk +chalkboard +chalkboards +chalked +chalkier +chalkiest +chalkiness +chalking +chalks +chalky +challenge +challenged +challenger +challengers +challenges +challenging +challis +chamber +chambered +chamberlain +chamberlains +chambermaid +chambermaids +chambers +chambray +chameleon +chameleons +chammies +chammy +chamois +chamoix +chamomile +chamomiles +champ +champagne +champagnes +champed +champing +champion +championed +championing +champions +championship +championships +champs +chance +chanced +chancel +chancelleries +chancellery +chancellor +chancellors +chancellorship +chancels +chanceries +chancery +chances +chancier +chanciest +chanciness +chancing +chancre +chancres +chancy +chandelier +chandeliers +chandler +chandlers +change +changeability +changeable +changeableness +changeably +changed +changeless +changelessly +changeling +changelings +changeover +changeovers +changer +changers +changes +changing +channel +channeled +channeling +channelization +channelize +channelized +channelizes +channelizing +channelled +channelling +channels +chanson +chansons +chant +chanted +chanter +chanters +chanteuse +chanteuses +chantey +chanteys +chanticleer +chanticleers +chanties +chanting +chants +chanty +chaos +chaotic +chaotically +chap +chaparral +chaparrals +chapbook +chapbooks +chapeau +chapeaus +chapeaux +chapel +chapels +chaperon +chaperonage +chaperone +chaperoned +chaperones +chaperoning +chaperons +chaplain +chaplaincies +chaplaincy +chaplains +chaplet +chaplets +chapped +chapping +chaps +chapt +chapter +chapters +char +charabanc +charabancs +character +characteristic +characteristically +characteristics +characterization +characterizations +characterize +characterized +characterizes +characterizing +characterless +characters +charade +charades +charbroil +charbroiled +charbroiling +charbroils +charcoal +charcoals +chard +chardonnay +chardonnays +charge +chargeable +charged +charger +chargers +charges +charging +charier +chariest +charily +chariness +chariot +charioteer +charioteers +chariots +charisma +charismatic +charismatics +charitable +charitableness +charitably +charities +charity +charlatan +charlatanism +charlatanry +charlatans +charm +charmed +charmer +charmers +charming +charmingly +charms +charred +charring +chars +chart +charted +charter +chartered +charterer +charterers +chartering +charters +charting +chartreuse +charts +charwoman +charwomen +chary +chase +chased +chaser +chasers +chases +chasing +chasm +chasms +chassis +chaste +chastely +chasten +chastened +chasteness +chastening +chastens +chaster +chastest +chastise +chastised +chastisement +chastisements +chastiser +chastisers +chastises +chastising +chastity +chasuble +chasubles +chat +chateau +chateaus +chateaux +chatelaine +chatelaines +chats +chatted +chattel +chattels +chatter +chatterbox +chatterboxes +chattered +chatterer +chatterers +chattering +chatters +chattier +chattiest +chattily +chattiness +chatting +chatty +chauffeur +chauffeured +chauffeuring +chauffeurs +chauvinism +chauvinist +chauvinistic +chauvinistically +chauvinists +cheap +cheapen +cheapened +cheapening +cheapens +cheaper +cheapest +cheaply +cheapness +cheapskate +cheapskates +cheat +cheated +cheater +cheaters +cheating +cheats +check +checkbook +checkbooks +checked +checker +checkerboard +checkerboards +checkered +checkering +checkers +checking +checklist +checklists +checkmate +checkmated +checkmates +checkmating +checkoff +checkoffs +checkout +checkouts +checkpoint +checkpoints +checkroom +checkrooms +checks +checkup +checkups +cheddar +cheek +cheekbone +cheekbones +cheeked +cheekier +cheekiest +cheekily +cheekiness +cheeking +cheeks +cheeky +cheep +cheeped +cheeping +cheeps +cheer +cheered +cheerer +cheerers +cheerful +cheerfuller +cheerfullest +cheerfully +cheerfulness +cheerier +cheeriest +cheerily +cheeriness +cheering +cheerio +cheerios +cheerleader +cheerleaders +cheerless +cheerlessly +cheerlessness +cheers +cheery +cheese +cheeseburger +cheeseburgers +cheesecake +cheesecakes +cheesecloth +cheeseparing +cheeses +cheesier +cheesiest +cheesiness +cheesy +cheetah +cheetahs +chef +chefs +chemical +chemically +chemicals +chemise +chemises +chemist +chemistry +chemists +chemo +chemotherapeutic +chemotherapy +chemurgy +chenille +cheque +chequer +chequers +cheques +cherish +cherished +cherishes +cherishing +cheroot +cheroots +cherries +cherry +chert +cherub +cherubic +cherubim +cherubims +cherubs +chervil +chess +chessboard +chessboards +chessman +chessmen +chest +chested +chesterfield +chesterfields +chestful +chestfuls +chestier +chestiest +chestnut +chestnuts +chests +chesty +chevalier +chevaliers +cheviot +chevron +chevrons +chew +chewed +chewer +chewers +chewier +chewiest +chewiness +chewing +chews +chewy +chiaroscuro +chic +chicane +chicaneries +chicanery +chicanes +chicer +chicest +chichi +chichis +chick +chickadee +chickadees +chicken +chickened +chickenfeed +chickenhearted +chickening +chickenpox +chickens +chickpea +chickpeas +chicks +chickweed +chicle +chicness +chicories +chicory +chid +chidden +chide +chided +chides +chiding +chidingly +chief +chiefdom +chiefer +chiefest +chiefly +chiefs +chieftain +chieftains +chieftainship +chieftainships +chiffon +chiffonier +chiffoniers +chigger +chiggers +chignon +chignons +chihuahua +chihuahuas +chilblain +chilblains +child +childbearing +childbirth +childbirths +childcare +childhood +childhoods +childish +childishly +childishness +childless +childlessness +childlike +childproof +childproofed +childproofing +childproofs +children +chile +chiles +chili +chilies +chill +chilled +chiller +chillers +chillest +chilli +chillier +chillies +chilliest +chilliness +chilling +chillingly +chillness +chills +chilly +chimaera +chimaeras +chime +chimed +chimer +chimera +chimeras +chimeric +chimerical +chimers +chimes +chiming +chimney +chimneys +chimp +chimpanzee +chimpanzees +chimps +chin +china +chinaware +chinchilla +chinchillas +chine +chines +chink +chinked +chinking +chinks +chinned +chinning +chino +chinos +chins +chinstrap +chinstraps +chintz +chintzier +chintziest +chintzy +chip +chipmunk +chipmunks +chipped +chipper +chippers +chipping +chips +chirography +chiropodist +chiropodists +chiropody +chiropractic +chiropractics +chiropractor +chiropractors +chirp +chirped +chirpier +chirpiest +chirping +chirps +chirpy +chirrup +chirruped +chirruping +chirrupped +chirrupping +chirrups +chis +chisel +chiseled +chiseler +chiselers +chiseling +chiselled +chiseller +chisellers +chiselling +chisels +chit +chitchat +chitchats +chitchatted +chitchatting +chitin +chitinous +chitlings +chitlins +chits +chitterlings +chivalrous +chivalrously +chivalrousness +chivalry +chive +chives +chlamydia +chlamydiae +chlamydias +chloral +chlordane +chloride +chlorides +chlorinate +chlorinated +chlorinates +chlorinating +chlorination +chlorine +chlorofluorocarbon +chlorofluorocarbons +chloroform +chloroformed +chloroforming +chloroforms +chlorophyl +chlorophyll +chloroplast +chloroplasts +chocaholic +chocaholics +chock +chockablock +chocked +chocking +chocks +chocoholic +chocoholics +chocolate +chocolates +chocolatey +chocolaty +choice +choicer +choices +choicest +choir +choirboy +choirboys +choirmaster +choirmasters +choirs +choke +chokecherries +chokecherry +choked +choker +chokers +chokes +choking +choler +cholera +choleric +cholesterol +chomp +chomped +chomping +chomps +choose +chooser +choosers +chooses +choosey +choosier +choosiest +choosiness +choosing +choosy +chop +chophouse +chophouses +chopped +chopper +choppered +choppering +choppers +choppier +choppiest +choppily +choppiness +chopping +choppy +chops +chopstick +chopsticks +choral +chorale +chorales +chorally +chorals +chord +chordal +chordate +chordates +chords +chore +chorea +choreograph +choreographed +choreographer +choreographers +choreographic +choreographically +choreographing +choreographs +choreography +chores +chorister +choristers +choroid +choroids +chortle +chortled +chortler +chortlers +chortles +chortling +chorus +chorused +choruses +chorusing +chorussed +chorussing +chose +chosen +chow +chowder +chowders +chowed +chowing +chows +chrism +christen +christened +christening +christenings +christens +chromatic +chromatically +chromatin +chrome +chromed +chromes +chroming +chromium +chromosomal +chromosome +chromosomes +chronic +chronically +chronicle +chronicled +chronicler +chroniclers +chronicles +chronicling +chronograph +chronographs +chronological +chronologically +chronologies +chronologist +chronologists +chronology +chronometer +chronometers +chrysalides +chrysalis +chrysalises +chrysanthemum +chrysanthemums +chub +chubbier +chubbiest +chubbiness +chubby +chubs +chuck +chucked +chuckhole +chuckholes +chucking +chuckle +chuckled +chuckles +chuckling +chucks +chug +chugged +chugging +chugs +chukka +chukkas +chum +chummed +chummier +chummiest +chummily +chumminess +chumming +chummy +chump +chumps +chums +chunk +chunkier +chunkiest +chunkiness +chunks +chunky +church +churches +churchgoer +churchgoers +churchgoing +churchman +churchmen +churchwarden +churchwardens +churchyard +churchyards +churl +churlish +churlishly +churlishness +churls +churn +churned +churner +churners +churning +churns +chute +chutes +chutney +chutzpa +chutzpah +chyme +ciao +cicada +cicadae +cicadas +cicatrice +cicatrices +cicatrix +cicatrixes +cicerone +cicerones +ciceroni +cider +ciders +cigar +cigaret +cigarets +cigarette +cigarettes +cigarillo +cigarillos +cigars +cilantro +cilia +cilium +cinch +cinched +cinches +cinching +cinchona +cinchonas +cincture +cinctures +cinder +cindered +cindering +cinders +cinema +cinemas +cinematic +cinematographer +cinematographers +cinematographic +cinematography +cinnabar +cinnamon +cipher +ciphered +ciphering +ciphers +circa +circadian +circle +circled +circles +circlet +circlets +circling +circuit +circuital +circuited +circuiting +circuitous +circuitously +circuitousness +circuitry +circuits +circuity +circular +circularity +circularize +circularized +circularizes +circularizing +circularly +circulars +circulate +circulated +circulates +circulating +circulation +circulations +circulatory +circumcise +circumcised +circumcises +circumcising +circumcision +circumcisions +circumference +circumferences +circumferential +circumflex +circumflexes +circumlocution +circumlocutions +circumnavigate +circumnavigated +circumnavigates +circumnavigating +circumnavigation +circumnavigations +circumpolar +circumscribe +circumscribed +circumscribes +circumscribing +circumscription +circumscriptions +circumspect +circumspection +circumspectly +circumstance +circumstanced +circumstances +circumstancing +circumstantial +circumstantially +circumvent +circumvented +circumventing +circumvention +circumvents +circus +circuses +cirque +cirques +cirrhosis +cirrhotic +cirrhotics +cirrus +cistern +cisterns +citadel +citadels +citation +citations +cite +cited +cites +cities +citified +citing +citizen +citizenry +citizens +citizenship +citric +citron +citronella +citrons +citrous +citrus +citruses +city +civet +civets +civic +civically +civics +civies +civil +civilian +civilians +civilisation +civilisations +civilise +civilised +civilises +civilising +civilities +civility +civilization +civilizations +civilize +civilized +civilizes +civilizing +civilly +civvies +clack +clacked +clacking +clacks +clad +cladding +claim +claimable +claimant +claimants +claimed +claimer +claimers +claiming +claims +clairvoyance +clairvoyant +clairvoyants +clam +clambake +clambakes +clamber +clambered +clamberer +clamberers +clambering +clambers +clammed +clammier +clammiest +clammily +clamminess +clamming +clammy +clamor +clamored +clamoring +clamorous +clamors +clamour +clamoured +clamouring +clamours +clamp +clampdown +clampdowns +clamped +clamping +clamps +clams +clan +clandestine +clandestinely +clang +clanged +clanging +clangor +clangorous +clangorously +clangour +clangs +clank +clanked +clanking +clanks +clannish +clannishness +clans +clansman +clansmen +clap +clapboard +clapboarded +clapboarding +clapboards +clapped +clapper +clappers +clapping +claps +claptrap +claque +claques +claret +clarets +clarification +clarifications +clarified +clarifies +clarify +clarifying +clarinet +clarinetist +clarinetists +clarinets +clarinettist +clarinettists +clarion +clarions +clarity +clash +clashed +clashes +clashing +clasp +clasped +clasping +clasps +class +classed +classes +classic +classical +classically +classicism +classicist +classicists +classics +classier +classiest +classifiable +classification +classifications +classified +classifieds +classifier +classifiers +classifies +classify +classifying +classiness +classing +classless +classmate +classmates +classroom +classrooms +classwork +classy +clatter +clattered +clattering +clatters +clausal +clause +clauses +claustrophobia +claustrophobic +clavichord +clavichords +clavicle +clavicles +clavier +claviers +claw +clawed +clawing +claws +clay +clayey +clayier +clayiest +clean +cleanable +cleaned +cleaner +cleaners +cleanest +cleaning +cleanings +cleanlier +cleanliest +cleanliness +cleanly +cleanness +cleans +cleanse +cleansed +cleanser +cleansers +cleanses +cleansing +cleanup +cleanups +clear +clearance +clearances +cleared +clearer +clearest +clearheaded +clearing +clearinghouse +clearinghouses +clearings +clearly +clearness +clears +cleat +cleats +cleavage +cleavages +cleave +cleaved +cleaver +cleavers +cleaves +cleaving +clef +clefs +cleft +clefts +clematis +clematises +clemency +clement +clemently +clench +clenched +clenches +clenching +clerestories +clerestory +clergies +clergy +clergyman +clergymen +clergywoman +clergywomen +cleric +clerical +clericalism +clerically +clerics +clerk +clerked +clerking +clerks +clerkship +clever +cleverer +cleverest +cleverly +cleverness +clevis +clevises +clew +clewed +clewing +clews +cliche +cliched +cliches +click +clicked +clicker +clickers +clicking +clicks +client +clientele +clienteles +clients +cliff +cliffhanger +cliffhangers +cliffs +climacteric +climactic +climate +climates +climatic +climatically +climatologist +climatologists +climatology +climax +climaxed +climaxes +climaxing +climb +climbable +climbed +climber +climbers +climbing +climbs +clime +climes +clinch +clinched +clincher +clinchers +clinches +clinching +cling +clinger +clingers +clingier +clingiest +clinging +clings +clingy +clinic +clinical +clinically +clinician +clinicians +clinics +clink +clinked +clinker +clinkers +clinking +clinks +cliometric +cliometrician +cliometricians +cliometrics +clip +clipboard +clipboards +clipped +clipper +clippers +clipping +clippings +clips +clipt +clique +cliques +cliquey +cliquier +cliquiest +cliquish +cliquishly +cliquishness +clitoral +clitorides +clitoris +clitorises +cloaca +cloacae +cloacas +cloak +cloaked +cloaking +cloakroom +cloakrooms +cloaks +clobber +clobbered +clobbering +clobbers +cloche +cloches +clock +clocked +clocking +clocks +clockwise +clockwork +clockworks +clod +cloddish +clodhopper +clodhoppers +clods +clog +clogged +clogging +clogs +cloisonne +cloister +cloistered +cloistering +cloisters +cloistral +clomp +clomped +clomping +clomps +clonal +clone +cloned +clones +cloning +clonk +clonked +clonking +clonks +clop +clopped +clopping +clops +close +closed +closefisted +closely +closemouthed +closeness +closeout +closeouts +closer +closes +closest +closet +closeted +closeting +closets +closeup +closeups +closing +closings +closure +closures +clot +cloth +clothe +clothed +clothes +clotheshorse +clotheshorses +clothesline +clotheslines +clothespin +clothespins +clothier +clothiers +clothing +cloths +clots +clotted +clotting +cloture +clotures +cloud +cloudburst +cloudbursts +clouded +cloudier +cloudiest +cloudiness +clouding +cloudless +clouds +cloudy +clout +clouted +clouting +clouts +clove +cloven +clover +cloverleaf +cloverleafs +cloverleaves +clovers +cloves +clown +clowned +clowning +clownish +clownishly +clownishness +clowns +cloy +cloyed +cloying +cloys +club +clubbed +clubbing +clubfeet +clubfoot +clubfooted +clubhouse +clubhouses +clubs +cluck +clucked +clucking +clucks +clue +clued +clueing +clueless +clues +cluing +clump +clumped +clumpier +clumpiest +clumping +clumps +clumpy +clumsier +clumsiest +clumsily +clumsiness +clumsy +clung +clunk +clunked +clunker +clunkers +clunkier +clunkiest +clunking +clunks +clunky +cluster +clustered +clustering +clusters +clutch +clutched +clutches +clutching +clutter +cluttered +cluttering +clutters +cnidarian +cnidarians +coach +coached +coaches +coaching +coachman +coachmen +coadjutor +coadjutors +coagulant +coagulants +coagulate +coagulated +coagulates +coagulating +coagulation +coagulator +coagulators +coal +coaled +coalesce +coalesced +coalescence +coalescent +coalesces +coalescing +coalface +coalfaces +coaling +coalition +coalitionist +coalitionists +coalitions +coals +coarse +coarsely +coarsen +coarsened +coarseness +coarsening +coarsens +coarser +coarsest +coast +coastal +coasted +coaster +coasters +coasting +coastline +coastlines +coasts +coat +coated +coating +coatings +coats +coattail +coattails +coauthor +coauthored +coauthoring +coauthors +coax +coaxed +coaxer +coaxers +coaxes +coaxial +coaxing +coaxingly +cobalt +cobble +cobbled +cobbler +cobblers +cobbles +cobblestone +cobblestones +cobbling +cobra +cobras +cobs +cobweb +cobwebbier +cobwebbiest +cobwebby +cobwebs +coca +cocain +cocaine +cocci +coccus +coccyges +coccyx +coccyxes +cochineal +cochlea +cochleae +cochlear +cochleas +cock +cockade +cockades +cockamamie +cockatoo +cockatoos +cockatrice +cockatrices +cockcrow +cockcrows +cocked +cockerel +cockerels +cockeyed +cockfight +cockfighting +cockfights +cockier +cockiest +cockily +cockiness +cocking +cockle +cockles +cockleshell +cockleshells +cockney +cockneys +cockpit +cockpits +cockroach +cockroaches +cocks +cockscomb +cockscombs +cocksucker +cocksuckers +cocksure +cocktail +cocktails +cocky +coco +cocoa +cocoanut +cocoanuts +cocoas +coconut +coconuts +cocoon +cocooned +cocooning +cocoons +cocos +coda +codas +coddle +coddled +coddles +coddling +code +coded +codeine +codependency +codependent +codependents +coder +coders +codes +codex +codfish +codfishes +codger +codgers +codices +codicil +codicils +codification +codifications +codified +codifier +codifiers +codifies +codify +codifying +coding +codpiece +codpieces +cods +coed +coeds +coeducation +coeducational +coefficient +coefficients +coelenterate +coelenterates +coequal +coequally +coequals +coerce +coerced +coercer +coercers +coerces +coercing +coercion +coercive +coeval +coevally +coevals +coexist +coexisted +coexistence +coexistent +coexisting +coexists +coextensive +coffee +coffeecake +coffeecakes +coffeehouse +coffeehouses +coffeemaker +coffeemakers +coffeepot +coffeepots +coffees +coffer +cofferdam +cofferdams +coffers +coffin +coffined +coffining +coffins +cogency +cogent +cogently +cogitate +cogitated +cogitates +cogitating +cogitation +cogitative +cogitator +cogitators +cognac +cognacs +cognate +cognates +cognition +cognitional +cognitive +cognitively +cognizable +cognizance +cognizant +cognomen +cognomens +cognomina +cognoscente +cognoscenti +cogs +cogwheel +cogwheels +cohabit +cohabitant +cohabitants +cohabitation +cohabited +cohabiting +cohabits +coheir +coheirs +cohere +cohered +coherence +coherency +coherent +coherently +coheres +cohering +cohesion +cohesive +cohesively +cohesiveness +coho +cohort +cohorts +cohos +coif +coifed +coiffed +coiffing +coiffure +coiffured +coiffures +coiffuring +coifing +coifs +coil +coiled +coiling +coils +coin +coinage +coinages +coincide +coincided +coincidence +coincidences +coincident +coincidental +coincidentally +coincides +coinciding +coined +coiner +coiners +coining +coins +coinsurance +coital +coitus +coke +coked +cokes +coking +cola +colander +colanders +colas +cold +coldblooded +colder +coldest +coldly +coldness +colds +coleslaw +coleus +coleuses +colic +colicky +coliseum +coliseums +colitis +collaborate +collaborated +collaborates +collaborating +collaboration +collaborations +collaborative +collaborator +collaborators +collage +collages +collapse +collapsed +collapses +collapsible +collapsing +collar +collarbone +collarbones +collard +collards +collared +collaring +collarless +collars +collate +collated +collateral +collateralize +collateralized +collateralizes +collateralizing +collaterally +collates +collating +collation +collations +collator +collators +colleague +colleagues +collect +collectable +collectables +collected +collectedly +collectible +collectibles +collecting +collection +collections +collective +collectively +collectives +collectivism +collectivist +collectivists +collectivization +collectivize +collectivized +collectivizes +collectivizing +collector +collectors +collects +colleen +colleens +college +colleges +collegiality +collegian +collegians +collegiate +collide +collided +collides +colliding +collie +collier +collieries +colliers +colliery +collies +collision +collisions +collocate +collocated +collocates +collocating +collocation +collocations +colloid +colloidal +colloids +colloquia +colloquial +colloquialism +colloquialisms +colloquially +colloquies +colloquium +colloquiums +colloquy +collude +colluded +colludes +colluding +collusion +collusive +cologne +colognes +colon +colonel +colonelcy +colonels +colones +colonial +colonialism +colonialist +colonialists +colonially +colonials +colonies +colonist +colonists +colonization +colonize +colonized +colonizer +colonizers +colonizes +colonizing +colonnade +colonnaded +colonnades +colons +colony +colophon +colophons +color +colorant +colorants +coloration +coloratura +coloraturas +colorblind +colorblindness +colored +coloreds +colorfast +colorfastness +colorful +colorfully +colorfulness +coloring +colorization +colorize +colorized +colorizes +colorizing +colorless +colorlessly +colorlessness +colors +colossal +colossally +colossi +colossus +colossuses +colostomies +colostomy +colostrum +colour +coloured +colouring +colours +colt +coltish +colts +columbine +columbines +column +columnar +columned +columnist +columnists +columns +coma +comaker +comakers +comas +comatose +comb +combat +combatant +combatants +combated +combating +combative +combativeness +combats +combatted +combatting +combed +comber +combers +combination +combinations +combine +combined +combiner +combiners +combines +combing +combings +combining +combo +combos +combs +combustibility +combustible +combustibles +combustion +combustive +come +comeback +comebacks +comedian +comedians +comedic +comedienne +comediennes +comedies +comedown +comedowns +comedy +comelier +comeliest +comeliness +comely +comer +comers +comes +comestible +comestibles +comet +comets +comeuppance +comeuppances +comfier +comfiest +comfit +comfits +comfort +comfortable +comfortableness +comfortably +comforted +comforter +comforters +comforting +comfortingly +comforts +comfy +comic +comical +comicality +comically +comics +coming +comings +comity +comma +command +commandant +commandants +commanded +commandeer +commandeered +commandeering +commandeers +commander +commanders +commanding +commandment +commandments +commando +commandoes +commandos +commands +commas +commemorate +commemorated +commemorates +commemorating +commemoration +commemorations +commemorative +commemorator +commemorators +commence +commenced +commencement +commencements +commences +commencing +commend +commendable +commendably +commendation +commendations +commendatory +commended +commending +commends +commensurable +commensurate +commensurately +comment +commentaries +commentary +commentate +commentated +commentates +commentating +commentator +commentators +commented +commenting +comments +commerce +commercial +commercialism +commercialization +commercialize +commercialized +commercializes +commercializing +commercially +commercials +commie +commies +commingle +commingled +commingles +commingling +commiserate +commiserated +commiserates +commiserating +commiseration +commiserations +commiserative +commissar +commissariat +commissariats +commissaries +commissars +commissary +commission +commissioned +commissioner +commissioners +commissioning +commissions +commit +commitment +commitments +commits +committal +committals +committed +committee +committeeman +committeemen +committees +committeewoman +committeewomen +committing +commode +commodes +commodious +commodiously +commodities +commodity +commodore +commodores +common +commonalty +commoner +commoners +commonest +commonly +commonness +commonplace +commonplaces +commons +commonsense +commonweal +commonwealth +commonwealths +commotion +commotions +communal +communally +commune +communed +communes +communicability +communicable +communicably +communicant +communicants +communicate +communicated +communicates +communicating +communication +communications +communicative +communicator +communicators +communing +communion +communions +communique +communiques +communism +communist +communistic +communists +communities +community +commutable +commutation +commutations +commutative +commutator +commutators +commute +commuted +commuter +commuters +commutes +commuting +comp +compact +compacted +compacter +compactest +compacting +compactly +compactness +compactor +compactors +compacts +companies +companion +companionable +companionably +companions +companionship +companionway +companionways +company +comparability +comparable +comparably +comparative +comparatively +comparatives +compare +compared +compares +comparing +comparison +comparisons +compartment +compartmental +compartmentalization +compartmentalize +compartmentalized +compartmentalizes +compartmentalizing +compartments +compass +compassed +compasses +compassing +compassion +compassionate +compassionately +compatibility +compatible +compatibles +compatibly +compatriot +compatriots +comped +compeer +compeers +compel +compelled +compelling +compellingly +compels +compendia +compendious +compendium +compendiums +compensate +compensated +compensates +compensating +compensation +compensations +compensatory +compete +competed +competence +competences +competencies +competency +competent +competently +competes +competing +competition +competitions +competitive +competitively +competitiveness +competitor +competitors +compilation +compilations +compile +compiled +compiler +compilers +compiles +compiling +comping +complacence +complacency +complacent +complacently +complain +complainant +complainants +complained +complainer +complainers +complaining +complains +complaint +complaints +complaisance +complaisant +complaisantly +complected +complement +complementary +complemented +complementing +complements +complete +completed +completely +completeness +completer +completes +completest +completing +completion +complex +complexes +complexion +complexional +complexioned +complexions +complexities +complexity +complexly +compliance +compliant +compliantly +complicate +complicated +complicatedly +complicates +complicating +complication +complications +complicit +complicity +complied +complies +compliment +complimentary +complimented +complimenting +compliments +comply +complying +component +components +comport +comported +comporting +comportment +comports +compose +composed +composedly +composer +composers +composes +composing +composite +compositely +composites +composition +compositions +compositor +compositors +compost +composted +composting +composts +composure +compote +compotes +compound +compoundable +compounded +compounding +compounds +comprehend +comprehended +comprehending +comprehends +comprehensibility +comprehensible +comprehensibly +comprehension +comprehensions +comprehensive +comprehensively +comprehensiveness +comprehensives +compress +compressed +compresses +compressible +compressing +compression +compressor +compressors +comprise +comprised +comprises +comprising +compromise +compromised +compromises +compromising +comps +comptroller +comptrollers +compulsion +compulsions +compulsive +compulsively +compulsiveness +compulsories +compulsorily +compulsory +compunction +compunctions +computation +computational +computations +compute +computed +computer +computerization +computerize +computerized +computerizes +computerizing +computers +computes +computing +comrade +comradely +comrades +comradeship +concatenate +concatenated +concatenates +concatenating +concatenation +concatenations +concave +concavely +concaveness +concavities +concavity +conceal +concealable +concealed +concealer +concealers +concealing +concealment +conceals +concede +conceded +concedes +conceding +conceit +conceited +conceitedly +conceitedness +conceits +conceivable +conceivably +conceive +conceived +conceives +conceiving +concentrate +concentrated +concentrates +concentrating +concentration +concentrations +concentric +concentrically +concept +conception +conceptional +conceptions +concepts +conceptual +conceptualization +conceptualizations +conceptualize +conceptualized +conceptualizes +conceptualizing +conceptually +concern +concerned +concerning +concerns +concert +concerted +concertedly +concerti +concertina +concertinaed +concertinaing +concertinas +concerting +concertize +concertized +concertizes +concertizing +concertmaster +concertmasters +concerto +concertos +concerts +concession +concessionaire +concessionaires +concessional +concessionary +concessions +conch +conches +conchs +concierge +concierges +conciliate +conciliated +conciliates +conciliating +conciliation +conciliator +conciliators +conciliatory +concise +concisely +conciseness +conciser +concisest +concision +conclave +conclaves +conclude +concluded +concludes +concluding +conclusion +conclusions +conclusive +conclusively +conclusiveness +concoct +concocted +concocting +concoction +concoctions +concocts +concomitant +concomitantly +concomitants +concord +concordance +concordances +concordant +concordat +concordats +concourse +concourses +concrete +concreted +concretely +concreteness +concretes +concreting +concretion +concretions +concubinage +concubine +concubines +concupiscence +concupiscent +concur +concurred +concurrence +concurrences +concurrent +concurrently +concurring +concurs +concuss +concussed +concusses +concussing +concussion +concussions +concussive +condemn +condemnation +condemnations +condemnatory +condemned +condemner +condemners +condemning +condemns +condensate +condensates +condensation +condensations +condense +condensed +condenser +condensers +condenses +condensing +condescend +condescended +condescending +condescendingly +condescends +condescension +condign +condiment +condiments +condition +conditional +conditionally +conditionals +conditioned +conditioner +conditioners +conditioning +conditions +condo +condoes +condole +condoled +condolence +condolences +condoles +condoling +condom +condominium +condominiums +condoms +condone +condoned +condones +condoning +condor +condors +condos +conduce +conduced +conduces +conducing +conducive +conduct +conductance +conducted +conductibility +conductible +conducting +conduction +conductive +conductivity +conductor +conductors +conductress +conductresses +conducts +conduit +conduits +cone +cones +coney +coneys +confab +confabbed +confabbing +confabs +confabulate +confabulated +confabulates +confabulating +confabulation +confabulations +confection +confectioner +confectioneries +confectioners +confectionery +confections +confederacies +confederacy +confederate +confederated +confederates +confederating +confederation +confederations +confer +conferee +conferees +conference +conferences +conferment +conferments +conferrable +conferral +conferred +conferrer +conferrers +conferring +confers +confess +confessed +confessedly +confesses +confessing +confession +confessional +confessionals +confessions +confessor +confessors +confetti +confidant +confidante +confidantes +confidants +confide +confided +confidence +confidences +confident +confidential +confidentiality +confidentially +confidently +confider +confiders +confides +confiding +configuration +configurations +configure +configured +configures +configuring +confine +confined +confinement +confinements +confines +confining +confirm +confirmation +confirmations +confirmed +confirming +confirms +confiscate +confiscated +confiscates +confiscating +confiscation +confiscations +confiscator +confiscators +confiscatory +conflagration +conflagrations +conflate +conflated +conflates +conflating +conflation +conflations +conflict +conflicted +conflicting +conflicts +confluence +confluences +confluent +conform +conformable +conformance +conformation +conformations +conformed +conformer +conformers +conforming +conformism +conformist +conformists +conformity +conforms +confound +confounded +confounding +confounds +confraternities +confraternity +confrere +confreres +confront +confrontation +confrontational +confrontations +confronted +confronting +confronts +confuse +confused +confusedly +confuses +confusing +confusingly +confusion +confutation +confute +confuted +confutes +confuting +conga +congaed +congaing +congas +congeal +congealed +congealing +congealment +congeals +congenial +congeniality +congenially +congenital +congenitally +conger +congeries +congers +congest +congested +congesting +congestion +congestive +congests +conglomerate +conglomerated +conglomerates +conglomerating +conglomeration +conglomerations +congrats +congratulate +congratulated +congratulates +congratulating +congratulation +congratulations +congratulatory +congregant +congregants +congregate +congregated +congregates +congregating +congregation +congregational +congregationalism +congregationalist +congregationalists +congregations +congress +congresses +congressional +congressman +congressmen +congressperson +congresspersons +congresswoman +congresswomen +congruence +congruent +congruently +congruities +congruity +congruous +conic +conical +conically +conics +conies +conifer +coniferous +conifers +conjectural +conjecture +conjectured +conjectures +conjecturing +conjoin +conjoined +conjoiner +conjoiners +conjoining +conjoins +conjoint +conjointly +conjugal +conjugally +conjugate +conjugated +conjugates +conjugating +conjugation +conjugations +conjunct +conjunction +conjunctions +conjunctiva +conjunctivae +conjunctivas +conjunctive +conjunctives +conjunctivitis +conjuncts +conjuncture +conjunctures +conjuration +conjurations +conjure +conjured +conjurer +conjurers +conjures +conjuring +conjuror +conjurors +conk +conked +conking +conks +conman +conmen +connect +connectable +connected +connecter +connecters +connectible +connecting +connection +connections +connective +connectives +connectivity +connector +connectors +connects +conned +connexion +connexions +conning +conniption +conniptions +connivance +connive +connived +conniver +connivers +connives +conniving +connoisseur +connoisseurs +connotation +connotations +connotative +connote +connoted +connotes +connoting +connubial +conquer +conquerable +conquered +conquering +conqueror +conquerors +conquers +conquest +conquests +conquistador +conquistadores +conquistadors +cons +consanguineous +consanguinity +conscience +conscienceless +consciences +conscientious +conscientiously +conscientiousness +conscious +consciously +consciousness +consciousnesses +conscript +conscripted +conscripting +conscription +conscripts +consecrate +consecrated +consecrates +consecrating +consecration +consecrations +consecutive +consecutively +consensual +consensus +consensuses +consent +consented +consenting +consents +consequence +consequences +consequent +consequential +consequentially +consequently +conservancies +conservancy +conservation +conservationism +conservationist +conservationists +conservatism +conservative +conservatively +conservatives +conservator +conservatories +conservators +conservatory +conserve +conserved +conserves +conserving +consider +considerable +considerably +considerate +considerately +considerateness +consideration +considerations +considered +considering +considers +consign +consigned +consignee +consignees +consigning +consignment +consignments +consignor +consignors +consigns +consist +consisted +consistence +consistences +consistencies +consistency +consistent +consistently +consisting +consistories +consistory +consists +consolable +consolation +consolations +consolatory +console +consoled +consoles +consolidate +consolidated +consolidates +consolidating +consolidation +consolidations +consolidator +consolidators +consoling +consolingly +consomme +consonance +consonances +consonant +consonantly +consonants +consort +consorted +consortia +consorting +consortium +consortiums +consorts +conspectus +conspectuses +conspicuous +conspicuously +conspicuousness +conspiracies +conspiracy +conspirator +conspiratorial +conspiratorially +conspirators +conspire +conspired +conspires +conspiring +constable +constables +constabularies +constabulary +constancy +constant +constantly +constants +constellation +constellations +consternation +constipate +constipated +constipates +constipating +constipation +constituencies +constituency +constituent +constituents +constitute +constituted +constitutes +constituting +constitution +constitutional +constitutionality +constitutionally +constitutionals +constitutions +constitutive +constrain +constrained +constraining +constrains +constraint +constraints +constrict +constricted +constricting +constriction +constrictions +constrictive +constrictor +constrictors +constricts +construable +construct +constructed +constructing +construction +constructional +constructionist +constructionists +constructions +constructive +constructively +constructiveness +constructor +constructors +constructs +construe +construed +construes +construing +consubstantiation +consul +consular +consulate +consulates +consuls +consulship +consult +consultancies +consultancy +consultant +consultants +consultation +consultations +consultative +consulted +consulting +consults +consumable +consumables +consume +consumed +consumer +consumerism +consumerist +consumerists +consumers +consumes +consuming +consummate +consummated +consummately +consummates +consummating +consummation +consummations +consumption +consumptive +consumptives +contact +contacted +contacting +contacts +contagion +contagions +contagious +contagiously +contagiousness +contain +containable +contained +container +containerization +containerize +containerized +containerizes +containerizing +containers +containing +containment +contains +contaminant +contaminants +contaminate +contaminated +contaminates +contaminating +contamination +contaminator +contaminators +contemn +contemned +contemning +contemns +contemplate +contemplated +contemplates +contemplating +contemplation +contemplative +contemplatives +contemporaneity +contemporaneous +contemporaneously +contemporaries +contemporary +contempt +contemptible +contemptibly +contemptuous +contemptuously +contemptuousness +contend +contended +contender +contenders +contending +contends +content +contented +contentedly +contentedness +contenting +contention +contentions +contentious +contentiously +contentiousness +contently +contentment +contents +conterminous +conterminously +contest +contestable +contestant +contestants +contested +contesting +contests +context +contexts +contextual +contextualize +contextualized +contextualizes +contextualizing +contextually +contiguity +contiguous +contiguously +continence +continent +continental +continentals +continents +contingencies +contingency +contingent +contingently +contingents +continua +continual +continually +continuance +continuances +continuation +continuations +continue +continued +continues +continuing +continuity +continuous +continuously +continuum +continuums +contort +contorted +contorting +contortion +contortionist +contortionists +contortions +contorts +contour +contoured +contouring +contours +contraband +contraception +contraceptive +contraceptives +contract +contracted +contractible +contractile +contracting +contraction +contractions +contractor +contractors +contracts +contractual +contractually +contradict +contradicted +contradicting +contradiction +contradictions +contradictory +contradicts +contradistinction +contradistinctions +contrail +contrails +contraindicate +contraindicated +contraindicates +contraindicating +contraindication +contraindications +contralto +contraltos +contraption +contraptions +contrapuntal +contrapuntally +contraries +contrariety +contrarily +contrariness +contrariwise +contrary +contrast +contrasted +contrasting +contrasts +contravene +contravened +contravenes +contravening +contravention +contraventions +contretemps +contribute +contributed +contributes +contributing +contribution +contributions +contributor +contributors +contributory +contrite +contritely +contriteness +contrition +contrivance +contrivances +contrive +contrived +contriver +contrivers +contrives +contriving +control +controllable +controlled +controller +controllers +controlling +controls +controversial +controversially +controversies +controversy +controvert +controverted +controvertible +controverting +controverts +contumacious +contumaciously +contumacy +contumelies +contumelious +contumely +contuse +contused +contuses +contusing +contusion +contusions +conundrum +conundrums +conurbation +conurbations +convalesce +convalesced +convalescence +convalescences +convalescent +convalescents +convalesces +convalescing +convection +convectional +convective +convene +convened +convener +conveners +convenes +convenience +conveniences +convenient +conveniently +convening +convent +conventicle +conventicles +convention +conventional +conventionality +conventionalize +conventionalized +conventionalizes +conventionalizing +conventionally +conventions +convents +converge +converged +convergence +convergences +convergent +converges +converging +conversant +conversation +conversational +conversationalist +conversationalists +conversationally +conversations +converse +conversed +conversely +converses +conversing +conversion +conversions +convert +converted +converter +converters +convertibility +convertible +convertibles +converting +convertor +convertors +converts +convex +convexity +convexly +convey +conveyable +conveyance +conveyances +conveyed +conveyer +conveyers +conveying +conveyor +conveyors +conveys +convict +convicted +convicting +conviction +convictions +convicts +convince +convinced +convinces +convincing +convincingly +convivial +conviviality +convivially +convocation +convocations +convoke +convoked +convokes +convoking +convoluted +convolution +convolutions +convoy +convoyed +convoying +convoys +convulse +convulsed +convulses +convulsing +convulsion +convulsions +convulsive +convulsively +cony +cooed +cooing +cook +cookbook +cookbooks +cooked +cooker +cookeries +cookers +cookery +cookie +cookies +cooking +cookout +cookouts +cooks +cookware +cookwares +cooky +cool +coolant +coolants +cooled +cooler +coolers +coolest +coolie +coolies +cooling +coolly +coolness +cools +coon +coons +coonskin +coonskins +coop +cooped +cooper +cooperage +cooperate +cooperated +cooperates +cooperating +cooperation +cooperative +cooperatively +cooperativeness +cooperatives +cooperator +cooperators +coopered +coopering +coopers +cooping +coops +coordinate +coordinated +coordinately +coordinates +coordinating +coordination +coordinator +coordinators +coos +coot +cootie +cooties +coots +copacetic +copay +cope +coped +copes +copied +copier +copiers +copies +copilot +copilots +coping +copings +copious +copiously +copiousness +copped +copper +copperhead +copperheads +copperplate +coppers +coppery +coppice +coppices +copping +copra +cops +copse +copses +copter +copters +copula +copulae +copulas +copulate +copulated +copulates +copulating +copulation +copulative +copulatives +copy +copybook +copybooks +copycat +copycats +copycatted +copycatting +copying +copyist +copyists +copyright +copyrighted +copyrighting +copyrights +copywriter +copywriters +coquetries +coquetry +coquette +coquetted +coquettes +coquetting +coquettish +coquettishly +coracle +coracles +coral +corals +corbel +corbels +cord +cordage +corded +cordial +cordiality +cordially +cordials +cordillera +cordilleras +cording +cordite +cordless +cordon +cordoned +cordoning +cordons +cordovan +cords +corduroy +corduroys +core +cored +corer +corers +cores +corespondent +corespondents +corgi +corgis +coriander +coring +cork +corked +corker +corkers +corking +corks +corkscrew +corkscrewed +corkscrewing +corkscrews +corm +cormorant +cormorants +corms +corn +cornball +cornballs +cornbread +corncob +corncobs +cornea +corneal +corneas +corned +corner +cornered +cornering +corners +cornerstone +cornerstones +cornet +cornets +cornflakes +cornflower +cornflowers +cornice +cornices +cornier +corniest +cornily +corniness +corning +cornmeal +cornrow +cornrowed +cornrowing +cornrows +corns +cornstalk +cornstalks +cornstarch +cornucopia +cornucopias +corny +corolla +corollaries +corollary +corollas +corona +coronae +coronal +coronals +coronaries +coronary +coronas +coronation +coronations +coroner +coroners +coronet +coronets +corpora +corporal +corporals +corporate +corporately +corporation +corporations +corporeal +corporeality +corporeally +corps +corpse +corpses +corpsman +corpsmen +corpulence +corpulent +corpus +corpuscle +corpuscles +corpuscular +corpuses +corral +corralled +corralling +corrals +correct +correctable +corrected +correcter +correctest +correcting +correction +correctional +corrections +corrective +correctives +correctly +correctness +corrects +correlate +correlated +correlates +correlating +correlation +correlations +correlative +correlatives +correspond +corresponded +correspondence +correspondences +correspondent +correspondents +corresponding +correspondingly +corresponds +corridor +corridors +corroborate +corroborated +corroborates +corroborating +corroboration +corroborations +corroborative +corroborator +corroborators +corroboratory +corrode +corroded +corrodes +corroding +corrosion +corrosive +corrosively +corrosives +corrugate +corrugated +corrugates +corrugating +corrugation +corrugations +corrupt +corrupted +corrupter +corruptest +corruptibility +corruptible +corrupting +corruption +corruptions +corruptly +corruptness +corrupts +corsage +corsages +corsair +corsairs +corset +corseted +corseting +corsets +cortege +corteges +cortex +cortexes +cortical +cortices +cortisone +corundum +coruscate +coruscated +coruscates +coruscating +coruscation +corvette +corvettes +cosier +cosies +cosiest +cosign +cosignatories +cosignatory +cosigned +cosigner +cosigners +cosigning +cosigns +cosily +cosine +cosines +cosiness +cosmetic +cosmetically +cosmetician +cosmeticians +cosmetics +cosmetologist +cosmetologists +cosmetology +cosmic +cosmically +cosmogonies +cosmogonist +cosmogonists +cosmogony +cosmological +cosmologies +cosmologist +cosmologists +cosmology +cosmonaut +cosmonauts +cosmopolitan +cosmopolitanism +cosmopolitans +cosmos +cosmoses +cosponsor +cosponsored +cosponsoring +cosponsors +cosset +cosseted +cosseting +cossets +cost +costar +costarred +costarring +costars +costed +costing +costlier +costliest +costliness +costly +costs +costume +costumed +costumer +costumers +costumes +costuming +cosy +cotangent +cotangents +cote +coterie +coteries +coterminous +cotes +cotillion +cotillions +cots +cottage +cottager +cottagers +cottages +cottar +cottars +cotter +cotters +cotton +cottoned +cottoning +cottonmouth +cottonmouths +cottons +cottonseed +cottonseeds +cottontail +cottontails +cottonwood +cottonwoods +cottony +cotyledon +cotyledons +couch +couched +couches +couching +cougar +cougars +cough +coughed +coughing +coughs +could +coulee +coulees +coulomb +coulombs +council +councillor +councillors +councilman +councilmen +councilor +councilors +councilperson +councilpersons +councils +councilwoman +councilwomen +counsel +counseled +counseling +counselled +counselling +counsellor +counsellors +counselor +counselors +counsels +count +countable +countdown +countdowns +counted +countenance +countenanced +countenances +countenancing +counter +counteract +counteracted +counteracting +counteraction +counteractions +counteractive +counteracts +counterattack +counterattacked +counterattacking +counterattacks +counterbalance +counterbalanced +counterbalances +counterbalancing +counterclaim +counterclaimed +counterclaiming +counterclaims +counterclockwise +counterculture +countered +counterespionage +counterfeit +counterfeited +counterfeiter +counterfeiters +counterfeiting +counterfeits +counterfoil +counterfoils +countering +counterinsurgencies +counterinsurgency +counterintelligence +counterman +countermand +countermanded +countermanding +countermands +countermeasure +countermeasures +countermen +counteroffensive +counteroffensives +counteroffer +counteroffered +counteroffering +counteroffers +counterpane +counterpanes +counterpart +counterparts +counterpoint +counterpoints +counterpoise +counterpoised +counterpoises +counterpoising +counterproductive +counterrevolution +counterrevolutionaries +counterrevolutionary +counterrevolutions +counters +countersank +countersign +countersignature +countersignatures +countersigned +countersigning +countersigns +countersink +countersinking +countersinks +counterspies +counterspy +countersunk +countertenor +countertenors +countervail +countervailed +countervailing +countervails +counterweight +counterweights +countess +countesses +counties +counting +countless +countries +countrified +country +countryman +countrymen +countryside +countrysides +countrywoman +countrywomen +counts +county +coup +coupe +coupes +couple +coupled +couples +couplet +couplets +coupling +couplings +coupon +coupons +coups +courage +courageous +courageously +courageousness +courier +couriered +couriering +couriers +course +coursed +courser +coursers +courses +coursing +court +courted +courteous +courteously +courteousness +courtesan +courtesans +courtesies +courtesy +courthouse +courthouses +courtier +courtiers +courting +courtlier +courtliest +courtliness +courtly +courtroom +courtrooms +courts +courtship +courtships +courtyard +courtyards +couscous +cousin +cousins +couture +couturier +couturiers +cove +coven +covenant +covenanted +covenanting +covenants +covens +cover +coverage +coverall +coveralls +covered +covering +coverings +coverlet +coverlets +covers +covert +covertly +covertness +coverts +coverup +coverups +coves +covet +coveted +coveting +covetous +covetously +covetousness +covets +covey +coveys +coward +cowardice +cowardliness +cowardly +cowards +cowbell +cowbells +cowbird +cowbirds +cowboy +cowboys +cowcatcher +cowcatchers +cowed +cower +cowered +cowering +cowers +cowgirl +cowgirls +cowhand +cowhands +cowherd +cowherds +cowhide +cowhides +cowing +cowl +cowlick +cowlicks +cowling +cowlings +cowls +cowman +cowmen +coworker +coworkers +cowpoke +cowpokes +cowpox +cowpuncher +cowpunchers +cowrie +cowries +cows +cowslip +cowslips +coxcomb +coxcombs +coxswain +coxswains +coyer +coyest +coyly +coyness +coyote +coyotes +coypu +coypus +cozen +cozenage +cozened +cozening +cozens +cozier +cozies +coziest +cozily +coziness +cozy +crab +crabbed +crabber +crabbers +crabbier +crabbiest +crabbily +crabbiness +crabbing +crabby +crabgrass +crablike +crabs +crack +crackdown +crackdowns +cracked +cracker +crackerjack +crackerjacks +crackers +crackhead +crackheads +cracking +crackle +crackled +crackles +crackling +cracklings +crackly +crackpot +crackpots +cracks +crackup +crackups +cradle +cradled +cradles +cradling +craft +crafted +craftier +craftiest +craftily +craftiness +crafting +crafts +craftsman +craftsmanship +craftsmen +craftswoman +craftswomen +crafty +crag +craggier +craggiest +cragginess +craggy +crags +cram +crammed +cramming +cramp +cramped +cramping +crampon +crampons +cramps +crams +cranberries +cranberry +crane +craned +cranes +crania +cranial +craning +cranium +craniums +crank +crankcase +crankcases +cranked +crankier +crankiest +crankily +crankiness +cranking +cranks +crankshaft +crankshafts +cranky +crannied +crannies +cranny +crap +crape +crapes +crapped +crappie +crappier +crappies +crappiest +crapping +crappy +craps +crapshooter +crapshooters +crash +crashed +crashes +crashing +crass +crasser +crassest +crassly +crassness +crate +crated +crater +cratered +cratering +craters +crates +crating +cravat +cravats +crave +craved +craven +cravenly +cravenness +cravens +craves +craving +cravings +craw +crawdad +crawdads +crawfish +crawfishes +crawl +crawled +crawler +crawlers +crawlier +crawlies +crawliest +crawling +crawls +crawlspace +crawlspaces +crawly +craws +crayfish +crayfishes +crayon +crayoned +crayoning +crayons +craze +crazed +crazes +crazier +crazies +craziest +crazily +craziness +crazing +crazy +creak +creaked +creakier +creakiest +creakily +creakiness +creaking +creaks +creaky +cream +creamed +creamer +creameries +creamers +creamery +creamier +creamiest +creamily +creaminess +creaming +creams +creamy +crease +creased +creases +creasing +create +created +creates +creating +creation +creationism +creationist +creationists +creations +creative +creatively +creativeness +creatives +creativity +creator +creators +creature +creatures +creche +creches +credence +credential +credentials +credenza +credenzas +credibility +credible +credibly +credit +creditable +creditably +credited +crediting +creditor +creditors +credits +credo +credos +credulity +credulous +credulously +credulousness +creed +creeds +creek +creeks +creel +creels +creep +creeped +creeper +creepers +creepier +creepiest +creepily +creepiness +creeping +creeps +creepy +cremains +cremate +cremated +cremates +cremating +cremation +cremations +crematoria +crematories +crematorium +crematoriums +crematory +creme +cremes +crenelate +crenelated +crenelates +crenelating +crenelation +crenelations +crenellate +crenellated +crenellates +crenellating +crenellation +crenellations +creole +creoles +creosote +creosoted +creosotes +creosoting +crepe +crepes +crept +crescendi +crescendo +crescendos +crescent +crescents +cress +crest +crested +crestfallen +cresting +crestless +crests +cretaceous +cretin +cretinism +cretinous +cretins +cretonne +crevasse +crevasses +crevice +crevices +crew +crewed +crewel +crewelwork +crewing +crewman +crewmen +crews +crib +cribbage +cribbed +cribber +cribbers +cribbing +cribs +crick +cricked +cricket +cricketer +cricketers +crickets +cricking +cricks +cried +crier +criers +cries +crime +crimes +criminal +criminality +criminally +criminals +criminologist +criminologists +criminology +crimp +crimped +crimping +crimps +crimson +crimsoned +crimsoning +crimsons +cringe +cringed +cringes +cringing +crinkle +crinkled +crinkles +crinklier +crinkliest +crinkling +crinkly +crinoline +crinolines +cripple +crippled +crippler +cripplers +cripples +crippling +crises +crisis +crisp +crisped +crisper +crispest +crispier +crispiest +crispiness +crisping +crisply +crispness +crisps +crispy +crisscross +crisscrossed +crisscrosses +crisscrossing +criteria +criterion +criterions +critic +critical +critically +criticism +criticisms +criticize +criticized +criticizer +criticizers +criticizes +criticizing +critics +critique +critiqued +critiques +critiquing +critter +critters +croak +croaked +croakier +croakiest +croaking +croaks +croaky +crochet +crocheted +crocheter +crocheters +crocheting +crochets +croci +crock +crocked +crockery +crocks +crocodile +crocodiles +crocus +crocuses +croissant +croissants +crone +crones +cronies +crony +cronyism +crook +crooked +crookeder +crookedest +crookedly +crookedness +crooking +crookneck +crooknecks +crooks +croon +crooned +crooner +crooners +crooning +croons +crop +cropland +croplands +cropped +cropper +croppers +cropping +crops +croquet +croquette +croquettes +crosier +crosiers +cross +crossbar +crossbars +crossbeam +crossbeams +crossbones +crossbow +crossbowman +crossbowmen +crossbows +crossbred +crossbreed +crossbreeding +crossbreeds +crosscheck +crosschecked +crosschecking +crosschecks +crosscurrent +crosscurrents +crosscut +crosscuts +crosscutting +crossed +crosser +crosses +crossest +crossfire +crossfires +crosshatch +crosshatched +crosshatches +crosshatching +crossing +crossings +crossly +crossness +crossover +crossovers +crosspatch +crosspatches +crosspiece +crosspieces +crossroad +crossroads +crosstown +crosswalk +crosswalks +crossways +crosswind +crosswinds +crosswise +crossword +crosswords +crotch +crotches +crotchet +crotchets +crotchety +crouch +crouched +crouches +crouching +croup +croupier +croupiers +croupiest +croupy +crouton +croutons +crow +crowbar +crowbars +crowd +crowded +crowding +crowds +crowed +crowfeet +crowfoot +crowfoots +crowing +crown +crowned +crowning +crowns +crows +crozier +croziers +cruces +crucial +crucially +crucible +crucibles +crucified +crucifies +crucifix +crucifixes +crucifixion +crucifixions +cruciform +cruciforms +crucify +crucifying +crud +cruddier +cruddiest +cruddy +crude +crudely +crudeness +cruder +crudest +crudites +crudities +crudity +cruel +crueler +cruelest +crueller +cruellest +cruelly +cruelness +cruelties +cruelty +cruet +cruets +cruise +cruised +cruiser +cruisers +cruises +cruising +cruller +crullers +crumb +crumbed +crumbier +crumbiest +crumbing +crumble +crumbled +crumbles +crumblier +crumbliest +crumbliness +crumbling +crumbly +crumbs +crumby +crummier +crummiest +crumminess +crummy +crumpet +crumpets +crumple +crumpled +crumples +crumpling +crunch +crunched +crunches +crunchier +crunchiest +crunchiness +crunching +crunchy +crupper +cruppers +crusade +crusaded +crusader +crusaders +crusades +crusading +cruse +cruses +crush +crushed +crusher +crushers +crushes +crushing +crust +crustacean +crustaceans +crustal +crusted +crustier +crustiest +crustily +crustiness +crusting +crusts +crusty +crutch +crutches +crux +cruxes +crybabies +crybaby +crying +cryogenic +cryogenics +cryosurgery +crypt +cryptic +cryptically +cryptogram +cryptograms +cryptographer +cryptographers +cryptography +crypts +crystal +crystalline +crystallization +crystallize +crystallized +crystallizes +crystallizing +crystals +cubbyhole +cubbyholes +cube +cubed +cuber +cubers +cubes +cubic +cubical +cubicle +cubicles +cubing +cubism +cubist +cubists +cubit +cubits +cubs +cuckold +cuckolded +cuckolding +cuckoldry +cuckolds +cuckoo +cuckoos +cucumber +cucumbers +cuddle +cuddled +cuddles +cuddlier +cuddliest +cuddling +cuddly +cudgel +cudgeled +cudgeling +cudgelled +cudgelling +cudgels +cuds +cued +cueing +cues +cuff +cuffed +cuffing +cufflink +cufflinks +cuffs +cuing +cuisine +cuisines +culinary +cull +culled +cullender +cullenders +culling +culls +culminate +culminated +culminates +culminating +culmination +culminations +culotte +culottes +culpability +culpable +culpably +culprit +culprits +cult +cultism +cultist +cultists +cultivable +cultivatable +cultivate +cultivated +cultivates +cultivating +cultivation +cultivator +cultivators +cults +cultural +culturally +culture +cultured +cultures +culturing +culvert +culverts +cumber +cumbered +cumbering +cumbers +cumbersome +cumbersomeness +cumbrous +cumin +cummed +cummerbund +cummerbunds +cumming +cumquat +cumquats +cums +cumulative +cumulatively +cumuli +cumulonimbi +cumulonimbus +cumulonimbuses +cumulus +cuneiform +cunnilingus +cunning +cunninger +cunningest +cunningly +cunt +cunts +cupboard +cupboards +cupcake +cupcakes +cupful +cupfuls +cupid +cupidity +cupids +cupola +cupolaed +cupolas +cupped +cupping +cupric +cups +cupsful +curability +curable +curacies +curacy +curare +curate +curated +curates +curating +curative +curatives +curator +curatorial +curators +curb +curbed +curbing +curbs +curbstone +curbstones +curd +curdle +curdled +curdles +curdling +curds +cure +cured +curer +curers +cures +curettage +curfew +curfews +curia +curiae +curie +curies +curing +curio +curios +curiosities +curiosity +curious +curiously +curiousness +curium +curl +curled +curler +curlers +curlew +curlews +curlicue +curlicued +curlicues +curlicuing +curlier +curliest +curliness +curling +curls +curly +curlycue +curlycues +curmudgeon +curmudgeonly +curmudgeons +currant +currants +currencies +currency +current +currently +currents +curricula +curricular +curriculum +curriculums +curried +curries +curry +currycomb +currycombed +currycombing +currycombs +currying +curs +curse +cursed +cursedly +curses +cursing +cursive +cursively +cursor +cursorily +cursoriness +cursors +cursory +curst +curt +curtail +curtailed +curtailing +curtailment +curtailments +curtails +curtain +curtained +curtaining +curtains +curter +curtest +curtly +curtness +curtsey +curtseyed +curtseying +curtseys +curtsied +curtsies +curtsy +curtsying +curvaceous +curvaceousness +curvacious +curvature +curvatures +curve +curved +curves +curvier +curviest +curving +curvy +cushier +cushiest +cushion +cushioned +cushioning +cushions +cushy +cusp +cuspid +cuspidor +cuspidors +cuspids +cusps +cuss +cussed +cusses +cussing +custard +custards +custodial +custodian +custodians +custodianship +custody +custom +customarily +customary +customer +customers +customhouse +customhouses +customization +customize +customized +customizes +customizing +customs +cutaneous +cutaway +cutaways +cutback +cutbacks +cute +cutely +cuteness +cuter +cutesie +cutesier +cutesiest +cutest +cutesy +cuticle +cuticles +cutie +cuties +cutlas +cutlases +cutlass +cutlasses +cutler +cutlers +cutlery +cutlet +cutlets +cutoff +cutoffs +cutout +cutouts +cuts +cutter +cutters +cutthroat +cutthroats +cutting +cuttingly +cuttings +cuttlefish +cuttlefishes +cutup +cutups +cutworm +cutworms +cyan +cyanide +cybernetic +cybernetics +cyberpunk +cyberpunks +cyberspace +cyborg +cyborgs +cyclamen +cyclamens +cycle +cycled +cycles +cyclic +cyclical +cyclically +cycling +cyclist +cyclists +cyclometer +cyclometers +cyclone +cyclones +cyclonic +cyclopaedia +cyclopaedias +cyclopedia +cyclopedias +cyclopes +cyclops +cyclotron +cyclotrons +cyder +cyders +cygnet +cygnets +cylinder +cylinders +cylindrical +cymbal +cymbalist +cymbalists +cymbals +cynic +cynical +cynically +cynicism +cynics +cynosure +cynosures +cypher +cyphered +cyphering +cyphers +cypress +cypresses +cyst +cystic +cysts +cytologist +cytologists +cytology +cytoplasm +cytoplasmic +cytosine +czar +czarina +czarinas +czarist +czarists +czars +dabbed +dabber +dabbers +dabbing +dabble +dabbled +dabbler +dabblers +dabbles +dabbling +dabs +dace +daces +dacha +dachas +dachshund +dachshunds +dactyl +dactylic +dactylics +dactyls +dadaism +dadaist +dadaists +daddies +daddy +dado +dadoes +dados +dads +daemon +daemonic +daemons +daffier +daffiest +daffiness +daffodil +daffodils +daffy +daft +dafter +daftest +daftly +daftness +dagger +daggers +daguerreotype +daguerreotyped +daguerreotypes +daguerreotyping +dahlia +dahlias +dailies +dailiness +daily +daintier +dainties +daintiest +daintily +daintiness +dainty +daiquiri +daiquiris +dairies +dairy +dairying +dairymaid +dairymaids +dairyman +dairymen +dairywoman +dairywomen +dais +daises +daisies +daisy +dale +dales +dalliance +dalliances +dallied +dallier +dalliers +dallies +dally +dallying +dalmatian +dalmatians +damage +damageable +damaged +damages +damaging +damask +damasked +damasking +damasks +dame +dames +dammed +damming +dammit +damn +damnable +damnably +damnation +damndest +damned +damnedest +damning +damns +damp +damped +dampen +dampened +dampener +dampeners +dampening +dampens +damper +dampers +dampest +damping +damply +dampness +damps +dams +damsel +damselflies +damselfly +damsels +damson +damsons +dance +danced +dancer +dancers +dances +dancing +dandelion +dandelions +dander +dandier +dandies +dandiest +dandified +dandifies +dandify +dandifying +dandle +dandled +dandles +dandling +dandruff +dandy +dang +danged +danger +dangerous +dangerously +dangers +danging +dangle +dangled +dangler +danglers +dangles +dangling +dangs +danish +danishes +dank +danker +dankest +dankly +dankness +danseuse +danseuses +dapper +dapperer +dapperest +dapple +dappled +dapples +dappling +dare +dared +daredevil +daredevilry +daredevils +darer +darers +dares +daresay +daring +daringly +dark +darken +darkened +darkener +darkeners +darkening +darkens +darker +darkest +darkly +darkness +darkroom +darkrooms +darling +darlingest +darlings +darn +darned +darneder +darnedest +darner +darners +darning +darns +dart +dartboard +dartboards +darted +darter +darters +darting +darts +dash +dashboard +dashboards +dashed +dasher +dashers +dashes +dashiki +dashikis +dashing +dashingly +dastard +dastardliness +dastardly +dastards +data +databank +databanks +database +databases +date +dated +dateless +dateline +datelined +datelines +datelining +dater +daters +dates +dating +dative +datives +datum +daub +daubed +dauber +daubers +daubing +daubs +daughter +daughterly +daughters +daunt +daunted +daunting +dauntingly +dauntless +dauntlessly +dauntlessness +daunts +dauphin +dauphins +davenport +davenports +davit +davits +dawdle +dawdled +dawdler +dawdlers +dawdles +dawdling +dawn +dawned +dawning +dawns +daybed +daybeds +daybreak +daycare +daydream +daydreamed +daydreamer +daydreamers +daydreaming +daydreams +daydreamt +daylight +daylights +days +daytime +daze +dazed +dazedly +dazes +dazing +dazzle +dazzled +dazzler +dazzlers +dazzles +dazzling +dazzlingly +deacon +deaconess +deaconesses +deacons +deactivate +deactivated +deactivates +deactivating +deactivation +dead +deadbeat +deadbeats +deadbolt +deadbolts +deaden +deadened +deadening +deadens +deader +deadest +deadlier +deadliest +deadline +deadlines +deadliness +deadlock +deadlocked +deadlocking +deadlocks +deadly +deadpan +deadpanned +deadpanning +deadpans +deadweight +deadweights +deadwood +deaf +deafen +deafened +deafening +deafeningly +deafens +deafer +deafest +deafness +deal +dealer +dealers +dealership +dealerships +dealing +dealings +deals +dealt +dean +deaneries +deanery +deans +deanship +dear +dearer +dearest +dearie +dearies +dearly +dearness +dears +dearth +dearths +deary +death +deathbed +deathbeds +deathblow +deathblows +deathless +deathlessly +deathlike +deathly +deaths +deathtrap +deathtraps +deathwatch +deathwatches +debacle +debacles +debar +debark +debarkation +debarked +debarking +debarks +debarment +debarred +debarring +debars +debase +debased +debasement +debasements +debases +debasing +debatable +debate +debated +debater +debaters +debates +debating +debauch +debauched +debauchee +debauchees +debaucheries +debauchery +debauches +debauching +debenture +debentures +debilitate +debilitated +debilitates +debilitating +debilitation +debilities +debility +debit +debited +debiting +debits +debonair +debonaire +debonairly +debonairness +debouch +debouched +debouches +debouching +debrief +debriefed +debriefing +debriefings +debriefs +debris +debs +debt +debtor +debtors +debts +debug +debugged +debugging +debugs +debunk +debunked +debunking +debunks +debut +debutante +debutantes +debuted +debuting +debuts +decade +decadence +decadency +decadent +decadently +decadents +decades +decaf +decaffeinate +decaffeinated +decaffeinates +decaffeinating +decagon +decagons +decal +decals +decamp +decamped +decamping +decampment +decamps +decant +decanted +decanter +decanters +decanting +decants +decapitate +decapitated +decapitates +decapitating +decapitation +decapitations +decapitator +decapitators +decathlon +decathlons +decay +decayed +decaying +decays +decease +deceased +deceases +deceasing +decedent +decedents +deceit +deceitful +deceitfully +deceitfulness +deceits +deceive +deceived +deceiver +deceivers +deceives +deceiving +deceivingly +decelerate +decelerated +decelerates +decelerating +deceleration +decelerator +decelerators +decencies +decency +decennial +decennials +decent +decently +decentralization +decentralize +decentralized +decentralizes +decentralizing +deception +deceptions +deceptive +deceptively +deceptiveness +decibel +decibels +decidable +decide +decided +decidedly +decides +deciding +deciduous +deciliter +deciliters +decimal +decimals +decimate +decimated +decimates +decimating +decimation +decimeter +decimeters +decipher +decipherable +deciphered +deciphering +deciphers +decision +decisions +decisive +decisively +decisiveness +deck +decked +deckhand +deckhands +decking +decks +declaim +declaimed +declaimer +declaimers +declaiming +declaims +declamation +declamations +declamatory +declarable +declaration +declarations +declarative +declaratory +declare +declared +declarer +declarers +declares +declaring +declassification +declassified +declassifies +declassify +declassifying +declension +declensions +declination +decline +declined +decliner +decliners +declines +declining +declivities +declivity +decode +decoded +decoder +decoders +decodes +decoding +decolletage +decolletages +decollete +decolonization +decolonize +decolonized +decolonizes +decolonizing +decommission +decommissioned +decommissioning +decommissions +decompose +decomposed +decomposes +decomposing +decomposition +decompress +decompressed +decompresses +decompressing +decompression +decongestant +decongestants +deconstruction +deconstructions +decontaminate +decontaminated +decontaminates +decontaminating +decontamination +decontrol +decontrolled +decontrolling +decontrols +decor +decorate +decorated +decorates +decorating +decoration +decorations +decorative +decoratively +decorator +decorators +decorous +decorously +decorousness +decors +decorum +decoupage +decoupaged +decoupages +decoupaging +decoy +decoyed +decoying +decoys +decrease +decreased +decreases +decreasing +decreasingly +decree +decreed +decreeing +decrees +decrepit +decrepitude +decrescendi +decrescendo +decrescendos +decried +decries +decriminalization +decriminalize +decriminalized +decriminalizes +decriminalizing +decry +decrying +dedicate +dedicated +dedicates +dedicating +dedication +dedications +dedicator +dedicators +dedicatory +deduce +deduced +deduces +deducible +deducing +deduct +deducted +deductible +deductibles +deducting +deduction +deductions +deductive +deductively +deducts +deed +deeded +deeding +deeds +deejay +deejays +deem +deemed +deeming +deems +deep +deepen +deepened +deepening +deepens +deeper +deepest +deeply +deepness +deeps +deer +deers +deerskin +deescalate +deescalated +deescalates +deescalating +deescalation +deface +defaced +defacement +defacer +defacers +defaces +defacing +defalcate +defalcated +defalcates +defalcating +defalcation +defalcations +defamation +defamatory +defame +defamed +defamer +defamers +defames +defaming +default +defaulted +defaulter +defaulters +defaulting +defaults +defeat +defeated +defeater +defeaters +defeating +defeatism +defeatist +defeatists +defeats +defecate +defecated +defecates +defecating +defecation +defect +defected +defecting +defection +defections +defective +defectively +defectiveness +defectives +defector +defectors +defects +defence +defences +defend +defendant +defendants +defended +defender +defenders +defending +defends +defense +defensed +defenseless +defenselessly +defenselessness +defenses +defensible +defensibly +defensing +defensive +defensively +defensiveness +defer +deference +deferential +deferentially +deferment +deferments +deferral +deferrals +deferred +deferring +defers +deffer +deffest +defiance +defiant +defiantly +deficiencies +deficiency +deficient +deficit +deficits +defied +defies +defile +defiled +defilement +defiler +defilers +defiles +defiling +definable +define +defined +definer +definers +defines +defining +definite +definitely +definiteness +definition +definitions +definitive +definitively +deflate +deflated +deflates +deflating +deflation +deflationary +deflect +deflected +deflecting +deflection +deflections +deflective +deflector +deflectors +deflects +deflower +deflowered +deflowering +deflowers +defog +defogged +defogger +defoggers +defogging +defogs +defoliant +defoliants +defoliate +defoliated +defoliates +defoliating +defoliation +defoliator +defoliators +deforest +deforestation +deforested +deforesting +deforests +deform +deformation +deformations +deformed +deforming +deformities +deformity +deforms +defraud +defrauded +defrauder +defrauders +defrauding +defrauds +defray +defrayal +defrayed +defraying +defrays +defrock +defrocked +defrocking +defrocks +defrost +defrosted +defroster +defrosters +defrosting +defrosts +deft +defter +deftest +deftly +deftness +defunct +defuse +defused +defuses +defusing +defy +defying +degas +degases +degassed +degassing +degeneracy +degenerate +degenerated +degenerates +degenerating +degeneration +degenerative +degradable +degradation +degrade +degraded +degrades +degrading +degree +degrees +dehumanization +dehumanize +dehumanized +dehumanizes +dehumanizing +dehumidified +dehumidifier +dehumidifiers +dehumidifies +dehumidify +dehumidifying +dehydrate +dehydrated +dehydrates +dehydrating +dehydration +dehydrator +dehydrators +dehydrogenate +dehydrogenated +dehydrogenates +dehydrogenating +deice +deiced +deicer +deicers +deices +deicing +deification +deified +deifies +deify +deifying +deign +deigned +deigning +deigns +deism +deist +deistic +deists +deities +deity +deject +dejected +dejectedly +dejecting +dejection +dejects +delay +delayed +delayer +delayers +delaying +delays +delectable +delectably +delectation +delegate +delegated +delegates +delegating +delegation +delegations +delete +deleted +deleterious +deletes +deleting +deletion +deletions +delft +delftware +deli +deliberate +deliberated +deliberately +deliberateness +deliberates +deliberating +deliberation +deliberations +deliberative +delicacies +delicacy +delicate +delicately +delicateness +delicatessen +delicatessens +delicious +deliciously +deliciousness +delight +delighted +delightedly +delightful +delightfully +delighting +delights +delimit +delimitation +delimited +delimiting +delimits +delineate +delineated +delineates +delineating +delineation +delineations +delinquencies +delinquency +delinquent +delinquently +delinquents +deliquesce +deliquesced +deliquescent +deliquesces +deliquescing +deliria +delirious +deliriously +deliriousness +delirium +deliriums +delis +deliver +deliverance +delivered +deliverer +deliverers +deliveries +delivering +delivers +delivery +deliveryman +deliverymen +dell +dells +delouse +deloused +delouses +delousing +delphinia +delphinium +delphiniums +delta +deltas +delude +deluded +deludes +deluding +deluge +deluged +deluges +deluging +delusion +delusional +delusions +delusive +delusively +deluxe +delve +delved +delver +delvers +delves +delving +demagnetization +demagnetize +demagnetized +demagnetizes +demagnetizing +demagog +demagogic +demagogs +demagogue +demagoguery +demagogues +demagogy +demand +demanded +demanding +demands +demarcate +demarcated +demarcates +demarcating +demarcation +demean +demeaned +demeaning +demeanor +demeanour +demeans +demented +dementedly +dementia +demerit +demerits +demesne +demesnes +demigod +demigoddess +demigoddesses +demigods +demijohn +demijohns +demilitarization +demilitarize +demilitarized +demilitarizes +demilitarizing +demimondaine +demimondaines +demimonde +demise +demised +demises +demising +demitasse +demitasses +demo +demobilization +demobilize +demobilized +demobilizes +demobilizing +democracies +democracy +democrat +democratic +democratically +democratization +democratize +democratized +democratizes +democratizing +democrats +demode +demodulate +demodulated +demodulates +demodulating +demodulation +demographer +demographers +demographic +demographically +demographics +demography +demolish +demolished +demolishes +demolishing +demolition +demolitions +demon +demonetization +demonetize +demonetized +demonetizes +demonetizing +demoniac +demoniacal +demoniacally +demonic +demonologies +demonology +demons +demonstrable +demonstrably +demonstrate +demonstrated +demonstrates +demonstrating +demonstration +demonstrations +demonstrative +demonstratively +demonstrativeness +demonstratives +demonstrator +demonstrators +demoralization +demoralize +demoralized +demoralizes +demoralizing +demos +demote +demoted +demotes +demotic +demoting +demotion +demotions +demulcent +demulcents +demur +demure +demurely +demureness +demurer +demurest +demurral +demurrals +demurred +demurrer +demurrers +demurring +demurs +demystification +demystified +demystifies +demystify +demystifying +denationalize +denationalized +denationalizes +denationalizing +denature +denatured +denatures +denaturing +dendrite +dendrites +dengue +deniable +denial +denials +denied +denier +deniers +denies +denigrate +denigrated +denigrates +denigrating +denigration +denim +denims +denizen +denizens +denominate +denominated +denominates +denominating +denomination +denominational +denominations +denominator +denominators +denotation +denotations +denotative +denote +denoted +denotes +denoting +denouement +denouements +denounce +denounced +denouncement +denouncements +denounces +denouncing +dens +dense +densely +denseness +denser +densest +densities +density +dent +dental +dentally +dented +dentifrice +dentifrices +dentin +dentine +denting +dentist +dentistry +dentists +dentition +dents +denture +dentures +denuclearize +denuclearized +denuclearizes +denuclearizing +denudation +denude +denuded +denudes +denuding +denunciation +denunciations +deny +denying +deodorant +deodorants +deodorization +deodorize +deodorized +deodorizer +deodorizers +deodorizes +deodorizing +depart +departed +departing +department +departmental +departmentalization +departmentalize +departmentalized +departmentalizes +departmentalizing +departmentally +departments +departs +departure +departures +depend +dependability +dependable +dependably +dependance +dependant +dependants +depended +dependence +dependencies +dependency +dependent +dependently +dependents +depending +depends +depersonalize +depersonalized +depersonalizes +depersonalizing +depict +depicted +depicting +depiction +depictions +depicts +depilatories +depilatory +deplane +deplaned +deplanes +deplaning +deplete +depleted +depletes +depleting +depletion +deplorable +deplorably +deplore +deplored +deplores +deploring +deploy +deployed +deploying +deployment +deployments +deploys +depolarization +depolarize +depolarized +depolarizes +depolarizing +depoliticize +depoliticized +depoliticizes +depoliticizing +deponent +deponents +depopulate +depopulated +depopulates +depopulating +depopulation +deport +deportation +deportations +deported +deportee +deportees +deporting +deportment +deports +depose +deposed +deposes +deposing +deposit +deposited +depositing +deposition +depositions +depositor +depositories +depositors +depository +deposits +depot +depots +deprave +depraved +depraves +depraving +depravities +depravity +deprecate +deprecated +deprecates +deprecating +deprecation +deprecatory +depreciate +depreciated +depreciates +depreciating +depreciation +depredation +depredations +depress +depressant +depressants +depressed +depresses +depressing +depressingly +depression +depressions +depressive +depressives +depressor +depressors +depressurize +depressurized +depressurizes +depressurizing +deprivation +deprivations +deprive +deprived +deprives +depriving +deprogram +deprogramed +deprograming +deprogrammed +deprogramming +deprograms +depth +depths +deputation +deputations +depute +deputed +deputes +deputies +deputing +deputize +deputized +deputizes +deputizing +deputy +derail +derailed +derailing +derailleur +derailleurs +derailment +derailments +derails +derange +deranged +derangement +deranges +deranging +derbies +derby +deregulate +deregulated +deregulates +deregulating +deregulation +derelict +dereliction +derelicts +deride +derided +derides +deriding +derision +derisive +derisively +derisiveness +derisory +derivation +derivations +derivative +derivatives +derive +derived +derives +deriving +dermal +dermatitis +dermatological +dermatologist +dermatologists +dermatology +dermis +derogate +derogated +derogates +derogating +derogation +derogatorily +derogatory +derrick +derricks +derriere +derrieres +derringer +derringers +dervish +dervishes +desalinate +desalinated +desalinates +desalinating +desalination +desalinization +desalinize +desalinized +desalinizes +desalinizing +desalt +desalted +desalting +desalts +descant +descanted +descanting +descants +descend +descendant +descendants +descended +descendent +descendents +descending +descends +descent +descents +describable +describe +described +describer +describers +describes +describing +descried +descries +description +descriptions +descriptive +descriptively +descriptiveness +descry +descrying +desecrate +desecrated +desecrates +desecrating +desecration +desegregate +desegregated +desegregates +desegregating +desegregation +desensitization +desensitize +desensitized +desensitizes +desensitizing +desert +deserted +deserter +deserters +deserting +desertion +desertions +deserts +deserve +deserved +deservedly +deserves +deserving +deshabille +desiccant +desiccants +desiccate +desiccated +desiccates +desiccating +desiccation +desiccator +desiccators +desiderata +desideratum +design +designate +designated +designates +designating +designation +designations +designed +designer +designers +designing +designs +desirability +desirable +desirableness +desirably +desire +desired +desires +desiring +desirous +desist +desisted +desisting +desists +desk +desks +desktop +desktops +desolate +desolated +desolately +desolateness +desolates +desolating +desolation +despair +despaired +despairing +despairingly +despairs +despatch +despatched +despatches +despatching +desperado +desperadoes +desperados +desperate +desperately +desperateness +desperation +despicable +despicably +despise +despised +despises +despising +despite +despoil +despoiled +despoiler +despoilers +despoiling +despoilment +despoils +despoliation +despondence +despondency +despondent +despondently +despot +despotic +despotically +despotism +despots +dessert +desserts +destabilization +destabilize +destabilized +destabilizes +destabilizing +destination +destinations +destine +destined +destines +destinies +destining +destiny +destitute +destitution +destroy +destroyed +destroyer +destroyers +destroying +destroys +destruct +destructed +destructibility +destructible +destructing +destruction +destructive +destructively +destructiveness +destructs +desuetude +desultorily +desultory +detach +detachable +detached +detaches +detaching +detachment +detachments +detail +detailed +detailing +details +detain +detained +detainee +detainees +detaining +detainment +detains +detect +detectable +detected +detectible +detecting +detection +detective +detectives +detector +detectors +detects +detente +detentes +detention +detentions +deter +detergent +detergents +deteriorate +deteriorated +deteriorates +deteriorating +deterioration +determent +determinable +determinant +determinants +determinate +determination +determinations +determine +determined +determinedly +determiner +determiners +determines +determining +determinism +deterred +deterrence +deterrent +deterrents +deterring +deters +detest +detestable +detestably +detestation +detested +detesting +detests +dethrone +dethroned +dethronement +dethrones +dethroning +detonate +detonated +detonates +detonating +detonation +detonations +detonator +detonators +detour +detoured +detouring +detours +detox +detoxed +detoxes +detoxification +detoxified +detoxifies +detoxify +detoxifying +detoxing +detract +detracted +detracting +detraction +detractor +detractors +detracts +detriment +detrimental +detrimentally +detriments +detritus +deuce +deuces +deuterium +devaluation +devaluations +devalue +devalued +devalues +devaluing +devastate +devastated +devastates +devastating +devastatingly +devastation +devastator +devastators +develop +developed +developer +developers +developing +development +developmental +developmentally +developments +develops +deviance +deviancy +deviant +deviants +deviate +deviated +deviates +deviating +deviation +deviations +device +devices +devil +deviled +deviling +devilish +devilishly +devilishness +devilled +devilling +devilment +devilries +devilry +devils +deviltries +deviltry +devious +deviously +deviousness +devise +devised +devises +devising +devitalize +devitalized +devitalizes +devitalizing +devoid +devolution +devolve +devolved +devolves +devolving +devote +devoted +devotedly +devotee +devotees +devotes +devoting +devotion +devotional +devotionals +devotions +devour +devoured +devouring +devours +devout +devouter +devoutest +devoutly +devoutness +dewberries +dewberry +dewclaw +dewclaws +dewdrop +dewdrops +dewier +dewiest +dewiness +dewlap +dewlaps +dewy +dexterity +dexterous +dexterously +dexterousness +dextrose +dextrous +dhoti +dhotis +dhow +dhows +diabetes +diabetic +diabetics +diabolic +diabolical +diabolically +diacritic +diacritical +diacritics +diadem +diadems +diaereses +diaeresis +diagnose +diagnosed +diagnoses +diagnosing +diagnosis +diagnostic +diagnostically +diagnostician +diagnosticians +diagnostics +diagonal +diagonally +diagonals +diagram +diagramed +diagraming +diagrammatic +diagrammatically +diagrammed +diagramming +diagrams +dial +dialect +dialectal +dialectic +dialectical +dialectics +dialects +dialed +dialing +dialled +dialling +dialog +dialogs +dialogue +dialogues +dials +dialyses +dialysis +diameter +diameters +diametric +diametrical +diametrically +diamond +diamondback +diamondbacks +diamonds +diapason +diapasons +diaper +diapered +diapering +diapers +diaphanous +diaphragm +diaphragmatic +diaphragms +diaries +diarist +diarists +diarrhea +diarrhoea +diary +diaspora +diasporas +diastase +diastole +diastolic +diathermy +diatom +diatomic +diatoms +diatonic +diatribe +diatribes +dibble +dibbled +dibbles +dibbling +dibs +dice +diced +dices +dicey +dichotomies +dichotomous +dichotomy +dicier +diciest +dicing +dick +dicker +dickered +dickering +dickers +dickey +dickeys +dickies +dicks +dicky +dicotyledon +dicotyledonous +dicotyledons +dicta +dictate +dictated +dictates +dictating +dictation +dictations +dictator +dictatorial +dictatorially +dictators +dictatorship +dictatorships +diction +dictionaries +dictionary +dictum +dictums +didactic +didactically +diddle +diddled +diddler +diddlers +diddles +diddling +dido +didoes +didos +didst +died +diehard +diehards +dielectric +dielectrics +diereses +dieresis +dies +diesel +dieseled +dieseling +diesels +diet +dietaries +dietary +dieted +dieter +dieters +dietetic +dietetics +dietician +dieticians +dieting +dietitian +dietitians +diets +differ +differed +difference +differences +different +differential +differentials +differentiate +differentiated +differentiates +differentiating +differentiation +differently +differing +differs +difficult +difficulties +difficultly +difficulty +diffidence +diffident +diffidently +diffraction +diffuse +diffused +diffusely +diffuseness +diffuses +diffusing +diffusion +diffusive +digerati +digest +digested +digestibility +digestible +digesting +digestion +digestions +digestive +digests +digger +diggers +digging +diggings +digit +digital +digitalis +digitally +digitize +digitized +digitizes +digitizing +digits +dignified +dignifies +dignify +dignifying +dignitaries +dignitary +dignities +dignity +digraph +digraphs +digress +digressed +digresses +digressing +digression +digressions +digressive +digs +dike +diked +dikes +diking +dilapidated +dilapidation +dilatation +dilate +dilated +dilates +dilating +dilation +dilator +dilators +dilatory +dilemma +dilemmas +dilettante +dilettantes +dilettanti +dilettantish +dilettantism +diligence +diligent +diligently +dill +dillies +dills +dilly +dillydallied +dillydallies +dillydally +dillydallying +dilute +diluted +dilutes +diluting +dilution +dime +dimension +dimensional +dimensions +dimes +diminish +diminished +diminishes +diminishing +diminuendo +diminuendoes +diminuendos +diminution +diminutions +diminutive +diminutives +dimity +dimly +dimmed +dimmer +dimmers +dimmest +dimming +dimness +dimple +dimpled +dimples +dimpling +dimply +dims +dimwit +dimwits +dimwitted +dinar +dinars +dine +dined +diner +diners +dines +dinette +dinettes +ding +dingbat +dingbats +dingdong +dinged +dinghies +dinghy +dingier +dingiest +dingily +dinginess +dinging +dingle +dingles +dingo +dingoes +dings +dingus +dinguses +dingy +dining +dinkier +dinkies +dinkiest +dinky +dinned +dinner +dinnered +dinnering +dinners +dinnertime +dinnerware +dinning +dinosaur +dinosaurs +dins +dint +diocesan +diocesans +diocese +dioceses +diode +diodes +diorama +dioramas +dioxide +dioxides +dioxin +dioxins +diphtheria +diphthong +diphthongs +diploid +diploids +diploma +diplomacy +diplomas +diplomat +diplomata +diplomatic +diplomatically +diplomatist +diplomatists +diplomats +dipole +dipoles +dipped +dipper +dippers +dippier +dippiest +dipping +dippy +dips +dipsomania +dipsomaniac +dipsomaniacs +dipstick +dipsticks +dipterous +diptych +diptychs +dire +direct +directed +directer +directest +directing +direction +directional +directions +directive +directives +directly +directness +director +directorate +directorates +directorial +directories +directors +directorship +directorships +directory +directs +direful +direly +direr +direst +dirge +dirges +dirigible +dirigibles +dirk +dirks +dirndl +dirndls +dirt +dirtied +dirtier +dirties +dirtiest +dirtily +dirtiness +dirty +dirtying +disabilities +disability +disable +disabled +disablement +disables +disabling +disabuse +disabused +disabuses +disabusing +disadvantage +disadvantaged +disadvantageous +disadvantageously +disadvantages +disadvantaging +disaffect +disaffected +disaffecting +disaffection +disaffects +disaffiliate +disaffiliated +disaffiliates +disaffiliating +disaffiliation +disagree +disagreeable +disagreeableness +disagreeably +disagreed +disagreeing +disagreement +disagreements +disagrees +disallow +disallowed +disallowing +disallows +disappear +disappearance +disappearances +disappeared +disappearing +disappears +disappoint +disappointed +disappointing +disappointingly +disappointment +disappointments +disappoints +disapprobation +disapproval +disapprove +disapproved +disapproves +disapproving +disapprovingly +disarm +disarmament +disarmed +disarming +disarmingly +disarms +disarrange +disarranged +disarrangement +disarranges +disarranging +disarray +disarrayed +disarraying +disarrays +disassemble +disassembled +disassembles +disassembling +disassociate +disassociated +disassociates +disassociating +disassociation +disaster +disasters +disastrous +disastrously +disavow +disavowal +disavowals +disavowed +disavowing +disavows +disband +disbanded +disbanding +disbandment +disbands +disbar +disbarment +disbarred +disbarring +disbars +disbelief +disbelieve +disbelieved +disbeliever +disbelievers +disbelieves +disbelieving +disbelievingly +disbursal +disburse +disbursed +disbursement +disbursements +disburses +disbursing +disc +discard +discarded +discarding +discards +discern +discerned +discernible +discernibly +discerning +discerningly +discernment +discerns +discharge +discharged +discharges +discharging +disciple +disciples +discipleship +disciplinarian +disciplinarians +disciplinary +discipline +disciplined +disciplines +disciplining +disclaim +disclaimed +disclaimer +disclaimers +disclaiming +disclaims +disclose +disclosed +discloses +disclosing +disclosure +disclosures +disco +discoed +discographies +discography +discoing +discolor +discoloration +discolorations +discolored +discoloring +discolors +discolour +discoloured +discolouring +discolours +discombobulate +discombobulated +discombobulates +discombobulating +discombobulation +discomfit +discomfited +discomfiting +discomfits +discomfiture +discomfort +discomforted +discomforting +discomforts +discommode +discommoded +discommodes +discommoding +discompose +discomposed +discomposes +discomposing +discomposure +disconcert +disconcerted +disconcerting +disconcertingly +disconcerts +disconnect +disconnected +disconnectedly +disconnectedness +disconnecting +disconnection +disconnections +disconnects +disconsolate +disconsolately +discontent +discontented +discontentedly +discontenting +discontentment +discontents +discontinuance +discontinuances +discontinuation +discontinuations +discontinue +discontinued +discontinues +discontinuing +discontinuities +discontinuity +discontinuous +discontinuously +discord +discordance +discordant +discordantly +discorded +discording +discords +discos +discotheque +discotheques +discount +discounted +discountenance +discountenanced +discountenances +discountenancing +discounter +discounters +discounting +discounts +discourage +discouraged +discouragement +discouragements +discourages +discouraging +discouragingly +discourse +discoursed +discourses +discoursing +discourteous +discourteously +discourtesies +discourtesy +discover +discovered +discoverer +discoverers +discoveries +discovering +discovers +discovery +discredit +discreditable +discreditably +discredited +discrediting +discredits +discreet +discreeter +discreetest +discreetly +discreetness +discrepancies +discrepancy +discrepant +discrete +discretely +discreteness +discretion +discretionary +discriminate +discriminated +discriminates +discriminating +discrimination +discriminator +discriminators +discriminatory +discs +discursive +discursively +discursiveness +discus +discuses +discuss +discussant +discussants +discussed +discusses +discussing +discussion +discussions +disdain +disdained +disdainful +disdainfully +disdaining +disdains +disease +diseased +diseases +disembark +disembarkation +disembarked +disembarking +disembarks +disembodied +disembodies +disembodiment +disembody +disembodying +disembowel +disemboweled +disemboweling +disembowelled +disembowelling +disembowelment +disembowels +disenchant +disenchanted +disenchanting +disenchantment +disenchants +disencumber +disencumbered +disencumbering +disencumbers +disenfranchise +disenfranchised +disenfranchisement +disenfranchises +disenfranchising +disengage +disengaged +disengagement +disengagements +disengages +disengaging +disentangle +disentangled +disentanglement +disentangles +disentangling +disequilibrium +disestablish +disestablished +disestablishes +disestablishing +disestablishment +disesteem +disesteemed +disesteeming +disesteems +disfavor +disfavored +disfavoring +disfavors +disfavour +disfavours +disfigure +disfigured +disfigurement +disfigurements +disfigures +disfiguring +disfranchise +disfranchised +disfranchisement +disfranchises +disfranchising +disgorge +disgorged +disgorgement +disgorges +disgorging +disgrace +disgraced +disgraceful +disgracefully +disgracefulness +disgraces +disgracing +disgruntle +disgruntled +disgruntlement +disgruntles +disgruntling +disguise +disguised +disguises +disguising +disgust +disgusted +disgustedly +disgusting +disgustingly +disgusts +dish +dishabille +disharmonious +disharmony +dishcloth +dishcloths +dishearten +disheartened +disheartening +dishearteningly +disheartens +dished +dishes +dishevel +disheveled +disheveling +dishevelled +dishevelling +dishevelment +dishevels +dishing +dishonest +dishonestly +dishonesty +dishonor +dishonorable +dishonorably +dishonored +dishonoring +dishonors +dishpan +dishpans +dishrag +dishrags +dishtowel +dishtowels +dishware +dishwasher +dishwashers +dishwater +disillusion +disillusioned +disillusioning +disillusionment +disillusions +disinclination +disincline +disinclined +disinclines +disinclining +disinfect +disinfectant +disinfectants +disinfected +disinfecting +disinfection +disinfects +disinflation +disinformation +disingenuous +disingenuously +disinherit +disinheritance +disinherited +disinheriting +disinherits +disintegrate +disintegrated +disintegrates +disintegrating +disintegration +disinter +disinterest +disinterested +disinterestedly +disinterestedness +disinterests +disinterment +disinterred +disinterring +disinters +disinvestment +disjoint +disjointed +disjointedly +disjointedness +disjointing +disjoints +disjunctive +disk +diskette +diskettes +disks +dislike +disliked +dislikes +disliking +dislocate +dislocated +dislocates +dislocating +dislocation +dislocations +dislodge +dislodged +dislodges +dislodging +disloyal +disloyally +disloyalty +dismal +dismally +dismantle +dismantled +dismantlement +dismantles +dismantling +dismay +dismayed +dismaying +dismays +dismember +dismembered +dismembering +dismemberment +dismembers +dismiss +dismissal +dismissals +dismissed +dismisses +dismissing +dismissive +dismissively +dismount +dismounted +dismounting +dismounts +disobedience +disobedient +disobediently +disobey +disobeyed +disobeying +disobeys +disoblige +disobliged +disobliges +disobliging +disorder +disordered +disordering +disorderliness +disorderly +disorders +disorganization +disorganize +disorganized +disorganizes +disorganizing +disorient +disorientate +disorientated +disorientates +disorientating +disorientation +disoriented +disorienting +disorients +disown +disowned +disowning +disowns +disparage +disparaged +disparagement +disparages +disparaging +disparagingly +disparate +disparately +disparities +disparity +dispassion +dispassionate +dispassionately +dispatch +dispatched +dispatcher +dispatchers +dispatches +dispatching +dispel +dispelled +dispelling +dispels +dispensable +dispensaries +dispensary +dispensation +dispensations +dispense +dispensed +dispenser +dispensers +dispenses +dispensing +dispersal +disperse +dispersed +disperses +dispersing +dispersion +dispirit +dispirited +dispiriting +dispirits +displace +displaced +displacement +displacements +displaces +displacing +display +displayed +displaying +displays +displease +displeased +displeases +displeasing +displeasure +disport +disported +disporting +disports +disposable +disposables +disposal +disposals +dispose +disposed +disposer +disposers +disposes +disposing +disposition +dispositions +dispossess +dispossessed +dispossesses +dispossessing +dispossession +dispraise +dispraised +dispraises +dispraising +disproof +disproofs +disproportion +disproportional +disproportionate +disproportionately +disproportions +disprovable +disprove +disproved +disproven +disproves +disproving +disputable +disputably +disputant +disputants +disputation +disputations +disputatious +disputatiously +dispute +disputed +disputer +disputers +disputes +disputing +disqualification +disqualifications +disqualified +disqualifies +disqualify +disqualifying +disquiet +disquieted +disquieting +disquiets +disquietude +disquisition +disquisitions +disregard +disregarded +disregardful +disregarding +disregards +disrepair +disreputable +disreputably +disrepute +disrespect +disrespected +disrespectful +disrespectfully +disrespecting +disrespects +disrobe +disrobed +disrobes +disrobing +disrupt +disrupted +disrupting +disruption +disruptions +disruptive +disruptively +disrupts +diss +dissatisfaction +dissatisfied +dissatisfies +dissatisfy +dissatisfying +dissect +dissected +dissecting +dissection +dissections +dissector +dissectors +dissects +dissed +dissemblance +dissemble +dissembled +dissembler +dissemblers +dissembles +dissembling +disseminate +disseminated +disseminates +disseminating +dissemination +dissension +dissensions +dissent +dissented +dissenter +dissenters +dissenting +dissents +dissertation +dissertations +disservice +disservices +disses +dissever +dissevered +dissevering +dissevers +dissidence +dissident +dissidents +dissimilar +dissimilarities +dissimilarity +dissimilitude +dissimilitudes +dissimulate +dissimulated +dissimulates +dissimulating +dissimulation +dissimulator +dissimulators +dissing +dissipate +dissipated +dissipates +dissipating +dissipation +dissociate +dissociated +dissociates +dissociating +dissociation +dissoluble +dissolute +dissolutely +dissoluteness +dissolution +dissolve +dissolved +dissolves +dissolving +dissonance +dissonances +dissonant +dissuade +dissuaded +dissuades +dissuading +dissuasion +dissuasive +distaff +distaffs +distal +distally +distance +distanced +distances +distancing +distant +distantly +distaste +distasteful +distastefully +distastefulness +distastes +distemper +distend +distended +distending +distends +distension +distensions +distention +distentions +distil +distill +distillate +distillates +distillation +distillations +distilled +distiller +distilleries +distillers +distillery +distilling +distills +distils +distinct +distincter +distinctest +distinction +distinctions +distinctive +distinctively +distinctiveness +distinctly +distinctness +distinguish +distinguishable +distinguished +distinguishes +distinguishing +distort +distorted +distorting +distortion +distortions +distorts +distract +distracted +distractedly +distracting +distraction +distractions +distracts +distrait +distraught +distress +distressed +distresses +distressful +distressing +distressingly +distribute +distributed +distributes +distributing +distribution +distributions +distributive +distributively +distributor +distributors +district +districts +distrust +distrusted +distrustful +distrustfully +distrusting +distrusts +disturb +disturbance +disturbances +disturbed +disturber +disturbers +disturbing +disturbs +disunion +disunite +disunited +disunites +disuniting +disunity +disuse +disused +disuses +disusing +ditch +ditched +ditches +ditching +dither +dithered +ditherer +ditherers +dithering +dithers +ditsier +ditsiest +ditsy +ditties +ditto +dittoed +dittoes +dittoing +dittos +ditty +ditz +ditzes +ditzier +ditziest +ditzy +diuretic +diuretics +diurnal +diurnally +diva +divalent +divan +divans +divas +dive +dived +diver +diverge +diverged +divergence +divergences +divergent +diverges +diverging +divers +diverse +diversely +diverseness +diversification +diversified +diversifies +diversify +diversifying +diversion +diversionary +diversions +diversities +diversity +divert +diverted +diverticulitis +diverting +diverts +dives +divest +divested +divesting +divestiture +divestitures +divestment +divests +dividable +divide +divided +dividend +dividends +divider +dividers +divides +dividing +divination +divine +divined +divinely +diviner +diviners +divines +divinest +diving +divining +divinities +divinity +divisibility +divisible +division +divisional +divisions +divisive +divisively +divisiveness +divisor +divisors +divorce +divorced +divorcee +divorcees +divorcement +divorcements +divorces +divorcing +divot +divots +divulge +divulged +divulges +divulging +divvied +divvies +divvy +divvying +dixieland +dizzied +dizzier +dizzies +dizziest +dizzily +dizziness +dizzy +dizzying +djellaba +djellabah +djellabahs +djellabas +djinn +djinns +doable +dobbin +dobbins +doberman +dobermans +docent +docents +docile +docilely +docility +dock +docked +docket +docketed +docketing +dockets +docking +docks +dockworker +dockworkers +dockyard +dockyards +docs +doctor +doctoral +doctorate +doctorates +doctored +doctoring +doctors +doctrinaire +doctrinaires +doctrinal +doctrine +doctrines +docudrama +docudramas +document +documentaries +documentary +documentation +documented +documenting +documents +dodder +doddered +doddering +dodders +dodge +dodged +dodger +dodgers +dodges +dodging +dodo +dodoes +dodos +doer +doers +does +doeskin +doeskins +doff +doffed +doffing +doffs +dogcart +dogcarts +dogcatcher +dogcatchers +doge +dogear +dogeared +dogearing +dogears +doges +dogfight +dogfights +dogfish +dogfishes +dogged +doggedly +doggedness +doggerel +doggie +doggier +doggies +doggiest +dogging +doggone +doggoned +doggoneder +doggonedest +doggoner +doggones +doggonest +doggoning +doggy +doghouse +doghouses +dogie +dogies +dogleg +doglegged +doglegging +doglegs +dogma +dogmas +dogmata +dogmatic +dogmatically +dogmatism +dogmatist +dogmatists +dogs +dogtrot +dogtrots +dogtrotted +dogtrotting +dogwood +dogwoods +dogy +doilies +doily +doing +doings +doldrums +dole +doled +doleful +dolefully +dolefulness +doles +doling +doll +dollar +dollars +dolled +dollhouse +dollhouses +dollies +dolling +dollop +dolloped +dolloping +dollops +dolls +dolly +dolmen +dolmens +dolomite +dolor +dolorous +dolorously +dolphin +dolphins +dolt +doltish +doltishly +doltishness +dolts +domain +domains +dome +domed +domes +domestic +domestically +domesticate +domesticated +domesticates +domesticating +domestication +domesticity +domestics +domicile +domiciled +domiciles +domiciliary +domiciling +dominance +dominant +dominantly +dominants +dominate +dominated +dominates +dominating +domination +dominatrices +dominatrix +dominatrixes +domineer +domineered +domineering +domineeringly +domineers +doming +dominion +dominions +domino +dominoes +dominos +dona +donas +donate +donated +donates +donating +donation +donations +done +dong +dongle +dongles +dongs +donkey +donkeys +donned +donning +donnybrook +donnybrooks +donor +donors +dons +donut +donuts +doodad +doodads +doodle +doodlebug +doodlebugs +doodled +doodler +doodlers +doodles +doodling +doohickey +doohickeys +doom +doomed +dooming +dooms +doomsayer +doomsayers +doomsday +door +doorbell +doorbells +doorkeeper +doorkeepers +doorknob +doorknobs +doorman +doormat +doormats +doormen +doorplate +doorplates +doors +doorstep +doorstepped +doorstepping +doorsteps +doorstop +doorstops +doorway +doorways +dooryard +dooryards +dopa +dope +doped +doper +dopers +dopes +dopey +dopier +dopiest +dopiness +doping +dopy +dories +dork +dorkier +dorkiest +dorks +dorky +dorm +dormancy +dormant +dormer +dormers +dormice +dormitories +dormitory +dormouse +dorms +dorsal +dorsally +dory +dosage +dosages +dose +dosed +doses +dosimeter +dosimeters +dosing +dossier +dossiers +dost +dotage +dotard +dotards +dote +doted +doter +doters +dotes +doth +doting +dotingly +dots +dotted +dottier +dottiest +dotting +dotty +double +doubled +doubleheader +doubleheaders +doubles +doublespeak +doublet +doublets +doubling +doubloon +doubloons +doubly +doubt +doubted +doubter +doubters +doubtful +doubtfuller +doubtfullest +doubtfully +doubtfulness +doubting +doubtingly +doubtless +doubtlessly +doubts +douche +douched +douches +douching +dough +doughier +doughiest +doughnut +doughnuts +doughtier +doughtiest +doughty +doughy +dour +dourer +dourest +dourly +dourness +douse +doused +douses +dousing +dove +dovecote +dovecotes +doves +dovetail +dovetailed +dovetailing +dovetails +dovish +dowager +dowagers +dowdier +dowdiest +dowdily +dowdiness +dowdy +dowel +doweled +doweling +dowelled +dowelling +dowels +dower +dowered +dowering +dowers +down +downbeat +downbeats +downcast +downdraft +downdrafts +downed +downer +downers +downfall +downfallen +downfalls +downgrade +downgraded +downgrades +downgrading +downhearted +downheartedly +downheartedness +downhill +downhills +downier +downiest +downing +download +downloaded +downloading +downloads +downplay +downplayed +downplaying +downplays +downpour +downpours +downrange +downright +downriver +downs +downscale +downscaled +downscales +downscaling +downshift +downshifted +downshifting +downshifts +downside +downsides +downsize +downsized +downsizes +downsizing +downspout +downspouts +downstage +downstairs +downstate +downstream +downswing +downswings +downtime +downtown +downtrend +downtrends +downtrodden +downturn +downturns +downward +downwards +downwind +downy +dowries +dowry +dowse +dowsed +dowser +dowsers +dowses +dowsing +doxologies +doxology +doyen +doyenne +doyennes +doyens +doze +dozed +dozen +dozens +dozenth +dozes +dozing +drab +drabber +drabbest +drably +drabness +drabs +drachma +drachmae +drachmai +drachmas +draconian +draft +drafted +draftee +draftees +drafter +drafters +draftier +draftiest +draftily +draftiness +drafting +drafts +draftsman +draftsmanship +draftsmen +draftswoman +draftswomen +drafty +drag +dragged +draggier +draggiest +dragging +draggy +dragnet +dragnets +dragon +dragonflies +dragonfly +dragons +dragoon +dragooned +dragooning +dragoons +drags +drain +drainage +drainboard +drainboards +drained +drainer +drainers +draining +drainpipe +drainpipes +drains +drake +drakes +dram +drama +dramas +dramatic +dramatically +dramatics +dramatist +dramatists +dramatization +dramatizations +dramatize +dramatized +dramatizes +dramatizing +drams +drank +drape +draped +draper +draperies +drapers +drapery +drapes +draping +drastic +drastically +drat +draught +draughted +draughtier +draughtiest +draughting +draughts +draughtsman +draughtsmen +draughty +draw +drawback +drawbacks +drawbridge +drawbridges +drawer +drawers +drawing +drawings +drawl +drawled +drawling +drawls +drawn +draws +drawstring +drawstrings +dray +drays +dread +dreaded +dreadful +dreadfully +dreadfulness +dreading +dreadlocks +dreadnaught +dreadnaughts +dreadnought +dreadnoughts +dreads +dream +dreamboat +dreamboats +dreamed +dreamer +dreamers +dreamier +dreamiest +dreamily +dreaminess +dreaming +dreamland +dreamless +dreamlike +dreams +dreamt +dreamworld +dreamworlds +dreamy +drear +drearier +dreariest +drearily +dreariness +dreary +dredge +dredged +dredger +dredgers +dredges +dredging +dregs +drench +drenched +drenches +drenching +dress +dressage +dressed +dresser +dressers +dresses +dressier +dressiest +dressiness +dressing +dressings +dressmaker +dressmakers +dressmaking +dressy +drest +drew +dribble +dribbled +dribbler +dribblers +dribbles +dribbling +driblet +driblets +dried +drier +driers +dries +driest +drift +drifted +drifter +drifters +drifting +drifts +driftwood +drill +drilled +driller +drillers +drilling +drillmaster +drillmasters +drills +drily +drink +drinkable +drinker +drinkers +drinking +drinks +drip +dripped +drippier +drippiest +dripping +drippings +drippy +drips +dript +drive +drivel +driveled +driveler +drivelers +driveling +drivelled +drivelling +drivels +driven +driver +drivers +drives +driveway +driveways +driving +drizzle +drizzled +drizzles +drizzlier +drizzliest +drizzling +drizzly +drogue +drogues +droll +droller +drolleries +drollery +drollest +drollness +drolly +dromedaries +dromedary +drone +droned +drones +droning +drool +drooled +drooling +drools +droop +drooped +droopier +droopiest +droopiness +drooping +droops +droopy +drop +dropkick +dropkicks +droplet +droplets +dropout +dropouts +dropped +dropper +droppers +dropping +droppings +drops +dropsical +dropsy +dross +drought +droughts +drouth +drouths +drove +drover +drovers +droves +drown +drowned +drowning +drownings +drowns +drowse +drowsed +drowses +drowsier +drowsiest +drowsily +drowsiness +drowsing +drowsy +drub +drubbed +drubber +drubbers +drubbing +drubbings +drubs +drudge +drudged +drudgery +drudges +drudging +drug +drugged +druggie +druggies +drugging +druggist +druggists +druggy +drugs +drugstore +drugstores +druid +druidism +druids +drum +drumbeat +drumbeats +drumlin +drumlins +drummed +drummer +drummers +drumming +drums +drumstick +drumsticks +drunk +drunkard +drunkards +drunken +drunkenly +drunkenness +drunker +drunkest +drunks +drupe +drupes +druthers +dryad +dryads +dryer +dryers +drying +dryly +dryness +drys +drywall +dual +dualism +duality +dubbed +dubber +dubbers +dubbin +dubbing +dubiety +dubious +dubiously +dubiousness +dubs +ducal +ducat +ducats +duchess +duchesses +duchies +duchy +duck +duckbill +duckbills +ducked +duckier +duckies +duckiest +ducking +duckling +ducklings +duckpins +ducks +duckweed +ducky +duct +ductile +ductility +ductless +ducts +dude +duded +dudes +dudgeon +duding +duds +duel +dueled +dueler +duelers +dueling +duelist +duelists +duelled +duelling +duellist +duellists +duels +duenna +duennas +dues +duet +duets +duff +duffer +duffers +duffs +dugout +dugouts +duke +dukedom +dukedoms +dukes +dulcet +dulcimer +dulcimers +dull +dullard +dullards +dulled +duller +dullest +dulling +dullness +dulls +dully +dulness +duly +dumb +dumbbell +dumbbells +dumber +dumbest +dumbfound +dumbfounded +dumbfounding +dumbfounds +dumbly +dumbness +dumbstruck +dumbwaiter +dumbwaiters +dumdum +dumdums +dumfound +dumfounded +dumfounding +dumfounds +dummies +dummy +dump +dumped +dumpier +dumpiest +dumpiness +dumping +dumpling +dumplings +dumps +dumpster +dumpsters +dumpy +dunce +dunces +dunderhead +dunderheads +dune +dunes +dung +dungaree +dungarees +dunged +dungeon +dungeons +dunghill +dunghills +dunging +dungs +dunk +dunked +dunking +dunks +dunned +dunner +dunnest +dunning +dunno +duns +duodecimal +duodena +duodenal +duodenum +duodenums +duos +dupe +duped +duper +dupers +dupes +duping +duple +duplex +duplexes +duplicate +duplicated +duplicates +duplicating +duplication +duplicator +duplicators +duplicitous +duplicity +durability +durable +durably +durance +duration +duress +during +durst +durum +dusk +duskier +duskiest +duskiness +dusky +dust +dusted +duster +dusters +dustier +dustiest +dustiness +dusting +dustless +dustpan +dustpans +dusts +dusty +dutch +duteous +duteously +dutiable +duties +dutiful +dutifully +dutifulness +duty +duvet +duvets +dwarf +dwarfed +dwarfing +dwarfish +dwarfism +dwarfs +dwarves +dweeb +dweebs +dwell +dwelled +dweller +dwellers +dwelling +dwellings +dwells +dwelt +dwindle +dwindled +dwindles +dwindling +dybbuk +dybbukim +dybbuks +dyed +dyeing +dyer +dyers +dyes +dyestuff +dying +dyke +dykes +dynamic +dynamical +dynamically +dynamics +dynamism +dynamite +dynamited +dynamiter +dynamiters +dynamites +dynamiting +dynamo +dynamos +dynastic +dynasties +dynasty +dysentery +dysfunction +dysfunctional +dysfunctions +dyslectic +dyslectics +dyslexia +dyslexic +dyslexics +dyspepsia +dyspeptic +dyspeptics +dysprosium +each +eager +eagerer +eagerest +eagerly +eagerness +eagle +eagles +eaglet +eaglets +earache +earaches +eardrum +eardrums +eared +earful +earfuls +earl +earldom +earldoms +earlier +earliest +earliness +earlobe +earlobes +earls +early +earmark +earmarked +earmarking +earmarks +earmuff +earmuffs +earn +earned +earner +earners +earnest +earnestly +earnestness +earnests +earning +earnings +earns +earphone +earphones +earplug +earplugs +earring +earrings +ears +earshot +earsplitting +earth +earthbound +earthed +earthen +earthenware +earthier +earthiest +earthiness +earthing +earthlier +earthliest +earthling +earthlings +earthly +earthquake +earthquakes +earths +earthshaking +earthward +earthwards +earthwork +earthworks +earthworm +earthworms +earthy +earwax +earwig +earwigs +ease +eased +easel +easels +easement +easements +eases +easier +easiest +easily +easiness +easing +east +eastbound +easterlies +easterly +eastern +easterner +easterners +easternmost +eastward +eastwards +easy +easygoing +eatable +eatables +eaten +eater +eateries +eaters +eatery +eating +eats +eave +eaves +eavesdrop +eavesdropped +eavesdropper +eavesdroppers +eavesdropping +eavesdrops +ebbed +ebbing +ebbs +ebonies +ebony +ebullience +ebullient +ebulliently +ebullition +eccentric +eccentrically +eccentricities +eccentricity +eccentrics +ecclesiastic +ecclesiastical +ecclesiastically +ecclesiastics +echelon +echelons +echinoderm +echinoderms +echo +echoed +echoes +echoic +echoing +echolocation +echos +eclair +eclairs +eclat +eclectic +eclectically +eclecticism +eclectics +eclipse +eclipsed +eclipses +eclipsing +ecliptic +eclogue +eclogues +ecocide +ecologic +ecological +ecologically +ecologist +ecologists +ecology +economic +economical +economically +economics +economies +economist +economists +economize +economized +economizer +economizers +economizes +economizing +economy +ecosystem +ecosystems +ecru +ecstasies +ecstasy +ecstatic +ecstatically +ecumenical +ecumenically +ecumenicism +ecumenism +eczema +eddied +eddies +eddy +eddying +edelweiss +edema +edge +edged +edger +edgers +edges +edgeways +edgewise +edgier +edgiest +edgily +edginess +edging +edgings +edgy +edibility +edible +edibleness +edibles +edict +edicts +edification +edifice +edifices +edified +edifier +edifiers +edifies +edify +edifying +edit +edited +editing +edition +editions +editor +editorial +editorialize +editorialized +editorializes +editorializing +editorially +editorials +editors +editorship +edits +educability +educable +educate +educated +educates +educating +education +educational +educationally +educations +educator +educators +educe +educed +educes +educing +edutainment +eels +eerie +eerier +eeriest +eerily +eeriness +eery +efface +effaced +effacement +effaces +effacing +effect +effected +effecting +effective +effectively +effectiveness +effects +effectual +effectually +effectuate +effectuated +effectuates +effectuating +effeminacy +effeminate +effeminately +effendi +effendis +efferent +effervesce +effervesced +effervescence +effervescent +effervescently +effervesces +effervescing +effete +effetely +effeteness +efficacious +efficaciously +efficacy +efficiencies +efficiency +efficient +efficiently +effigies +effigy +efflorescence +efflorescent +effluence +effluent +effluents +effluvia +effluvium +effluviums +effort +effortless +effortlessly +effortlessness +efforts +effrontery +effulgence +effulgent +effuse +effused +effuses +effusing +effusion +effusions +effusive +effusively +effusiveness +egad +egalitarian +egalitarianism +egalitarians +eggbeater +eggbeaters +eggcup +eggcups +egged +egghead +eggheads +egging +eggnog +eggplant +eggplants +eggs +eggshell +eggshells +egis +eglantine +eglantines +egocentric +egocentrically +egocentricity +egocentrics +egoism +egoist +egoistic +egoistical +egoistically +egoists +egomania +egomaniac +egomaniacs +egos +egotism +egotist +egotistic +egotistical +egotistically +egotists +egregious +egregiously +egregiousness +egress +egresses +egret +egrets +eider +eiderdown +eiderdowns +eiders +eight +eighteen +eighteens +eighteenth +eighteenths +eighth +eighths +eighties +eightieth +eightieths +eights +eighty +einsteinium +either +ejaculate +ejaculated +ejaculates +ejaculating +ejaculation +ejaculations +ejaculatory +eject +ejected +ejecting +ejection +ejections +ejector +ejectors +ejects +eked +ekes +eking +elaborate +elaborated +elaborately +elaborateness +elaborates +elaborating +elaboration +elaborations +elan +eland +elands +elapse +elapsed +elapses +elapsing +elastic +elastically +elasticity +elasticize +elasticized +elasticizes +elasticizing +elastics +elate +elated +elatedly +elates +elating +elation +elbow +elbowed +elbowing +elbowroom +elbows +elder +elderberries +elderberry +elderly +elders +eldest +elect +electable +elected +electing +election +electioneer +electioneered +electioneering +electioneers +elections +elective +electives +elector +electoral +electorate +electorates +electors +electric +electrical +electrically +electrician +electricians +electricity +electrification +electrified +electrifier +electrifiers +electrifies +electrify +electrifying +electrocardiogram +electrocardiograms +electrocardiograph +electrocardiographs +electrocardiography +electrocute +electrocuted +electrocutes +electrocuting +electrocution +electrocutions +electrode +electrodes +electroencephalogram +electroencephalograms +electroencephalograph +electroencephalographic +electroencephalographs +electroencephalography +electrologist +electrologists +electrolysis +electrolyte +electrolytes +electrolytic +electromagnet +electromagnetic +electromagnetically +electromagnetism +electromagnets +electromotive +electron +electronic +electronically +electronics +electrons +electroplate +electroplated +electroplates +electroplating +electroscope +electroscopes +electroscopic +electroshock +electrostatics +electrotype +electrotypes +elects +eleemosynary +elegance +elegant +elegantly +elegiac +elegiacal +elegiacs +elegies +elegy +element +elemental +elementally +elementary +elements +elephant +elephantiasis +elephantine +elephants +elevate +elevated +elevates +elevating +elevation +elevations +elevator +elevators +eleven +elevens +eleventh +elevenths +elfin +elfish +elicit +elicitation +elicited +eliciting +elicits +elide +elided +elides +eliding +eligibility +eligible +eliminate +eliminated +eliminates +eliminating +elimination +eliminations +elision +elisions +elite +elites +elitism +elitist +elitists +elixir +elixirs +elks +ellipse +ellipses +ellipsis +ellipsoid +ellipsoidal +ellipsoids +elliptic +elliptical +elliptically +ells +elms +elocution +elocutionary +elocutionist +elocutionists +elodea +elodeas +elongate +elongated +elongates +elongating +elongation +elongations +elope +eloped +elopement +elopements +elopes +eloping +eloquence +eloquent +eloquently +else +elsewhere +elucidate +elucidated +elucidates +elucidating +elucidation +elucidations +elude +eluded +eludes +eluding +elusive +elusively +elusiveness +elver +elvers +elves +emaciate +emaciated +emaciates +emaciating +emaciation +email +emailed +emailing +emails +emanate +emanated +emanates +emanating +emanation +emanations +emancipate +emancipated +emancipates +emancipating +emancipation +emancipator +emancipators +emasculate +emasculated +emasculates +emasculating +emasculation +embalm +embalmed +embalmer +embalmers +embalming +embalms +embank +embanked +embanking +embankment +embankments +embanks +embargo +embargoed +embargoes +embargoing +embark +embarkation +embarkations +embarked +embarking +embarks +embarrass +embarrassed +embarrasses +embarrassing +embarrassingly +embarrassment +embarrassments +embassies +embassy +embattled +embed +embedded +embedding +embeds +embellish +embellished +embellishes +embellishing +embellishment +embellishments +ember +embers +embezzle +embezzled +embezzlement +embezzler +embezzlers +embezzles +embezzling +embitter +embittered +embittering +embitterment +embitters +emblazon +emblazoned +emblazoning +emblazonment +emblazons +emblem +emblematic +emblems +embodied +embodies +embodiment +embody +embodying +embolden +emboldened +emboldening +emboldens +embolism +embolisms +emboss +embossed +embosser +embossers +embosses +embossing +embouchure +embower +embowered +embowering +embowers +embrace +embraceable +embraced +embraces +embracing +embrasure +embrasures +embrocation +embrocations +embroider +embroidered +embroiderer +embroiderers +embroideries +embroidering +embroiders +embroidery +embroil +embroiled +embroiling +embroilment +embroils +embryo +embryologist +embryologists +embryology +embryonic +embryos +emcee +emceed +emceeing +emcees +emend +emendation +emendations +emended +emending +emends +emerald +emeralds +emerge +emerged +emergence +emergencies +emergency +emergent +emerges +emerging +emerita +emeritus +emery +emetic +emetics +emigrant +emigrants +emigrate +emigrated +emigrates +emigrating +emigration +emigrations +emigre +emigres +eminence +eminences +eminent +eminently +emir +emirate +emirates +emirs +emissaries +emissary +emission +emissions +emit +emits +emitted +emitter +emitters +emitting +emollient +emollients +emolument +emoluments +emote +emoted +emotes +emoticon +emoticons +emoting +emotion +emotional +emotionalism +emotionalize +emotionalized +emotionalizes +emotionalizing +emotionally +emotions +emotive +empanel +empaneled +empaneling +empanelled +empanelling +empanels +empathetic +empathize +empathized +empathizes +empathizing +empathy +emperor +emperors +emphases +emphasis +emphasize +emphasized +emphasizes +emphasizing +emphatic +emphatically +emphysema +empire +empires +empiric +empirical +empirically +empiricism +empiricist +empiricists +emplacement +emplacements +employ +employable +employe +employed +employee +employees +employer +employers +employes +employing +employment +employments +employs +emporia +emporium +emporiums +empower +empowered +empowering +empowerment +empowers +empress +empresses +emptied +emptier +empties +emptiest +emptily +emptiness +empty +emptying +empyrean +emulate +emulated +emulates +emulating +emulation +emulations +emulative +emulator +emulators +emulsification +emulsified +emulsifier +emulsifiers +emulsifies +emulsify +emulsifying +emulsion +emulsions +emus +enable +enabled +enabler +enablers +enables +enabling +enact +enacted +enacting +enactment +enactments +enacts +enamel +enameled +enameler +enamelers +enameling +enamelled +enamelling +enamels +enamelware +enamor +enamored +enamoring +enamors +enamour +enamoured +enamouring +enamours +encamp +encamped +encamping +encampment +encampments +encamps +encapsulate +encapsulated +encapsulates +encapsulating +encapsulation +encapsulations +encase +encased +encasement +encases +encasing +encephalitic +encephalitis +enchain +enchained +enchaining +enchains +enchant +enchanted +enchanter +enchanters +enchanting +enchantingly +enchantment +enchantments +enchantress +enchantresses +enchants +enchilada +enchiladas +encipher +enciphered +enciphering +enciphers +encircle +encircled +encirclement +encircles +encircling +enclave +enclaves +enclose +enclosed +encloses +enclosing +enclosure +enclosures +encode +encoded +encoder +encoders +encodes +encoding +encomia +encomium +encomiums +encompass +encompassed +encompasses +encompassing +encore +encored +encores +encoring +encounter +encountered +encountering +encounters +encourage +encouraged +encouragement +encouragements +encourages +encouraging +encouragingly +encroach +encroached +encroaches +encroaching +encroachment +encroachments +encrust +encrustation +encrustations +encrusted +encrusting +encrusts +encumber +encumbered +encumbering +encumbers +encumbrance +encumbrances +encyclical +encyclicals +encyclopaedia +encyclopaedias +encyclopaedic +encyclopedia +encyclopedias +encyclopedic +encyst +encysted +encysting +encystment +encysts +endanger +endangered +endangering +endangerment +endangers +endear +endeared +endearing +endearingly +endearment +endearments +endears +endeavor +endeavored +endeavoring +endeavors +endeavour +endeavoured +endeavouring +endeavours +ended +endemic +endemically +endemics +ending +endings +endive +endives +endless +endlessly +endlessness +endmost +endocrine +endocrines +endocrinologist +endocrinologists +endocrinology +endogenous +endogenously +endorphin +endorphins +endorse +endorsed +endorsement +endorsements +endorser +endorsers +endorses +endorsing +endoscope +endoscopes +endoscopic +endoscopy +endothermic +endow +endowed +endowing +endowment +endowments +endows +endpoint +endpoints +ends +endue +endued +endues +enduing +endurable +endurance +endure +endured +endures +enduring +endways +endwise +enema +enemas +enemata +enemies +enemy +energetic +energetically +energies +energize +energized +energizer +energizers +energizes +energizing +energy +enervate +enervated +enervates +enervating +enervation +enfeeble +enfeebled +enfeeblement +enfeebles +enfeebling +enfilade +enfiladed +enfilades +enfilading +enfold +enfolded +enfolding +enfolds +enforce +enforceable +enforced +enforcement +enforcer +enforcers +enforces +enforcing +enfranchise +enfranchised +enfranchisement +enfranchises +enfranchising +engage +engaged +engagement +engagements +engages +engaging +engagingly +engender +engendered +engendering +engenders +engine +engineer +engineered +engineering +engineers +engines +engorge +engorged +engorgement +engorges +engorging +engram +engrams +engrave +engraved +engraver +engravers +engraves +engraving +engravings +engross +engrossed +engrosses +engrossing +engrossment +engulf +engulfed +engulfing +engulfment +engulfs +enhance +enhanced +enhancement +enhancements +enhances +enhancing +enigma +enigmas +enigmatic +enigmatically +enjambement +enjambements +enjambment +enjambments +enjoin +enjoined +enjoining +enjoins +enjoy +enjoyable +enjoyably +enjoyed +enjoying +enjoyment +enjoyments +enjoys +enlarge +enlargeable +enlarged +enlargement +enlargements +enlarger +enlargers +enlarges +enlarging +enlighten +enlightened +enlightening +enlightenment +enlightens +enlist +enlisted +enlistee +enlistees +enlisting +enlistment +enlistments +enlists +enliven +enlivened +enlivening +enlivenment +enlivens +enmesh +enmeshed +enmeshes +enmeshing +enmeshment +enmities +enmity +ennoble +ennobled +ennoblement +ennobles +ennobling +ennui +enormities +enormity +enormous +enormously +enormousness +enough +enplane +enplaned +enplanes +enplaning +enquire +enquired +enquires +enquiries +enquiring +enquiry +enrage +enraged +enrages +enraging +enrapture +enraptured +enraptures +enrapturing +enrich +enriched +enriches +enriching +enrichment +enrol +enroll +enrolled +enrolling +enrollment +enrollments +enrolls +enrolment +enrolments +enrols +ensconce +ensconced +ensconces +ensconcing +ensemble +ensembles +enshrine +enshrined +enshrinement +enshrines +enshrining +enshroud +enshrouded +enshrouding +enshrouds +ensign +ensigns +ensilage +enslave +enslaved +enslavement +enslaves +enslaving +ensnare +ensnared +ensnarement +ensnares +ensnaring +ensue +ensued +ensues +ensuing +ensure +ensured +ensurer +ensurers +ensures +ensuring +entail +entailed +entailing +entailment +entails +entangle +entangled +entanglement +entanglements +entangles +entangling +entente +ententes +enter +entered +entering +enteritis +enterprise +enterprises +enterprising +enterprisingly +enters +entertain +entertained +entertainer +entertainers +entertaining +entertainingly +entertainment +entertainments +entertains +enthral +enthrall +enthralled +enthralling +enthrallment +enthralls +enthrals +enthrone +enthroned +enthronement +enthronements +enthrones +enthroning +enthuse +enthused +enthuses +enthusiasm +enthusiasms +enthusiast +enthusiastic +enthusiastically +enthusiasts +enthusing +entice +enticed +enticement +enticements +entices +enticing +enticingly +entire +entirely +entirety +entities +entitle +entitled +entitlement +entitlements +entitles +entitling +entity +entomb +entombed +entombing +entombment +entombs +entomological +entomologist +entomologists +entomology +entourage +entourages +entrails +entrance +entranced +entrancement +entrances +entrancing +entrancingly +entrant +entrants +entrap +entrapment +entrapped +entrapping +entraps +entreat +entreated +entreaties +entreating +entreatingly +entreats +entreaty +entree +entrees +entrench +entrenched +entrenches +entrenching +entrenchment +entrenchments +entrepreneur +entrepreneurial +entrepreneurs +entries +entropy +entrust +entrusted +entrusting +entrusts +entry +entryway +entryways +entwine +entwined +entwines +entwining +enumerable +enumerate +enumerated +enumerates +enumerating +enumeration +enumerations +enumerator +enumerators +enunciate +enunciated +enunciates +enunciating +enunciation +enure +enured +enures +enuresis +enuring +envelop +envelope +enveloped +enveloper +envelopers +envelopes +enveloping +envelopment +envelops +envenom +envenomed +envenoming +envenoms +enviable +enviably +envied +envies +envious +enviously +enviousness +environment +environmental +environmentalism +environmentalist +environmentalists +environmentally +environments +environs +envisage +envisaged +envisages +envisaging +envision +envisioned +envisioning +envisions +envoy +envoys +envy +envying +envyingly +enzymatic +enzyme +enzymes +eolian +eons +epaulet +epaulets +epaulette +epaulettes +epee +epees +ephedrine +ephemera +ephemeral +ephemerally +epic +epicenter +epicenters +epicentre +epicentres +epics +epicure +epicurean +epicureans +epicures +epidemic +epidemically +epidemics +epidemiologist +epidemiologists +epidemiology +epidermal +epidermic +epidermis +epidermises +epiglottides +epiglottis +epiglottises +epigram +epigrammatic +epigrams +epigraph +epigraphs +epigraphy +epilepsy +epileptic +epileptics +epilog +epilogs +epilogue +epilogues +epinephrin +epinephrine +epiphanies +epiphany +episcopacy +episcopal +episcopate +episode +episodes +episodic +episodically +epistle +epistles +epistolary +epitaph +epitaphs +epithelial +epithelium +epithet +epithets +epitome +epitomes +epitomize +epitomized +epitomizes +epitomizing +epoch +epochal +epochs +epoxied +epoxies +epoxy +epoxyed +epoxying +epsilon +epsilons +equability +equable +equably +equal +equaled +equaling +equality +equalization +equalize +equalized +equalizer +equalizers +equalizes +equalizing +equalled +equalling +equally +equals +equanimity +equatable +equate +equated +equates +equating +equation +equations +equator +equatorial +equators +equerries +equerry +equestrian +equestrianism +equestrians +equestrienne +equestriennes +equidistant +equidistantly +equilateral +equilaterals +equilibrium +equine +equines +equinoctial +equinox +equinoxes +equip +equipage +equipages +equipment +equipoise +equipped +equipping +equips +equitable +equitably +equitation +equities +equity +equivalence +equivalences +equivalencies +equivalency +equivalent +equivalently +equivalents +equivocal +equivocally +equivocalness +equivocate +equivocated +equivocates +equivocating +equivocation +equivocations +equivocator +equivocators +eradicable +eradicate +eradicated +eradicates +eradicating +eradication +eradicator +eradicators +eras +erasable +erase +erased +eraser +erasers +erases +erasing +erasure +erasures +erbium +erect +erected +erectile +erecting +erection +erections +erectly +erectness +erector +erectors +erects +erelong +eremite +eremites +ergo +ergonomic +ergonomically +ergonomics +ergosterol +ergot +ergs +ermine +ermines +erode +eroded +erodes +erodible +eroding +erogenous +erosion +erosive +erotic +erotica +erotically +eroticism +errand +errands +errant +errata +erratas +erratic +erratically +erratum +erred +erring +erroneous +erroneously +error +errors +errs +ersatz +ersatzes +erst +erstwhile +eruct +eructation +eructations +eructed +eructing +eructs +erudite +eruditely +erudition +erupt +erupted +erupting +eruption +eruptions +eruptive +erupts +erysipelas +erythrocyte +erythrocytes +escalate +escalated +escalates +escalating +escalation +escalations +escalator +escalators +escallop +escalloped +escalloping +escallops +escalop +escalops +escapade +escapades +escape +escaped +escapee +escapees +escapement +escapements +escapes +escaping +escapism +escapist +escapists +escargot +escargots +escarole +escaroles +escarpment +escarpments +eschew +eschewed +eschewing +eschews +escort +escorted +escorting +escorts +escritoire +escritoires +escrow +escrows +escudo +escudos +escutcheon +escutcheons +esophageal +esophagi +esophagus +esoteric +esoterically +espadrille +espadrilles +espalier +espaliered +espaliering +espaliers +especial +especially +espied +espies +espionage +esplanade +esplanades +espousal +espouse +espoused +espouses +espousing +espresso +espressos +esprit +espy +espying +esquire +esquires +essay +essayed +essayer +essayers +essaying +essayist +essayists +essays +essence +essences +essential +essentially +essentials +establish +established +establishes +establishing +establishment +establishments +estate +estates +esteem +esteemed +esteeming +esteems +ester +esters +esthete +esthetes +esthetic +esthetically +esthetics +estimable +estimate +estimated +estimates +estimating +estimation +estimations +estimator +estimators +estrange +estranged +estrangement +estrangements +estranges +estranging +estrogen +estrous +estrus +estruses +estuaries +estuary +etas +etch +etched +etcher +etchers +etches +etching +etchings +eternal +eternally +eternalness +eternities +eternity +ethane +ethanol +ether +ethereal +ethereally +ethic +ethical +ethically +ethics +ethnic +ethnically +ethnicity +ethnics +ethnocentric +ethnocentrism +ethnological +ethnologist +ethnologists +ethnology +ethological +ethologist +ethologists +ethology +ethos +ethyl +ethylene +etiologic +etiological +etiologies +etiology +etiquette +etude +etudes +etymological +etymologically +etymologies +etymologist +etymologists +etymology +eucalypti +eucalyptus +eucalyptuses +euchre +euchred +euchres +euchring +euclidean +eugenic +eugenically +eugenicist +eugenicists +eugenics +eulogies +eulogist +eulogistic +eulogists +eulogize +eulogized +eulogizer +eulogizers +eulogizes +eulogizing +eulogy +eunuch +eunuchs +euphemism +euphemisms +euphemistic +euphemistically +euphonious +euphoniously +euphony +euphoria +euphoric +euphorically +eureka +euro +europium +euros +euthanasia +euthanize +euthanized +euthanizes +euthanizing +euthenics +evacuate +evacuated +evacuates +evacuating +evacuation +evacuations +evacuee +evacuees +evade +evaded +evader +evaders +evades +evading +evaluate +evaluated +evaluates +evaluating +evaluation +evaluations +evanescence +evanescent +evangelic +evangelical +evangelicalism +evangelically +evangelicals +evangelism +evangelist +evangelistic +evangelists +evangelize +evangelized +evangelizes +evangelizing +evaporate +evaporated +evaporates +evaporating +evaporation +evaporator +evaporators +evasion +evasions +evasive +evasively +evasiveness +even +evened +evener +evenest +evenhanded +evenhandedly +evening +evenings +evenly +evenness +evens +evensong +event +eventful +eventfully +eventfulness +eventide +events +eventual +eventualities +eventuality +eventually +eventuate +eventuated +eventuates +eventuating +ever +everglade +everglades +evergreen +evergreens +everlasting +everlastingly +everlastings +evermore +every +everybody +everyday +everyone +everyplace +everything +everywhere +eves +evict +evicted +evicting +eviction +evictions +evicts +evidence +evidenced +evidences +evidencing +evident +evidently +evil +evildoer +evildoers +evildoing +eviler +evilest +eviller +evillest +evilly +evilness +evils +evince +evinced +evinces +evincing +eviscerate +eviscerated +eviscerates +eviscerating +evisceration +evocation +evocations +evocative +evocatively +evoke +evoked +evokes +evoking +evolution +evolutionary +evolutionist +evolutionists +evolve +evolved +evolves +evolving +ewer +ewers +ewes +exacerbate +exacerbated +exacerbates +exacerbating +exacerbation +exact +exacted +exacter +exactest +exacting +exactingly +exaction +exactitude +exactly +exactness +exacts +exaggerate +exaggerated +exaggeratedly +exaggerates +exaggerating +exaggeration +exaggerations +exaggerator +exaggerators +exalt +exaltation +exalted +exalting +exalts +exam +examination +examinations +examine +examined +examiner +examiners +examines +examining +example +exampled +examples +exampling +exams +exasperate +exasperated +exasperates +exasperating +exasperation +excavate +excavated +excavates +excavating +excavation +excavations +excavator +excavators +exceed +exceeded +exceeding +exceedingly +exceeds +excel +excelled +excellence +excellencies +excellency +excellent +excellently +excelling +excels +excelsior +except +excepted +excepting +exception +exceptionable +exceptional +exceptionally +exceptions +excepts +excerpt +excerpted +excerpting +excerpts +excess +excesses +excessive +excessively +exchange +exchangeable +exchanged +exchanges +exchanging +exchequer +exchequers +excise +excised +excises +excising +excision +excisions +excitability +excitable +excitably +excitation +excite +excited +excitedly +excitement +excitements +exciter +exciters +excites +exciting +excitingly +exclaim +exclaimed +exclaiming +exclaims +exclamation +exclamations +exclamatory +exclude +excluded +excludes +excluding +exclusion +exclusive +exclusively +exclusiveness +exclusives +exclusivity +excommunicate +excommunicated +excommunicates +excommunicating +excommunication +excommunications +excoriate +excoriated +excoriates +excoriating +excoriation +excoriations +excrement +excremental +excrescence +excrescences +excrescent +excreta +excrete +excreted +excretes +excreting +excretion +excretions +excretory +excruciating +excruciatingly +exculpate +exculpated +exculpates +exculpating +exculpation +exculpatory +excursion +excursionist +excursionists +excursions +excursive +excursively +excursiveness +excusable +excusably +excuse +excused +excuses +excusing +exec +execrable +execrably +execrate +execrated +execrates +execrating +execration +execs +execute +executed +executes +executing +execution +executioner +executioners +executions +executive +executives +executor +executors +executrices +executrix +executrixes +exegeses +exegesis +exegetic +exegetical +exemplar +exemplars +exemplary +exemplification +exemplifications +exemplified +exemplifies +exemplify +exemplifying +exempt +exempted +exempting +exemption +exemptions +exempts +exercise +exercised +exerciser +exercisers +exercises +exercising +exert +exerted +exerting +exertion +exertions +exerts +exes +exhalation +exhalations +exhale +exhaled +exhales +exhaling +exhaust +exhausted +exhaustible +exhausting +exhaustion +exhaustive +exhaustively +exhaustiveness +exhausts +exhibit +exhibited +exhibiting +exhibition +exhibitionism +exhibitionist +exhibitionists +exhibitions +exhibitor +exhibitors +exhibits +exhilarate +exhilarated +exhilarates +exhilarating +exhilaration +exhort +exhortation +exhortations +exhorted +exhorting +exhorts +exhumation +exhumations +exhume +exhumed +exhumes +exhuming +exigence +exigences +exigencies +exigency +exigent +exiguity +exiguous +exile +exiled +exiles +exiling +exist +existed +existence +existences +existent +existential +existentialism +existentialist +existentialists +existentially +existing +exists +exit +exited +exiting +exits +exobiology +exodus +exoduses +exogenous +exonerate +exonerated +exonerates +exonerating +exoneration +exorbitance +exorbitant +exorbitantly +exorcise +exorcised +exorcises +exorcising +exorcism +exorcisms +exorcist +exorcists +exorcize +exorcized +exorcizes +exorcizing +exoskeleton +exoskeletons +exosphere +exospheres +exothermic +exotic +exotically +exoticism +exotics +expand +expandable +expanded +expanding +expands +expanse +expanses +expansible +expansion +expansionary +expansionism +expansionist +expansionists +expansions +expansive +expansively +expansiveness +expatiate +expatiated +expatiates +expatiating +expatiation +expatriate +expatriated +expatriates +expatriating +expatriation +expect +expectancy +expectant +expectantly +expectation +expectations +expected +expecting +expectorant +expectorants +expectorate +expectorated +expectorates +expectorating +expectoration +expects +expedience +expediences +expediencies +expediency +expedient +expediently +expedients +expedite +expedited +expediter +expediters +expedites +expediting +expedition +expeditionary +expeditions +expeditious +expeditiously +expeditiousness +expeditor +expeditors +expel +expelled +expelling +expels +expend +expendable +expendables +expended +expending +expenditure +expenditures +expends +expense +expenses +expensive +expensively +expensiveness +experience +experienced +experiences +experiencing +experiment +experimental +experimentally +experimentation +experimented +experimenter +experimenters +experimenting +experiments +expert +expertise +expertly +expertness +experts +expiate +expiated +expiates +expiating +expiation +expiatory +expiration +expire +expired +expires +expiring +expiry +explain +explainable +explained +explaining +explains +explanation +explanations +explanatory +expletive +expletives +explicable +explicate +explicated +explicates +explicating +explication +explications +explicit +explicitly +explicitness +explode +exploded +explodes +exploding +exploit +exploitable +exploitation +exploitative +exploited +exploiter +exploiters +exploiting +exploits +exploration +explorations +exploratory +explore +explored +explorer +explorers +explores +exploring +explosion +explosions +explosive +explosively +explosiveness +explosives +expo +exponent +exponential +exponentially +exponents +export +exportable +exportation +exported +exporter +exporters +exporting +exports +expos +expose +exposed +exposes +exposing +exposition +expositions +expositor +expositors +expository +expostulate +expostulated +expostulates +expostulating +expostulation +expostulations +exposure +exposures +expound +expounded +expounder +expounders +expounding +expounds +express +expressed +expresses +expressible +expressing +expression +expressionism +expressionist +expressionistic +expressionists +expressionless +expressions +expressive +expressively +expressiveness +expressly +expressway +expressways +expropriate +expropriated +expropriates +expropriating +expropriation +expropriations +expropriator +expropriators +expulsion +expulsions +expunge +expunged +expunges +expunging +expurgate +expurgated +expurgates +expurgating +expurgation +expurgations +exquisite +exquisitely +exquisiteness +extant +extemporaneous +extemporaneously +extemporaneousness +extempore +extemporization +extemporize +extemporized +extemporizes +extemporizing +extend +extendable +extended +extender +extenders +extendible +extending +extends +extensible +extension +extensions +extensive +extensively +extensiveness +extent +extents +extenuate +extenuated +extenuates +extenuating +extenuation +exterior +exteriors +exterminate +exterminated +exterminates +exterminating +extermination +exterminations +exterminator +exterminators +external +externalization +externalizations +externalize +externalized +externalizes +externalizing +externally +externals +extinct +extincted +extincting +extinction +extinctions +extincts +extinguish +extinguishable +extinguished +extinguisher +extinguishers +extinguishes +extinguishing +extirpate +extirpated +extirpates +extirpating +extirpation +extol +extoll +extolled +extolling +extolls +extols +extort +extorted +extorting +extortion +extortionate +extortionately +extortioner +extortioners +extortionist +extortionists +extorts +extra +extract +extracted +extracting +extraction +extractions +extractor +extractors +extracts +extracurricular +extraditable +extradite +extradited +extradites +extraditing +extradition +extraditions +extralegal +extramarital +extramural +extraneous +extraneously +extraordinarily +extraordinary +extrapolate +extrapolated +extrapolates +extrapolating +extrapolation +extrapolations +extras +extrasensory +extraterrestrial +extraterrestrials +extraterritorial +extraterritoriality +extravagance +extravagances +extravagant +extravagantly +extravaganza +extravaganzas +extravehicular +extravert +extraverts +extreme +extremely +extremeness +extremer +extremes +extremest +extremism +extremist +extremists +extremities +extremity +extricable +extricate +extricated +extricates +extricating +extrication +extrinsic +extrinsically +extroversion +extrovert +extroverted +extroverts +extrude +extruded +extrudes +extruding +extrusion +extrusions +extrusive +exuberance +exuberant +exuberantly +exudation +exude +exuded +exudes +exuding +exult +exultant +exultantly +exultation +exulted +exulting +exults +exurb +exurban +exurbanite +exurbanites +exurbia +exurbs +eyeball +eyeballed +eyeballing +eyeballs +eyebrow +eyebrows +eyed +eyedropper +eyedroppers +eyeful +eyefuls +eyeglass +eyeglasses +eyeing +eyelash +eyelashes +eyeless +eyelet +eyelets +eyelid +eyelids +eyeliner +eyeliners +eyeopener +eyeopeners +eyeopening +eyepiece +eyepieces +eyes +eyesight +eyesore +eyesores +eyestrain +eyeteeth +eyetooth +eyewash +eyewitness +eyewitnesses +eying +eyrie +eyries +eyry +fable +fabled +fables +fabric +fabricate +fabricated +fabricates +fabricating +fabrication +fabrications +fabricator +fabricators +fabrics +fabulous +fabulously +facade +facades +face +facecloth +facecloths +faced +faceless +facelift +facelifts +faces +facet +faceted +faceting +facetious +facetiously +facetiousness +facets +facetted +facetting +facial +facially +facials +facile +facilely +facilitate +facilitated +facilitates +facilitating +facilitation +facilitator +facilitators +facilities +facility +facing +facings +facsimile +facsimiled +facsimileing +facsimiles +fact +faction +factional +factionalism +factions +factious +factitious +factoid +factoids +factor +factored +factorial +factorials +factories +factoring +factors +factory +factotum +factotums +facts +factual +factually +faculties +faculty +faddish +faddist +faddists +fade +faded +fades +fading +fads +faecal +faeces +faerie +faeries +faery +fagged +fagging +faggot +faggoting +faggots +fagot +fagoting +fagots +fags +faience +fail +failed +failing +failings +faille +fails +failure +failures +fain +fainer +fainest +faint +fainted +fainter +faintest +fainthearted +fainting +faintly +faintness +faints +fair +fairer +fairest +fairground +fairgrounds +fairies +fairing +fairings +fairly +fairness +fairs +fairway +fairways +fairy +fairyland +fairylands +faith +faithful +faithfully +faithfulness +faithfuls +faithless +faithlessly +faithlessness +faiths +fajita +fajitas +fake +faked +faker +fakers +fakes +faking +fakir +fakirs +falcon +falconer +falconers +falconry +falcons +fall +fallacies +fallacious +fallaciously +fallacy +fallen +fallibility +fallible +fallibleness +fallibly +falling +falloff +falloffs +fallout +fallow +fallowed +fallowing +fallows +falls +false +falsehood +falsehoods +falsely +falseness +falser +falsest +falsetto +falsettos +falsie +falsies +falsification +falsifications +falsified +falsifier +falsifiers +falsifies +falsify +falsifying +falsities +falsity +falter +faltered +faltering +falteringly +falters +fame +famed +familial +familiar +familiarity +familiarization +familiarize +familiarized +familiarizes +familiarizing +familiarly +familiars +families +family +famine +famines +famish +famished +famishes +famishing +famous +famously +fanatic +fanatical +fanatically +fanaticism +fanatics +fancied +fancier +fanciers +fancies +fanciest +fanciful +fancifully +fancifulness +fancily +fanciness +fancy +fancying +fancywork +fandango +fandangoes +fandangos +fanfare +fanfares +fang +fanged +fangs +fanlight +fanlights +fanned +fannies +fanning +fanny +fans +fantail +fantails +fantasia +fantasias +fantasied +fantasies +fantasize +fantasized +fantasizes +fantasizing +fantastic +fantastical +fantastically +fantasy +fantasying +fanzine +fanzines +farad +farads +faraway +farce +farces +farcical +farcically +fare +fared +fares +farewell +farewells +farfetched +farina +farinaceous +faring +farm +farmed +farmer +farmers +farmhand +farmhands +farmhouse +farmhouses +farming +farmland +farms +farmstead +farmsteads +farmyard +farmyards +faro +farrago +farragoes +farragos +farrier +farriers +farrow +farrowed +farrowing +farrows +farseeing +farsighted +farsightedness +fart +farted +farther +farthermost +farthest +farthing +farthings +farting +farts +fascia +fasciae +fascias +fascicle +fascicles +fascinate +fascinated +fascinates +fascinating +fascinatingly +fascination +fascinations +fascism +fascist +fascistic +fascists +fashion +fashionable +fashionably +fashioned +fashioner +fashioners +fashioning +fashions +fast +fastback +fastbacks +fastball +fastballs +fasted +fasten +fastened +fastener +fasteners +fastening +fastenings +fastens +faster +fastest +fastidious +fastidiously +fastidiousness +fasting +fastness +fastnesses +fasts +fatal +fatalism +fatalist +fatalistic +fatalistically +fatalists +fatalities +fatality +fatally +fatback +fate +fated +fateful +fatefully +fatefulness +fates +fathead +fatheaded +fatheads +father +fathered +fatherhood +fathering +fatherland +fatherlands +fatherless +fatherly +fathers +fathom +fathomable +fathomed +fathoming +fathomless +fathoms +fatigue +fatigued +fatigues +fatiguing +fating +fatness +fats +fatten +fattened +fattening +fattens +fatter +fattest +fattier +fatties +fattiest +fattiness +fatty +fatuity +fatuous +fatuously +fatuousness +fatwa +fatwas +faucet +faucets +fault +faulted +faultfinder +faultfinders +faultfinding +faultier +faultiest +faultily +faultiness +faulting +faultless +faultlessly +faultlessness +faults +faulty +faun +fauna +faunae +faunas +fauns +fauvism +fauvist +fauvists +favor +favorable +favorably +favored +favoring +favorite +favorites +favoritism +favors +favour +favoured +favouring +favours +fawn +fawned +fawner +fawners +fawnest +fawning +fawns +faxed +faxes +faxing +fayer +fayest +fays +faze +fazed +fazes +fazing +fealty +fear +feared +fearful +fearfully +fearfulness +fearing +fearless +fearlessly +fearlessness +fears +fearsome +feasibility +feasible +feasibly +feast +feasted +feaster +feasters +feasting +feasts +feat +feather +featherbedding +feathered +featherier +featheriest +feathering +featherless +feathers +featherweight +featherweights +feathery +feats +feature +featured +featureless +features +featuring +febrile +fecal +feces +feckless +fecklessly +fecund +fecundate +fecundated +fecundates +fecundating +fecundation +fecundity +federal +federalism +federalist +federalists +federalization +federalize +federalized +federalizes +federalizing +federally +federals +federate +federated +federates +federating +federation +federations +fedora +fedoras +feds +feeble +feebleness +feebler +feeblest +feebly +feed +feedback +feedbag +feedbags +feeder +feeders +feeding +feedings +feedlot +feedlots +feeds +feel +feeler +feelers +feeling +feelingly +feelings +feels +fees +feet +feign +feigned +feigning +feigns +feint +feinted +feinting +feints +feistier +feistiest +feisty +feldspar +felicitate +felicitated +felicitates +felicitating +felicitation +felicitations +felicities +felicitous +felicitously +felicity +feline +felines +fell +fellatio +felled +feller +fellest +felling +fellow +fellowman +fellowmen +fellows +fellowship +fellowships +fells +felon +felonies +felonious +felons +felony +felt +felted +felting +felts +female +femaleness +females +feminine +femininely +feminines +femininity +feminism +feminist +feminists +femora +femoral +femur +femurs +fence +fenced +fencer +fencers +fences +fencing +fend +fended +fender +fenders +fending +fends +fenestration +fennel +fens +feral +ferment +fermentation +fermented +fermenting +ferments +fermium +fern +fernier +ferniest +ferns +ferny +ferocious +ferociously +ferociousness +ferocity +ferret +ferreted +ferreting +ferrets +ferric +ferried +ferries +ferromagnetic +ferrous +ferrule +ferrules +ferry +ferryboat +ferryboats +ferrying +ferryman +ferrymen +fertile +fertility +fertilization +fertilize +fertilized +fertilizer +fertilizers +fertilizes +fertilizing +ferule +ferules +fervency +fervent +fervently +fervid +fervidly +fervor +fervour +fess +fessed +fesses +fessing +fest +festal +fester +festered +festering +festers +festival +festivals +festive +festively +festiveness +festivities +festivity +festoon +festooned +festooning +festoons +fests +feta +fetal +fetch +fetched +fetcher +fetchers +fetches +fetching +fetchingly +fete +feted +fetes +fetich +fetiches +fetid +fetidness +feting +fetish +fetishes +fetishism +fetishist +fetishistic +fetishists +fetlock +fetlocks +fetter +fettered +fettering +fetters +fettle +fettuccine +fetus +fetuses +feud +feudal +feudalism +feudalistic +feuded +feuding +feuds +fever +fevered +feverish +feverishly +feverishness +fevers +fewer +fewest +fewness +fezes +fezzes +fiance +fiancee +fiancees +fiances +fiasco +fiascoes +fiascos +fiat +fiats +fibbed +fibber +fibbers +fibbing +fiber +fiberboard +fiberfill +fiberglass +fibers +fibre +fibres +fibril +fibrillate +fibrillated +fibrillates +fibrillating +fibrillation +fibrils +fibrin +fibroid +fibrosis +fibrous +fibs +fibula +fibulae +fibular +fibulas +fiche +fiches +fichu +fichus +fickle +fickleness +fickler +ficklest +fiction +fictional +fictionalization +fictionalizations +fictionalize +fictionalized +fictionalizes +fictionalizing +fictionally +fictions +fictitious +fictitiously +fictive +ficus +fiddle +fiddled +fiddler +fiddlers +fiddles +fiddlesticks +fiddling +fidelity +fidget +fidgeted +fidgeting +fidgets +fidgety +fiduciaries +fiduciary +fief +fiefdom +fiefdoms +fiefs +field +fielded +fielder +fielders +fielding +fields +fieldwork +fieldworker +fieldworkers +fiend +fiendish +fiendishly +fiends +fierce +fiercely +fierceness +fiercer +fiercest +fierier +fieriest +fieriness +fiery +fiesta +fiestas +fife +fifer +fifers +fifes +fifteen +fifteens +fifteenth +fifteenths +fifth +fifthly +fifths +fifties +fiftieth +fiftieths +fifty +fight +fighter +fighters +fighting +fights +figment +figments +figs +figuration +figurative +figuratively +figure +figured +figurehead +figureheads +figures +figurine +figurines +figuring +filament +filamentous +filaments +filbert +filberts +filch +filched +filches +filching +file +filed +filer +filers +files +filet +fileted +fileting +filets +filial +filibuster +filibustered +filibusterer +filibusterers +filibustering +filibusters +filigree +filigreed +filigreeing +filigrees +filing +filings +fill +filled +filler +fillers +fillet +filleted +filleting +fillets +fillies +filling +fillings +fillip +filliped +filliping +fillips +fills +filly +film +filmed +filmier +filmiest +filminess +filming +filmmaker +filmmakers +films +filmstrip +filmstrips +filmy +filter +filterable +filtered +filterer +filterers +filtering +filters +filth +filthier +filthiest +filthily +filthiness +filthy +filtrable +filtrate +filtrated +filtrates +filtrating +filtration +finagle +finagled +finagler +finaglers +finagles +finagling +final +finale +finales +finalist +finalists +finality +finalization +finalize +finalized +finalizes +finalizing +finally +finals +finance +financed +finances +financial +financially +financier +financiers +financing +finch +finches +find +finder +finders +finding +findings +finds +fine +fined +finely +fineness +finer +finery +fines +finespun +finesse +finessed +finesses +finessing +finest +finger +fingerboard +fingerboards +fingered +fingering +fingerings +fingerling +fingerlings +fingernail +fingernails +fingerprint +fingerprinted +fingerprinting +fingerprints +fingers +fingertip +fingertips +finial +finials +finical +finickier +finickiest +finickiness +finicky +fining +finis +finises +finish +finished +finisher +finishers +finishes +finishing +finite +finitely +fink +finked +finking +finks +finned +finnier +finniest +finny +fins +fiord +fiords +fire +firearm +firearms +fireball +fireballs +firebomb +firebombed +firebombing +firebombs +firebox +fireboxes +firebrand +firebrands +firebreak +firebreaks +firebrick +firebricks +firebug +firebugs +firecracker +firecrackers +fired +firedamp +firefight +firefighter +firefighters +firefighting +firefights +fireflies +firefly +firehouse +firehouses +firelight +fireman +firemen +fireplace +fireplaces +fireplug +fireplugs +firepower +fireproof +fireproofed +fireproofing +fireproofs +firer +firers +fires +fireside +firesides +firestorm +firestorms +firetrap +firetraps +firetruck +firetrucks +firewall +firewalls +firewater +firewood +firework +fireworks +firing +firm +firmament +firmaments +firmed +firmer +firmest +firming +firmly +firmness +firms +firmware +firs +first +firstborn +firstborns +firsthand +firstly +firsts +firth +firths +fiscal +fiscally +fiscals +fish +fishbowl +fishbowls +fishcake +fishcakes +fished +fisher +fisheries +fisherman +fishermen +fishers +fishery +fishes +fishhook +fishhooks +fishier +fishiest +fishily +fishiness +fishing +fishmonger +fishmongers +fishnet +fishnets +fishpond +fishponds +fishtail +fishtailed +fishtailing +fishtails +fishwife +fishwives +fishy +fissile +fission +fissionable +fissure +fissures +fist +fistfight +fistfights +fistful +fistfuls +fisticuffs +fists +fistula +fistulae +fistulas +fistulous +fitful +fitfully +fitfulness +fitly +fitness +fits +fitted +fitter +fitters +fittest +fitting +fittingly +fittings +five +fives +fixable +fixate +fixated +fixates +fixating +fixation +fixations +fixative +fixatives +fixed +fixedly +fixer +fixers +fixes +fixing +fixings +fixity +fixture +fixtures +fizz +fizzed +fizzes +fizzier +fizziest +fizzing +fizzle +fizzled +fizzles +fizzling +fizzy +fjord +fjords +flab +flabbergast +flabbergasted +flabbergasting +flabbergasts +flabbier +flabbiest +flabbily +flabbiness +flabby +flaccid +flaccidity +flaccidly +flack +flacks +flag +flagella +flagellate +flagellated +flagellates +flagellating +flagellation +flagellum +flagellums +flagged +flagging +flagman +flagmen +flagon +flagons +flagpole +flagpoles +flagrance +flagrancy +flagrant +flagrantly +flags +flagship +flagships +flagstaff +flagstaffs +flagstone +flagstones +flail +flailed +flailing +flails +flair +flairs +flak +flake +flaked +flakes +flakier +flakiest +flakiness +flaking +flaky +flambe +flambeed +flambeing +flambes +flamboyance +flamboyancy +flamboyant +flamboyantly +flame +flamed +flamenco +flamencos +flameproof +flameproofed +flameproofing +flameproofs +flames +flamethrower +flamethrowers +flaming +flamingo +flamingoes +flamingos +flammability +flammable +flammables +flan +flange +flanges +flank +flanked +flanker +flankers +flanking +flanks +flannel +flanneled +flannelet +flannelette +flanneling +flannelled +flannelling +flannels +flans +flap +flapjack +flapjacks +flapped +flapper +flappers +flapping +flaps +flare +flared +flares +flareup +flareups +flaring +flash +flashback +flashbacks +flashbulb +flashbulbs +flashcard +flashcards +flashcube +flashcubes +flashed +flasher +flashers +flashes +flashest +flashgun +flashguns +flashier +flashiest +flashily +flashiness +flashing +flashlight +flashlights +flashpoint +flashpoints +flashy +flask +flasks +flat +flatbed +flatbeds +flatboat +flatboats +flatcar +flatcars +flatfeet +flatfish +flatfishes +flatfoot +flatfooted +flatfoots +flatiron +flatirons +flatland +flatly +flatness +flats +flatted +flatten +flattened +flattening +flattens +flatter +flattered +flatterer +flatterers +flattering +flatteringly +flatters +flattery +flattest +flatting +flattish +flattop +flattops +flatulence +flatulent +flatus +flatware +flatworm +flatworms +flaunt +flaunted +flaunting +flauntingly +flaunts +flautist +flautists +flavor +flavored +flavorful +flavoring +flavorings +flavorless +flavors +flavorsome +flavour +flavoured +flavouring +flavours +flaw +flawed +flawing +flawless +flawlessly +flawlessness +flaws +flax +flaxen +flay +flayed +flaying +flays +flea +fleabag +fleabags +fleas +fleck +flecked +flecking +flecks +fled +fledgeling +fledgelings +fledgling +fledglings +flee +fleece +fleeced +fleecer +fleecers +fleeces +fleecier +fleeciest +fleeciness +fleecing +fleecy +fleeing +flees +fleet +fleeted +fleeter +fleetest +fleeting +fleetingly +fleetingness +fleetly +fleetness +fleets +flesh +fleshed +fleshes +fleshier +fleshiest +fleshing +fleshlier +fleshliest +fleshly +fleshpot +fleshpots +fleshy +flew +flex +flexed +flexes +flexibility +flexible +flexibly +flexing +flexitime +flextime +flibbertigibbet +flibbertigibbets +flick +flicked +flicker +flickered +flickering +flickers +flicking +flicks +flied +flier +fliers +flies +fliest +flight +flightier +flightiest +flightiness +flightless +flights +flighty +flimflam +flimflammed +flimflamming +flimflams +flimsier +flimsiest +flimsily +flimsiness +flimsy +flinch +flinched +flinches +flinching +fling +flinging +flings +flint +flintier +flintiest +flintlock +flintlocks +flints +flinty +flip +flippancy +flippant +flippantly +flipped +flipper +flippers +flippest +flipping +flips +flirt +flirtation +flirtations +flirtatious +flirtatiously +flirtatiousness +flirted +flirting +flirts +flit +flits +flitted +flitting +float +floated +floater +floaters +floating +floats +flock +flocked +flocking +flocks +floe +floes +flog +flogged +flogger +floggers +flogging +floggings +flogs +flood +flooded +floodgate +floodgates +flooding +floodlight +floodlighted +floodlighting +floodlights +floodlit +floodplain +floodplains +floods +floodwater +floor +floorboard +floorboards +floored +flooring +floors +floorwalker +floorwalkers +floozie +floozies +floozy +flop +flophouse +flophouses +flopped +floppier +floppies +floppiest +floppily +floppiness +flopping +floppy +flops +flora +florae +floral +floras +florescence +florescent +floret +florets +florid +florider +floridest +floridly +floridness +florin +florins +florist +florists +floss +flossed +flosses +flossier +flossiest +flossing +flossy +flotation +flotations +flotilla +flotillas +flotsam +flounce +flounced +flounces +flouncier +flounciest +flouncing +flouncy +flounder +floundered +floundering +flounders +flour +floured +flouring +flourish +flourished +flourishes +flourishing +flours +floury +flout +flouted +flouter +flouters +flouting +flouts +flow +flowchart +flowcharts +flowed +flower +flowerbed +flowerbeds +flowered +flowerier +floweriest +floweriness +flowering +flowerless +flowerpot +flowerpots +flowers +flowery +flowing +flown +flows +flub +flubbed +flubbing +flubs +fluctuate +fluctuated +fluctuates +fluctuating +fluctuation +fluctuations +flue +fluency +fluent +fluently +flues +fluff +fluffed +fluffier +fluffiest +fluffiness +fluffing +fluffs +fluffy +fluid +fluidity +fluidly +fluids +fluke +flukes +flukey +flukier +flukiest +fluky +flume +flumes +flummox +flummoxed +flummoxes +flummoxing +flung +flunk +flunked +flunkey +flunkeys +flunkies +flunking +flunks +flunky +fluoresce +fluoresced +fluorescence +fluorescent +fluoresces +fluorescing +fluoridate +fluoridated +fluoridates +fluoridating +fluoridation +fluoride +fluorides +fluorine +fluorite +fluorocarbon +fluorocarbons +fluoroscope +fluoroscopes +fluoroscopic +flurried +flurries +flurry +flurrying +flush +flushed +flusher +flushes +flushest +flushing +fluster +flustered +flustering +flusters +flute +fluted +flutes +fluting +flutist +flutists +flutter +fluttered +fluttering +flutters +fluttery +flux +fluxed +fluxes +fluxing +flyable +flyblown +flyby +flybys +flycatcher +flycatchers +flyer +flyers +flying +flyleaf +flyleaves +flypaper +flypapers +flyspeck +flyspecked +flyspecking +flyspecks +flyswatter +flyswatters +flyway +flyways +flyweight +flyweights +flywheel +flywheels +foal +foaled +foaling +foals +foam +foamed +foamier +foamiest +foaminess +foaming +foams +foamy +fobbed +fobbing +fobs +focal +focally +foci +focus +focused +focuses +focusing +focussed +focusses +focussing +fodder +fodders +foes +foetal +foetus +foetuses +fogbound +fogey +fogeys +fogged +foggier +foggiest +foggily +fogginess +fogging +foggy +foghorn +foghorns +fogies +fogs +fogy +fogyish +foible +foibles +foil +foiled +foiling +foils +foist +foisted +foisting +foists +fold +foldaway +folded +folder +folders +folding +foldout +foldouts +folds +foliage +folio +folios +folk +folklore +folkloric +folklorist +folklorists +folks +folksier +folksiest +folksiness +folksinger +folksingers +folksinging +folksy +folktale +folktales +folkway +folkways +follicle +follicles +follies +follow +followed +follower +followers +following +followings +follows +folly +foment +fomentation +fomented +fomenting +foments +fond +fondant +fondants +fonder +fondest +fondle +fondled +fondles +fondling +fondly +fondness +fondu +fondue +fondues +font +fontanel +fontanelle +fontanelles +fontanels +fonts +food +foodie +foodies +foods +foodstuff +foodstuffs +fool +fooled +fooleries +foolery +foolhardier +foolhardiest +foolhardily +foolhardiness +foolhardy +fooling +foolish +foolishly +foolishness +foolproof +fools +foolscap +foot +footage +football +footballer +footballers +footballs +footbridge +footbridges +footed +footfall +footfalls +foothill +foothills +foothold +footholds +footing +footings +footless +footlights +footling +footlings +footlocker +footlockers +footloose +footman +footmen +footnote +footnoted +footnotes +footnoting +footpath +footpaths +footprint +footprints +footrace +footraces +footrest +footrests +foots +footsie +footsies +footsore +footstep +footsteps +footstool +footstools +footwear +footwork +foppery +foppish +foppishness +fops +fora +forage +foraged +forager +foragers +forages +foraging +foray +forayed +foraying +forays +forbad +forbade +forbear +forbearance +forbearing +forbears +forbid +forbidden +forbidding +forbiddingly +forbids +forbore +forborne +force +forced +forceful +forcefully +forcefulness +forceps +forces +forcible +forcibly +forcing +ford +fordable +forded +fording +fords +fore +forearm +forearmed +forearming +forearms +forebear +forebears +forebode +foreboded +forebodes +foreboding +forebodings +forecast +forecasted +forecaster +forecasters +forecasting +forecastle +forecastles +forecasts +foreclose +foreclosed +forecloses +foreclosing +foreclosure +foreclosures +forecourt +forecourts +foredoom +foredoomed +foredooming +foredooms +forefather +forefathers +forefeet +forefinger +forefingers +forefoot +forefront +forefronts +foregather +foregathered +foregathering +foregathers +forego +foregoes +foregoing +foregone +foreground +foregrounded +foregrounding +foregrounds +forehand +forehands +forehead +foreheads +foreign +foreigner +foreigners +foreignness +foreknew +foreknow +foreknowing +foreknowledge +foreknown +foreknows +foreleg +forelegs +forelimb +forelimbs +forelock +forelocks +foreman +foremast +foremasts +foremen +foremost +forename +forenamed +forenames +forenoon +forenoons +forensic +forensically +forensics +foreordain +foreordained +foreordaining +foreordains +forepart +foreparts +foreperson +forepersons +foreplay +forequarter +forequarters +forerunner +forerunners +fores +foresail +foresails +foresaw +foresee +foreseeable +foreseeing +foreseen +foreseer +foreseers +foresees +foreshadow +foreshadowed +foreshadowing +foreshadows +foreshorten +foreshortened +foreshortening +foreshortens +foresight +foresighted +foresightedness +foreskin +foreskins +forest +forestall +forestalled +forestalling +forestalls +forestation +forested +forester +foresters +foresting +forestland +forestry +forests +foreswear +foreswearing +foreswears +foreswore +foresworn +foretaste +foretasted +foretastes +foretasting +foretell +foretelling +foretells +forethought +foretold +forever +forevermore +forewarn +forewarned +forewarning +forewarns +forewent +forewoman +forewomen +foreword +forewords +forfeit +forfeited +forfeiting +forfeits +forfeiture +forgather +forgathered +forgathering +forgathers +forgave +forge +forged +forger +forgeries +forgers +forgery +forges +forget +forgetful +forgetfully +forgetfulness +forgets +forgettable +forgetting +forging +forgings +forgivable +forgive +forgiven +forgiveness +forgiver +forgivers +forgives +forgiving +forgo +forgoer +forgoers +forgoes +forgoing +forgone +forgot +forgotten +fork +forked +forkful +forkfuls +forking +forklift +forklifts +forks +forlorn +forlornly +form +formal +formaldehyde +formalism +formalist +formalists +formalities +formality +formalization +formalize +formalized +formalizes +formalizing +formally +formals +format +formated +formating +formation +formations +formative +formats +formatted +formatting +formed +former +formerly +formfitting +formic +formidable +formidably +forming +formless +formlessly +formlessness +forms +formula +formulae +formulaic +formulas +formulate +formulated +formulates +formulating +formulation +formulations +formulator +formulators +fornicate +fornicated +fornicates +fornicating +fornication +fornicator +fornicators +forsake +forsaken +forsakes +forsaking +forsook +forsooth +forswear +forswearing +forswears +forswore +forsworn +forsythia +forsythias +fort +forte +fortes +forth +forthcoming +forthright +forthrightly +forthrightness +forthwith +forties +fortieth +fortieths +fortification +fortifications +fortified +fortifier +fortifiers +fortifies +fortify +fortifying +fortissimo +fortitude +fortnight +fortnightly +fortnights +fortress +fortresses +forts +fortuitous +fortuitously +fortuitousness +fortuity +fortunate +fortunately +fortune +fortunes +fortuneteller +fortunetellers +fortunetelling +forty +forum +forums +forward +forwarded +forwarder +forwarders +forwardest +forwarding +forwardly +forwardness +forwards +forwent +fossil +fossilization +fossilize +fossilized +fossilizes +fossilizing +fossils +foster +fostered +fostering +fosters +fought +foul +foulard +fouled +fouler +foulest +fouling +foully +foulmouthed +foulness +fouls +found +foundation +foundations +founded +founder +foundered +foundering +founders +founding +foundling +foundlings +foundries +foundry +founds +fount +fountain +fountainhead +fountainheads +fountains +founts +four +fourfold +fourposter +fourposters +fours +fourscore +foursome +foursomes +foursquare +fourteen +fourteens +fourteenth +fourteenths +fourth +fourthly +fourths +fowl +fowled +fowling +fowls +foxed +foxes +foxfire +foxglove +foxgloves +foxhole +foxholes +foxhound +foxhounds +foxier +foxiest +foxily +foxiness +foxing +foxtrot +foxtrots +foxtrotted +foxtrotting +foxy +foyer +foyers +fracas +fracases +fractal +fractals +fraction +fractional +fractionally +fractions +fractious +fractiously +fractiousness +fracture +fractured +fractures +fracturing +fragile +fragility +fragment +fragmentary +fragmentation +fragmented +fragmenting +fragments +fragrance +fragrances +fragrant +fragrantly +frail +frailer +frailest +frailly +frailness +frailties +frailty +frame +framed +framer +framers +frames +framework +frameworks +framing +franc +franchise +franchised +franchisee +franchisees +franchiser +franchisers +franchises +franchising +franchisor +franchisors +francium +francs +frangibility +frangible +frank +franked +franker +frankest +frankfurter +frankfurters +frankincense +franking +frankly +frankness +franks +frantic +frantically +frappe +frappes +frat +fraternal +fraternally +fraternities +fraternity +fraternization +fraternize +fraternized +fraternizer +fraternizers +fraternizes +fraternizing +fratricidal +fratricide +fratricides +frats +fraud +frauds +fraudulence +fraudulent +fraudulently +fraught +fray +frayed +fraying +frays +frazzle +frazzled +frazzles +frazzling +freak +freaked +freakier +freakiest +freaking +freakish +freakishly +freakishness +freakout +freakouts +freaks +freaky +freckle +freckled +freckles +frecklier +freckliest +freckling +freckly +free +freebase +freebased +freebases +freebasing +freebee +freebees +freebie +freebies +freebooter +freebooters +freeborn +freed +freedman +freedmen +freedom +freedoms +freehand +freehold +freeholder +freeholders +freeholds +freeing +freelance +freelanced +freelancer +freelancers +freelances +freelancing +freeload +freeloaded +freeloader +freeloaders +freeloading +freeloads +freely +freeman +freemen +freer +frees +freest +freestanding +freestone +freestones +freestyle +freestyles +freethinker +freethinkers +freethinking +freeware +freeway +freeways +freewheel +freewheeled +freewheeling +freewheels +freewill +freezable +freeze +freezer +freezers +freezes +freezing +freight +freighted +freighter +freighters +freighting +freights +frenetic +frenetically +frenzied +frenziedly +frenzies +frenzy +frequencies +frequency +frequent +frequented +frequenter +frequenters +frequentest +frequenting +frequently +frequents +fresco +frescoes +frescos +fresh +freshen +freshened +freshener +fresheners +freshening +freshens +fresher +freshest +freshet +freshets +freshly +freshman +freshmen +freshness +freshwater +fret +fretful +fretfully +fretfulness +frets +fretsaw +fretsaws +fretted +fretting +fretwork +friable +friar +friaries +friars +friary +fricassee +fricasseed +fricasseeing +fricassees +fricative +fricatives +friction +frictional +fridge +fridges +fried +friedcake +friedcakes +friend +friendless +friendlier +friendlies +friendliest +friendliness +friendly +friends +friendship +friendships +frier +friers +fries +frieze +friezes +frig +frigate +frigates +frigged +frigging +fright +frighted +frighten +frightened +frightening +frighteningly +frightens +frightful +frightfully +frightfulness +frighting +frights +frigid +frigidity +frigidly +frigidness +frigs +frill +frillier +frilliest +frills +frilly +fringe +fringed +fringes +fringing +fripperies +frippery +frisk +frisked +friskier +friskiest +friskily +friskiness +frisking +frisks +frisky +fritter +frittered +frittering +fritters +fritz +frivolities +frivolity +frivolous +frivolously +frivolousness +friz +frizz +frizzed +frizzes +frizzier +frizziest +frizzing +frizzle +frizzled +frizzles +frizzlier +frizzliest +frizzling +frizzly +frizzy +frock +frocks +frog +frogman +frogmen +frogs +frolic +frolicked +frolicker +frolickers +frolicking +frolics +frolicsome +from +frond +fronds +front +frontage +frontages +frontal +frontally +fronted +frontier +frontiers +frontiersman +frontiersmen +fronting +frontispiece +frontispieces +frontrunner +frontrunners +fronts +frontward +frontwards +frosh +frost +frostbit +frostbite +frostbites +frostbiting +frostbitten +frosted +frostier +frostiest +frostily +frostiness +frosting +frostings +frosts +frosty +froth +frothed +frothier +frothiest +frothiness +frothing +froths +frothy +froufrou +froward +frowardness +frown +frowned +frowning +frowns +frowsier +frowsiest +frowsy +frowzier +frowziest +frowzily +frowziness +frowzy +froze +frozen +fructified +fructifies +fructify +fructifying +fructose +frugal +frugality +frugally +fruit +fruitcake +fruitcakes +fruited +fruitful +fruitfully +fruitfulness +fruitier +fruitiest +fruitiness +fruiting +fruition +fruitless +fruitlessly +fruitlessness +fruits +fruity +frump +frumpier +frumpiest +frumpish +frumps +frumpy +frusta +frustrate +frustrated +frustrates +frustrating +frustratingly +frustration +frustrations +frustum +frustums +fryer +fryers +frying +fuchsia +fuchsias +fuck +fucked +fucker +fuckers +fucking +fucks +fuddle +fuddled +fuddles +fuddling +fudge +fudged +fudges +fudging +fuehrer +fuehrers +fuel +fueled +fueling +fuelled +fuelling +fuels +fugal +fugitive +fugitives +fugue +fugues +fuhrer +fuhrers +fulcra +fulcrum +fulcrums +fulfil +fulfill +fulfilled +fulfilling +fulfillment +fulfills +fulfilment +fulfils +full +fullback +fullbacks +fulled +fuller +fullers +fullest +fulling +fullness +fulls +fully +fulminate +fulminated +fulminates +fulminating +fulmination +fulminations +fulness +fulsome +fulsomely +fulsomeness +fumble +fumbled +fumbler +fumblers +fumbles +fumbling +fumblingly +fume +fumed +fumes +fumier +fumiest +fumigant +fumigants +fumigate +fumigated +fumigates +fumigating +fumigation +fumigator +fumigators +fuming +fumy +function +functional +functionally +functionaries +functionary +functioned +functioning +functions +fund +fundamental +fundamentalism +fundamentalist +fundamentalists +fundamentally +fundamentals +funded +funding +fundraiser +fundraisers +funds +funeral +funerals +funerary +funereal +funereally +fungal +fungi +fungible +fungibles +fungicidal +fungicide +fungicides +fungoid +fungous +fungus +funguses +funicular +funiculars +funk +funked +funkier +funkiest +funkiness +funking +funks +funky +funnel +funneled +funneling +funnelled +funnelling +funnels +funner +funnest +funnier +funnies +funniest +funnily +funniness +funny +funnyman +funnymen +furbelow +furbish +furbished +furbishes +furbishing +furies +furious +furiously +furl +furled +furling +furlong +furlongs +furlough +furloughed +furloughing +furloughs +furls +furnace +furnaces +furnish +furnished +furnishes +furnishing +furnishings +furniture +furor +furore +furores +furors +furred +furrier +furriers +furriest +furriness +furring +furrow +furrowed +furrowing +furrows +furry +furs +further +furtherance +furthered +furthering +furthermore +furthermost +furthers +furthest +furtive +furtively +furtiveness +fury +furze +fuse +fused +fusee +fusees +fuselage +fuselages +fuses +fusibility +fusible +fusileer +fusileers +fusilier +fusiliers +fusillade +fusillades +fusing +fusion +fuss +fussbudget +fussbudgets +fussed +fusses +fussier +fussiest +fussily +fussiness +fussing +fusspot +fusspots +fussy +fustian +fustier +fustiest +fustiness +fusty +futile +futilely +futility +futon +futons +future +futures +futurism +futurist +futuristic +futurists +futurities +futurity +futurologist +futurologists +futurology +futz +futzed +futzes +futzing +fuze +fuzed +fuzes +fuzing +fuzz +fuzzed +fuzzes +fuzzier +fuzziest +fuzzily +fuzziness +fuzzing +fuzzy +gabardine +gabardines +gabbed +gabbier +gabbiest +gabbiness +gabbing +gabble +gabbled +gabbles +gabbling +gabby +gaberdine +gaberdines +gabfest +gabfests +gable +gabled +gables +gabs +gadabout +gadabouts +gadded +gadder +gadders +gadding +gadflies +gadfly +gadget +gadgetry +gadgets +gadolinium +gads +gaff +gaffe +gaffed +gaffer +gaffers +gaffes +gaffing +gaffs +gaga +gage +gaged +gages +gagged +gagging +gaggle +gaggles +gaging +gags +gaiety +gaily +gain +gained +gainer +gainers +gainful +gainfully +gaining +gains +gainsaid +gainsay +gainsayer +gainsayers +gainsaying +gainsays +gait +gaiter +gaiters +gaits +gala +galactic +galas +galaxies +galaxy +gale +galena +gales +gall +gallant +gallanter +gallantest +gallantly +gallantry +gallants +gallbladder +gallbladders +galled +galleon +galleons +galleria +gallerias +galleries +gallery +galley +galleys +gallimaufries +gallimaufry +galling +gallium +gallivant +gallivanted +gallivanting +gallivants +gallon +gallons +gallop +galloped +galloping +gallops +gallows +gallowses +galls +gallstone +gallstones +galoot +galoots +galore +galosh +galoshe +galoshes +gals +galumph +galumphed +galumphing +galumphs +galvanic +galvanise +galvanised +galvanises +galvanising +galvanism +galvanization +galvanize +galvanized +galvanizes +galvanizing +galvanometer +galvanometers +gambit +gambits +gamble +gambled +gambler +gamblers +gambles +gambling +gambol +gamboled +gamboling +gambolled +gambolling +gambols +game +gamecock +gamecocks +gamed +gamekeeper +gamekeepers +gamely +gameness +gamer +games +gamesmanship +gamest +gamester +gamesters +gamete +gametes +gametic +gamey +gamier +gamiest +gamin +gamine +gamines +gaminess +gaming +gamins +gamma +gammas +gammon +gamut +gamuts +gamy +gander +ganders +gang +gangbusters +ganged +ganging +gangland +ganglia +ganglier +gangliest +gangling +ganglion +ganglionic +ganglions +gangly +gangplank +gangplanks +gangrene +gangrened +gangrenes +gangrening +gangrenous +gangs +gangster +gangsters +gangway +gangways +gannet +gannets +gantlet +gantlets +gantries +gantry +gaol +gaoled +gaoler +gaolers +gaoling +gaols +gape +gaped +gapes +gaping +gaps +garage +garaged +garages +garaging +garb +garbage +garbanzo +garbanzos +garbed +garbing +garble +garbled +garbles +garbling +garbs +garcon +garcons +garden +gardened +gardener +gardeners +gardenia +gardenias +gardening +gardens +garfish +garfishes +gargantuan +gargle +gargled +gargles +gargling +gargoyle +gargoyles +garish +garishly +garishness +garland +garlanded +garlanding +garlands +garlic +garlicky +garment +garments +garner +garnered +garnering +garners +garnet +garnets +garnish +garnished +garnishee +garnisheed +garnisheeing +garnishees +garnishes +garnishing +garnishment +garnishments +garote +garoted +garotes +garoting +garotte +garotted +garottes +garotting +garret +garrets +garrison +garrisoned +garrisoning +garrisons +garrote +garroted +garroter +garroters +garrotes +garroting +garrotte +garrotted +garrottes +garrotting +garrulity +garrulous +garrulously +garrulousness +gars +garter +garters +gasbag +gasbags +gaseous +gases +gash +gashed +gashes +gashing +gasket +gaskets +gaslight +gaslights +gasohol +gasolene +gasoline +gasp +gasped +gasping +gasps +gassed +gasses +gassier +gassiest +gassing +gassy +gastric +gastritis +gastroenteritis +gastrointestinal +gastronomic +gastronomical +gastronomically +gastronomy +gastropod +gastropods +gasworks +gate +gatecrash +gatecrashed +gatecrasher +gatecrashers +gatecrashes +gatecrashing +gated +gatehouse +gatehouses +gatekeeper +gatekeepers +gatepost +gateposts +gates +gateway +gateways +gather +gathered +gatherer +gatherers +gathering +gatherings +gathers +gating +gator +gators +gauche +gauchely +gaucheness +gaucher +gaucherie +gauchest +gaucho +gauchos +gaudier +gaudiest +gaudily +gaudiness +gaudy +gauge +gauged +gauges +gauging +gaunt +gaunter +gauntest +gauntlet +gauntlets +gauntness +gauze +gauzier +gauziest +gauziness +gauzy +gave +gavel +gavels +gavotte +gavottes +gawk +gawked +gawkier +gawkiest +gawkily +gawkiness +gawking +gawks +gawky +gayer +gayest +gayety +gayly +gayness +gays +gaze +gazebo +gazeboes +gazebos +gazed +gazelle +gazelles +gazer +gazers +gazes +gazette +gazetted +gazetteer +gazetteers +gazettes +gazetting +gazing +gazpacho +gear +gearbox +gearboxes +geared +gearing +gears +gearshift +gearshifts +gearwheel +gearwheels +gecko +geckoes +geckos +geed +geegaw +geegaws +geeing +geek +geekier +geekiest +geeks +geeky +gees +geese +geez +geezer +geezers +geisha +geishas +gelatin +gelatine +gelatinous +gelcap +gelcaps +geld +gelded +gelding +geldings +gelds +gelid +gelignite +gelled +gelling +gels +gelt +gemmology +gemological +gemologist +gemologists +gemology +gems +gemstone +gemstones +gendarme +gendarmes +gender +genders +gene +genealogical +genealogically +genealogies +genealogist +genealogists +genealogy +genera +general +generalissimo +generalissimos +generalist +generalists +generalities +generality +generalization +generalizations +generalize +generalized +generalizes +generalizing +generally +generals +generalship +generate +generated +generates +generating +generation +generational +generations +generative +generator +generators +generic +generically +generics +generosities +generosity +generous +generously +generousness +genes +geneses +genesis +genetic +genetically +geneticist +geneticists +genetics +genial +geniality +genially +genie +genies +genii +genital +genitalia +genitally +genitals +genitive +genitives +genitourinary +genius +geniuses +genocidal +genocide +genome +genomes +genre +genres +gent +genteel +genteelly +genteelness +gentian +gentians +gentile +gentiles +gentility +gentle +gentled +gentlefolk +gentlefolks +gentleman +gentlemanly +gentlemen +gentleness +gentler +gentles +gentlest +gentlewoman +gentlewomen +gentling +gently +gentries +gentrification +gentrified +gentrifies +gentrify +gentrifying +gentry +gents +genuflect +genuflected +genuflecting +genuflection +genuflections +genuflects +genuine +genuinely +genuineness +genus +genuses +geocentric +geocentrically +geochemistry +geode +geodes +geodesic +geodesics +geodesy +geodetic +geographer +geographers +geographic +geographical +geographically +geographies +geography +geologic +geological +geologically +geologies +geologist +geologists +geology +geomagnetic +geomagnetism +geometric +geometrical +geometrically +geometries +geometry +geophysical +geophysicist +geophysicists +geophysics +geopolitical +geopolitics +geostationary +geosynchronous +geosyncline +geosynclines +geothermal +geothermic +geranium +geraniums +gerbil +gerbils +geriatric +geriatrics +germ +germane +germanium +germicidal +germicide +germicides +germinal +germinate +germinated +germinates +germinating +germination +germs +gerontological +gerontologist +gerontologists +gerontology +gerrymander +gerrymandered +gerrymandering +gerrymanders +gerund +gerunds +gestapo +gestapos +gestate +gestated +gestates +gestating +gestation +gestational +gesticulate +gesticulated +gesticulates +gesticulating +gesticulation +gesticulations +gestural +gesture +gestured +gestures +gesturing +gesundheit +getaway +getaways +gets +getting +getup +gewgaw +gewgaws +geyser +geysered +geysering +geysers +ghastlier +ghastliest +ghastliness +ghastly +ghat +ghats +gherkin +gherkins +ghetto +ghettoes +ghettoize +ghettoized +ghettoizes +ghettoizing +ghettos +ghost +ghosted +ghosting +ghostlier +ghostliest +ghostliness +ghostly +ghosts +ghostwrite +ghostwriter +ghostwriters +ghostwrites +ghostwriting +ghostwritten +ghostwrote +ghoul +ghoulish +ghoulishly +ghoulishness +ghouls +giant +giantess +giantesses +giants +gibber +gibbered +gibbering +gibberish +gibbers +gibbet +gibbeted +gibbeting +gibbets +gibbon +gibbons +gibbous +gibe +gibed +gibes +gibing +giblet +giblets +giddier +giddiest +giddily +giddiness +giddy +gift +gifted +gifting +gifts +gigabyte +gigabytes +gigahertz +gigantic +gigantically +gigged +gigging +giggle +giggled +giggler +gigglers +giggles +gigglier +giggliest +giggling +giggly +gigolo +gigolos +gigs +gild +gilded +gilder +gilders +gilding +gilds +gill +gills +gilt +gilts +gimbals +gimcrack +gimcrackery +gimcracks +gimlet +gimleted +gimleting +gimlets +gimme +gimmes +gimmick +gimmickry +gimmicks +gimmicky +gimp +gimped +gimpier +gimpiest +gimping +gimps +gimpy +ginger +gingerbread +gingerly +gingersnap +gingersnaps +gingery +gingham +gingivitis +gingko +gingkoes +gingkos +ginkgo +ginkgoes +ginkgos +ginned +ginning +gins +ginseng +gipsies +gipsy +giraffe +giraffes +gird +girded +girder +girders +girding +girdle +girdled +girdles +girdling +girds +girl +girlfriend +girlfriends +girlhood +girlhoods +girlish +girlishly +girlishness +girls +girly +girt +girted +girth +girths +girting +girts +gismo +gismos +gist +give +giveaway +giveaways +giveback +givebacks +given +givens +giver +givers +gives +giving +gizmo +gizmos +gizzard +gizzards +glace +glaceed +glaceing +glaces +glacial +glacially +glaciate +glaciated +glaciates +glaciating +glaciation +glaciations +glacier +glaciers +glad +gladden +gladdened +gladdening +gladdens +gladder +gladdest +glade +glades +gladiator +gladiatorial +gladiators +gladiola +gladiolas +gladioli +gladiolus +gladioluses +gladly +gladness +glads +gladsome +glamor +glamorization +glamorize +glamorized +glamorizes +glamorizing +glamorous +glamorously +glamour +glamourize +glamourized +glamourizes +glamourizing +glamourous +glance +glanced +glances +glancing +gland +glandes +glands +glandular +glans +glare +glared +glares +glaring +glaringly +glasnost +glass +glassblower +glassblowers +glassblowing +glassed +glasses +glassful +glassfuls +glassier +glassiest +glassily +glassiness +glassing +glassware +glassy +glaucoma +glaze +glazed +glazes +glazier +glaziers +glazing +gleam +gleamed +gleaming +gleams +glean +gleaned +gleaner +gleaners +gleaning +gleanings +gleans +glee +gleeful +gleefully +gleefulness +glen +glens +glib +glibber +glibbest +glibly +glibness +glide +glided +glider +gliders +glides +gliding +glimmer +glimmered +glimmering +glimmerings +glimmers +glimpse +glimpsed +glimpses +glimpsing +glint +glinted +glinting +glints +glissandi +glissando +glissandos +glisten +glistened +glistening +glistens +glister +glistered +glistering +glisters +glitch +glitches +glitter +glittered +glittering +glitters +glittery +glitz +glitzier +glitziest +glitzy +gloaming +gloamings +gloat +gloated +gloating +gloatingly +gloats +glob +global +globalism +globalist +globalists +globalization +globalize +globalized +globalizes +globalizing +globally +globe +globes +globetrotter +globetrotters +globs +globular +globule +globules +globulin +glockenspiel +glockenspiels +gloom +gloomier +gloomiest +gloomily +gloominess +gloomy +glop +gloppier +gloppiest +gloppy +gloried +glories +glorification +glorified +glorifies +glorify +glorifying +glorious +gloriously +glory +glorying +gloss +glossaries +glossary +glossed +glosses +glossier +glossies +glossiest +glossily +glossiness +glossing +glossolalia +glossy +glottal +glottides +glottis +glottises +glove +gloved +gloves +gloving +glow +glowed +glower +glowered +glowering +glowers +glowing +glowingly +glows +glowworm +glowworms +glucose +glue +glued +glueing +glues +gluey +gluier +gluiest +gluing +glum +glumly +glummer +glummest +glumness +glut +gluten +glutenous +glutinous +glutinously +gluts +glutted +glutting +glutton +gluttonous +gluttonously +gluttons +gluttony +glycerin +glycerine +glycerol +glycogen +gnarl +gnarled +gnarling +gnarls +gnash +gnashed +gnashes +gnashing +gnat +gnats +gnaw +gnawed +gnawing +gnawn +gnaws +gneiss +gnome +gnomes +gnomic +gnomish +gnus +goad +goaded +goading +goads +goal +goalie +goalies +goalkeeper +goalkeepers +goalkeeping +goalpost +goalposts +goals +goaltender +goaltenders +goat +goatee +goatees +goatherd +goatherds +goats +goatskin +goatskins +gobbed +gobbet +gobbets +gobbing +gobble +gobbled +gobbledegook +gobbledygook +gobbler +gobblers +gobbles +gobbling +goblet +goblets +goblin +goblins +gobs +godchild +godchildren +goddammit +goddamn +goddamned +goddaughter +goddaughters +goddess +goddesses +godfather +godfathers +godforsaken +godhead +godhood +godless +godlessness +godlier +godliest +godlike +godliness +godly +godmother +godmothers +godparent +godparents +gods +godsend +godsends +godson +godsons +goer +goers +goes +gofer +gofers +goggle +goggled +goggles +goggling +going +goings +goiter +goiters +goitre +goitres +gold +goldbrick +goldbricked +goldbricker +goldbrickers +goldbricking +goldbricks +golden +goldener +goldenest +goldenrod +goldfinch +goldfinches +goldfish +goldfishes +goldmine +goldmines +golds +goldsmith +goldsmiths +golf +golfed +golfer +golfers +golfing +golfs +gollies +golly +gonad +gonadal +gonads +gondola +gondolas +gondolier +gondoliers +gone +goner +goners +gong +gonged +gonging +gongs +gonna +gonorrhea +gonorrheal +gonorrhoea +goober +goobers +good +goodby +goodbye +goodbyes +goodbys +goodhearted +goodie +goodies +goodish +goodlier +goodliest +goodly +goodness +goods +goodwill +goody +gooey +goof +goofball +goofballs +goofed +goofier +goofiest +goofiness +goofing +goofs +goofy +gooier +gooiest +gook +gooks +goon +goons +goop +goose +gooseberries +gooseberry +goosebumps +goosed +gooseflesh +gooses +goosing +gopher +gophers +gore +gored +gores +gorge +gorged +gorgeous +gorgeously +gorgeousness +gorges +gorging +gorgon +gorgons +gorier +goriest +gorilla +gorillas +gorily +goriness +goring +gormandize +gormandized +gormandizer +gormandizers +gormandizes +gormandizing +gorp +gorse +gory +gosh +goshawk +goshawks +gosling +goslings +gospel +gospels +gossamer +gossip +gossiped +gossiper +gossipers +gossiping +gossipped +gossipping +gossips +gossipy +gotcha +gotta +gotten +gouge +gouged +gouger +gougers +gouges +gouging +goulash +goulashes +gourd +gourde +gourdes +gourds +gourmand +gourmands +gourmet +gourmets +gout +goutier +goutiest +gouty +govern +governable +governance +governed +governess +governesses +governing +government +governmental +governments +governor +governors +governorship +governs +gown +gowned +gowning +gowns +grab +grabbed +grabber +grabbers +grabbier +grabbiest +grabbing +grabby +grabs +grace +graced +graceful +gracefully +gracefulness +graceless +gracelessly +gracelessness +graces +gracing +gracious +graciously +graciousness +grackle +grackles +grad +gradate +gradated +gradates +gradating +gradation +gradations +grade +graded +grader +graders +grades +gradient +gradients +grading +grads +gradual +gradualism +gradually +gradualness +graduate +graduated +graduates +graduating +graduation +graduations +graffiti +graffito +graft +grafted +grafter +grafters +grafting +grafts +graham +grain +grained +grainier +grainiest +graininess +grains +grainy +gram +grammar +grammarian +grammarians +grammars +grammatical +grammatically +gramme +grammes +gramophone +gramophones +grampus +grampuses +grams +granaries +granary +grand +grandam +grandams +grandaunt +grandaunts +grandchild +grandchildren +granddad +granddaddies +granddaddy +granddads +granddaughter +granddaughters +grandee +grandees +grander +grandest +grandeur +grandfather +grandfathered +grandfathering +grandfatherly +grandfathers +grandiloquence +grandiloquent +grandiose +grandiosely +grandiosity +grandly +grandma +grandmas +grandmother +grandmotherly +grandmothers +grandnephew +grandnephews +grandness +grandniece +grandnieces +grandpa +grandparent +grandparents +grandpas +grands +grandson +grandsons +grandstand +grandstanded +grandstanding +grandstands +granduncle +granduncles +grange +granges +granite +granitic +grannie +grannies +granny +granola +grant +granted +grantee +grantees +granter +granters +granting +grantor +grantors +grants +grantsmanship +granular +granularity +granulate +granulated +granulates +granulating +granulation +granule +granules +grape +grapefruit +grapefruits +grapes +grapeshot +grapevine +grapevines +graph +graphed +graphic +graphical +graphically +graphics +graphing +graphite +graphologist +graphologists +graphology +graphs +grapnel +grapnels +grapple +grappled +grapples +grappling +grasp +graspable +grasped +grasping +grasps +grass +grassed +grasses +grasshopper +grasshoppers +grassier +grassiest +grassing +grassland +grassy +grate +grated +grateful +gratefully +gratefulness +grater +graters +grates +gratification +gratifications +gratified +gratifies +gratify +gratifying +grating +gratingly +gratings +gratis +gratitude +gratuities +gratuitous +gratuitously +gratuitousness +gratuity +gravamen +gravamens +gravamina +grave +graved +gravedigger +gravediggers +gravel +graveled +graveling +gravelled +gravelling +gravelly +gravels +gravely +graven +graveness +graver +graves +graveside +gravesides +gravest +gravestone +gravestones +graveyard +graveyards +gravid +gravies +gravimeter +gravimeters +graving +gravitate +gravitated +gravitates +gravitating +gravitation +gravitational +gravity +gravy +gray +graybeard +graybeards +grayed +grayer +grayest +graying +grayish +grayness +grays +graze +grazed +grazer +grazers +grazes +grazing +grease +greased +greasepaint +greases +greasier +greasiest +greasily +greasiness +greasing +greasy +great +greatcoat +greatcoats +greater +greatest +greathearted +greatly +greatness +greats +grebe +grebes +greed +greedier +greediest +greedily +greediness +greedy +green +greenback +greenbacks +greenbelt +greenbelts +greened +greener +greenery +greenest +greengage +greengages +greengrocer +greengrocers +greenhorn +greenhorns +greenhouse +greenhouses +greening +greenish +greenly +greenmail +greenness +greenroom +greenrooms +greens +greensward +greenwood +greet +greeted +greeter +greeters +greeting +greetings +greets +gregarious +gregariously +gregariousness +gremlin +gremlins +grenade +grenades +grenadier +grenadiers +grenadine +grew +grey +greyed +greyer +greyest +greyhound +greyhounds +greying +greys +grid +griddle +griddlecake +griddlecakes +griddles +gridiron +gridirons +gridlock +gridlocked +gridlocks +grids +grief +griefs +grievance +grievances +grieve +grieved +griever +grievers +grieves +grieving +grievous +grievously +grievousness +griffin +griffins +griffon +griffons +grill +grille +grilled +grilles +grilling +grills +grim +grimace +grimaced +grimaces +grimacing +grime +grimed +grimes +grimier +grimiest +griminess +griming +grimly +grimmer +grimmest +grimness +grimy +grin +grind +grinder +grinders +grinding +grinds +grindstone +grindstones +gringo +gringos +grinned +grinning +grins +grip +gripe +griped +griper +gripers +gripes +griping +grippe +gripped +gripper +grippers +gripping +grips +gript +grislier +grisliest +grisliness +grisly +grist +gristle +gristlier +gristliest +gristly +gristmill +gristmills +grit +grits +gritted +gritter +gritters +grittier +grittiest +grittiness +gritting +gritty +grizzled +grizzlier +grizzlies +grizzliest +grizzly +groan +groaned +groaning +groans +groat +groats +grocer +groceries +grocers +grocery +grog +groggier +groggiest +groggily +grogginess +groggy +groin +groins +grommet +grommets +groom +groomed +groomer +groomers +grooming +grooms +groomsman +groomsmen +groove +grooved +grooves +groovier +grooviest +grooving +groovy +grope +groped +groper +gropers +gropes +groping +grosbeak +grosbeaks +grosgrain +gross +grossed +grosser +grosses +grossest +grossing +grossly +grossness +grotesque +grotesquely +grotesqueness +grotesques +grotto +grottoes +grottos +grouch +grouched +grouches +grouchier +grouchiest +grouchily +grouchiness +grouching +grouchy +ground +groundbreaking +groundbreakings +grounded +grounder +grounders +groundhog +groundhogs +grounding +groundings +groundless +groundlessly +groundnut +groundnuts +grounds +groundswell +groundswells +groundwater +groundwork +group +grouped +grouper +groupers +groupie +groupies +grouping +groupings +groups +groupware +grouse +groused +grouser +grousers +grouses +grousing +grout +grouted +grouting +grouts +grove +grovel +groveled +groveler +grovelers +groveling +grovelled +groveller +grovellers +grovelling +grovels +groves +grow +grower +growers +growing +growl +growled +growler +growlers +growling +growls +grown +grownup +grownups +grows +growth +growths +grub +grubbed +grubber +grubbers +grubbier +grubbiest +grubbily +grubbiness +grubbing +grubby +grubs +grubstake +grudge +grudged +grudges +grudging +grudgingly +gruel +grueling +gruelling +gruesome +gruesomely +gruesomeness +gruesomer +gruesomest +gruff +gruffer +gruffest +gruffly +gruffness +grumble +grumbled +grumbler +grumblers +grumbles +grumbling +grump +grumpier +grumpiest +grumpily +grumpiness +grumps +grumpy +grunge +grungier +grungiest +grungy +grunion +grunions +grunt +grunted +grunting +grunts +gryphon +gryphons +guacamole +guanine +guano +guarani +guaranies +guaranis +guarantee +guaranteed +guaranteeing +guarantees +guarantied +guaranties +guarantor +guarantors +guaranty +guarantying +guard +guarded +guardedly +guarder +guarders +guardhouse +guardhouses +guardian +guardians +guardianship +guarding +guardrail +guardrails +guardroom +guardrooms +guards +guardsman +guardsmen +guava +guavas +gubernatorial +guerilla +guerillas +guerrilla +guerrillas +guess +guessed +guesser +guessers +guesses +guessing +guesstimate +guesstimated +guesstimates +guesstimating +guesswork +guest +guested +guesting +guests +guff +guffaw +guffawed +guffawing +guffaws +guidance +guide +guidebook +guidebooks +guided +guideline +guidelines +guidepost +guideposts +guider +guiders +guides +guiding +guild +guilder +guilders +guildhall +guildhalls +guilds +guile +guileful +guileless +guilelessly +guilelessness +guillotine +guillotined +guillotines +guillotining +guilt +guilted +guiltier +guiltiest +guiltily +guiltiness +guilting +guiltless +guilts +guilty +guinea +guineas +guise +guises +guitar +guitarist +guitarists +guitars +gulag +gulags +gulch +gulches +gulden +guldens +gulf +gulfs +gull +gulled +gullet +gullets +gulley +gulleys +gullibility +gullible +gullies +gulling +gulls +gully +gulp +gulped +gulper +gulpers +gulping +gulps +gumbo +gumboil +gumboils +gumbos +gumdrop +gumdrops +gummed +gummier +gummiest +gumming +gummy +gumption +gums +gumshoe +gumshoed +gumshoeing +gumshoes +gunboat +gunboats +gunfight +gunfighter +gunfighters +gunfights +gunfire +gunk +gunkier +gunkiest +gunky +gunman +gunmen +gunmetal +gunned +gunnel +gunnels +gunner +gunners +gunnery +gunning +gunny +gunnysack +gunnysacks +gunpoint +gunpowder +gunrunner +gunrunners +gunrunning +guns +gunship +gunships +gunshot +gunshots +gunslinger +gunslingers +gunsmith +gunsmiths +gunwale +gunwales +guppies +guppy +gurgle +gurgled +gurgles +gurgling +gurney +gurneys +guru +gurus +gush +gushed +gusher +gushers +gushes +gushier +gushiest +gushing +gushy +gusset +gusseted +gusseting +gussets +gussied +gussies +gussy +gussying +gust +gustatory +gusted +gustier +gustiest +gustily +gusting +gusto +gusts +gusty +gutless +gutlessness +guts +gutsier +gutsiest +gutsy +gutted +gutter +guttered +guttering +gutters +guttersnipe +guttersnipes +guttier +guttiest +gutting +guttural +gutturals +gutty +guyed +guying +guys +guzzle +guzzled +guzzler +guzzlers +guzzles +guzzling +gymkhana +gymkhanas +gymnasia +gymnasium +gymnasiums +gymnast +gymnastic +gymnastically +gymnastics +gymnasts +gymnosperm +gymnosperms +gyms +gynaecology +gynecologic +gynecological +gynecologist +gynecologists +gynecology +gypped +gypper +gyppers +gypping +gyps +gypsies +gypster +gypsters +gypsum +gypsy +gyrate +gyrated +gyrates +gyrating +gyration +gyrations +gyrator +gyrators +gyrfalcon +gyrfalcons +gyro +gyros +gyroscope +gyroscopes +gyroscopic +gyve +gyved +gyves +gyving +haberdasher +haberdasheries +haberdashers +haberdashery +habiliment +habiliments +habit +habitability +habitable +habitat +habitation +habitations +habitats +habits +habitual +habitually +habitualness +habituate +habituated +habituates +habituating +habituation +habitue +habitues +hacienda +haciendas +hack +hacked +hacker +hackers +hacking +hackle +hackles +hackney +hackneyed +hackneying +hackneys +hacks +hacksaw +hacksaws +hackwork +haddock +haddocks +hadj +hadjes +hadji +hadjis +hadst +haemoglobin +haemophilia +haemorrhage +haemorrhaged +haemorrhages +haemorrhaging +haemorrhoids +hafnium +haft +hafts +haggard +haggardly +haggardness +haggis +haggises +haggish +haggle +haggled +haggler +hagglers +haggles +haggling +hagiographer +hagiographers +hagiographies +hagiography +hags +hahnium +haiku +hail +hailed +hailing +hails +hailstone +hailstones +hailstorm +hailstorms +hair +hairball +hairballs +hairbreadth +hairbreadths +hairbrush +hairbrushes +haircloth +haircut +haircuts +hairdo +hairdos +hairdresser +hairdressers +hairdressing +hairdryer +hairdryers +haired +hairier +hairiest +hairiness +hairless +hairlike +hairline +hairlines +hairnet +hairnets +hairpiece +hairpieces +hairpin +hairpins +hairs +hairsbreadth +hairsbreadths +hairsplitter +hairsplitters +hairsplitting +hairspring +hairsprings +hairstyle +hairstyles +hairstylist +hairstylists +hairy +hajj +hajjes +hajji +hajjis +hake +hakes +halal +halberd +halberds +halcyon +hale +haled +haler +hales +halest +half +halfback +halfbacks +halfhearted +halfheartedly +halfheartedness +halfpence +halfpennies +halfpenny +halftime +halftimes +halftone +halftones +halfway +halfwit +halfwits +halibut +halibuts +haling +halite +halitosis +hall +halleluiah +halleluiahs +hallelujah +hallelujahs +halliard +halliards +hallmark +hallmarked +hallmarking +hallmarks +hallo +halloo +hallooed +hallooing +halloos +hallos +hallow +hallowed +hallowing +hallows +halls +hallucinate +hallucinated +hallucinates +hallucinating +hallucination +hallucinations +hallucinatory +hallucinogen +hallucinogenic +hallucinogenics +hallucinogens +hallway +hallways +halo +haloed +haloes +halogen +halogens +haloing +halos +halt +halted +halter +haltered +haltering +halters +halting +haltingly +halts +halve +halved +halves +halving +halyard +halyards +hamburg +hamburger +hamburgers +hamburgs +hamlet +hamlets +hammed +hammer +hammered +hammerer +hammerers +hammerhead +hammerheads +hammering +hammerlock +hammerlocks +hammers +hammertoe +hammertoes +hammier +hammiest +hamming +hammock +hammocks +hammy +hamper +hampered +hampering +hampers +hams +hamster +hamsters +hamstring +hamstringing +hamstrings +hamstrung +hand +handbag +handbags +handball +handballs +handbarrow +handbarrows +handbill +handbills +handbook +handbooks +handcar +handcars +handcart +handcarts +handclasp +handclasps +handcraft +handcrafted +handcrafting +handcrafts +handcuff +handcuffed +handcuffing +handcuffs +handed +handful +handfuls +handgun +handguns +handhold +handholds +handicap +handicapped +handicapper +handicappers +handicapping +handicaps +handicraft +handicrafts +handier +handiest +handily +handiness +handing +handiwork +handkerchief +handkerchiefs +handkerchieves +handle +handlebar +handlebars +handled +handler +handlers +handles +handling +handmade +handmaid +handmaiden +handmaidens +handmaids +handout +handouts +handpick +handpicked +handpicking +handpicks +handrail +handrails +hands +handsaw +handsaws +handset +handsets +handsful +handshake +handshakes +handsome +handsomely +handsomeness +handsomer +handsomest +handspring +handsprings +handstand +handstands +handwork +handwoven +handwriting +handwritten +handy +handyman +handymen +hang +hangar +hangars +hangdog +hanged +hanger +hangers +hanging +hangings +hangman +hangmen +hangnail +hangnails +hangout +hangouts +hangover +hangovers +hangs +hangup +hangups +hank +hanker +hankered +hankering +hankerings +hankers +hankie +hankies +hanks +hanky +hansom +hansoms +haphazard +haphazardly +haphazardness +hapless +haplessly +haplessness +haploid +haploids +haply +happen +happened +happening +happenings +happens +happenstance +happenstances +happier +happiest +happily +happiness +happy +harangue +harangued +harangues +haranguing +harass +harassed +harasser +harassers +harasses +harassing +harassment +harbinger +harbingers +harbor +harbored +harboring +harbors +harbour +harboured +harbouring +harbours +hard +hardback +hardbacks +hardball +hardboard +hardbound +hardcore +hardcover +hardcovers +harden +hardened +hardener +hardeners +hardening +hardens +harder +hardest +hardhat +hardhats +hardheaded +hardheadedly +hardheadedness +hardhearted +hardheartedly +hardheartedness +hardier +hardiest +hardihood +hardily +hardiness +hardline +hardliner +hardliners +hardly +hardness +hardscrabble +hardship +hardships +hardstand +hardstands +hardtack +hardtop +hardtops +hardware +hardwood +hardwoods +hardworking +hardy +hare +harebell +harebells +harebrained +hared +harelip +harelipped +harelips +harem +harems +hares +haring +hark +harked +harken +harkened +harkening +harkens +harking +harks +harlequin +harlequins +harlot +harlotry +harlots +harm +harmed +harmful +harmfully +harmfulness +harming +harmless +harmlessly +harmlessness +harmonic +harmonica +harmonically +harmonicas +harmonics +harmonies +harmonious +harmoniously +harmoniousness +harmonium +harmoniums +harmonization +harmonize +harmonized +harmonizer +harmonizers +harmonizes +harmonizing +harmony +harms +harness +harnessed +harnesses +harnessing +harp +harped +harpies +harping +harpist +harpists +harpoon +harpooned +harpooner +harpooners +harpooning +harpoons +harps +harpsichord +harpsichordist +harpsichordists +harpsichords +harpy +harridan +harridans +harried +harrier +harriers +harries +harrow +harrowed +harrowing +harrows +harry +harrying +harsh +harsher +harshest +harshly +harshness +hart +harts +harvest +harvested +harvester +harvesters +harvesting +harvests +hash +hashed +hasheesh +hashes +hashing +hashish +hasp +hasps +hassle +hassled +hassles +hassling +hassock +hassocks +hast +haste +hasted +hasten +hastened +hastening +hastens +hastes +hastier +hastiest +hastily +hastiness +hasting +hasty +hatbox +hatboxes +hatch +hatchback +hatchbacks +hatcheck +hatchecks +hatched +hatcheries +hatchery +hatches +hatchet +hatchets +hatching +hatchway +hatchways +hate +hated +hateful +hatefully +hatefulness +hatemonger +hatemongers +hater +haters +hates +hath +hating +hatred +hatreds +hats +hatted +hatter +hatters +hatting +hauberk +hauberks +haughtier +haughtiest +haughtily +haughtiness +haughty +haul +haulage +hauled +hauler +haulers +hauling +hauls +haunch +haunches +haunt +haunted +haunter +haunters +haunting +hauntingly +haunts +hauteur +have +haven +havens +haversack +haversacks +haves +having +havoc +hawed +hawing +hawk +hawked +hawker +hawkers +hawking +hawkish +hawkishness +hawks +haws +hawser +hawsers +hawthorn +hawthorns +haycock +haycocks +hayed +haying +hayloft +haylofts +haymow +haymows +hayrick +hayricks +hayride +hayrides +hays +hayseed +hayseeds +haystack +haystacks +haywire +hazard +hazarded +hazarding +hazardous +hazardously +hazards +haze +hazed +hazel +hazelnut +hazelnuts +hazels +hazer +hazers +hazes +hazier +haziest +hazily +haziness +hazing +hazings +hazy +head +headache +headaches +headband +headbands +headboard +headboards +headdress +headdresses +headed +header +headers +headfirst +headgear +headhunt +headhunted +headhunter +headhunters +headhunting +headhunts +headier +headiest +headily +headiness +heading +headings +headlamp +headlamps +headland +headlands +headless +headlight +headlights +headline +headlined +headliner +headliners +headlines +headlining +headlock +headlocks +headlong +headman +headmaster +headmasters +headmen +headmistress +headmistresses +headphone +headphones +headpiece +headpieces +headpin +headpins +headquarter +headquartered +headquartering +headquarters +headrest +headrests +headroom +heads +headset +headsets +headship +headships +headshrinker +headshrinkers +headsman +headsmen +headstall +headstalls +headstand +headstands +headstone +headstones +headstrong +headwaiter +headwaiters +headwaters +headway +headwind +headwinds +headword +headwords +heady +heal +healed +healer +healers +healing +heals +health +healthcare +healthful +healthfully +healthfulness +healthier +healthiest +healthily +healthiness +healthy +heap +heaped +heaping +heaps +hear +heard +hearer +hearers +hearing +hearings +hearken +hearkened +hearkening +hearkens +hears +hearsay +hearse +hearses +heart +heartache +heartaches +heartbeat +heartbeats +heartbreak +heartbreaking +heartbreaks +heartbroken +heartburn +hearten +heartened +heartening +heartens +heartfelt +hearth +hearths +hearthstone +hearthstones +heartier +hearties +heartiest +heartily +heartiness +heartland +heartlands +heartless +heartlessly +heartlessness +heartrending +heartrendingly +hearts +heartsick +heartsickness +heartstrings +heartthrob +heartthrobs +heartwarming +heartwood +hearty +heat +heated +heatedly +heater +heaters +heath +heathen +heathendom +heathenish +heathenism +heathens +heather +heaths +heating +heatproof +heatproofed +heatproofing +heatproofs +heats +heatstroke +heave +heaved +heaven +heavenlier +heavenliest +heavenly +heavens +heavenward +heavenwards +heaver +heavers +heaves +heavier +heavies +heaviest +heavily +heaviness +heaving +heavy +heavyhearted +heavyset +heavyweight +heavyweights +heck +heckle +heckled +heckler +hecklers +heckles +heckling +hectare +hectares +hectic +hectically +hectogram +hectograms +hectometer +hectometers +hector +hectored +hectoring +hectors +hedge +hedged +hedgehog +hedgehogs +hedgehop +hedgehopped +hedgehopping +hedgehops +hedger +hedgerow +hedgerows +hedgers +hedges +hedging +hedonism +hedonist +hedonistic +hedonists +heed +heeded +heedful +heedfully +heeding +heedless +heedlessly +heedlessness +heeds +heehaw +heehawed +heehawing +heehaws +heel +heeled +heeling +heelless +heels +heft +hefted +heftier +heftiest +heftily +heftiness +hefting +hefts +hefty +hegemony +hegira +hegiras +heifer +heifers +height +heighten +heightened +heightening +heightens +heights +heinous +heinously +heinousness +heir +heiress +heiresses +heirloom +heirlooms +heirs +heist +heisted +heisting +heists +held +helical +helices +helicopter +helicoptered +helicoptering +helicopters +heliocentric +heliotrope +heliotropes +heliport +heliports +helium +helix +helixes +hell +hellbent +hellcat +hellcats +hellebore +hellhole +hellholes +hellion +hellions +hellish +hellishly +hellishness +hello +hellos +helluva +helm +helmet +helmeted +helmets +helms +helmsman +helmsmen +helot +helots +help +helped +helper +helpers +helpful +helpfully +helpfulness +helping +helpings +helpless +helplessly +helplessness +helpmate +helpmates +helpmeet +helpmeets +helps +helve +helves +hematite +hematologic +hematological +hematologist +hematologists +hematology +heme +hemisphere +hemispheres +hemispheric +hemispherical +hemline +hemlines +hemlock +hemlocks +hemmed +hemmer +hemmers +hemming +hemoglobin +hemophilia +hemophiliac +hemophiliacs +hemorrhage +hemorrhaged +hemorrhages +hemorrhagic +hemorrhaging +hemorrhoid +hemorrhoids +hemostat +hemostats +hemp +hempen +hems +hemstitch +hemstitched +hemstitches +hemstitching +hence +henceforth +henceforward +henchman +henchmen +henna +hennaed +hennaing +hennas +henpeck +henpecked +henpecking +henpecks +hens +heparin +hepatic +hepatitis +hepper +heppest +heptagon +heptagonal +heptagons +heptathlon +heptathlons +herald +heralded +heraldic +heralding +heraldry +heralds +herb +herbaceous +herbage +herbal +herbalist +herbalists +herbicidal +herbicide +herbicides +herbivore +herbivores +herbivorous +herbs +herculean +herd +herded +herder +herders +herding +herds +herdsman +herdsmen +here +hereabout +hereabouts +hereafter +hereafters +hereby +hereditary +heredity +herein +hereof +hereon +heresies +heresy +heretic +heretical +heretics +hereto +heretofore +hereunto +hereupon +herewith +heritable +heritage +heritages +hermaphrodite +hermaphrodites +hermaphroditic +hermetic +hermetical +hermetically +hermit +hermitage +hermitages +hermits +hernia +herniae +hernial +hernias +herniate +herniated +herniates +herniating +herniation +hero +heroes +heroic +heroically +heroics +heroin +heroine +heroines +heroism +heron +herons +heros +herpes +herpetologist +herpetologists +herpetology +herring +herringbone +herrings +hers +herself +hertz +hertzes +hesitance +hesitancy +hesitant +hesitantly +hesitate +hesitated +hesitates +hesitating +hesitatingly +hesitation +hesitations +hetero +heterodox +heterodoxy +heterogeneity +heterogeneous +heterogeneously +heteros +heterosexual +heterosexuality +heterosexuals +heuristic +heuristically +heuristics +hewed +hewer +hewers +hewing +hewn +hews +hexadecimal +hexagon +hexagonal +hexagons +hexagram +hexagrams +hexameter +hexameters +hexed +hexes +hexing +heyday +heydays +hiatus +hiatuses +hibachi +hibachis +hibernate +hibernated +hibernates +hibernating +hibernation +hibernator +hibernators +hibiscus +hibiscuses +hiccough +hiccoughed +hiccoughing +hiccoughs +hiccup +hiccuped +hiccuping +hiccupped +hiccupping +hiccups +hick +hickey +hickeys +hickies +hickories +hickory +hicks +hidden +hide +hideaway +hideaways +hidebound +hided +hideous +hideously +hideousness +hideout +hideouts +hider +hiders +hides +hiding +hied +hieing +hierarchic +hierarchical +hierarchically +hierarchies +hierarchy +hieroglyph +hieroglyphic +hieroglyphics +hieroglyphs +hies +high +highball +highballs +highborn +highboy +highboys +highbrow +highbrows +highchair +highchairs +higher +highest +highfalutin +highfaluting +highhanded +highhandedly +highhandedness +highjack +highjacked +highjacking +highjacks +highland +highlander +highlanders +highlands +highlight +highlighted +highlighter +highlighters +highlighting +highlights +highly +highness +highrise +highrises +highroad +highroads +highs +hightail +hightailed +hightailing +hightails +highway +highwayman +highwaymen +highways +hijack +hijacked +hijacker +hijackers +hijacking +hijackings +hijacks +hike +hiked +hiker +hikers +hikes +hiking +hilarious +hilariously +hilariousness +hilarity +hill +hillbillies +hillbilly +hillier +hilliest +hilliness +hillock +hillocks +hills +hillside +hillsides +hilltop +hilltops +hilly +hilt +hilts +hims +himself +hind +hinder +hindered +hindering +hinders +hindmost +hindquarter +hindquarters +hindrance +hindrances +hinds +hindsight +hinge +hinged +hinges +hinging +hint +hinted +hinter +hinterland +hinterlands +hinters +hinting +hints +hipbone +hipbones +hipness +hipped +hipper +hippest +hippie +hippies +hipping +hippo +hippodrome +hippodromes +hippopotami +hippopotamus +hippopotamuses +hippos +hippy +hips +hipster +hipsters +hire +hired +hireling +hirelings +hires +hiring +hirsute +hirsuteness +hiss +hissed +hisses +hissing +hist +histamine +histamines +histogram +histograms +histologist +histologists +histology +historian +historians +historic +historical +historically +historicity +histories +historiographer +historiographers +historiography +history +histrionic +histrionically +histrionics +hitch +hitched +hitcher +hitchers +hitches +hitchhike +hitchhiked +hitchhiker +hitchhikers +hitchhikes +hitchhiking +hitching +hither +hitherto +hits +hitter +hitters +hitting +hive +hived +hives +hiving +hoagie +hoagies +hoagy +hoard +hoarded +hoarder +hoarders +hoarding +hoardings +hoards +hoarfrost +hoarier +hoariest +hoariness +hoarse +hoarsely +hoarseness +hoarser +hoarsest +hoary +hoax +hoaxed +hoaxer +hoaxers +hoaxes +hoaxing +hobbies +hobble +hobbled +hobbler +hobblers +hobbles +hobbling +hobby +hobbyhorse +hobbyhorses +hobbyist +hobbyists +hobgoblin +hobgoblins +hobnail +hobnailed +hobnailing +hobnails +hobnob +hobnobbed +hobnobbing +hobnobs +hobo +hoboes +hobos +hobs +hock +hocked +hockey +hocking +hocks +hockshop +hockshops +hodgepodge +hodgepodges +hods +hoecake +hoecakes +hoed +hoedown +hoedowns +hoeing +hoer +hoers +hoes +hogan +hogans +hogback +hogbacks +hogged +hogging +hoggish +hoggishly +hogs +hogshead +hogsheads +hogtie +hogtied +hogtieing +hogties +hogtying +hogwash +hoist +hoisted +hoisting +hoists +hoke +hoked +hokes +hokey +hokier +hokiest +hoking +hokum +hold +holder +holders +holding +holdings +holdout +holdouts +holdover +holdovers +holds +holdup +holdups +hole +holed +holes +holey +holiday +holidayed +holidaying +holidays +holier +holiest +holiness +holing +holistic +holistically +holler +hollered +hollering +hollers +hollies +hollow +hollowed +hollower +hollowest +hollowing +hollowly +hollowness +hollows +holly +hollyhock +hollyhocks +holmium +holocaust +holocausts +hologram +holograms +holograph +holographic +holographs +holography +holster +holstered +holstering +holsters +holy +homage +homages +hombre +hombres +homburg +homburgs +home +homebodies +homebody +homeboy +homeboys +homecoming +homecomings +homed +homegrown +homeland +homelands +homeless +homelessness +homelier +homeliest +homelike +homeliness +homely +homemade +homemaker +homemakers +homemaking +homeopath +homeopathic +homeopaths +homeopathy +homeostasis +homeostatic +homeowner +homeowners +homepage +homepages +homer +homered +homering +homeroom +homerooms +homers +homes +homeschooling +homesick +homesickness +homespun +homestead +homesteaded +homesteader +homesteaders +homesteading +homesteads +homestretch +homestretches +hometown +hometowns +homeward +homewards +homework +homey +homeyness +homeys +homicidal +homicide +homicides +homier +homiest +homiletic +homilies +homily +hominess +homing +hominid +hominids +hominy +homo +homogeneity +homogeneous +homogeneously +homogenization +homogenize +homogenized +homogenizes +homogenizing +homograph +homographs +homologous +homonym +homonyms +homophobia +homophobic +homophone +homophones +homos +homosexual +homosexuality +homosexuals +homy +honcho +honchos +hone +honed +honer +honers +hones +honest +honester +honestest +honestly +honesty +honey +honeybee +honeybees +honeycomb +honeycombed +honeycombing +honeycombs +honeydew +honeydews +honeyed +honeying +honeylocust +honeylocusts +honeymoon +honeymooned +honeymooner +honeymooners +honeymooning +honeymoons +honeys +honeysuckle +honeysuckles +honied +honing +honk +honked +honker +honkers +honkie +honkies +honking +honks +honky +honor +honorable +honorableness +honorably +honoraria +honorarily +honorarium +honorariums +honorary +honored +honoree +honorees +honorer +honorers +honorific +honorifics +honoring +honors +honour +honourable +honoured +honouring +honours +hons +hooch +hood +hooded +hooding +hoodlum +hoodlums +hoodoo +hoodooed +hoodooing +hoodoos +hoods +hoodwink +hoodwinked +hoodwinking +hoodwinks +hooey +hoof +hoofed +hoofing +hoofs +hook +hooka +hookah +hookahs +hookas +hooked +hooker +hookers +hookey +hooking +hooks +hookup +hookups +hookworm +hookworms +hooky +hooligan +hooliganism +hooligans +hoop +hooped +hooping +hoopla +hoops +hooray +hoorayed +hooraying +hoorays +hoosegow +hoosegows +hoot +hootch +hooted +hootenannies +hootenanny +hooter +hooters +hooting +hoots +hooves +hope +hoped +hopeful +hopefully +hopefulness +hopefuls +hopeless +hopelessly +hopelessness +hopes +hoping +hopped +hopper +hoppers +hopping +hops +hopscotch +hopscotched +hopscotches +hopscotching +hora +horas +horde +horded +hordes +hording +horehound +horehounds +horizon +horizons +horizontal +horizontally +horizontals +hormonal +hormone +hormones +horn +hornblende +horned +hornet +hornets +hornier +horniest +hornless +hornlike +hornpipe +hornpipes +horns +horny +horologic +horological +horologist +horologists +horology +horoscope +horoscopes +horrendous +horrendously +horrible +horribleness +horribly +horrid +horrider +horridest +horridly +horrific +horrifically +horrified +horrifies +horrify +horrifying +horror +horrors +horse +horseback +horsed +horseflesh +horseflies +horsefly +horsehair +horsehide +horselaugh +horselaughs +horseless +horseman +horsemanship +horsemen +horseplay +horsepower +horseradish +horseradishes +horses +horseshoe +horseshoed +horseshoeing +horseshoes +horsetail +horsetails +horsewhip +horsewhipped +horsewhipping +horsewhips +horsewoman +horsewomen +horsey +horsier +horsiest +horsing +horsy +hortatory +horticultural +horticulture +horticulturist +horticulturists +hosanna +hosannas +hose +hosed +hoses +hosier +hosiers +hosiery +hosing +hospice +hospices +hospitable +hospitably +hospital +hospitality +hospitalization +hospitalizations +hospitalize +hospitalized +hospitalizes +hospitalizing +hospitals +host +hostage +hostages +hosted +hostel +hosteled +hosteler +hostelers +hosteling +hostelled +hostelling +hostelries +hostelry +hostels +hostess +hostessed +hostesses +hostessing +hostile +hostilely +hostiles +hostilities +hostility +hosting +hostler +hostlers +hosts +hotbed +hotbeds +hotblooded +hotbox +hotboxes +hotcake +hotcakes +hotdog +hotdogged +hotdogging +hotdogs +hotel +hotelier +hoteliers +hotels +hotfoot +hotfooted +hotfooting +hotfoots +hothead +hotheaded +hotheadedly +hotheadedness +hotheads +hothouse +hothouses +hotline +hotlines +hotly +hotness +hotplate +hotplates +hots +hotshot +hotshots +hotspot +hotspots +hotter +hottest +hound +hounded +hounding +hounds +hour +hourglass +hourglasses +houri +houris +hourly +hours +house +houseboat +houseboats +housebound +houseboy +houseboys +housebreak +housebreaker +housebreakers +housebreaking +housebreaks +housebroke +housebroken +houseclean +housecleaned +housecleaning +housecleans +housecoat +housecoats +housed +houseflies +housefly +houseful +housefuls +household +householder +householders +households +househusband +househusbands +housekeeper +housekeepers +housekeeping +houselights +housemaid +housemaids +houseman +housemen +housemother +housemothers +houseparent +houseparents +houseplant +houseplants +houses +housetop +housetops +housewares +housewarming +housewarmings +housewife +housewifely +housewives +housework +housing +housings +hove +hovel +hovels +hover +hovercraft +hovercrafts +hovered +hovering +hovers +howbeit +howdah +howdahs +howdy +however +howitzer +howitzers +howl +howled +howler +howlers +howling +howls +hows +howsoever +hoyden +hoydenish +hoydens +huarache +huaraches +hubbies +hubbub +hubbubs +hubby +hubcap +hubcaps +hubris +hubs +huckleberries +huckleberry +huckster +huckstered +huckstering +hucksterism +hucksters +huddle +huddled +huddles +huddling +hued +hues +huff +huffed +huffier +huffiest +huffily +huffiness +huffing +huffs +huffy +huge +hugely +hugeness +huger +hugest +hugged +hugging +hugs +hula +hulas +hulk +hulking +hulks +hull +hullabaloo +hullabaloos +hulled +huller +hullers +hulling +hullo +hullos +hulls +human +humane +humanely +humaneness +humaner +humanest +humanism +humanist +humanistic +humanists +humanitarian +humanitarianism +humanitarians +humanities +humanity +humanization +humanize +humanized +humanizer +humanizers +humanizes +humanizing +humankind +humanly +humanness +humanoid +humanoids +humans +humble +humbled +humbleness +humbler +humblers +humbles +humblest +humbling +humbly +humbug +humbugged +humbugging +humbugs +humdinger +humdingers +humdrum +humeral +humeri +humerus +humid +humider +humidest +humidification +humidified +humidifier +humidifiers +humidifies +humidify +humidifying +humidity +humidly +humidor +humidors +humiliate +humiliated +humiliates +humiliating +humiliatingly +humiliation +humiliations +humility +hummed +hummer +hummers +humming +hummingbird +hummingbirds +hummock +hummocks +hummocky +hummus +humongous +humor +humored +humoring +humorist +humorists +humorless +humorlessly +humorlessness +humorous +humorously +humorousness +humors +humour +humoured +humouring +humours +hump +humpback +humpbacked +humpbacks +humped +humph +humphed +humphing +humphs +humping +humps +hums +humungous +humus +hunch +hunchback +hunchbacked +hunchbacks +hunched +hunches +hunching +hundred +hundredfold +hundreds +hundredth +hundredths +hundredweight +hundredweights +hung +hunger +hungered +hungering +hungers +hungover +hungrier +hungriest +hungrily +hungriness +hungry +hunk +hunker +hunkered +hunkering +hunkers +hunkier +hunkiest +hunks +hunky +hunt +hunted +hunter +hunters +hunting +huntress +huntresses +hunts +huntsman +huntsmen +hurdle +hurdled +hurdler +hurdlers +hurdles +hurdling +hurl +hurled +hurler +hurlers +hurling +hurls +hurrah +hurrahed +hurrahing +hurrahs +hurray +hurrayed +hurraying +hurrays +hurricane +hurricanes +hurried +hurriedly +hurries +hurry +hurrying +hurt +hurtful +hurtfully +hurtfulness +hurting +hurtle +hurtled +hurtles +hurtling +hurts +husband +husbanded +husbanding +husbandman +husbandmen +husbandry +husbands +hush +hushed +hushes +hushing +husk +husked +husker +huskers +huskier +huskies +huskiest +huskily +huskiness +husking +husks +husky +hussar +hussars +hussies +hussy +hustings +hustle +hustled +hustler +hustlers +hustles +hustling +hutch +hutches +huts +hutzpa +hutzpah +huzza +huzzaed +huzzah +huzzahed +huzzahing +huzzahs +huzzaing +huzzas +hyacinth +hyacinths +hyaena +hyaenas +hybrid +hybridism +hybridization +hybridize +hybridized +hybridizes +hybridizing +hybrids +hydra +hydrae +hydrangea +hydrangeas +hydrant +hydrants +hydras +hydrate +hydrated +hydrates +hydrating +hydration +hydraulic +hydraulically +hydraulics +hydro +hydrocarbon +hydrocarbons +hydrocephalus +hydrocephaly +hydrodynamic +hydrodynamics +hydroelectric +hydroelectrically +hydroelectricity +hydrofoil +hydrofoils +hydrogen +hydrogenate +hydrogenated +hydrogenates +hydrogenating +hydrogenation +hydrogenous +hydrologist +hydrologists +hydrology +hydrolysis +hydrolyze +hydrolyzed +hydrolyzes +hydrolyzing +hydrometer +hydrometers +hydrometry +hydrophobia +hydrophobic +hydrophone +hydrophones +hydroplane +hydroplaned +hydroplanes +hydroplaning +hydroponic +hydroponically +hydroponics +hydrosphere +hydrotherapy +hydrous +hydroxide +hydroxides +hyena +hyenas +hygiene +hygienic +hygienically +hygienist +hygienists +hygrometer +hygrometers +hying +hymen +hymeneal +hymens +hymn +hymnal +hymnals +hymnbook +hymnbooks +hymned +hymning +hymns +hype +hyped +hyper +hyperactive +hyperactivity +hyperbola +hyperbolae +hyperbolas +hyperbole +hyperbolic +hypercritical +hypercritically +hyperglycemia +hyperlink +hyperlinks +hypermedia +hypersensitive +hypersensitiveness +hypersensitivities +hypersensitivity +hypertension +hypertensive +hypertensives +hypertext +hyperthyroid +hyperthyroidism +hypertrophied +hypertrophies +hypertrophy +hypertrophying +hyperventilate +hyperventilated +hyperventilates +hyperventilating +hyperventilation +hypes +hyphen +hyphenate +hyphenated +hyphenates +hyphenating +hyphenation +hyphened +hyphening +hyphens +hyping +hypnosis +hypnotherapy +hypnotic +hypnotically +hypnotics +hypnotism +hypnotist +hypnotists +hypnotize +hypnotized +hypnotizes +hypnotizing +hypo +hypoallergenic +hypochondria +hypochondriac +hypochondriacs +hypocrisies +hypocrisy +hypocrite +hypocrites +hypocritical +hypocritically +hypodermic +hypodermics +hypoglycemia +hypoglycemic +hypoglycemics +hypos +hypotenuse +hypotenuses +hypothalami +hypothalamus +hypothermia +hypotheses +hypothesis +hypothesize +hypothesized +hypothesizes +hypothesizing +hypothetical +hypothetically +hypothyroid +hypothyroidism +hyssop +hysterectomies +hysterectomy +hysteria +hysteric +hysterical +hysterically +hysterics +iamb +iambi +iambic +iambics +iambs +iambus +iambuses +ibex +ibexes +ibices +ibidem +ibis +ibises +ibuprofen +iceberg +icebergs +iceboat +iceboats +icebound +icebox +iceboxes +icebreaker +icebreakers +icecap +icecaps +iced +iceman +icemen +ices +ichthyologist +ichthyologists +ichthyology +icicle +icicles +icier +iciest +icily +iciness +icing +icings +ickier +ickiest +icky +icon +iconic +iconoclasm +iconoclast +iconoclastic +iconoclasts +iconography +icons +ictus +idea +ideal +idealism +idealist +idealistic +idealistically +idealists +idealization +idealize +idealized +idealizes +idealizing +ideally +ideals +ideas +idem +identical +identically +identifiable +identification +identified +identifies +identify +identifying +identities +identity +ideogram +ideograms +ideograph +ideographs +ideological +ideologically +ideologies +ideologist +ideologists +ideologue +ideologues +ideology +ides +idiocies +idiocy +idiom +idiomatic +idiomatically +idioms +idiopathic +idiosyncrasies +idiosyncrasy +idiosyncratic +idiosyncratically +idiot +idiotic +idiotically +idiots +idle +idled +idleness +idler +idlers +idles +idlest +idling +idly +idol +idolater +idolaters +idolatress +idolatresses +idolatrous +idolatry +idolization +idolize +idolized +idolizes +idolizing +idols +idyl +idyll +idyllic +idyllically +idylls +idyls +iffier +iffiest +iffiness +iffy +igloo +igloos +igneous +ignitable +ignite +ignited +ignites +ignitible +igniting +ignition +ignitions +ignoble +ignobler +ignoblest +ignobly +ignominies +ignominious +ignominiously +ignominy +ignoramus +ignoramuses +ignorance +ignorant +ignorantly +ignore +ignored +ignores +ignoring +iguana +iguanas +ikon +ikons +ilea +ileitis +ileum +ilia +ilium +ilks +illegal +illegalities +illegality +illegally +illegals +illegibility +illegible +illegibly +illegitimacy +illegitimate +illegitimately +illiberal +illiberality +illiberally +illicit +illicitly +illicitness +illimitable +illiteracy +illiterate +illiterately +illiterates +illness +illnesses +illogical +illogicality +illogically +ills +illuminable +illuminate +illuminated +illuminates +illuminating +illuminatingly +illumination +illuminations +illumine +illumined +illumines +illumining +illusion +illusionist +illusionists +illusions +illusive +illusory +illustrate +illustrated +illustrates +illustrating +illustration +illustrations +illustrative +illustratively +illustrator +illustrators +illustrious +illustriously +illustriousness +image +imaged +imagery +images +imaginable +imaginably +imaginary +imagination +imaginations +imaginative +imaginatively +imagine +imagined +imagines +imaging +imagining +imago +imagoes +imam +imams +imbalance +imbalances +imbecile +imbeciles +imbecilic +imbecilities +imbecility +imbed +imbedded +imbedding +imbeds +imbibe +imbibed +imbiber +imbibers +imbibes +imbibing +imbrication +imbroglio +imbroglios +imbue +imbued +imbues +imbuing +imitable +imitate +imitated +imitates +imitating +imitation +imitations +imitative +imitatively +imitativeness +imitator +imitators +immaculate +immaculately +immaculateness +immanence +immanency +immanent +immanently +immaterial +immateriality +immaterially +immaterialness +immature +immaturely +immaturity +immeasurable +immeasurably +immediacies +immediacy +immediate +immediately +immediateness +immemorial +immemorially +immense +immensely +immensities +immensity +immerse +immersed +immerses +immersible +immersing +immersion +immersions +immigrant +immigrants +immigrate +immigrated +immigrates +immigrating +immigration +imminence +imminent +imminently +immobile +immobility +immobilization +immobilize +immobilized +immobilizes +immobilizing +immoderate +immoderately +immodest +immodestly +immodesty +immolate +immolated +immolates +immolating +immolation +immoral +immoralities +immorality +immorally +immortal +immortality +immortalize +immortalized +immortalizes +immortalizing +immortally +immortals +immovability +immovable +immovably +immune +immunity +immunization +immunizations +immunize +immunized +immunizes +immunizing +immunodeficiency +immunodeficient +immunologic +immunological +immunologist +immunologists +immunology +immure +immured +immures +immuring +immutability +immutable +immutably +impact +impacted +impacting +impacts +impair +impaired +impairing +impairment +impairments +impairs +impala +impalas +impale +impaled +impalement +impales +impaling +impalpable +impalpably +impanel +impaneled +impaneling +impanelled +impanelling +impanels +impart +imparted +impartial +impartiality +impartially +imparting +imparts +impassable +impassably +impasse +impasses +impassibility +impassible +impassibly +impassioned +impassive +impassively +impassiveness +impassivity +impasto +impatience +impatiences +impatiens +impatient +impatiently +impeach +impeachable +impeached +impeacher +impeachers +impeaches +impeaching +impeachment +impeachments +impeccability +impeccable +impeccably +impecunious +impecuniously +impecuniousness +impedance +impede +impeded +impedes +impediment +impedimenta +impediments +impeding +impel +impelled +impeller +impellers +impelling +impels +impend +impended +impending +impends +impenetrability +impenetrable +impenetrably +impenitence +impenitent +impenitently +imperative +imperatively +imperatives +imperceptibility +imperceptible +imperceptibly +imperceptive +imperfect +imperfection +imperfections +imperfectly +imperfectness +imperfects +imperial +imperialism +imperialist +imperialistic +imperialistically +imperialists +imperially +imperials +imperil +imperiled +imperiling +imperilled +imperilling +imperilment +imperils +imperious +imperiously +imperiousness +imperishable +imperishably +impermanence +impermanent +impermanently +impermeability +impermeable +impermeably +impermissible +impersonal +impersonally +impersonate +impersonated +impersonates +impersonating +impersonation +impersonations +impersonator +impersonators +impertinence +impertinent +impertinently +imperturbability +imperturbable +imperturbably +impervious +imperviously +impetigo +impetuosity +impetuous +impetuously +impetuousness +impetus +impetuses +impieties +impiety +impinge +impinged +impingement +impinges +impinging +impious +impiously +impiousness +impish +impishly +impishness +implacability +implacable +implacably +implant +implantable +implantation +implanted +implanting +implants +implausibilities +implausibility +implausible +implausibly +implement +implementation +implementations +implemented +implementing +implements +implicate +implicated +implicates +implicating +implication +implications +implicit +implicitly +implicitness +implied +implies +implode +imploded +implodes +imploding +implore +implored +implores +imploring +imploringly +implosion +implosions +implosive +imply +implying +impolite +impolitely +impoliteness +impolitenesses +impolitic +imponderable +imponderables +import +importable +importance +important +importantly +importation +importations +imported +importer +importers +importing +imports +importunate +importunately +importune +importuned +importunes +importuning +importunity +impose +imposed +imposer +imposers +imposes +imposing +imposingly +imposition +impositions +impossibilities +impossibility +impossible +impossibly +impost +imposter +imposters +impostor +impostors +imposts +imposture +impostures +impotence +impotency +impotent +impotently +impound +impounded +impounding +impounds +impoverish +impoverished +impoverishes +impoverishing +impoverishment +impracticable +impracticably +impractical +impracticality +impractically +imprecate +imprecated +imprecates +imprecating +imprecation +imprecations +imprecise +imprecisely +impreciseness +imprecision +impregnability +impregnable +impregnably +impregnate +impregnated +impregnates +impregnating +impregnation +impresario +impresarios +impress +impressed +impresses +impressibility +impressible +impressing +impression +impressionability +impressionable +impressionism +impressionist +impressionistic +impressionists +impressions +impressive +impressively +impressiveness +imprimatur +imprimaturs +imprint +imprinted +imprinter +imprinters +imprinting +imprints +imprison +imprisoned +imprisoning +imprisonment +imprisonments +imprisons +improbabilities +improbability +improbable +improbably +impromptu +impromptus +improper +improperly +improprieties +impropriety +improvable +improve +improved +improvement +improvements +improves +improvidence +improvident +improvidently +improving +improvisation +improvisational +improvisations +improvise +improvised +improviser +improvisers +improvises +improvising +improvisor +improvisors +imprudence +imprudent +imprudently +imps +impudence +impudent +impudently +impugn +impugned +impugner +impugners +impugning +impugns +impulse +impulsed +impulses +impulsing +impulsion +impulsive +impulsively +impulsiveness +impunity +impure +impurely +impurer +impurest +impurities +impurity +imputable +imputation +imputations +impute +imputed +imputes +imputing +inabilities +inability +inaccessibility +inaccessible +inaccessibly +inaccuracies +inaccuracy +inaccurate +inaccurately +inaction +inactivate +inactivated +inactivates +inactivating +inactivation +inactive +inactively +inactivity +inadequacies +inadequacy +inadequate +inadequately +inadmissibility +inadmissible +inadvertence +inadvertent +inadvertently +inadvisability +inadvisable +inalienability +inalienable +inalienably +inamorata +inamoratas +inane +inanely +inaner +inanest +inanimate +inanimately +inanimateness +inanities +inanity +inapplicable +inappreciable +inappreciably +inapproachable +inappropriate +inappropriately +inappropriateness +inapt +inaptly +inaptness +inarguable +inarticulate +inarticulately +inarticulateness +inartistic +inattention +inattentive +inattentively +inattentiveness +inaudibility +inaudible +inaudibly +inaugural +inaugurals +inaugurate +inaugurated +inaugurates +inaugurating +inauguration +inaugurations +inauspicious +inauspiciously +inauthentic +inboard +inboards +inborn +inbound +inbounded +inbounding +inbounds +inbred +inbreed +inbreeding +inbreeds +incalculable +incalculably +incandescence +incandescent +incandescently +incantation +incantations +incapability +incapable +incapably +incapacitate +incapacitated +incapacitates +incapacitating +incapacity +incarcerate +incarcerated +incarcerates +incarcerating +incarceration +incarcerations +incarnadine +incarnadined +incarnadines +incarnadining +incarnate +incarnated +incarnates +incarnating +incarnation +incarnations +incautious +incendiaries +incendiary +incense +incensed +incenses +incensing +incentive +incentives +inception +inceptions +incertitude +incessant +incessantly +incest +incestuous +incestuously +incestuousness +inch +inched +inches +inching +inchoate +inchworm +inchworms +incidence +incidences +incident +incidental +incidentally +incidentals +incidents +incinerate +incinerated +incinerates +incinerating +incineration +incinerator +incinerators +incipience +incipient +incipiently +incise +incised +incises +incising +incision +incisions +incisive +incisively +incisiveness +incisor +incisors +incite +incited +incitement +incitements +inciter +inciters +incites +inciting +incivilities +incivility +inclemency +inclement +inclination +inclinations +incline +inclined +inclines +inclining +inclose +inclosed +incloses +inclosing +inclosure +inclosures +include +included +includes +including +inclusion +inclusions +inclusive +inclusively +inclusiveness +incognito +incognitos +incoherence +incoherent +incoherently +incombustible +income +incomes +incoming +incommensurate +incommensurately +incommode +incommoded +incommodes +incommoding +incommodious +incommunicable +incommunicado +incomparable +incomparably +incompatibilities +incompatibility +incompatible +incompatibles +incompatibly +incompetence +incompetency +incompetent +incompetently +incompetents +incomplete +incompletely +incompleteness +incompletes +incomprehensibility +incomprehensible +incomprehensibly +incomprehension +inconceivability +inconceivable +inconceivably +inconclusive +inconclusively +inconclusiveness +incongruities +incongruity +incongruous +incongruously +incongruousness +inconsequential +inconsequentially +inconsiderable +inconsiderate +inconsiderately +inconsiderateness +inconsideration +inconsistencies +inconsistency +inconsistent +inconsistently +inconsolable +inconsolably +inconspicuous +inconspicuously +inconspicuousness +inconstancy +inconstant +inconstantly +incontestability +incontestable +incontestably +incontinence +incontinent +incontrovertible +incontrovertibly +inconvenience +inconvenienced +inconveniences +inconveniencing +inconvenient +inconveniently +incorporate +incorporated +incorporates +incorporating +incorporation +incorporeal +incorrect +incorrectly +incorrectness +incorrigibility +incorrigible +incorrigibly +incorruptibility +incorruptible +incorruptibly +increase +increased +increases +increasing +increasingly +incredibility +incredible +incredibly +incredulity +incredulous +incredulously +increment +incremental +increments +incriminate +incriminated +incriminates +incriminating +incrimination +incriminatory +incrust +incrustation +incrustations +incrusted +incrusting +incrusts +incubate +incubated +incubates +incubating +incubation +incubator +incubators +incubi +incubus +incubuses +inculcate +inculcated +inculcates +inculcating +inculcation +inculpable +inculpate +inculpated +inculpates +inculpating +incumbencies +incumbency +incumbent +incumbents +incumber +incumbered +incumbering +incumbers +incunabula +incunabulum +incur +incurable +incurables +incurably +incurious +incurred +incurring +incurs +incursion +incursions +indebted +indebtedness +indecencies +indecency +indecent +indecently +indecipherable +indecision +indecisive +indecisively +indecisiveness +indecorous +indecorously +indeed +indefatigable +indefatigably +indefeasible +indefeasibly +indefensible +indefensibly +indefinable +indefinably +indefinite +indefinitely +indefiniteness +indelible +indelibly +indelicacies +indelicacy +indelicate +indelicately +indemnification +indemnifications +indemnified +indemnifies +indemnify +indemnifying +indemnities +indemnity +indemonstrable +indent +indentation +indentations +indented +indenting +indention +indents +indenture +indentured +indentures +indenturing +independence +independent +independently +independents +indescribable +indescribably +indestructibility +indestructible +indestructibly +indeterminable +indeterminably +indeterminacy +indeterminate +indeterminately +index +indexation +indexations +indexed +indexer +indexers +indexes +indexing +indicate +indicated +indicates +indicating +indication +indications +indicative +indicatively +indicatives +indicator +indicators +indices +indict +indictable +indicted +indicting +indictment +indictments +indicts +indifference +indifferent +indifferently +indigence +indigenous +indigent +indigently +indigents +indigestible +indigestion +indignant +indignantly +indignation +indignities +indignity +indigo +indirect +indirected +indirecting +indirection +indirectly +indirectness +indirects +indiscernible +indiscreet +indiscreetly +indiscretion +indiscretions +indiscriminate +indiscriminately +indispensability +indispensable +indispensables +indispensably +indisposed +indisposition +indispositions +indisputable +indisputably +indissoluble +indissolubly +indistinct +indistinctly +indistinctness +indistinguishable +indistinguishably +indite +indited +indites +inditing +indium +individual +individualism +individualist +individualistic +individualistically +individualists +individuality +individualization +individualize +individualized +individualizes +individualizing +individually +individuals +individuate +individuated +individuates +individuating +individuation +indivisibility +indivisible +indivisibly +indoctrinate +indoctrinated +indoctrinates +indoctrinating +indoctrination +indolence +indolent +indolently +indomitable +indomitably +indoor +indoors +indorse +indorsed +indorses +indorsing +indubitable +indubitably +induce +induced +inducement +inducements +inducer +inducers +induces +inducing +induct +inductance +inducted +inductee +inductees +inducting +induction +inductions +inductive +inducts +indue +indued +indues +induing +indulge +indulged +indulgence +indulgences +indulgent +indulgently +indulges +indulging +industrial +industrialism +industrialist +industrialists +industrialization +industrialize +industrialized +industrializes +industrializing +industrially +industries +industrious +industriously +industriousness +industry +indwell +indwelled +indwelling +indwells +indwelt +inebriate +inebriated +inebriates +inebriating +inebriation +inedible +ineducable +ineffability +ineffable +ineffably +ineffective +ineffectively +ineffectiveness +ineffectual +ineffectually +inefficacy +inefficiencies +inefficiency +inefficient +inefficiently +inelastic +inelegance +inelegant +inelegantly +ineligibility +ineligible +ineligibles +ineligibly +ineluctable +ineluctably +inept +inepter +ineptest +ineptitude +ineptly +ineptness +inequalities +inequality +inequitable +inequitably +inequities +inequity +ineradicable +inerrant +inert +inertia +inertial +inertly +inertness +inescapable +inescapably +inessential +inessentials +inestimable +inestimably +inevitability +inevitable +inevitables +inevitably +inexact +inexactly +inexactness +inexcusable +inexcusably +inexhaustible +inexhaustibly +inexorable +inexorably +inexpedience +inexpediency +inexpedient +inexpensive +inexpensively +inexpensiveness +inexperience +inexperienced +inexpert +inexpertly +inexpiable +inexplicable +inexplicably +inexpressible +inexpressibly +inexpressive +inextinguishable +inextricable +inextricably +infallibility +infallible +infallibly +infamies +infamous +infamously +infamy +infancy +infant +infanticide +infanticides +infantile +infantries +infantry +infantryman +infantrymen +infants +infarct +infarction +infarcts +infatuate +infatuated +infatuates +infatuating +infatuation +infatuations +infect +infected +infecting +infection +infections +infectious +infectiously +infectiousness +infects +infelicities +infelicitous +infelicity +infer +inference +inferences +inferential +inferior +inferiority +inferiors +infernal +infernally +inferno +infernos +inferred +inferring +infers +infertile +infertility +infest +infestation +infestations +infested +infesting +infests +infidel +infidelities +infidelity +infidels +infield +infielder +infielders +infields +infighter +infighters +infighting +infiltrate +infiltrated +infiltrates +infiltrating +infiltration +infiltrator +infiltrators +infinite +infinitely +infinitesimal +infinitesimally +infinitesimals +infinities +infinitival +infinitive +infinitives +infinitude +infinity +infirm +infirmaries +infirmary +infirmities +infirmity +inflame +inflamed +inflames +inflaming +inflammability +inflammable +inflammation +inflammations +inflammatory +inflatable +inflatables +inflate +inflated +inflates +inflating +inflation +inflationary +inflect +inflected +inflecting +inflection +inflectional +inflections +inflects +inflexibility +inflexible +inflexibly +inflexion +inflexions +inflict +inflicted +inflicting +infliction +inflictive +inflicts +inflight +inflorescence +inflorescent +inflow +inflows +influence +influenced +influences +influencing +influential +influentially +influenza +influx +influxes +info +infold +infolded +infolding +infolds +infomercial +infomercials +inform +informal +informality +informally +informant +informants +information +informational +informative +informatively +informativeness +informed +informer +informers +informing +informs +infotainment +infra +infraction +infractions +infrared +infrasonic +infrastructure +infrastructures +infrequence +infrequency +infrequent +infrequently +infringe +infringed +infringement +infringements +infringes +infringing +infuriate +infuriated +infuriates +infuriating +infuriatingly +infuse +infused +infuser +infusers +infuses +infusing +infusion +infusions +ingenious +ingeniously +ingeniousness +ingenue +ingenues +ingenuity +ingenuous +ingenuously +ingenuousness +ingest +ingested +ingesting +ingestion +ingests +inglenook +inglenooks +inglorious +ingloriously +ingot +ingots +ingrain +ingrained +ingraining +ingrains +ingrate +ingrates +ingratiate +ingratiated +ingratiates +ingratiating +ingratiatingly +ingratiation +ingratitude +ingredient +ingredients +ingress +ingresses +ingrowing +ingrown +inguinal +inhabit +inhabitable +inhabitant +inhabitants +inhabited +inhabiting +inhabits +inhalant +inhalants +inhalation +inhalations +inhalator +inhalators +inhale +inhaled +inhaler +inhalers +inhales +inhaling +inharmonious +inhere +inhered +inherent +inherently +inheres +inhering +inherit +inheritable +inheritance +inheritances +inherited +inheriting +inheritor +inheritors +inherits +inhibit +inhibited +inhibiting +inhibition +inhibitions +inhibitor +inhibitors +inhibitory +inhibits +inhospitable +inhospitably +inhuman +inhumane +inhumanely +inhumanities +inhumanity +inhumanly +inimical +inimically +inimitable +inimitably +iniquities +iniquitous +iniquitously +iniquity +initial +initialed +initialing +initialize +initialized +initializes +initializing +initialled +initialling +initially +initials +initiate +initiated +initiates +initiating +initiation +initiations +initiative +initiatives +initiator +initiators +initiatory +inject +injected +injecting +injection +injections +injector +injectors +injects +injudicious +injudiciously +injudiciousness +injunction +injunctions +injure +injured +injurer +injurers +injures +injuries +injuring +injurious +injury +injustice +injustices +inkblot +inkblots +inked +inkier +inkiest +inkiness +inking +inkling +inklings +inks +inkstand +inkstands +inkwell +inkwells +inky +inlaid +inland +inlay +inlaying +inlays +inlet +inlets +inmate +inmates +inmost +innards +innate +innately +innateness +inner +innermost +innersole +innersoles +innerspring +innervate +innervated +innervates +innervating +innervation +inning +innings +innkeeper +innkeepers +innocence +innocent +innocently +innocents +innocuous +innocuously +innocuousness +innovate +innovated +innovates +innovating +innovation +innovations +innovative +innovator +innovators +inns +innuendo +innuendoes +innuendos +innumerable +innumerably +innumeracy +innumerate +inoculate +inoculated +inoculates +inoculating +inoculation +inoculations +inoffensive +inoffensively +inoffensiveness +inoperable +inoperative +inopportune +inopportunely +inordinate +inordinately +inorganic +inorganically +inpatient +inpatients +input +inputs +inputted +inputting +inquest +inquests +inquietude +inquire +inquired +inquirer +inquirers +inquires +inquiries +inquiring +inquiringly +inquiry +inquisition +inquisitional +inquisitions +inquisitive +inquisitively +inquisitiveness +inquisitor +inquisitorial +inquisitors +inroad +inroads +inrush +inrushes +insalubrious +insane +insanely +insaner +insanest +insanitary +insanity +insatiability +insatiable +insatiably +inscribe +inscribed +inscriber +inscribers +inscribes +inscribing +inscription +inscriptions +inscrutability +inscrutable +inscrutableness +inscrutably +inseam +inseams +insect +insecticidal +insecticide +insecticides +insectivore +insectivores +insectivorous +insects +insecure +insecurely +insecurities +insecurity +inseminate +inseminated +inseminates +inseminating +insemination +insensate +insensibility +insensible +insensibly +insensitive +insensitively +insensitivity +insentience +insentient +inseparability +inseparable +inseparables +inseparably +insert +inserted +inserting +insertion +insertions +inserts +inset +insets +insetted +insetting +inshore +inside +insider +insiders +insides +insidious +insidiously +insidiousness +insight +insightful +insights +insigne +insignia +insignias +insignificance +insignificant +insignificantly +insincere +insincerely +insincerity +insinuate +insinuated +insinuates +insinuating +insinuation +insinuations +insinuative +insinuator +insinuators +insipid +insipidity +insipidly +insist +insisted +insistence +insistent +insistently +insisting +insistingly +insists +insobriety +insofar +insole +insolence +insolent +insolently +insoles +insolubility +insoluble +insolubly +insolvable +insolvency +insolvent +insolvents +insomnia +insomniac +insomniacs +insomuch +insouciance +insouciant +inspect +inspected +inspecting +inspection +inspections +inspector +inspectorate +inspectorates +inspectors +inspects +inspiration +inspirational +inspirations +inspire +inspired +inspires +inspiring +inspirit +inspirited +inspiriting +inspirits +instability +instal +install +installation +installations +installed +installer +installers +installing +installment +installments +installs +instalment +instalments +instals +instance +instanced +instances +instancing +instant +instantaneous +instantaneously +instanter +instantiate +instantiated +instantiates +instantiating +instantly +instants +instate +instated +instates +instating +instead +instep +insteps +instigate +instigated +instigates +instigating +instigation +instigator +instigators +instil +instill +instillation +instilled +instilling +instills +instils +instinct +instinctive +instinctively +instincts +instinctual +institute +instituted +instituter +instituters +institutes +instituting +institution +institutional +institutionalization +institutionalize +institutionalized +institutionalizes +institutionalizing +institutionally +institutions +institutor +institutors +instruct +instructed +instructing +instruction +instructional +instructions +instructive +instructively +instructor +instructors +instructs +instrument +instrumental +instrumentalist +instrumentalists +instrumentality +instrumentally +instrumentals +instrumentation +instrumented +instrumenting +instruments +insubordinate +insubordination +insubstantial +insubstantially +insufferable +insufferably +insufficiency +insufficient +insufficiently +insular +insularity +insulate +insulated +insulates +insulating +insulation +insulator +insulators +insulin +insult +insulted +insulting +insultingly +insults +insuperable +insuperably +insupportable +insurable +insurance +insurances +insure +insured +insureds +insurer +insurers +insures +insurgence +insurgences +insurgencies +insurgency +insurgent +insurgents +insuring +insurmountable +insurmountably +insurrection +insurrectionist +insurrectionists +insurrections +insusceptible +intact +intagli +intaglio +intaglios +intake +intakes +intangibility +intangible +intangibles +intangibly +integer +integers +integral +integrally +integrals +integrate +integrated +integrates +integrating +integration +integrative +integrity +integument +integuments +intellect +intellects +intellectual +intellectualism +intellectualize +intellectualized +intellectualizes +intellectualizing +intellectually +intellectuals +intelligence +intelligent +intelligently +intelligentsia +intelligibility +intelligible +intelligibly +intemperance +intemperate +intemperately +intend +intended +intendeds +intending +intends +intense +intensely +intenser +intensest +intensification +intensified +intensifier +intensifiers +intensifies +intensify +intensifying +intensities +intensity +intensive +intensively +intensiveness +intensives +intent +intention +intentional +intentionally +intentions +intently +intentness +intents +inter +interact +interacted +interacting +interaction +interactions +interactive +interactively +interacts +interbred +interbreed +interbreeding +interbreeds +intercede +interceded +intercedes +interceding +intercept +intercepted +intercepting +interception +interceptions +interceptor +interceptors +intercepts +intercession +intercessions +intercessor +intercessors +intercessory +interchange +interchangeable +interchangeably +interchanged +interchanges +interchanging +intercollegiate +intercom +intercommunicate +intercommunicated +intercommunicates +intercommunicating +intercommunication +intercoms +interconnect +interconnected +interconnecting +interconnection +interconnections +interconnects +intercontinental +intercourse +intercultural +interdenominational +interdepartmental +interdependence +interdependent +interdependently +interdict +interdicted +interdicting +interdiction +interdicts +interdisciplinary +interest +interested +interesting +interestingly +interests +interface +interfaced +interfaces +interfacing +interfaith +interfere +interfered +interference +interferes +interfering +interferon +interfile +interfiled +interfiles +interfiling +intergalactic +intergovernmental +interim +interior +interiors +interject +interjected +interjecting +interjection +interjections +interjects +interlace +interlaced +interlaces +interlacing +interlard +interlarded +interlarding +interlards +interleave +interleaved +interleaves +interleaving +interleukin +interline +interlinear +interlined +interlines +interlining +interlinings +interlink +interlinked +interlinking +interlinks +interlock +interlocked +interlocking +interlocks +interlocutor +interlocutors +interlocutory +interlope +interloped +interloper +interlopers +interlopes +interloping +interlude +interludes +intermarriage +intermarriages +intermarried +intermarries +intermarry +intermarrying +intermediaries +intermediary +intermediate +intermediately +intermediates +interment +interments +intermezzi +intermezzo +intermezzos +interminable +interminably +intermingle +intermingled +intermingles +intermingling +intermission +intermissions +intermittent +intermittently +intermix +intermixed +intermixes +intermixing +intern +internal +internalization +internalize +internalized +internalizes +internalizing +internally +international +internationalise +internationalised +internationalises +internationalising +internationalism +internationalist +internationalists +internationalize +internationalized +internationalizes +internationalizing +internationally +internationals +interne +internecine +interned +internee +internees +internes +interning +internist +internists +internment +interns +internship +internships +interoffice +interpersonal +interplanetary +interplay +interpolate +interpolated +interpolates +interpolating +interpolation +interpolations +interpose +interposed +interposes +interposing +interposition +interpret +interpretation +interpretations +interpretative +interpreted +interpreter +interpreters +interpreting +interpretive +interprets +interracial +interred +interregna +interregnum +interregnums +interrelate +interrelated +interrelates +interrelating +interrelation +interrelations +interrelationship +interrelationships +interring +interrogate +interrogated +interrogates +interrogating +interrogation +interrogations +interrogative +interrogatively +interrogatives +interrogator +interrogatories +interrogators +interrogatory +interrupt +interrupted +interrupter +interrupters +interrupting +interruption +interruptions +interrupts +inters +interscholastic +intersect +intersected +intersecting +intersection +intersections +intersects +intersession +intersessions +intersperse +interspersed +intersperses +interspersing +interspersion +interstate +interstates +interstellar +interstice +interstices +interstitial +intertwine +intertwined +intertwines +intertwining +interurban +interval +intervals +intervene +intervened +intervenes +intervening +intervention +interventionism +interventionist +interventionists +interventions +interview +interviewed +interviewee +interviewees +interviewer +interviewers +interviewing +interviews +intervocalic +interweave +interweaved +interweaves +interweaving +interwove +interwoven +intestacy +intestate +intestinal +intestine +intestines +intimacies +intimacy +intimate +intimated +intimately +intimates +intimating +intimation +intimations +intimidate +intimidated +intimidates +intimidating +intimidatingly +intimidation +into +intolerable +intolerably +intolerance +intolerant +intolerantly +intonation +intonations +intone +intoned +intoner +intoners +intones +intoning +intoxicant +intoxicants +intoxicate +intoxicated +intoxicates +intoxicating +intoxication +intractability +intractable +intractably +intramural +intramuscular +intransigence +intransigent +intransigently +intransigents +intransitive +intransitively +intransitives +intrastate +intrauterine +intravenous +intravenouses +intravenously +intrench +intrenched +intrenches +intrenching +intrepid +intrepidity +intrepidly +intricacies +intricacy +intricate +intricately +intrigue +intrigued +intriguer +intriguers +intrigues +intriguing +intriguingly +intrinsic +intrinsically +intro +introduce +introduced +introduces +introducing +introduction +introductions +introductory +introit +introits +intros +introspect +introspected +introspecting +introspection +introspective +introspectively +introspects +introversion +introvert +introverted +introverts +intrude +intruded +intruder +intruders +intrudes +intruding +intrusion +intrusions +intrusive +intrusively +intrusiveness +intrust +intrusted +intrusting +intrusts +intuit +intuited +intuiting +intuition +intuitions +intuitive +intuitively +intuitiveness +intuits +inundate +inundated +inundates +inundating +inundation +inundations +inure +inured +inures +inuring +invade +invaded +invader +invaders +invades +invading +invalid +invalidate +invalidated +invalidates +invalidating +invalidation +invalided +invaliding +invalidism +invalidity +invalidly +invalids +invaluable +invaluably +invariability +invariable +invariables +invariably +invasion +invasions +invasive +invective +inveigh +inveighed +inveighing +inveighs +inveigle +inveigled +inveigler +inveiglers +inveigles +inveigling +invent +invented +inventing +invention +inventions +inventive +inventively +inventiveness +inventor +inventoried +inventories +inventors +inventory +inventorying +invents +inverse +inversely +inverses +inversion +inversions +invert +invertebrate +invertebrates +inverted +inverting +inverts +invest +invested +investigate +investigated +investigates +investigating +investigation +investigations +investigative +investigator +investigators +investigatory +investing +investiture +investitures +investment +investments +investor +investors +invests +inveteracy +inveterate +invidious +invidiously +invidiousness +invigorate +invigorated +invigorates +invigorating +invigoratingly +invigoration +invincibility +invincible +invincibly +inviolability +inviolable +inviolably +inviolate +invisibility +invisible +invisibly +invitation +invitational +invitationals +invitations +invite +invited +invitee +invitees +invites +inviting +invitingly +invocation +invocations +invoice +invoiced +invoices +invoicing +invoke +invoked +invokes +invoking +involuntarily +involuntariness +involuntary +involution +involve +involved +involvement +involvements +involves +involving +invulnerability +invulnerable +invulnerably +inward +inwardly +inwards +iodide +iodides +iodine +iodize +iodized +iodizes +iodizing +ionic +ionization +ionize +ionized +ionizer +ionizers +ionizes +ionizing +ionosphere +ionospheres +ionospheric +ions +iota +iotas +ipecac +ipecacs +irascibility +irascible +irascibly +irate +irately +irateness +ireful +irenic +irides +iridescence +iridescent +iridescently +iridium +iris +irises +irked +irking +irks +irksome +irksomely +irksomeness +iron +ironclad +ironclads +ironed +ironic +ironical +ironically +ironies +ironing +irons +ironstone +ironware +ironwood +ironwoods +ironwork +irony +irradiate +irradiated +irradiates +irradiating +irradiation +irrational +irrationality +irrationally +irrationals +irreclaimable +irreconcilability +irreconcilable +irreconcilably +irrecoverable +irrecoverably +irredeemable +irredeemably +irreducible +irreducibly +irrefutable +irrefutably +irregardless +irregular +irregularities +irregularity +irregularly +irregulars +irrelevance +irrelevances +irrelevancies +irrelevancy +irrelevant +irrelevantly +irreligious +irremediable +irremediably +irremovable +irreparable +irreparably +irreplaceable +irrepressible +irrepressibly +irreproachable +irreproachably +irresistible +irresistibly +irresolute +irresolutely +irresoluteness +irresolution +irrespective +irresponsibility +irresponsible +irresponsibly +irretrievable +irretrievably +irreverence +irreverent +irreverently +irreversible +irreversibly +irrevocable +irrevocably +irrigable +irrigate +irrigated +irrigates +irrigating +irrigation +irritability +irritable +irritably +irritant +irritants +irritate +irritated +irritates +irritating +irritatingly +irritation +irritations +irrupt +irrupted +irrupting +irruption +irruptions +irruptive +irrupts +isinglass +island +islander +islanders +islands +isle +isles +islet +islets +isms +isobar +isobaric +isobars +isolate +isolated +isolates +isolating +isolation +isolationism +isolationist +isolationists +isomer +isomeric +isomerism +isomers +isometric +isometrically +isometrics +isosceles +isotherm +isotherms +isotope +isotopes +isotopic +issuance +issue +issued +issuer +issuers +issues +issuing +isthmi +isthmian +isthmus +isthmuses +italic +italicization +italicize +italicized +italicizes +italicizing +italics +itch +itched +itches +itchier +itchiest +itchiness +itching +itchy +item +itemization +itemize +itemized +itemizes +itemizing +items +iterate +iterated +iterates +iterating +iteration +iterations +iterative +itinerant +itinerants +itineraries +itinerary +itself +ivied +ivies +ivories +ivory +jabbed +jabber +jabbered +jabberer +jabberers +jabbering +jabbers +jabbing +jabot +jabots +jabs +jacaranda +jacarandas +jack +jackal +jackals +jackass +jackasses +jackboot +jackboots +jackdaw +jackdaws +jacked +jacket +jacketed +jackets +jackhammer +jackhammers +jacking +jackknife +jackknifed +jackknifes +jackknifing +jackknives +jackpot +jackpots +jackrabbit +jackrabbits +jacks +jackstraw +jackstraws +jacquard +jade +jaded +jadedly +jadedness +jadeite +jades +jading +jagged +jaggeder +jaggedest +jaggedly +jaggedness +jags +jaguar +jaguars +jail +jailbird +jailbirds +jailbreak +jailbreaks +jailed +jailer +jailers +jailing +jailor +jailors +jails +jalapeno +jalapenos +jalopies +jalopy +jalousie +jalousies +jamb +jambalaya +jamboree +jamborees +jambs +jammed +jamming +jams +jangle +jangled +jangler +janglers +jangles +jangling +janitor +janitorial +janitors +japan +japanned +japanning +japans +jape +japed +japes +japing +jardiniere +jardinieres +jarful +jarfuls +jargon +jarred +jarring +jarringly +jars +jasmine +jasmines +jasper +jato +jatos +jaundice +jaundiced +jaundices +jaundicing +jaunt +jaunted +jauntier +jauntiest +jauntily +jauntiness +jaunting +jaunts +jaunty +java +javelin +javelins +jawbone +jawboned +jawbones +jawboning +jawbreaker +jawbreakers +jawed +jawing +jaws +jaybird +jaybirds +jays +jaywalk +jaywalked +jaywalker +jaywalkers +jaywalking +jaywalks +jazz +jazzed +jazzes +jazzier +jazziest +jazzing +jazzy +jealous +jealousies +jealously +jealousy +jean +jeans +jeep +jeeps +jeer +jeered +jeering +jeeringly +jeers +jeez +jehad +jehads +jejuna +jejune +jejunum +jejunums +jell +jelled +jellied +jellies +jelling +jello +jells +jelly +jellybean +jellybeans +jellyfish +jellyfishes +jellying +jellylike +jellyroll +jellyrolls +jennet +jennets +jennies +jenny +jeopardize +jeopardized +jeopardizes +jeopardizing +jeopardy +jeremiad +jeremiads +jerk +jerked +jerkier +jerkiest +jerkily +jerkin +jerkiness +jerking +jerkins +jerks +jerkwater +jerky +jerrybuilt +jersey +jerseys +jessamine +jessamines +jest +jested +jester +jesters +jesting +jestingly +jests +jetliner +jetliners +jetport +jetports +jets +jetsam +jetted +jetties +jetting +jettison +jettisoned +jettisoning +jettisons +jetty +jewed +jewel +jeweled +jeweler +jewelers +jeweling +jewelled +jeweller +jewelleries +jewellers +jewellery +jewelling +jewelries +jewelry +jewels +jewing +jews +jibbed +jibbing +jibe +jibed +jibes +jibing +jibs +jiff +jiffies +jiffs +jiffy +jigged +jigger +jiggered +jiggering +jiggers +jigging +jiggle +jiggled +jiggles +jigglier +jiggliest +jiggling +jiggly +jigs +jigsaw +jigsawed +jigsawing +jigsawn +jigsaws +jihad +jihads +jilt +jilted +jilting +jilts +jimmied +jimmies +jimmy +jimmying +jimsonweed +jingle +jingled +jingles +jinglier +jingliest +jingling +jingly +jingoism +jingoist +jingoistic +jingoists +jinn +jinni +jinnis +jinns +jinricksha +jinrickshas +jinrikisha +jinrikishas +jinriksha +jinrikshas +jinx +jinxed +jinxes +jinxing +jitney +jitneys +jitterbug +jitterbugged +jitterbugger +jitterbuggers +jitterbugging +jitterbugs +jitterier +jitteriest +jitters +jittery +jiujitsu +jive +jived +jives +jiving +jobbed +jobber +jobbers +jobbing +jobholder +jobholders +jobless +joblessness +jobs +jock +jockey +jockeyed +jockeying +jockeys +jocks +jockstrap +jockstraps +jocose +jocosely +jocoseness +jocosity +jocular +jocularity +jocularly +jocund +jocundity +jocundly +jodhpurs +jogged +jogger +joggers +jogging +joggle +joggled +joggles +joggling +jogs +john +johnnies +johnny +johnnycake +johnnycakes +johns +join +joined +joiner +joiners +joinery +joining +joins +joint +jointed +jointing +jointly +joints +joist +joists +joke +joked +joker +jokers +jokes +jokey +jokier +jokiest +joking +jokingly +joky +jollied +jollier +jollies +jolliest +jollification +jollifications +jollily +jolliness +jollity +jolly +jollying +jolt +jolted +jolter +jolters +jolting +jolts +jonquil +jonquils +josh +joshed +josher +joshers +joshes +joshing +jostle +jostled +jostles +jostling +jots +jotted +jotter +jotters +jotting +jottings +joule +joules +jounce +jounced +jounces +jouncier +jounciest +jouncing +jouncy +journal +journalese +journalism +journalist +journalistic +journalists +journals +journey +journeyed +journeyer +journeyers +journeying +journeyman +journeymen +journeys +joust +jousted +jouster +jousters +jousting +jousts +jovial +joviality +jovially +jowl +jowlier +jowliest +jowls +jowly +joyed +joyful +joyfuller +joyfullest +joyfully +joyfulness +joying +joyless +joylessly +joylessness +joyous +joyously +joyousness +joyridden +joyride +joyrider +joyriders +joyrides +joyriding +joyrode +joys +joystick +joysticks +jubilant +jubilantly +jubilation +jubilee +jubilees +judge +judged +judgement +judgements +judges +judgeship +judging +judgment +judgmental +judgmentally +judgments +judicatories +judicatory +judicature +judicial +judicially +judiciaries +judiciary +judicious +judiciously +judiciousness +judo +jugful +jugfuls +jugged +juggernaut +juggernauts +jugging +juggle +juggled +juggler +jugglers +jugglery +juggles +juggling +jugs +jugular +jugulars +juice +juiced +juicer +juicers +juices +juicier +juiciest +juicily +juiciness +juicing +juicy +jujitsu +jujube +jujubes +jujutsu +jukebox +jukeboxes +julep +juleps +julienne +julienned +juliennes +julienning +jumble +jumbled +jumbles +jumbling +jumbo +jumbos +jump +jumped +jumper +jumpers +jumpier +jumpiest +jumpily +jumpiness +jumping +jumps +jumpsuit +jumpsuits +jumpy +junco +juncoes +juncos +junction +junctions +juncture +junctures +jungle +jungles +junior +juniors +juniper +junipers +junk +junked +junker +junkers +junket +junketed +junketeer +junketeers +junketer +junketers +junketing +junkets +junkie +junkier +junkies +junkiest +junking +junks +junky +junkyard +junkyards +junta +juntas +juridic +juridical +juridically +juries +jurisdiction +jurisdictional +jurisprudence +jurist +juristic +jurists +juror +jurors +jury +juryman +jurymen +jurywoman +jurywomen +just +juster +justest +justice +justices +justifiable +justifiably +justification +justifications +justified +justifies +justify +justifying +justly +justness +jute +juts +jutted +jutting +juvenile +juveniles +juxtapose +juxtaposed +juxtaposes +juxtaposing +juxtaposition +juxtapositions +kabob +kabobs +kabuki +kaddish +kaddishes +kaffeeklatch +kaffeeklatches +kaffeeklatsch +kaffeeklatsches +kaftan +kaftans +kaiser +kaisers +kale +kaleidoscope +kaleidoscopes +kaleidoscopic +kaleidoscopically +kamikaze +kamikazes +kangaroo +kangaroos +kaolin +kapok +kappa +kappas +kaput +karakul +karaoke +karaokes +karat +karate +karats +karma +karmic +kart +karts +katydid +katydids +kayak +kayaked +kayaking +kayaks +kayo +kayoed +kayoing +kayos +kazoo +kazoos +kebab +kebabs +kebob +kebobs +keel +keeled +keelhaul +keelhauled +keelhauling +keelhauls +keeling +keels +keen +keened +keener +keenest +keening +keenly +keenness +keens +keep +keeper +keepers +keeping +keeps +keepsake +keepsakes +kegs +kelp +kelvin +kelvins +kenned +kennel +kenneled +kenneling +kennelled +kennelling +kennels +kenning +keno +kens +kent +kepi +kepis +kept +keratin +kerb +kerbs +kerchief +kerchiefs +kerchieves +kernel +kernels +kerosene +kerosine +kestrel +kestrels +ketch +ketches +ketchup +kettle +kettledrum +kettledrums +kettles +keyboard +keyboarded +keyboarder +keyboarders +keyboarding +keyboardist +keyboardists +keyboards +keyed +keyhole +keyholes +keying +keynote +keynoted +keynoter +keynoters +keynotes +keynoting +keypad +keypads +keypunch +keypunched +keypuncher +keypunchers +keypunches +keypunching +keys +keystone +keystones +keystroke +keystrokes +keyword +keywords +khaki +khakis +khan +khans +kibble +kibbled +kibbles +kibbling +kibbutz +kibbutzim +kibitz +kibitzed +kibitzer +kibitzers +kibitzes +kibitzing +kibosh +kick +kickback +kickbacks +kickball +kicked +kicker +kickers +kickier +kickiest +kicking +kickoff +kickoffs +kicks +kickstand +kickstands +kicky +kidded +kidder +kidders +kiddie +kiddies +kidding +kiddish +kiddo +kiddoes +kiddos +kiddy +kidnap +kidnaped +kidnaper +kidnapers +kidnaping +kidnapped +kidnapper +kidnappers +kidnapping +kidnappings +kidnaps +kidney +kidneys +kids +kidskin +kielbasa +kielbasas +kielbasi +kielbasy +kill +killdeer +killdeers +killed +killer +killers +killing +killings +killjoy +killjoys +kills +kiln +kilned +kilning +kilns +kilo +kilobyte +kilobytes +kilocycle +kilocycles +kilogram +kilograms +kilohertz +kilohertzes +kiloliter +kiloliters +kilometer +kilometers +kilometre +kilometres +kilos +kiloton +kilotons +kilowatt +kilowatts +kilt +kilter +kilts +kimono +kimonos +kind +kinda +kinder +kindergarten +kindergartener +kindergarteners +kindergartens +kindergartner +kindergartners +kindest +kindhearted +kindheartedly +kindheartedness +kindle +kindled +kindles +kindlier +kindliest +kindliness +kindling +kindly +kindness +kindnesses +kindred +kinds +kine +kinematic +kinematics +kinetic +kinetically +kinetics +kinfolk +kinfolks +king +kingdom +kingdoms +kingfisher +kingfishers +kinglier +kingliest +kingly +kingpin +kingpins +kings +kingship +kink +kinked +kinkier +kinkiest +kinkily +kinkiness +kinking +kinks +kinky +kinsfolk +kinsfolks +kinship +kinsman +kinsmen +kinswoman +kinswomen +kiosk +kiosks +kipped +kipper +kippered +kippering +kippers +kipping +kips +kirk +kirks +kirsch +kismet +kiss +kissable +kissed +kisser +kissers +kisses +kissing +kissoff +kissoffs +kitbag +kitbags +kitchen +kitchenette +kitchenettes +kitchens +kitchenware +kite +kited +kites +kith +kiting +kits +kitsch +kitschier +kitschiest +kitschy +kitten +kittenish +kittens +kitties +kitty +kiwi +kiwifruit +kiwifruits +kiwis +kleptomania +kleptomaniac +kleptomaniacs +klutz +klutzes +klutzier +klutziest +klutziness +klutzy +knack +knacks +knackwurst +knackwursts +knapsack +knapsacks +knave +knavery +knaves +knavish +knavishly +knead +kneaded +kneader +kneaders +kneading +kneads +knee +kneecap +kneecapped +kneecapping +kneecaps +kneed +kneeing +kneel +kneeled +kneeling +kneels +knees +knell +knelled +knelling +knells +knelt +knew +knickerbockers +knickers +knickknack +knickknacks +knife +knifed +knifes +knifing +knight +knighted +knighthood +knighthoods +knighting +knightlier +knightliest +knightliness +knightly +knights +knish +knishes +knit +knits +knitted +knitter +knitters +knitting +knitwear +knives +knob +knobbier +knobbiest +knobby +knobs +knock +knockdown +knockdowns +knocked +knocker +knockers +knocking +knockoff +knockoffs +knockout +knockouts +knocks +knockwurst +knockwursts +knoll +knolls +knot +knothole +knotholes +knots +knotted +knottier +knottiest +knotting +knotty +know +knowable +knowing +knowingly +knowledge +knowledgeable +knowledgeably +known +knows +knuckle +knuckled +knucklehead +knuckleheads +knuckles +knuckling +knurl +knurled +knurling +knurls +koala +koalas +kohlrabi +kohlrabies +kola +kolas +kook +kookaburra +kookaburras +kookie +kookier +kookiest +kookiness +kooks +kooky +kopeck +kopecks +kopek +kopeks +kosher +koshered +koshering +koshers +kowtow +kowtowed +kowtowing +kowtows +kraal +kraals +kraut +krauts +krill +krona +krone +kroner +kronor +kronur +krypton +kuchen +kuchens +kudos +kudzu +kudzus +kumquat +kumquats +kvetch +kvetched +kvetcher +kvetchers +kvetches +kvetching +label +labeled +labeling +labelled +labelling +labels +labia +labial +labials +labile +labium +labor +laboratories +laboratory +labored +laborer +laborers +laboring +laborious +laboriously +laboriousness +labors +laborsaving +labour +laboured +labouring +labours +labs +laburnum +laburnums +labyrinth +labyrinthine +labyrinths +lace +laced +lacerate +lacerated +lacerates +lacerating +laceration +lacerations +laces +lacewing +lacewings +lacework +lachrymal +lachrymose +lacier +laciest +lacing +lack +lackadaisical +lackadaisically +lacked +lackey +lackeys +lacking +lackluster +lacklustre +lacks +laconic +laconically +lacquer +lacquered +lacquering +lacquers +lacrimal +lacrosse +lactate +lactated +lactates +lactating +lactation +lacteal +lactic +lactose +lacuna +lacunae +lacunas +lacy +ladder +laddered +laddering +ladders +laddie +laddies +lade +laded +laden +lades +ladies +lading +ladings +ladle +ladled +ladles +ladling +lads +lady +ladybird +ladybirds +ladybug +ladybugs +ladyfinger +ladyfingers +ladylike +ladylove +ladyloves +ladyship +laetrile +lager +lagers +laggard +laggardly +laggards +lagged +lagging +lagnappe +lagnappes +lagniappe +lagniappes +lagoon +lagoons +lags +laid +lain +lair +laird +lairds +lairs +laity +lake +lakefront +lakes +lallygag +lallygagged +lallygagging +lallygags +lama +lamas +lamaseries +lamasery +lamb +lambada +lambadas +lambast +lambaste +lambasted +lambastes +lambasting +lambasts +lambda +lambdas +lambed +lambency +lambent +lambently +lambing +lambkin +lambkins +lambs +lambskin +lambskins +lame +lamebrain +lamebrained +lamebrains +lamed +lamely +lameness +lament +lamentable +lamentably +lamentation +lamentations +lamented +lamenting +laments +lamer +lames +lamest +lamina +laminae +laminar +laminas +laminate +laminated +laminates +laminating +lamination +laming +lammed +lamming +lamp +lampblack +lamplight +lamplighter +lamplighters +lampoon +lampooned +lampooning +lampoons +lamppost +lampposts +lamprey +lampreys +lamps +lampshade +lampshades +lams +lanai +lanais +lance +lanced +lancer +lancers +lances +lancet +lancets +lancing +land +landau +landaus +landed +landfall +landfalls +landfill +landfills +landholder +landholders +landholding +landing +landings +landladies +landlady +landless +landlocked +landlord +landlords +landlubber +landlubbers +landmark +landmarks +landmass +landmasses +landowner +landowners +landowning +lands +landscape +landscaped +landscaper +landscapers +landscapes +landscaping +landslid +landslidden +landslide +landslides +landsliding +landsman +landsmen +landward +landwards +lane +lanes +language +languages +languid +languidly +languidness +languish +languished +languishes +languishing +languor +languorous +languorously +lank +lanker +lankest +lankier +lankiest +lankiness +lankly +lankness +lanky +lanolin +lantern +lanterns +lanthanum +lanyard +lanyards +lapboard +lapboards +lapdog +lapdogs +lapel +lapels +lapidaries +lapidary +lapin +lapins +lapped +lappet +lappets +lapping +laps +lapse +lapsed +lapses +lapsing +laptop +laptops +lapwing +lapwings +larboard +larboards +larcenies +larcenist +larcenists +larcenous +larceny +larch +larches +lard +larded +larder +larders +lardier +lardiest +larding +lards +lardy +large +largehearted +largely +largeness +larger +larges +largess +largesse +largest +largish +largo +largos +lariat +lariats +lark +larked +larking +larks +larkspur +larkspurs +larva +larvae +larval +larvas +laryngeal +larynges +laryngitis +larynx +larynxes +lasagna +lasagnas +lasagne +lasagnes +lascivious +lasciviously +lasciviousness +laser +lasers +lash +lashed +lashes +lashing +lashings +lass +lasses +lassie +lassies +lassitude +lasso +lassoed +lassoes +lassoing +lassos +last +lasted +lasting +lastingly +lastly +lasts +latch +latched +latches +latching +latchkey +latchkeys +late +latecomer +latecomers +lately +latency +lateness +latent +later +lateral +lateraled +lateraling +lateralled +lateralling +laterally +laterals +latest +latex +lath +lathe +lathed +lather +lathered +lathering +lathers +lathery +lathes +lathing +laths +latish +latitude +latitudes +latitudinal +latitudinarian +latitudinarians +latrine +latrines +latte +latter +latterly +lattes +lattice +latticed +lattices +latticework +latticeworks +laud +laudable +laudably +laudanum +laudatory +lauded +lauding +lauds +laugh +laughable +laughably +laughed +laughing +laughingly +laughingstock +laughingstocks +laughs +laughter +launch +launched +launcher +launchers +launches +launching +launchpad +launchpads +launder +laundered +launderer +launderers +launderette +launderettes +laundering +launders +laundress +laundresses +laundrette +laundrettes +laundries +laundromat +laundromats +laundry +laundryman +laundrymen +laundrywoman +laundrywomen +laureate +laureates +laureateship +laurel +laurels +lava +lavage +lavalier +lavaliere +lavalieres +lavaliers +lavatories +lavatory +lave +laved +lavender +lavenders +laves +laving +lavish +lavished +lavisher +lavishes +lavishest +lavishing +lavishly +lavishness +lawbreaker +lawbreakers +lawbreaking +lawful +lawfully +lawfulness +lawgiver +lawgivers +lawless +lawlessly +lawlessness +lawmaker +lawmakers +lawmaking +lawman +lawmen +lawn +lawnmower +lawnmowers +lawns +lawrencium +laws +lawsuit +lawsuits +lawyer +lawyers +laxative +laxatives +laxer +laxest +laxity +laxly +laxness +layaway +layer +layered +layering +layers +layette +layettes +laying +layman +laymen +layoff +layoffs +layout +layouts +layover +layovers +laypeople +layperson +laypersons +lays +layup +layups +laywoman +laywomen +laze +lazed +lazes +lazied +lazier +lazies +laziest +lazily +laziness +lazing +lazy +lazybones +lazying +leach +leached +leaches +leaching +lead +leaded +leaden +leader +leaderless +leaders +leadership +leading +leads +leaf +leafage +leafed +leafier +leafiest +leafing +leafless +leaflet +leafleted +leafleting +leaflets +leafletted +leafletting +leafs +leafstalk +leafstalks +leafy +league +leagued +leagues +leaguing +leak +leakage +leakages +leaked +leakier +leakiest +leakiness +leaking +leaks +leaky +lean +leaned +leaner +leanest +leaning +leanings +leanness +leans +leant +leap +leaped +leaper +leapers +leapfrog +leapfrogged +leapfrogging +leapfrogs +leaping +leaps +leapt +learn +learned +learnedly +learner +learners +learning +learns +learnt +leas +lease +leaseback +leasebacks +leased +leasehold +leaseholder +leaseholders +leaseholds +leaser +leasers +leases +leash +leashed +leashes +leashing +leasing +least +leastways +leastwise +leather +leatherette +leatherneck +leathernecks +leathery +leave +leaved +leaven +leavened +leavening +leavens +leaver +leavers +leaves +leaving +leavings +lech +lecher +lecherous +lecherously +lecherousness +lechers +lechery +leches +lecithin +lectern +lecterns +lecture +lectured +lecturer +lecturers +lectures +lectureship +lectureships +lecturing +ledge +ledger +ledgers +ledges +leech +leeched +leeches +leeching +leek +leeks +leer +leered +leerier +leeriest +leeriness +leering +leers +leery +lees +leeward +leewards +leeway +left +lefter +leftest +leftie +lefties +leftism +leftist +leftists +leftmost +leftover +leftovers +lefts +leftward +leftwards +lefty +legacies +legacy +legal +legalese +legalism +legalisms +legalistic +legality +legalization +legalize +legalized +legalizes +legalizing +legally +legals +legate +legatee +legatees +legates +legation +legations +legato +legatos +legend +legendarily +legendary +legends +legerdemain +legged +leggier +leggiest +leggin +legginess +legging +leggings +leggins +leggy +leghorn +leghorns +legibility +legible +legibly +legion +legionaries +legionary +legionnaire +legionnaires +legions +legislate +legislated +legislates +legislating +legislation +legislative +legislatively +legislator +legislators +legislature +legislatures +legit +legitimacy +legitimate +legitimated +legitimately +legitimates +legitimating +legitimatize +legitimatized +legitimatizes +legitimatizing +legitimization +legitimize +legitimized +legitimizes +legitimizing +legless +legman +legmen +legroom +legrooms +legs +legume +legumes +leguminous +legwork +leis +leisure +leisured +leisureliness +leisurely +leisurewear +leitmotif +leitmotifs +leitmotiv +leitmotivs +lemming +lemmings +lemon +lemonade +lemons +lemony +lemur +lemurs +lend +lender +lenders +lending +lends +length +lengthen +lengthened +lengthening +lengthens +lengthier +lengthiest +lengthily +lengthiness +lengths +lengthways +lengthwise +lengthy +lenience +leniency +lenient +leniently +lenitive +lens +lenses +lent +lenten +lentil +lentils +lento +leonine +leopard +leopardess +leopardesses +leopards +leotard +leotards +leper +lepers +leprechaun +leprechauns +leprosy +leprous +lept +lepta +lepton +leptons +lesbian +lesbianism +lesbians +lesion +lesions +less +lessee +lessees +lessen +lessened +lessening +lessens +lesser +lesson +lessons +lessor +lessors +lest +letdown +letdowns +lethal +lethally +lethargic +lethargically +lethargy +lets +letter +lettered +letterer +letterers +letterhead +letterheads +lettering +letterpress +letters +letting +lettuce +lettuces +letup +letups +leukaemia +leukemia +leukemic +leukemics +leukocyte +leukocytes +levee +levees +level +leveled +leveler +levelers +levelheaded +levelheadedness +leveling +levelled +leveller +levellers +levelling +levelly +levelness +levels +lever +leverage +leveraged +leverages +leveraging +levered +levering +levers +leviathan +leviathans +levied +levier +leviers +levies +levitate +levitated +levitates +levitating +levitation +levity +levy +levying +lewd +lewder +lewdest +lewdly +lewdness +lexica +lexical +lexicographer +lexicographers +lexicographic +lexicographical +lexicography +lexicon +lexicons +liabilities +liability +liable +liaise +liaised +liaises +liaising +liaison +liaisons +liar +liars +libation +libations +libber +libbers +libel +libeled +libeler +libelers +libeling +libelled +libeller +libellers +libelling +libellous +libelous +libels +liberal +liberalism +liberality +liberalization +liberalizations +liberalize +liberalized +liberalizes +liberalizing +liberally +liberalness +liberals +liberate +liberated +liberates +liberating +liberation +liberator +liberators +libertarian +libertarians +liberties +libertine +libertines +liberty +libidinal +libidinous +libido +libidos +librarian +librarians +libraries +library +libretti +librettist +librettists +libretto +librettos +lice +licence +licenced +licences +licencing +license +licensed +licensee +licensees +licenses +licensing +licentiate +licentiates +licentious +licentiously +licentiousness +lichee +lichees +lichen +lichens +licit +licitly +lick +licked +licking +lickings +licks +licorice +licorices +lidded +lidless +lido +lidos +lids +lied +lieder +lief +liefer +liefest +liege +lieges +lien +liens +lies +lieu +lieutenancy +lieutenant +lieutenants +life +lifeblood +lifeboat +lifeboats +lifebuoy +lifebuoys +lifeguard +lifeguards +lifeless +lifelessly +lifelessness +lifelike +lifeline +lifelines +lifelong +lifer +lifers +lifesaver +lifesavers +lifesaving +lifestyle +lifestyles +lifetime +lifetimes +lifework +lifeworks +lift +lifted +lifter +lifters +lifting +liftoff +liftoffs +lifts +ligament +ligaments +ligate +ligated +ligates +ligating +ligation +ligature +ligatured +ligatures +ligaturing +light +lighted +lighten +lightened +lightener +lighteners +lightening +lightens +lighter +lighters +lightest +lightface +lightfaced +lightheaded +lighthearted +lightheartedly +lightheartedness +lighthouse +lighthouses +lighting +lightly +lightness +lightning +lightninged +lightnings +lightproof +lights +lightship +lightships +lightweight +lightweights +ligneous +lignite +likability +likable +likableness +like +likeable +liked +likelier +likeliest +likelihood +likelihoods +likeliness +likely +liken +likened +likeness +likenesses +likening +likens +liker +likes +likest +likewise +liking +lilac +lilacs +lilies +lilliputian +lilt +lilted +lilting +lilts +lily +limb +limber +limbered +limbering +limberness +limbers +limbless +limbo +limbos +limbs +lime +limeade +limeades +limed +limelight +limerick +limericks +limes +limestone +limier +limiest +liming +limit +limitation +limitations +limited +limiter +limiters +limiting +limitless +limitlessness +limits +limn +limned +limning +limns +limo +limos +limousine +limousines +limp +limped +limper +limpest +limpet +limpets +limpid +limpidity +limpidly +limpidness +limping +limply +limpness +limps +limy +linage +linchpin +linchpins +linden +lindens +line +lineage +lineages +lineal +lineally +lineament +lineaments +linear +linearity +linearly +linebacker +linebackers +lined +lineman +linemen +linen +linens +liner +liners +lines +linesman +linesmen +lineup +lineups +ling +linger +lingered +lingerer +lingerers +lingerie +lingering +lingeringly +lingers +lingo +lingoes +lingos +lings +lingual +linguine +linguini +linguist +linguistic +linguistically +linguistics +linguists +liniment +liniments +lining +linings +link +linkage +linkages +linked +linking +links +linkup +linkups +linnet +linnets +linoleum +linseed +lint +lintel +lintels +lintier +lintiest +linty +lion +lioness +lionesses +lionhearted +lionization +lionize +lionized +lionizes +lionizing +lions +lipid +lipids +liposuction +lipped +lippier +lippiest +lippy +lipread +lipreader +lipreaders +lipreading +lipreads +lips +lipstick +lipsticks +liquefaction +liquefied +liquefies +liquefy +liquefying +liqueur +liqueurs +liquid +liquidate +liquidated +liquidates +liquidating +liquidation +liquidations +liquidator +liquidators +liquidity +liquidize +liquidized +liquidizer +liquidizers +liquidizes +liquidizing +liquids +liquified +liquifies +liquify +liquifying +liquor +liquored +liquorice +liquorices +liquoring +liquors +lira +liras +lire +lisle +lisp +lisped +lisper +lispers +lisping +lisps +lissom +lissome +list +listed +listen +listened +listener +listeners +listening +listens +listing +listings +listless +listlessly +listlessness +lists +litanies +litany +litchi +litchis +lite +liter +literacy +literal +literally +literalness +literals +literariness +literary +literate +literately +literates +literati +literature +liters +lithe +lithely +litheness +lither +lithesome +lithest +lithium +lithograph +lithographed +lithographer +lithographers +lithographic +lithographically +lithographing +lithographs +lithography +lithosphere +lithospheres +litigant +litigants +litigate +litigated +litigates +litigating +litigation +litigator +litigators +litigious +litigiousness +litmus +litotes +litre +litres +litter +litterateur +litterateurs +litterbug +litterbugs +littered +litterer +litterers +littering +litters +little +littleness +littler +littlest +littoral +littorals +liturgical +liturgically +liturgies +liturgist +liturgists +liturgy +livability +livable +live +liveable +lived +livelier +liveliest +livelihood +livelihoods +liveliness +livelong +lively +liven +livened +livening +livens +liver +liveried +liveries +liverish +livers +liverwort +liverworts +liverwurst +livery +liveryman +liverymen +lives +livest +livestock +livid +lividly +living +livings +lizard +lizards +llama +llamas +llano +llanos +load +loaded +loader +loaders +loading +loads +loadstar +loadstars +loadstone +loadstones +loaf +loafed +loafer +loafers +loafing +loafs +loam +loamier +loamiest +loamy +loan +loaned +loaner +loaners +loaning +loans +loansharking +loanword +loanwords +loath +loathe +loathed +loather +loathers +loathes +loathing +loathings +loathsome +loathsomely +loathsomeness +loaves +lobar +lobbed +lobber +lobbers +lobbied +lobbies +lobbing +lobby +lobbying +lobbyist +lobbyists +lobe +lobed +lobes +lobotomies +lobotomize +lobotomized +lobotomizes +lobotomizing +lobotomy +lobs +lobster +lobsters +local +locale +locales +localities +locality +localization +localize +localized +localizes +localizing +locally +locals +locate +located +locates +locating +location +locations +locator +locators +loch +lochs +loci +lock +locked +locker +lockers +locket +lockets +locking +lockjaw +lockout +lockouts +locks +locksmith +locksmiths +lockstep +lockup +lockups +loco +locomotion +locomotive +locomotives +locoweed +locoweeds +locus +locust +locusts +locution +locutions +lode +lodes +lodestar +lodestars +lodestone +lodestones +lodge +lodged +lodger +lodgers +lodges +lodging +lodgings +loft +lofted +loftier +loftiest +loftily +loftiness +lofting +lofts +lofty +loganberries +loganberry +logarithm +logarithmic +logarithms +logbook +logbooks +loge +loges +logged +logger +loggerhead +loggerheads +loggers +loggia +loggias +logging +logic +logical +logicality +logically +logician +logicians +logier +logiest +logistic +logistical +logistically +logistics +logjam +logjams +logo +logos +logotype +logotypes +logrolling +logs +logy +loin +loincloth +loincloths +loins +loiter +loitered +loiterer +loiterers +loitering +loiters +loll +lolled +lolling +lollipop +lollipops +lolls +lollygag +lollygagged +lollygagging +lollygags +lollypop +lollypops +lone +lonelier +loneliest +loneliness +lonely +loner +loners +lonesome +lonesomely +lonesomeness +long +longboat +longboats +longbow +longbows +longed +longer +longest +longevity +longhair +longhairs +longhand +longhorn +longhorns +longing +longingly +longings +longish +longitude +longitudes +longitudinal +longitudinally +longs +longshoreman +longshoremen +longsighted +longstanding +longtime +longueur +longueurs +longways +loofah +loofahs +look +lookalike +lookalikes +looked +looker +lookers +looking +lookout +lookouts +looks +loom +loomed +looming +looms +loon +looney +looneys +loonier +loonies +looniest +loons +loony +loop +looped +loophole +loopholes +loopier +loopiest +looping +loops +loopy +loose +loosed +loosely +loosen +loosened +looseness +loosening +loosens +looser +looses +loosest +loosing +loot +looted +looter +looters +looting +loots +lope +loped +lopes +loping +lopped +lopping +lops +lopsided +lopsidedly +lopsidedness +loquacious +loquaciousness +loquacity +lord +lorded +lording +lordlier +lordliest +lordliness +lordly +lords +lordship +lordships +lore +lorgnette +lorgnettes +loris +lorises +lorn +lorries +lorry +lose +loser +losers +loses +losing +losings +loss +losses +lost +loth +lotion +lotions +lots +lotteries +lottery +lotto +lotus +lotuses +loud +louder +loudest +loudhailer +loudhailers +loudly +loudmouth +loudmouthed +loudmouths +loudness +loudspeaker +loudspeakers +lounge +lounged +lounger +loungers +lounges +lounging +lour +loured +louring +lours +louse +louses +lousier +lousiest +lousily +lousiness +lousy +lout +loutish +loutishly +louts +louver +louvered +louvers +louvre +louvred +louvres +lovable +lovableness +lovably +love +loveable +lovebird +lovebirds +lovechild +lovechildren +loved +loveless +lovelier +lovelies +loveliest +loveliness +lovelorn +lovely +lovemaking +lover +lovers +loves +loveseat +loveseats +lovesick +loving +lovingly +lowborn +lowboy +lowboys +lowbrow +lowbrows +lowdown +lowed +lower +lowercase +lowered +lowering +lowermost +lowers +lowest +lowing +lowland +lowlander +lowlanders +lowlands +lowlier +lowliest +lowlife +lowlifes +lowliness +lowlives +lowly +lowness +lows +loyal +loyaler +loyalest +loyalism +loyalist +loyalists +loyaller +loyallest +loyally +loyalties +loyalty +lozenge +lozenges +luau +luaus +lubber +lubberly +lubbers +lube +lubed +lubes +lubing +lubricant +lubricants +lubricate +lubricated +lubricates +lubricating +lubrication +lubricator +lubricators +lubricious +lubricity +lucid +lucider +lucidest +lucidity +lucidly +lucidness +luck +lucked +luckier +luckiest +luckily +luckiness +lucking +luckless +lucks +lucky +lucrative +lucratively +lucrativeness +lucre +lucubrate +lucubrated +lucubrates +lucubrating +lucubration +ludicrous +ludicrously +ludicrousness +luff +luffed +luffing +luffs +luggage +lugged +lugger +luggers +lugging +lugs +lugsail +lugsails +lugubrious +lugubriously +lugubriousness +lukewarm +lukewarmly +lukewarmness +lull +lullabies +lullaby +lulled +lulling +lulls +lumbago +lumbar +lumber +lumbered +lumberer +lumberers +lumbering +lumberjack +lumberjacks +lumberman +lumbermen +lumbers +lumberyard +lumberyards +luminaries +luminary +luminescence +luminescent +luminosity +luminous +luminously +lummox +lummoxes +lump +lumped +lumpier +lumpiest +lumpiness +lumping +lumpish +lumps +lumpy +lunacies +lunacy +lunar +lunatic +lunatics +lunch +lunched +luncheon +luncheonette +luncheonettes +luncheons +lunches +lunching +lunchroom +lunchrooms +lunchtime +lunchtimes +lung +lunge +lunged +lunges +lungfish +lungfishes +lunging +lungs +lunkhead +lunkheads +lupin +lupine +lupines +lupins +lupus +lurch +lurched +lurches +lurching +lure +lured +lures +lurid +luridly +luridness +luring +lurk +lurked +lurking +lurks +luscious +lusciously +lusciousness +lush +lusher +lushes +lushest +lushly +lushness +lust +lusted +luster +lusterless +lustful +lustfully +lustier +lustiest +lustily +lustiness +lusting +lustre +lustrous +lustrously +lusts +lusty +lutanist +lutanists +lute +lutenist +lutenists +lutes +lutetium +luxuriance +luxuriant +luxuriantly +luxuriate +luxuriated +luxuriates +luxuriating +luxuriation +luxuries +luxurious +luxuriously +luxuriousness +luxury +lyceum +lyceums +lychee +lychees +lying +lymph +lymphatic +lymphatics +lymphocyte +lymphocytes +lymphoid +lymphoma +lymphomas +lymphomata +lynch +lynched +lyncher +lynchers +lynches +lynching +lynchings +lynchpin +lynchpins +lynx +lynxes +lyre +lyrebird +lyrebirds +lyres +lyric +lyrical +lyrically +lyricism +lyricist +lyricists +lyrics +macabre +macadam +macadamia +macadamias +macadamize +macadamized +macadamizes +macadamizing +macaque +macaques +macaroni +macaronies +macaronis +macaroon +macaroons +macaw +macaws +mace +maced +macerate +macerated +macerates +macerating +maceration +maces +mach +machete +machetes +machinate +machinated +machinates +machinating +machination +machinations +machine +machined +machinery +machines +machining +machinist +machinists +machismo +macho +macing +macintosh +macintoshes +mackerel +mackerels +mackinaw +mackinaws +mackintosh +mackintoshes +macrame +macro +macrobiotic +macrobiotics +macrocosm +macrocosms +macroeconomics +macron +macrons +macros +macroscopic +macs +madam +madame +madams +madcap +madcaps +madden +maddened +maddening +maddeningly +maddens +madder +madders +maddest +madding +made +mademoiselle +mademoiselles +madhouse +madhouses +madly +madman +madmen +madness +madras +madrases +madrigal +madrigals +mads +madwoman +madwomen +maelstrom +maelstroms +maestri +maestro +maestros +mafia +mafias +mafiosi +mafioso +mafiosos +magazine +magazines +magenta +maggot +maggots +maggoty +magi +magic +magical +magically +magician +magicians +magisterial +magisterially +magistracy +magistrate +magistrates +magma +magnanimity +magnanimous +magnanimously +magnate +magnates +magnesia +magnesium +magnet +magnetic +magnetically +magnetise +magnetised +magnetises +magnetising +magnetism +magnetite +magnetizable +magnetization +magnetize +magnetized +magnetizes +magnetizing +magneto +magnetometer +magnetometers +magnetos +magnets +magnification +magnifications +magnificence +magnificent +magnificently +magnified +magnifier +magnifiers +magnifies +magnify +magnifying +magniloquence +magniloquent +magnitude +magnitudes +magnolia +magnolias +magnum +magnums +magpie +magpies +mags +magus +maharaja +maharajah +maharajahs +maharajas +maharanee +maharanees +maharani +maharanis +maharishi +maharishis +mahatma +mahatmas +mahjong +mahoganies +mahogany +mahout +mahouts +maid +maiden +maidenhair +maidenhead +maidenheads +maidenhood +maidenly +maidens +maids +maidservant +maidservants +mail +mailbag +mailbags +mailbox +mailboxes +mailed +mailer +mailers +mailing +mailings +maillot +maillots +mailman +mailmen +mails +maim +maimed +maiming +maims +main +mainframe +mainframes +mainland +mainlands +mainline +mainlined +mainlines +mainlining +mainly +mainmast +mainmasts +mains +mainsail +mainsails +mainspring +mainsprings +mainstay +mainstays +mainstream +mainstreamed +mainstreaming +mainstreams +maintain +maintainable +maintained +maintaining +maintains +maintenance +maintop +maintops +maiolica +maisonette +maisonettes +maize +majestic +majestically +majesties +majesty +majolica +major +majordomo +majordomos +majored +majorette +majorettes +majoring +majorities +majority +majors +make +makeover +makeovers +maker +makers +makes +makeshift +makeshifts +makeup +makeups +making +makings +malachite +maladies +maladjusted +maladjustment +maladroit +maladroitly +maladroitness +malady +malaise +malamute +malamutes +malapropism +malapropisms +malaria +malarial +malarkey +malathion +malcontent +malcontents +male +malediction +maledictions +malefaction +malefactor +malefactors +malefic +maleficence +maleficent +maleness +males +malevolence +malevolent +malevolently +malfeasance +malformation +malformations +malformed +malfunction +malfunctioned +malfunctioning +malfunctions +malice +malicious +maliciously +maliciousness +malign +malignancies +malignancy +malignant +malignantly +maligned +maligning +malignity +maligns +malinger +malingered +malingerer +malingerers +malingering +malingers +mall +mallard +mallards +malleability +malleable +mallet +mallets +mallow +mallows +malls +malnourished +malnutrition +malocclusion +malodorous +malpractice +malpractices +malt +malted +malteds +maltier +maltiest +malting +maltose +maltreat +maltreated +maltreating +maltreatment +maltreats +malts +malty +mama +mamas +mamba +mambas +mambo +mamboed +mamboing +mambos +mamma +mammal +mammalian +mammalians +mammals +mammary +mammas +mammies +mammogram +mammograms +mammography +mammon +mammoth +mammoths +mammy +manacle +manacled +manacles +manacling +manage +manageability +manageable +managed +management +manager +managerial +managers +manages +managing +manana +mananas +manatee +manatees +mandala +mandalas +mandamus +mandamuses +mandarin +mandarins +mandate +mandated +mandates +mandating +mandatory +mandible +mandibles +mandibular +mandolin +mandolins +mandrake +mandrakes +mandrel +mandrels +mandril +mandrill +mandrills +mandrils +mane +maned +manege +manes +maneuver +maneuverability +maneuverable +maneuvered +maneuvering +maneuvers +manful +manfully +manganese +mange +manger +mangers +mangier +mangiest +manginess +mangle +mangled +mangles +mangling +mango +mangoes +mangos +mangrove +mangroves +mangy +manhandle +manhandled +manhandles +manhandling +manhole +manholes +manhood +manhunt +manhunts +mania +maniac +maniacal +maniacally +maniacs +manias +manic +manically +manics +manicure +manicured +manicures +manicuring +manicurist +manicurists +manifest +manifestation +manifestations +manifested +manifesting +manifestly +manifesto +manifestoes +manifestos +manifests +manifold +manifolded +manifolding +manifolds +manikin +manikins +manila +manilla +manioc +maniocs +manipulable +manipulate +manipulated +manipulates +manipulating +manipulation +manipulations +manipulative +manipulatively +manipulator +manipulators +mankind +manlier +manliest +manlike +manliness +manly +manna +manned +mannequin +mannequins +manner +mannered +mannerism +mannerisms +mannerly +manners +mannikin +mannikins +manning +mannish +mannishly +mannishness +manoeuvre +manoeuvred +manoeuvres +manoeuvring +manometer +manometers +manor +manorial +manors +manpower +manque +mans +mansard +mansards +manse +manservant +manses +mansion +mansions +manslaughter +manta +mantas +mantel +mantelpiece +mantelpieces +mantels +mantes +mantilla +mantillas +mantis +mantises +mantissa +mantissas +mantle +mantled +mantles +mantling +mantra +mantras +manual +manually +manuals +manufacture +manufactured +manufacturer +manufacturers +manufactures +manufacturing +manumission +manumissions +manumit +manumits +manumitted +manumitting +manure +manured +manures +manuring +manuscript +manuscripts +many +maple +maples +mapmaker +mapmakers +mapped +mapper +mappers +mapping +maps +marabou +marabous +marabout +marabouts +maraca +maracas +maraschino +maraschinos +marathon +marathoner +marathoners +marathons +maraud +marauded +marauder +marauders +marauding +marauds +marble +marbled +marbleize +marbleized +marbleizes +marbleizing +marbles +marbling +march +marched +marcher +marchers +marches +marching +marchioness +marchionesses +mare +mares +margarine +margarita +margaritas +margin +marginal +marginalia +marginalization +marginalize +marginalized +marginalizes +marginalizing +marginally +margins +maria +mariachi +mariachis +marigold +marigolds +marihuana +marijuana +marimba +marimbas +marina +marinade +marinaded +marinades +marinading +marinara +marinas +marinate +marinated +marinates +marinating +marination +marine +mariner +mariners +marines +marionette +marionettes +marital +maritally +maritime +marjoram +mark +markdown +markdowns +marked +markedly +marker +markers +market +marketability +marketable +marketed +marketeer +marketeers +marketer +marketers +marketing +marketplace +marketplaces +markets +marking +markings +markka +markkaa +markkas +marks +marksman +marksmanship +marksmen +markup +markups +marl +marlin +marlinespike +marlinespikes +marlins +marlinspike +marlinspikes +marmalade +marmoreal +marmoset +marmosets +marmot +marmots +maroon +marooned +marooning +maroons +marque +marquee +marquees +marques +marquess +marquesses +marquetry +marquis +marquise +marquises +marquisette +marred +marriage +marriageability +marriageable +marriages +married +marrieds +marries +marring +marrow +marrows +marry +marrying +mars +marsh +marshal +marshaled +marshaling +marshalled +marshalling +marshals +marshes +marshier +marshiest +marshland +marshmallow +marshmallows +marshy +marsupial +marsupials +mart +marten +martens +martial +martially +martin +martinet +martinets +martingale +martingales +martini +martinis +martins +marts +martyr +martyrdom +martyred +martyring +martyrs +marvel +marveled +marveling +marvelled +marvelling +marvellous +marvellously +marvelous +marvelously +marvels +marzipan +mascara +mascaraed +mascaraing +mascaras +mascot +mascots +masculine +masculines +masculinity +maser +masers +mash +mashed +masher +mashers +mashes +mashing +mask +masked +masker +maskers +masking +masks +masochism +masochist +masochistic +masochistically +masochists +mason +masonry +masons +masque +masquerade +masqueraded +masquerader +masqueraders +masquerades +masquerading +masques +mass +massacre +massacred +massacres +massacring +massage +massaged +massages +massaging +massed +masses +masseur +masseurs +masseuse +masseuses +massif +massifs +massing +massive +massively +massiveness +mast +mastectomies +mastectomy +masted +master +mastered +masterful +masterfully +mastering +masterly +mastermind +masterminded +masterminding +masterminds +masterpiece +masterpieces +masters +masterstroke +masterstrokes +masterwork +masterworks +mastery +masthead +mastheads +mastic +masticate +masticated +masticates +masticating +mastication +mastiff +mastiffs +mastodon +mastodons +mastoid +mastoids +masts +masturbate +masturbated +masturbates +masturbating +masturbation +masturbatory +matador +matadors +match +matchbook +matchbooks +matchbox +matchboxes +matched +matches +matching +matchless +matchlock +matchlocks +matchmaker +matchmakers +matchmaking +matchstick +matchsticks +matchwood +mate +mated +material +materialise +materialised +materialises +materialising +materialism +materialist +materialistic +materialistically +materialists +materialization +materialize +materialized +materializes +materializing +materially +materials +materiel +maternal +maternally +maternity +mates +math +mathematical +mathematically +mathematician +mathematicians +mathematics +matinee +matinees +mating +matins +matriarch +matriarchal +matriarchies +matriarchs +matriarchy +matrices +matricidal +matricide +matricides +matriculate +matriculated +matriculates +matriculating +matriculation +matrimonial +matrimony +matrix +matrixes +matron +matronly +matrons +mats +matt +matte +matted +matter +mattered +mattering +matters +mattes +matting +mattins +mattock +mattocks +mattress +mattresses +matts +maturate +maturated +maturates +maturating +maturation +mature +matured +maturely +maturer +matures +maturest +maturing +maturities +maturity +matzo +matzoh +matzohs +matzos +matzot +matzoth +maudlin +maul +mauled +mauler +maulers +mauling +mauls +maunder +maundered +maundering +maunders +mausolea +mausoleum +mausoleums +mauve +mauver +mauvest +maven +mavens +maverick +mavericks +mavin +mavins +mawkish +mawkishly +mawkishness +maws +maxed +maxes +maxi +maxilla +maxillae +maxillary +maxillas +maxim +maxima +maximal +maximally +maximization +maximize +maximized +maximizes +maximizing +maxims +maximum +maximums +maxing +maxis +maybe +maybes +mayday +maydays +mayflies +mayflower +mayflowers +mayfly +mayhem +mayo +mayonnaise +mayor +mayoral +mayoralty +mayoress +mayoresses +mayors +maypole +maypoles +mayst +maze +mazes +mazourka +mazourkas +mazurka +mazurkas +mead +meadow +meadowlark +meadowlarks +meadows +meager +meagerer +meagerest +meagerly +meagerness +meagre +meagrer +meagrest +meal +mealier +mealiest +mealiness +meals +mealtime +mealtimes +mealy +mealybug +mealybugs +mealymouthed +mean +meander +meandered +meandering +meanderings +meanders +meaner +meanest +meanie +meanies +meaning +meaningful +meaningfully +meaningfulness +meaningless +meaninglessly +meaninglessness +meanings +meanly +meanness +means +meant +meantime +meanwhile +meany +measles +measlier +measliest +measly +measurable +measurably +measure +measured +measureless +measurement +measurements +measures +measuring +meat +meatball +meatballs +meatier +meatiest +meatiness +meatless +meatloaf +meatloaves +meatpacking +meats +meaty +mecca +meccas +mechanic +mechanical +mechanically +mechanics +mechanism +mechanisms +mechanistic +mechanistically +mechanization +mechanize +mechanized +mechanizes +mechanizing +medal +medalist +medalists +medallion +medallions +medallist +medallists +medals +meddle +meddled +meddler +meddlers +meddles +meddlesome +meddling +media +mediaeval +medial +medially +median +medians +medias +mediate +mediated +mediates +mediating +mediation +mediator +mediators +medic +medicaid +medical +medically +medicals +medicament +medicare +medicate +medicated +medicates +medicating +medication +medications +medicinal +medicinally +medicine +medicines +medico +medicos +medics +medieval +medievalist +medievalists +mediocre +mediocrities +mediocrity +meditate +meditated +meditates +meditating +meditation +meditations +meditative +meditatively +medium +mediums +medley +medleys +meds +medulla +medullae +medullas +meed +meek +meeker +meekest +meekly +meekness +meerschaum +meerschaums +meet +meeting +meetinghouse +meetinghouses +meetings +meets +mega +megabit +megabits +megabucks +megabyte +megabytes +megacycle +megacycles +megadeath +megadeaths +megahertz +megahertzes +megalith +megalithic +megaliths +megalomania +megalomaniac +megalomaniacs +megalopolis +megalopolises +megaphone +megaphoned +megaphones +megaphoning +megaton +megatons +megawatt +megawatts +meiosis +meiotic +melamine +melancholia +melancholic +melancholy +melange +melanges +melanin +melanoma +melanomas +melanomata +meld +melded +melding +melds +melee +melees +meliorate +meliorated +meliorates +meliorating +melioration +meliorative +mellifluous +mellifluously +mellifluousness +mellow +mellowed +mellower +mellowest +mellowing +mellowly +mellowness +mellows +melodic +melodically +melodies +melodious +melodiously +melodiousness +melodrama +melodramas +melodramatic +melodramatically +melodramatics +melody +melon +melons +melt +meltdown +meltdowns +melted +melting +melts +member +members +membership +memberships +membrane +membranes +membranous +memento +mementoes +mementos +memo +memoir +memoirs +memorabilia +memorability +memorable +memorably +memoranda +memorandum +memorandums +memorial +memorialize +memorialized +memorializes +memorializing +memorials +memories +memorization +memorize +memorized +memorizes +memorizing +memory +memos +menace +menaced +menaces +menacing +menacingly +menage +menagerie +menageries +menages +mend +mendacious +mendaciously +mendacity +mended +mendelevium +mender +menders +mendicancy +mendicant +mendicants +mending +mends +menfolk +menfolks +menhaden +menhadens +menial +menially +menials +meningeal +meninges +meningitis +meninx +menisci +meniscus +meniscuses +menopausal +menopause +menorah +menorahs +mensch +mensches +menservants +menses +menstrual +menstruate +menstruated +menstruates +menstruating +menstruation +mensurable +mensuration +menswear +mental +mentalist +mentalists +mentalities +mentality +mentally +menthol +mentholated +mention +mentioned +mentioning +mentions +mentor +mentored +mentoring +mentors +menu +menus +meow +meowed +meowing +meows +mercantile +mercantilism +mercenaries +mercenary +mercer +mercerize +mercerized +mercerizes +mercerizing +mercers +merchandise +merchandised +merchandiser +merchandisers +merchandises +merchandising +merchandize +merchandized +merchandizes +merchandizing +merchant +merchantable +merchantman +merchantmen +merchants +mercies +merciful +mercifully +merciless +mercilessly +mercilessness +mercurial +mercurially +mercuric +mercury +mercy +mere +merely +meres +merest +meretricious +meretriciously +meretriciousness +merganser +mergansers +merge +merged +merger +mergers +merges +merging +meridian +meridians +meringue +meringues +merino +merinos +merit +merited +meriting +meritocracies +meritocracy +meritorious +meritoriously +meritoriousness +merits +mermaid +mermaids +merman +mermen +merrier +merriest +merrily +merriment +merriness +merry +merrymaker +merrymakers +merrymaking +mesa +mesas +mescal +mescaline +mescals +mesdames +mesdemoiselles +mesh +meshed +meshes +meshing +mesmerism +mesmerize +mesmerized +mesmerizer +mesmerizers +mesmerizes +mesmerizing +mesomorph +mesomorphs +meson +mesons +mesosphere +mesospheres +mesquit +mesquite +mesquites +mesquits +mess +message +messaged +messages +messaging +messed +messeigneurs +messenger +messengers +messes +messiah +messiahs +messianic +messier +messiest +messieurs +messily +messiness +messing +messmate +messmates +messy +mestizo +mestizoes +mestizos +metabolic +metabolically +metabolism +metabolisms +metabolite +metabolites +metabolize +metabolized +metabolizes +metabolizing +metacarpal +metacarpals +metacarpi +metacarpus +metal +metalanguage +metalanguages +metallic +metallurgic +metallurgical +metallurgist +metallurgists +metallurgy +metals +metalwork +metalworker +metalworkers +metalworking +metamorphic +metamorphism +metamorphose +metamorphosed +metamorphoses +metamorphosing +metamorphosis +metaphor +metaphoric +metaphorical +metaphorically +metaphors +metaphysical +metaphysically +metaphysics +metastases +metastasis +metastasize +metastasized +metastasizes +metastasizing +metastatic +metatarsal +metatarsals +metatarsi +metatarsus +metatheses +metathesis +mete +meted +metempsychosis +meteor +meteoric +meteorically +meteorite +meteorites +meteoroid +meteoroids +meteorologic +meteorological +meteorologist +meteorologists +meteorology +meteors +meter +metered +metering +meters +metes +methadon +methadone +methamphetamine +methane +methanol +methinks +method +methodical +methodically +methodicalness +methodological +methodologically +methodologies +methodology +methods +methought +methyl +meticulous +meticulously +meticulousness +metier +metiers +meting +metre +metres +metric +metrical +metrically +metricate +metricated +metricates +metricating +metrication +metricize +metricized +metricizes +metricizing +metro +metronome +metronomes +metropolis +metropolises +metropolitan +metros +mettle +mettlesome +mewed +mewing +mewl +mewled +mewling +mewls +mews +mezzanine +mezzanines +mezzo +mezzos +miasma +miasmas +miasmata +mica +mice +mickey +mickeys +micra +micro +microbe +microbes +microbial +microbiological +microbiologist +microbiologists +microbiology +microbreweries +microbrewery +microchip +microchips +microcircuit +microcircuits +microcomputer +microcomputers +microcosm +microcosmic +microcosms +microdot +microdots +microeconomics +microelectronic +microelectronics +microfiber +microfibers +microfiche +microfiches +microfilm +microfilmed +microfilming +microfilms +microgroove +microgrooves +microlight +microlights +micromanage +micromanaged +micromanagement +micromanages +micromanaging +micrometeorite +micrometeorites +micrometer +micrometers +micron +microns +microorganism +microorganisms +microphone +microphones +microprocessor +microprocessors +micros +microscope +microscopes +microscopic +microscopical +microscopically +microscopy +microsecond +microseconds +microsurgery +microwavable +microwave +microwaveable +microwaved +microwaves +microwaving +midair +midday +midden +middens +middies +middle +middlebrow +middlebrows +middleman +middlemen +middlemost +middles +middleweight +middleweights +middling +middy +midge +midges +midget +midgets +midi +midis +midland +midlands +midlife +midmost +midnight +midpoint +midpoints +midrib +midribs +midriff +midriffs +midsection +midsections +midshipman +midshipmen +midships +midsize +midst +midstream +midsummer +midterm +midterms +midtown +midway +midways +midweek +midweeks +midwife +midwifed +midwiferies +midwifery +midwifes +midwifing +midwinter +midwived +midwives +midwiving +midyear +midyears +mien +miens +miff +miffed +miffing +miffs +might +mightier +mightiest +mightily +mightiness +mighty +mignonette +mignonettes +migraine +migraines +migrant +migrants +migrate +migrated +migrates +migrating +migration +migrations +migratory +mikado +mikados +mike +miked +mikes +miking +miladies +milady +milch +mild +milder +mildest +mildew +mildewed +mildewing +mildews +mildly +mildness +mile +mileage +mileages +milepost +mileposts +miler +milers +miles +milestone +milestones +milieu +milieus +milieux +militancy +militant +militantly +militants +militaries +militarily +militarism +militarist +militaristic +militarists +militarization +militarize +militarized +militarizes +militarizing +military +militate +militated +militates +militating +militia +militiaman +militiamen +militias +milk +milked +milker +milkers +milkier +milkiest +milkiness +milking +milkmaid +milkmaids +milkman +milkmen +milks +milkshake +milkshakes +milksop +milksops +milkweed +milkweeds +milky +mill +millage +milled +millennia +millennial +millennium +millenniums +millepede +millepedes +miller +millers +millet +milliard +milliards +millibar +millibars +milligram +milligrams +milliliter +milliliters +millilitre +millilitres +millimeter +millimeters +millimetre +millimetres +milliner +milliners +millinery +milling +millings +million +millionaire +millionaires +millionnaire +millionnaires +millions +millionth +millionths +millipede +millipedes +millisecond +milliseconds +millpond +millponds +millrace +millraces +mills +millstone +millstones +millstream +millstreams +millwright +millwrights +milquetoast +milquetoasts +mils +milt +milted +milting +milts +mime +mimed +mimeograph +mimeographed +mimeographing +mimeographs +mimes +mimetic +mimic +mimicked +mimicker +mimickers +mimicking +mimicries +mimicry +mimics +miming +mimosa +mimosas +minaret +minarets +minatory +mince +minced +mincemeat +mincer +mincers +minces +mincing +mind +minded +mindful +mindfully +mindfulness +minding +mindless +mindlessly +mindlessness +minds +mindset +mindsets +mine +mined +minefield +minefields +miner +mineral +mineralogical +mineralogist +mineralogists +mineralogy +minerals +miners +mines +minestrone +minesweeper +minesweepers +mingle +mingled +mingles +mingling +mini +miniature +miniatures +miniaturist +miniaturists +miniaturization +miniaturize +miniaturized +miniaturizes +miniaturizing +minibike +minibikes +minibus +minibuses +minibusses +minicam +minicams +minicomputer +minicomputers +minim +minima +minimal +minimalism +minimalist +minimalists +minimally +minimization +minimize +minimized +minimizes +minimizing +minims +minimum +minimums +mining +minion +minions +minis +miniscule +miniscules +miniseries +miniskirt +miniskirts +minister +ministered +ministerial +ministering +ministers +ministrant +ministrants +ministration +ministrations +ministries +ministry +minivan +minivans +mink +minks +minnesinger +minnesingers +minnow +minnows +minor +minored +minoring +minorities +minority +minors +minoxidil +minster +minsters +minstrel +minstrels +minstrelsy +mint +mintage +minted +minter +minters +mintier +mintiest +minting +mints +minty +minuend +minuends +minuet +minuets +minus +minuscule +minuscules +minuses +minute +minuted +minutely +minuteman +minutemen +minuteness +minuter +minutes +minutest +minutia +minutiae +minuting +minx +minxes +miracle +miracles +miraculous +miraculously +mirage +mirages +mire +mired +mires +mirier +miriest +miring +mirror +mirrored +mirroring +mirrors +mirth +mirthful +mirthfully +mirthfulness +mirthless +mirthlessly +miry +misaddress +misaddressed +misaddresses +misaddressing +misadventure +misadventures +misaligned +misalignment +misalliance +misalliances +misanthrope +misanthropes +misanthropic +misanthropically +misanthropist +misanthropists +misanthropy +misapplication +misapplied +misapplies +misapply +misapplying +misapprehend +misapprehended +misapprehending +misapprehends +misapprehension +misapprehensions +misappropriate +misappropriated +misappropriates +misappropriating +misappropriation +misappropriations +misbegotten +misbehave +misbehaved +misbehaves +misbehaving +misbehavior +miscalculate +miscalculated +miscalculates +miscalculating +miscalculation +miscalculations +miscall +miscalled +miscalling +miscalls +miscarriage +miscarriages +miscarried +miscarries +miscarry +miscarrying +miscast +miscasting +miscasts +miscegenation +miscellaneous +miscellaneously +miscellanies +miscellany +mischance +mischances +mischief +mischievous +mischievously +mischievousness +miscibility +miscible +misconceive +misconceived +misconceives +misconceiving +misconception +misconceptions +misconduct +misconducted +misconducting +misconducts +misconstruction +misconstructions +misconstrue +misconstrued +misconstrues +misconstruing +miscount +miscounted +miscounting +miscounts +miscreant +miscreants +miscue +miscued +miscues +miscuing +misdeal +misdealing +misdeals +misdealt +misdeed +misdeeds +misdemeanor +misdemeanors +misdemeanour +misdemeanours +misdiagnose +misdiagnosed +misdiagnoses +misdiagnosing +misdiagnosis +misdid +misdirect +misdirected +misdirecting +misdirection +misdirects +misdo +misdoes +misdoing +misdoings +misdone +miser +miserable +miserableness +miserably +miseries +miserliness +miserly +misers +misery +misfeasance +misfile +misfiled +misfiles +misfiling +misfire +misfired +misfires +misfiring +misfit +misfits +misfitted +misfitting +misfortune +misfortunes +misgiving +misgivings +misgovern +misgoverned +misgoverning +misgovernment +misgoverns +misguidance +misguide +misguided +misguidedly +misguides +misguiding +mishandle +mishandled +mishandles +mishandling +mishap +mishaps +mishear +misheard +mishearing +mishears +mishmash +mishmashes +misidentified +misidentifies +misidentify +misidentifying +misinform +misinformation +misinformed +misinforming +misinforms +misinterpret +misinterpretation +misinterpretations +misinterpreted +misinterpreting +misinterprets +misjudge +misjudged +misjudges +misjudging +misjudgment +misjudgments +mislabel +mislabeled +mislabeling +mislabelled +mislabelling +mislabels +mislaid +mislay +mislaying +mislays +mislead +misleading +misleadingly +misleads +misled +mismanage +mismanaged +mismanagement +mismanages +mismanaging +mismatch +mismatched +mismatches +mismatching +misname +misnamed +misnames +misnaming +misnomer +misnomers +misogamist +misogamists +misogamy +misogynist +misogynistic +misogynists +misogynous +misogyny +misplace +misplaced +misplacement +misplaces +misplacing +misplay +misplayed +misplaying +misplays +misprint +misprinted +misprinting +misprints +misprision +mispronounce +mispronounced +mispronounces +mispronouncing +mispronunciation +mispronunciations +misquotation +misquotations +misquote +misquoted +misquotes +misquoting +misread +misreading +misreadings +misreads +misreport +misreported +misreporting +misreports +misrepresent +misrepresentation +misrepresentations +misrepresented +misrepresenting +misrepresents +misrule +misruled +misrules +misruling +miss +missal +missals +missed +misses +misshape +misshaped +misshapen +misshapes +misshaping +missile +missilery +missiles +missilry +missing +mission +missionaries +missionary +missioner +missioners +missions +missis +missises +missive +missives +misspeak +misspeaking +misspeaks +misspell +misspelled +misspelling +misspellings +misspells +misspelt +misspend +misspending +misspends +misspent +misspoke +misspoken +misstate +misstated +misstatement +misstatements +misstates +misstating +misstep +missteps +missus +missuses +mist +mistakable +mistake +mistaken +mistakenly +mistakes +mistaking +misted +mister +misters +mistier +mistiest +mistily +mistime +mistimed +mistimes +mistiming +mistiness +misting +mistletoe +mistook +mistral +mistrals +mistreat +mistreated +mistreating +mistreatment +mistreats +mistress +mistresses +mistrial +mistrials +mistrust +mistrusted +mistrustful +mistrustfully +mistrusting +mistrusts +mists +misty +misunderstand +misunderstanding +misunderstandings +misunderstands +misunderstood +misuse +misused +misuses +misusing +mite +miter +mitered +mitering +miters +mites +mitigate +mitigated +mitigates +mitigating +mitigation +mitosis +mitotic +mitre +mitred +mitres +mitring +mitt +mitten +mittens +mitts +mixable +mixed +mixer +mixers +mixes +mixing +mixt +mixture +mixtures +mizzen +mizzenmast +mizzenmasts +mizzens +mnemonic +mnemonically +mnemonics +moan +moaned +moaner +moaners +moaning +moans +moat +moats +mobbed +mobbing +mobile +mobiles +mobility +mobilization +mobilizations +mobilize +mobilized +mobilizer +mobilizers +mobilizes +mobilizing +mobs +mobster +mobsters +moccasin +moccasins +mocha +mochas +mock +mocked +mocker +mockeries +mockers +mockery +mocking +mockingbird +mockingbirds +mockingly +mocks +mockup +mockups +modal +modals +mode +model +modeled +modeler +modelers +modeling +modelled +modelling +models +modem +modems +moderate +moderated +moderately +moderateness +moderates +moderating +moderation +moderator +moderators +modern +modernism +modernist +modernistic +modernists +modernity +modernization +modernize +modernized +modernizer +modernizers +modernizes +modernizing +modernly +modernness +moderns +modes +modest +modestly +modesty +modicum +modicums +modification +modifications +modified +modifier +modifiers +modifies +modify +modifying +modish +modishly +modishness +mods +modular +modulate +modulated +modulates +modulating +modulation +modulations +modulator +modulators +module +modules +mogul +moguls +mohair +moieties +moiety +moil +moiled +moiling +moils +moire +moires +moist +moisten +moistened +moistener +moisteners +moistening +moistens +moister +moistest +moistly +moistness +moisture +moisturize +moisturized +moisturizer +moisturizers +moisturizes +moisturizing +molar +molars +molasses +mold +moldboard +moldboards +molded +molder +moldered +moldering +molders +moldier +moldiest +moldiness +molding +moldings +molds +moldy +mole +molecular +molecularity +molecule +molecules +molehill +molehills +moles +moleskin +molest +molestation +molested +molester +molesters +molesting +molests +moll +mollies +mollification +mollified +mollifies +mollify +mollifying +molls +mollusc +molluscan +molluscans +molluscs +mollusk +molluskan +molluskans +mollusks +molly +mollycoddle +mollycoddled +mollycoddles +mollycoddling +molt +molted +molten +molter +molters +molting +molts +molybdenum +moment +momentarily +momentariness +momentary +momentous +momentously +momentousness +moments +momentum +momma +mommas +mommie +mommies +mommy +moms +monarch +monarchic +monarchical +monarchies +monarchism +monarchist +monarchistic +monarchists +monarchs +monarchy +monasteries +monastery +monastic +monastical +monastically +monasticism +monastics +monaural +monetarily +monetarism +monetarist +monetarists +monetary +monetize +monetized +monetizes +monetizing +money +moneybag +moneybags +moneyed +moneygrubber +moneygrubbers +moneygrubbing +moneylender +moneylenders +moneymaker +moneymakers +moneymaking +mongeese +monger +mongered +mongering +mongers +mongolism +mongoloid +mongoloids +mongoose +mongooses +mongrel +mongrels +monicker +monickers +monied +moniker +monikers +monism +monist +monists +monition +monitions +monitor +monitored +monitoring +monitors +monitory +monk +monkey +monkeyed +monkeying +monkeys +monkeyshine +monkeyshines +monkish +monks +monkshood +monkshoods +mono +monochromatic +monochrome +monochromes +monocle +monocled +monocles +monoclonal +monocotyledon +monocotyledonous +monocotyledons +monocular +monodic +monodies +monodist +monodists +monody +monogamist +monogamists +monogamous +monogamously +monogamy +monogram +monogrammed +monogramming +monograms +monograph +monographs +monolingual +monolinguals +monolith +monolithic +monoliths +monolog +monologist +monologists +monologs +monologue +monologues +monologuist +monologuists +monomania +monomaniac +monomaniacal +monomaniacs +monomer +monomers +mononucleosis +monophonic +monoplane +monoplanes +monopolies +monopolist +monopolistic +monopolists +monopolization +monopolize +monopolized +monopolizer +monopolizers +monopolizes +monopolizing +monopoly +monorail +monorails +monosyllabic +monosyllable +monosyllables +monotheism +monotheist +monotheistic +monotheists +monotone +monotones +monotonous +monotonously +monotonousness +monotony +monounsaturated +monoxide +monoxides +monseigneur +monsieur +monsignor +monsignori +monsignors +monsoon +monsoonal +monsoons +monster +monsters +monstrance +monstrances +monstrosities +monstrosity +monstrous +monstrously +montage +montages +month +monthlies +monthly +months +monument +monumental +monumentally +monuments +mooch +mooched +moocher +moochers +mooches +mooching +mood +moodier +moodiest +moodily +moodiness +moods +moody +mooed +mooing +moon +moonbeam +moonbeams +mooned +mooning +moonless +moonlight +moonlighted +moonlighter +moonlighters +moonlighting +moonlights +moonlit +moons +moonscape +moonscapes +moonshine +moonshiner +moonshiners +moonshot +moonshots +moonstone +moonstones +moonstruck +moonwalk +moonwalks +moor +moored +mooring +moorings +moorland +moors +moos +moose +moot +mooted +mooting +moots +mope +moped +mopeds +moper +mopers +mopes +mopey +mopier +mopiest +moping +mopish +mopped +moppet +moppets +mopping +mops +mopy +moraine +moraines +moral +morale +moralist +moralistic +moralistically +moralists +moralities +morality +moralization +moralize +moralized +moralizer +moralizers +moralizes +moralizing +morally +morals +morass +morasses +moratoria +moratorium +moratoriums +moray +morays +morbid +morbidity +morbidly +morbidness +mordancy +mordant +mordantly +mordants +more +morel +morels +moreover +mores +morgue +morgues +moribund +morn +morning +mornings +morns +morocco +moron +moronic +moronically +morons +morose +morosely +moroseness +morph +morphed +morpheme +morphemes +morphemic +morphia +morphine +morphing +morphological +morphology +morphs +morrow +morrows +morsel +morsels +mortal +mortality +mortally +mortals +mortar +mortarboard +mortarboards +mortared +mortaring +mortars +mortgage +mortgaged +mortgagee +mortgagees +mortgager +mortgagers +mortgages +mortgaging +mortgagor +mortgagors +mortice +morticed +mortices +mortician +morticians +morticing +mortification +mortified +mortifies +mortify +mortifying +mortise +mortised +mortises +mortising +mortuaries +mortuary +mosaic +mosaics +mosey +moseyed +moseying +moseys +mosh +moshed +moshes +moshing +mosque +mosques +mosquito +mosquitoes +mosquitos +moss +mossback +mossbacks +mosses +mossier +mossiest +mossy +most +mostly +mote +motel +motels +motes +motet +motets +moth +mothball +mothballed +mothballing +mothballs +mother +motherboard +motherboards +mothered +motherfucker +motherfuckers +motherfucking +motherhood +mothering +motherland +motherlands +motherless +motherliness +motherly +mothers +moths +motif +motifs +motile +motility +motion +motioned +motioning +motionless +motionlessly +motionlessness +motions +motivate +motivated +motivates +motivating +motivation +motivational +motivations +motivator +motivators +motive +motiveless +motives +motley +motlier +motliest +motocross +motocrosses +motor +motorbike +motorbiked +motorbikes +motorbiking +motorboat +motorboated +motorboating +motorboats +motorcade +motorcades +motorcar +motorcars +motorcycle +motorcycled +motorcycles +motorcycling +motorcyclist +motorcyclists +motored +motoring +motorist +motorists +motorization +motorize +motorized +motorizes +motorizing +motorman +motormen +motormouth +motormouths +motors +mots +mottle +mottled +mottles +mottling +motto +mottoes +mottos +moue +moues +mould +moulded +moulder +mouldered +mouldering +moulders +mouldier +mouldiest +moulding +mouldings +moulds +mouldy +moult +moulted +moulting +moults +mound +mounded +mounding +mounds +mount +mountable +mountain +mountaineer +mountaineered +mountaineering +mountaineers +mountainous +mountains +mountainside +mountainsides +mountaintop +mountaintops +mountebank +mountebanks +mounted +mounter +mounters +mounting +mountings +mounts +mourn +mourned +mourner +mourners +mournful +mournfully +mournfulness +mourning +mourns +mouse +moused +mouser +mousers +mouses +mousetrap +mousetrapped +mousetrapping +mousetraps +mousey +mousier +mousiest +mousiness +mousing +mousse +moussed +mousses +moussing +moustache +moustaches +mousy +mouth +mouthed +mouthful +mouthfuls +mouthier +mouthiest +mouthiness +mouthing +mouthpiece +mouthpieces +mouths +mouthwash +mouthwashes +mouthwatering +mouthy +mouton +movable +movables +move +moveable +moveables +moved +movement +movements +mover +movers +moves +movie +moviegoer +moviegoers +movies +moving +movingly +mowed +mower +mowers +mowing +mown +mows +moxie +mozzarella +much +mucilage +mucilaginous +muck +mucked +muckier +muckiest +mucking +muckrake +muckraked +muckraker +muckrakers +muckrakes +muckraking +mucks +mucky +mucous +mucus +muddied +muddier +muddies +muddiest +muddily +muddiness +muddle +muddled +muddleheaded +muddles +muddling +muddy +muddying +mudflat +mudflats +mudguard +mudguards +mudroom +mudrooms +mudslide +mudslides +mudslinger +mudslingers +mudslinging +muenster +muezzin +muezzins +muff +muffed +muffin +muffing +muffins +muffle +muffled +muffler +mufflers +muffles +muffling +muffs +mufti +muftis +mugful +mugfuls +mugged +mugger +muggers +muggier +muggiest +mugginess +mugging +muggings +muggy +mugs +mugshot +mugshots +mugwump +mugwumps +mujahedin +mukluk +mukluks +mulatto +mulattoes +mulattos +mulberries +mulberry +mulch +mulched +mulches +mulching +mulct +mulcted +mulcting +mulcts +mule +mules +muleskinner +muleskinners +muleteer +muleteers +mulish +mulishly +mulishness +mull +mullah +mullahs +mulled +mullein +mullet +mullets +mulligan +mulligans +mulligatawny +mulling +mullion +mullioned +mullions +mulls +multicolored +multicultural +multiculturalism +multidimensional +multidisciplinary +multifaceted +multifamily +multifarious +multifariously +multifariousness +multiform +multilateral +multilaterally +multilevel +multilingual +multilingualism +multimedia +multimillionaire +multimillionaires +multinational +multinationals +multiple +multiples +multiplex +multiplexed +multiplexer +multiplexers +multiplexes +multiplexing +multiplexor +multiplexors +multiplicand +multiplicands +multiplication +multiplications +multiplicities +multiplicity +multiplied +multiplier +multipliers +multiplies +multiply +multiplying +multiprocessor +multiprocessors +multipurpose +multiracial +multistage +multistory +multitasking +multitude +multitudes +multitudinous +multivitamin +multivitamins +mumble +mumbled +mumbler +mumblers +mumbles +mumbletypeg +mumbling +mummer +mummers +mummery +mummies +mummification +mummified +mummifies +mummify +mummifying +mummy +mumps +mums +munch +munched +munches +munchies +munching +munchkin +munchkins +mundane +mundanely +municipal +municipalities +municipality +municipally +municipals +munificence +munificent +munificently +munition +munitioned +munitioning +munitions +mural +muralist +muralists +murals +murder +murdered +murderer +murderers +murderess +murderesses +murdering +murderous +murderously +murders +murk +murkier +murkiest +murkily +murkiness +murky +murmur +murmured +murmurer +murmurers +murmuring +murmurings +murmurous +murmurs +murrain +muscat +muscatel +muscatels +muscats +muscle +musclebound +muscled +muscles +muscling +muscular +muscularity +muscularly +musculature +muse +mused +muses +musette +musettes +museum +museums +mush +mushed +mushes +mushier +mushiest +mushiness +mushing +mushroom +mushroomed +mushrooming +mushrooms +mushy +music +musical +musicale +musicales +musicality +musically +musicals +musician +musicianly +musicians +musicianship +musicological +musicologist +musicologists +musicology +musing +musingly +musings +musk +muskeg +muskegs +muskellunge +muskellunges +musket +musketeer +musketeers +musketry +muskets +muskie +muskier +muskies +muskiest +muskiness +muskmelon +muskmelons +muskox +muskoxen +muskrat +muskrats +musky +muslin +muss +mussed +mussel +mussels +musses +mussier +mussiest +mussing +mussy +must +mustache +mustached +mustaches +mustachio +mustachios +mustang +mustangs +mustard +muster +mustered +mustering +musters +mustier +mustiest +mustily +mustiness +musts +musty +mutability +mutable +mutably +mutagen +mutagens +mutant +mutants +mutate +mutated +mutates +mutating +mutation +mutational +mutations +mutative +mute +muted +mutely +muteness +muter +mutes +mutest +mutilate +mutilated +mutilates +mutilating +mutilation +mutilations +mutilator +mutilators +mutineer +mutineers +muting +mutinied +mutinies +mutinous +mutinously +mutiny +mutinying +mutt +mutter +muttered +mutterer +mutterers +muttering +mutterings +mutters +mutton +muttonchops +muttony +mutts +mutual +mutuality +mutually +muumuu +muumuus +muzzle +muzzled +muzzles +muzzling +mycologist +mycologists +mycology +myelitis +myna +mynah +mynahs +mynas +myopia +myopic +myopically +myriad +myriads +myrmidon +myrmidons +myrrh +myrtle +myrtles +myself +mysteries +mysterious +mysteriously +mysteriousness +mystery +mystic +mystical +mystically +mysticism +mystics +mystification +mystified +mystifies +mystify +mystifying +mystique +myth +mythic +mythical +mythological +mythologies +mythologist +mythologists +mythologize +mythologized +mythologizes +mythologizing +mythology +myths +nabbed +nabbing +nabob +nabobs +nabs +nacelle +nacelles +nacho +nachos +nacre +nacreous +nadir +nadirs +nagged +nagger +naggers +nagging +nags +naiad +naiades +naiads +naif +naifs +nail +nailbrush +nailbrushes +nailed +nailing +nails +naive +naively +naiver +naivest +naivete +naivety +naked +nakedly +nakedness +name +nameable +named +namedrop +namedropped +namedropping +namedrops +nameless +namelessly +namely +nameplate +nameplates +names +namesake +namesakes +naming +nannies +nanny +nanosecond +nanoseconds +napalm +napalmed +napalming +napalms +nape +napes +naphtha +naphthalene +napkin +napkins +napless +napoleon +napoleons +napped +napper +nappers +nappier +nappies +nappiest +napping +nappy +naps +narc +narcissi +narcissism +narcissist +narcissistic +narcissists +narcissus +narcissuses +narcolepsy +narcosis +narcotic +narcotics +narcotization +narcotize +narcotized +narcotizes +narcotizing +narcs +nark +narks +narrate +narrated +narrates +narrating +narration +narrations +narrative +narratives +narrator +narrators +narrow +narrowed +narrower +narrowest +narrowing +narrowly +narrowness +narrows +narwhal +narwhals +nary +nasal +nasality +nasalization +nasalize +nasalized +nasalizes +nasalizing +nasally +nasals +nascence +nascent +nastier +nastiest +nastily +nastiness +nasturtium +nasturtiums +nasty +natal +natch +nation +national +nationalism +nationalist +nationalistic +nationalistically +nationalists +nationalities +nationality +nationalization +nationalizations +nationalize +nationalized +nationalizes +nationalizing +nationally +nationals +nationhood +nations +nationwide +native +natives +nativities +nativity +natter +nattered +nattering +natters +nattier +nattiest +nattily +nattiness +natty +natural +naturalism +naturalist +naturalistic +naturalists +naturalization +naturalize +naturalized +naturalizes +naturalizing +naturally +naturalness +naturals +nature +natures +naught +naughtier +naughtiest +naughtily +naughtiness +naughts +naughty +nausea +nauseate +nauseated +nauseates +nauseating +nauseatingly +nauseous +nauseously +nauseousness +nautical +nautically +nautili +nautilus +nautiluses +naval +nave +navel +navels +naves +navies +navigability +navigable +navigate +navigated +navigates +navigating +navigation +navigational +navigator +navigators +navy +nays +naysayer +naysayers +neanderthal +neanderthals +neap +neaps +near +nearby +neared +nearer +nearest +nearing +nearly +nearness +nears +nearsighted +nearsightedly +nearsightedness +neat +neaten +neatened +neatening +neatens +neater +neatest +neath +neatly +neatness +nebula +nebulae +nebular +nebulas +nebulous +nebulously +nebulousness +necessaries +necessarily +necessary +necessitate +necessitated +necessitates +necessitating +necessities +necessitous +necessity +neck +necked +neckerchief +neckerchiefs +neckerchieves +necking +necklace +necklaces +neckline +necklines +necks +necktie +neckties +necrology +necromancer +necromancers +necromancy +necropoleis +necropoles +necropoli +necropolis +necropolises +necrosis +necrotic +nectar +nectarine +nectarines +need +needed +needful +needfully +needier +neediest +neediness +needing +needle +needled +needlepoint +needles +needless +needlessly +needlessness +needlewoman +needlewomen +needlework +needling +needs +needy +nefarious +nefariously +nefariousness +negate +negated +negates +negating +negation +negations +negative +negatived +negatively +negativeness +negatives +negativing +negativism +negativity +neglect +neglected +neglectful +neglectfully +neglectfulness +neglecting +neglects +neglige +negligee +negligees +negligence +negligent +negligently +negliges +negligible +negligibly +negotiability +negotiable +negotiate +negotiated +negotiates +negotiating +negotiation +negotiations +negotiator +negotiators +negritude +neigh +neighbor +neighbored +neighborhood +neighborhoods +neighboring +neighborliness +neighborly +neighbors +neighbour +neighboured +neighbouring +neighbours +neighed +neighing +neighs +neither +nelson +nelsons +nematode +nematodes +nemeses +nemesis +neoclassic +neoclassical +neoclassicism +neocolonialism +neocolonialist +neocolonialists +neoconservative +neoconservatives +neodymium +neolithic +neologism +neologisms +neon +neonatal +neonate +neonates +neophyte +neophytes +neoplasm +neoplasms +neoplastic +neoprene +nepenthe +nephew +nephews +nephrite +nephritic +nephritis +nepotism +nepotist +nepotists +neptunium +nerd +nerdier +nerdiest +nerds +nerdy +nerve +nerved +nerveless +nervelessly +nervelessness +nerves +nervier +nerviest +nerviness +nerving +nervous +nervously +nervousness +nervy +nest +nested +nesting +nestle +nestled +nestles +nestling +nestlings +nests +nether +nethermost +netherworld +nets +nett +netted +netting +nettle +nettled +nettles +nettlesome +nettling +netts +network +networked +networking +networks +neural +neuralgia +neuralgic +neurally +neurasthenia +neurasthenic +neurasthenics +neuritic +neuritics +neuritis +neurological +neurologically +neurologist +neurologists +neurology +neuron +neuronal +neurons +neuroses +neurosis +neurosurgeon +neurosurgeons +neurosurgery +neurotic +neurotically +neurotics +neurotransmitter +neurotransmitters +neuter +neutered +neutering +neuters +neutral +neutralism +neutralist +neutralists +neutrality +neutralization +neutralize +neutralized +neutralizer +neutralizers +neutralizes +neutralizing +neutrally +neutrals +neutrino +neutrinos +neutron +neutrons +never +nevermore +nevertheless +nevi +nevus +newbie +newbies +newborn +newborns +newcomer +newcomers +newel +newels +newer +newest +newfangled +newly +newlywed +newlyweds +newness +news +newsboy +newsboys +newscast +newscaster +newscasters +newscasts +newsdealer +newsdealers +newsgirl +newsgirls +newsgroup +newsgroups +newsier +newsiest +newsletter +newsletters +newsman +newsmen +newspaper +newspaperman +newspapermen +newspapers +newspaperwoman +newspaperwomen +newsprint +newsreel +newsreels +newsroom +newsrooms +newsstand +newsstands +newsweeklies +newsweekly +newswoman +newswomen +newsworthiness +newsworthy +newsy +newt +newton +newtons +newts +next +nexus +nexuses +niacin +nibble +nibbled +nibbler +nibblers +nibbles +nibbling +nibs +nice +nicely +niceness +nicer +nicest +niceties +nicety +niche +niches +nick +nicked +nickel +nickelodeon +nickelodeons +nickels +nicker +nickered +nickering +nickers +nicking +nicknack +nicknacks +nickname +nicknamed +nicknames +nicknaming +nicks +nicotine +niece +nieces +niftier +niftiest +nifty +niggard +niggardliness +niggardly +niggards +nigger +niggers +niggle +niggled +niggler +nigglers +niggles +niggling +nigh +nigher +nighest +night +nightcap +nightcaps +nightclothes +nightclub +nightclubbed +nightclubbing +nightclubs +nightdress +nightdresses +nightfall +nightgown +nightgowns +nighthawk +nighthawks +nightie +nighties +nightingale +nightingales +nightlife +nightlong +nightly +nightmare +nightmares +nightmarish +nights +nightshade +nightshades +nightshirt +nightshirts +nightspot +nightspots +nightstand +nightstands +nightstick +nightsticks +nighttime +nightwear +nighty +nihilism +nihilist +nihilistic +nihilists +nimbi +nimble +nimbleness +nimbler +nimblest +nimbly +nimbus +nimbuses +nimrod +nimrods +nincompoop +nincompoops +nine +ninepin +ninepins +nines +nineteen +nineteens +nineteenth +nineteenths +nineties +ninetieth +ninetieths +ninety +ninja +ninjas +ninnies +ninny +ninth +ninths +niobium +nipped +nipper +nippers +nippier +nippiest +nippiness +nipping +nipple +nipples +nippy +nips +nirvana +nisei +niseis +nite +niter +nites +nitpick +nitpicked +nitpicker +nitpickers +nitpicking +nitpicks +nitrate +nitrated +nitrates +nitrating +nitration +nitre +nitrification +nitrite +nitrites +nitrocellulose +nitrogen +nitrogenous +nitroglycerin +nitroglycerine +nits +nitwit +nitwits +nixed +nixes +nixing +nobelium +nobility +noble +nobleman +noblemen +nobleness +nobler +nobles +noblest +noblewoman +noblewomen +nobly +nobodies +nobody +nocturnal +nocturnally +nocturne +nocturnes +nodal +nodded +nodding +noddle +noddles +node +nodes +nods +nodular +nodule +nodules +noel +noels +noes +noggin +noggins +nohow +noise +noised +noiseless +noiselessly +noiselessness +noisemaker +noisemakers +noises +noisier +noisiest +noisily +noisiness +noising +noisome +noisy +nomad +nomadic +nomads +nomenclature +nomenclatures +nominal +nominally +nominate +nominated +nominates +nominating +nomination +nominations +nominative +nominatives +nominator +nominators +nominee +nominees +nonabrasive +nonabsorbent +nonabsorbents +nonacademic +nonacceptance +nonacid +nonactive +nonactives +nonaddictive +nonadhesive +nonadjacent +nonadjustable +nonadministrative +nonage +nonagenarian +nonagenarians +nonages +nonaggression +nonalcoholic +nonaligned +nonalignment +nonallergic +nonappearance +nonappearances +nonassignable +nonathletic +nonattendance +nonautomotive +nonavailability +nonbasic +nonbeliever +nonbelievers +nonbelligerent +nonbelligerents +nonbinding +nonbreakable +nonburnable +noncaloric +noncancerous +nonce +nonchalance +nonchalant +nonchalantly +nonchargeable +nonclerical +nonclericals +nonclinical +noncollectable +noncom +noncombat +noncombatant +noncombatants +noncombustible +noncommercial +noncommercials +noncommittal +noncommittally +noncommunicable +noncompeting +noncompetitive +noncompliance +noncomplying +noncomprehending +noncoms +nonconducting +nonconductor +nonconductors +nonconforming +nonconformist +nonconformists +nonconformity +nonconsecutive +nonconstructive +noncontagious +noncontinuous +noncontributing +noncontributory +noncontroversial +nonconvertible +noncooperation +noncorroding +noncorrosive +noncredit +noncriminal +noncriminals +noncritical +noncrystalline +noncumulative +noncustodial +nondairy +nondeductible +nondeliveries +nondelivery +nondemocratic +nondenominational +nondepartmental +nondepreciating +nondescript +nondestructive +nondetachable +nondisciplinary +nondisclosure +nondiscrimination +nondiscriminatory +nondramatic +nondrinker +nondrinkers +nondrying +none +noneducational +noneffective +nonelastic +nonelectric +nonelectrical +nonenforceable +nonentities +nonentity +nonequivalent +nonequivalents +nonessential +nonesuch +nonesuches +nonetheless +nonevent +nonevents +nonexchangeable +nonexclusive +nonexempt +nonexempts +nonexistence +nonexistent +nonexplosive +nonexplosives +nonfactual +nonfading +nonfat +nonfatal +nonfattening +nonferrous +nonfiction +nonfictional +nonflammable +nonflowering +nonfluctuating +nonflying +nonfood +nonfoods +nonfreezing +nonfunctional +nongovernmental +nongranular +nonhazardous +nonhereditary +nonhuman +nonidentical +noninclusive +nonindependent +nonindustrial +noninfectious +noninflammatory +noninflationary +noninflected +nonintellectual +nonintellectuals +noninterchangeable +noninterference +nonintervention +nonintoxicating +noninvasive +nonirritating +nonjudgmental +nonjudicial +nonlegal +nonlethal +nonlinear +nonliterary +nonliving +nonmagnetic +nonmalignant +nonmember +nonmembers +nonmetal +nonmetallic +nonmetals +nonmigratory +nonmilitant +nonmilitary +nonnarcotic +nonnarcotics +nonnative +nonnatives +nonnegotiable +nonnuclear +nonnumerical +nonobjective +nonobligatory +nonobservance +nonobservant +nonoccupational +nonoccurrence +nonofficial +nonoperational +nonoperative +nonparallel +nonparallels +nonpareil +nonpareils +nonparticipant +nonparticipants +nonparticipating +nonpartisan +nonpartisans +nonpaying +nonpayment +nonpayments +nonperformance +nonperforming +nonperishable +nonperson +nonpersons +nonphysical +nonphysically +nonplus +nonplused +nonpluses +nonplusing +nonplussed +nonplusses +nonplussing +nonpoisonous +nonpolitical +nonpolluting +nonporous +nonpracticing +nonprejudicial +nonprescription +nonproductive +nonprofessional +nonprofessionals +nonprofit +nonprofitable +nonprofits +nonproliferation +nonpublic +nonpunishable +nonracial +nonradioactive +nonrandom +nonreactive +nonreciprocal +nonreciprocals +nonreciprocating +nonrecognition +nonrecoverable +nonrecurring +nonredeemable +nonrefillable +nonrefundable +nonreligious +nonrenewable +nonrepresentational +nonresident +nonresidential +nonresidents +nonresidual +nonresiduals +nonresistance +nonresistant +nonrestrictive +nonreturnable +nonreturnables +nonrhythmic +nonrigid +nonsalaried +nonscheduled +nonscientific +nonscoring +nonseasonal +nonsectarian +nonsecular +nonsegregated +nonsense +nonsensical +nonsensically +nonsensitive +nonsexist +nonsexual +nonskid +nonslip +nonsmoker +nonsmokers +nonsmoking +nonsocial +nonspeaking +nonspecialist +nonspecialists +nonspecializing +nonspecific +nonspiritual +nonspirituals +nonstaining +nonstandard +nonstarter +nonstarters +nonstick +nonstop +nonstrategic +nonstriking +nonstructural +nonsuccessive +nonsupport +nonsupporting +nonsurgical +nonsustaining +nonsympathizer +nonsympathizers +nontarnishable +nontaxable +nontechnical +nontenured +nontheatrical +nonthinking +nonthreatening +nontoxic +nontraditional +nontransferable +nontransparent +nontropical +nonuniform +nonunion +nonuser +nonusers +nonvenomous +nonverbal +nonviable +nonviolence +nonviolent +nonviolently +nonvirulent +nonvocal +nonvocational +nonvolatile +nonvoter +nonvoters +nonvoting +nonwhite +nonwhites +nonworking +nonyielding +noodle +noodled +noodles +noodling +nook +nooks +noon +noonday +noontide +noontime +noose +nooses +nope +norm +normal +normalcy +normality +normalization +normalize +normalized +normalizes +normalizing +normally +normative +norms +north +northbound +northeast +northeaster +northeasterly +northeastern +northeasters +northeastward +northeastwards +norther +northerlies +northerly +northern +northerner +northerners +northernmost +northers +northward +northwards +northwest +northwester +northwesterly +northwestern +northwesters +northwestward +northwestwards +nose +nosebleed +nosebleeds +nosecone +nosecones +nosed +nosedive +nosedived +nosedives +nosediving +nosedove +nosegay +nosegays +noses +nosey +nosh +noshed +nosher +noshers +noshes +noshing +nosier +nosiest +nosily +nosiness +nosing +nostalgia +nostalgic +nostalgically +nostril +nostrils +nostrum +nostrums +nosy +notabilities +notability +notable +notables +notably +notarial +notaries +notarization +notarize +notarized +notarizes +notarizing +notary +notate +notated +notates +notating +notation +notations +notch +notched +notches +notching +note +notebook +notebooks +noted +notepaper +notes +noteworthier +noteworthiest +noteworthiness +noteworthy +nothing +nothingness +nothings +notice +noticeable +noticeably +noticed +notices +noticing +notification +notifications +notified +notifier +notifiers +notifies +notify +notifying +noting +notion +notional +notions +notoriety +notorious +notoriously +notwithstanding +nougat +nougats +nought +noughts +noun +nouns +nourish +nourished +nourishes +nourishing +nourishment +nova +novae +novas +novel +novelette +novelettes +novelist +novelists +novelization +novelizations +novelize +novelized +novelizes +novelizing +novella +novellas +novelle +novels +novelties +novelty +novena +novenae +novenas +novice +novices +novitiate +novitiates +nowadays +noway +noways +nowhere +nowise +noxious +nozzle +nozzles +nuance +nuanced +nuances +nubbier +nubbiest +nubbin +nubbins +nubby +nubile +nubs +nuclear +nucleate +nucleated +nucleates +nucleating +nucleation +nuclei +nucleoli +nucleolus +nucleon +nucleons +nucleus +nucleuses +nude +nuder +nudes +nudest +nudge +nudged +nudges +nudging +nudism +nudist +nudists +nudity +nugatory +nugget +nuggets +nuisance +nuisances +nuke +nuked +nukes +nuking +null +nullification +nullified +nullifies +nullify +nullifying +nullity +numb +numbed +number +numbered +numbering +numberless +numbers +numbest +numbing +numbly +numbness +numbs +numbskull +numbskulls +numerable +numeracy +numeral +numerals +numerate +numerated +numerates +numerating +numeration +numerations +numerator +numerators +numeric +numerical +numerically +numerologist +numerologists +numerology +numerous +numerously +numinous +numismatic +numismatics +numismatist +numismatists +numskull +numskulls +nuncio +nuncios +nunneries +nunnery +nuns +nuptial +nuptials +nurse +nursed +nurseling +nurselings +nursemaid +nursemaids +nurser +nurseries +nursers +nursery +nurseryman +nurserymen +nurses +nursing +nursling +nurslings +nurture +nurtured +nurturer +nurturers +nurtures +nurturing +nutcracker +nutcrackers +nuthatch +nuthatches +nutmeat +nutmeats +nutmeg +nutmegs +nutpick +nutpicks +nutria +nutrias +nutrient +nutrients +nutriment +nutriments +nutrition +nutritional +nutritionally +nutritionist +nutritionists +nutritious +nutritiously +nutritiousness +nutritive +nuts +nutshell +nutshells +nutted +nuttier +nuttiest +nuttiness +nutting +nutty +nuzzle +nuzzled +nuzzler +nuzzlers +nuzzles +nuzzling +nylon +nylons +nymph +nymphet +nymphets +nymphomania +nymphomaniac +nymphomaniacs +nymphs +oafish +oafishly +oafishness +oafs +oaken +oaks +oakum +oared +oaring +oarlock +oarlocks +oars +oarsman +oarsmen +oarswoman +oarswomen +oases +oasis +oatcake +oatcakes +oaten +oath +oaths +oatmeal +oats +obbligati +obbligato +obbligatos +obduracy +obdurate +obdurately +obdurateness +obedience +obedient +obediently +obeisance +obeisances +obeisant +obelisk +obelisks +obese +obesity +obey +obeyed +obeying +obeys +obfuscate +obfuscated +obfuscates +obfuscating +obfuscation +obis +obit +obits +obituaries +obituary +object +objected +objectified +objectifies +objectify +objectifying +objecting +objection +objectionable +objectionably +objections +objective +objectively +objectiveness +objectives +objectivity +objector +objectors +objects +objurgate +objurgated +objurgates +objurgating +objurgation +objurgations +oblate +oblation +oblations +obligate +obligated +obligates +obligating +obligation +obligations +obligatorily +obligatory +oblige +obliged +obliges +obliging +obligingly +oblique +obliquely +obliqueness +obliques +obliquity +obliterate +obliterated +obliterates +obliterating +obliteration +oblivion +oblivious +obliviously +obliviousness +oblong +oblongs +obloquy +obnoxious +obnoxiously +obnoxiousness +oboe +oboes +oboist +oboists +obscene +obscenely +obscener +obscenest +obscenities +obscenity +obscurantism +obscurantist +obscurantists +obscure +obscured +obscurely +obscurer +obscures +obscurest +obscuring +obscurities +obscurity +obsequies +obsequious +obsequiously +obsequiousness +obsequy +observable +observably +observance +observances +observant +observantly +observation +observational +observations +observatories +observatory +observe +observed +observer +observers +observes +observing +obsess +obsessed +obsesses +obsessing +obsession +obsessional +obsessions +obsessive +obsessively +obsessiveness +obsessives +obsidian +obsolesce +obsolesced +obsolescence +obsolescent +obsolesces +obsolescing +obsolete +obsoleted +obsoletes +obsoleting +obstacle +obstacles +obstetric +obstetrical +obstetrician +obstetricians +obstetrics +obstinacy +obstinate +obstinately +obstreperous +obstreperously +obstreperousness +obstruct +obstructed +obstructing +obstruction +obstructionism +obstructionist +obstructionists +obstructions +obstructive +obstructively +obstructiveness +obstructs +obtain +obtainable +obtained +obtaining +obtainment +obtains +obtrude +obtruded +obtrudes +obtruding +obtrusion +obtrusive +obtrusively +obtrusiveness +obtuse +obtusely +obtuseness +obtuser +obtusest +obverse +obverses +obviate +obviated +obviates +obviating +obviation +obvious +obviously +obviousness +ocarina +ocarinas +occasion +occasional +occasionally +occasioned +occasioning +occasions +occidental +occidentals +occlude +occluded +occludes +occluding +occlusion +occlusions +occlusive +occult +occultism +occultist +occultists +occupancy +occupant +occupants +occupation +occupational +occupationally +occupations +occupied +occupier +occupiers +occupies +occupy +occupying +occur +occurred +occurrence +occurrences +occurring +occurs +ocean +oceanfront +oceanfronts +oceangoing +oceanic +oceanographer +oceanographers +oceanographic +oceanography +oceanology +oceans +ocelot +ocelots +ocher +ochre +octagon +octagonal +octagons +octane +octave +octaves +octavo +octavos +octet +octets +octette +octettes +octogenarian +octogenarians +octopi +octopus +octopuses +ocular +oculars +oculist +oculists +odalisque +odalisques +oddball +oddballs +odder +oddest +oddities +oddity +oddly +oddment +oddments +oddness +odds +odes +odious +odiously +odiousness +odium +odometer +odometers +odor +odored +odoriferous +odorless +odorous +odors +odour +odours +odyssey +odysseys +oedipal +oenology +oenophile +oenophiles +oesophagi +oesophagus +oeuvre +oeuvres +offal +offbeat +offbeats +offed +offence +offences +offend +offended +offender +offenders +offending +offends +offense +offenses +offensive +offensively +offensiveness +offensives +offer +offered +offering +offerings +offers +offertories +offertory +offhand +offhanded +offhandedly +offhandedness +office +officeholder +officeholders +officer +officers +offices +official +officialdom +officialism +officially +officials +officiant +officiants +officiate +officiated +officiates +officiating +officiator +officiators +officious +officiously +officiousness +offing +offings +offish +offline +offload +offloaded +offloading +offloads +offprint +offprints +offs +offset +offsets +offsetting +offshoot +offshoots +offshore +offside +offspring +offsprings +offstage +offtrack +often +oftener +oftenest +oftentimes +ofttimes +ogle +ogled +ogler +oglers +ogles +ogling +ogre +ogreish +ogres +ogress +ogresses +ohmmeter +ohmmeters +ohms +oilcloth +oilcloths +oiled +oilier +oiliest +oiliness +oiling +oils +oilskin +oilskins +oily +oink +oinked +oinking +oinks +ointment +ointments +okapi +okapis +okay +okayed +okaying +okays +okra +okras +olden +older +oldest +oldie +oldies +oldish +oldness +oldster +oldsters +oleaginous +oleander +oleanders +oleo +oleomargarin +oleomargarine +oles +olfactories +olfactory +oligarch +oligarchic +oligarchical +oligarchies +oligarchs +oligarchy +oligopolies +oligopoly +olive +olives +ombudsman +ombudsmen +omega +omegas +omelet +omelets +omelette +omelettes +omen +omens +omicron +omicrons +ominous +ominously +ominousness +omission +omissions +omit +omits +omitted +omitting +omnibus +omnibuses +omnibusses +omnipotence +omnipotent +omnipresence +omnipresent +omniscience +omniscient +omnivore +omnivores +omnivorous +omnivorously +omnivorousness +once +oncogene +oncogenes +oncologist +oncologists +oncology +oncoming +oneness +onerous +onerously +onerousness +ones +oneself +onetime +ongoing +onion +onions +onionskin +online +onlooker +onlookers +onlooking +only +onomatopoeia +onomatopoeic +onomatopoetic +onrush +onrushes +onrushing +onscreen +onset +onsets +onshore +onside +onslaught +onslaughts +onstage +onto +ontogeny +ontological +ontology +onus +onuses +onward +onwards +onyx +onyxes +oodles +oohed +oohing +oohs +oops +ooze +oozed +oozes +oozier +ooziest +oozing +oozy +opacity +opal +opalescence +opalescent +opals +opaque +opaqued +opaquely +opaqueness +opaquer +opaques +opaquest +opaquing +oped +open +opened +opener +openers +openest +openhanded +openhandedness +openhearted +opening +openings +openly +openness +opens +openwork +opera +operable +operas +operate +operated +operates +operatic +operatically +operating +operation +operational +operationally +operations +operative +operatives +operator +operators +operetta +operettas +opes +ophthalmic +ophthalmologist +ophthalmologists +ophthalmology +opiate +opiates +opine +opined +opines +oping +opining +opinion +opinionated +opinions +opium +opossum +opossums +opponent +opponents +opportune +opportunely +opportunism +opportunist +opportunistic +opportunistically +opportunists +opportunities +opportunity +oppose +opposed +opposes +opposing +opposite +oppositely +opposites +opposition +oppress +oppressed +oppresses +oppressing +oppression +oppressive +oppressively +oppressiveness +oppressor +oppressors +opprobrious +opprobriously +opprobrium +opted +optic +optical +optically +optician +opticians +optics +optima +optimal +optimally +optimism +optimist +optimistic +optimistically +optimists +optimization +optimize +optimized +optimizes +optimizing +optimum +optimums +opting +option +optional +optionally +optioned +optioning +options +optometrist +optometrists +optometry +opts +opulence +opulent +opulently +opus +opuses +oracle +oracles +oracular +oral +orally +orals +orange +orangeade +orangeades +oranger +orangeries +orangery +oranges +orangest +orangutan +orangutang +orangutangs +orangutans +orate +orated +orates +orating +oration +orations +orator +oratorical +oratorically +oratories +oratorio +oratorios +orators +oratory +orbicular +orbit +orbital +orbitals +orbited +orbiter +orbiters +orbiting +orbits +orbs +orchard +orchards +orchestra +orchestral +orchestras +orchestrate +orchestrated +orchestrates +orchestrating +orchestration +orchestrations +orchid +orchids +ordain +ordained +ordaining +ordainment +ordains +ordeal +ordeals +order +ordered +ordering +orderlies +orderliness +orderly +orders +ordinal +ordinals +ordinance +ordinances +ordinarily +ordinariness +ordinary +ordinate +ordinates +ordination +ordinations +ordnance +ordure +oregano +ores +organ +organdie +organdy +organelle +organelles +organic +organically +organics +organism +organismic +organisms +organist +organists +organization +organizational +organizationally +organizations +organize +organized +organizer +organizers +organizes +organizing +organs +organza +orgasm +orgasmic +orgasms +orgiastic +orgies +orgy +oriel +oriels +orient +oriental +orientals +orientate +orientated +orientates +orientating +orientation +orientations +oriented +orienting +orients +orifice +orifices +origami +origin +original +originality +originally +originals +originate +originated +originates +originating +origination +originator +originators +origins +oriole +orioles +orison +orisons +ormolu +ornament +ornamental +ornamentation +ornamented +ornamenting +ornaments +ornate +ornately +ornateness +ornerier +orneriest +orneriness +ornery +ornithological +ornithologist +ornithologists +ornithology +orotund +orotundities +orotundity +orphan +orphanage +orphanages +orphaned +orphaning +orphans +orris +orrises +orthodontia +orthodontic +orthodontics +orthodontist +orthodontists +orthodox +orthodoxies +orthodoxy +orthographic +orthographically +orthographies +orthography +orthopaedic +orthopaedics +orthopaedist +orthopaedists +orthopedic +orthopedics +orthopedist +orthopedists +orzo +oscillate +oscillated +oscillates +oscillating +oscillation +oscillations +oscillator +oscillators +oscillatory +oscilloscope +oscilloscopes +osculate +osculated +osculates +osculating +osculation +osculations +osier +osiers +osmium +osmosis +osmotic +osprey +ospreys +ossification +ossified +ossifies +ossify +ossifying +ostensible +ostensibly +ostentation +ostentatious +ostentatiously +osteoarthritis +osteopath +osteopathic +osteopaths +osteopathy +osteoporosis +ostracism +ostracize +ostracized +ostracizes +ostracizing +ostrich +ostriches +other +others +otherwise +otherworldly +otiose +otter +otters +ottoman +ottomans +oubliette +oubliettes +ouch +ought +ounce +ounces +ours +ourselves +oust +ousted +ouster +ousters +ousting +ousts +outage +outages +outargue +outargued +outargues +outarguing +outback +outbacks +outbalance +outbalanced +outbalances +outbalancing +outbid +outbidding +outbids +outboard +outboards +outboast +outboasted +outboasting +outboasts +outbound +outbox +outboxed +outboxes +outboxing +outbreak +outbreaks +outbuilding +outbuildings +outburst +outbursts +outcast +outcasts +outclass +outclassed +outclasses +outclassing +outcome +outcomes +outcries +outcrop +outcropped +outcropping +outcroppings +outcrops +outcry +outdated +outdid +outdistance +outdistanced +outdistances +outdistancing +outdo +outdoes +outdoing +outdone +outdoor +outdoors +outdoorsy +outdraw +outdrawing +outdrawn +outdraws +outdrew +outed +outer +outermost +outerwear +outface +outfaced +outfaces +outfacing +outfield +outfielder +outfielders +outfields +outfight +outfighting +outfights +outfit +outfits +outfitted +outfitter +outfitters +outfitting +outflank +outflanked +outflanking +outflanks +outflow +outflows +outfought +outfox +outfoxed +outfoxes +outfoxing +outgo +outgoes +outgoing +outgrew +outgrow +outgrowing +outgrown +outgrows +outgrowth +outgrowths +outguess +outguessed +outguesses +outguessing +outgun +outgunned +outgunning +outguns +outhit +outhits +outhitting +outhouse +outhouses +outing +outings +outlaid +outlandish +outlandishly +outlandishness +outlast +outlasted +outlasting +outlasts +outlaw +outlawed +outlawing +outlaws +outlay +outlaying +outlays +outlet +outlets +outline +outlined +outlines +outlining +outlive +outlived +outlives +outliving +outlook +outlooks +outlying +outmaneuver +outmaneuvered +outmaneuvering +outmaneuvers +outmanoeuvre +outmanoeuvred +outmanoeuvres +outmanoeuvring +outmatch +outmatched +outmatches +outmatching +outmoded +outnumber +outnumbered +outnumbering +outnumbers +outpatient +outpatients +outperform +outperformed +outperforming +outperforms +outplace +outplaced +outplacement +outplaces +outplacing +outplay +outplayed +outplaying +outplays +outpoint +outpointed +outpointing +outpoints +outpost +outposts +outpouring +outpourings +outproduce +outproduced +outproduces +outproducing +output +outputs +outputted +outputting +outrace +outraced +outraces +outracing +outrage +outraged +outrageous +outrageously +outrages +outraging +outran +outrank +outranked +outranking +outranks +outre +outreach +outreached +outreaches +outreaching +outrider +outriders +outrigger +outriggers +outright +outrun +outrunning +outruns +outs +outscore +outscored +outscores +outscoring +outsell +outselling +outsells +outset +outsets +outshine +outshined +outshines +outshining +outshone +outshout +outshouted +outshouting +outshouts +outside +outsider +outsiders +outsides +outsize +outsized +outsizes +outskirt +outskirts +outsmart +outsmarted +outsmarting +outsmarts +outsold +outsource +outsourced +outsources +outsourcing +outspend +outspending +outspends +outspent +outspoken +outspokenly +outspokenness +outspread +outspreading +outspreads +outstanding +outstandingly +outstation +outstations +outstay +outstayed +outstaying +outstays +outstretch +outstretched +outstretches +outstretching +outstrip +outstripped +outstripping +outstrips +outstript +outtake +outtakes +outvote +outvoted +outvotes +outvoting +outward +outwardly +outwards +outwear +outwearing +outwears +outweigh +outweighed +outweighing +outweighs +outwit +outwits +outwitted +outwitting +outwore +outwork +outworked +outworking +outworks +outworn +ouzo +oval +ovals +ovarian +ovaries +ovary +ovate +ovation +ovations +oven +ovenbird +ovenbirds +ovens +over +overabundance +overabundant +overachieve +overachieved +overachiever +overachievers +overachieves +overachieving +overact +overacted +overacting +overactive +overacts +overage +overages +overaggressive +overall +overalls +overambitious +overanxious +overarm +overarmed +overarming +overarms +overate +overattentive +overawe +overawed +overawes +overawing +overbalance +overbalanced +overbalances +overbalancing +overbear +overbearing +overbearingly +overbears +overbid +overbidding +overbids +overbite +overbites +overblown +overboard +overbold +overbook +overbooked +overbooking +overbooks +overbore +overborne +overbought +overbuild +overbuilding +overbuilds +overbuilt +overburden +overburdened +overburdening +overburdens +overbuy +overbuying +overbuys +overcame +overcapacity +overcapitalize +overcapitalized +overcapitalizes +overcapitalizing +overcareful +overcast +overcasting +overcasts +overcautious +overcharge +overcharged +overcharges +overcharging +overcloud +overclouded +overclouding +overclouds +overcoat +overcoats +overcome +overcomes +overcoming +overcompensate +overcompensated +overcompensates +overcompensating +overcompensation +overconfidence +overconfident +overconscientious +overcook +overcooked +overcooking +overcooks +overcritical +overcrowd +overcrowded +overcrowding +overcrowds +overdecorate +overdecorated +overdecorates +overdecorating +overdependent +overdevelop +overdeveloped +overdeveloping +overdevelops +overdid +overdo +overdoes +overdoing +overdone +overdose +overdosed +overdoses +overdosing +overdraft +overdrafts +overdraw +overdrawing +overdrawn +overdraws +overdress +overdressed +overdresses +overdressing +overdrew +overdrive +overdub +overdubbed +overdubbing +overdubs +overdue +overeager +overeat +overeaten +overeating +overeats +overemotional +overemphasis +overemphasize +overemphasized +overemphasizes +overemphasizing +overenthusiastic +overestimate +overestimated +overestimates +overestimating +overestimation +overexcite +overexcited +overexcites +overexciting +overexercise +overexercised +overexercises +overexercising +overexert +overexerted +overexerting +overexertion +overexerts +overexpose +overexposed +overexposes +overexposing +overexposure +overextend +overextended +overextending +overextends +overfed +overfeed +overfeeding +overfeeds +overfill +overfilled +overfilling +overfills +overflew +overflies +overflight +overflights +overflow +overflowed +overflowing +overflown +overflows +overfly +overflying +overfond +overfull +overgeneralize +overgeneralized +overgeneralizes +overgeneralizing +overgenerous +overgraze +overgrazed +overgrazes +overgrazing +overgrew +overgrow +overgrowing +overgrown +overgrows +overgrowth +overhand +overhanded +overhands +overhang +overhanging +overhangs +overhasty +overhaul +overhauled +overhauling +overhauls +overhead +overheads +overhear +overheard +overhearing +overhears +overheat +overheated +overheating +overheats +overhung +overindulge +overindulged +overindulgence +overindulgent +overindulges +overindulging +overjoy +overjoyed +overjoying +overjoys +overkill +overladen +overlaid +overlain +overland +overlap +overlapped +overlapping +overlaps +overlarge +overlay +overlaying +overlays +overleaf +overlie +overlies +overload +overloaded +overloading +overloads +overlong +overlook +overlooked +overlooking +overlooks +overlord +overlords +overly +overlying +overmaster +overmastered +overmastering +overmasters +overmodest +overmuch +overnice +overnight +overnights +overoptimism +overoptimistic +overpaid +overparticular +overpass +overpasses +overpay +overpaying +overpays +overplay +overplayed +overplaying +overplays +overpopulate +overpopulated +overpopulates +overpopulating +overpopulation +overpower +overpowered +overpowering +overpoweringly +overpowers +overpraise +overpraised +overpraises +overpraising +overprecise +overprice +overpriced +overprices +overpricing +overprint +overprinted +overprinting +overprints +overproduce +overproduced +overproduces +overproducing +overproduction +overprotect +overprotected +overprotecting +overprotective +overprotects +overqualified +overran +overrate +overrated +overrates +overrating +overreach +overreached +overreaches +overreaching +overreact +overreacted +overreacting +overreaction +overreactions +overreacts +overrefined +overridden +override +overrides +overriding +overripe +overrode +overrule +overruled +overrules +overruling +overrun +overrunning +overruns +overs +oversaw +oversea +overseas +oversee +overseeing +overseen +overseer +overseers +oversees +oversell +overselling +oversells +oversensitive +oversensitiveness +oversexed +overshadow +overshadowed +overshadowing +overshadows +overshoe +overshoes +overshoot +overshooting +overshoots +overshot +oversight +oversights +oversimple +oversimplification +oversimplifications +oversimplified +oversimplifies +oversimplify +oversimplifying +oversize +oversized +oversleep +oversleeping +oversleeps +overslept +oversold +overspecialization +overspecialize +overspecialized +overspecializes +overspecializing +overspend +overspending +overspends +overspent +overspill +overspilled +overspilling +overspills +overspilt +overspread +overspreading +overspreads +overstate +overstated +overstatement +overstatements +overstates +overstating +overstay +overstayed +overstaying +overstays +overstep +overstepped +overstepping +oversteps +overstimulate +overstimulated +overstimulates +overstimulating +overstock +overstocked +overstocking +overstocks +overstrict +overstrung +overstuffed +oversubscribe +oversubscribed +oversubscribes +oversubscribing +oversubtle +oversupplied +oversupplies +oversupply +oversupplying +oversuspicious +overt +overtake +overtaken +overtakes +overtaking +overtax +overtaxed +overtaxes +overtaxing +overthrew +overthrow +overthrowing +overthrown +overthrows +overtime +overtimes +overtire +overtired +overtires +overtiring +overtly +overtone +overtones +overtook +overture +overtures +overturn +overturned +overturning +overturns +overuse +overused +overuses +overusing +overvalue +overvalued +overvalues +overvaluing +overview +overviews +overweening +overweight +overwhelm +overwhelmed +overwhelming +overwhelmingly +overwhelms +overwinter +overwintered +overwintering +overwinters +overwork +overworked +overworking +overworks +overwrought +overzealous +oviduct +oviducts +oviparous +ovoid +ovoids +ovular +ovulate +ovulated +ovulates +ovulating +ovulation +ovule +ovules +ovum +owed +owes +owing +owlet +owlets +owlish +owlishly +owls +owned +owner +owners +ownership +owning +owns +oxblood +oxbow +oxbows +oxcart +oxcarts +oxen +oxford +oxfords +oxidant +oxidants +oxidation +oxide +oxides +oxidization +oxidize +oxidized +oxidizer +oxidizers +oxidizes +oxidizing +oxyacetylene +oxygen +oxygenate +oxygenated +oxygenates +oxygenating +oxygenation +oxymora +oxymoron +oxymorons +oyster +oysters +ozone +pablum +pabulum +pace +paced +pacemaker +pacemakers +pacer +pacers +paces +pacesetter +pacesetters +pachyderm +pachyderms +pachysandra +pachysandras +pacific +pacifically +pacification +pacified +pacifier +pacifiers +pacifies +pacifism +pacifist +pacifistic +pacifists +pacify +pacifying +pacing +pack +package +packaged +packager +packagers +packages +packaging +packed +packer +packers +packet +packets +packing +packinghouse +packinghouses +packs +packsaddle +packsaddles +pact +pacts +padded +paddies +padding +paddle +paddled +paddler +paddlers +paddles +paddling +paddock +paddocked +paddocking +paddocks +paddy +padlock +padlocked +padlocking +padlocks +padre +padres +pads +paean +paeans +paediatric +paediatrician +paediatricians +paediatrics +paella +pagan +paganism +pagans +page +pageant +pageantry +pageants +pageboy +pageboys +paged +pager +pagers +pages +paginate +paginated +paginates +paginating +pagination +paging +pagoda +pagodas +paid +pail +pailful +pailfuls +pails +pailsful +pain +pained +painful +painfuller +painfullest +painfully +painfulness +paining +painkiller +painkillers +painkilling +painless +painlessly +painlessness +pains +painstaking +painstakingly +paint +paintbox +paintboxes +paintbrush +paintbrushes +painted +painter +painters +painting +paintings +paints +pair +paired +pairing +pairs +paisley +paisleys +pajama +pajamas +palace +palaces +paladin +paladins +palanquin +palanquins +palatable +palatal +palatalization +palatalize +palatalized +palatalizes +palatalizing +palatals +palate +palates +palatial +palatially +palatinate +palatinates +palatine +palatines +palaver +palavered +palavering +palavers +pale +paled +paleface +palefaces +palely +paleness +paleographer +paleographers +paleography +paleolithic +paleontologist +paleontologists +paleontology +paler +pales +palest +palette +palettes +palfrey +palfreys +palimony +palimpsest +palimpsests +palindrome +palindromes +paling +palings +palisade +palisades +palish +pall +palladium +pallbearer +pallbearers +palled +pallet +pallets +palliate +palliated +palliates +palliating +palliation +palliative +palliatives +pallid +pallider +pallidest +pallidly +pallidness +palling +pallor +palls +palm +palmate +palmed +palmetto +palmettoes +palmettos +palmier +palmiest +palming +palmist +palmistry +palmists +palms +palmtop +palmtops +palmy +palomino +palominos +palpable +palpably +palpate +palpated +palpates +palpating +palpation +palpitate +palpitated +palpitates +palpitating +palpitation +palpitations +pals +palsied +palsies +palsy +palsying +paltrier +paltriest +paltriness +paltry +pampas +pamper +pampered +pampering +pampers +pamphlet +pamphleted +pamphleteer +pamphleteers +pamphleting +pamphlets +panacea +panaceas +panache +panama +panamas +pancake +pancaked +pancakes +pancaking +panchromatic +pancreas +pancreases +pancreatic +panda +pandas +pandemic +pandemics +pandemonium +pander +pandered +panderer +panderers +pandering +panders +pane +panegyric +panegyrics +panel +paneled +paneling +panelist +panelists +panelled +panelling +panels +panes +pang +pangs +panhandle +panhandled +panhandler +panhandlers +panhandles +panhandling +panic +panicked +panicking +panicky +panics +panier +paniers +panned +pannier +panniers +panning +panoplies +panoply +panorama +panoramas +panoramic +panpipes +pans +pansies +pansy +pant +pantaloons +panted +pantheism +pantheist +pantheistic +pantheists +pantheon +pantheons +panther +panthers +pantie +panties +panting +pantomime +pantomimed +pantomimes +pantomimic +pantomiming +pantomimist +pantomimists +pantries +pantry +pants +pantsuit +pantsuits +panty +pantyhose +pantyliner +pantyliners +pantywaist +pantywaists +papa +papacies +papacy +papal +paparazzi +papas +papaw +papaws +papaya +papayas +paper +paperback +paperbacks +paperboard +paperboy +paperboys +papered +paperer +paperers +papergirl +papergirls +paperhanger +paperhangers +paperhanging +papering +papers +paperweight +paperweights +paperwork +papery +papilla +papillae +papillary +papist +papists +papoose +papooses +pappies +pappy +paprika +paps +papyri +papyrus +papyruses +para +parable +parables +parabola +parabolas +parabolic +parachute +parachuted +parachutes +parachuting +parachutist +parachutists +parade +paraded +parader +paraders +parades +paradigm +paradigmatic +paradigms +parading +paradisaical +paradise +paradises +paradox +paradoxes +paradoxical +paradoxically +paraffin +paragon +paragons +paragraph +paragraphed +paragraphing +paragraphs +parakeet +parakeets +paralegal +paralegals +parallax +parallaxes +parallel +paralleled +paralleling +parallelism +parallelisms +parallelled +parallelling +parallelogram +parallelograms +parallels +paralyse +paralysed +paralyses +paralysing +paralysis +paralytic +paralytics +paralyze +paralyzed +paralyzes +paralyzing +paralyzingly +paramecia +paramecium +parameciums +paramedic +paramedical +paramedicals +paramedics +parameter +parameters +parametric +paramilitaries +paramilitary +paramount +paramour +paramours +paranoia +paranoiac +paranoiacs +paranoid +paranoids +paranormal +parapet +parapets +paraphernalia +paraphrase +paraphrased +paraphrases +paraphrasing +paraplegia +paraplegic +paraplegics +paraprofessional +paraprofessionals +parapsychologist +parapsychologists +parapsychology +paraquat +paras +parasite +parasites +parasitic +parasitically +parasitism +parasol +parasols +parasympathetic +parathion +parathyroid +parathyroids +paratrooper +paratroopers +paratroops +paratyphoid +parboil +parboiled +parboiling +parboils +parcel +parceled +parceling +parcelled +parcelling +parcels +parch +parched +parches +parching +parchment +parchments +pardon +pardonable +pardonably +pardoned +pardoner +pardoners +pardoning +pardons +pare +pared +paregoric +parent +parentage +parental +parented +parentheses +parenthesis +parenthesize +parenthesized +parenthesizes +parenthesizing +parenthetic +parenthetical +parenthetically +parenthood +parenting +parents +parer +parers +pares +paresis +parfait +parfaits +pariah +pariahs +parietal +parimutuel +parimutuels +paring +parings +parish +parishes +parishioner +parishioners +parity +park +parka +parkas +parked +parking +parks +parkway +parkways +parlance +parlay +parlayed +parlaying +parlays +parley +parleyed +parleying +parleys +parliament +parliamentarian +parliamentarians +parliamentary +parliaments +parlor +parlors +parlour +parlours +parlous +parmigiana +parmigiano +parochial +parochialism +parochially +parodied +parodies +parodist +parodists +parody +parodying +parole +paroled +parolee +parolees +paroles +paroling +paroxysm +paroxysmal +paroxysms +parquet +parqueted +parqueting +parquetry +parquets +parrakeet +parrakeets +parred +parricidal +parricide +parricides +parried +parries +parring +parrot +parroted +parroting +parrots +parry +parrying +pars +parse +parsec +parsecs +parsed +parses +parsimonious +parsimoniously +parsimony +parsing +parsley +parsnip +parsnips +parson +parsonage +parsonages +parsons +part +partake +partaken +partaker +partakers +partakes +partaking +parted +parterre +parterres +parthenogenesis +partial +partiality +partially +partials +participant +participants +participate +participated +participates +participating +participation +participator +participators +participatory +participial +participle +participles +particle +particleboard +particles +particular +particularities +particularity +particularization +particularize +particularized +particularizes +particularizing +particularly +particulars +particulate +particulates +partied +parties +parting +partings +partisan +partisans +partisanship +partition +partitioned +partitioning +partitions +partitive +partitives +partizan +partizans +partly +partner +partnered +partnering +partners +partnership +partnerships +partook +partridge +partridges +parts +parturition +partway +party +partying +parvenu +parvenus +pascal +pascals +paschal +pasha +pashas +pass +passable +passably +passage +passages +passageway +passageways +passbook +passbooks +passe +passed +passel +passels +passenger +passengers +passer +passerby +passers +passersby +passes +passim +passing +passingly +passion +passionate +passionately +passionflower +passionflowers +passionless +passions +passive +passively +passiveness +passives +passivity +passkey +passkeys +passport +passports +password +passwords +past +pasta +pastas +paste +pasteboard +pasted +pastel +pastels +pastern +pasterns +pastes +pasteurization +pasteurize +pasteurized +pasteurizer +pasteurizers +pasteurizes +pasteurizing +pastiche +pastiches +pastier +pasties +pastiest +pastille +pastilles +pastime +pastimes +pastiness +pasting +pastor +pastoral +pastorals +pastorate +pastorates +pastors +pastrami +pastries +pastry +pasts +pasturage +pasture +pastured +pastureland +pastures +pasturing +pasty +patch +patched +patches +patchier +patchiest +patchily +patchiness +patching +patchwork +patchworks +patchy +pate +patella +patellae +patellas +patent +patented +patenting +patently +patents +paterfamilias +paterfamiliases +paternal +paternalism +paternalistic +paternally +paternity +paternoster +paternosters +pates +path +pathetic +pathetically +pathfinder +pathfinders +pathless +pathogen +pathogenic +pathogens +pathological +pathologically +pathologies +pathologist +pathologists +pathology +pathos +paths +pathway +pathways +patience +patient +patienter +patientest +patiently +patients +patina +patinae +patinas +patio +patios +patois +patresfamilias +patriarch +patriarchal +patriarchate +patriarchates +patriarchies +patriarchs +patriarchy +patrician +patricians +patricide +patricides +patrimonial +patrimonies +patrimony +patriot +patriotic +patriotically +patriotism +patriots +patrol +patrolled +patrolling +patrolman +patrolmen +patrols +patrolwoman +patrolwomen +patron +patronage +patroness +patronesses +patronize +patronized +patronizer +patronizers +patronizes +patronizing +patronizingly +patrons +patronymic +patronymically +patronymics +patroon +patroons +pats +patsies +patsy +patted +patter +pattered +pattering +pattern +patterned +patterning +patterns +patters +patties +patting +patty +paucity +paunch +paunches +paunchier +paunchiest +paunchy +pauper +pauperism +pauperize +pauperized +pauperizes +pauperizing +paupers +pause +paused +pauses +pausing +pave +paved +pavement +pavements +paves +pavilion +pavilions +paving +pavings +pawed +pawing +pawl +pawls +pawn +pawnbroker +pawnbrokers +pawnbroking +pawned +pawning +pawns +pawnshop +pawnshops +pawpaw +pawpaws +paws +payable +payback +paybacks +paycheck +paychecks +payday +paydays +payed +payee +payees +payer +payers +paying +payload +payloads +paymaster +paymasters +payment +payments +payoff +payoffs +payola +payout +payouts +payroll +payrolls +pays +payslip +payslips +peace +peaceable +peaceably +peaceful +peacefully +peacefulness +peacekeeper +peacekeepers +peacekeeping +peacemaker +peacemakers +peacemaking +peaces +peacetime +peach +peaches +peachier +peachiest +peachy +peacock +peacocks +peafowl +peafowls +peahen +peahens +peak +peaked +peaking +peaks +peal +pealed +pealing +peals +peanut +peanuts +pear +pearl +pearled +pearlier +pearliest +pearling +pearls +pearly +pears +peas +peasant +peasantry +peasants +pease +peashooter +peashooters +peat +peatier +peatiest +peaty +pebble +pebbled +pebbles +pebblier +pebbliest +pebbling +pebbly +pecan +pecans +peccadillo +peccadilloes +peccadillos +peccaries +peccary +peck +pecked +pecking +pecks +pectic +pectin +pectoral +pectorals +peculate +peculated +peculates +peculating +peculation +peculator +peculators +peculiar +peculiarities +peculiarity +peculiarly +pecuniary +pedagog +pedagogic +pedagogical +pedagogs +pedagogue +pedagogues +pedagogy +pedal +pedaled +pedaling +pedalled +pedalling +pedals +pedant +pedantic +pedantically +pedantry +pedants +peddle +peddled +peddler +peddlers +peddles +peddling +pederast +pederasts +pederasty +pedestal +pedestals +pedestrian +pedestrianize +pedestrianized +pedestrianizes +pedestrianizing +pedestrians +pediatric +pediatrician +pediatricians +pediatrics +pedicab +pedicabs +pedicure +pedicured +pedicures +pedicuring +pedicurist +pedicurists +pedigree +pedigreed +pedigrees +pediment +pediments +pedlar +pedlars +pedometer +pedometers +peduncle +peduncles +peed +peeing +peek +peekaboo +peeked +peeking +peeks +peel +peeled +peeler +peelers +peeling +peelings +peels +peen +peens +peep +peeped +peeper +peepers +peephole +peepholes +peeping +peeps +peepshow +peepshows +peer +peerage +peerages +peered +peeress +peeresses +peering +peerless +peers +pees +peeve +peeved +peeves +peeving +peevish +peevishly +peevishness +peewee +peewees +pegboard +pegboards +pegged +pegging +pegs +peignoir +peignoirs +pejoration +pejorative +pejoratively +pejoratives +peke +pekes +pekinese +pekingese +pekoe +pelagic +pelf +pelican +pelicans +pellagra +pellet +pelleted +pelleting +pellets +pellmell +pellucid +pelt +pelted +pelting +pelts +pelves +pelvic +pelvis +pelvises +pemmican +penal +penalization +penalize +penalized +penalizes +penalizing +penalties +penalty +penance +penances +pence +penchant +penchants +pencil +penciled +penciling +pencilled +pencilling +pencils +pend +pendant +pendants +pended +pendent +pendents +pending +pends +pendulous +pendulum +pendulums +penes +penetrability +penetrable +penetrate +penetrated +penetrates +penetrating +penetratingly +penetration +penetrations +penetrative +penguin +penguins +penicillin +penile +peninsula +peninsular +peninsulas +penis +penises +penitence +penitent +penitential +penitentiaries +penitentiary +penitently +penitents +penknife +penknives +penlight +penlights +penlite +penlites +penman +penmanship +penmen +pennant +pennants +penned +pennies +penniless +penning +pennon +pennons +penny +pennyweight +pennyweights +penologist +penologists +penology +pens +pension +pensionable +pensioned +pensioner +pensioners +pensioning +pensions +pensive +pensively +pensiveness +pent +pentacle +pentacles +pentagon +pentagonal +pentagons +pentagram +pentagrams +pentameter +pentameters +pentathlete +pentathletes +pentathlon +pentathlons +penthouse +penthouses +penuche +penultimate +penultimates +penumbra +penumbrae +penumbras +penurious +penuriously +penuriousness +penury +peon +peonage +peonies +peons +peony +people +peopled +peoples +peopling +pepped +pepper +peppercorn +peppercorns +peppered +peppering +peppermint +peppermints +pepperoni +pepperonis +peppers +peppery +peppier +peppiest +peppiness +pepping +peppy +peps +pepsin +peptic +peptics +peradventure +perambulate +perambulated +perambulates +perambulating +perambulation +perambulations +perambulator +perambulators +percale +percales +perceivable +perceive +perceived +perceives +perceiving +percent +percentage +percentages +percentile +percentiles +percents +perceptible +perceptibly +perception +perceptional +perceptions +perceptive +perceptively +perceptiveness +perceptual +perceptually +perch +perchance +perched +perches +perching +percipience +percipient +percolate +percolated +percolates +percolating +percolation +percolator +percolators +percussion +percussionist +percussionists +perdition +perdurable +peregrinate +peregrinated +peregrinates +peregrinating +peregrination +peregrinations +peregrine +peregrines +peremptorily +peremptory +perennial +perennially +perennials +perestroika +perfect +perfecta +perfectas +perfected +perfecter +perfectest +perfectibility +perfectible +perfecting +perfection +perfectionism +perfectionist +perfectionists +perfections +perfectly +perfectness +perfects +perfidies +perfidious +perfidiously +perfidy +perforate +perforated +perforates +perforating +perforation +perforations +perforce +perform +performance +performances +performed +performer +performers +performing +performs +perfume +perfumed +perfumer +perfumeries +perfumers +perfumery +perfumes +perfuming +perfunctorily +perfunctory +pergola +pergolas +perhaps +pericardia +pericardium +pericardiums +perigee +perigees +perihelia +perihelion +perihelions +peril +periled +periling +perilled +perilling +perilous +perilously +perils +perimeter +perimeters +perinea +perineum +period +periodic +periodical +periodically +periodicals +periodicity +periodontal +periodontics +periodontist +periodontists +periods +peripatetic +peripatetics +peripheral +peripherally +peripherals +peripheries +periphery +periphrases +periphrasis +periphrastic +periscope +periscopes +perish +perishable +perishables +perished +perishes +perishing +peristalsis +peristaltic +peristyle +peristyles +peritonea +peritoneal +peritoneum +peritoneums +peritonitis +periwig +periwigs +periwinkle +periwinkles +perjure +perjured +perjurer +perjurers +perjures +perjuries +perjuring +perjury +perk +perked +perkier +perkiest +perkily +perkiness +perking +perks +perky +perm +permafrost +permanence +permanency +permanent +permanently +permanents +permeability +permeable +permeate +permeated +permeates +permeating +permeation +permed +perming +permissible +permissibly +permission +permissive +permissively +permissiveness +permit +permits +permitted +permitting +perms +permutation +permutations +permute +permuted +permutes +permuting +pernicious +perniciously +perniciousness +pernickety +peroration +perorations +peroxide +peroxided +peroxides +peroxiding +perpendicular +perpendicularity +perpendicularly +perpendiculars +perpetrate +perpetrated +perpetrates +perpetrating +perpetration +perpetrator +perpetrators +perpetual +perpetually +perpetuals +perpetuate +perpetuated +perpetuates +perpetuating +perpetuation +perpetuity +perplex +perplexed +perplexedly +perplexes +perplexing +perplexities +perplexity +perquisite +perquisites +persecute +persecuted +persecutes +persecuting +persecution +persecutions +persecutor +persecutors +perseverance +persevere +persevered +perseveres +persevering +persiflage +persimmon +persimmons +persist +persisted +persistence +persistent +persistently +persisting +persists +persnickety +person +persona +personable +personae +personage +personages +personal +personalities +personality +personalize +personalized +personalizes +personalizing +personally +personals +personalty +personas +personification +personifications +personified +personifies +personify +personifying +personnel +persons +perspective +perspectives +perspicacious +perspicaciously +perspicacity +perspicuity +perspicuous +perspiration +perspire +perspired +perspires +perspiring +persuadable +persuade +persuaded +persuader +persuaders +persuades +persuading +persuasion +persuasions +persuasive +persuasively +persuasiveness +pert +pertain +pertained +pertaining +pertains +perter +pertest +pertinacious +pertinaciously +pertinacity +pertinence +pertinent +pertinently +pertly +pertness +perturb +perturbation +perturbations +perturbed +perturbing +perturbs +pertussis +peruke +perukes +perusal +perusals +peruse +perused +peruses +perusing +pervade +pervaded +pervades +pervading +pervasive +pervasively +pervasiveness +perverse +perversely +perverseness +perversion +perversions +perversity +pervert +perverted +perverting +perverts +peseta +pesetas +peskier +peskiest +peskily +peskiness +pesky +peso +pesos +pessimism +pessimist +pessimistic +pessimistically +pessimists +pest +pester +pestered +pestering +pesters +pesticide +pesticides +pestiferous +pestilence +pestilences +pestilent +pestilential +pestle +pestled +pestles +pestling +pesto +pests +petal +petaled +petals +petard +petards +petcock +petcocks +peter +petered +petering +peters +petiole +petioles +petite +petites +petition +petitioned +petitioner +petitioners +petitioning +petitions +petrel +petrels +petrifaction +petrified +petrifies +petrify +petrifying +petrochemical +petrochemicals +petrodollar +petrodollars +petrol +petrolatum +petroleum +petrologist +petrologists +petrology +pets +petted +petticoat +petticoats +pettier +pettiest +pettifog +pettifogged +pettifogger +pettifoggers +pettifoggery +pettifogging +pettifogs +pettily +pettiness +petting +pettish +pettishly +petty +petulance +petulant +petulantly +petunia +petunias +pewee +pewees +pewit +pewits +pews +pewter +pewters +peyote +pfennig +pfennige +pfennigs +phaeton +phaetons +phagocyte +phagocytes +phalanger +phalangers +phalanges +phalanx +phalanxes +phalli +phallic +phallus +phalluses +phantasied +phantasies +phantasm +phantasmagoria +phantasmagorias +phantasmal +phantasms +phantasy +phantasying +phantom +phantoms +pharaoh +pharaohs +pharisaic +pharisee +pharisees +pharmaceutic +pharmaceutical +pharmaceuticals +pharmaceutics +pharmacies +pharmacist +pharmacists +pharmacological +pharmacologist +pharmacologists +pharmacology +pharmacopeia +pharmacopeias +pharmacopoeia +pharmacopoeias +pharmacy +pharyngeal +pharynges +pharyngitis +pharynx +pharynxes +phase +phased +phaseout +phaseouts +phases +phasing +phat +phatter +phattest +pheasant +pheasants +phenacetin +phenobarbital +phenol +phenom +phenomena +phenomenal +phenomenally +phenomenon +phenomenons +phenoms +pheromone +pheromones +phew +phial +phials +philander +philandered +philanderer +philanderers +philandering +philanders +philanthropic +philanthropically +philanthropies +philanthropist +philanthropists +philanthropy +philatelic +philatelist +philatelists +philately +philharmonic +philharmonics +philippic +philippics +philistine +philistines +philistinism +philodendra +philodendron +philodendrons +philological +philologist +philologists +philology +philosopher +philosophers +philosophic +philosophical +philosophically +philosophies +philosophize +philosophized +philosophizer +philosophizers +philosophizes +philosophizing +philosophy +philter +philters +philtre +philtres +phis +phlebitis +phlegm +phlegmatic +phlegmatically +phloem +phlox +phloxes +phobia +phobias +phobic +phobics +phoebe +phoebes +phoenix +phoenixes +phone +phoned +phoneme +phonemes +phonemic +phonemically +phones +phonetic +phonetically +phonetician +phoneticians +phonetics +phoney +phoneyed +phoneying +phoneys +phonic +phonically +phonics +phonied +phonier +phonies +phoniest +phoniness +phoning +phonograph +phonographic +phonographs +phonological +phonologically +phonologist +phonologists +phonology +phony +phonying +phooey +phosphate +phosphates +phosphor +phosphorescence +phosphorescent +phosphorescently +phosphoric +phosphorous +phosphors +phosphorus +photo +photocell +photocells +photocopied +photocopier +photocopiers +photocopies +photocopy +photocopying +photoed +photoelectric +photoelectrically +photoengrave +photoengraved +photoengraver +photoengravers +photoengraves +photoengraving +photoengravings +photofinishing +photogenic +photogenically +photograph +photographed +photographer +photographers +photographic +photographically +photographing +photographs +photography +photoing +photojournalism +photojournalist +photojournalists +photometer +photometers +photon +photons +photos +photosensitive +photostat +photostated +photostatic +photostating +photostats +photostatted +photostatting +photosynthesis +photosynthesize +photosynthesized +photosynthesizes +photosynthesizing +photosynthetic +phrasal +phrase +phrased +phraseology +phrases +phrasing +phrasings +phrenetic +phrenologist +phrenologists +phrenology +phyla +phylacteries +phylactery +phylogeny +phylum +physic +physical +physically +physicals +physician +physicians +physicist +physicists +physicked +physicking +physics +physiognomies +physiognomy +physiography +physiologic +physiological +physiologically +physiologist +physiologists +physiology +physiotherapist +physiotherapists +physiotherapy +physique +physiques +pianissimi +pianissimo +pianissimos +pianist +pianists +piano +pianoforte +pianofortes +pianos +piaster +piasters +piastre +piastres +piazza +piazzas +piazze +pibroch +pibrochs +pica +picador +picadors +picaresque +picayune +piccalilli +piccolo +piccolos +pick +pickaback +pickabacked +pickabacking +pickabacks +pickax +pickaxe +pickaxed +pickaxes +pickaxing +picked +picker +pickerel +pickerels +pickers +picket +picketed +picketing +pickets +pickier +pickiest +picking +pickings +pickle +pickled +pickles +pickling +pickpocket +pickpockets +picks +pickup +pickups +picky +picnic +picnicked +picnicker +picnickers +picnicking +picnics +picot +picots +pics +pictograph +pictographs +pictorial +pictorially +pictorials +picture +pictured +pictures +picturesque +picturesquely +picturesqueness +picturing +piddle +piddled +piddles +piddling +piddly +pidgin +pidgins +piebald +piebalds +piece +pieced +piecemeal +pieces +piecework +pieceworker +pieceworkers +piecing +pied +pieing +pier +pierce +pierced +pierces +piercing +piercingly +piercings +piers +pies +piety +piffle +piffling +pigeon +pigeonhole +pigeonholed +pigeonholes +pigeonholing +pigeons +pigged +piggier +piggies +piggiest +pigging +piggish +piggishly +piggishness +piggy +piggyback +piggybacked +piggybacking +piggybacks +pigheaded +pigheadedly +pigheadedness +piglet +piglets +pigment +pigmentation +pigments +pigmies +pigmy +pigpen +pigpens +pigs +pigskin +pigskins +pigsties +pigsty +pigtail +pigtails +piing +pike +piked +piker +pikers +pikes +pikestaff +pikestaffs +pikestaves +piking +pilaf +pilaff +pilaffs +pilafs +pilaster +pilasters +pilau +pilaus +pilchard +pilchards +pile +piled +piles +pileup +pileups +pilfer +pilferage +pilfered +pilferer +pilferers +pilfering +pilfers +pilgrim +pilgrimage +pilgrimages +pilgrims +piling +pilings +pill +pillage +pillaged +pillager +pillagers +pillages +pillaging +pillar +pillars +pillbox +pillboxes +pilled +pilling +pillion +pillions +pilloried +pillories +pillory +pillorying +pillow +pillowcase +pillowcases +pillowed +pillowing +pillows +pillowslip +pillowslips +pills +pilot +piloted +pilothouse +pilothouses +piloting +pilots +pimento +pimentos +pimiento +pimientos +pimp +pimped +pimpernel +pimpernels +pimping +pimple +pimpled +pimples +pimplier +pimpliest +pimply +pimps +pinafore +pinafores +pinata +pinatas +pinball +pincer +pincers +pinch +pinched +pinches +pinching +pincushion +pincushions +pine +pineapple +pineapples +pined +pines +piney +pinfeather +pinfeathers +ping +pinged +pinging +pings +pinhead +pinheads +pinhole +pinholes +pinier +piniest +pining +pinion +pinioned +pinioning +pinions +pink +pinked +pinker +pinkest +pinkeye +pinkie +pinkies +pinking +pinkish +pinkness +pinko +pinkoes +pinkos +pinks +pinky +pinnacle +pinnacles +pinnate +pinned +pinning +pinochle +pinocle +pinon +pinones +pinons +pinpoint +pinpointed +pinpointing +pinpoints +pinprick +pinpricks +pins +pinsetter +pinsetters +pinstripe +pinstriped +pinstripes +pint +pinto +pintoes +pintos +pints +pinup +pinups +pinwheel +pinwheeled +pinwheeling +pinwheels +piny +pinyin +pinyon +pinyons +pioneer +pioneered +pioneering +pioneers +pious +piously +piousness +pipe +piped +pipeline +pipelines +piper +pipers +pipes +pipette +pipettes +piping +pipit +pipits +pipped +pippin +pipping +pippins +pips +pipsqueak +pipsqueaks +piquancy +piquant +piquantly +pique +piqued +piques +piquing +piracy +piranha +piranhas +pirate +pirated +pirates +piratical +piratically +pirating +pirogi +piroshki +pirouette +pirouetted +pirouettes +pirouetting +piscatorial +pismire +pismires +piss +pissed +pisses +pissing +pistachio +pistachios +pistil +pistillate +pistils +pistol +pistols +piston +pistons +pita +pitapat +pitapats +pitch +pitchblende +pitched +pitcher +pitchers +pitches +pitchfork +pitchforked +pitchforking +pitchforks +pitching +pitchman +pitchmen +piteous +piteously +piteousness +pitfall +pitfalls +pith +pithier +pithiest +pithily +pithiness +pithy +pitiable +pitiably +pitied +pities +pitiful +pitifully +pitiless +pitilessly +pitilessness +piton +pitons +pits +pittance +pittances +pitted +pitting +pituitaries +pituitary +pity +pitying +pivot +pivotal +pivoted +pivoting +pivots +pixel +pixels +pixie +pixies +pixy +pizazz +pizza +pizzas +pizzazz +pizzeria +pizzerias +pizzicati +pizzicato +placard +placarded +placarding +placards +placate +placated +placates +placating +placation +placatory +place +placebo +placeboes +placebos +placed +placeholder +placeholders +placekick +placekicked +placekicker +placekickers +placekicking +placekicks +placement +placements +placenta +placentae +placental +placentas +placer +placers +places +placid +placidity +placidly +placing +placket +plackets +plagiarism +plagiarisms +plagiarist +plagiarists +plagiarize +plagiarized +plagiarizer +plagiarizers +plagiarizes +plagiarizing +plagiary +plague +plagued +plagues +plaguing +plaid +plaids +plain +plainclothes +plainclothesman +plainclothesmen +plainer +plainest +plainly +plainness +plains +plainsman +plainsmen +plainsong +plainspoken +plaint +plaintiff +plaintiffs +plaintive +plaintively +plaints +plait +plaited +plaiting +plaits +plan +plane +planed +planeload +planeloads +planer +planers +planes +planet +planetaria +planetarium +planetariums +planetary +planets +plangency +plangent +planing +plank +planked +planking +planks +plankton +planned +planner +planners +planning +plans +plant +plantain +plantains +plantar +plantation +plantations +planted +planter +planters +planting +plantings +plantlike +plants +plaque +plaques +plash +plashed +plashes +plashing +plasma +plaster +plasterboard +plastered +plasterer +plasterers +plastering +plasters +plastic +plasticity +plasticize +plasticized +plasticizes +plasticizing +plastics +plat +plate +plateau +plateaued +plateauing +plateaus +plateaux +plated +plateful +platefuls +platelet +platelets +platen +platens +plates +platform +platformed +platforming +platforms +platies +plating +platinum +platitude +platitudes +platitudinous +platonic +platoon +platooned +platooning +platoons +plats +platted +platter +platters +platting +platy +platypi +platypus +platypuses +platys +plaudit +plaudits +plausibility +plausible +plausibly +play +playable +playact +playacted +playacting +playacts +playback +playbacks +playbill +playbills +playbook +playbooks +playboy +playboys +played +player +players +playfellow +playfellows +playful +playfully +playfulness +playgirl +playgirls +playgoer +playgoers +playground +playgrounds +playhouse +playhouses +playing +playmate +playmates +playoff +playoffs +playpen +playpens +playroom +playrooms +plays +plaything +playthings +playtime +playwright +playwrights +plaza +plazas +plea +plead +pleaded +pleader +pleaders +pleading +pleadings +pleads +pleas +pleasant +pleasanter +pleasantest +pleasantly +pleasantness +pleasantries +pleasantry +please +pleased +pleases +pleasing +pleasingly +pleasurable +pleasurably +pleasure +pleasured +pleasureful +pleasures +pleasuring +pleat +pleated +pleating +pleats +plebe +plebeian +plebeians +plebes +plebiscite +plebiscites +plectra +plectrum +plectrums +pled +pledge +pledged +pledges +pledging +plenaries +plenary +plenipotentiaries +plenipotentiary +plenitude +plenitudes +plenteous +plentiful +plentifully +plenty +pleonasm +pleonasms +plethora +pleura +pleurae +pleurisy +plexiglass +plexus +plexuses +pliability +pliable +pliancy +pliant +plied +pliers +plies +plight +plighted +plighting +plights +plinth +plinths +plod +plodded +plodder +plodders +plodding +plods +plop +plopped +plopping +plops +plot +plots +plotted +plotter +plotters +plotting +plough +ploughed +ploughing +ploughs +plover +plovers +plow +plowed +plowing +plowman +plowmen +plows +plowshare +plowshares +ploy +ploys +pluck +plucked +pluckier +pluckiest +pluckily +pluckiness +plucking +plucks +plucky +plug +plugged +plugging +plugs +plum +plumage +plumb +plumbed +plumber +plumbers +plumbing +plumbs +plume +plumed +plumes +plumier +plumiest +pluming +plummet +plummeted +plummeting +plummets +plump +plumped +plumper +plumpest +plumping +plumply +plumpness +plumps +plums +plumy +plunder +plundered +plunderer +plunderers +plundering +plunders +plunge +plunged +plunger +plungers +plunges +plunging +plunk +plunked +plunking +plunks +pluperfect +pluperfects +plural +pluralism +pluralist +pluralistic +pluralists +pluralities +plurality +pluralization +pluralize +pluralized +pluralizes +pluralizing +plurals +plus +pluses +plush +plusher +plushest +plushier +plushiest +plushly +plushness +plushy +plusses +plutocracies +plutocracy +plutocrat +plutocratic +plutocrats +plutonium +pluvial +plying +plywood +pneumatic +pneumatically +pneumonia +poach +poached +poacher +poachers +poaches +poaching +pock +pocked +pocket +pocketbook +pocketbooks +pocketed +pocketful +pocketfuls +pocketing +pocketknife +pocketknives +pockets +pocketsful +pockmark +pockmarked +pockmarking +pockmarks +pocks +podded +podding +podia +podiatrist +podiatrists +podiatry +podium +podiums +pods +poem +poems +poesy +poet +poetaster +poetasters +poetess +poetesses +poetic +poetical +poetically +poetry +poets +pogrom +pogroms +poignancy +poignant +poignantly +poinciana +poincianas +poinsettia +poinsettias +point +pointblank +pointed +pointedly +pointer +pointers +pointier +pointiest +pointillism +pointillist +pointillists +pointing +pointless +pointlessly +pointlessness +points +pointy +poise +poised +poises +poising +poison +poisoned +poisoner +poisoners +poisoning +poisonings +poisonous +poisonously +poisons +poke +poked +poker +pokers +pokes +pokey +pokeys +pokier +pokiest +poking +poky +polar +polarities +polarity +polarization +polarize +polarized +polarizes +polarizing +pole +polecat +polecats +poled +polemic +polemical +polemically +polemicist +polemicists +polemics +poles +polestar +polestars +police +policed +policeman +policemen +polices +policewoman +policewomen +policies +policing +policy +policyholder +policyholders +poling +polio +poliomyelitis +polish +polished +polisher +polishers +polishes +polishing +politburo +politburos +polite +politely +politeness +politer +politesse +politest +politic +political +politically +politician +politicians +politicization +politicize +politicized +politicizes +politicizing +politicking +politico +politicoes +politicos +politics +polities +polity +polka +polkaed +polkaing +polkas +poll +pollack +pollacks +polled +pollen +pollinate +pollinated +pollinates +pollinating +pollination +pollinator +pollinators +polling +polliwog +polliwogs +pollock +pollocks +polls +pollster +pollsters +pollutant +pollutants +pollute +polluted +polluter +polluters +pollutes +polluting +pollution +pollywog +pollywogs +polo +polonaise +polonaises +polonium +pols +poltergeist +poltergeists +poltroon +poltroons +polyandrous +polyandry +polyclinic +polyclinics +polyester +polyesters +polyethylene +polygamist +polygamists +polygamous +polygamy +polyglot +polyglots +polygon +polygonal +polygons +polygraph +polygraphed +polygraphing +polygraphs +polyhedra +polyhedral +polyhedron +polyhedrons +polymath +polymaths +polymer +polymeric +polymerization +polymerize +polymerized +polymerizes +polymerizing +polymers +polynomial +polynomials +polyp +polyphonic +polyphony +polypropylene +polyps +polystyrene +polysyllabic +polysyllable +polysyllables +polytechnic +polytechnics +polytheism +polytheist +polytheistic +polytheists +polyunsaturated +polyurethane +polyurethanes +polyvinyl +pomade +pomaded +pomades +pomading +pomander +pomanders +pomegranate +pomegranates +pommel +pommeled +pommeling +pommelled +pommelling +pommels +pomp +pompadour +pompadours +pompano +pompanos +pompom +pompoms +pompon +pompons +pomposity +pompous +pompously +pompousness +poncho +ponchos +pond +ponder +pondered +ponderer +ponderers +pondering +ponderous +ponderously +ponderousness +ponders +ponds +pone +pones +pongee +poniard +poniards +ponies +pontiff +pontiffs +pontifical +pontifically +pontificate +pontificated +pontificates +pontificating +pontoon +pontoons +pony +ponytail +ponytails +pooch +pooches +poodle +poodles +poof +poofs +pooh +poohed +poohing +poohs +pool +pooled +pooling +poolroom +poolrooms +pools +poop +pooped +pooping +poops +poor +poorboy +poorboys +poorer +poorest +poorhouse +poorhouses +poorly +poorness +popcorn +pope +popes +popgun +popguns +popinjay +popinjays +poplar +poplars +poplin +popover +popovers +poppa +poppas +popped +popper +poppers +poppies +popping +poppy +poppycock +pops +populace +populaces +popular +popularity +popularization +popularize +popularized +popularizes +popularizing +popularly +populate +populated +populates +populating +population +populations +populism +populist +populists +populous +populousness +porcelain +porch +porches +porcine +porcupine +porcupines +pore +pored +pores +porgies +porgy +poring +pork +porker +porkers +porkier +porkies +porkiest +porky +porn +porno +pornographer +pornographers +pornographic +pornographically +pornography +porosity +porous +porousness +porphyritic +porphyry +porpoise +porpoised +porpoises +porpoising +porridge +porringer +porringers +port +portability +portable +portables +portage +portaged +portages +portaging +portal +portals +portcullis +portcullises +ported +portend +portended +portending +portends +portent +portentous +portentously +portents +porter +porterhouse +porterhouses +porters +portfolio +portfolios +porthole +portholes +portico +porticoes +porticos +portiere +portieres +porting +portion +portioned +portioning +portions +portlier +portliest +portliness +portly +portmanteau +portmanteaus +portmanteaux +portrait +portraitist +portraitists +portraits +portraiture +portray +portrayal +portrayals +portrayed +portraying +portrays +ports +portulaca +pose +posed +poser +posers +poses +poseur +poseurs +posh +posher +poshest +posies +posing +posit +posited +positing +position +positioned +positioning +positions +positive +positively +positiveness +positives +positron +positrons +posits +posse +posses +possess +possessed +possesses +possessing +possession +possessions +possessive +possessively +possessiveness +possessives +possessor +possessors +possibilities +possibility +possible +possibles +possibly +possum +possums +post +postage +postal +postcard +postcards +postconsonantal +postdate +postdated +postdates +postdating +postdoctoral +posted +poster +posterior +posteriors +posterity +posters +postgraduate +postgraduates +posthaste +posthumous +posthumously +posthypnotic +postilion +postilions +postillion +postillions +postindustrial +posting +postings +postlude +postludes +postman +postmark +postmarked +postmarking +postmarks +postmaster +postmasters +postmen +postmenopausal +postmeridian +postmistress +postmistresses +postmodern +postmodernism +postmodernist +postmodernists +postmortem +postmortems +postnasal +postnatal +postoperative +postpaid +postpartum +postpone +postponed +postponement +postponements +postpones +postponing +postprandial +posts +postscript +postscripts +postseason +postseasons +postulate +postulated +postulates +postulating +postulation +postulations +posture +postured +postures +posturing +posturings +postwar +posy +potability +potable +potables +potash +potassium +potato +potatoes +potbellied +potbellies +potbelly +potboiler +potboilers +potency +potent +potentate +potentates +potential +potentialities +potentiality +potentially +potently +potful +potfuls +pothead +potheads +pother +potherb +potherbs +pothered +pothering +pothers +potholder +potholders +pothole +potholed +potholes +potholing +pothook +pothooks +potion +potions +potluck +potlucks +potpie +potpies +potpourri +potpourris +pots +potsherd +potsherds +potshot +potshots +pottage +potted +potter +pottered +potteries +pottering +potters +pottery +pottier +potties +pottiest +potting +potty +pouch +pouched +pouches +pouching +poulterer +poulterers +poultice +poulticed +poultices +poulticing +poultry +pounce +pounced +pounces +pouncing +pound +poundage +pounded +pounding +poundings +pounds +pour +poured +pouring +pours +pout +pouted +pouter +pouters +pouting +pouts +poverty +powder +powdered +powdering +powders +powdery +power +powerboat +powerboats +powered +powerful +powerfully +powerhouse +powerhouses +powering +powerless +powerlessly +powerlessness +powers +powwow +powwowed +powwowing +powwows +poxes +practicability +practicable +practicably +practical +practicalities +practicality +practically +practicals +practice +practiced +practices +practicing +practicum +practicums +practise +practised +practises +practising +practitioner +practitioners +praetor +praetorian +praetors +pragmatic +pragmatical +pragmatically +pragmatics +pragmatism +pragmatist +pragmatists +prairie +prairies +praise +praised +praises +praiseworthier +praiseworthiest +praiseworthiness +praiseworthy +praising +praline +pralines +pram +prams +prance +pranced +prancer +prancers +prances +prancing +prancingly +prank +pranks +prankster +pranksters +praseodymium +prate +prated +prater +praters +prates +pratfall +pratfalls +prating +prattle +prattled +prattler +prattlers +prattles +prattling +prawn +prawned +prawning +prawns +pray +prayed +prayer +prayerful +prayerfully +prayers +praying +prays +preach +preached +preacher +preachers +preaches +preachier +preachiest +preaching +preachment +preachy +preadolescence +preadolescences +preamble +preambles +prearrange +prearranged +prearrangement +prearranges +prearranging +preassigned +precancel +precanceled +precanceling +precancelled +precancelling +precancels +precancerous +precarious +precariously +precariousness +precaution +precautionary +precautions +precede +preceded +precedence +precedent +precedents +precedes +preceding +precept +preceptor +preceptors +precepts +precinct +precincts +preciosity +precious +preciously +preciousness +precipice +precipices +precipitant +precipitants +precipitate +precipitated +precipitately +precipitates +precipitating +precipitation +precipitations +precipitous +precipitously +precis +precise +precised +precisely +preciseness +preciser +precises +precisest +precising +precision +preclude +precluded +precludes +precluding +preclusion +precocious +precociously +precociousness +precocity +precognition +precognitive +precolonial +preconceive +preconceived +preconceives +preconceiving +preconception +preconceptions +precondition +preconditioned +preconditioning +preconditions +precook +precooked +precooking +precooks +precursor +precursors +precursory +predate +predated +predates +predating +predator +predators +predatory +predawn +predecease +predeceased +predeceases +predeceasing +predecessor +predecessors +predesignate +predesignated +predesignates +predesignating +predestination +predestine +predestined +predestines +predestining +predetermination +predetermine +predetermined +predeterminer +predeterminers +predetermines +predetermining +predicable +predicament +predicaments +predicate +predicated +predicates +predicating +predication +predicative +predict +predictability +predictable +predictably +predicted +predicting +prediction +predictions +predictive +predictor +predictors +predicts +predigest +predigested +predigesting +predigests +predilection +predilections +predispose +predisposed +predisposes +predisposing +predisposition +predispositions +predominance +predominant +predominantly +predominate +predominated +predominates +predominating +preemie +preemies +preeminence +preeminent +preeminently +preempt +preempted +preempting +preemption +preemptive +preempts +preen +preened +preening +preens +preexist +preexisted +preexistence +preexisting +preexists +prefab +prefabbed +prefabbing +prefabricate +prefabricated +prefabricates +prefabricating +prefabrication +prefabs +preface +prefaced +prefaces +prefacing +prefatory +prefect +prefects +prefecture +prefectures +prefer +preferable +preferably +preference +preferences +preferential +preferentially +preferment +preferred +preferring +prefers +prefigure +prefigured +prefigures +prefiguring +prefix +prefixed +prefixes +prefixing +preform +preformed +preforming +preforms +pregame +pregames +pregnancies +pregnancy +pregnant +preheat +preheated +preheating +preheats +prehensile +prehistoric +prehistorical +prehistorically +prehistory +prejudge +prejudged +prejudges +prejudging +prejudgment +prejudgments +prejudice +prejudiced +prejudices +prejudicial +prejudicing +prekindergarten +prekindergartens +prelacy +prelate +prelates +prelim +preliminaries +preliminary +prelims +preliterate +prelude +preludes +premarital +premature +prematurely +premed +premedical +premeditate +premeditated +premeditates +premeditating +premeditation +premeds +premenstrual +premier +premiere +premiered +premieres +premiering +premiers +premiership +premierships +premise +premised +premises +premising +premiss +premisses +premium +premiums +premix +premixed +premixes +premixing +premolar +premolars +premonition +premonitions +premonitory +prenatal +prenatally +prenuptial +preoccupation +preoccupations +preoccupied +preoccupies +preoccupy +preoccupying +preoperative +preordain +preordained +preordaining +preordains +prep +prepackage +prepackaged +prepackages +prepackaging +prepaid +preparation +preparations +preparatory +prepare +prepared +preparedness +prepares +preparing +prepay +prepaying +prepayment +prepayments +prepays +preponderance +preponderances +preponderant +preponderantly +preponderate +preponderated +preponderates +preponderating +preposition +prepositional +prepositionally +prepositions +prepossess +prepossessed +prepossesses +prepossessing +prepossession +prepossessions +preposterous +preposterously +prepped +preppie +preppier +preppies +preppiest +prepping +preppy +preps +prepubescence +prepubescent +prepubescents +prepuce +prepuces +prequel +prequels +prerecord +prerecorded +prerecording +prerecords +preregister +preregistered +preregistering +preregisters +preregistration +prerequisite +prerequisites +prerogative +prerogatives +presage +presaged +presages +presaging +presbyopia +presbyter +presbyteries +presbyters +presbytery +preschool +preschooler +preschoolers +preschools +prescience +prescient +presciently +prescribe +prescribed +prescribes +prescribing +prescript +prescription +prescriptions +prescriptive +prescriptively +prescripts +preseason +preseasons +presence +presences +present +presentable +presentably +presentation +presentations +presented +presenter +presenters +presentiment +presentiments +presenting +presently +presentment +presentments +presents +preservable +preservation +preservationist +preservationists +preservative +preservatives +preserve +preserved +preserver +preservers +preserves +preserving +preset +presets +presetting +preshrank +preshrink +preshrinking +preshrinks +preshrunk +preshrunken +preside +presided +presidencies +presidency +president +presidential +presidents +presides +presidia +presiding +presidium +presidiums +presort +presorted +presorting +presorts +press +pressed +presser +pressers +presses +pressing +pressingly +pressings +pressman +pressmen +pressure +pressured +pressures +pressuring +pressurization +pressurize +pressurized +pressurizer +pressurizers +pressurizes +pressurizing +prestidigitation +prestige +prestigious +presto +prestos +presumable +presumably +presume +presumed +presumes +presuming +presumption +presumptions +presumptive +presumptuous +presumptuously +presumptuousness +presuppose +presupposed +presupposes +presupposing +presupposition +presuppositions +pretax +preteen +preteens +pretence +pretences +pretend +pretended +pretender +pretenders +pretending +pretends +pretense +pretenses +pretension +pretensions +pretentious +pretentiously +pretentiousness +preterit +preterite +preterites +preterits +preterm +preternatural +preternaturally +pretest +pretested +pretesting +pretests +pretext +pretexts +pretrial +prettied +prettier +pretties +prettiest +prettified +prettifies +prettify +prettifying +prettily +prettiness +pretty +prettying +pretzel +pretzels +prevail +prevailed +prevailing +prevails +prevalence +prevalent +prevaricate +prevaricated +prevaricates +prevaricating +prevarication +prevarications +prevaricator +prevaricators +prevent +preventable +preventative +preventatives +prevented +preventible +preventing +prevention +preventive +preventives +prevents +preview +previewed +previewing +previews +previous +previously +prevision +previsions +prevue +prevues +prewar +prey +preyed +preying +preys +priapic +price +priced +priceless +prices +pricey +pricier +priciest +pricing +prick +pricked +pricker +prickers +pricking +prickle +prickled +prickles +pricklier +prickliest +prickliness +prickling +prickly +pricks +pricy +pride +prided +prideful +pridefully +prides +priding +pried +prier +priers +pries +priest +priestess +priestesses +priesthood +priesthoods +priestlier +priestliest +priestliness +priestly +priests +prig +priggish +priggishness +prigs +prim +primacy +primaeval +primal +primaries +primarily +primary +primate +primates +prime +primed +primer +primers +primes +primeval +priming +primitive +primitively +primitiveness +primitives +primly +primmer +primmest +primness +primogenitor +primogenitors +primogeniture +primordial +primordially +primp +primped +primping +primps +primrose +primroses +prince +princedom +princedoms +princelier +princeliest +princeliness +princely +princes +princess +princesses +principal +principalities +principality +principally +principals +principle +principled +principles +print +printable +printed +printer +printers +printing +printings +printout +printouts +prints +prior +prioress +prioresses +priories +priorities +prioritize +prioritized +prioritizes +prioritizing +priority +priors +priory +prise +prised +prises +prising +prism +prismatic +prisms +prison +prisoner +prisoners +prisons +prissier +prissiest +prissily +prissiness +prissy +pristine +prithee +privacy +private +privateer +privateers +privately +privater +privates +privatest +privation +privations +privatization +privatizations +privatize +privatized +privatizes +privatizing +privet +privets +privier +privies +priviest +privilege +privileged +privileges +privileging +privily +privy +prize +prized +prizefight +prizefighter +prizefighters +prizefighting +prizefights +prizes +prizewinner +prizewinners +prizewinning +prizing +proactive +probabilities +probability +probable +probables +probably +probate +probated +probates +probating +probation +probational +probationary +probationer +probationers +probe +probed +probes +probing +probity +problem +problematic +problematical +problematically +problems +proboscides +proboscis +proboscises +procaine +procedural +procedure +procedures +proceed +proceeded +proceeding +proceedings +proceeds +process +processed +processes +processing +procession +processional +processionals +processioned +processioning +processions +processor +processors +proclaim +proclaimed +proclaiming +proclaims +proclamation +proclamations +proclivities +proclivity +proconsul +proconsular +proconsuls +procrastinate +procrastinated +procrastinates +procrastinating +procrastination +procrastinator +procrastinators +procreate +procreated +procreates +procreating +procreation +procreative +proctor +proctored +proctoring +proctors +procurable +procurator +procurators +procure +procured +procurement +procurer +procurers +procures +procuring +prod +prodded +prodding +prodigal +prodigality +prodigals +prodigies +prodigious +prodigiously +prodigy +prods +produce +produced +producer +producers +produces +producible +producing +product +production +productions +productive +productively +productiveness +productivity +products +prof +profanation +profanations +profane +profaned +profanely +profaneness +profanes +profaning +profanities +profanity +profess +professed +professedly +professes +professing +profession +professional +professionalism +professionalize +professionalized +professionalizes +professionalizing +professionally +professionals +professions +professor +professorial +professorially +professors +professorship +professorships +proffer +proffered +proffering +proffers +proficiency +proficient +proficiently +proficients +profile +profiled +profiles +profiling +profit +profitability +profitable +profitably +profited +profiteer +profiteered +profiteering +profiteers +profiterole +profiteroles +profiting +profitless +profits +profligacy +profligate +profligately +profligates +profound +profounder +profoundest +profoundly +profoundness +profs +profundities +profundity +profuse +profusely +profuseness +profuser +profusest +profusion +profusions +progenitor +progenitors +progeny +progesterone +prognathous +prognoses +prognosis +prognostic +prognosticate +prognosticated +prognosticates +prognosticating +prognostication +prognostications +prognosticator +prognosticators +prognostics +program +programed +programer +programers +programing +programmable +programmables +programmatic +programme +programmed +programmer +programmers +programmes +programming +programs +progress +progressed +progresses +progressing +progression +progressions +progressive +progressively +progressiveness +progressives +prohibit +prohibited +prohibiting +prohibition +prohibitionist +prohibitionists +prohibitions +prohibitive +prohibitively +prohibitory +prohibits +project +projected +projectile +projectiles +projecting +projection +projectionist +projectionists +projections +projector +projectors +projects +prolapse +prolapsed +prolapses +prolapsing +proletarian +proletarians +proletariat +proliferate +proliferated +proliferates +proliferating +proliferation +prolific +prolifically +prolix +prolixity +prolixly +prolog +prologs +prologue +prologues +prolong +prolongation +prolongations +prolonged +prolonging +prolongs +prom +promenade +promenaded +promenades +promenading +promethium +prominence +prominent +prominently +promiscuity +promiscuous +promiscuously +promise +promised +promises +promising +promisingly +promissory +promo +promoed +promoing +promontories +promontory +promos +promote +promoted +promoter +promoters +promotes +promoting +promotion +promotional +promotions +prompt +prompted +prompter +prompters +promptest +prompting +promptings +promptitude +promptly +promptness +prompts +proms +promulgate +promulgated +promulgates +promulgating +promulgation +promulgator +promulgators +prone +proneness +proner +pronest +prong +pronged +pronghorn +pronghorns +prongs +pronominal +pronominals +pronoun +pronounce +pronounceable +pronounced +pronouncement +pronouncements +pronounces +pronouncing +pronouns +pronto +pronuclear +pronunciation +pronunciations +proof +proofed +proofing +proofread +proofreader +proofreaders +proofreading +proofreads +proofs +prop +propaganda +propagandist +propagandists +propagandize +propagandized +propagandizes +propagandizing +propagate +propagated +propagates +propagating +propagation +propagator +propagators +propane +propel +propellant +propellants +propelled +propellent +propellents +propeller +propellers +propelling +propels +propensities +propensity +proper +properer +properest +properly +propertied +properties +property +prophecies +prophecy +prophesied +prophesier +prophesiers +prophesies +prophesy +prophesying +prophet +prophetess +prophetesses +prophetic +prophetical +prophetically +prophets +prophylactic +prophylactics +prophylaxis +propinquity +propitiate +propitiated +propitiates +propitiating +propitiation +propitiatory +propitious +propitiously +proponent +proponents +proportion +proportional +proportionally +proportionate +proportionately +proportioned +proportioning +proportions +proposal +proposals +propose +proposed +proposer +proposers +proposes +proposing +proposition +propositional +propositioned +propositioning +propositions +propound +propounded +propounding +propounds +propped +propping +proprietaries +proprietary +proprieties +proprietor +proprietorial +proprietors +proprietorship +proprietress +proprietresses +propriety +props +propulsion +propulsive +prorate +prorated +prorates +prorating +prorogation +prorogue +prorogued +prorogues +proroguing +pros +prosaic +prosaically +proscenia +proscenium +prosceniums +prosciutto +proscribe +proscribed +proscribes +proscribing +proscription +proscriptions +prose +prosecute +prosecuted +prosecutes +prosecuting +prosecution +prosecutions +prosecutor +prosecutors +proselyte +proselyted +proselytes +proselyting +proselytism +proselytize +proselytized +proselytizer +proselytizers +proselytizes +proselytizing +prosier +prosiest +prosodies +prosody +prospect +prospected +prospecting +prospective +prospectively +prospector +prospectors +prospects +prospectus +prospectuses +prosper +prospered +prospering +prosperity +prosperous +prosperously +prospers +prostate +prostates +prostheses +prosthesis +prosthetic +prostitute +prostituted +prostitutes +prostituting +prostitution +prostrate +prostrated +prostrates +prostrating +prostration +prostrations +prosy +protactinium +protagonist +protagonists +protean +protect +protected +protecting +protection +protectionism +protectionist +protectionists +protections +protective +protectively +protectiveness +protector +protectorate +protectorates +protectors +protects +protege +proteges +protein +proteins +protest +protestation +protestations +protested +protester +protesters +protesting +protestor +protestors +protests +protocol +protocols +proton +protons +protoplasm +protoplasmic +prototype +prototypes +prototypical +protozoa +protozoan +protozoans +protozoic +protozoon +protract +protracted +protracting +protraction +protractor +protractors +protracts +protrude +protruded +protrudes +protruding +protrusile +protrusion +protrusions +protuberance +protuberances +protuberant +proud +prouder +proudest +proudly +provability +provable +prove +proved +proven +provenance +provender +provenience +proverb +proverbial +proverbially +proverbs +proves +provide +provided +providence +provident +providential +providentially +providently +provider +providers +provides +providing +province +provinces +provincial +provincialism +provincially +provincials +proving +provision +provisional +provisionally +provisioned +provisioning +provisions +proviso +provisoes +provisos +provocation +provocations +provocative +provocatively +provocativeness +provoke +provoked +provoker +provokers +provokes +provoking +provokingly +provolone +provost +provosts +prow +prowess +prowl +prowled +prowler +prowlers +prowling +prowls +prows +proxies +proximate +proximity +proxy +prude +prudence +prudent +prudential +prudentially +prudently +prudery +prudes +prudish +prudishly +prudishness +prune +pruned +pruner +pruners +prunes +pruning +prurience +prurient +pryer +pryers +prying +psalm +psalmist +psalmists +psalms +psalteries +psaltery +pseudo +pseudonym +pseudonymous +pseudonyms +pseudoscience +pseudosciences +pshaw +pshaws +psis +psittacosis +psoriasis +psst +psych +psyche +psyched +psychedelic +psychedelically +psychedelics +psyches +psychiatric +psychiatrist +psychiatrists +psychiatry +psychic +psychical +psychically +psychics +psyching +psycho +psychoactive +psychoanalysis +psychoanalyst +psychoanalysts +psychoanalytic +psychoanalytical +psychoanalyze +psychoanalyzed +psychoanalyzes +psychoanalyzing +psychobabble +psychodrama +psychodramas +psychogenic +psychological +psychologically +psychologies +psychologist +psychologists +psychology +psychoneurosis +psychopath +psychopathic +psychopaths +psychopathy +psychos +psychoses +psychosis +psychosomatic +psychotherapies +psychotherapist +psychotherapists +psychotherapy +psychotic +psychotically +psychotics +psychotropic +psychotropics +psychs +ptarmigan +ptarmigans +pterodactyl +pterodactyls +ptomaine +ptomaines +pubertal +puberty +pubes +pubescence +pubescent +pubic +pubis +public +publican +publicans +publication +publications +publicist +publicists +publicity +publicize +publicized +publicizes +publicizing +publicly +publish +published +publisher +publishers +publishes +publishing +pubs +puce +puck +pucker +puckered +puckering +puckers +puckish +puckishly +puckishness +pucks +pudding +puddings +puddle +puddled +puddles +puddling +pudenda +pudendum +pudgier +pudgiest +pudginess +pudgy +pueblo +pueblos +puerile +puerility +puerperal +puff +puffball +puffballs +puffed +puffer +puffers +puffier +puffiest +puffin +puffiness +puffing +puffins +puffs +puffy +pugilism +pugilist +pugilistic +pugilists +pugnacious +pugnaciously +pugnaciousness +pugnacity +pugs +puke +puked +pukes +puking +pukka +pulchritude +pulchritudinous +pule +puled +pules +puling +pull +pullback +pullbacks +pulled +puller +pullers +pullet +pullets +pulley +pulleys +pulling +pullout +pullouts +pullover +pullovers +pulls +pullup +pullups +pulmonary +pulp +pulped +pulpier +pulpiest +pulpiness +pulping +pulpit +pulpits +pulps +pulpwood +pulpy +pulsar +pulsars +pulsate +pulsated +pulsates +pulsating +pulsation +pulsations +pulse +pulsed +pulses +pulsing +pulverization +pulverize +pulverized +pulverizes +pulverizing +puma +pumas +pumice +pummel +pummeled +pummeling +pummelled +pummelling +pummels +pump +pumped +pumper +pumpernickel +pumpers +pumping +pumpkin +pumpkins +pumps +punch +punched +puncheon +puncheons +puncher +punchers +punches +punchier +punchiest +punching +punchy +punctilio +punctilious +punctiliously +punctiliousness +punctual +punctuality +punctually +punctuate +punctuated +punctuates +punctuating +punctuation +puncture +punctured +punctures +puncturing +pundit +punditry +pundits +pungency +pungent +pungently +punier +puniest +puniness +punish +punishable +punished +punishes +punishing +punishment +punishments +punitive +punitively +punk +punker +punkest +punkin +punkins +punks +punned +punning +puns +punster +punsters +punt +punted +punter +punters +punting +punts +puny +pupa +pupae +pupal +pupas +pupil +pupils +pupped +puppet +puppeteer +puppeteers +puppetry +puppets +puppies +pupping +puppy +pups +purblind +purchasable +purchase +purchased +purchaser +purchasers +purchases +purchasing +purdah +pure +purebred +purebreds +puree +pureed +pureeing +purees +purely +pureness +purer +purest +purgative +purgatives +purgatorial +purgatories +purgatory +purge +purged +purger +purgers +purges +purging +purification +purified +purifier +purifiers +purifies +purify +purifying +purine +purines +purism +purist +puristic +purists +puritan +puritanical +puritanically +puritanism +puritans +purity +purl +purled +purlieu +purlieus +purling +purloin +purloined +purloining +purloins +purls +purple +purpler +purples +purplest +purplish +purport +purported +purportedly +purporting +purports +purpose +purposed +purposeful +purposefully +purposefulness +purposeless +purposelessly +purposely +purposes +purposing +purr +purred +purring +purrs +purse +pursed +purser +pursers +purses +pursing +pursuance +pursuant +pursue +pursued +pursuer +pursuers +pursues +pursuing +pursuit +pursuits +purulence +purulent +purvey +purveyance +purveyed +purveying +purveyor +purveyors +purveys +purview +push +pushcart +pushcarts +pushed +pusher +pushers +pushes +pushier +pushiest +pushily +pushiness +pushing +pushover +pushovers +pushup +pushups +pushy +pusillanimity +pusillanimous +pusillanimously +puss +pusses +pussier +pussies +pussiest +pussy +pussycat +pussycats +pussyfoot +pussyfooted +pussyfooting +pussyfoots +pustular +pustule +pustules +putative +putdown +putdowns +putout +putouts +putrefaction +putrefactive +putrefied +putrefies +putrefy +putrefying +putrescence +putrescent +putrid +puts +putsch +putsches +putt +putted +puttee +puttees +putter +puttered +putterer +putterers +puttering +putters +puttied +putties +putting +putts +putty +puttying +puzzle +puzzled +puzzlement +puzzler +puzzlers +puzzles +puzzling +pygmies +pygmy +pyjamas +pylon +pylons +pylori +pyloric +pylorus +pyorrhea +pyorrhoea +pyramid +pyramidal +pyramided +pyramiding +pyramids +pyre +pyres +pyrimidine +pyrimidines +pyrite +pyrites +pyromania +pyromaniac +pyromaniacs +pyrotechnic +pyrotechnical +pyrotechnics +python +pythons +pyxes +quack +quacked +quackery +quacking +quacks +quad +quadrangle +quadrangles +quadrangular +quadrant +quadrants +quadraphonic +quadratic +quadratics +quadrennia +quadrennial +quadrennium +quadrenniums +quadriceps +quadricepses +quadrilateral +quadrilaterals +quadrille +quadrilles +quadrillion +quadrillions +quadriplegia +quadriplegic +quadriplegics +quadrivium +quadruped +quadrupedal +quadrupeds +quadruple +quadrupled +quadruples +quadruplet +quadruplets +quadruplicate +quadruplicated +quadruplicates +quadruplicating +quadruplication +quadrupling +quads +quaff +quaffed +quaffing +quaffs +quagmire +quagmires +quahaug +quahaugs +quahog +quahogs +quail +quailed +quailing +quails +quaint +quainter +quaintest +quaintly +quaintness +quake +quaked +quakes +quakier +quakiest +quaking +quaky +qualification +qualifications +qualified +qualifier +qualifiers +qualifies +qualify +qualifying +qualitative +qualitatively +qualities +quality +qualm +qualmish +qualms +quandaries +quandary +quanta +quantifiable +quantification +quantified +quantifier +quantifiers +quantifies +quantify +quantifying +quantitative +quantitatively +quantities +quantity +quantum +quarantine +quarantined +quarantines +quarantining +quark +quarks +quarrel +quarreled +quarreler +quarrelers +quarreling +quarrelled +quarrelling +quarrels +quarrelsome +quarrelsomeness +quarried +quarries +quarry +quarrying +quart +quarter +quarterback +quarterbacked +quarterbacking +quarterbacks +quarterdeck +quarterdecks +quartered +quarterfinal +quarterfinals +quartering +quarterlies +quarterly +quartermaster +quartermasters +quarters +quarterstaff +quarterstaffs +quarterstaves +quartet +quartets +quartette +quartettes +quarto +quartos +quarts +quartz +quasar +quasars +quash +quashed +quashes +quashing +quasi +quatrain +quatrains +quaver +quavered +quavering +quavers +quavery +quay +quays +queasier +queasiest +queasily +queasiness +queasy +queen +queened +queening +queenlier +queenliest +queenly +queens +queer +queered +queerer +queerest +queering +queerly +queerness +queers +quell +quelled +quelling +quells +quench +quenchable +quenched +quencher +quenchers +quenches +quenching +quenchless +queried +queries +querulous +querulously +querulousness +query +querying +quest +quested +questing +question +questionable +questionably +questioned +questioner +questioners +questioning +questioningly +questionings +questionnaire +questionnaires +questions +quests +queue +queued +queueing +queues +queuing +quibble +quibbled +quibbler +quibblers +quibbles +quibbling +quiche +quiches +quick +quicken +quickened +quickening +quickens +quicker +quickest +quickie +quickies +quicklime +quickly +quickness +quicksand +quicksands +quicksilver +quickstep +quicksteps +quid +quids +quiescence +quiescent +quiescently +quiet +quieted +quieter +quietest +quieting +quietly +quietness +quiets +quietude +quietus +quietuses +quill +quills +quilt +quilted +quilter +quilters +quilting +quilts +quince +quinces +quinine +quinsy +quint +quintessence +quintessences +quintessential +quintessentially +quintet +quintets +quintette +quintettes +quints +quintuple +quintupled +quintuples +quintuplet +quintuplets +quintupling +quip +quipped +quipping +quips +quipster +quipsters +quire +quires +quirk +quirked +quirkier +quirkiest +quirkiness +quirking +quirks +quirky +quirt +quirts +quisling +quislings +quit +quitclaim +quitclaims +quite +quits +quittance +quitted +quitter +quitters +quitting +quiver +quivered +quivering +quivers +quivery +quixotic +quixotically +quiz +quizzed +quizzer +quizzers +quizzes +quizzical +quizzically +quizzing +quoin +quoins +quoit +quoited +quoiting +quoits +quondam +quorum +quorums +quota +quotability +quotable +quotas +quotation +quotations +quote +quoted +quotes +quoth +quotidian +quotient +quotients +quoting +qwerty +rabbet +rabbeted +rabbeting +rabbets +rabbi +rabbinate +rabbinic +rabbinical +rabbis +rabbit +rabbits +rabble +rabbles +rabid +rabidly +rabidness +rabies +raccoon +raccoons +race +racecourse +racecourses +raced +racehorse +racehorses +raceme +racemes +racer +racers +races +racetrack +racetracks +raceway +raceways +racial +racialism +racialist +racialists +racially +racier +raciest +racily +raciness +racing +racism +racist +racists +rack +racked +racket +racketed +racketeer +racketeered +racketeering +racketeers +racketing +rackets +racking +racks +raconteur +raconteurs +racoon +racoons +racquet +racquetball +racquetballs +racquets +racy +radar +radars +radarscope +radarscopes +radial +radially +radials +radiance +radiant +radiantly +radiate +radiated +radiates +radiating +radiation +radiations +radiator +radiators +radical +radicalism +radicalization +radicalize +radicalized +radicalizes +radicalizing +radically +radicals +radicchio +radii +radio +radioactive +radioactively +radioactivity +radiocarbon +radioed +radiogram +radiograms +radiographer +radiographers +radiography +radioing +radioisotope +radioisotopes +radiologist +radiologists +radiology +radioman +radiomen +radiometer +radiometers +radiometric +radiometry +radiophone +radiophones +radios +radioscopy +radiosonde +radiosondes +radiotelegraph +radiotelegraphed +radiotelegraphing +radiotelegraphs +radiotelegraphy +radiotelephone +radiotelephones +radiotherapist +radiotherapists +radiotherapy +radish +radishes +radium +radius +radiuses +radon +rads +raffia +raffish +raffishly +raffishness +raffle +raffled +raffles +raffling +raft +rafted +rafter +rafters +rafting +rafts +raga +ragamuffin +ragamuffins +ragas +ragbag +rage +raged +rages +ragged +raggeder +raggedest +raggedier +raggediest +raggedly +raggedness +raggedy +ragging +raging +ragingly +raglan +raglans +ragout +ragouts +rags +ragtag +ragtime +ragweed +raid +raided +raider +raiders +raiding +raids +rail +railed +railing +railings +railleries +raillery +railroad +railroaded +railroader +railroaders +railroading +railroads +rails +railway +railways +raiment +rain +rainbow +rainbows +raincoat +raincoats +raindrop +raindrops +rained +rainfall +rainfalls +rainier +rainiest +raining +rainmaker +rainmakers +rainmaking +rainproof +rains +rainstorm +rainstorms +rainwater +rainy +raise +raised +raiser +raisers +raises +raisin +raising +raisins +raja +rajah +rajahs +rajas +rake +raked +rakes +raking +rakish +rakishly +rakishness +rallied +rallies +rally +rallying +ramble +rambled +rambler +ramblers +rambles +rambling +rambunctious +rambunctiously +rambunctiousness +ramekin +ramekins +ramequin +ramequins +ramie +ramification +ramifications +ramified +ramifies +ramify +ramifying +ramjet +ramjets +rammed +ramming +ramp +rampage +rampaged +rampages +rampaging +rampancy +rampant +rampantly +rampart +ramparts +ramps +ramrod +ramrodded +ramrodding +ramrods +rams +ramshackle +ranch +ranched +rancher +ranchers +ranches +ranching +rancid +rancidity +rancidness +rancor +rancorous +rancorously +rancour +rand +randier +randiest +randiness +random +randomization +randomize +randomized +randomizes +randomizing +randomly +randomness +randy +ranee +ranees +rang +range +ranged +ranger +rangers +ranges +rangier +rangiest +ranginess +ranging +rangy +rani +ranis +rank +ranked +ranker +rankest +ranking +rankings +rankle +rankled +rankles +rankling +rankly +rankness +ranks +ransack +ransacked +ransacking +ransacks +ransom +ransomed +ransomer +ransomers +ransoming +ransoms +rant +ranted +ranter +ranters +ranting +rants +rapacious +rapaciously +rapaciousness +rapacity +rape +raped +raper +rapers +rapes +rapeseed +rapid +rapider +rapidest +rapidity +rapidly +rapidness +rapids +rapier +rapiers +rapine +raping +rapist +rapists +rapped +rappel +rappelled +rappelling +rappels +rapper +rappers +rapping +rapport +rapports +rapprochement +rapprochements +raps +rapscallion +rapscallions +rapt +raptly +raptness +rapture +raptures +rapturous +rapturously +rare +rarebit +rarebits +rared +rarefaction +rarefied +rarefies +rarefy +rarefying +rarely +rareness +rarer +rares +rarest +raring +rarities +rarity +rascal +rascally +rascals +rash +rasher +rashers +rashes +rashest +rashly +rashness +rasp +raspberries +raspberry +rasped +raspier +raspiest +rasping +rasps +raspy +ratatouille +ratchet +ratcheted +ratcheting +ratchets +rate +rated +rater +raters +rates +rather +rathskeller +rathskellers +ratification +ratified +ratifier +ratifiers +ratifies +ratify +ratifying +rating +ratings +ratio +ratiocinate +ratiocinated +ratiocinates +ratiocinating +ratiocination +ration +rational +rationale +rationales +rationalism +rationalist +rationalistic +rationalists +rationality +rationalization +rationalizations +rationalize +rationalized +rationalizes +rationalizing +rationally +rationals +rationed +rationing +rations +ratios +ratlike +ratlin +ratline +ratlines +ratlins +rats +rattan +rattans +ratted +ratter +ratters +rattier +rattiest +ratting +rattle +rattlebrain +rattlebrained +rattlebrains +rattled +rattler +rattlers +rattles +rattlesnake +rattlesnakes +rattletrap +rattletraps +rattling +rattly +rattrap +rattraps +ratty +raucous +raucously +raucousness +raunchier +raunchiest +raunchily +raunchiness +raunchy +ravage +ravaged +ravager +ravagers +ravages +ravaging +rave +raved +ravel +raveled +raveling +ravelled +ravelling +ravels +raven +ravened +ravening +ravenous +ravenously +ravens +raves +ravine +ravines +raving +ravings +ravioli +raviolis +ravish +ravished +ravisher +ravishers +ravishes +ravishing +ravishingly +ravishment +rawboned +rawer +rawest +rawhide +rawness +rayon +rays +raze +razed +razes +razing +razor +razorback +razorbacks +razors +razz +razzed +razzes +razzing +razzmatazz +reabsorb +reabsorbed +reabsorbing +reabsorbs +reach +reachable +reached +reaches +reaching +reacquaint +reacquainted +reacquainting +reacquaints +reacquire +reacquired +reacquires +reacquiring +react +reactant +reactants +reacted +reacting +reaction +reactionaries +reactionary +reactions +reactivate +reactivated +reactivates +reactivating +reactivation +reactive +reactor +reactors +reacts +read +readabilities +readability +readable +readdress +readdressed +readdresses +readdressing +reader +readers +readership +readerships +readied +readier +readies +readiest +readily +readiness +reading +readings +readjust +readjusted +readjusting +readjustment +readjustments +readjusts +readmission +readmit +readmits +readmitted +readmitting +readopt +readopted +readopting +readopts +readout +readouts +reads +ready +readying +reaffirm +reaffirmation +reaffirmations +reaffirmed +reaffirming +reaffirms +reagent +reagents +real +realer +reales +realest +realign +realigned +realigning +realignment +realignments +realigns +realism +realist +realistic +realistically +realists +realities +reality +realizable +realization +realize +realized +realizes +realizing +reallocate +reallocated +reallocates +reallocating +reallocation +really +realm +realms +realness +realpolitik +realtor +realtors +realty +ream +reamed +reamer +reamers +reaming +reams +reanalyses +reanalysis +reanalyze +reanalyzed +reanalyzes +reanalyzing +reanimate +reanimated +reanimates +reanimating +reanimation +reap +reaped +reaper +reapers +reaping +reappear +reappearance +reappearances +reappeared +reappearing +reappears +reapplication +reapplications +reapplied +reapplies +reapply +reapplying +reappoint +reappointed +reappointing +reappointment +reappoints +reapportion +reapportioned +reapportioning +reapportionment +reapportions +reappraisal +reappraisals +reappraise +reappraised +reappraises +reappraising +reaps +rear +reared +rearguard +rearguards +rearing +rearm +rearmament +rearmed +rearming +rearmost +rearms +rearrange +rearranged +rearrangement +rearrangements +rearranges +rearranging +rearrest +rearrested +rearresting +rearrests +rears +rearward +rearwards +reascend +reascended +reascending +reascends +reason +reasonable +reasonableness +reasonably +reasoned +reasoner +reasoners +reasoning +reasons +reassemble +reassembled +reassembles +reassembling +reassembly +reassert +reasserted +reasserting +reassertion +reasserts +reassess +reassessed +reassesses +reassessing +reassessment +reassessments +reassign +reassigned +reassigning +reassignment +reassignments +reassigns +reassurance +reassurances +reassure +reassured +reassures +reassuring +reassuringly +reattach +reattached +reattaches +reattaching +reattachment +reattain +reattained +reattaining +reattains +reattempt +reattempted +reattempting +reattempts +reauthorize +reauthorized +reauthorizes +reauthorizing +reawaken +reawakened +reawakening +reawakens +rebate +rebated +rebates +rebating +rebel +rebelled +rebelling +rebellion +rebellions +rebellious +rebelliously +rebelliousness +rebels +rebid +rebidding +rebids +rebind +rebinding +rebinds +rebirth +rebirths +reboil +reboiled +reboiling +reboils +reboot +rebooted +rebooting +reboots +reborn +rebound +rebounded +rebounding +rebounds +rebroadcast +rebroadcasted +rebroadcasting +rebroadcasts +rebuff +rebuffed +rebuffing +rebuffs +rebuild +rebuilding +rebuilds +rebuilt +rebuke +rebuked +rebukes +rebuking +rebukingly +reburial +reburials +reburied +reburies +rebury +reburying +rebus +rebuses +rebut +rebuts +rebuttal +rebuttals +rebutted +rebutting +recalcitrance +recalcitrant +recalculate +recalculated +recalculates +recalculating +recalculation +recalculations +recall +recalled +recalling +recalls +recant +recantation +recantations +recanted +recanting +recants +recap +recapitalize +recapitalized +recapitalizes +recapitalizing +recapitulate +recapitulated +recapitulates +recapitulating +recapitulation +recapitulations +recapped +recapping +recaps +recapture +recaptured +recaptures +recapturing +recast +recasting +recastings +recasts +recede +receded +recedes +receding +receipt +receipted +receipting +receipts +receivable +receivables +receive +received +receiver +receivers +receivership +receives +receiving +recent +recenter +recentest +recently +recentness +receptacle +receptacles +reception +receptionist +receptionists +receptions +receptive +receptively +receptiveness +receptivity +receptor +receptors +recess +recessed +recesses +recessing +recession +recessional +recessionals +recessionary +recessions +recessive +recessives +recharge +rechargeable +recharged +recharges +recharging +recharter +rechartered +rechartering +recharters +recheck +rechecked +rechecking +rechecks +recherche +rechristen +rechristened +rechristening +rechristens +recidivism +recidivist +recidivists +recipe +recipes +recipient +recipients +reciprocal +reciprocally +reciprocals +reciprocate +reciprocated +reciprocates +reciprocating +reciprocation +reciprocity +recirculate +recirculated +recirculates +recirculating +recital +recitalist +recitalists +recitals +recitation +recitations +recitative +recitatives +recite +recited +reciter +reciters +recites +reciting +reckless +recklessly +recklessness +reckon +reckoned +reckoning +reckonings +reckons +reclaim +reclaimable +reclaimed +reclaiming +reclaims +reclamation +reclassification +reclassified +reclassifies +reclassify +reclassifying +recline +reclined +recliner +recliners +reclines +reclining +recluse +recluses +reclusive +recognition +recognizable +recognizably +recognizance +recognize +recognized +recognizes +recognizing +recoil +recoiled +recoiling +recoils +recollect +recollected +recollecting +recollection +recollections +recollects +recolonization +recolonize +recolonized +recolonizes +recolonizing +recolor +recolored +recoloring +recolors +recombine +recombined +recombines +recombining +recommence +recommenced +recommencement +recommences +recommencing +recommend +recommendable +recommendation +recommendations +recommended +recommending +recommends +recommission +recommissioned +recommissioning +recommissions +recommit +recommits +recommitted +recommitting +recompense +recompensed +recompenses +recompensing +recompose +recomposed +recomposes +recomposing +recompute +recomputed +recomputes +recomputing +reconcilable +reconcile +reconciled +reconciles +reconciliation +reconciliations +reconciling +recondite +recondition +reconditioned +reconditioning +reconditions +reconfirm +reconfirmation +reconfirmations +reconfirmed +reconfirming +reconfirms +reconnaissance +reconnaissances +reconnect +reconnected +reconnecting +reconnects +reconnoiter +reconnoitered +reconnoitering +reconnoiters +reconnoitre +reconnoitred +reconnoitres +reconnoitring +reconquer +reconquered +reconquering +reconquers +reconquest +reconsecrate +reconsecrated +reconsecrates +reconsecrating +reconsecration +reconsider +reconsideration +reconsidered +reconsidering +reconsiders +reconsign +reconsigned +reconsigning +reconsigns +reconstitute +reconstituted +reconstitutes +reconstituting +reconstitution +reconstruct +reconstructed +reconstructing +reconstruction +reconstructions +reconstructs +recontact +recontacted +recontacting +recontacts +recontaminate +recontaminated +recontaminates +recontaminating +reconvene +reconvened +reconvenes +reconvening +reconvert +reconverted +reconverting +reconverts +recook +recooked +recooking +recooks +recopied +recopies +recopy +recopying +record +recorded +recorder +recorders +recording +recordings +records +recount +recounted +recounting +recounts +recoup +recouped +recouping +recoups +recourse +recover +recoverable +recovered +recoveries +recovering +recovers +recovery +recreant +recreants +recreate +recreated +recreates +recreating +recreation +recreational +recreations +recriminate +recriminated +recriminates +recriminating +recrimination +recriminations +recriminatory +recross +recrossed +recrosses +recrossing +recrudesce +recrudesced +recrudescence +recrudescent +recrudesces +recrudescing +recruit +recruited +recruiter +recruiters +recruiting +recruitment +recruits +recrystallize +recrystallized +recrystallizes +recrystallizing +recta +rectal +rectally +rectangle +rectangles +rectangular +rectifiable +rectification +rectifications +rectified +rectifier +rectifiers +rectifies +rectify +rectifying +rectilinear +rectitude +recto +rector +rectories +rectors +rectory +rectos +rectum +rectums +recumbent +recuperate +recuperated +recuperates +recuperating +recuperation +recuperative +recur +recurred +recurrence +recurrences +recurrent +recurrently +recurring +recurs +recyclable +recyclables +recycle +recycled +recycles +recycling +redact +redacted +redacting +redaction +redactor +redactors +redacts +redbird +redbirds +redbreast +redbreasts +redcap +redcaps +redcoat +redcoats +redden +reddened +reddening +reddens +redder +reddest +reddish +redecorate +redecorated +redecorates +redecorating +redecoration +rededicate +rededicated +rededicates +rededicating +redeem +redeemable +redeemed +redeemer +redeemers +redeeming +redeems +redefine +redefined +redefines +redefining +redefinition +redeliver +redelivered +redelivering +redelivers +redemption +redemptive +redeploy +redeployed +redeploying +redeployment +redeploys +redeposit +redeposited +redepositing +redeposits +redesign +redesigned +redesigning +redesigns +redetermine +redetermined +redetermines +redetermining +redevelop +redeveloped +redeveloping +redevelopment +redevelopments +redevelops +redhead +redheaded +redheads +redial +redialed +redialing +redialled +redialling +redials +redid +redirect +redirected +redirecting +redirects +rediscover +rediscovered +rediscovering +rediscovers +rediscovery +redissolve +redissolved +redissolves +redissolving +redistribute +redistributed +redistributes +redistributing +redistribution +redistrict +redistricted +redistricting +redistricts +redivide +redivided +redivides +redividing +redlining +redneck +rednecks +redness +redo +redoes +redoing +redolence +redolent +redone +redouble +redoubled +redoubles +redoubling +redoubt +redoubtable +redoubtably +redoubts +redound +redounded +redounding +redounds +redraft +redrafted +redrafting +redrafts +redraw +redrawing +redrawn +redraws +redress +redressed +redresses +redressing +redrew +reds +redskin +redskins +reduce +reduced +reducer +reducers +reduces +reducible +reducing +reduction +reductions +reductive +redundancies +redundancy +redundant +redundantly +reduplicate +reduplicated +reduplicates +reduplicating +reduplication +redwood +redwoods +redye +redyed +redyeing +redyes +reecho +reechoed +reechoes +reechoing +reed +reedier +reediest +reediness +reedit +reedited +reediting +reedits +reeds +reeducate +reeducated +reeducates +reeducating +reeducation +reedy +reef +reefed +reefer +reefers +reefing +reefs +reek +reeked +reeking +reeks +reel +reelect +reelected +reelecting +reelection +reelections +reelects +reeled +reeling +reels +reembark +reembarked +reembarking +reembarks +reembodied +reembodies +reembody +reembodying +reemerge +reemerged +reemergence +reemerges +reemerging +reemphasize +reemphasized +reemphasizes +reemphasizing +reemploy +reemployed +reemploying +reemployment +reemploys +reenact +reenacted +reenacting +reenactment +reenactments +reenacts +reenforce +reenforced +reenforces +reenforcing +reengage +reengaged +reengages +reengaging +reenlist +reenlisted +reenlisting +reenlistment +reenlists +reenter +reentered +reentering +reenters +reentries +reentry +reequip +reequipped +reequipping +reequips +reestablish +reestablished +reestablishes +reestablishing +reestablishment +reevaluate +reevaluated +reevaluates +reevaluating +reevaluation +reevaluations +reeve +reeved +reeves +reeving +reexamination +reexaminations +reexamine +reexamined +reexamines +reexamining +reexplain +reexplained +reexplaining +reexplains +reexport +reexported +reexporting +reexports +reface +refaced +refaces +refacing +refashion +refashioned +refashioning +refashions +refasten +refastened +refastening +refastens +refection +refectories +refectory +refer +referable +referee +refereed +refereeing +referees +reference +referenced +references +referencing +referenda +referendum +referendums +referent +referents +referral +referrals +referred +referrer +referrers +referring +refers +reffed +reffing +refile +refiled +refiles +refiling +refill +refillable +refilled +refilling +refills +refinance +refinanced +refinances +refinancing +refine +refined +refinement +refinements +refiner +refineries +refiners +refinery +refines +refining +refinish +refinished +refinishes +refinishing +refit +refits +refitted +refitting +reflect +reflected +reflecting +reflection +reflections +reflective +reflectively +reflector +reflectors +reflects +reflex +reflexes +reflexion +reflexions +reflexive +reflexively +reflexives +refocus +refocused +refocuses +refocusing +refocussed +refocusses +refocussing +refold +refolded +refolding +refolds +reforest +reforestation +reforested +reforesting +reforests +reforge +reforged +reforges +reforging +reform +reformation +reformations +reformative +reformatories +reformatory +reformed +reformer +reformers +reforming +reforms +reformulate +reformulated +reformulates +reformulating +reformulation +reformulations +refortified +refortifies +refortify +refortifying +refract +refracted +refracting +refraction +refractive +refractories +refractory +refracts +refrain +refrained +refraining +refrains +refreeze +refreezes +refreezing +refresh +refreshed +refresher +refreshers +refreshes +refreshing +refreshingly +refreshment +refreshments +refrigerant +refrigerants +refrigerate +refrigerated +refrigerates +refrigerating +refrigeration +refrigerator +refrigerators +refroze +refrozen +refs +refuel +refueled +refueling +refuelled +refuelling +refuels +refuge +refugee +refugees +refuges +refulgence +refulgent +refund +refundable +refunded +refunding +refunds +refurbish +refurbished +refurbishes +refurbishing +refurbishment +refurbishments +refurnish +refurnished +refurnishes +refurnishing +refusal +refusals +refuse +refused +refuses +refusing +refutable +refutation +refutations +refute +refuted +refuter +refuters +refutes +refuting +regain +regained +regaining +regains +regal +regale +regaled +regalement +regales +regalia +regaling +regally +regard +regarded +regarding +regardless +regards +regather +regathered +regathering +regathers +regatta +regattas +regencies +regency +regeneracy +regenerate +regenerated +regenerates +regenerating +regeneration +regenerative +regent +regents +reggae +regicide +regicides +regime +regimen +regimens +regiment +regimental +regimentation +regimented +regimenting +regiments +regimes +region +regional +regionalism +regionalisms +regionally +regions +register +registered +registering +registers +registrant +registrants +registrar +registrars +registration +registrations +registries +registry +regnant +regrade +regraded +regrades +regrading +regress +regressed +regresses +regressing +regression +regressions +regressive +regret +regretful +regretfully +regrets +regrettable +regrettably +regretted +regretting +regrew +regrind +regrinding +regrinds +reground +regroup +regrouped +regrouping +regroups +regrow +regrowing +regrown +regrows +regrowth +regular +regularise +regularised +regularises +regularising +regularity +regularization +regularize +regularized +regularizes +regularizing +regularly +regulars +regulate +regulated +regulates +regulating +regulation +regulations +regulative +regulator +regulators +regulatory +regurgitate +regurgitated +regurgitates +regurgitating +regurgitation +rehab +rehabbed +rehabbing +rehabilitate +rehabilitated +rehabilitates +rehabilitating +rehabilitation +rehabilitative +rehabs +rehang +rehanged +rehanging +rehangs +rehash +rehashed +rehashes +rehashing +rehear +reheard +rehearing +rehearings +rehears +rehearsal +rehearsals +rehearse +rehearsed +rehearses +rehearsing +reheat +reheated +reheating +reheats +rehire +rehired +rehires +rehiring +rehouse +rehoused +rehouses +rehousing +rehung +reign +reigned +reigning +reignite +reignited +reignites +reigniting +reigns +reimbursable +reimburse +reimbursed +reimbursement +reimbursements +reimburses +reimbursing +reimpose +reimposed +reimposes +reimposing +rein +reincarnate +reincarnated +reincarnates +reincarnating +reincarnation +reincarnations +reincorporate +reincorporated +reincorporates +reincorporating +reincorporation +reindeer +reindeers +reined +reinfect +reinfected +reinfecting +reinfection +reinfections +reinfects +reinforce +reinforced +reinforcement +reinforcements +reinforces +reinforcing +reining +reinoculate +reinoculated +reinoculates +reinoculating +reins +reinsert +reinserted +reinserting +reinsertion +reinserts +reinspect +reinspected +reinspecting +reinspects +reinstate +reinstated +reinstatement +reinstates +reinstating +reintegrate +reintegrated +reintegrates +reintegrating +reintegration +reinterpret +reinterpretation +reinterpretations +reinterpreted +reinterpreting +reinterprets +reintroduce +reintroduced +reintroduces +reintroducing +reintroduction +reinvent +reinvented +reinventing +reinvention +reinventions +reinvents +reinvest +reinvested +reinvesting +reinvestment +reinvests +reinvigorate +reinvigorated +reinvigorates +reinvigorating +reissue +reissued +reissues +reissuing +reiterate +reiterated +reiterates +reiterating +reiteration +reiterations +reiterative +reject +rejected +rejecting +rejection +rejections +rejects +rejoice +rejoiced +rejoices +rejoicing +rejoicings +rejoin +rejoinder +rejoinders +rejoined +rejoining +rejoins +rejudge +rejudged +rejudges +rejudging +rejuvenate +rejuvenated +rejuvenates +rejuvenating +rejuvenation +rekindle +rekindled +rekindles +rekindling +relabel +relabeled +relabeling +relabelled +relabelling +relabels +relaid +relapse +relapsed +relapses +relapsing +relate +related +relatedness +relater +relaters +relates +relating +relation +relational +relations +relationship +relationships +relative +relatively +relatives +relativism +relativity +relaunch +relaunched +relaunches +relaunching +relax +relaxant +relaxants +relaxation +relaxations +relaxed +relaxer +relaxers +relaxes +relaxing +relay +relayed +relaying +relays +relearn +relearned +relearning +relearns +release +released +releases +releasing +relegate +relegated +relegates +relegating +relegation +relent +relented +relenting +relentless +relentlessly +relentlessness +relents +relevance +relevancy +relevant +relevantly +reliability +reliable +reliably +reliance +reliant +relic +relics +relied +relief +reliefs +relies +relieve +relieved +reliever +relievers +relieves +relieving +relight +relighted +relighting +relights +religion +religions +religious +religiously +religiousness +reline +relined +relines +relining +relinquish +relinquished +relinquishes +relinquishing +relinquishment +reliquaries +reliquary +relish +relished +relishes +relishing +relit +relivable +relive +relived +relives +reliving +reload +reloaded +reloading +reloads +relocate +relocated +relocates +relocating +relocation +reluctance +reluctant +reluctantly +rely +relying +remade +remain +remainder +remainders +remained +remaining +remains +remake +remakes +remaking +remand +remanded +remanding +remands +remap +remapped +remapping +remaps +remark +remarkable +remarkableness +remarkably +remarked +remarking +remarks +remarriage +remarriages +remarried +remarries +remarry +remarrying +rematch +rematches +remeasure +remeasured +remeasures +remeasuring +remediable +remedial +remedially +remediation +remedied +remedies +remedy +remedying +remelt +remelted +remelting +remelts +remember +remembered +remembering +remembers +remembrance +remembrances +remigrate +remigrated +remigrates +remigrating +remind +reminded +reminder +reminders +reminding +reminds +reminisce +reminisced +reminiscence +reminiscences +reminiscent +reminiscently +reminisces +reminiscing +remiss +remission +remissions +remissly +remissness +remit +remits +remittance +remittances +remitted +remitting +remix +remixed +remixes +remixing +remnant +remnants +remodel +remodeled +remodeling +remodelled +remodelling +remodels +remold +remolded +remolding +remolds +remonstrance +remonstrances +remonstrant +remonstrants +remonstrate +remonstrated +remonstrates +remonstrating +remorse +remorseful +remorsefully +remorseless +remorselessly +remorselessness +remote +remotely +remoteness +remoter +remotes +remotest +remount +remounted +remounting +remounts +removable +removal +removals +remove +removed +remover +removers +removes +removing +rems +remunerate +remunerated +remunerates +remunerating +remuneration +remunerations +remunerative +renaissance +renaissances +renal +rename +renamed +renames +renaming +renascence +renascences +renascent +rend +render +rendered +rendering +renderings +renders +rendezvous +rendezvoused +rendezvouses +rendezvousing +rending +rendition +renditions +rends +renegade +renegades +renege +reneged +reneger +renegers +reneges +reneging +renegotiable +renegotiate +renegotiated +renegotiates +renegotiating +renegotiation +renew +renewable +renewal +renewals +renewed +renewing +renews +rennet +rennin +renominate +renominated +renominates +renominating +renomination +renounce +renounced +renouncement +renounces +renouncing +renovate +renovated +renovates +renovating +renovation +renovations +renovator +renovators +renown +renowned +rent +rental +rentals +rented +renter +renters +renting +rents +renumber +renumbered +renumbering +renumbers +renunciation +renunciations +reoccupation +reoccupied +reoccupies +reoccupy +reoccupying +reoccur +reoccurred +reoccurring +reoccurs +reopen +reopened +reopening +reopens +reorder +reordered +reordering +reorders +reorganization +reorganizations +reorganize +reorganized +reorganizes +reorganizing +reorient +reorientation +reoriented +reorienting +reorients +repack +repackage +repackaged +repackages +repackaging +repacked +repacking +repacks +repaid +repaint +repainted +repainting +repaints +repair +repairable +repaired +repairer +repairers +repairing +repairman +repairmen +repairs +reparable +reparation +reparations +repartee +repast +repasts +repatriate +repatriated +repatriates +repatriating +repatriation +repave +repaved +repaves +repaving +repay +repayable +repaying +repayment +repayments +repays +repeal +repealed +repealing +repeals +repeat +repeatable +repeated +repeatedly +repeater +repeaters +repeating +repeats +repel +repellant +repellants +repelled +repellent +repellents +repelling +repels +repent +repentance +repentant +repentantly +repented +repenting +repents +repercussion +repercussions +repertoire +repertoires +repertories +repertory +repetition +repetitions +repetitious +repetitiously +repetitiousness +repetitive +repetitively +repetitiveness +rephotograph +rephotographed +rephotographing +rephotographs +rephrase +rephrased +rephrases +rephrasing +repine +repined +repines +repining +replace +replaceable +replaced +replacement +replacements +replaces +replacing +replant +replanted +replanting +replants +replay +replayed +replaying +replays +replenish +replenished +replenishes +replenishing +replenishment +replete +repleteness +repletion +replica +replicas +replicate +replicated +replicates +replicating +replication +replications +replied +replies +reply +replying +repopulate +repopulated +repopulates +repopulating +report +reportage +reported +reportedly +reporter +reporters +reporting +reportorial +reports +repose +reposed +reposeful +reposes +reposing +repositories +repository +repossess +repossessed +repossesses +repossessing +repossession +repossessions +reprehend +reprehended +reprehending +reprehends +reprehensibility +reprehensible +reprehensibly +reprehension +represent +representation +representational +representations +representative +representatives +represented +representing +represents +repress +repressed +represses +repressing +repression +repressions +repressive +repressively +reprice +repriced +reprices +repricing +reprieve +reprieved +reprieves +reprieving +reprimand +reprimanded +reprimanding +reprimands +reprint +reprinted +reprinting +reprints +reprisal +reprisals +reprise +reprised +reprises +reprising +reproach +reproachable +reproached +reproaches +reproachful +reproachfully +reproaching +reprobate +reprobates +reprocess +reprocessed +reprocesses +reprocessing +reproduce +reproduced +reproducer +reproducers +reproduces +reproducible +reproducing +reproduction +reproductions +reproductive +reprogram +reprogramed +reprograming +reprogrammed +reprogramming +reprograms +reproof +reproofs +reprove +reproved +reproves +reproving +reprovingly +reps +reptile +reptiles +reptilian +reptilians +republic +republican +republicanism +republicans +republication +republications +republics +republish +republished +republishes +republishing +repudiate +repudiated +repudiates +repudiating +repudiation +repudiations +repudiator +repudiators +repugnance +repugnant +repulse +repulsed +repulses +repulsing +repulsion +repulsive +repulsively +repulsiveness +repurchase +repurchased +repurchases +repurchasing +reputability +reputable +reputably +reputation +reputations +repute +reputed +reputedly +reputes +reputing +request +requested +requesting +requests +requiem +requiems +require +required +requirement +requirements +requires +requiring +requisite +requisites +requisition +requisitioned +requisitioning +requisitions +requital +requite +requited +requiter +requiters +requites +requiting +reran +reread +rereading +rereads +rerecord +rerecorded +rerecording +rerecords +reroute +rerouted +reroutes +rerouting +rerun +rerunning +reruns +resalable +resale +resales +reschedule +rescheduled +reschedules +rescheduling +rescind +rescinded +rescinding +rescinds +rescission +rescue +rescued +rescuer +rescuers +rescues +rescuing +reseal +resealable +resealed +resealing +reseals +research +researched +researcher +researchers +researches +researching +resection +resections +reseed +reseeded +reseeding +reseeds +resell +reselling +resells +resemblance +resemblances +resemble +resembled +resembles +resembling +resent +resented +resentful +resentfully +resentfulness +resenting +resentment +resentments +resents +reserpine +reservation +reservations +reserve +reserved +reservedly +reservedness +reserves +reserving +reservist +reservists +reservoir +reservoirs +reset +resets +resetting +resettle +resettled +resettlement +resettles +resettling +resew +resewed +resewing +resewn +resews +reshape +reshaped +reshapes +reshaping +resharpen +resharpened +resharpening +resharpens +reship +reshipment +reshipped +reshipping +reships +reshuffle +reshuffled +reshuffles +reshuffling +reside +resided +residence +residences +residencies +residency +resident +residential +residents +resides +residing +residual +residuals +residue +residues +residuum +resign +resignation +resignations +resigned +resignedly +resigning +resigns +resilience +resiliency +resilient +resiliently +resin +resinous +resins +resist +resistance +resistances +resistant +resisted +resister +resisters +resistible +resisting +resistless +resistor +resistors +resists +resold +resole +resoled +resoles +resoling +resolute +resolutely +resoluteness +resolution +resolutions +resolvable +resolve +resolved +resolves +resolving +resonance +resonances +resonant +resonantly +resonate +resonated +resonates +resonating +resonator +resonators +resorption +resort +resorted +resorting +resorts +resound +resounded +resounding +resoundingly +resounds +resource +resourced +resourceful +resourcefully +resourcefulness +resources +resourcing +resow +resowed +resowing +resown +resows +respect +respectability +respectable +respectably +respected +respecter +respecters +respectful +respectfully +respectfulness +respecting +respective +respectively +respects +respell +respelled +respelling +respells +respelt +respiration +respirator +respirators +respiratory +respire +respired +respires +respiring +respite +respites +resplendence +resplendent +resplendently +respond +responded +respondent +respondents +responding +responds +response +responses +responsibilities +responsibility +responsible +responsibly +responsive +responsively +responsiveness +respray +resprayed +respraying +resprays +rest +restaff +restaffed +restaffing +restaffs +restart +restarted +restarting +restarts +restate +restated +restatement +restatements +restates +restating +restaurant +restauranteur +restauranteurs +restaurants +restaurateur +restaurateurs +rested +restful +restfuller +restfullest +restfully +restfulness +resting +restitch +restitched +restitches +restitching +restitution +restive +restively +restiveness +restless +restlessly +restlessness +restock +restocked +restocking +restocks +restoration +restorations +restorative +restoratives +restore +restored +restorer +restorers +restores +restoring +restrain +restrained +restrainer +restrainers +restraining +restrains +restraint +restraints +restrengthen +restrengthened +restrengthening +restrengthens +restrict +restricted +restricting +restriction +restrictions +restrictive +restrictively +restrictiveness +restricts +restring +restringing +restrings +restroom +restrooms +restructure +restructured +restructures +restructuring +restructurings +restrung +rests +restudied +restudies +restudy +restudying +restyle +restyled +restyles +restyling +resubmit +resubmits +resubmitted +resubmitting +resubscribe +resubscribed +resubscribes +resubscribing +result +resultant +resultants +resulted +resulting +results +resume +resumed +resumes +resuming +resumption +resumptions +resupplied +resupplies +resupply +resupplying +resurface +resurfaced +resurfaces +resurfacing +resurgence +resurgences +resurgent +resurrect +resurrected +resurrecting +resurrection +resurrections +resurrects +resurvey +resurveyed +resurveying +resurveys +resuscitate +resuscitated +resuscitates +resuscitating +resuscitation +resuscitator +resuscitators +retail +retailed +retailer +retailers +retailing +retails +retain +retained +retainer +retainers +retaining +retains +retake +retaken +retakes +retaking +retaliate +retaliated +retaliates +retaliating +retaliation +retaliations +retaliative +retaliatory +retard +retardant +retardants +retardation +retarded +retarder +retarders +retarding +retards +retaught +retch +retched +retches +retching +reteach +reteaches +reteaching +retell +retelling +retells +retention +retentive +retentively +retentiveness +retest +retested +retesting +retests +rethink +rethinking +rethinks +rethought +reticence +reticent +reticently +reticulation +reticulations +retie +retied +reties +retina +retinae +retinal +retinas +retinue +retinues +retire +retired +retiree +retirees +retirement +retirements +retires +retiring +retold +retook +retool +retooled +retooling +retools +retort +retorted +retorting +retorts +retouch +retouched +retouches +retouching +retrace +retraced +retraces +retracing +retract +retractable +retracted +retractile +retracting +retraction +retractions +retracts +retrain +retrained +retraining +retrains +retread +retreaded +retreading +retreads +retreat +retreated +retreating +retreats +retrench +retrenched +retrenches +retrenching +retrenchment +retrenchments +retrial +retrials +retribution +retributions +retributive +retried +retries +retrievable +retrieval +retrievals +retrieve +retrieved +retriever +retrievers +retrieves +retrieving +retro +retroactive +retroactively +retrod +retrodden +retrofire +retrofired +retrofires +retrofiring +retrofit +retrofits +retrofitted +retrofitting +retrograde +retrograded +retrogrades +retrograding +retrogress +retrogressed +retrogresses +retrogressing +retrogression +retrogressive +retrorocket +retrorockets +retros +retrospect +retrospected +retrospecting +retrospection +retrospective +retrospectively +retrospectives +retrospects +retrovirus +retroviruses +retry +retrying +retsina +return +returnable +returnables +returned +returnee +returnees +returner +returners +returning +returns +retying +retype +retyped +retypes +retyping +reunification +reunified +reunifies +reunify +reunifying +reunion +reunions +reunite +reunited +reunites +reuniting +reupholster +reupholstered +reupholstering +reupholsters +reusable +reuse +reused +reuses +reusing +revaluation +revaluations +revalue +revalued +revalues +revaluing +revamp +revamped +revamping +revampings +revamps +reveal +revealed +revealing +revealingly +reveals +reveille +revel +revelation +revelations +reveled +reveler +revelers +reveling +revelled +reveller +revellers +revelling +revelries +revelry +revels +revenge +revenged +revengeful +revengefully +revenges +revenging +revenue +revenuer +revenuers +revenues +reverberate +reverberated +reverberates +reverberating +reverberation +reverberations +revere +revered +reverence +reverenced +reverences +reverencing +reverend +reverends +reverent +reverential +reverentially +reverently +reveres +reverie +reveries +revering +revers +reversal +reversals +reverse +reversed +reversely +reverses +reversible +reversibly +reversing +reversion +revert +reverted +revertible +reverting +reverts +revery +revetment +revetments +review +reviewed +reviewer +reviewers +reviewing +reviews +revile +reviled +revilement +reviler +revilers +reviles +reviling +revise +revised +reviser +revisers +revises +revising +revision +revisionism +revisionist +revisionists +revisions +revisit +revisited +revisiting +revisits +revitalization +revitalize +revitalized +revitalizes +revitalizing +revival +revivalism +revivalist +revivalists +revivals +revive +revived +revives +revivification +revivified +revivifies +revivify +revivifying +reviving +revocable +revocation +revocations +revokable +revoke +revoked +revokes +revoking +revolt +revolted +revolting +revoltingly +revolts +revolution +revolutionaries +revolutionary +revolutionise +revolutionised +revolutionises +revolutionising +revolutionist +revolutionists +revolutionize +revolutionized +revolutionizes +revolutionizing +revolutions +revolvable +revolve +revolved +revolver +revolvers +revolves +revolving +revs +revue +revues +revulsion +revved +revving +reward +rewarded +rewarding +rewards +rewarm +rewarmed +rewarming +rewarms +rewash +rewashed +rewashes +rewashing +reweave +reweaved +reweaves +reweaving +rewed +rewedded +rewedding +reweds +reweigh +reweighed +reweighing +reweighs +rewind +rewinding +rewinds +rewire +rewired +rewires +rewiring +reword +reworded +rewording +rewords +rework +reworked +reworking +reworks +rewound +rewove +rewoven +rewrite +rewrites +rewriting +rewritten +rewrote +rezone +rezoned +rezones +rezoning +rhapsodic +rhapsodical +rhapsodies +rhapsodize +rhapsodized +rhapsodizes +rhapsodizing +rhapsody +rhea +rheas +rhenium +rheostat +rheostats +rhesus +rhesuses +rhetoric +rhetorical +rhetorically +rhetorician +rhetoricians +rheum +rheumatic +rheumatically +rheumatics +rheumatism +rheumatoid +rheumier +rheumiest +rheumy +rhinestone +rhinestones +rhinitis +rhino +rhinoceri +rhinoceros +rhinoceroses +rhinos +rhizome +rhizomes +rhodium +rhododendron +rhododendrons +rhombi +rhomboid +rhomboidal +rhomboids +rhombus +rhombuses +rhos +rhubarb +rhubarbs +rhyme +rhymed +rhymer +rhymers +rhymes +rhymester +rhymesters +rhyming +rhythm +rhythmic +rhythmical +rhythmically +rhythms +rial +rials +ribald +ribaldry +ribbed +ribber +ribbers +ribbing +ribbon +ribbons +riboflavin +ribs +rice +riced +ricer +ricers +rices +rich +richer +riches +richest +richly +richness +ricing +rick +ricked +ricketier +ricketiest +rickets +rickety +ricking +rickrack +ricks +ricksha +rickshas +rickshaw +rickshaws +ricochet +ricocheted +ricocheting +ricochets +ricochetted +ricochetting +ricotta +riddance +ridded +ridden +ridding +riddle +riddled +riddles +riddling +ride +rider +riderless +riders +ridership +rides +ridge +ridged +ridgepole +ridgepoles +ridges +ridgier +ridgiest +ridging +ridgy +ridicule +ridiculed +ridicules +ridiculing +ridiculous +ridiculously +ridiculousness +riding +rids +rife +rifer +rifest +riff +riffed +riffing +riffle +riffled +riffles +riffling +riffraff +riffs +rifle +rifled +rifleman +riflemen +rifler +riflers +rifles +rifling +rift +rifted +rifting +rifts +rigamarole +rigamaroles +rigatoni +rigged +rigger +riggers +rigging +right +righted +righteous +righteously +righteousness +righter +rightest +rightful +rightfully +rightfulness +righting +rightism +rightist +rightists +rightly +rightmost +rightness +rights +rightsize +rightsized +rightsizes +rightsizing +rightward +rightwards +rigid +rigidity +rigidly +rigidness +rigmarole +rigmaroles +rigor +rigorous +rigorously +rigorousness +rigors +rigour +rigours +rigs +rile +riled +riles +riling +rill +rills +rime +rimed +rimes +riming +rimless +rimmed +rimming +rims +rind +rinds +ring +ringed +ringer +ringers +ringgit +ringgits +ringing +ringleader +ringleaders +ringlet +ringlets +ringlike +ringmaster +ringmasters +rings +ringside +ringworm +rink +rinks +rinse +rinsed +rinses +rinsing +riot +rioted +rioter +rioters +rioting +riotous +riotously +riots +riparian +ripcord +ripcords +ripe +ripely +ripen +ripened +ripeness +ripening +ripens +riper +ripest +ripoff +ripoffs +ripost +riposte +riposted +ripostes +riposting +riposts +ripped +ripper +rippers +ripping +ripple +rippled +ripples +rippling +ripply +rips +ripsaw +ripsaws +riptide +riptides +rise +risen +riser +risers +rises +risibility +risible +rising +risings +risk +risked +riskier +riskiest +riskily +riskiness +risking +risks +risky +risotto +risottos +risque +rite +rites +ritual +ritualism +ritualistic +ritualistically +ritually +rituals +ritzier +ritziest +ritzy +rival +rivaled +rivaling +rivalled +rivalling +rivalries +rivalry +rivals +rive +rived +riven +river +riverbank +riverbanks +riverbed +riverbeds +riverboat +riverboats +rivers +riverside +riversides +rives +rivet +riveted +riveter +riveters +riveting +rivets +rivetted +rivetting +riving +rivulet +rivulets +riyal +riyals +roach +roaches +road +roadbed +roadbeds +roadblock +roadblocked +roadblocking +roadblocks +roadhouse +roadhouses +roadie +roadies +roadkill +roadrunner +roadrunners +roads +roadshow +roadshows +roadside +roadsides +roadster +roadsters +roadway +roadways +roadwork +roadworthy +roam +roamed +roamer +roamers +roaming +roams +roan +roans +roar +roared +roarer +roarers +roaring +roars +roast +roasted +roaster +roasters +roasting +roastings +roasts +robbed +robber +robberies +robbers +robbery +robbing +robe +robed +robes +robin +robing +robins +robot +robotic +robotics +robotize +robotized +robotizes +robotizing +robots +robs +robust +robuster +robustest +robustly +robustness +rock +rockabilly +rockbound +rocked +rocker +rockers +rocket +rocketed +rocketing +rocketry +rockets +rockfall +rockfalls +rockier +rockiest +rockiness +rocking +rocks +rocky +rococo +rode +rodent +rodents +rodeo +rodeos +rods +roebuck +roebucks +roentgen +roentgens +roes +roger +rogered +rogering +rogers +rogue +roguery +rogues +roguish +roguishly +roguishness +roil +roiled +roiling +roils +roister +roistered +roisterer +roisterers +roistering +roisters +role +roles +roll +rollback +rollbacks +rolled +roller +rollers +rollerskating +rollick +rollicked +rollicking +rollicks +rolling +rollover +rollovers +rolls +romaine +roman +romance +romanced +romancer +romancers +romances +romancing +romantic +romantically +romanticism +romanticist +romanticists +romanticize +romanticized +romanticizes +romanticizing +romantics +romeo +romeos +romp +romped +romper +rompers +romping +romps +rondo +rondos +rood +roods +roof +roofed +roofer +roofers +roofing +roofless +roofs +rooftop +rooftops +rook +rooked +rookeries +rookery +rookie +rookies +rooking +rooks +room +roomed +roomer +roomers +roomette +roomettes +roomful +roomfuls +roomier +roomiest +roominess +rooming +roommate +roommates +rooms +roomy +roost +roosted +rooster +roosters +roosting +roosts +root +rooted +rooter +rooters +rooting +rootless +rootlet +rootlets +roots +rope +roped +roper +ropers +ropes +ropier +ropiest +roping +ropy +rosaries +rosary +rose +roseate +rosebud +rosebuds +rosebush +rosebushes +rosemary +roses +rosette +rosettes +rosewater +rosewood +rosewoods +rosier +rosiest +rosily +rosin +rosined +rosiness +rosining +rosins +roster +rosters +rostra +rostrum +rostrums +rosy +rotaries +rotary +rotate +rotated +rotates +rotating +rotation +rotations +rotatory +rote +rotgut +rotisserie +rotisseries +rotogravure +rotogravures +rotor +rotors +rototiller +rototillers +rots +rotted +rotten +rottener +rottenest +rottenly +rottenness +rotting +rotund +rotunda +rotundas +rotundity +rotundness +rouble +roubles +roue +roues +rouge +rouged +rouges +rough +roughage +roughed +roughen +roughened +roughening +roughens +rougher +roughest +roughhouse +roughhoused +roughhouses +roughhousing +roughing +roughly +roughneck +roughnecked +roughnecking +roughnecks +roughness +roughs +roughshod +rouging +roulette +round +roundabout +roundabouts +rounded +roundelay +roundelays +rounder +roundest +roundhouse +roundhouses +rounding +roundish +roundly +roundness +rounds +roundtable +roundtables +roundup +roundups +roundworm +roundworms +rouse +roused +rouses +rousing +roust +roustabout +roustabouts +rousted +rousting +rousts +rout +route +routed +router +routers +routes +routine +routinely +routines +routing +routinize +routinized +routinizes +routinizing +routs +rove +roved +rover +rovers +roves +roving +rowboat +rowboats +rowdier +rowdies +rowdiest +rowdily +rowdiness +rowdy +rowdyism +rowed +rowel +roweled +roweling +rowelled +rowelling +rowels +rower +rowers +rowing +rows +royal +royalist +royalists +royally +royals +royalties +royalty +rubati +rubato +rubatos +rubbed +rubber +rubberier +rubberiest +rubberize +rubberized +rubberizes +rubberizing +rubberneck +rubbernecked +rubbernecker +rubberneckers +rubbernecking +rubbernecks +rubbers +rubbery +rubbing +rubbish +rubbishy +rubble +rubdown +rubdowns +rube +rubella +rubes +rubicund +rubidium +rubier +rubies +rubiest +ruble +rubles +rubric +rubrics +rubs +ruby +rucksack +rucksacks +ruckus +ruckuses +rudder +rudderless +rudders +ruddier +ruddiest +ruddiness +ruddy +rude +rudely +rudeness +ruder +rudest +rudiment +rudimentary +rudiments +rued +rueful +ruefully +ruefulness +rueing +rues +ruff +ruffed +ruffian +ruffianly +ruffians +ruffing +ruffle +ruffled +ruffles +ruffling +ruffly +ruffs +rugby +rugged +ruggeder +ruggedest +ruggedly +ruggedness +rugs +ruin +ruination +ruined +ruing +ruining +ruinous +ruinously +ruins +rule +ruled +ruler +rulers +rules +ruling +rulings +rumba +rumbaed +rumbaing +rumbas +rumble +rumbled +rumbles +rumbling +rumblings +ruminant +ruminants +ruminate +ruminated +ruminates +ruminating +rumination +ruminations +ruminative +rummage +rummaged +rummages +rummaging +rummer +rummest +rummy +rumor +rumored +rumoring +rumormonger +rumormongers +rumors +rumour +rumoured +rumouring +rumours +rump +rumple +rumpled +rumples +rumpling +rumply +rumps +rumpus +rumpuses +rums +runabout +runabouts +runaround +runarounds +runaway +runaways +rundown +rundowns +rune +runes +rung +rungs +runic +runlet +runlets +runnel +runnels +runner +runners +runnier +runniest +running +runny +runoff +runoffs +runs +runt +runtier +runtiest +runts +runty +runway +runways +rupee +rupees +rupiah +rupiahs +rupture +ruptured +ruptures +rupturing +rural +ruse +ruses +rush +rushed +rusher +rushers +rushes +rushing +rushy +rusk +rusks +russet +russets +rust +rusted +rustic +rustically +rusticate +rusticated +rusticates +rusticating +rustication +rusticity +rustics +rustier +rustiest +rustiness +rusting +rustle +rustled +rustler +rustlers +rustles +rustling +rustproof +rustproofed +rustproofing +rustproofs +rusts +rusty +rutabaga +rutabagas +ruthenium +rutherfordium +ruthless +ruthlessly +ruthlessness +ruts +rutted +ruttier +ruttiest +rutting +rutty +sabbath +sabbaths +sabbatical +sabbaticals +saber +sabers +sable +sables +sabot +sabotage +sabotaged +sabotages +sabotaging +saboteur +saboteurs +sabots +sabra +sabras +sabre +sabres +saccharin +saccharine +sacerdotal +sachem +sachems +sachet +sachets +sack +sackcloth +sacked +sacker +sackers +sackful +sackfuls +sacking +sacks +sacra +sacrament +sacramental +sacraments +sacred +sacredly +sacredness +sacrifice +sacrificed +sacrifices +sacrificial +sacrificially +sacrificing +sacrilege +sacrileges +sacrilegious +sacrilegiously +sacristan +sacristans +sacristies +sacristy +sacroiliac +sacroiliacs +sacrosanct +sacrosanctness +sacrum +sacs +sadden +saddened +saddening +saddens +sadder +saddest +saddle +saddlebag +saddlebags +saddled +saddles +saddling +sadism +sadist +sadistic +sadistically +sadists +sadly +sadness +sadomasochism +sadomasochist +sadomasochistic +sadomasochists +safari +safaried +safariing +safaris +safe +safeguard +safeguarded +safeguarding +safeguards +safekeeping +safely +safeness +safer +safes +safest +safeties +safety +safflower +safflowers +saffron +saffrons +saga +sagacious +sagaciously +sagacity +sagas +sage +sagebrush +sagely +sager +sages +sagest +sagged +saggier +saggiest +sagging +saggy +sago +sags +saguaro +saguaros +sahib +sahibs +said +sail +sailboard +sailboarder +sailboarders +sailboarding +sailboards +sailboat +sailboats +sailcloth +sailed +sailfish +sailfishes +sailing +sailings +sailor +sailors +sailplane +sailplanes +sails +saint +sainted +sainthood +saintlier +saintliest +saintlike +saintliness +saintly +saints +saith +sake +saki +salaam +salaamed +salaaming +salaams +salable +salacious +salaciously +salaciousness +salacity +salad +salads +salamander +salamanders +salami +salamis +salaried +salaries +salary +sale +saleable +sales +salesclerk +salesclerks +salesgirl +salesgirls +salesladies +saleslady +salesman +salesmanship +salesmen +salespeople +salesperson +salespersons +saleswoman +saleswomen +salience +salient +saliently +salients +saline +salines +salinity +saliva +salivary +salivate +salivated +salivates +salivating +salivation +sallied +sallies +sallow +sallower +sallowest +sallowness +sally +sallying +salmon +salmonella +salmonellae +salmonellas +salmons +salon +salons +saloon +saloons +salsa +salsas +salt +saltbox +saltboxes +saltcellar +saltcellars +salted +salter +saltest +saltier +saltiest +saltine +saltines +saltiness +salting +saltpeter +saltpetre +salts +saltshaker +saltshakers +saltwater +salty +salubrious +salutary +salutation +salutations +salutatorian +salutatorians +salutatory +salute +saluted +salutes +saluting +salvage +salvageable +salvaged +salvages +salvaging +salvation +salve +salved +salver +salvers +salves +salving +salvo +salvoes +salvos +samarium +samba +sambaed +sambaing +sambas +same +sameness +samovar +samovars +sampan +sampans +sample +sampled +sampler +samplers +samples +sampling +samurai +sanatoria +sanatorium +sanatoriums +sancta +sanctification +sanctified +sanctifies +sanctify +sanctifying +sanctimonious +sanctimoniously +sanctimoniousness +sanctimony +sanction +sanctioned +sanctioning +sanctions +sanctity +sanctuaries +sanctuary +sanctum +sanctums +sand +sandal +sandals +sandalwood +sandbag +sandbagged +sandbagging +sandbags +sandbank +sandbanks +sandbar +sandbars +sandblast +sandblasted +sandblaster +sandblasters +sandblasting +sandblasts +sandbox +sandboxes +sandcastle +sandcastles +sanded +sander +sanders +sandhog +sandhogs +sandier +sandiest +sandiness +sanding +sandlot +sandlots +sandlotter +sandlotters +sandman +sandmen +sandpaper +sandpapered +sandpapering +sandpapers +sandpiper +sandpipers +sands +sandstone +sandstorm +sandstorms +sandwich +sandwiched +sandwiches +sandwiching +sandy +sane +sanely +saneness +saner +sanest +sang +sangfroid +sangria +sanguinary +sanguine +sanguinely +sanitaria +sanitarian +sanitarians +sanitarium +sanitariums +sanitary +sanitation +sanitize +sanitized +sanitizes +sanitizing +sanity +sank +sans +sapience +sapient +sapless +sapling +saplings +sapped +sapphire +sapphires +sappier +sappiest +sappiness +sapping +sappy +saprophyte +saprophytes +saprophytic +saps +sapsucker +sapsuckers +sapwood +saran +sarape +sarapes +sarcasm +sarcasms +sarcastic +sarcastically +sarcoma +sarcomas +sarcomata +sarcophagi +sarcophagus +sarcophaguses +sardine +sardines +sardonic +sardonically +saree +sarees +sarge +sarges +sari +saris +sarong +sarongs +sarsaparilla +sarsaparillas +sartorial +sartorially +sash +sashay +sashayed +sashaying +sashays +sashes +sass +sassafras +sassafrases +sassed +sasses +sassier +sassiest +sassing +sassy +satanic +satanical +satanically +satanism +satanist +satanists +satchel +satchels +sate +sated +sateen +satellite +satellites +sates +satiable +satiate +satiated +satiates +satiating +satiation +satiety +satin +sating +satinwood +satinwoods +satiny +satire +satires +satiric +satirical +satirically +satirist +satirists +satirize +satirized +satirizes +satirizing +satisfaction +satisfactions +satisfactorily +satisfactory +satisfied +satisfies +satisfy +satisfying +satisfyingly +satori +satrap +satraps +saturate +saturated +saturates +saturating +saturation +saturnine +satyr +satyriasis +satyric +satyrs +sauce +sauced +saucepan +saucepans +saucer +saucers +sauces +saucier +sauciest +saucily +sauciness +saucing +saucy +sauerkraut +sauna +saunaed +saunaing +saunas +saunter +sauntered +sauntering +saunters +saurian +sauropod +sauropods +sausage +sausages +saute +sauted +sauteed +sauteing +sauterne +sauternes +sautes +savable +savage +savaged +savagely +savageness +savager +savageries +savagery +savages +savagest +savaging +savanna +savannah +savannahs +savannas +savant +savants +save +saveable +saved +saver +savers +saves +saving +savings +savior +saviors +saviour +saviours +savor +savored +savorier +savories +savoriest +savoriness +savoring +savors +savory +savour +savoured +savourier +savouries +savouriest +savouring +savours +savoury +savoy +savoys +savvied +savvier +savvies +savviest +savvy +savvying +sawbones +sawboneses +sawbuck +sawbucks +sawdust +sawed +sawflies +sawfly +sawhorse +sawhorses +sawing +sawmill +sawmills +sawn +saws +sawyer +sawyers +saxes +saxifrage +saxifrages +saxophone +saxophones +saxophonist +saxophonists +saying +sayings +says +scab +scabbard +scabbards +scabbed +scabbier +scabbiest +scabbiness +scabbing +scabby +scabies +scabrous +scabs +scad +scads +scaffold +scaffolding +scaffolds +scalawag +scalawags +scald +scalded +scalding +scalds +scale +scaled +scaleless +scalene +scales +scalier +scaliest +scaliness +scaling +scallion +scallions +scallop +scalloped +scalloping +scallops +scalp +scalped +scalpel +scalpels +scalper +scalpers +scalping +scalps +scaly +scam +scammed +scamming +scamp +scamper +scampered +scampering +scampers +scampi +scampies +scamps +scams +scan +scandal +scandalize +scandalized +scandalizes +scandalizing +scandalmonger +scandalmongers +scandalous +scandalously +scandals +scandium +scanned +scanner +scanners +scanning +scans +scansion +scant +scanted +scanter +scantest +scantier +scantiest +scantily +scantiness +scanting +scantly +scantness +scants +scanty +scapegoat +scapegoated +scapegoating +scapegoats +scapegrace +scapegraces +scapula +scapulae +scapular +scapulars +scapulas +scar +scarab +scarabs +scarce +scarcely +scarceness +scarcer +scarcest +scarcity +scare +scarecrow +scarecrows +scared +scareder +scaredest +scaremonger +scaremongers +scares +scarf +scarfed +scarfing +scarfs +scarier +scariest +scarification +scarified +scarifies +scarify +scarifying +scarily +scariness +scaring +scarlatina +scarlet +scarp +scarped +scarping +scarps +scarred +scarring +scars +scarves +scary +scat +scathing +scathingly +scatological +scatology +scats +scatted +scatter +scatterbrain +scatterbrained +scatterbrains +scattered +scattering +scatterings +scatters +scatting +scavenge +scavenged +scavenger +scavengers +scavenges +scavenging +scenario +scenarios +scenarist +scenarists +scene +scenery +scenes +scenic +scenically +scent +scented +scenting +scentless +scents +scepter +scepters +sceptic +sceptical +scepticism +sceptics +sceptre +sceptres +schedule +scheduled +schedules +scheduling +schematic +schematically +schematics +scheme +schemed +schemer +schemers +schemes +scheming +scherzi +scherzo +scherzos +schilling +schillings +schism +schismatic +schismatics +schisms +schist +schizo +schizoid +schizoids +schizophrenia +schizophrenic +schizophrenics +schizos +schlemiel +schlemiels +schlep +schlepp +schlepped +schlepping +schlepps +schleps +schlock +schlocky +schmaltz +schmaltzier +schmaltziest +schmaltzy +schmalz +schmo +schmoe +schmoes +schmooze +schmoozed +schmoozes +schmoozing +schmos +schmuck +schmucks +schnapps +schnaps +schnauzer +schnauzers +schnitzel +schnitzels +schnook +schnooks +schnoz +schnozes +schnozzle +schnozzles +scholar +scholarly +scholars +scholarship +scholarships +scholastic +scholastically +school +schoolbag +schoolbags +schoolbook +schoolbooks +schoolboy +schoolboys +schoolchild +schoolchildren +schooled +schoolfellow +schoolfellows +schoolgirl +schoolgirls +schoolhouse +schoolhouses +schooling +schoolmarm +schoolmarmish +schoolmarms +schoolmaster +schoolmasters +schoolmate +schoolmates +schoolmistress +schoolmistresses +schoolroom +schoolrooms +schools +schoolteacher +schoolteachers +schoolwork +schoolyard +schoolyards +schooner +schooners +schrod +schrods +schuss +schussboomer +schussboomers +schussed +schusses +schussing +schwa +schwas +sciatic +sciatica +science +sciences +scientific +scientifically +scientist +scientists +scimitar +scimitars +scintilla +scintillas +scintillate +scintillated +scintillates +scintillating +scintillation +scion +scions +scissor +scissored +scissoring +scissors +sclerosis +sclerotic +scoff +scoffed +scoffer +scoffers +scoffing +scofflaw +scofflaws +scoffs +scold +scolded +scolding +scoldings +scolds +scoliosis +scollop +scolloped +scolloping +scollops +sconce +sconces +scone +scones +scoop +scooped +scoopful +scoopfuls +scooping +scoops +scoot +scooted +scooter +scooters +scooting +scoots +scope +scoped +scopes +scoping +scorbutic +scorch +scorched +scorcher +scorchers +scorches +scorching +score +scoreboard +scoreboards +scorecard +scorecards +scored +scorekeeper +scorekeepers +scoreless +scorer +scorers +scores +scoring +scorn +scorned +scorner +scorners +scornful +scornfully +scorning +scorns +scorpion +scorpions +scotch +scotched +scotches +scotching +scoundrel +scoundrels +scour +scoured +scourer +scourers +scourge +scourged +scourges +scourging +scouring +scours +scout +scouted +scouting +scoutmaster +scoutmasters +scouts +scow +scowl +scowled +scowling +scowls +scows +scrabble +scrabbled +scrabbler +scrabblers +scrabbles +scrabbling +scrag +scraggier +scraggiest +scragglier +scraggliest +scraggly +scraggy +scrags +scram +scramble +scrambled +scrambler +scramblers +scrambles +scrambling +scrammed +scramming +scrams +scrap +scrapbook +scrapbooks +scrape +scraped +scraper +scrapers +scrapes +scrapheap +scrapheaps +scraping +scrapped +scrapper +scrappers +scrappier +scrappiest +scrapping +scrappy +scraps +scrapyard +scrapyards +scratch +scratched +scratches +scratchier +scratchiest +scratchily +scratchiness +scratching +scratchy +scrawl +scrawled +scrawling +scrawls +scrawly +scrawnier +scrawniest +scrawniness +scrawny +scream +screamed +screamer +screamers +screaming +screams +scree +screech +screeched +screeches +screechier +screechiest +screeching +screechy +screen +screened +screening +screenings +screenplay +screenplays +screens +screenwriter +screenwriters +screenwriting +screes +screw +screwball +screwballs +screwdriver +screwdrivers +screwed +screwier +screwiest +screwiness +screwing +screws +screwworm +screwworms +screwy +scribal +scribble +scribbled +scribbler +scribblers +scribbles +scribbling +scribe +scribes +scrim +scrimmage +scrimmaged +scrimmages +scrimmaging +scrimp +scrimped +scrimping +scrimps +scrims +scrimshaw +scrimshawed +scrimshawing +scrimshaws +scrip +scrips +script +scripted +scripting +scripts +scriptural +scripture +scriptures +scriptwriter +scriptwriters +scrivener +scriveners +scrod +scrods +scrofula +scrofulous +scroll +scrolled +scrolling +scrolls +scrooge +scrooges +scrota +scrotal +scrotum +scrotums +scrounge +scrounged +scrounger +scroungers +scrounges +scroungier +scroungiest +scrounging +scroungy +scrub +scrubbed +scrubber +scrubbers +scrubbier +scrubbiest +scrubbing +scrubby +scrubs +scruff +scruffier +scruffiest +scruffily +scruffiness +scruffs +scruffy +scrumptious +scrumptiously +scrunch +scrunched +scrunches +scrunchie +scrunchies +scrunching +scrunchy +scruple +scrupled +scruples +scrupling +scrupulosity +scrupulous +scrupulously +scrupulousness +scrutinize +scrutinized +scrutinizes +scrutinizing +scrutiny +scuba +scubaed +scubaing +scubas +scud +scudded +scudding +scuds +scuff +scuffed +scuffing +scuffle +scuffled +scuffles +scuffling +scuffs +scull +sculled +sculler +sculleries +scullers +scullery +sculling +scullion +scullions +sculls +sculpt +sculpted +sculpting +sculptor +sculptors +sculptress +sculptresses +sculpts +sculptural +sculpture +sculptured +sculptures +sculpturing +scum +scumbag +scumbags +scummed +scummier +scummiest +scumming +scummy +scums +scupper +scuppered +scuppering +scuppers +scurf +scurfier +scurfiest +scurfy +scurried +scurries +scurrility +scurrilous +scurrilously +scurrilousness +scurry +scurrying +scurvier +scurviest +scurvily +scurvy +scutcheon +scutcheons +scuttle +scuttlebutt +scuttled +scuttles +scuttling +scuzzier +scuzziest +scuzzy +scythe +scythed +scythes +scything +seabed +seabeds +seabird +seabirds +seaboard +seaboards +seaborne +seacoast +seacoasts +seafarer +seafarers +seafaring +seafloor +seafloors +seafood +seafront +seafronts +seagoing +seagull +seagulls +seahorse +seahorses +seal +sealant +sealants +sealed +sealer +sealers +sealing +seals +sealskin +seam +seaman +seamanship +seamed +seamen +seamier +seamiest +seaming +seamless +seamlessly +seams +seamstress +seamstresses +seamy +seance +seances +seaplane +seaplanes +seaport +seaports +sear +search +searched +searcher +searchers +searches +searching +searchingly +searchlight +searchlights +seared +searing +sears +seas +seascape +seascapes +seashell +seashells +seashore +seashores +seasick +seasickness +seaside +seasides +season +seasonable +seasonably +seasonal +seasonally +seasoned +seasoning +seasonings +seasons +seat +seated +seating +seatmate +seatmates +seats +seawall +seawalls +seaward +seawards +seawater +seaway +seaways +seaweed +seaworthier +seaworthiest +seaworthiness +seaworthy +sebaceous +seborrhea +seborrhoea +secant +secants +secede +seceded +secedes +seceding +secession +secessionist +secessionists +seclude +secluded +secludes +secluding +seclusion +seclusive +second +secondaries +secondarily +secondary +seconded +seconder +seconders +secondhand +seconding +secondly +seconds +secrecy +secret +secretarial +secretariat +secretariats +secretaries +secretary +secretaryship +secrete +secreted +secretes +secreting +secretion +secretions +secretive +secretively +secretiveness +secretly +secretory +secrets +secs +sect +sectarian +sectarianism +sectarians +sectaries +sectary +section +sectional +sectionalism +sectionals +sectioned +sectioning +sections +sector +sectors +sects +secular +secularism +secularist +secularists +secularization +secularize +secularized +secularizes +secularizing +secure +secured +securely +securer +secures +securest +securing +securities +security +sedan +sedans +sedate +sedated +sedately +sedateness +sedater +sedates +sedatest +sedating +sedation +sedative +sedatives +sedentary +sedge +sedgy +sediment +sedimentary +sedimentation +sediments +sedition +seditious +seduce +seduced +seducer +seducers +seduces +seducing +seduction +seductions +seductive +seductively +seductiveness +seductress +seductresses +sedulous +sedulously +seed +seedbed +seedbeds +seedcase +seedcases +seeded +seeder +seeders +seedier +seediest +seediness +seeding +seedless +seedling +seedlings +seedpod +seedpods +seeds +seedy +seeing +seek +seeker +seekers +seeking +seeks +seem +seemed +seeming +seemingly +seemlier +seemliest +seemliness +seemly +seems +seen +seep +seepage +seeped +seeping +seeps +seer +seers +seersucker +sees +seesaw +seesawed +seesawing +seesaws +seethe +seethed +seethes +seething +segment +segmentation +segmented +segmenting +segments +segregate +segregated +segregates +segregating +segregation +segregationist +segregationists +segue +segued +segueing +segues +seigneur +seigneurs +seignior +seigniorial +seigniors +seine +seined +seiner +seiners +seines +seining +seismic +seismically +seismograph +seismographer +seismographers +seismographic +seismographs +seismography +seismologic +seismological +seismologist +seismologists +seismology +seize +seized +seizes +seizing +seizure +seizures +seldom +select +selected +selecting +selection +selections +selective +selectively +selectivity +selectman +selectmen +selectness +selector +selectors +selects +selenium +selenographer +selenographers +selenography +self +selfish +selfishly +selfishness +selfless +selflessly +selflessness +selfsame +sell +seller +sellers +selling +sellout +sellouts +sells +seltzer +selvage +selvages +selvedge +selvedges +selves +semantic +semantically +semanticist +semanticists +semantics +semaphore +semaphored +semaphores +semaphoring +semblance +semblances +semen +semester +semesters +semi +semiannual +semiannually +semiarid +semiautomatic +semiautomatics +semicircle +semicircles +semicircular +semicolon +semicolons +semiconducting +semiconductor +semiconductors +semiconscious +semidarkness +semidetached +semifinal +semifinalist +semifinalists +semifinals +semigloss +semimonthlies +semimonthly +seminal +seminar +seminarian +seminarians +seminaries +seminars +seminary +semiofficial +semiotic +semiotics +semipermeable +semiprecious +semiprivate +semipro +semiprofessional +semiprofessionals +semipros +semiretired +semis +semiskilled +semisolid +semisweet +semitone +semitones +semitrailer +semitrailers +semitransparent +semitropical +semivowel +semivowels +semiweeklies +semiweekly +semiyearly +semolina +sempstress +sempstresses +senate +senates +senator +senatorial +senators +send +sender +senders +sending +sendoff +sendoffs +sends +senescence +senescent +senile +senility +senior +seniority +seniors +senna +senor +senora +senoras +senorita +senoritas +senors +sensation +sensational +sensationalism +sensationalist +sensationalists +sensationalize +sensationalized +sensationalizes +sensationalizing +sensationally +sensations +sense +sensed +senseless +senselessly +senselessness +senses +sensibilities +sensibility +sensible +sensibleness +sensibly +sensing +sensitive +sensitively +sensitiveness +sensitives +sensitivities +sensitivity +sensitization +sensitize +sensitized +sensitizes +sensitizing +sensor +sensors +sensory +sensual +sensualist +sensualists +sensuality +sensually +sensuous +sensuously +sensuousness +sent +sentence +sentenced +sentences +sentencing +sententious +sententiously +sentience +sentient +sentiment +sentimental +sentimentalism +sentimentalist +sentimentalists +sentimentality +sentimentalization +sentimentalize +sentimentalized +sentimentalizes +sentimentalizing +sentimentally +sentiments +sentinel +sentinels +sentries +sentry +sepal +sepals +separability +separable +separably +separate +separated +separately +separateness +separates +separating +separation +separations +separatism +separatist +separatists +separative +separator +separators +sepia +sepsis +septa +septet +septets +septette +septettes +septic +septicemia +septicemic +septuagenarian +septuagenarians +septum +septums +sepulcher +sepulchered +sepulchering +sepulchers +sepulchral +sepulchre +sepulchred +sepulchres +sepulchring +sequel +sequels +sequence +sequenced +sequences +sequencing +sequential +sequentially +sequester +sequestered +sequestering +sequesters +sequestrate +sequestrated +sequestrates +sequestrating +sequestration +sequestrations +sequin +sequined +sequinned +sequins +sequoia +sequoias +sera +seraglio +seraglios +serape +serapes +seraph +seraphic +seraphim +seraphs +sere +serenade +serenaded +serenades +serenading +serendipitous +serendipity +serene +serenely +sereneness +serener +serenest +serenity +serer +serest +serf +serfdom +serfs +serge +sergeant +sergeants +serial +serialization +serialize +serialized +serializes +serializing +serially +serials +series +serif +serifs +serigraph +serigraphs +serious +seriously +seriousness +sermon +sermonize +sermonized +sermonizes +sermonizing +sermons +serology +serous +serpent +serpentine +serpents +serrate +serrated +serration +serrations +serried +serum +serums +servant +servants +serve +served +server +servers +serves +service +serviceability +serviceable +serviced +serviceman +servicemen +services +servicewoman +servicewomen +servicing +serviette +serviettes +servile +servility +serving +servings +servitor +servitors +servitude +servo +servomechanism +servomechanisms +servomotor +servomotors +servos +sesame +sesames +sesquicentennial +sesquicentennials +session +sessions +setback +setbacks +sets +setscrew +setscrews +settee +settees +setter +setters +setting +settings +settle +settled +settlement +settlements +settler +settlers +settles +settling +setup +setups +seven +sevens +seventeen +seventeens +seventeenth +seventeenths +seventh +sevenths +seventies +seventieth +seventieths +seventy +sever +several +severally +severance +severances +severe +severed +severely +severeness +severer +severest +severing +severity +severs +sewage +sewed +sewer +sewerage +sewers +sewing +sewn +sews +sexagenarian +sexagenarians +sexed +sexes +sexier +sexiest +sexily +sexiness +sexing +sexism +sexist +sexists +sexless +sexologist +sexologists +sexology +sexpot +sexpots +sextant +sextants +sextet +sextets +sextette +sextettes +sexton +sextons +sextuplet +sextuplets +sexual +sexuality +sexually +sexy +shabbier +shabbiest +shabbily +shabbiness +shabby +shack +shackle +shackled +shackles +shackling +shacks +shad +shade +shaded +shades +shadier +shadiest +shadily +shadiness +shading +shadings +shadow +shadowbox +shadowboxed +shadowboxes +shadowboxing +shadowed +shadowier +shadowiest +shadowing +shadows +shadowy +shads +shady +shaft +shafted +shafting +shafts +shag +shagged +shaggier +shaggiest +shagginess +shagging +shaggy +shags +shah +shahs +shake +shakedown +shakedowns +shaken +shakeout +shakeouts +shaker +shakers +shakes +shakeup +shakeups +shakier +shakiest +shakily +shakiness +shaking +shaky +shale +shall +shallot +shallots +shallow +shallower +shallowest +shallowly +shallowness +shallows +shalom +shalt +sham +shaman +shamanic +shamans +shamble +shambled +shambles +shambling +shame +shamed +shamefaced +shamefacedly +shameful +shamefully +shamefulness +shameless +shamelessly +shamelessness +shames +shaming +shammed +shammies +shamming +shammy +shampoo +shampooed +shampooer +shampooers +shampooing +shampoos +shamrock +shamrocks +shams +shanghai +shanghaied +shanghaiing +shanghais +shank +shanks +shanties +shantung +shanty +shantytown +shantytowns +shape +shaped +shapeless +shapelessly +shapelessness +shapelier +shapeliest +shapeliness +shapely +shapes +shaping +shard +shards +share +sharecrop +sharecropped +sharecropper +sharecroppers +sharecropping +sharecrops +shared +shareholder +shareholders +sharer +sharers +shares +shareware +sharia +sharing +shark +sharks +sharkskin +sharp +sharped +sharpen +sharpened +sharpener +sharpeners +sharpening +sharpens +sharper +sharpers +sharpest +sharpie +sharpies +sharping +sharply +sharpness +sharps +sharpshooter +sharpshooters +sharpshooting +sharpy +shat +shatter +shattered +shattering +shatterproof +shatters +shave +shaved +shaven +shaver +shavers +shaves +shaving +shavings +shawl +shawls +shay +shays +sheaf +shear +sheared +shearer +shearers +shearing +shears +sheath +sheathe +sheathed +sheathes +sheathing +sheathings +sheaths +sheave +sheaved +sheaves +sheaving +shebang +shebangs +shed +shedding +sheds +sheen +sheenier +sheeniest +sheeny +sheep +sheepdog +sheepdogs +sheepfold +sheepfolds +sheepherder +sheepherders +sheepish +sheepishly +sheepishness +sheepskin +sheepskins +sheer +sheered +sheerer +sheerest +sheering +sheerness +sheers +sheet +sheeting +sheetlike +sheets +sheik +sheikdom +sheikdoms +sheikh +sheikhdom +sheikhdoms +sheikhs +sheiks +shekel +shekels +shelf +shell +shellac +shellack +shellacked +shellacking +shellackings +shellacks +shellacs +shelled +shellfire +shellfish +shellfishes +shelling +shells +shelter +sheltered +sheltering +shelters +shelve +shelved +shelves +shelving +shenanigan +shenanigans +shepherd +shepherded +shepherdess +shepherdesses +shepherding +shepherds +sherbert +sherberts +sherbet +sherbets +sherd +sherds +sheriff +sheriffs +sherries +sherry +shes +shew +shewed +shewing +shewn +shews +shiatsu +shibboleth +shibboleths +shied +shield +shielded +shielding +shields +shier +shies +shiest +shift +shifted +shiftier +shiftiest +shiftily +shiftiness +shifting +shiftless +shiftlessly +shiftlessness +shifts +shifty +shill +shillalah +shillalahs +shilled +shillelagh +shillelaghs +shilling +shillings +shills +shim +shimmed +shimmer +shimmered +shimmering +shimmers +shimmery +shimmied +shimmies +shimming +shimmy +shimmying +shims +shin +shinbone +shinbones +shindig +shindigs +shine +shined +shiner +shiners +shines +shingle +shingled +shingles +shingling +shinguard +shinguards +shinier +shiniest +shininess +shining +shinned +shinnied +shinnies +shinning +shinny +shinnying +shins +shinsplints +shiny +ship +shipboard +shipboards +shipbuilder +shipbuilders +shipbuilding +shipload +shiploads +shipmate +shipmates +shipment +shipments +shipowner +shipowners +shipped +shipper +shippers +shipping +ships +shipshape +shipwreck +shipwrecked +shipwrecking +shipwrecks +shipwright +shipwrights +shipyard +shipyards +shire +shires +shirk +shirked +shirker +shirkers +shirking +shirks +shirr +shirred +shirring +shirrings +shirrs +shirt +shirtfront +shirtfronts +shirting +shirtless +shirts +shirtsleeve +shirtsleeves +shirttail +shirttails +shirtwaist +shirtwaists +shit +shits +shittier +shittiest +shitting +shitty +shiv +shiver +shivered +shivering +shivers +shivery +shivs +shlemiel +shlemiels +shlep +shlepp +shlepped +shlepping +shlepps +shleps +shlock +shmaltz +shoal +shoaled +shoaling +shoals +shoat +shoats +shock +shocked +shocker +shockers +shocking +shockingly +shockproof +shocks +shod +shodden +shoddier +shoddiest +shoddily +shoddiness +shoddy +shoe +shoed +shoehorn +shoehorned +shoehorning +shoehorns +shoeing +shoelace +shoelaces +shoemaker +shoemakers +shoes +shoeshine +shoeshines +shoestring +shoestrings +shoetree +shoetrees +shogun +shogunate +shoguns +shone +shoo +shooed +shooing +shook +shoon +shoos +shoot +shooter +shooters +shooting +shootings +shootout +shootouts +shoots +shop +shopkeeper +shopkeepers +shoplift +shoplifted +shoplifter +shoplifters +shoplifting +shoplifts +shoppe +shopped +shopper +shoppers +shoppes +shopping +shops +shoptalk +shopworn +shore +shorebird +shorebirds +shored +shoreline +shorelines +shores +shoring +shorn +short +shortage +shortages +shortbread +shortcake +shortcakes +shortchange +shortchanged +shortchanges +shortchanging +shortcoming +shortcomings +shortcut +shortcuts +shorted +shorten +shortened +shortening +shortenings +shortens +shorter +shortest +shortfall +shortfalls +shorthand +shorthanded +shorthorn +shorthorns +shortie +shorties +shorting +shortly +shortness +shorts +shortsighted +shortsightedly +shortsightedness +shortstop +shortstops +shortwave +shortwaves +shorty +shot +shotgun +shotgunned +shotgunning +shotguns +shots +should +shoulder +shouldered +shouldering +shoulders +shout +shouted +shouter +shouters +shouting +shouts +shove +shoved +shovel +shoveled +shovelful +shovelfuls +shoveling +shovelled +shovelling +shovels +shoves +shoving +show +showbiz +showboat +showboated +showboating +showboats +showcase +showcased +showcases +showcasing +showdown +showdowns +showed +shower +showered +showering +showers +showery +showgirl +showgirls +showier +showiest +showily +showiness +showing +showings +showman +showmanship +showmen +shown +showoff +showoffs +showpiece +showpieces +showplace +showplaces +showroom +showrooms +shows +showstopper +showstoppers +showy +shrank +shrapnel +shred +shredded +shredder +shredders +shredding +shreds +shrew +shrewd +shrewder +shrewdest +shrewdly +shrewdness +shrewish +shrews +shriek +shrieked +shrieking +shrieks +shrift +shrike +shrikes +shrill +shrilled +shriller +shrillest +shrilling +shrillness +shrills +shrilly +shrimp +shrimped +shrimping +shrimps +shrine +shrines +shrink +shrinkable +shrinkage +shrinking +shrinks +shrive +shrived +shrivel +shriveled +shriveling +shrivelled +shrivelling +shrivels +shriven +shrives +shriving +shroud +shrouded +shrouding +shrouds +shrove +shrub +shrubberies +shrubbery +shrubbier +shrubbiest +shrubby +shrubs +shrug +shrugged +shrugging +shrugs +shrunk +shrunken +shtick +shticks +shuck +shucked +shucking +shucks +shudder +shuddered +shuddering +shudders +shuffle +shuffleboard +shuffleboards +shuffled +shuffler +shufflers +shuffles +shuffling +shun +shunned +shunning +shuns +shunt +shunted +shunting +shunts +shush +shushed +shushes +shushing +shut +shutdown +shutdowns +shuteye +shutoff +shutoffs +shutout +shutouts +shuts +shutter +shutterbug +shutterbugs +shuttered +shuttering +shutters +shutting +shuttle +shuttlecock +shuttlecocked +shuttlecocking +shuttlecocks +shuttled +shuttles +shuttling +shyer +shyest +shying +shyly +shyness +shyster +shysters +sibilant +sibilants +sibling +siblings +sibyl +sibylline +sibyls +sicced +siccing +sick +sickbed +sickbeds +sicked +sicken +sickened +sickening +sickeningly +sickens +sicker +sickest +sickie +sickies +sicking +sickish +sickle +sickles +sicklier +sickliest +sickly +sickness +sicknesses +sicko +sickos +sickout +sickouts +sickroom +sickrooms +sicks +sics +side +sidearm +sidearms +sidebar +sidebars +sideboard +sideboards +sideburns +sidecar +sidecars +sided +sidekick +sidekicks +sidelight +sidelights +sideline +sidelined +sidelines +sidelining +sidelong +sideman +sidemen +sidepiece +sidepieces +sidereal +sides +sidesaddle +sidesaddles +sideshow +sideshows +sidesplitting +sidestep +sidestepped +sidestepping +sidesteps +sidestroke +sidestroked +sidestrokes +sidestroking +sideswipe +sideswiped +sideswipes +sideswiping +sidetrack +sidetracked +sidetracking +sidetracks +sidewalk +sidewalks +sidewall +sidewalls +sideways +sidewinder +sidewinders +sidewise +siding +sidings +sidle +sidled +sidles +sidling +siege +sieges +sienna +sierra +sierras +siesta +siestas +sieve +sieved +sieves +sieving +sift +sifted +sifter +sifters +sifting +sifts +sigh +sighed +sighing +sighs +sight +sighted +sighting +sightings +sightless +sightlier +sightliest +sightly +sightread +sightreading +sightreads +sights +sightseeing +sightseer +sightseers +sigma +sigmas +sign +signage +signal +signaled +signaler +signalers +signaling +signalization +signalize +signalized +signalizes +signalizing +signalled +signalling +signally +signalman +signalmen +signals +signatories +signatory +signature +signatures +signboard +signboards +signed +signer +signers +signet +signets +significance +significant +significantly +signification +significations +signified +signifies +signify +signifying +signing +signings +signor +signora +signoras +signore +signori +signorina +signorinas +signorine +signors +signpost +signposted +signposting +signposts +signs +silage +silence +silenced +silencer +silencers +silences +silencing +silent +silenter +silentest +silently +silents +silhouette +silhouetted +silhouettes +silhouetting +silica +silicate +silicates +siliceous +silicious +silicon +silicone +silicosis +silk +silken +silkier +silkiest +silkily +silkiness +silks +silkscreen +silkscreened +silkscreening +silkscreens +silkworm +silkworms +silky +sill +sillier +sillies +silliest +silliness +sills +silly +silo +silos +silt +silted +siltier +siltiest +silting +silts +silty +silvan +silver +silvered +silverfish +silverfishes +silvering +silvers +silversmith +silversmiths +silverware +silvery +simian +simians +similar +similarities +similarity +similarly +simile +similes +similitude +simmer +simmered +simmering +simmers +simonize +simonized +simonizes +simonizing +simony +simpatico +simper +simpered +simpering +simpers +simple +simpleminded +simpleness +simpler +simplest +simpleton +simpletons +simplicity +simplification +simplifications +simplified +simplifies +simplify +simplifying +simplistic +simplistically +simply +simulate +simulated +simulates +simulating +simulation +simulations +simulator +simulators +simulcast +simulcasted +simulcasting +simulcasts +simultaneity +simultaneous +simultaneously +since +sincere +sincerely +sincerer +sincerest +sincerity +sine +sinecure +sinecures +sines +sinew +sinews +sinewy +sinful +sinfully +sinfulness +sing +singable +singe +singed +singeing +singer +singers +singes +singing +single +singled +singleness +singles +singleton +singletons +singletree +singletrees +singling +singly +sings +singsong +singsongs +singular +singularities +singularity +singularly +singulars +sinister +sink +sinkable +sinker +sinkers +sinkhole +sinkholes +sinking +sinks +sinless +sinned +sinner +sinners +sinning +sins +sinuosity +sinuous +sinus +sinuses +sinusitis +siphon +siphoned +siphoning +siphons +sipped +sipper +sippers +sipping +sips +sire +sired +siree +siren +sirens +sires +siring +sirloin +sirloins +sirocco +siroccos +sirree +sirs +sirup +sirups +sisal +sises +sissier +sissies +sissiest +sissified +sissy +sister +sisterhood +sisterhoods +sisterliness +sisterly +sisters +sitar +sitarist +sitarists +sitars +sitcom +sitcoms +site +sited +sites +siting +sits +sitter +sitters +sitting +sittings +situate +situated +situates +situating +situation +situations +situp +situps +sixes +sixfold +sixpence +sixpences +sixshooter +sixshooters +sixteen +sixteens +sixteenth +sixteenths +sixth +sixths +sixties +sixtieth +sixtieths +sixty +sizable +size +sizeable +sized +sizes +sizing +sizzle +sizzled +sizzles +sizzling +skate +skateboard +skateboarded +skateboarder +skateboarders +skateboarding +skateboards +skated +skater +skaters +skates +skating +skedaddle +skedaddled +skedaddles +skedaddling +skeet +skein +skeins +skeletal +skeleton +skeletons +skeptic +skeptical +skeptically +skepticism +skeptics +sketch +sketched +sketcher +sketchers +sketches +sketchier +sketchiest +sketchily +sketchiness +sketching +sketchy +skew +skewed +skewer +skewered +skewering +skewers +skewing +skews +skid +skidded +skidding +skids +skied +skier +skiers +skies +skiff +skiffs +skiing +skilful +skill +skilled +skillet +skillets +skillful +skillfully +skillfulness +skills +skim +skimmed +skimmer +skimmers +skimming +skimp +skimped +skimpier +skimpiest +skimpily +skimpiness +skimping +skimps +skimpy +skims +skin +skincare +skinflick +skinflicks +skinflint +skinflints +skinhead +skinheads +skinless +skinned +skinnier +skinniest +skinniness +skinning +skinny +skins +skintight +skip +skipped +skipper +skippered +skippering +skippers +skipping +skips +skirmish +skirmished +skirmishes +skirmishing +skirt +skirted +skirting +skirts +skis +skit +skits +skitter +skittered +skittering +skitters +skittish +skittishly +skittishness +skivvied +skivvies +skivvy +skivvying +skoal +skoals +skulduggery +skulk +skulked +skulker +skulkers +skulking +skulks +skull +skullcap +skullcaps +skullduggery +skulls +skunk +skunked +skunking +skunks +skycap +skycaps +skydive +skydived +skydiver +skydivers +skydives +skydiving +skydove +skyed +skying +skyjack +skyjacked +skyjacker +skyjackers +skyjacking +skyjackings +skyjacks +skylark +skylarked +skylarking +skylarks +skylight +skylights +skyline +skylines +skyrocket +skyrocketed +skyrocketing +skyrockets +skyscraper +skyscrapers +skyward +skywards +skywriter +skywriters +skywriting +slab +slabbed +slabbing +slabs +slack +slacked +slacken +slackened +slackening +slackens +slacker +slackers +slackest +slacking +slackly +slackness +slacks +slag +slain +slake +slaked +slakes +slaking +slalom +slalomed +slaloming +slaloms +slam +slammed +slammer +slammers +slamming +slams +slander +slandered +slanderer +slanderers +slandering +slanderous +slanders +slang +slangier +slangiest +slangy +slant +slanted +slanting +slantingly +slants +slantwise +slap +slapdash +slaphappier +slaphappiest +slaphappy +slapped +slapping +slaps +slapstick +slash +slashed +slasher +slashers +slashes +slashing +slat +slate +slated +slates +slather +slathered +slathering +slathers +slating +slats +slattern +slatternly +slatterns +slaughter +slaughtered +slaughterer +slaughterers +slaughterhouse +slaughterhouses +slaughtering +slaughters +slave +slaved +slaveholder +slaveholders +slaver +slavered +slavering +slavers +slavery +slaves +slaving +slavish +slavishly +slavishness +slaw +slay +slayer +slayers +slaying +slayings +slays +sleaze +sleazes +sleazier +sleaziest +sleazily +sleaziness +sleazy +sled +sledded +sledder +sledders +sledding +sledge +sledged +sledgehammer +sledgehammered +sledgehammering +sledgehammers +sledges +sledging +sleds +sleek +sleeked +sleeker +sleekest +sleeking +sleekly +sleekness +sleeks +sleep +sleeper +sleepers +sleepier +sleepiest +sleepily +sleepiness +sleeping +sleepless +sleeplessly +sleeplessness +sleepover +sleepovers +sleeps +sleepwalk +sleepwalked +sleepwalker +sleepwalkers +sleepwalking +sleepwalks +sleepwear +sleepy +sleepyhead +sleepyheads +sleet +sleeted +sleetier +sleetiest +sleeting +sleets +sleety +sleeve +sleeved +sleeveless +sleeves +sleigh +sleighed +sleighing +sleighs +sleight +sleights +slender +slenderer +slenderest +slenderize +slenderized +slenderizes +slenderizing +slenderness +slept +sleuth +sleuths +slew +slewed +slewing +slews +slice +sliced +slicer +slicers +slices +slicing +slick +slicked +slicker +slickers +slickest +slicking +slickly +slickness +slicks +slid +slide +slider +sliders +slides +sliding +slier +sliest +slight +slighted +slighter +slightest +slighting +slightly +slightness +slights +slily +slim +slime +slimier +slimiest +sliminess +slimmed +slimmer +slimmest +slimming +slimness +slims +slimy +sling +slinging +slings +slingshot +slingshots +slink +slinked +slinkier +slinkiest +slinking +slinks +slinky +slip +slipcase +slipcases +slipcover +slipcovers +slipknot +slipknots +slippage +slippages +slipped +slipper +slipperier +slipperiest +slipperiness +slippers +slippery +slipping +slips +slipshod +slipstream +slipstreams +slipway +slipways +slit +slither +slithered +slithering +slithers +slithery +slits +slitting +sliver +slivered +slivering +slivers +slob +slobber +slobbered +slobbering +slobbers +slobbery +slobs +sloe +sloes +slog +slogan +slogans +slogged +slogging +slogs +sloop +sloops +slop +slope +sloped +slopes +sloping +slopped +sloppier +sloppiest +sloppily +sloppiness +slopping +sloppy +slops +slosh +sloshed +sloshes +sloshing +slot +sloth +slothful +slothfully +slothfulness +sloths +slots +slotted +slotting +slouch +slouched +sloucher +slouchers +slouches +slouchier +slouchiest +slouching +slouchy +slough +sloughed +sloughing +sloughs +sloven +slovenlier +slovenliest +slovenliness +slovenly +slovens +slow +slowdown +slowdowns +slowed +slower +slowest +slowing +slowly +slowness +slowpoke +slowpokes +slows +sludge +sludgier +sludgiest +sludgy +slue +slued +slues +slug +sluggard +sluggards +slugged +slugger +sluggers +slugging +sluggish +sluggishly +sluggishness +slugs +sluice +sluiced +sluices +sluicing +sluing +slum +slumber +slumbered +slumbering +slumberous +slumbers +slumbrous +slumlord +slumlords +slummed +slummier +slummiest +slumming +slummy +slump +slumped +slumping +slumps +slums +slung +slunk +slur +slurp +slurped +slurping +slurps +slurred +slurring +slurry +slurs +slush +slushier +slushiest +slushiness +slushy +slut +sluts +sluttier +sluttiest +sluttish +slutty +slyer +slyest +slyly +slyness +smack +smacked +smacker +smackers +smacking +smacks +small +smaller +smallest +smallish +smallness +smallpox +smalls +smarmier +smarmiest +smarmy +smart +smarted +smarten +smartened +smartening +smartens +smarter +smartest +smarties +smarting +smartly +smartness +smarts +smarty +smartypants +smash +smashed +smasher +smashers +smashes +smashing +smashup +smashups +smattering +smatterings +smear +smeared +smearier +smeariest +smearing +smears +smeary +smell +smelled +smellier +smelliest +smelliness +smelling +smells +smelly +smelt +smelted +smelter +smelters +smelting +smelts +smidgen +smidgens +smidgeon +smidgeons +smidgin +smidgins +smilax +smile +smiled +smiles +smiley +smileys +smiling +smilingly +smirch +smirched +smirches +smirching +smirk +smirked +smirking +smirks +smit +smite +smites +smith +smithereens +smithies +smiths +smithy +smiting +smitten +smock +smocked +smocking +smocks +smog +smoggier +smoggiest +smoggy +smoke +smoked +smokehouse +smokehouses +smokeless +smoker +smokers +smokes +smokescreen +smokescreens +smokestack +smokestacks +smokier +smokiest +smokiness +smoking +smoky +smolder +smoldered +smoldering +smolders +smooch +smooched +smooches +smooching +smooth +smoothed +smoother +smoothes +smoothest +smoothie +smoothies +smoothing +smoothly +smoothness +smooths +smorgasbord +smorgasbords +smote +smother +smothered +smothering +smothers +smoulder +smouldered +smouldering +smoulders +smudge +smudged +smudges +smudgier +smudgiest +smudging +smudgy +smug +smugger +smuggest +smuggle +smuggled +smuggler +smugglers +smuggles +smuggling +smugly +smugness +smut +smuts +smuttier +smuttiest +smuttiness +smutty +snack +snacked +snacking +snacks +snaffle +snaffled +snaffles +snaffling +snafu +snafus +snag +snagged +snagging +snags +snail +snails +snake +snakebite +snakebites +snaked +snakelike +snakes +snakier +snakiest +snaking +snaky +snap +snapdragon +snapdragons +snapped +snapper +snappers +snappier +snappiest +snappily +snappiness +snapping +snappish +snappishly +snappishness +snappy +snaps +snapshot +snapshots +snare +snared +snares +snaring +snarl +snarled +snarlier +snarliest +snarling +snarlingly +snarls +snarly +snatch +snatched +snatcher +snatchers +snatches +snatching +snazzier +snazziest +snazzily +snazzy +sneak +sneaked +sneaker +sneakers +sneakier +sneakiest +sneakily +sneakiness +sneaking +sneakingly +sneaks +sneaky +sneer +sneered +sneering +sneeringly +sneers +sneeze +sneezed +sneezes +sneezing +snicker +snickered +snickering +snickers +snide +snidely +snider +snidest +sniff +sniffed +sniffer +sniffers +sniffing +sniffle +sniffled +sniffles +sniffling +sniffs +snifter +snifters +snigger +sniggered +sniggering +sniggers +snip +snipe +sniped +sniper +snipers +snipes +sniping +snipped +snippet +snippets +snippier +snippiest +snipping +snippy +snips +snit +snitch +snitched +snitches +snitching +snits +snivel +sniveled +sniveler +snivelers +sniveling +snivelled +snivelling +snivels +snob +snobbery +snobbier +snobbiest +snobbish +snobbishly +snobbishness +snobby +snobs +snood +snoods +snooker +snookered +snookering +snookers +snoop +snooped +snooper +snoopers +snoopier +snoopiest +snooping +snoops +snoopy +snoot +snootier +snootiest +snootily +snootiness +snoots +snooty +snooze +snoozed +snoozes +snoozing +snore +snored +snorer +snorers +snores +snoring +snorkel +snorkeled +snorkeler +snorkelers +snorkeling +snorkelled +snorkelling +snorkels +snort +snorted +snorter +snorters +snorting +snorts +snot +snots +snottier +snottiest +snottily +snottiness +snotty +snout +snouts +snow +snowball +snowballed +snowballing +snowballs +snowbank +snowbanks +snowbird +snowbirds +snowboard +snowboarded +snowboarder +snowboarders +snowboarding +snowboards +snowbound +snowdrift +snowdrifts +snowdrop +snowdrops +snowed +snowfall +snowfalls +snowfield +snowfields +snowflake +snowflakes +snowier +snowiest +snowiness +snowing +snowman +snowmen +snowmobile +snowmobiled +snowmobiles +snowmobiling +snowplow +snowplowed +snowplowing +snowplows +snows +snowshoe +snowshoed +snowshoeing +snowshoes +snowstorm +snowstorms +snowsuit +snowsuits +snowy +snub +snubbed +snubbing +snubs +snuck +snuff +snuffbox +snuffboxes +snuffed +snuffer +snuffers +snuffing +snuffle +snuffled +snuffles +snuffling +snuffly +snuffs +snug +snugged +snugger +snuggest +snugging +snuggle +snuggled +snuggles +snuggling +snugly +snugness +snugs +soak +soaked +soaking +soakings +soaks +soap +soapbox +soapboxes +soaped +soapier +soapiest +soapiness +soaping +soaps +soapstone +soapsuds +soapy +soar +soared +soaring +soars +sobbed +sobbing +sobbingly +sober +sobered +soberer +soberest +sobering +soberly +soberness +sobers +sobriety +sobriquet +sobriquets +sobs +soccer +sociability +sociable +sociables +sociably +social +socialism +socialist +socialistic +socialists +socialite +socialites +socialization +socialize +socialized +socializes +socializing +socially +socials +societal +societies +society +socioeconomic +sociological +sociologically +sociologist +sociologists +sociology +sociopath +sociopaths +sock +socked +socket +sockets +sockeye +sockeyes +socking +socks +soda +sodas +sodded +sodden +soddenly +sodding +sodium +sodomite +sodomites +sodomize +sodomized +sodomizes +sodomizing +sodomy +sods +soever +sofa +sofabed +sofabeds +sofas +soft +softball +softballs +softbound +soften +softened +softener +softeners +softening +softens +softer +softest +softhearted +softie +softies +softly +softness +software +softwood +softwoods +softy +soggier +soggiest +soggily +sogginess +soggy +soigne +soignee +soil +soiled +soiling +soils +soiree +soirees +sojourn +sojourned +sojourner +sojourners +sojourning +sojourns +solace +solaced +solaces +solacing +solar +solaria +solarium +solariums +sold +solder +soldered +solderer +solderers +soldering +solders +soldier +soldiered +soldiering +soldierly +soldiers +soldiery +sole +solecism +solecisms +soled +solely +solemn +solemner +solemness +solemnest +solemnified +solemnifies +solemnify +solemnifying +solemnity +solemnization +solemnize +solemnized +solemnizes +solemnizing +solemnly +solemnness +solenoid +solenoids +soles +soli +solicit +solicitation +solicitations +solicited +soliciting +solicitor +solicitors +solicitous +solicitously +solicitousness +solicits +solicitude +solid +solidarity +solider +solidest +solidi +solidification +solidified +solidifies +solidify +solidifying +solidity +solidly +solidness +solids +solidus +soliloquies +soliloquize +soliloquized +soliloquizes +soliloquizing +soliloquy +soling +solipsism +solitaire +solitaires +solitaries +solitariness +solitary +solitude +solo +soloed +soloing +soloist +soloists +solos +sols +solstice +solstices +solubility +soluble +solubles +solute +solutes +solution +solutions +solvable +solve +solved +solvency +solvent +solvents +solver +solvers +solves +solving +somatic +somber +somberer +somberest +somberly +somberness +sombre +sombrer +sombrero +sombreros +sombrest +some +somebodies +somebody +someday +somehow +someone +someplace +somersault +somersaulted +somersaulting +somersaults +somerset +somersets +somersetted +somersetting +something +sometime +sometimes +someway +someways +somewhat +somewhere +somnambulism +somnambulist +somnambulists +somnolence +somnolent +sonar +sonars +sonata +sonatas +sonatina +sonatinas +song +songbird +songbirds +songbook +songbooks +songfest +songfests +songs +songster +songsters +songstress +songstresses +songwriter +songwriters +sonic +sonnet +sonnets +sonnies +sonny +sonogram +sonograms +sonority +sonorous +sonorously +sonorousness +sons +soon +sooner +soonest +soot +sooth +soothe +soothed +soother +soothers +soothes +soothing +soothingly +soothsayer +soothsayers +soothsaying +sootier +sootiest +sooty +sophism +sophist +sophistic +sophistical +sophisticate +sophisticated +sophisticates +sophisticating +sophistication +sophistries +sophistry +sophists +sophomore +sophomores +sophomoric +soporific +soporifically +soporifics +sopped +soppier +soppiest +sopping +soppy +soprano +sopranos +sops +sorbet +sorbets +sorcerer +sorcerers +sorceress +sorceresses +sorcery +sordid +sordidly +sordidness +sore +sorehead +soreheads +sorely +soreness +sorer +sores +sorest +sorghum +sororities +sorority +sorrel +sorrels +sorrier +sorriest +sorrily +sorriness +sorrow +sorrowed +sorrowful +sorrowfully +sorrowfulness +sorrowing +sorrows +sorry +sort +sorta +sorted +sorter +sorters +sortie +sortied +sortieing +sorties +sorting +sorts +sots +sottish +soubriquet +soubriquets +souffle +souffles +sough +soughed +soughing +soughs +sought +soul +soulful +soulfully +soulfulness +soulless +soullessly +souls +sound +soundboard +soundboards +sounded +sounder +sounders +soundest +sounding +soundings +soundless +soundlessly +soundly +soundness +soundproof +soundproofed +soundproofing +soundproofs +sounds +soundtrack +soundtracks +soup +soupcon +soupcons +souped +soupier +soupiest +souping +soups +soupy +sour +source +sourced +sources +sourcing +sourdough +sourdoughs +soured +sourer +sourest +souring +sourish +sourly +sourness +sourpuss +sourpusses +sours +sous +sousaphone +sousaphones +souse +soused +souses +sousing +south +southbound +southeast +southeaster +southeasterly +southeastern +southeasters +southeastward +southeastwards +southerlies +southerly +southern +southerner +southerners +southernmost +southerns +southpaw +southpaws +southward +southwards +southwest +southwester +southwesterly +southwestern +southwesters +southwestward +southwestwards +souvenir +souvenirs +sovereign +sovereigns +sovereignty +soviet +soviets +sowed +sower +sowers +sowing +sown +sows +soya +soybean +soybeans +space +spacecraft +spacecrafts +spaced +spaceflight +spaceflights +spaceman +spacemen +spaceport +spaceports +spacer +spacers +spaces +spaceship +spaceships +spacesuit +spacesuits +spacewalk +spacewalked +spacewalking +spacewalks +spacewoman +spacewomen +spacey +spacier +spaciest +spaciness +spacing +spacious +spaciously +spaciousness +spacy +spade +spaded +spadeful +spadefuls +spades +spadework +spadices +spading +spadix +spadixes +spaghetti +spake +span +spandex +spangle +spangled +spangles +spangling +spaniel +spaniels +spank +spanked +spanking +spankings +spanks +spanned +spanner +spanners +spanning +spans +spar +spare +spared +sparely +spareness +sparer +spareribs +spares +sparest +sparing +sparingly +spark +sparked +sparkier +sparkiest +sparking +sparkle +sparkled +sparkler +sparklers +sparkles +sparkling +sparks +sparky +sparred +sparring +sparrow +sparrows +spars +sparse +sparsely +sparseness +sparser +sparsest +sparsity +spartan +spas +spasm +spasmodic +spasmodically +spasms +spastic +spastics +spat +spate +spates +spathe +spathes +spatial +spatially +spats +spatted +spatter +spattered +spattering +spatters +spatting +spatula +spatulas +spavin +spavined +spawn +spawned +spawning +spawns +spay +spayed +spaying +spays +speak +speakeasies +speakeasy +speaker +speakers +speaking +speaks +spear +speared +spearfish +spearfished +spearfishes +spearfishing +spearhead +spearheaded +spearheading +spearheads +spearing +spearmint +spears +spec +specced +speccing +special +specialist +specialists +specialities +speciality +specialization +specializations +specialize +specialized +specializes +specializing +specially +specials +specialties +specialty +specie +species +specific +specifically +specification +specifications +specificity +specifics +specified +specifies +specify +specifying +specimen +specimens +specious +speciously +speciousness +speck +specked +specking +speckle +speckled +speckles +speckling +specks +specs +spectacle +spectacles +spectacular +spectacularly +spectaculars +spectator +spectators +specter +specters +spectra +spectral +spectre +spectres +spectrometer +spectrometers +spectroscope +spectroscopes +spectroscopic +spectroscopy +spectrum +spectrums +speculate +speculated +speculates +speculating +speculation +speculations +speculative +speculatively +speculator +speculators +sped +speech +speeches +speechless +speechlessly +speechlessness +speed +speedboat +speedboats +speeded +speeder +speeders +speedier +speediest +speedily +speediness +speeding +speedometer +speedometers +speeds +speedster +speedsters +speedup +speedups +speedway +speedways +speedwell +speedy +speleologist +speleologists +speleology +spell +spellbind +spellbinder +spellbinders +spellbinding +spellbinds +spellbound +spelldown +spelldowns +spelled +speller +spellers +spelling +spellings +spells +spelt +spelunker +spelunkers +spelunking +spend +spendable +spender +spenders +spending +spends +spendthrift +spendthrifts +spent +sperm +spermatozoa +spermatozoon +spermicidal +spermicide +spermicides +sperms +spew +spewed +spewer +spewers +spewing +spews +sphagnum +sphagnums +sphere +spheres +spherical +spherically +spheroid +spheroidal +spheroids +sphincter +sphincters +sphinges +sphinx +sphinxes +spice +spiced +spices +spicier +spiciest +spicily +spiciness +spicing +spicule +spicules +spicy +spider +spiders +spiderweb +spiderwebs +spidery +spied +spiel +spieled +spieling +spiels +spies +spiffier +spiffiest +spiffy +spigot +spigots +spike +spiked +spikes +spikier +spikiest +spikiness +spiking +spiky +spill +spillage +spillages +spilled +spilling +spillover +spillovers +spills +spillway +spillways +spilt +spin +spinach +spinal +spinally +spinals +spindle +spindled +spindles +spindlier +spindliest +spindling +spindly +spine +spineless +spinelessly +spines +spinet +spinets +spinier +spiniest +spinnaker +spinnakers +spinner +spinneret +spinnerets +spinners +spinning +spinoff +spinoffs +spins +spinster +spinsterhood +spinsterish +spinsters +spiny +spiracle +spiracles +spiraea +spiraeas +spiral +spiraled +spiraling +spiralled +spiralling +spirally +spirals +spire +spirea +spireas +spires +spirit +spirited +spiriting +spiritless +spirits +spiritual +spiritualism +spiritualist +spiritualistic +spiritualists +spirituality +spiritually +spirituals +spirituous +spirochete +spirochetes +spiry +spit +spitball +spitballs +spite +spited +spiteful +spitefuller +spitefullest +spitefully +spitefulness +spites +spitfire +spitfires +spiting +spits +spitted +spitting +spittle +spittoon +spittoons +splash +splashdown +splashdowns +splashed +splashes +splashier +splashiest +splashily +splashiness +splashing +splashy +splat +splats +splatted +splatter +splattered +splattering +splatters +splatting +splay +splayed +splayfeet +splayfoot +splayfooted +splaying +splays +spleen +spleens +splendid +splendider +splendidest +splendidly +splendor +splendorous +splendour +splenetic +splice +spliced +splicer +splicers +splices +splicing +splint +splinted +splinter +splintered +splintering +splinters +splintery +splinting +splints +split +splits +splitting +splittings +splotch +splotched +splotches +splotchier +splotchiest +splotching +splotchy +splurge +splurged +splurges +splurging +splutter +spluttered +spluttering +splutters +spoil +spoilage +spoiled +spoiler +spoilers +spoiling +spoils +spoilsport +spoilsports +spoilt +spoke +spoken +spokes +spokesman +spokesmen +spokespeople +spokesperson +spokespersons +spokeswoman +spokeswomen +spoliation +sponge +spongecake +spongecakes +sponged +sponger +spongers +sponges +spongier +spongiest +sponginess +sponging +spongy +sponsor +sponsored +sponsoring +sponsors +sponsorship +spontaneity +spontaneous +spontaneously +spoof +spoofed +spoofing +spoofs +spook +spooked +spookier +spookiest +spookiness +spooking +spooks +spooky +spool +spooled +spooling +spools +spoon +spoonbill +spoonbills +spooned +spoonerism +spoonerisms +spoonful +spoonfuls +spooning +spoons +spoonsful +spoor +spoored +spooring +spoors +sporadic +sporadically +spore +spored +spores +sporing +sport +sported +sportier +sportiest +sportiness +sporting +sportingly +sportive +sportively +sports +sportscast +sportscaster +sportscasters +sportscasts +sportsman +sportsmanlike +sportsmanship +sportsmen +sportswear +sportswoman +sportswomen +sportswriter +sportswriters +sporty +spot +spotless +spotlessly +spotlessness +spotlight +spotlighted +spotlighting +spotlights +spotlit +spots +spotted +spotter +spotters +spottier +spottiest +spottily +spottiness +spotting +spotty +spousal +spousals +spouse +spouses +spout +spouted +spouting +spouts +sprain +sprained +spraining +sprains +sprang +sprat +sprats +sprawl +sprawled +sprawling +sprawls +spray +sprayed +sprayer +sprayers +spraying +sprays +spread +spreadable +spreader +spreaders +spreading +spreads +spreadsheet +spreadsheets +spree +sprees +sprier +spriest +sprig +sprightlier +sprightliest +sprightliness +sprightly +sprigs +spring +springboard +springboards +springbok +springboks +springier +springiest +springily +springiness +springing +springlike +springs +springtime +springy +sprinkle +sprinkled +sprinkler +sprinklers +sprinkles +sprinkling +sprinklings +sprint +sprinted +sprinter +sprinters +sprinting +sprints +sprite +sprites +spritz +spritzed +spritzer +spritzers +spritzes +spritzing +sprocket +sprockets +sprout +sprouted +sprouting +sprouts +spruce +spruced +sprucely +spruceness +sprucer +spruces +sprucest +sprucing +sprung +spry +spryer +spryest +spryly +spryness +spud +spuds +spume +spumed +spumes +spumier +spumiest +spuming +spumone +spumoni +spumy +spun +spunk +spunkier +spunkiest +spunky +spur +spurge +spurious +spuriously +spuriousness +spurn +spurned +spurning +spurns +spurred +spurring +spurs +spurt +spurted +spurting +spurts +sputnik +sputniks +sputter +sputtered +sputtering +sputters +sputum +spyglass +spyglasses +spying +squab +squabble +squabbled +squabbler +squabblers +squabbles +squabbling +squabs +squad +squadron +squadrons +squads +squalid +squalider +squalidest +squalidly +squalidness +squall +squalled +squallier +squalliest +squalling +squalls +squally +squalor +squamous +squander +squandered +squandering +squanders +square +squared +squarely +squareness +squarer +squares +squarest +squaring +squarish +squash +squashed +squashes +squashier +squashiest +squashing +squashy +squat +squatness +squats +squatted +squatter +squatters +squattest +squatting +squaw +squawk +squawked +squawker +squawkers +squawking +squawks +squaws +squeak +squeaked +squeaker +squeakers +squeakier +squeakiest +squeakily +squeakiness +squeaking +squeaks +squeaky +squeal +squealed +squealer +squealers +squealing +squeals +squeamish +squeamishly +squeamishness +squeegee +squeegeed +squeegeeing +squeegees +squeezable +squeeze +squeezed +squeezer +squeezers +squeezes +squeezing +squelch +squelched +squelches +squelchier +squelchiest +squelching +squelchy +squib +squibs +squid +squids +squiggle +squiggled +squiggles +squigglier +squiggliest +squiggling +squiggly +squint +squinted +squinter +squintest +squinting +squints +squire +squired +squires +squiring +squirm +squirmed +squirmier +squirmiest +squirming +squirms +squirmy +squirrel +squirreled +squirreling +squirrelled +squirrelling +squirrels +squirt +squirted +squirting +squirts +squish +squished +squishes +squishier +squishiest +squishing +squishy +stab +stabbed +stabber +stabbers +stabbing +stabbings +stability +stabilization +stabilize +stabilized +stabilizer +stabilizers +stabilizes +stabilizing +stable +stabled +stableman +stablemen +stabler +stables +stablest +stabling +stably +stabs +staccati +staccato +staccatos +stack +stacked +stacking +stacks +stadia +stadium +stadiums +staff +staffed +staffer +staffers +staffing +staffs +stag +stage +stagecoach +stagecoaches +stagecraft +staged +stagehand +stagehands +stages +stagestruck +stagflation +stagger +staggered +staggering +staggeringly +staggers +stagier +stagiest +staging +stagings +stagnancy +stagnant +stagnantly +stagnate +stagnated +stagnates +stagnating +stagnation +stags +stagy +staid +staider +staidest +staidly +staidness +stain +stained +staining +stainless +stains +stair +staircase +staircases +stairs +stairway +stairways +stairwell +stairwells +stake +staked +stakeholder +stakeholders +stakeout +stakeouts +stakes +staking +stalactite +stalactites +stalagmite +stalagmites +stale +staled +stalemate +stalemated +stalemates +stalemating +staleness +staler +stales +stalest +staling +stalk +stalked +stalker +stalkers +stalking +stalkings +stalks +stall +stalled +stalling +stallion +stallions +stalls +stalwart +stalwarts +stamen +stamens +stamina +stammer +stammered +stammerer +stammerers +stammering +stammeringly +stammers +stamp +stamped +stampede +stampeded +stampedes +stampeding +stamper +stampers +stamping +stamps +stance +stances +stanch +stanched +stancher +stanches +stanchest +stanching +stanchion +stanchions +stand +standalone +standard +standardization +standardize +standardized +standardizes +standardizing +standards +standby +standbys +standee +standees +stander +standers +standing +standings +standoff +standoffish +standoffs +standout +standouts +standpipe +standpipes +standpoint +standpoints +stands +standstill +standstills +standup +stank +stanza +stanzas +staph +staphylococcal +staphylococci +staphylococcus +staple +stapled +stapler +staplers +staples +stapling +star +starboard +starch +starched +starches +starchier +starchiest +starchily +starchiness +starching +starchy +stardom +stardust +stare +stared +starer +starers +stares +starfish +starfishes +stargaze +stargazed +stargazer +stargazers +stargazes +stargazing +staring +stark +starker +starkest +starkly +starkness +starless +starlet +starlets +starlight +starling +starlings +starlit +starred +starrier +starriest +starring +starry +stars +start +started +starter +starters +starting +startle +startled +startles +startling +starts +starvation +starve +starved +starveling +starvelings +starves +starving +stash +stashed +stashes +stashing +stat +state +statecraft +stated +statehood +statehouse +statehouses +stateless +statelessness +statelier +stateliest +stateliness +stately +statement +statemented +statementing +statements +stateroom +staterooms +states +stateside +statesman +statesmanlike +statesmanship +statesmen +stateswoman +stateswomen +static +statically +stating +station +stationary +stationed +stationer +stationers +stationery +stationing +stations +statistic +statistical +statistically +statistician +statisticians +statistics +stats +statuary +statue +statues +statuesque +statuette +statuettes +stature +statures +status +statuses +statute +statutes +statutorily +statutory +staunch +staunched +stauncher +staunches +staunchest +staunching +staunchly +staunchness +stave +staved +staves +staving +stay +stayed +staying +stays +stead +steadfast +steadfastly +steadfastness +steadied +steadier +steadies +steadiest +steadily +steadiness +steads +steady +steadying +steak +steakhouse +steakhouses +steaks +steal +stealing +steals +stealth +stealthier +stealthiest +stealthily +stealthiness +stealthy +steam +steamboat +steamboats +steamed +steamer +steamers +steamfitter +steamfitters +steamfitting +steamier +steamiest +steaminess +steaming +steamroll +steamrolled +steamroller +steamrollered +steamrollering +steamrollers +steamrolling +steamrolls +steams +steamship +steamships +steamy +steed +steeds +steel +steeled +steelier +steeliest +steeliness +steeling +steels +steelworker +steelworkers +steelworks +steely +steelyard +steelyards +steep +steeped +steepen +steepened +steepening +steepens +steeper +steepest +steeping +steeple +steeplechase +steeplechases +steeplejack +steeplejacks +steeples +steeply +steepness +steeps +steer +steerable +steerage +steered +steering +steers +steersman +steersmen +stegosauri +stegosaurus +stegosauruses +stein +steins +stellar +stem +stemless +stemmed +stemming +stems +stemware +stench +stenches +stencil +stenciled +stenciling +stencilled +stencilling +stencils +steno +stenographer +stenographers +stenographic +stenography +stenos +stentorian +step +stepbrother +stepbrothers +stepchild +stepchildren +stepdaughter +stepdaughters +stepfather +stepfathers +stepladder +stepladders +stepmother +stepmothers +stepparent +stepparents +steppe +stepped +stepper +steppers +steppes +stepping +steppingstone +steppingstones +steps +stepsister +stepsisters +stepson +stepsons +stereo +stereophonic +stereos +stereoscope +stereoscopes +stereoscopic +stereotype +stereotyped +stereotypes +stereotypical +stereotyping +sterile +sterility +sterilization +sterilize +sterilized +sterilizer +sterilizers +sterilizes +sterilizing +sterling +stern +sterna +sterner +sternest +sternly +sternness +sterns +sternum +sternums +steroid +steroidal +steroids +stertorous +stet +stethoscope +stethoscopes +stets +stetson +stetsons +stetted +stetting +stevedore +stevedores +stew +steward +stewarded +stewardess +stewardesses +stewarding +stewards +stewardship +stewed +stewing +stews +stick +sticker +stickers +stickier +stickies +stickiest +stickily +stickiness +sticking +stickleback +sticklebacks +stickler +sticklers +stickpin +stickpins +sticks +stickup +stickups +sticky +sties +stiff +stiffed +stiffen +stiffened +stiffener +stiffeners +stiffening +stiffens +stiffer +stiffest +stiffing +stiffly +stiffness +stiffs +stifle +stifled +stifles +stifling +stiflingly +stigma +stigmas +stigmata +stigmatic +stigmatization +stigmatize +stigmatized +stigmatizes +stigmatizing +stile +stiles +stiletto +stilettoes +stilettos +still +stillbirth +stillbirths +stillborn +stilled +stiller +stillest +stilling +stillness +stills +stilt +stilted +stilts +stimulant +stimulants +stimulate +stimulated +stimulates +stimulating +stimulation +stimulative +stimuli +stimulus +sting +stinger +stingers +stingier +stingiest +stingily +stinginess +stinging +stingray +stingrays +stings +stingy +stink +stinkbug +stinkbugs +stinker +stinkers +stinkier +stinkiest +stinking +stinks +stinky +stint +stinted +stinting +stints +stipend +stipends +stipple +stippled +stipples +stippling +stipulate +stipulated +stipulates +stipulating +stipulation +stipulations +stir +stirred +stirrer +stirrers +stirring +stirringly +stirrings +stirrup +stirrups +stirs +stitch +stitched +stitchery +stitches +stitching +stoat +stoats +stock +stockade +stockaded +stockades +stockading +stockbreeder +stockbreeders +stockbroker +stockbrokers +stockbroking +stocked +stockholder +stockholders +stockier +stockiest +stockily +stockiness +stockinet +stockinette +stocking +stockings +stockpile +stockpiled +stockpiles +stockpiling +stockpot +stockpots +stockroom +stockrooms +stocks +stocktaking +stocky +stockyard +stockyards +stodgier +stodgiest +stodgily +stodginess +stodgy +stogie +stogies +stogy +stoic +stoical +stoically +stoicism +stoics +stoke +stoked +stoker +stokers +stokes +stoking +stole +stolen +stoles +stolid +stolider +stolidest +stolidity +stolidly +stolidness +stolon +stolons +stomach +stomachache +stomachaches +stomached +stomacher +stomachers +stomaching +stomachs +stomp +stomped +stomping +stomps +stone +stoned +stonemason +stonemasons +stones +stonewall +stonewalled +stonewalling +stonewalls +stoneware +stonewashed +stonework +stoney +stonier +stoniest +stonily +stoniness +stoning +stony +stood +stooge +stooges +stool +stools +stoop +stooped +stooping +stoops +stop +stopcock +stopcocks +stopgap +stopgaps +stoplight +stoplights +stopover +stopovers +stoppage +stoppages +stopped +stopper +stoppered +stoppering +stoppers +stopping +stopple +stoppled +stopples +stoppling +stops +stopwatch +stopwatches +storage +store +stored +storefront +storefronts +storehouse +storehouses +storekeeper +storekeepers +storeroom +storerooms +stores +storey +storeys +storied +stories +storing +stork +storks +storm +stormed +stormier +stormiest +stormily +storminess +storming +storms +stormy +story +storyboard +storyboards +storybook +storybooks +storyteller +storytellers +storytelling +stoup +stoups +stout +stouter +stoutest +stouthearted +stoutly +stoutness +stove +stovepipe +stovepipes +stoves +stow +stowage +stowaway +stowaways +stowed +stowing +stows +straddle +straddled +straddler +straddlers +straddles +straddling +strafe +strafed +strafes +strafing +straggle +straggled +straggler +stragglers +straggles +stragglier +straggliest +straggling +straggly +straight +straightaway +straightaways +straightedge +straightedges +straighten +straightened +straightener +straighteners +straightening +straightens +straighter +straightest +straightforward +straightforwardly +straightforwardness +straightforwards +straightjacket +straightjacketed +straightjacketing +straightjackets +straightly +straightness +straights +straightway +strain +strained +strainer +strainers +straining +strains +strait +straiten +straitened +straitening +straitens +straitjacket +straitjacketed +straitjacketing +straitjackets +straitlaced +straits +strand +stranded +stranding +strands +strange +strangely +strangeness +stranger +strangers +strangest +strangle +strangled +stranglehold +strangleholds +strangler +stranglers +strangles +strangling +strangulate +strangulated +strangulates +strangulating +strangulation +strap +strapless +straplesses +strapped +strapping +straps +strata +stratagem +stratagems +strategic +strategical +strategically +strategics +strategies +strategist +strategists +strategy +strati +stratification +stratified +stratifies +stratify +stratifying +stratosphere +stratospheres +stratospheric +stratum +stratums +stratus +straw +strawberries +strawberry +straws +stray +strayed +straying +strays +streak +streaked +streaker +streakers +streakier +streakiest +streaking +streaks +streaky +stream +streamed +streamer +streamers +streaming +streamline +streamlined +streamlines +streamlining +streams +street +streetcar +streetcars +streetlight +streetlights +streets +streetwalker +streetwalkers +streetwise +strength +strengthen +strengthened +strengthener +strengtheners +strengthening +strengthens +strengths +strenuous +strenuously +strenuousness +strep +streptococcal +streptococci +streptococcus +streptomycin +stress +stressed +stresses +stressful +stressing +stretch +stretchable +stretched +stretcher +stretchers +stretches +stretchier +stretchiest +stretching +stretchy +strew +strewed +strewing +strewn +strews +stria +striae +striated +striation +striations +stricken +strict +stricter +strictest +strictly +strictness +stricture +strictures +stridden +stride +stridency +strident +stridently +strides +striding +strife +strike +strikebreaker +strikebreakers +strikeout +strikeouts +striker +strikers +strikes +striking +strikingly +string +stringed +stringency +stringent +stringently +stringer +stringers +stringier +stringiest +stringiness +stringing +strings +stringy +strip +stripe +striped +stripes +stripier +stripiest +striping +stripling +striplings +stripped +stripper +strippers +stripping +strips +stript +striptease +stripteased +stripteaser +stripteasers +stripteases +stripteasing +stripy +strive +strived +striven +strives +striving +strobe +strobes +stroboscope +stroboscopes +stroboscopic +strode +stroke +stroked +strokes +stroking +stroll +strolled +stroller +strollers +strolling +strolls +strong +strongbox +strongboxes +stronger +strongest +stronghold +strongholds +strongly +strongman +strongmen +strontium +strop +strophe +strophes +strophic +stropped +stropping +strops +strove +struck +structural +structurally +structure +structured +structures +structuring +strudel +strudels +struggle +struggled +struggles +struggling +strum +strummed +strumming +strumpet +strumpets +strums +strung +strut +struts +strutted +strutting +strychnine +stub +stubbed +stubbier +stubbiest +stubbing +stubble +stubblier +stubbliest +stubbly +stubborn +stubborner +stubbornest +stubbornly +stubbornness +stubby +stubs +stucco +stuccoed +stuccoes +stuccoing +stuccos +stuck +stud +studbook +studbooks +studded +studding +student +students +studied +studiedly +studies +studio +studios +studious +studiously +studiousness +studs +study +studying +stuff +stuffed +stuffier +stuffiest +stuffily +stuffiness +stuffing +stuffings +stuffs +stuffy +stultification +stultified +stultifies +stultify +stultifying +stumble +stumbled +stumbler +stumblers +stumbles +stumbling +stump +stumped +stumpier +stumpiest +stumping +stumps +stumpy +stun +stung +stunk +stunned +stunning +stunningly +stuns +stunt +stunted +stunting +stunts +stupefaction +stupefied +stupefies +stupefy +stupefying +stupendous +stupendously +stupid +stupider +stupidest +stupidities +stupidity +stupidly +stupids +stupor +stupors +sturdier +sturdiest +sturdily +sturdiness +sturdy +sturgeon +sturgeons +stutter +stuttered +stutterer +stutterers +stuttering +stutters +stye +styes +style +styled +styles +styli +styling +stylish +stylishly +stylishness +stylist +stylistic +stylistically +stylists +stylize +stylized +stylizes +stylizing +stylus +styluses +stymie +stymied +stymieing +stymies +stymy +stymying +styptic +styptics +suasion +suave +suavely +suaveness +suaver +suavest +suavity +subaltern +subalterns +subarctic +subarea +subareas +subatomic +subbasement +subbasements +subbed +subbing +subbranch +subbranches +subcategories +subcategory +subcommittee +subcommittees +subcompact +subcompacts +subconscious +subconsciously +subconsciousness +subcontinent +subcontinental +subcontinents +subcontract +subcontracted +subcontracting +subcontractor +subcontractors +subcontracts +subculture +subcultures +subcutaneous +subcutaneously +subdivide +subdivided +subdivides +subdividing +subdivision +subdivisions +subdue +subdued +subdues +subduing +subfamilies +subfamily +subfreezing +subgroup +subgroups +subhead +subheading +subheadings +subheads +subhuman +subhumans +subject +subjected +subjecting +subjection +subjective +subjectively +subjectivity +subjects +subjoin +subjoined +subjoining +subjoins +subjugate +subjugated +subjugates +subjugating +subjugation +subjunctive +subjunctives +sublease +subleased +subleases +subleasing +sublet +sublets +subletting +sublimate +sublimated +sublimates +sublimating +sublimation +sublime +sublimed +sublimely +sublimer +sublimes +sublimest +subliminal +subliming +sublimity +submarginal +submarine +submariner +submariners +submarines +submerge +submerged +submergence +submerges +submerging +submerse +submersed +submerses +submersible +submersibles +submersing +submersion +submicroscopic +submission +submissions +submissive +submissively +submissiveness +submit +submits +submitted +submitting +subnormal +suborbital +suborder +suborders +subordinate +subordinated +subordinates +subordinating +subordination +suborn +subornation +suborned +suborning +suborns +subpena +subpenaed +subpenaing +subpenas +subplot +subplots +subpoena +subpoenaed +subpoenaing +subpoenas +subprofessional +subprofessionals +subroutine +subroutines +subs +subscribe +subscribed +subscriber +subscribers +subscribes +subscribing +subscript +subscription +subscriptions +subscripts +subsection +subsections +subsequent +subsequently +subservience +subservient +subserviently +subset +subsets +subside +subsided +subsidence +subsides +subsidiaries +subsidiary +subsidies +subsiding +subsidization +subsidize +subsidized +subsidizer +subsidizers +subsidizes +subsidizing +subsidy +subsist +subsisted +subsistence +subsisting +subsists +subsoil +subsonic +subspecies +substance +substances +substandard +substantial +substantially +substantiate +substantiated +substantiates +substantiating +substantiation +substantiations +substantive +substantively +substantives +substation +substations +substitute +substituted +substitutes +substituting +substitution +substitutions +substrata +substrate +substrates +substratum +substratums +substructure +substructures +subsume +subsumed +subsumes +subsuming +subsurface +subsystem +subsystems +subteen +subteens +subtenancy +subtenant +subtenants +subterfuge +subterfuges +subterranean +subtext +subtexts +subtitle +subtitled +subtitles +subtitling +subtle +subtler +subtlest +subtleties +subtlety +subtly +subtopic +subtopics +subtotal +subtotaled +subtotaling +subtotalled +subtotalling +subtotals +subtract +subtracted +subtracting +subtraction +subtractions +subtracts +subtrahend +subtrahends +subtropic +subtropical +subtropics +suburb +suburban +suburbanite +suburbanites +suburbans +suburbia +suburbs +subvention +subventions +subversion +subversive +subversively +subversiveness +subversives +subvert +subverted +subverting +subverts +subway +subways +subzero +succeed +succeeded +succeeding +succeeds +success +successes +successful +successfully +succession +successions +successive +successively +successor +successors +succinct +succincter +succinctest +succinctly +succinctness +succor +succored +succoring +succors +succotash +succour +succoured +succouring +succours +succulence +succulency +succulent +succulents +succumb +succumbed +succumbing +succumbs +such +suchlike +suck +sucked +sucker +suckered +suckering +suckers +sucking +suckle +suckled +suckles +suckling +sucklings +sucks +sucrose +suction +suctioned +suctioning +suctions +sudden +suddenly +suddenness +suds +sudsier +sudsiest +sudsy +sued +suede +sues +suet +suety +suffer +sufferance +suffered +sufferer +sufferers +suffering +sufferings +suffers +suffice +sufficed +suffices +sufficiency +sufficient +sufficiently +sufficing +suffix +suffixation +suffixed +suffixes +suffixing +suffocate +suffocated +suffocates +suffocating +suffocation +suffragan +suffragans +suffrage +suffragette +suffragettes +suffragist +suffragists +suffuse +suffused +suffuses +suffusing +suffusion +sugar +sugarcane +sugarcoat +sugarcoated +sugarcoating +sugarcoats +sugared +sugarier +sugariest +sugaring +sugarless +sugarplum +sugarplums +sugars +sugary +suggest +suggested +suggestibility +suggestible +suggesting +suggestion +suggestions +suggestive +suggestively +suggestiveness +suggests +suicidal +suicide +suicides +suing +suit +suitability +suitable +suitableness +suitably +suitcase +suitcases +suite +suited +suites +suiting +suitor +suitors +suits +sukiyaki +sulfa +sulfate +sulfates +sulfide +sulfides +sulfur +sulfuric +sulfurous +sulk +sulked +sulkier +sulkies +sulkiest +sulkily +sulkiness +sulking +sulks +sulky +sullen +sullener +sullenest +sullenly +sullenness +sullied +sullies +sully +sullying +sulphur +sulphured +sulphuring +sulphurous +sulphurs +sultan +sultana +sultanas +sultanate +sultanates +sultans +sultrier +sultriest +sultrily +sultriness +sultry +sumac +sumach +summaries +summarily +summarize +summarized +summarizes +summarizing +summary +summation +summations +summed +summer +summered +summerhouse +summerhouses +summering +summers +summertime +summery +summing +summit +summitry +summits +summon +summoned +summoner +summoners +summoning +summons +summonsed +summonses +summonsing +sumo +sump +sumps +sumptuous +sumptuously +sumptuousness +sums +sunbath +sunbathe +sunbathed +sunbather +sunbathers +sunbathes +sunbathing +sunbaths +sunbeam +sunbeams +sunbelt +sunbelts +sunblock +sunblocks +sunbonnet +sunbonnets +sunburn +sunburned +sunburning +sunburns +sunburnt +sunburst +sunbursts +sundae +sundaes +sunder +sundered +sundering +sunders +sundial +sundials +sundown +sundowns +sundries +sundry +sunfish +sunfishes +sunflower +sunflowers +sung +sunglasses +sunk +sunken +sunlamp +sunlamps +sunless +sunlight +sunlit +sunned +sunnier +sunniest +sunniness +sunning +sunny +sunrise +sunrises +sunroof +sunroofs +suns +sunscreen +sunscreens +sunset +sunsets +sunshade +sunshades +sunshine +sunshiny +sunspot +sunspots +sunstroke +suntan +suntanned +suntanning +suntans +sunup +super +superabundance +superabundances +superabundant +superannuate +superannuated +superannuates +superannuating +superannuation +superb +superber +superbest +superbly +supercargo +supercargoes +supercargos +supercharge +supercharged +supercharger +superchargers +supercharges +supercharging +supercilious +superciliously +superciliousness +supercities +supercity +supercomputer +supercomputers +superconducting +superconductive +superconductivity +superconductor +superconductors +superego +superegos +supererogation +supererogatory +superficial +superficiality +superficially +superfine +superfluity +superfluous +superfluously +superfluousness +superhero +superheroes +superhighway +superhighways +superhuman +superimpose +superimposed +superimposes +superimposing +superimposition +superintend +superintended +superintendence +superintendency +superintendent +superintendents +superintending +superintends +superior +superiority +superiors +superlative +superlatively +superlatives +superman +supermarket +supermarkets +supermen +supermom +supermoms +supernal +supernatural +supernaturally +supernova +supernovae +supernovas +supernumeraries +supernumerary +superpose +superposed +superposes +superposing +superposition +superpower +superpowers +supers +supersaturate +supersaturated +supersaturates +supersaturating +supersaturation +superscribe +superscribed +superscribes +superscribing +superscript +superscription +superscripts +supersede +superseded +supersedes +superseding +supersonic +superstar +superstars +superstition +superstitions +superstitious +superstitiously +superstore +superstores +superstructure +superstructures +supertanker +supertankers +supervene +supervened +supervenes +supervening +supervention +supervise +supervised +supervises +supervising +supervision +supervisor +supervisors +supervisory +superwoman +superwomen +supine +supped +supper +suppers +supping +supplant +supplanted +supplanting +supplants +supple +supplement +supplemental +supplementary +supplementation +supplemented +supplementing +supplements +suppleness +suppler +supplest +suppliant +suppliants +supplicant +supplicants +supplicate +supplicated +supplicates +supplicating +supplication +supplications +supplied +supplier +suppliers +supplies +supply +supplying +support +supportable +supported +supporter +supporters +supporting +supportive +supports +suppose +supposed +supposedly +supposes +supposing +supposition +suppositions +suppositories +suppository +suppress +suppressant +suppressants +suppressed +suppresses +suppressible +suppressing +suppression +suppressor +suppressors +suppurate +suppurated +suppurates +suppurating +suppuration +supra +supranational +supremacist +supremacists +supremacy +supreme +supremely +sups +surcease +surceased +surceases +surceasing +surcharge +surcharged +surcharges +surcharging +surcingle +surcingles +sure +surefire +surefooted +surely +sureness +surer +surest +sureties +surety +surf +surface +surfaced +surfaces +surfacing +surfboard +surfboarded +surfboarding +surfboards +surfed +surfeit +surfeited +surfeiting +surfeits +surfer +surfers +surfing +surfs +surge +surged +surgeon +surgeons +surgeries +surgery +surges +surgical +surgically +surging +surlier +surliest +surliness +surly +surmise +surmised +surmises +surmising +surmount +surmountable +surmounted +surmounting +surmounts +surname +surnames +surpass +surpassed +surpasses +surpassing +surplice +surplices +surplus +surplused +surpluses +surplusing +surplussed +surplussing +surprise +surprised +surprises +surprising +surprisingly +surreal +surrealism +surrealist +surrealistic +surrealistically +surrealists +surrender +surrendered +surrendering +surrenders +surreptitious +surreptitiously +surreptitiousness +surrey +surreys +surrogacy +surrogate +surrogates +surround +surrounded +surrounding +surroundings +surrounds +surtax +surtaxed +surtaxes +surtaxing +surveillance +survey +surveyed +surveying +surveyor +surveyors +surveys +survival +survivalist +survivalists +survivals +survive +survived +survives +surviving +survivor +survivors +susceptibility +susceptible +sushi +suspect +suspected +suspecting +suspects +suspend +suspended +suspender +suspenders +suspending +suspends +suspense +suspenseful +suspension +suspensions +suspicion +suspicions +suspicious +suspiciously +sustain +sustainable +sustained +sustaining +sustains +sustenance +sutler +sutlers +suture +sutured +sutures +suturing +suzerain +suzerains +suzerainty +svelte +svelter +sveltest +swab +swabbed +swabbing +swabs +swaddle +swaddled +swaddles +swaddling +swag +swagged +swagger +swaggered +swaggering +swaggers +swagging +swags +swain +swains +swallow +swallowed +swallowing +swallows +swallowtail +swallowtails +swam +swami +swamis +swamp +swamped +swampier +swampiest +swamping +swampland +swamps +swampy +swan +swank +swanked +swanker +swankest +swankier +swankiest +swankily +swankiness +swanking +swanks +swanky +swans +swansdown +swap +swapped +swapping +swaps +sward +swards +swarm +swarmed +swarming +swarms +swarthier +swarthiest +swarthy +swash +swashbuckler +swashbucklers +swashbuckling +swashed +swashes +swashing +swastika +swastikas +swat +swatch +swatches +swath +swathe +swathed +swathes +swathing +swaths +swats +swatted +swatter +swatters +swatting +sway +swayback +swaybacked +swayed +swaying +sways +swear +swearer +swearers +swearing +swears +swearword +swearwords +sweat +sweatband +sweatbands +sweated +sweater +sweaters +sweatier +sweatiest +sweating +sweatpants +sweats +sweatshirt +sweatshirts +sweatshop +sweatshops +sweaty +swede +swedes +sweep +sweeper +sweepers +sweeping +sweepingly +sweepings +sweeps +sweepstake +sweepstakes +sweet +sweetbread +sweetbreads +sweetbriar +sweetbriars +sweetbrier +sweetbriers +sweeten +sweetened +sweetener +sweeteners +sweetening +sweetens +sweeter +sweetest +sweetheart +sweethearts +sweetie +sweeties +sweetish +sweetly +sweetmeat +sweetmeats +sweetness +sweets +swell +swelled +sweller +swellest +swellhead +swellheaded +swellheads +swelling +swellings +swells +swelter +sweltered +sweltering +swelters +swept +sweptback +swerve +swerved +swerves +swerving +swift +swifter +swiftest +swiftly +swiftness +swifts +swig +swigged +swigging +swigs +swill +swilled +swilling +swills +swim +swimmer +swimmers +swimming +swimmingly +swims +swimsuit +swimsuits +swindle +swindled +swindler +swindlers +swindles +swindling +swine +swineherd +swineherds +swines +swing +swinger +swingers +swinging +swings +swinish +swipe +swiped +swipes +swiping +swirl +swirled +swirlier +swirliest +swirling +swirls +swirly +swish +swished +swisher +swishes +swishest +swishing +switch +switchback +switchbacks +switchblade +switchblades +switchboard +switchboards +switched +switcher +switchers +switches +switching +swivel +swiveled +swiveling +swivelled +swivelling +swivels +swob +swobbed +swobbing +swobs +swollen +swoon +swooned +swooning +swoons +swoop +swooped +swooping +swoops +swoosh +swooshed +swooshes +swooshing +swop +swopped +swopping +swops +sword +swordfish +swordfishes +swordplay +swords +swordsman +swordsmanship +swordsmen +swore +sworn +swum +swung +sybarite +sybarites +sybaritic +sycamore +sycamores +sycophancy +sycophant +sycophantic +sycophants +syllabi +syllabic +syllabicate +syllabicated +syllabicates +syllabicating +syllabication +syllabification +syllabified +syllabifies +syllabify +syllabifying +syllable +syllables +syllabus +syllabuses +syllogism +syllogisms +syllogistic +sylph +sylphic +sylphlike +sylphs +sylvan +symbioses +symbiosis +symbiotic +symbol +symbolic +symbolical +symbolically +symbolism +symbolization +symbolize +symbolized +symbolizes +symbolizing +symbols +symmetric +symmetrical +symmetrically +symmetry +sympathetic +sympathetically +sympathies +sympathize +sympathized +sympathizer +sympathizers +sympathizes +sympathizing +sympathy +symphonic +symphonies +symphony +symposia +symposium +symposiums +symptom +symptomatic +symptomatically +symptoms +synagog +synagogal +synagogs +synagogue +synagogues +synapse +synapses +synaptic +sync +synced +synch +synched +synching +synchronization +synchronizations +synchronize +synchronized +synchronizes +synchronizing +synchronous +synchs +syncing +syncopate +syncopated +syncopates +syncopating +syncopation +syncope +syncs +syndicate +syndicated +syndicates +syndicating +syndication +syndrome +syndromes +synergism +synergistic +synergy +synfuel +synfuels +synod +synods +synonym +synonymous +synonyms +synonymy +synopses +synopsis +synoptic +syntactic +syntactical +syntactically +syntax +syntheses +synthesis +synthesize +synthesized +synthesizer +synthesizers +synthesizes +synthesizing +synthetic +synthetically +synthetics +syphilis +syphilitic +syphilitics +syphon +syphoned +syphoning +syphons +syringe +syringed +syringes +syringing +syrup +syrups +syrupy +system +systematic +systematical +systematically +systematization +systematize +systematized +systematizes +systematizing +systemic +systemically +systemics +systems +systole +systoles +systolic +tabbed +tabbies +tabbing +tabbouleh +tabby +tabernacle +tabernacles +tabla +tablas +table +tableau +tableaus +tableaux +tablecloth +tablecloths +tabled +tableland +tablelands +tables +tablespoon +tablespoonful +tablespoonfuls +tablespoons +tablespoonsful +tablet +tabletop +tabletops +tablets +tableware +tabling +tabloid +tabloids +taboo +tabooed +tabooing +taboos +tabor +tabors +tabs +tabu +tabued +tabuing +tabular +tabulate +tabulated +tabulates +tabulating +tabulation +tabulator +tabulators +tabus +tachometer +tachometers +tachycardia +tacit +tacitly +tacitness +taciturn +taciturnity +taciturnly +tack +tacked +tacker +tackers +tackier +tackiest +tackiness +tacking +tackle +tackled +tackler +tacklers +tackles +tackling +tacks +tacky +taco +tacos +tact +tactful +tactfully +tactfulness +tactic +tactical +tactically +tactician +tacticians +tactics +tactile +tactility +tactless +tactlessly +tactlessness +tadpole +tadpoles +tads +taffeta +taffies +taffrail +taffrails +taffy +tagged +tagger +taggers +tagging +tags +taiga +taigas +tail +tailback +tailbacks +tailcoat +tailcoats +tailed +tailgate +tailgated +tailgater +tailgaters +tailgates +tailgating +tailing +tailless +taillight +taillights +tailor +tailored +tailoring +tailors +tailpipe +tailpipes +tails +tailspin +tailspins +tailwind +tailwinds +taint +tainted +tainting +taints +take +taken +takeoff +takeoffs +takeout +takeouts +takeover +takeovers +taker +takers +takes +taking +takings +talc +talcum +tale +talebearer +talebearers +talent +talented +talents +tales +tali +talisman +talismans +talk +talkative +talkatively +talkativeness +talked +talker +talkers +talkie +talkier +talkies +talkiest +talking +talks +talky +tall +tallboy +tallboys +taller +tallest +tallied +tallier +talliers +tallies +tallish +tallness +tallow +tallowy +tally +tallyho +tallyhoed +tallyhoing +tallyhos +tallying +talon +talons +talus +taluses +tamable +tamale +tamales +tamarack +tamaracks +tamarind +tamarinds +tambourine +tambourines +tame +tameable +tamed +tamely +tameness +tamer +tamers +tames +tamest +taming +tamp +tamped +tamper +tampered +tamperer +tamperers +tampering +tampers +tamping +tampon +tampons +tamps +tams +tanager +tanagers +tanbark +tandem +tandems +tandoori +tang +tangelo +tangelos +tangent +tangential +tangentially +tangents +tangerine +tangerines +tangibility +tangible +tangibleness +tangibles +tangibly +tangier +tangiest +tangle +tangled +tangles +tangling +tango +tangoed +tangoing +tangos +tangs +tangy +tank +tankard +tankards +tanked +tanker +tankers +tankful +tankfuls +tanking +tanks +tanned +tanner +tanneries +tanners +tannery +tannest +tannin +tanning +tans +tansy +tantalise +tantalised +tantalises +tantalising +tantalization +tantalize +tantalized +tantalizer +tantalizers +tantalizes +tantalizing +tantalizingly +tantalum +tantamount +tantra +tantrum +tantrums +tape +taped +tapeline +tapelines +taper +tapered +tapering +tapers +tapes +tapestries +tapestry +tapeworm +tapeworms +taping +tapioca +tapir +tapirs +tapped +tapper +tappers +tappet +tappets +tapping +taproom +taprooms +taproot +taproots +taps +tarantella +tarantellas +tarantula +tarantulae +tarantulas +tardier +tardiest +tardily +tardiness +tardy +tare +tared +tares +target +targeted +targeting +targets +tariff +tariffs +taring +tarmac +tarmacked +tarmacking +tarmacs +tarn +tarnish +tarnished +tarnishes +tarnishing +tarns +taro +taros +tarot +tarots +tarp +tarpaulin +tarpaulins +tarpon +tarpons +tarps +tarragon +tarragons +tarred +tarried +tarrier +tarries +tarriest +tarring +tarry +tarrying +tars +tarsal +tarsals +tarsi +tarsus +tart +tartan +tartans +tartar +tartaric +tartars +tarter +tartest +tartly +tartness +tarts +task +tasked +tasking +taskmaster +taskmasters +taskmistress +taskmistresses +tasks +tassel +tasseled +tasseling +tasselled +tasselling +tassels +taste +tasted +tasteful +tastefully +tastefulness +tasteless +tastelessly +tastelessness +taster +tasters +tastes +tastier +tastiest +tastily +tastiness +tasting +tastings +tasty +tatami +tatamis +tater +taters +tats +tatted +tatter +tatterdemalion +tatterdemalions +tattered +tattering +tatters +tatting +tattle +tattled +tattler +tattlers +tattles +tattletale +tattletales +tattling +tattoo +tattooed +tattooer +tattooers +tattooing +tattooist +tattooists +tattoos +taught +taunt +taunted +taunter +taunters +taunting +tauntingly +taunts +taupe +taus +taut +tauten +tautened +tautening +tautens +tauter +tautest +tautly +tautness +tautological +tautologically +tautologies +tautologous +tautology +tavern +taverns +tawdrier +tawdriest +tawdrily +tawdriness +tawdry +tawnier +tawniest +tawny +taxable +taxation +taxed +taxer +taxers +taxes +taxi +taxicab +taxicabs +taxidermist +taxidermists +taxidermy +taxied +taxies +taxiing +taximeter +taximeters +taxing +taxis +taxonomic +taxonomical +taxonomies +taxonomist +taxonomists +taxonomy +taxpayer +taxpayers +taxpaying +taxying +teacake +teacakes +teach +teachable +teacher +teachers +teaches +teaching +teachings +teacup +teacupful +teacupfuls +teacups +teak +teakettle +teakettles +teaks +teal +teals +team +teamed +teaming +teammate +teammates +teams +teamster +teamsters +teamwork +teapot +teapots +tear +teardrop +teardrops +teared +tearful +tearfully +teargas +teargases +teargassed +teargasses +teargassing +tearier +teariest +tearing +tearjerker +tearjerkers +tearoom +tearooms +tears +teary +teas +tease +teased +teasel +teasels +teaser +teasers +teases +teasing +teaspoon +teaspoonful +teaspoonfuls +teaspoons +teaspoonsful +teat +teats +tech +technetium +technical +technicalities +technicality +technically +technician +technicians +technique +techniques +technocracy +technocrat +technocratic +technocrats +technological +technologically +technologies +technologist +technologists +technology +techs +tectonic +tectonics +tedious +tediously +tediousness +tedium +teed +teeing +teem +teemed +teeming +teems +teen +teenage +teenaged +teenager +teenagers +teenier +teeniest +teens +teensier +teensiest +teensy +teeny +teenybopper +teenyboppers +teepee +teepees +tees +teeter +teetered +teetering +teeters +teeth +teethe +teethed +teethes +teething +teetotal +teetotaler +teetotalers +teetotalism +teetotaller +teetotallers +tektite +tektites +telecast +telecasted +telecaster +telecasters +telecasting +telecasts +telecommunication +telecommunications +telecommute +telecommuted +telecommuter +telecommuters +telecommutes +telecommuting +teleconference +teleconferenced +teleconferences +teleconferencing +telegenic +telegram +telegrams +telegraph +telegraphed +telegrapher +telegraphers +telegraphic +telegraphically +telegraphing +telegraphist +telegraphists +telegraphs +telegraphy +telekinesis +telekinetic +telemarketer +telemarketers +telemarketing +telemeter +telemeters +telemetries +telemetry +telepathic +telepathically +telepathy +telephone +telephoned +telephoner +telephoners +telephones +telephonic +telephoning +telephony +telephoto +telephotography +telephotos +teleplay +teleplays +teleprinter +teleprinters +teleprocessing +teleprompter +teleprompters +telescope +telescoped +telescopes +telescopic +telescopically +telescoping +teletext +teletexts +telethon +telethons +teletypewriter +teletypewriters +televangelism +televangelist +televangelists +televise +televised +televises +televising +television +televisions +telex +telexed +telexes +telexing +tell +teller +tellers +tellies +telling +tellingly +tells +telltale +telltales +tellurium +telly +temblor +temblors +temerity +temp +temped +temper +tempera +temperament +temperamental +temperamentally +temperaments +temperance +temperas +temperate +temperately +temperateness +temperature +temperatures +tempered +tempering +tempers +tempest +tempests +tempestuous +tempestuously +tempestuousness +tempi +temping +template +templates +temple +temples +tempo +temporal +temporally +temporaries +temporarily +temporariness +temporary +temporize +temporized +temporizer +temporizers +temporizes +temporizing +tempos +temps +tempt +temptation +temptations +tempted +tempter +tempters +tempting +temptingly +temptress +temptresses +tempts +tempura +tenability +tenable +tenably +tenacious +tenaciously +tenaciousness +tenacity +tenancies +tenancy +tenant +tenanted +tenanting +tenantry +tenants +tend +tended +tendencies +tendency +tendentious +tendentiously +tendentiousness +tender +tendered +tenderer +tenderest +tenderfeet +tenderfoot +tenderfoots +tenderhearted +tenderheartedness +tendering +tenderize +tenderized +tenderizer +tenderizers +tenderizes +tenderizing +tenderloin +tenderloins +tenderly +tenderness +tenders +tending +tendinitis +tendon +tendonitis +tendons +tendril +tendrils +tends +tenement +tenements +tenet +tenets +tenfold +tennis +tenon +tenoned +tenoning +tenons +tenor +tenors +tenpin +tenpins +tens +tense +tensed +tensely +tenseness +tenser +tenses +tensest +tensile +tensing +tension +tensions +tensity +tent +tentacle +tentacled +tentacles +tentative +tentatively +tentativeness +tented +tenterhook +tenterhooks +tenth +tenthly +tenths +tenting +tents +tenuity +tenuous +tenuously +tenuousness +tenure +tenured +tenures +tenuring +tepee +tepees +tepid +tepider +tepidest +tepidity +tepidly +tepidness +tequila +tequilas +terabyte +terabytes +terbium +tercentenaries +tercentenary +tercentennial +tercentennials +term +termagant +termagants +termed +terminable +terminal +terminally +terminals +terminate +terminated +terminates +terminating +termination +terminations +terming +termini +terminological +terminologically +terminologies +terminology +terminus +terminuses +termite +termites +termly +terms +tern +ternaries +ternary +terns +terrace +terraced +terraces +terracing +terracotta +terrain +terrains +terrapin +terrapins +terraria +terrarium +terrariums +terrazzo +terrazzos +terrestrial +terrestrially +terrestrials +terrible +terribleness +terribly +terrier +terriers +terrific +terrifically +terrified +terrifies +terrify +terrifying +terrifyingly +territorial +territorials +territories +territory +terror +terrorism +terrorist +terrorists +terrorize +terrorized +terrorizes +terrorizing +terrors +terry +terrycloth +terse +tersely +terseness +terser +tersest +tertiary +tessellate +tessellated +tessellates +tessellating +tessellation +tessellations +test +testament +testamentary +testaments +testate +testator +testators +testatrices +testatrix +tested +tester +testers +testes +testicle +testicles +testier +testiest +testified +testifier +testifiers +testifies +testify +testifying +testily +testimonial +testimonials +testimonies +testimony +testiness +testing +testis +testosterone +tests +testy +tetanus +tetchier +tetchiest +tetchy +tether +tethered +tethering +tethers +tetra +tetracycline +tetrahedra +tetrahedral +tetrahedron +tetrahedrons +tetrameter +tetrameters +tetras +text +textbook +textbooks +textile +textiles +texts +textual +textually +textural +texture +textured +textures +texturing +thalami +thalamus +thalidomide +thallium +than +thane +thanes +thank +thanked +thankful +thankfully +thankfulness +thanking +thankless +thanklessly +thanklessness +thanks +thanksgiving +thanksgivings +that +thatch +thatched +thatcher +thatchers +thatches +thatching +thaw +thawed +thawing +thaws +theater +theatergoer +theatergoers +theaters +theatre +theatres +theatrical +theatricality +theatrically +theatricals +theatrics +thee +theft +thefts +their +theirs +theism +theist +theistic +theists +them +thematic +thematically +theme +themes +themselves +then +thence +thenceforth +thenceforward +theocracies +theocracy +theocratic +theologian +theologians +theological +theologically +theologies +theology +theorem +theorems +theoretic +theoretical +theoretically +theoretician +theoreticians +theories +theorist +theorists +theorize +theorized +theorizes +theorizing +theory +theosophic +theosophical +theosophist +theosophists +theosophy +therapeutic +therapeutically +therapeutics +therapies +therapist +therapists +therapy +there +thereabout +thereabouts +thereafter +thereat +thereby +therefor +therefore +therein +thereof +thereon +thereto +theretofore +thereunto +thereupon +therewith +therm +thermal +thermally +thermals +thermodynamic +thermodynamics +thermometer +thermometers +thermometric +thermonuclear +thermoplastic +thermoplastics +thermos +thermoses +thermostat +thermostatic +thermostatically +thermostats +therms +thesauri +thesaurus +thesauruses +these +theses +thesis +thespian +thespians +theta +thetas +thew +thews +they +thiamin +thiamine +thick +thicken +thickened +thickener +thickeners +thickening +thickenings +thickens +thicker +thickest +thicket +thickets +thickheaded +thickly +thickness +thicknesses +thickset +thief +thieve +thieved +thievery +thieves +thieving +thievish +thigh +thighbone +thighbones +thighs +thimble +thimbleful +thimblefuls +thimbles +thin +thine +thing +thingamabob +thingamabobs +thingamajig +thingamajigs +things +think +thinkable +thinker +thinkers +thinking +thinks +thinly +thinned +thinner +thinners +thinness +thinnest +thinning +thins +third +thirdly +thirds +thirst +thirsted +thirstier +thirstiest +thirstily +thirstiness +thirsting +thirsts +thirsty +thirteen +thirteens +thirteenth +thirteenths +thirties +thirtieth +thirtieths +thirty +this +thistle +thistledown +thistles +thither +thole +tholes +thong +thongs +thoraces +thoracic +thorax +thoraxes +thorium +thorn +thornier +thorniest +thorniness +thorns +thorny +thorough +thoroughbred +thoroughbreds +thorougher +thoroughest +thoroughfare +thoroughfares +thoroughgoing +thoroughly +thoroughness +those +thou +though +thought +thoughtful +thoughtfully +thoughtfulness +thoughtless +thoughtlessly +thoughtlessness +thoughts +thous +thousand +thousandfold +thousands +thousandth +thousandths +thraldom +thrall +thralldom +thralled +thralling +thralls +thrash +thrashed +thrasher +thrashers +thrashes +thrashing +thrashings +thread +threadbare +threaded +threader +threaders +threadier +threadiest +threading +threadlike +threads +thready +threat +threaten +threatened +threatening +threateningly +threatens +threats +three +threefold +threepence +threepences +threes +threescore +threescores +threesome +threesomes +threnodies +threnody +thresh +threshed +thresher +threshers +threshes +threshing +threshold +thresholds +threw +thrice +thrift +thriftier +thriftiest +thriftily +thriftiness +thriftless +thrifts +thrifty +thrill +thrilled +thriller +thrillers +thrilling +thrillingly +thrills +thrive +thrived +thriven +thrives +thriving +throat +throatier +throatiest +throatily +throatiness +throats +throaty +throb +throbbed +throbbing +throbs +throe +throes +thrombi +thromboses +thrombosis +thrombotic +thrombus +throne +thrones +throng +thronged +thronging +throngs +throttle +throttled +throttler +throttlers +throttles +throttling +through +throughout +throughput +throughway +throughways +throve +throw +throwaway +throwaways +throwback +throwbacks +thrower +throwers +throwing +thrown +throws +thru +thrum +thrummed +thrumming +thrums +thrush +thrushes +thrust +thrusting +thrusts +thruway +thruways +thud +thudded +thudding +thuds +thug +thuggery +thuggish +thugs +thulium +thumb +thumbed +thumbing +thumbnail +thumbnails +thumbprint +thumbprints +thumbs +thumbscrew +thumbscrews +thumbtack +thumbtacks +thump +thumped +thumping +thumpings +thumps +thunder +thunderbolt +thunderbolts +thunderclap +thunderclaps +thundercloud +thunderclouds +thundered +thunderer +thunderers +thunderhead +thunderheads +thundering +thunderous +thunderously +thunders +thundershower +thundershowers +thunderstorm +thunderstorms +thunderstricken +thunderstruck +thus +thwack +thwacked +thwacker +thwackers +thwacking +thwacks +thwart +thwarted +thwarting +thwarts +thyme +thymi +thymine +thymus +thymuses +thyroid +thyroidal +thyroids +thyself +tiara +tiaras +tibia +tibiae +tibial +tibias +tick +ticked +ticker +tickers +ticket +ticketed +ticketing +tickets +ticking +tickle +tickled +tickler +ticklers +tickles +tickling +ticklish +ticklishly +ticklishness +ticks +ticktacktoe +ticktock +ticktocks +tics +tidal +tidally +tidbit +tidbits +tiddlywinks +tide +tided +tideland +tidelands +tides +tidewater +tidewaters +tideway +tideways +tidied +tidier +tidies +tidiest +tidily +tidiness +tiding +tidings +tidy +tidying +tieback +tiebacks +tiebreaker +tiebreakers +tied +tieing +tier +tiered +tiers +ties +tiff +tiffed +tiffing +tiffs +tiger +tigerish +tigers +tight +tighten +tightened +tightener +tighteners +tightening +tightens +tighter +tightest +tightfisted +tightly +tightness +tightrope +tightropes +tights +tightwad +tightwads +tigress +tigresses +tike +tikes +tilde +tildes +tile +tiled +tiler +tilers +tiles +tiling +till +tillable +tillage +tilled +tiller +tillers +tilling +tills +tilt +tilted +tilting +tilts +timber +timbered +timbering +timberland +timberline +timberlines +timbers +timbre +timbrel +timbrels +timbres +time +timed +timekeeper +timekeepers +timekeeping +timeless +timelessly +timelessness +timelier +timeliest +timeliness +timely +timeout +timeouts +timepiece +timepieces +timer +timers +times +timeserver +timeservers +timeserving +timetable +timetabled +timetables +timetabling +timeworn +timid +timider +timidest +timidity +timidly +timidness +timing +timorous +timorously +timorousness +timothy +timpani +timpanist +timpanists +tincture +tinctured +tinctures +tincturing +tinder +tinderbox +tinderboxes +tine +tines +tinfoil +ting +tinge +tinged +tingeing +tinges +tinging +tingle +tingled +tingles +tinglier +tingliest +tingling +tinglings +tingly +tings +tinier +tiniest +tininess +tinker +tinkered +tinkerer +tinkerers +tinkering +tinkers +tinkle +tinkled +tinkles +tinkling +tinned +tinnier +tinniest +tinniness +tinning +tinnitus +tinny +tinplate +tins +tinsel +tinseled +tinseling +tinselled +tinselling +tinsels +tinsmith +tinsmiths +tint +tinted +tinting +tintinnabulation +tintinnabulations +tints +tintype +tintypes +tinware +tiny +tipi +tipis +tipped +tipper +tippers +tippet +tippets +tipping +tipple +tippled +tippler +tipplers +tipples +tippling +tips +tipsier +tipsiest +tipsily +tipsiness +tipster +tipsters +tipsy +tiptoe +tiptoed +tiptoeing +tiptoes +tiptop +tiptops +tirade +tirades +tire +tired +tireder +tiredest +tiredly +tiredness +tireless +tirelessly +tirelessness +tires +tiresome +tiresomely +tiresomeness +tiring +tiro +tiros +tissue +tissues +titan +titanic +titanium +titans +titbit +titbits +tithe +tithed +tither +tithers +tithes +tithing +titian +titillate +titillated +titillates +titillating +titillatingly +titillation +titivate +titivated +titivates +titivating +titivation +title +titled +titleholder +titleholders +titles +titling +titlist +titlists +titmice +titmouse +tits +titter +tittered +tittering +titters +tittivate +tittivated +tittivates +tittivating +tittle +tittles +titular +tizzies +tizzy +toad +toadied +toadies +toads +toadstool +toadstools +toady +toadying +toadyism +toast +toasted +toaster +toasters +toastier +toastiest +toasting +toastmaster +toastmasters +toastmistress +toastmistresses +toasts +toasty +tobacco +tobaccoes +tobacconist +tobacconists +tobaccos +toboggan +tobogganed +tobogganer +tobogganers +tobogganing +toboggans +tocsin +tocsins +today +toddies +toddle +toddled +toddler +toddlers +toddles +toddling +toddy +toecap +toecaps +toed +toehold +toeholds +toeing +toenail +toenails +toes +toffee +toffees +toffies +toffy +tofu +toga +togae +togaed +togas +together +togetherness +togged +togging +toggle +toggled +toggles +toggling +togs +toil +toiled +toiler +toilers +toilet +toileted +toileting +toiletries +toiletry +toilets +toilette +toiling +toils +toilsome +toke +toked +token +tokenism +tokens +tokes +toking +told +tole +tolerable +tolerably +tolerance +tolerances +tolerant +tolerantly +tolerate +tolerated +tolerates +tolerating +toleration +toll +tollbooth +tollbooths +tolled +tollgate +tollgates +tolling +tolls +tollway +tollways +toluene +tomahawk +tomahawked +tomahawking +tomahawks +tomato +tomatoes +tomb +tombed +tombing +tomboy +tomboyish +tomboys +tombs +tombstone +tombstones +tomcat +tomcats +tome +tomes +tomfooleries +tomfoolery +tomographic +tomography +tomorrow +tomorrows +toms +tomtit +tomtits +tonal +tonalities +tonality +tonally +tone +tonearm +tonearms +toned +toneless +tonelessly +toner +tones +tong +tonged +tonging +tongs +tongue +tongued +tongueless +tongues +tonguing +tonic +tonics +tonier +toniest +tonight +toning +tonnage +tonnages +tonne +tonnes +tons +tonsil +tonsillectomies +tonsillectomy +tonsillitis +tonsils +tonsorial +tonsure +tonsured +tonsures +tonsuring +tony +took +tool +toolbox +toolboxes +tooled +tooling +toolmaker +toolmakers +tools +toot +tooted +tooter +tooters +tooth +toothache +toothaches +toothbrush +toothbrushes +toothed +toothier +toothiest +toothily +toothless +toothpaste +toothpastes +toothpick +toothpicks +toothsome +toothy +tooting +tootle +tootled +tootles +tootling +toots +topaz +topazes +topcoat +topcoats +topdressing +topdressings +topflight +topiary +topic +topical +topicality +topically +topics +topknot +topknots +topless +topmast +topmasts +topmost +topnotch +topographer +topographers +topographic +topographical +topographically +topographies +topography +topped +topper +toppers +topping +toppings +topple +toppled +topples +toppling +tops +topsail +topsails +topside +topsides +topsoil +topspin +toque +toques +torah +torahs +torch +torchbearer +torchbearers +torched +torches +torching +torchlight +tore +toreador +toreadors +torment +tormented +tormenter +tormenters +tormenting +tormentingly +tormentor +tormentors +torments +torn +tornado +tornadoes +tornados +torpedo +torpedoed +torpedoes +torpedoing +torpid +torpidity +torpidly +torpor +torque +torqued +torques +torquing +torrent +torrential +torrents +torrid +torridity +torridly +torridness +tors +torsi +torsion +torsional +torso +torsos +tort +torte +tortellini +tortes +tortilla +tortillas +tortoise +tortoises +tortoiseshell +tortoiseshells +tortoni +torts +tortuous +tortuously +tortuousness +torture +tortured +torturer +torturers +tortures +torturing +torturous +toss +tossed +tosses +tossing +tossup +tossups +tost +total +totaled +totaling +totalisator +totalisators +totalitarian +totalitarianism +totalitarians +totalities +totality +totalizator +totalizators +totalled +totalling +totally +totals +tote +toted +totem +totemic +totems +totes +toting +tots +totted +totter +tottered +totterer +totterers +tottering +totters +totting +toucan +toucans +touch +touchable +touchdown +touchdowns +touche +touched +touches +touchier +touchiest +touchily +touchiness +touching +touchingly +touchscreen +touchscreens +touchstone +touchstones +touchy +tough +toughen +toughened +toughener +tougheners +toughening +toughens +tougher +toughest +toughie +toughies +toughly +toughness +toughs +toupee +toupees +tour +toured +touring +tourism +tourist +tourists +tourmaline +tournament +tournaments +tourney +tourneys +tourniquet +tourniquets +tours +tousle +tousled +tousles +tousling +tout +touted +touting +touts +toward +towards +towboat +towboats +towed +towel +toweled +towelette +towelettes +toweling +towelled +towelling +towels +tower +towered +towering +towers +towhead +towheaded +towheads +towhee +towhees +towing +towline +towlines +town +townhouse +townhouses +townie +townies +towns +townsfolk +township +townships +townsman +townsmen +townspeople +townswoman +townswomen +towpath +towpaths +towrope +towropes +tows +toxemia +toxic +toxicity +toxicological +toxicologist +toxicologists +toxicology +toxin +toxins +toyed +toying +toys +trace +traceable +traced +tracer +traceries +tracers +tracery +traces +trachea +tracheae +tracheal +tracheas +tracheotomies +tracheotomy +tracing +tracings +track +trackball +trackballs +tracked +tracker +trackers +tracking +trackless +tracks +tract +tractability +tractable +tractably +traction +tractor +tractors +tracts +trade +traded +trademark +trademarked +trademarking +trademarks +tradeoff +tradeoffs +trader +traders +trades +tradesman +tradesmen +tradespeople +tradeswoman +tradeswomen +trading +tradings +tradition +traditional +traditionalism +traditionalist +traditionalists +traditionally +traditions +traduce +traduced +traducer +traducers +traduces +traducing +traffic +trafficked +trafficker +traffickers +trafficking +traffics +tragedian +tragedians +tragedienne +tragediennes +tragedies +tragedy +tragic +tragically +tragicomedies +tragicomedy +tragicomic +trail +trailblazer +trailblazers +trailblazing +trailed +trailer +trailers +trailing +trails +train +trainable +trained +trainee +trainees +trainer +trainers +training +trainload +trainloads +trainman +trainmen +trains +traipse +traipsed +traipses +traipsing +trait +traitor +traitorous +traitorously +traitors +traits +trajectories +trajectory +tram +trammed +trammel +trammeled +trammeling +trammelled +trammelling +trammels +tramming +tramp +tramped +tramper +trampers +tramping +trample +trampled +trampler +tramplers +tramples +trampling +trampoline +trampolined +trampolines +trampolining +tramps +trams +trance +trances +tranquil +tranquiler +tranquilest +tranquility +tranquilize +tranquilized +tranquilizer +tranquilizers +tranquilizes +tranquilizing +tranquiller +tranquillest +tranquillity +tranquillize +tranquillized +tranquillizer +tranquillizers +tranquillizes +tranquillizing +tranquilly +transact +transacted +transacting +transaction +transactions +transactor +transactors +transacts +transatlantic +transceiver +transceivers +transcend +transcended +transcendence +transcendent +transcendental +transcendentalism +transcendentalist +transcendentalists +transcendentally +transcending +transcends +transcontinental +transcribe +transcribed +transcriber +transcribers +transcribes +transcribing +transcript +transcription +transcriptions +transcripts +transducer +transducers +transect +transected +transecting +transects +transept +transepts +transfer +transferable +transferal +transferals +transference +transferred +transferring +transfers +transfiguration +transfigure +transfigured +transfigures +transfiguring +transfix +transfixed +transfixes +transfixing +transfixt +transform +transformable +transformation +transformations +transformed +transformer +transformers +transforming +transforms +transfuse +transfused +transfuses +transfusing +transfusion +transfusions +transgender +transgendered +transgenders +transgress +transgressed +transgresses +transgressing +transgression +transgressions +transgressor +transgressors +transience +transiency +transient +transiently +transients +transistor +transistorize +transistorized +transistorizes +transistorizing +transistors +transit +transited +transiting +transition +transitional +transitionally +transitioned +transitioning +transitions +transitive +transitively +transitiveness +transitives +transitivity +transitory +transits +translatable +translate +translated +translates +translating +translation +translations +translator +translators +transliterate +transliterated +transliterates +transliterating +transliteration +transliterations +translucence +translucency +translucent +translucently +transmigrate +transmigrated +transmigrates +transmigrating +transmigration +transmissible +transmission +transmissions +transmit +transmits +transmittable +transmittal +transmittance +transmitted +transmitter +transmitters +transmitting +transmogrification +transmogrified +transmogrifies +transmogrify +transmogrifying +transmutable +transmutation +transmutations +transmute +transmuted +transmutes +transmuting +transnational +transnationals +transoceanic +transom +transoms +transpacific +transparencies +transparency +transparent +transparently +transpiration +transpire +transpired +transpires +transpiring +transplant +transplantation +transplanted +transplanting +transplants +transpolar +transponder +transponders +transport +transportable +transportation +transported +transporter +transporters +transporting +transports +transpose +transposed +transposes +transposing +transposition +transpositions +transsexual +transsexualism +transsexuals +transship +transshipment +transshipped +transshipping +transships +transubstantiation +transverse +transversely +transverses +transvestism +transvestite +transvestites +trap +trapdoor +trapdoors +trapeze +trapezes +trapezia +trapezium +trapeziums +trapezoid +trapezoidal +trapezoids +trapped +trapper +trappers +trapping +trappings +traps +trapshooting +trash +trashed +trashes +trashier +trashiest +trashiness +trashing +trashy +trauma +traumas +traumata +traumatic +traumatically +traumatize +traumatized +traumatizes +traumatizing +travail +travailed +travailing +travails +travel +traveled +traveler +travelers +traveling +travelled +traveller +travellers +travelling +travelog +travelogs +travelogue +travelogues +travels +traversal +traversals +traverse +traversed +traverses +traversing +travestied +travesties +travesty +travestying +trawl +trawled +trawler +trawlers +trawling +trawls +tray +trays +treacheries +treacherous +treacherously +treacherousness +treachery +treacle +treacly +tread +treading +treadle +treadled +treadles +treadling +treadmill +treadmills +treads +treason +treasonable +treasonous +treasure +treasured +treasurer +treasurers +treasures +treasuries +treasuring +treasury +treat +treatable +treated +treaties +treating +treatise +treatises +treatment +treatments +treats +treaty +treble +trebled +trebles +trebling +tree +treed +treeing +treeless +treelike +trees +treetop +treetops +trefoil +trefoils +trek +trekked +trekker +trekkers +trekking +treks +trellis +trellised +trellises +trellising +trematode +trematodes +tremble +trembled +trembles +trembling +tremendous +tremendously +tremolo +tremolos +tremor +tremors +tremulous +tremulously +tremulousness +trench +trenchancy +trenchant +trenchantly +trenched +trencher +trencherman +trenchermen +trenchers +trenches +trenching +trend +trended +trendier +trendies +trendiest +trendily +trendiness +trending +trends +trendy +trepidation +trespass +trespassed +trespasser +trespassers +trespasses +trespassing +tress +tresses +trestle +trestles +trey +treys +triad +triads +triage +trial +trialled +trialling +trials +triangle +triangles +triangular +triangularly +triangulate +triangulated +triangulates +triangulating +triangulation +triathlon +triathlons +tribal +tribalism +tribe +tribes +tribesman +tribesmen +tribeswoman +tribeswomen +tribulation +tribulations +tribunal +tribunals +tribune +tribunes +tributaries +tributary +tribute +tributes +trice +tricentennial +tricentennials +triceps +tricepses +triceratops +triceratopses +trichina +trichinae +trichinas +trichinosis +trick +tricked +trickery +trickier +trickiest +trickily +trickiness +tricking +trickle +trickled +trickles +trickling +tricks +trickster +tricksters +tricky +tricolor +tricolors +tricolour +tricolours +tricycle +tricycles +trident +tridents +tried +triennial +triennially +triennials +trier +triers +tries +trifle +trifled +trifler +triflers +trifles +trifling +trifocals +trig +trigger +triggered +triggering +triggers +triglyceride +triglycerides +trigonometric +trigonometrical +trigonometry +trike +trikes +trilateral +trilbies +trilby +trill +trilled +trilling +trillion +trillions +trillionth +trillionths +trillium +trills +trilobite +trilobites +trilogies +trilogy +trim +trimaran +trimarans +trimester +trimesters +trimly +trimmed +trimmer +trimmers +trimmest +trimming +trimmings +trimness +trimonthly +trims +trinities +trinitrotoluene +trinity +trinket +trinkets +trio +trios +trip +tripartite +tripe +triple +tripled +triples +triplet +triplets +triplex +triplexes +triplicate +triplicated +triplicates +triplicating +tripling +triply +tripod +tripodal +tripods +tripped +tripper +trippers +tripping +trips +triptych +triptychs +trireme +triremes +trisect +trisected +trisecting +trisection +trisects +trite +tritely +triteness +triter +tritest +tritium +triumph +triumphal +triumphant +triumphantly +triumphed +triumphing +triumphs +triumvir +triumvirate +triumvirates +triumvirs +trivalent +trivet +trivets +trivia +trivial +trivialities +triviality +trivialization +trivialize +trivialized +trivializes +trivializing +trivially +trivium +trochaic +trochee +trochees +trod +trodden +troglodyte +troglodytes +troika +troikas +troll +trolled +trolley +trolleybus +trolleybuses +trolleybusses +trolleys +trollies +trolling +trollop +trollops +trolls +trolly +trombone +trombones +trombonist +trombonists +tromp +tromped +tromping +tromps +troop +trooped +trooper +troopers +trooping +troops +troopship +troopships +trope +tropes +trophies +trophy +tropic +tropical +tropically +tropics +tropism +tropisms +troposphere +tropospheres +trot +troth +trots +trotted +trotter +trotters +trotting +troubadour +troubadours +trouble +troubled +troublemaker +troublemakers +troubles +troubleshoot +troubleshooted +troubleshooter +troubleshooters +troubleshooting +troubleshoots +troubleshot +troublesome +troublesomely +troubling +trough +troughs +trounce +trounced +trouncer +trouncers +trounces +trouncing +troupe +trouped +trouper +troupers +troupes +trouping +trouser +trousers +trousseau +trousseaus +trousseaux +trout +trouts +trove +troves +trow +trowed +trowel +troweled +troweling +trowelled +trowelling +trowels +trowing +trows +troy +truancy +truant +truanted +truanting +truants +truce +truces +truck +trucked +trucker +truckers +trucking +truckle +truckled +truckles +truckling +truckload +truckloads +trucks +truculence +truculent +truculently +trudge +trudged +trudges +trudging +true +trued +trueing +truelove +trueloves +truer +trues +truest +truffle +truffles +truing +truism +truisms +truly +trump +trumped +trumpery +trumpet +trumpeted +trumpeter +trumpeters +trumpeting +trumpets +trumping +trumps +truncate +truncated +truncates +truncating +truncation +truncheon +truncheons +trundle +trundled +trundler +trundlers +trundles +trundling +trunk +trunks +truss +trussed +trusses +trussing +trust +trusted +trustee +trustees +trusteeship +trusteeships +trustful +trustfully +trustfulness +trustier +trusties +trustiest +trusting +trustingly +trusts +trustworthier +trustworthiest +trustworthiness +trustworthy +trusty +truth +truthful +truthfully +truthfulness +truths +trying +tryingly +tryout +tryouts +tryst +trysted +trysting +trysts +tsar +tsarina +tsarinas +tsars +tsetse +tsetses +tsunami +tsunamis +tuba +tubal +tubas +tubbier +tubbiest +tubby +tube +tubed +tubeless +tuber +tubercle +tubercles +tubercular +tuberculin +tuberculosis +tuberculous +tuberose +tuberous +tubers +tubes +tubful +tubfuls +tubing +tubs +tubular +tubule +tubules +tuck +tucked +tucker +tuckered +tuckering +tuckers +tucking +tucks +tuft +tufted +tufter +tufters +tufting +tufts +tugboat +tugboats +tugged +tugging +tugs +tuition +tularemia +tulip +tulips +tulle +tumble +tumbled +tumbledown +tumbler +tumblers +tumbles +tumbleweed +tumbleweeds +tumbling +tumbrel +tumbrels +tumbril +tumbrils +tumescence +tumescent +tumid +tumidity +tummies +tummy +tumor +tumorous +tumors +tumour +tumours +tumult +tumults +tumultuous +tumultuously +tuna +tunas +tundra +tundras +tune +tuned +tuneful +tunefully +tunefulness +tuneless +tunelessly +tuner +tuners +tunes +tuneup +tuneups +tungsten +tunic +tunics +tuning +tunnel +tunneled +tunneler +tunnelers +tunneling +tunnelled +tunneller +tunnellers +tunnelling +tunnels +tunnies +tunny +tuns +tuque +tuques +turban +turbans +turbid +turbidity +turbine +turbines +turbo +turbocharge +turbocharged +turbocharger +turbochargers +turbocharges +turbocharging +turbofan +turbofans +turbojet +turbojets +turboprop +turboprops +turbos +turbot +turbots +turbulence +turbulent +turbulently +turd +turds +tureen +tureens +turf +turfed +turfing +turfs +turfy +turgid +turgidity +turgidly +turkey +turkeys +turmeric +turmerics +turmoil +turmoils +turn +turnabout +turnabouts +turnaround +turnarounds +turnbuckle +turnbuckles +turncoat +turncoats +turned +turner +turners +turning +turnings +turnip +turnips +turnkey +turnkeys +turnoff +turnoffs +turnout +turnouts +turnover +turnovers +turnpike +turnpikes +turns +turnstile +turnstiles +turntable +turntables +turpentine +turpitude +turquoise +turquoises +turret +turreted +turrets +turtle +turtledove +turtledoves +turtleneck +turtlenecked +turtlenecks +turtles +turves +tush +tushes +tusk +tusked +tusks +tussle +tussled +tussles +tussling +tussock +tussocks +tussocky +tutelage +tutelary +tutor +tutored +tutorial +tutorials +tutoring +tutors +tutorship +tuts +tutted +tutti +tutting +tuttis +tutu +tutus +tuxedo +tuxedoes +tuxedos +tuxes +twaddle +twaddled +twaddler +twaddlers +twaddles +twaddling +twain +twang +twanged +twangier +twangiest +twanging +twangs +twangy +twas +tweak +tweaked +tweaking +tweaks +tweed +tweedier +tweediest +tweeds +tweedy +tween +tweet +tweeted +tweeter +tweeters +tweeting +tweets +tweezers +twelfth +twelfths +twelve +twelvemonth +twelvemonths +twelves +twenties +twentieth +twentieths +twenty +twerp +twerps +twice +twiddle +twiddled +twiddles +twiddlier +twiddliest +twiddling +twiddly +twig +twigged +twiggier +twiggiest +twigging +twiggy +twigs +twilight +twilit +twill +twilled +twin +twine +twined +twiner +twiners +twines +twinge +twinged +twingeing +twinges +twinging +twinight +twining +twinkle +twinkled +twinkles +twinkling +twinklings +twinkly +twinned +twinning +twins +twirl +twirled +twirler +twirlers +twirlier +twirliest +twirling +twirls +twirly +twist +twisted +twister +twisters +twistier +twistiest +twisting +twists +twisty +twit +twitch +twitched +twitches +twitchier +twitchiest +twitching +twitchy +twits +twitted +twitter +twittered +twittering +twitters +twittery +twitting +twixt +twofer +twofers +twofold +twopence +twopences +twopenny +twos +twosome +twosomes +tycoon +tycoons +tying +tyke +tykes +tympana +tympani +tympanist +tympanists +tympanum +tympanums +type +typecast +typecasting +typecasts +typed +typeface +typefaces +types +typescript +typescripts +typeset +typesets +typesetter +typesetters +typesetting +typewrite +typewriter +typewriters +typewrites +typewriting +typewritten +typewrote +typhoid +typhoon +typhoons +typhus +typical +typicality +typically +typification +typified +typifies +typify +typifying +typing +typist +typists +typo +typographer +typographers +typographic +typographical +typographically +typography +typology +typos +tyrannic +tyrannical +tyrannically +tyrannies +tyrannize +tyrannized +tyrannizes +tyrannizing +tyrannosaur +tyrannosaurs +tyrannosaurus +tyrannosauruses +tyrannous +tyranny +tyrant +tyrants +tyre +tyres +tyro +tyros +tzar +tzarina +tzarinas +tzars +ubiquitous +ubiquitously +ubiquity +udder +udders +ufologist +ufologists +ufology +uglier +ugliest +ugliness +ugly +ukase +ukases +ukelele +ukeleles +ukulele +ukuleles +ulcer +ulcerate +ulcerated +ulcerates +ulcerating +ulceration +ulcerations +ulcerous +ulcers +ulna +ulnae +ulnar +ulnas +ulster +ulsters +ulterior +ultimata +ultimate +ultimately +ultimatum +ultimatums +ultimo +ultra +ultraconservative +ultraconservatives +ultrahigh +ultralight +ultralights +ultramarine +ultramodern +ultras +ultrasonic +ultrasonically +ultrasound +ultrasounds +ultraviolet +ululate +ululated +ululates +ululating +ululation +ululations +umbel +umbels +umber +umbilical +umbilici +umbilicus +umbilicuses +umbra +umbrae +umbrage +umbras +umbrella +umbrellas +umiak +umiaks +umlaut +umlauts +umped +umping +umpire +umpired +umpires +umpiring +umps +umpteen +umpteenth +unabashed +unabashedly +unabated +unable +unabridged +unabridgeds +unaccented +unacceptable +unacceptably +unaccommodating +unaccompanied +unaccomplished +unaccountable +unaccountably +unaccounted +unaccredited +unaccustomed +unacknowledged +unacquainted +unadorned +unadulterated +unadventurous +unadvertised +unadvised +unadvisedly +unaesthetic +unaffected +unaffectedly +unaffiliated +unafraid +unaided +unalienable +unaligned +unalike +unalloyed +unalterable +unalterably +unaltered +unambiguous +unambiguously +unambitious +unanimity +unanimous +unanimously +unannounced +unanswerable +unanswered +unanticipated +unapologetic +unapparent +unappealing +unappealingly +unappetizing +unappreciated +unappreciative +unapproachable +unappropriated +unapproved +unarguable +unarguably +unarmed +unarmored +unashamed +unashamedly +unasked +unassailable +unassertive +unassisted +unassuming +unassumingly +unattached +unattainable +unattended +unattested +unattractive +unattractively +unauthentic +unauthorized +unavailability +unavailable +unavailing +unavailingly +unavoidable +unavoidably +unaware +unawareness +unawares +unbaked +unbalanced +unbaptized +unbar +unbarred +unbarring +unbars +unbearable +unbearably +unbeatable +unbeaten +unbecoming +unbecomingly +unbeknown +unbeknownst +unbelief +unbelievable +unbelievably +unbeliever +unbelievers +unbelieving +unbend +unbending +unbends +unbent +unbiased +unbiassed +unbid +unbidden +unbind +unbinding +unbinds +unbleached +unblemished +unblinking +unblock +unblocked +unblocking +unblocks +unblushing +unblushingly +unbolt +unbolted +unbolting +unbolts +unborn +unbosom +unbosomed +unbosoming +unbosoms +unbound +unbounded +unbowed +unbreakable +unbridgeable +unbridled +unbroken +unbuckle +unbuckled +unbuckles +unbuckling +unburden +unburdened +unburdening +unburdens +unbutton +unbuttoned +unbuttoning +unbuttons +uncannier +uncanniest +uncannily +uncanny +uncap +uncapped +uncapping +uncaps +uncaring +uncaught +unceasing +unceasingly +uncensored +unceremonious +unceremoniously +uncertain +uncertainly +uncertainties +uncertainty +unchain +unchained +unchaining +unchains +unchallenged +unchangeable +unchanged +unchanging +unchaperoned +uncharacteristic +uncharacteristically +uncharged +uncharitable +uncharitably +uncharted +unchaste +unchaster +unchastest +unchecked +unchristian +uncial +uncircumcised +uncivil +uncivilized +uncivilly +unclad +unclaimed +unclasp +unclasped +unclasping +unclasps +unclassified +uncle +unclean +uncleaned +uncleaner +uncleanest +uncleanlier +uncleanliest +uncleanliness +uncleanly +uncleanness +unclear +uncleared +unclearer +unclearest +uncles +uncloak +uncloaked +uncloaking +uncloaks +unclog +unclogged +unclogging +unclogs +unclothe +unclothed +unclothes +unclothing +unclouded +uncluttered +uncoil +uncoiled +uncoiling +uncoils +uncollected +uncolored +uncombed +uncombined +uncomfortable +uncomfortably +uncommitted +uncommon +uncommoner +uncommonest +uncommonly +uncommonness +uncommunicative +uncompensated +uncomplaining +uncomplainingly +uncompleted +uncomplicated +uncomplimentary +uncompounded +uncomprehending +uncomprehendingly +uncompromising +uncompromisingly +unconcealed +unconcern +unconcerned +unconcernedly +unconditional +unconditionally +unconditioned +unconfined +unconfirmed +unconformable +uncongenial +unconnected +unconquerable +unconquered +unconscionable +unconscionably +unconscious +unconsciously +unconsciousness +unconsecrated +unconsidered +unconsolidated +unconstitutional +unconstitutionality +unconstitutionally +unconstrained +unconsumed +unconsummated +uncontaminated +uncontested +uncontrollable +uncontrollably +uncontrolled +unconventional +unconventionality +unconventionally +unconverted +unconvinced +unconvincing +unconvincingly +uncooked +uncool +uncooperative +uncoordinated +uncork +uncorked +uncorking +uncorks +uncorrected +uncorroborated +uncountable +uncounted +uncouple +uncoupled +uncouples +uncoupling +uncouth +uncouthly +uncover +uncovered +uncovering +uncovers +uncritical +uncritically +uncross +uncrossed +uncrosses +uncrossing +uncrowded +uncrowned +unction +unctions +unctuous +unctuously +unctuousness +uncultivated +uncultured +uncured +uncurl +uncurled +uncurling +uncurls +uncustomary +uncut +undamaged +undated +undaunted +undauntedly +undeceive +undeceived +undeceives +undeceiving +undecided +undecideds +undecipherable +undeclared +undefeated +undefended +undefinable +undefined +undemanding +undemocratic +undemonstrative +undemonstratively +undeniable +undeniably +undependable +under +underachieve +underachieved +underachiever +underachievers +underachieves +underachieving +underact +underacted +underacting +underacts +underage +underarm +underarms +underbellies +underbelly +underbid +underbidding +underbids +underbrush +undercarriage +undercarriages +undercharge +undercharged +undercharges +undercharging +underclass +underclassman +underclassmen +underclothes +underclothing +undercoat +undercoated +undercoating +undercoatings +undercoats +undercover +undercurrent +undercurrents +undercut +undercuts +undercutting +underdeveloped +underdevelopment +underdog +underdogs +underdone +underemployed +underemployment +underestimate +underestimated +underestimates +underestimating +underestimation +underestimations +underexpose +underexposed +underexposes +underexposing +underexposure +underexposures +underfed +underfeed +underfeeding +underfeeds +underfoot +underfur +undergarment +undergarments +undergo +undergoes +undergoing +undergone +undergraduate +undergraduates +underground +undergrounds +undergrowth +underhand +underhanded +underhandedly +underhandedness +underlain +underlay +underlays +underlie +underlies +underline +underlined +underlines +underling +underlings +underlining +underlip +underlips +underlying +undermanned +undermentioned +undermine +undermined +undermines +undermining +undermost +underneath +underneaths +undernourished +undernourishment +underpaid +underpants +underpart +underparts +underpass +underpasses +underpay +underpaying +underpayment +underpayments +underpays +underpin +underpinned +underpinning +underpinnings +underpins +underplay +underplayed +underplaying +underplays +underpopulated +underprivileged +underproduction +underrate +underrated +underrates +underrating +underrepresented +underscore +underscored +underscores +underscoring +undersea +underseas +undersecretaries +undersecretary +undersell +underselling +undersells +undersexed +undershirt +undershirts +undershoot +undershooting +undershoots +undershorts +undershot +underside +undersides +undersign +undersigned +undersigning +undersigns +undersize +undersized +underskirt +underskirts +undersold +understaffed +understand +understandable +understandably +understanding +understandingly +understandings +understands +understate +understated +understatement +understatements +understates +understating +understood +understudied +understudies +understudy +understudying +undertake +undertaken +undertaker +undertakers +undertakes +undertaking +undertakings +underthings +undertone +undertones +undertook +undertow +undertows +undervaluation +undervalue +undervalued +undervalues +undervaluing +underwater +underway +underwear +underweight +underwent +underwhelm +underwhelmed +underwhelming +underwhelms +underworld +underworlds +underwrite +underwriter +underwriters +underwrites +underwriting +underwritten +underwrote +undeserved +undeservedly +undeserving +undesirability +undesirable +undesirables +undesirably +undesired +undetectable +undetected +undetermined +undeterred +undeveloped +undeviating +undid +undies +undifferentiated +undigested +undignified +undiluted +undiminished +undimmed +undiplomatic +undischarged +undisciplined +undisclosed +undiscovered +undiscriminating +undisguised +undismayed +undisputed +undissolved +undistinguished +undistributed +undisturbed +undivided +undo +undocumented +undoes +undoing +undoings +undomesticated +undone +undoubted +undoubtedly +undramatic +undreamed +undreamt +undress +undressed +undresses +undressing +undrinkable +undue +undulant +undulate +undulated +undulates +undulating +undulation +undulations +unduly +undying +unearned +unearth +unearthed +unearthing +unearthlier +unearthliest +unearthliness +unearthly +unearths +unease +uneasier +uneasiest +uneasily +uneasiness +uneasy +uneatable +uneaten +uneconomic +uneconomical +uneconomically +unedifying +unedited +uneducated +unembarrassed +unemotional +unemphatic +unemployable +unemployed +unemployment +unenclosed +unencumbered +unending +unendurable +unenforced +unenlightened +unenterprising +unenthusiastic +unenviable +unequal +unequaled +unequalled +unequally +unequipped +unequivocal +unequivocally +unerring +unerringly +unessential +unethical +unethically +uneven +unevenly +unevenness +uneventful +uneventfully +unexampled +unexceptionable +unexceptionably +unexceptional +unexceptionally +unexcited +unexciting +unexcused +unexpected +unexpectedly +unexpectedness +unexpired +unexplained +unexploited +unexplored +unexposed +unexpressed +unexpurgated +unfading +unfailing +unfailingly +unfair +unfairer +unfairest +unfairly +unfairness +unfaithful +unfaithfully +unfaithfulness +unfaltering +unfamiliar +unfamiliarity +unfashionable +unfashionably +unfasten +unfastened +unfastening +unfastens +unfathomable +unfathomably +unfavorable +unfavorably +unfeasible +unfed +unfeeling +unfeelingly +unfeigned +unfeminine +unfertilized +unfetter +unfettered +unfettering +unfetters +unfilled +unfiltered +unfinished +unfit +unfitness +unfits +unfitted +unfitter +unfittest +unfitting +unfix +unfixed +unfixes +unfixing +unflagging +unflaggingly +unflappability +unflappable +unflappably +unflattering +unflavored +unfledged +unflinching +unflinchingly +unfocused +unfold +unfolded +unfolding +unfolds +unforced +unforeseeable +unforeseen +unforgettable +unforgettably +unforgivable +unforgivably +unforgiving +unforgotten +unformed +unformulated +unfortified +unfortunate +unfortunately +unfortunates +unfounded +unframed +unfreeze +unfreezes +unfreezing +unfrequented +unfriendlier +unfriendliest +unfriendliness +unfriendly +unfrock +unfrocked +unfrocking +unfrocks +unfroze +unfrozen +unfruitful +unfulfilled +unfunded +unfunny +unfurl +unfurled +unfurling +unfurls +unfurnished +ungainlier +ungainliest +ungainliness +ungainly +ungenerous +ungentle +ungentlemanly +unglued +ungodlier +ungodliest +ungodliness +ungodly +ungovernable +ungoverned +ungraceful +ungracefully +ungracious +ungraciously +ungraded +ungrammatical +ungrammatically +ungrateful +ungratefully +ungratefulness +ungrudging +unguarded +unguent +unguents +unguided +ungulate +ungulates +unhallowed +unhampered +unhand +unhanded +unhandier +unhandiest +unhanding +unhands +unhandy +unhappier +unhappiest +unhappily +unhappiness +unhappy +unhardened +unharmed +unharness +unharnessed +unharnesses +unharnessing +unharvested +unhatched +unhealed +unhealthful +unhealthier +unhealthiest +unhealthily +unhealthiness +unhealthy +unheard +unheated +unheeded +unhelpful +unhelpfully +unheralded +unhesitating +unhesitatingly +unhindered +unhinge +unhinged +unhinges +unhinging +unhistorical +unhitch +unhitched +unhitches +unhitching +unholier +unholiest +unholiness +unholy +unhook +unhooked +unhooking +unhooks +unhorse +unhorsed +unhorses +unhorsing +unhurried +unhurriedly +unhurt +unicameral +unicellular +unicorn +unicorns +unicycle +unicycles +unidentifiable +unidentified +unidiomatic +unification +unified +unifies +uniform +uniformed +uniforming +uniformity +uniformly +uniforms +unify +unifying +unilateral +unilaterally +unimaginable +unimaginative +unimaginatively +unimpaired +unimpeachable +unimpeded +unimportant +unimposing +unimpressed +unimpressive +unimproved +unincorporated +uninfected +uninfluenced +uninformative +uninformed +uninhabitable +uninhabited +uninhibited +uninhibitedly +uninitiated +uninjured +uninspired +uninspiring +uninstructed +uninsured +unintelligent +unintelligible +unintelligibly +unintended +unintentional +unintentionally +uninterested +uninteresting +uninterrupted +uninvited +uninviting +union +unionism +unionist +unionists +unionization +unionize +unionized +unionizes +unionizing +unions +unique +uniquely +uniqueness +uniquer +uniquest +unisex +unison +unit +unitary +unite +united +unitedly +unites +unities +uniting +unitize +unitized +unitizes +unitizing +units +unity +univalent +univalve +univalves +universal +universality +universalize +universalized +universalizes +universalizing +universally +universals +universe +universes +universities +university +unjust +unjustifiable +unjustifiably +unjustified +unjustly +unkempt +unkind +unkinder +unkindest +unkindlier +unkindliest +unkindly +unkindness +unknowable +unknowing +unknowingly +unknown +unknowns +unlabeled +unlace +unlaced +unlaces +unlacing +unladen +unladylike +unlatch +unlatched +unlatches +unlatching +unlawful +unlawfully +unlawfulness +unleaded +unlearn +unlearned +unlearning +unlearns +unleash +unleashed +unleashes +unleashing +unleavened +unless +unlettered +unlicensed +unlighted +unlikable +unlike +unlikelier +unlikeliest +unlikelihood +unlikeliness +unlikely +unlikeness +unlimber +unlimbered +unlimbering +unlimbers +unlimited +unlined +unlisted +unlit +unlivable +unload +unloaded +unloading +unloads +unlock +unlocked +unlocking +unlocks +unloose +unloosed +unloosen +unloosened +unloosening +unloosens +unlooses +unloosing +unlovable +unloved +unlovelier +unloveliest +unlovely +unloving +unluckier +unluckiest +unluckily +unluckiness +unlucky +unmade +unmake +unmakes +unmaking +unman +unmanageable +unmanlier +unmanliest +unmanly +unmanned +unmannerly +unmanning +unmans +unmarked +unmarketable +unmarred +unmarried +unmask +unmasked +unmasking +unmasks +unmatched +unmeaning +unmeant +unmeasured +unmediated +unmentionable +unmentionables +unmentioned +unmerciful +unmercifully +unmerited +unmindful +unmistakable +unmistakably +unmitigated +unmixed +unmodified +unmolested +unmoral +unmorality +unmotivated +unmounted +unmovable +unmoved +unmusical +unnameable +unnamed +unnatural +unnaturally +unnaturalness +unnecessarily +unnecessary +unneeded +unnerve +unnerved +unnerves +unnerving +unnoticeable +unnoticed +unnumbered +unobjectionable +unobservant +unobserved +unobstructed +unobtainable +unobtrusive +unobtrusively +unobtrusiveness +unoccupied +unoffensive +unofficial +unofficially +unopened +unopposed +unorganized +unoriginal +unorthodox +unpack +unpacked +unpacking +unpacks +unpaid +unpainted +unpaired +unpalatable +unparalleled +unparallelled +unpardonable +unpardonably +unpasteurized +unpatriotic +unpaved +unpeeled +unperceived +unperceptive +unperformed +unperson +unpersons +unpersuaded +unpersuasive +unperturbed +unpin +unpinned +unpinning +unpins +unplanned +unpleasant +unpleasantly +unpleasantness +unpleasing +unplug +unplugged +unplugging +unplugs +unplumbed +unpolished +unpolitical +unpolluted +unpopular +unpopularity +unpractical +unpracticed +unprecedented +unprecedentedly +unpredictability +unpredictable +unpredictably +unprejudiced +unpremeditated +unprepared +unpreparedness +unprepossessing +unpressed +unpretentious +unpretentiously +unpreventable +unprincipled +unprintable +unprocessed +unproductive +unproductively +unprofessional +unprofessionally +unprofitable +unprofitably +unpromising +unprompted +unpronounceable +unpropitious +unprotected +unproved +unproven +unprovided +unprovoked +unpublished +unpunished +unqualified +unquenchable +unquestionable +unquestionably +unquestioned +unquestioning +unquestioningly +unquiet +unquieter +unquietest +unquote +unquoted +unquotes +unquoting +unrated +unravel +unraveled +unraveling +unravelled +unravelling +unravels +unread +unreadable +unready +unreal +unrealistic +unrealistically +unreality +unrealized +unreasonable +unreasonableness +unreasonably +unreasoning +unrecognizable +unrecognized +unreconstructed +unrecorded +unrecoverable +unreel +unreeled +unreeling +unreels +unrefined +unreformed +unregenerate +unregistered +unregulated +unrehearsed +unrelated +unrelenting +unrelentingly +unreliability +unreliable +unreliably +unrelieved +unremarkable +unremembered +unremitting +unremittingly +unrepentant +unreported +unrepresentative +unrepresented +unrequited +unreserved +unreservedly +unresistant +unresolved +unresponsive +unresponsively +unresponsiveness +unrest +unrestrained +unrestricted +unrewarded +unrewarding +unrighteous +unrighteousness +unripe +unripened +unriper +unripest +unrivaled +unrivalled +unroll +unrolled +unrolling +unrolls +unromantic +unruffled +unrulier +unruliest +unruliness +unruly +unsaddle +unsaddled +unsaddles +unsaddling +unsafe +unsafely +unsafer +unsafest +unsaid +unsalable +unsalted +unsanctioned +unsanitary +unsatisfactorily +unsatisfactory +unsatisfied +unsatisfying +unsaturated +unsaved +unsavory +unsavoury +unsay +unsaying +unsays +unscathed +unscented +unscheduled +unschooled +unscientific +unscientifically +unscramble +unscrambled +unscrambles +unscrambling +unscratched +unscrew +unscrewed +unscrewing +unscrews +unscripted +unscrupulous +unscrupulously +unscrupulousness +unseal +unsealed +unsealing +unseals +unsearchable +unseasonable +unseasonably +unseasoned +unseat +unseated +unseating +unseats +unseeing +unseeingly +unseemlier +unseemliest +unseemliness +unseemly +unseen +unsegmented +unsegregated +unselfish +unselfishly +unselfishness +unsentimental +unsettle +unsettled +unsettles +unsettling +unshackle +unshackled +unshackles +unshackling +unshakable +unshakably +unshakeable +unshaken +unshaped +unshapely +unshaven +unsheathe +unsheathed +unsheathes +unsheathing +unshod +unshorn +unsifted +unsightlier +unsightliest +unsightliness +unsightly +unsigned +unsinkable +unskilled +unskillful +unskillfully +unsmiling +unsnap +unsnapped +unsnapping +unsnaps +unsnarl +unsnarled +unsnarling +unsnarls +unsociable +unsocial +unsoiled +unsold +unsolicited +unsolvable +unsolved +unsophisticated +unsorted +unsought +unsound +unsounder +unsoundest +unsoundly +unsoundness +unsparing +unsparingly +unspeakable +unspeakably +unspecific +unspecified +unspectacular +unspent +unspoiled +unspoken +unsportsmanlike +unstable +unstabler +unstablest +unstably +unstained +unstated +unsteadier +unsteadiest +unsteadily +unsteadiness +unsteady +unstinting +unstintingly +unstop +unstoppable +unstopped +unstopping +unstops +unstrap +unstrapped +unstrapping +unstraps +unstressed +unstructured +unstrung +unstuck +unstudied +unsubstantial +unsubstantiated +unsuccessful +unsuccessfully +unsuitability +unsuitable +unsuitably +unsuited +unsullied +unsung +unsupervised +unsupported +unsure +unsurpassed +unsurprising +unsurprisingly +unsuspected +unsuspecting +unsuspectingly +unsustainable +unswayed +unsweetened +unswerving +unsymmetrical +unsympathetic +unsympathetically +unsystematic +untactful +untainted +untalented +untamed +untangle +untangled +untangles +untangling +untanned +untapped +untarnished +untasted +untaught +unteachable +untenable +untenanted +untended +untested +unthinkable +unthinkably +unthinking +unthinkingly +untidier +untidiest +untidily +untidiness +untidy +untie +untied +unties +until +untimelier +untimeliest +untimeliness +untimely +untiring +untiringly +untitled +unto +untold +untouchable +untouchables +untouched +untoward +untraceable +untrained +untrammeled +untrammelled +untranslatable +untranslated +untraveled +untreated +untried +untrimmed +untrod +untroubled +untrue +untruer +untruest +untruly +untrustworthy +untruth +untruthful +untruthfully +untruthfulness +untruths +untutored +untwist +untwisted +untwisting +untwists +untying +untypical +unusable +unused +unusual +unusually +unutterable +unutterably +unvaried +unvarnished +unvarying +unveil +unveiled +unveiling +unveils +unverifiable +unverified +unversed +unvoiced +unwanted +unwarier +unwariest +unwarily +unwariness +unwarrantable +unwarranted +unwary +unwashed +unwavering +unwearable +unwearied +unwed +unwelcome +unwell +unwholesome +unwholesomeness +unwieldier +unwieldiest +unwieldiness +unwieldy +unwilling +unwillingly +unwillingness +unwind +unwinding +unwinds +unwise +unwisely +unwiser +unwisest +unwitting +unwittingly +unwonted +unworkable +unworldlier +unworldliest +unworldliness +unworldly +unworn +unworried +unworthier +unworthiest +unworthily +unworthiness +unworthy +unwound +unwoven +unwrap +unwrapped +unwrapping +unwraps +unwrinkled +unwritten +unyielding +unyoke +unyoked +unyokes +unyoking +unzip +unzipped +unzipping +unzips +upbeat +upbeats +upbraid +upbraided +upbraiding +upbraids +upbringing +upbringings +upchuck +upchucked +upchucking +upchucks +upcoming +upcountry +update +updated +updates +updating +updraft +updrafts +upend +upended +upending +upends +upfront +upgrade +upgraded +upgrades +upgrading +upheaval +upheavals +upheld +uphill +uphills +uphold +upholder +upholders +upholding +upholds +upholster +upholstered +upholsterer +upholsterers +upholstering +upholsters +upholstery +upkeep +upland +uplands +uplift +uplifted +uplifting +uplifts +upload +uploaded +uploading +uploads +upmarket +upmost +upon +upped +upper +uppercase +upperclassman +upperclassmen +uppercut +uppercuts +uppercutting +uppermost +uppers +upping +uppish +uppity +upraise +upraised +upraises +upraising +uprear +upreared +uprearing +uprears +upright +uprightly +uprightness +uprights +uprising +uprisings +upriver +uproar +uproarious +uproariously +uproars +uproot +uprooted +uprooting +uproots +upscale +upset +upsets +upsetting +upshot +upshots +upside +upsides +upsilon +upsilons +upstage +upstaged +upstages +upstaging +upstairs +upstanding +upstart +upstarted +upstarting +upstarts +upstate +upstream +upstroke +upstrokes +upsurge +upsurged +upsurges +upsurging +upswing +upswings +uptake +uptakes +upthrust +upthrusting +upthrusts +uptick +upticks +uptight +uptown +upturn +upturned +upturning +upturns +upward +upwardly +upwards +upwind +uracil +uranium +urban +urbane +urbanely +urbaner +urbanest +urbanity +urbanization +urbanize +urbanized +urbanizes +urbanizing +urbanologist +urbanologists +urbanology +urchin +urchins +urea +uremia +uremic +ureter +ureters +urethane +urethra +urethrae +urethral +urethras +urge +urged +urgency +urgent +urgently +urges +urging +uric +urinal +urinals +urinalyses +urinalysis +urinary +urinate +urinated +urinates +urinating +urination +urine +urns +urogenital +urological +urologist +urologists +urology +ursine +urticaria +usability +usable +usage +usages +useability +useable +used +useful +usefully +usefulness +useless +uselessly +uselessness +user +users +uses +usher +ushered +usherette +usherettes +ushering +ushers +using +usual +usually +usurer +usurers +usurious +usurp +usurpation +usurped +usurper +usurpers +usurping +usurps +usury +utensil +utensils +uteri +uterine +uterus +uteruses +utilitarian +utilitarianism +utilitarians +utilities +utility +utilization +utilize +utilized +utilizes +utilizing +utmost +utopia +utopian +utopians +utopias +utter +utterance +utterances +uttered +uttering +utterly +uttermost +utters +uvula +uvulae +uvular +uvulars +uvulas +uxorious +vacancies +vacancy +vacant +vacantly +vacate +vacated +vacates +vacating +vacation +vacationed +vacationer +vacationers +vacationing +vacationist +vacationists +vacations +vaccinate +vaccinated +vaccinates +vaccinating +vaccination +vaccinations +vaccine +vaccines +vacillate +vacillated +vacillates +vacillating +vacillation +vacillations +vacua +vacuity +vacuole +vacuoles +vacuous +vacuously +vacuousness +vacuum +vacuumed +vacuuming +vacuums +vagabond +vagabondage +vagabonds +vagaries +vagarious +vagary +vagina +vaginae +vaginal +vaginally +vaginas +vagrancy +vagrant +vagrants +vague +vaguely +vagueness +vaguer +vaguest +vain +vainer +vainest +vainglorious +vaingloriously +vainglory +vainly +valance +valances +vale +valediction +valedictions +valedictorian +valedictorians +valedictories +valedictory +valence +valences +valencies +valency +valentine +valentines +vales +valet +valeted +valeting +valets +valetudinarian +valetudinarianism +valetudinarians +valiance +valiant +valiantly +valid +validate +validated +validates +validating +validation +validations +validity +validly +validness +valise +valises +valley +valleys +valor +valorous +valorously +valour +valuable +valuables +valuate +valuated +valuates +valuating +valuation +valuations +value +valued +valueless +valuer +valuers +values +valuing +valve +valved +valveless +valves +valving +valvular +vamoose +vamoosed +vamooses +vamoosing +vamp +vamped +vamping +vampire +vampires +vamps +vanadium +vandal +vandalise +vandalised +vandalises +vandalising +vandalism +vandalize +vandalized +vandalizes +vandalizing +vandals +vane +vanes +vanguard +vanguards +vanilla +vanillas +vanish +vanished +vanishes +vanishing +vanities +vanity +vanned +vanning +vanquish +vanquished +vanquisher +vanquishers +vanquishes +vanquishing +vans +vantage +vantages +vapid +vapidity +vapidly +vapidness +vapor +vaporise +vaporised +vaporises +vaporising +vaporization +vaporize +vaporized +vaporizer +vaporizers +vaporizes +vaporizing +vaporous +vapors +vapory +vapour +vapours +vaquero +vaqueros +variability +variable +variables +variably +variance +variances +variant +variants +variation +variations +varicolored +varicose +varied +variegate +variegated +variegates +variegating +variegation +varies +varietal +varietals +varieties +variety +various +variously +varlet +varlets +varmint +varmints +varnish +varnished +varnishes +varnishing +varsities +varsity +vary +varying +vascular +vase +vasectomies +vasectomy +vases +vasomotor +vassal +vassalage +vassals +vast +vaster +vastest +vastly +vastness +vasts +vats +vatted +vatting +vaudeville +vaudevillian +vaudevillians +vault +vaulted +vaulter +vaulters +vaulting +vaults +vaunt +vaunted +vaunting +vaunts +veal +vector +vectored +vectoring +vectors +veejay +veejays +veep +veeps +veer +veered +veering +veers +vegan +vegans +veges +vegetable +vegetables +vegetarian +vegetarianism +vegetarians +vegetate +vegetated +vegetates +vegetating +vegetation +vegetative +vegged +vegges +veggie +veggies +vegging +vegs +vehemence +vehemency +vehement +vehemently +vehicle +vehicles +vehicular +veil +veiled +veiling +veils +vein +veined +veining +veins +vela +velar +velars +veld +velds +veldt +veldts +vellum +velocipede +velocipedes +velocities +velocity +velour +velours +velum +velvet +velveteen +velvety +venal +venality +venally +venation +vend +vended +vender +venders +vendetta +vendettas +vendible +vending +vendor +vendors +vends +veneer +veneered +veneering +veneers +venerability +venerable +venerate +venerated +venerates +venerating +veneration +venereal +vengeance +vengeful +vengefully +venial +venireman +veniremen +venison +venom +venomous +venomously +venous +vent +vented +ventilate +ventilated +ventilates +ventilating +ventilation +ventilator +ventilators +venting +ventral +ventricle +ventricles +ventricular +ventriloquism +ventriloquist +ventriloquists +ventriloquy +vents +venture +ventured +ventures +venturesome +venturesomely +venturesomeness +venturing +venturous +venturously +venturousness +venue +venues +veracious +veraciously +veracity +veranda +verandah +verandahs +verandas +verb +verbal +verbalization +verbalize +verbalized +verbalizes +verbalizing +verbally +verbals +verbatim +verbena +verbenas +verbiage +verbose +verbosely +verbosity +verboten +verbs +verdant +verdantly +verdict +verdicts +verdigris +verdure +verge +verged +verger +vergers +verges +verging +verier +veriest +verifiable +verification +verified +verifies +verify +verifying +verily +verisimilitude +veritable +veritably +verities +verity +vermicelli +vermiculite +vermiform +vermilion +vermillion +vermin +verminous +vermouth +vernacular +vernaculars +vernal +vernier +verniers +veronica +verruca +verrucae +verrucas +versatile +versatility +verse +versed +verses +versification +versified +versifier +versifiers +versifies +versify +versifying +versing +version +versions +verso +versos +versus +vertebra +vertebrae +vertebral +vertebras +vertebrate +vertebrates +vertex +vertexes +vertical +vertically +verticals +vertices +vertiginous +vertigo +verve +very +vesicle +vesicles +vesicular +vesiculate +vesper +vespers +vessel +vessels +vest +vestal +vestals +vested +vestibule +vestibules +vestige +vestiges +vestigial +vestigially +vesting +vestment +vestments +vestries +vestry +vestryman +vestrymen +vests +vetch +vetches +veteran +veterans +veterinarian +veterinarians +veterinaries +veterinary +veto +vetoed +vetoes +vetoing +vets +vetted +vetting +vexation +vexations +vexatious +vexatiously +vexed +vexes +vexing +viability +viable +viably +viaduct +viaducts +vial +vials +viand +viands +vibe +vibes +vibraharp +vibraharps +vibrancy +vibrant +vibrantly +vibraphone +vibraphones +vibraphonist +vibraphonists +vibrate +vibrated +vibrates +vibrating +vibration +vibrations +vibrato +vibrator +vibrators +vibratory +vibratos +viburnum +viburnums +vicar +vicarage +vicarages +vicarious +vicariously +vicariousness +vicars +vice +viced +vicegerent +vicegerents +vicennial +viceregal +viceroy +viceroys +vices +vichyssoise +vicing +vicinity +vicious +viciously +viciousness +vicissitude +vicissitudes +victim +victimization +victimize +victimized +victimizes +victimizing +victims +victor +victories +victorious +victoriously +victors +victory +victual +victualed +victualing +victualled +victualling +victuals +vicuna +vicunas +videlicet +video +videocassette +videocassettes +videodisc +videodiscs +videodisk +videodisks +videoed +videoing +videophone +videophones +videos +videotape +videotaped +videotapes +videotaping +vied +vies +view +viewed +viewer +viewers +viewership +viewfinder +viewfinders +viewing +viewings +viewpoint +viewpoints +views +vigesimal +vigil +vigilance +vigilant +vigilante +vigilantes +vigilantism +vigilantist +vigilantists +vigilantly +vigils +vignette +vignetted +vignettes +vignetting +vignettist +vignettists +vigor +vigorous +vigorously +vigour +viking +vikings +vile +vilely +vileness +viler +vilest +vilification +vilified +vilifies +vilify +vilifying +villa +village +villager +villagers +villages +villain +villainies +villainous +villains +villainy +villas +villein +villeinage +villeins +villi +villus +vinaigrette +vincible +vindicate +vindicated +vindicates +vindicating +vindication +vindications +vindicator +vindicators +vindictive +vindictively +vindictiveness +vine +vinegar +vinegary +vines +vineyard +vineyards +vino +vinous +vintage +vintages +vintner +vintners +vinyl +vinyls +viol +viola +violable +violas +violate +violated +violates +violating +violation +violations +violator +violators +violence +violent +violently +violet +violets +violin +violinist +violinists +violins +violist +violists +violoncellist +violoncellists +violoncello +violoncellos +viols +viper +viperous +vipers +virago +viragoes +viragos +viral +vireo +vireos +virgin +virginal +virginals +virginity +virgins +virgule +virgules +virile +virility +virologist +virologists +virology +virtual +virtually +virtue +virtues +virtuosi +virtuosity +virtuoso +virtuosos +virtuous +virtuously +virtuousness +virulence +virulent +virulently +virus +viruses +visa +visaed +visage +visages +visaing +visas +viscera +visceral +viscerally +viscid +viscose +viscosity +viscount +viscountcies +viscountcy +viscountess +viscountesses +viscounts +viscous +viscus +vise +vised +vises +visibility +visible +visibly +vising +vision +visionaries +visionary +visioned +visioning +visions +visit +visitant +visitants +visitation +visitations +visited +visiting +visitor +visitors +visits +visor +visors +vista +vistas +visual +visualization +visualize +visualized +visualizer +visualizers +visualizes +visualizing +visually +visuals +vita +vitae +vital +vitality +vitalization +vitalize +vitalized +vitalizes +vitalizing +vitally +vitals +vitamin +vitamins +vitas +vitiate +vitiated +vitiates +vitiating +vitiation +viticulture +viticulturist +viticulturists +vitreous +vitrifaction +vitrification +vitrified +vitrifies +vitrify +vitrifying +vitrine +vitrines +vitriol +vitriolic +vittles +vituperate +vituperated +vituperates +vituperating +vituperation +vituperative +viva +vivace +vivacious +vivaciously +vivaciousness +vivacity +vivaria +vivarium +vivariums +vivas +vivid +vivider +vividest +vividly +vividness +vivified +vivifies +vivify +vivifying +viviparous +vivisect +vivisected +vivisecting +vivisection +vivisectional +vivisectionist +vivisectionists +vivisects +vixen +vixenish +vixenishly +vixens +vizier +viziers +vizir +vizirs +vizor +vizors +vocable +vocables +vocabularies +vocabulary +vocal +vocalic +vocalist +vocalists +vocalization +vocalizations +vocalize +vocalized +vocalizes +vocalizing +vocally +vocals +vocation +vocational +vocations +vocative +vocatives +vociferate +vociferated +vociferates +vociferating +vociferation +vociferous +vociferously +vociferousness +vodka +vogue +vogues +voguish +voice +voiced +voiceless +voicelessly +voicelessness +voices +voicing +void +voidable +voided +voiding +voids +voila +voile +volatile +volatility +volatilize +volatilized +volatilizes +volatilizing +volcanic +volcano +volcanoes +volcanos +vole +voles +volition +volitional +volley +volleyball +volleyballs +volleyed +volleying +volleys +volt +voltage +voltages +voltaic +voltmeter +voltmeters +volts +volubility +voluble +volubly +volume +volumes +voluminous +voluminously +voluminousness +voluntaries +voluntarily +voluntarism +voluntary +volunteer +volunteered +volunteering +volunteerism +volunteers +voluptuaries +voluptuary +voluptuous +voluptuously +voluptuousness +volute +volutes +vomit +vomited +vomiting +vomits +voodoo +voodooed +voodooing +voodooism +voodoos +voracious +voraciously +voraciousness +voracity +vortex +vortexes +vortices +votaries +votary +vote +voted +voter +voters +votes +voting +votive +vouch +vouched +voucher +vouchers +vouches +vouching +vouchsafe +vouchsafed +vouchsafes +vouchsafing +vowed +vowel +vowels +vowing +vows +voyage +voyaged +voyager +voyagers +voyages +voyageur +voyageurs +voyaging +voyeur +voyeurism +voyeuristic +voyeurs +vulcanization +vulcanize +vulcanized +vulcanizes +vulcanizing +vulgar +vulgarer +vulgarest +vulgarian +vulgarians +vulgarism +vulgarisms +vulgarities +vulgarity +vulgarization +vulgarize +vulgarized +vulgarizer +vulgarizers +vulgarizes +vulgarizing +vulgarly +vulnerabilities +vulnerability +vulnerable +vulnerably +vulpine +vulture +vultures +vulturous +vulva +vulvae +vulvas +vying +wackier +wackiest +wackiness +wacko +wackos +wacky +wadded +wadding +waddle +waddled +waddles +waddling +wade +waded +wader +waders +wades +wadi +wading +wadis +wads +wafer +wafers +waffle +waffled +waffler +wafflers +waffles +waffling +waft +wafted +wafting +wafts +wage +waged +wager +wagered +wagerer +wagerers +wagering +wagers +wages +wagged +waggeries +waggery +wagging +waggish +waggishly +waggishness +waggle +waggled +waggles +waggling +waggon +waggons +waging +wagon +wagoner +wagoners +wagons +wags +wagtail +wagtails +waif +waifs +wail +wailed +wailer +wailers +wailing +wails +wain +wains +wainscot +wainscoted +wainscoting +wainscotings +wainscots +wainscotted +wainscotting +wainscottings +wainwright +wainwrights +waist +waistband +waistbands +waistcoat +waistcoats +waistline +waistlines +waists +wait +waited +waiter +waiters +waiting +waitperson +waitpersons +waitress +waitresses +waits +waitstaff +waive +waived +waiver +waivers +waives +waiving +wake +waked +wakeful +wakefully +wakefulness +waken +wakened +wakening +wakens +wakes +waking +wale +waled +wales +waling +walk +walkaway +walkaways +walked +walker +walkers +walking +walkout +walkouts +walkover +walkovers +walks +walkway +walkways +wall +wallabies +wallaby +wallboard +walled +wallet +wallets +walleye +walleyed +walleyes +wallflower +wallflowers +walling +wallop +walloped +walloping +wallopings +wallops +wallow +wallowed +wallowing +wallows +wallpaper +wallpapered +wallpapering +wallpapers +walls +walnut +walnuts +walrus +walruses +waltz +waltzed +waltzer +waltzers +waltzes +waltzing +wampum +wand +wander +wandered +wanderer +wanderers +wandering +wanderings +wanderlust +wanderlusts +wanders +wands +wane +waned +wanes +wangle +wangled +wangler +wanglers +wangles +wangling +waning +wanly +wanna +wannabe +wannabes +wanner +wanness +wannest +want +wanted +wanting +wanton +wantoned +wantoning +wantonly +wantonness +wantons +wants +wapiti +wapitis +warble +warbled +warbler +warblers +warbles +warbling +warbonnet +warbonnets +ward +warded +warden +wardens +warder +warders +warding +wardrobe +wardrobes +wardroom +wardrooms +wards +ware +warehouse +warehoused +warehouses +warehousing +wares +warfare +warhead +warheads +warhorse +warhorses +warier +wariest +warily +wariness +warlike +warlock +warlocks +warlord +warlords +warm +warmblooded +warmed +warmer +warmers +warmest +warmhearted +warmheartedness +warming +warmish +warmly +warmness +warmonger +warmongering +warmongers +warms +warmth +warmup +warmups +warn +warned +warning +warnings +warns +warp +warpath +warpaths +warped +warping +warplane +warplanes +warps +warrant +warranted +warrantied +warranties +warranting +warrants +warranty +warrantying +warred +warren +warrens +warring +warrior +warriors +wars +warship +warships +wart +warthog +warthogs +wartier +wartiest +wartime +warts +warty +wary +wash +washable +washables +washbasin +washbasins +washboard +washboards +washbowl +washbowls +washcloth +washcloths +washed +washer +washers +washerwoman +washerwomen +washes +washier +washiest +washing +washings +washout +washouts +washrag +washrags +washroom +washrooms +washstand +washstands +washtub +washtubs +washy +wasp +waspish +waspishly +waspishness +wasps +wassail +wassailed +wassailing +wassails +wast +wastage +waste +wastebasket +wastebaskets +wasted +wasteful +wastefully +wastefulness +wasteland +wastelands +wastepaper +waster +wasters +wastes +wasting +wastrel +wastrels +watch +watchband +watchbands +watchdog +watchdogs +watched +watcher +watchers +watches +watchful +watchfully +watchfulness +watching +watchmaker +watchmakers +watchmaking +watchman +watchmen +watchtower +watchtowers +watchword +watchwords +water +waterbed +waterbeds +waterbird +waterbirds +waterborne +watercolor +watercolors +watercourse +watercourses +watercraft +watercress +watered +waterfall +waterfalls +waterfowl +waterfowls +waterfront +waterfronts +waterhole +waterholes +waterier +wateriest +wateriness +watering +waterlilies +waterlily +waterline +waterlines +waterlogged +watermark +watermarked +watermarking +watermarks +watermelon +watermelons +watermill +watermills +waterpower +waterproof +waterproofed +waterproofing +waterproofs +waters +watershed +watersheds +waterside +watersides +waterspout +waterspouts +watertight +waterway +waterways +waterwheel +waterwheels +waterworks +watery +watt +wattage +wattle +wattled +wattles +wattling +watts +wave +waved +wavelength +wavelengths +wavelet +wavelets +wavelike +waver +wavered +waverer +waverers +wavering +waveringly +wavers +waves +wavier +waviest +waviness +waving +wavy +waxed +waxen +waxes +waxier +waxiest +waxiness +waxing +waxwing +waxwings +waxwork +waxworks +waxy +waybill +waybills +wayfarer +wayfarers +wayfaring +wayfarings +waylaid +waylay +waylayer +waylayers +waylaying +waylays +ways +wayside +waysides +wayward +waywardly +waywardness +weak +weaken +weakened +weakener +weakeners +weakening +weakens +weaker +weakest +weakfish +weakfishes +weakling +weaklings +weakly +weakness +weaknesses +weal +weals +wealth +wealthier +wealthiest +wealthiness +wealthy +wean +weaned +weaning +weans +weapon +weaponless +weaponry +weapons +wear +wearable +wearer +wearers +wearied +wearier +wearies +weariest +wearily +weariness +wearing +wearisome +wearisomely +wears +weary +wearying +weasel +weaseled +weaseling +weaselled +weaselling +weaselly +weasels +weather +weathercock +weathercocks +weathered +weathering +weatherization +weatherize +weatherized +weatherizes +weatherizing +weatherman +weathermen +weatherperson +weatherpersons +weatherproof +weatherproofed +weatherproofing +weatherproofs +weathers +weatherstrip +weatherstripped +weatherstripping +weatherstrips +weave +weaved +weaver +weavers +weaves +weaving +webbed +webbing +webfeet +webfoot +webs +website +websites +wedded +wedding +weddings +wedge +wedged +wedges +wedgie +wedgies +wedging +wedlock +weds +weed +weeded +weeder +weeders +weedier +weediest +weeding +weedless +weeds +weedy +weeing +week +weekday +weekdays +weekend +weekended +weekending +weekends +weeklies +weekly +weeknight +weeknights +weeks +ween +weened +weenie +weenier +weenies +weeniest +weening +weens +weensier +weensiest +weensy +weeny +weep +weeper +weepers +weepier +weepies +weepiest +weeping +weeps +weepy +weer +wees +weest +weevil +weevils +weft +wefts +weigh +weighed +weighing +weighs +weight +weighted +weightier +weightiest +weightily +weightiness +weighting +weightless +weightlessly +weightlessness +weightlifter +weightlifters +weightlifting +weights +weighty +weir +weird +weirder +weirdest +weirdie +weirdies +weirdly +weirdness +weirdo +weirdos +weirs +welch +welched +welches +welching +welcome +welcomed +welcomes +welcoming +weld +weldable +welded +welder +welders +welding +welds +welfare +welkin +well +welled +wellhead +wellheads +welling +wellington +wellingtons +wellness +wells +wellspring +wellsprings +welsh +welshed +welsher +welshers +welshes +welshing +welt +welted +welter +weltered +weltering +welters +welterweight +welterweights +welting +welts +wench +wenches +wend +wended +wending +wends +wens +went +wept +were +werewolf +werewolves +werwolf +werwolves +west +westbound +westerlies +westerly +western +westerner +westerners +westernization +westernize +westernized +westernizes +westernizing +westernmost +westerns +westward +westwards +wetback +wetbacks +wetland +wetlands +wetly +wetness +wets +wetted +wetter +wetters +wettest +wetting +whack +whacked +whacker +whackers +whackier +whackiest +whacking +whacks +whacky +whale +whaleboat +whaleboats +whalebone +whaled +whaler +whalers +whales +whaling +wham +whammed +whammies +whamming +whammy +whams +wharf +wharfs +wharves +what +whatchamacallit +whatchamacallits +whatever +whatnot +whats +whatsoever +wheal +wheals +wheat +wheaten +whee +wheedle +wheedled +wheedler +wheedlers +wheedles +wheedling +wheel +wheelbarrow +wheelbarrows +wheelbase +wheelbases +wheelchair +wheelchairs +wheeled +wheelhouse +wheelhouses +wheelie +wheelies +wheeling +wheels +wheelwright +wheelwrights +wheeze +wheezed +wheezes +wheezier +wheeziest +wheezily +wheeziness +wheezing +wheezy +whelk +whelks +whelm +whelmed +whelming +whelms +whelp +whelped +whelping +whelps +when +whence +whenever +whens +whensoever +where +whereabouts +whereas +whereat +whereby +wherefore +wherefores +wherein +whereof +whereon +wheres +wheresoever +whereto +whereupon +wherever +wherewith +wherewithal +wherries +wherry +whet +whether +whets +whetstone +whetstones +whetted +whetting +whew +whey +which +whichever +whiff +whiffed +whiffing +whiffletree +whiffletrees +whiffs +while +whiled +whiles +whiling +whilom +whilst +whim +whimper +whimpered +whimpering +whimpers +whims +whimsey +whimseys +whimsical +whimsicality +whimsically +whimsies +whimsy +whine +whined +whiner +whiners +whines +whinier +whiniest +whining +whinnied +whinnies +whinny +whinnying +whiny +whip +whipcord +whiplash +whiplashes +whipped +whipper +whippers +whippersnapper +whippersnappers +whippet +whippets +whipping +whippings +whippletree +whippletrees +whippoorwill +whippoorwills +whips +whipsaw +whipsawed +whipsawing +whipsawn +whipsaws +whir +whirl +whirled +whirligig +whirligigs +whirling +whirlpool +whirlpools +whirls +whirlwind +whirlwinds +whirlybird +whirlybirds +whirr +whirred +whirring +whirrs +whirs +whisk +whisked +whisker +whiskered +whiskers +whiskery +whiskey +whiskeys +whiskies +whisking +whisks +whisky +whisper +whispered +whisperer +whisperers +whispering +whispers +whist +whistle +whistled +whistler +whistlers +whistles +whistling +whit +white +whitecap +whitecaps +whitefish +whitefishes +whitehead +whiteheads +whiten +whitened +whitener +whiteners +whiteness +whitening +whitenings +whitens +whiteout +whiteouts +whiter +whites +whitest +whitetail +whitetails +whitewall +whitewalls +whitewash +whitewashed +whitewashes +whitewashing +whitewater +whitey +whiteys +whither +whiting +whitings +whitish +whits +whittle +whittled +whittler +whittlers +whittles +whittling +whiz +whizkid +whizkids +whizz +whizzbang +whizzbangs +whizzed +whizzes +whizzing +whoa +whodunit +whodunits +whodunnit +whodunnits +whoever +whole +wholehearted +wholeheartedly +wholeheartedness +wholeness +wholes +wholesale +wholesaled +wholesaler +wholesalers +wholesales +wholesaling +wholesome +wholesomely +wholesomeness +wholesomer +wholesomest +wholly +whom +whomever +whomsoever +whoop +whooped +whoopee +whooper +whoopers +whooping +whoops +whoosh +whooshed +whooshes +whooshing +whopper +whoppers +whopping +whore +whorehouse +whorehouses +whoreish +whores +whorish +whorl +whorled +whorls +whose +whoso +whosoever +whys +wick +wicked +wickeder +wickedest +wickedly +wickedness +wicker +wickers +wickerwork +wicket +wickets +wicking +wicks +wide +widely +widemouthed +widen +widened +widener +wideners +wideness +widening +widens +wider +widespread +widest +widgeon +widgeons +widow +widowed +widower +widowers +widowhood +widowing +widows +width +widths +wield +wielded +wielder +wielders +wielding +wields +wiener +wieners +wienie +wienies +wife +wifeless +wifely +wigeon +wigeons +wigged +wigging +wiggle +wiggled +wiggler +wigglers +wiggles +wigglier +wiggliest +wiggling +wiggly +wight +wights +wiglet +wiglets +wigs +wigwag +wigwagged +wigwagging +wigwags +wigwam +wigwams +wild +wildcat +wildcats +wildcatted +wildcatter +wildcatters +wildcatting +wildebeest +wildebeests +wilder +wilderness +wildernesses +wildest +wildfire +wildfires +wildflower +wildflowers +wildfowl +wildfowls +wildlife +wildly +wildness +wilds +wile +wiled +wiles +wilful +wilfully +wilfulness +wilier +wiliest +wiliness +wiling +will +willed +willful +willfully +willfulness +willies +willing +willingly +willingness +williwaw +williwaws +willow +willowier +willowiest +willows +willowy +willpower +wills +wilt +wilted +wilting +wilts +wily +wimp +wimpier +wimpiest +wimpish +wimple +wimpled +wimples +wimpling +wimps +wimpy +wince +winced +winces +winch +winched +winches +winching +wincing +wind +windbag +windbags +windblown +windbreak +windbreaker +windbreakers +windbreaks +windburn +windburned +windchill +winded +winder +winders +windfall +windfalls +windflower +windflowers +windier +windiest +windily +windiness +winding +windjammer +windjammers +windlass +windlasses +windless +windmill +windmilled +windmilling +windmills +window +windowless +windowpane +windowpanes +windows +windowsill +windowsills +windpipe +windpipes +windproof +windrow +windrows +winds +windshield +windshields +windsock +windsocks +windstorm +windstorms +windsurf +windsurfed +windsurfer +windsurfers +windsurfing +windsurfs +windswept +windup +windups +windward +windy +wine +wined +wineglass +wineglasses +winegrower +winegrowers +winemaker +winemakers +wineries +winery +wines +wing +wingding +wingdings +winged +winging +wingless +winglike +wings +wingspan +wingspans +wingspread +wingspreads +wingtip +wingtips +winier +winiest +wining +wink +winked +winker +winkers +winking +winkle +winkled +winkles +winkling +winks +winnable +winner +winners +winning +winningly +winnings +winnow +winnowed +winnower +winnowers +winnowing +winnows +wino +winos +wins +winsome +winsomely +winsomeness +winsomer +winsomest +winter +wintered +wintergreen +winterier +winteriest +wintering +winterize +winterized +winterizes +winterizing +winters +wintertime +wintery +wintrier +wintriest +wintry +winy +wipe +wiped +wiper +wipers +wipes +wiping +wire +wired +wirehair +wirehairs +wireless +wirelesses +wires +wiretap +wiretapped +wiretapper +wiretappers +wiretapping +wiretaps +wirier +wiriest +wiriness +wiring +wiry +wisdom +wise +wiseacre +wiseacres +wisecrack +wisecracked +wisecracking +wisecracks +wisely +wiser +wises +wisest +wish +wishbone +wishbones +wished +wisher +wishers +wishes +wishful +wishfully +wishing +wisp +wispier +wispiest +wisps +wispy +wist +wistaria +wistarias +wisteria +wisterias +wistful +wistfully +wistfulness +witch +witchcraft +witched +witchery +witches +witching +with +withal +withdraw +withdrawal +withdrawals +withdrawing +withdrawn +withdraws +withdrew +withe +withed +wither +withered +withering +witheringly +withers +withes +withheld +withhold +withholding +withholds +within +withing +without +withstand +withstanding +withstands +withstood +witless +witlessly +witlessness +witness +witnessed +witnesses +witnessing +wits +witted +witticism +witticisms +wittier +wittiest +wittily +wittiness +witting +wittingly +witty +wive +wived +wives +wiving +wizard +wizardry +wizards +wizened +wizes +wizzes +woad +wobble +wobbled +wobbles +wobblier +wobbliest +wobbliness +wobbling +wobbly +woebegone +woeful +woefuller +woefullest +woefully +woefulness +woes +woke +woken +woks +wold +wolds +wolf +wolfed +wolfhound +wolfhounds +wolfing +wolfish +wolfram +wolfs +wolverine +wolverines +wolves +woman +womanhood +womanish +womanize +womanized +womanizer +womanizers +womanizes +womanizing +womankind +womanlier +womanliest +womanlike +womanliness +womanly +womb +wombat +wombats +wombs +women +womenfolk +womenfolks +wonder +wondered +wonderful +wonderfully +wonderfulness +wondering +wonderingly +wonderland +wonderlands +wonderment +wonders +wondrous +wondrously +wonk +wonkier +wonkiest +wonks +wonky +wont +wonted +wonton +wontons +wood +woodbine +woodblock +woodblocks +woodcarver +woodcarvers +woodcarving +woodcarvings +woodchuck +woodchucks +woodcock +woodcocks +woodcraft +woodcut +woodcuts +woodcutter +woodcutters +woodcutting +wooded +wooden +woodener +woodenest +woodenly +woodenness +woodier +woodies +woodiest +woodiness +wooding +woodland +woodlands +woodlot +woodlots +woodman +woodmen +woodpecker +woodpeckers +woodpile +woodpiles +woods +woodshed +woodsheds +woodsier +woodsiest +woodsiness +woodsman +woodsmen +woodsy +woodwind +woodwinds +woodwork +woodworker +woodworkers +woodworking +woody +wooed +wooer +wooers +woof +woofed +woofer +woofers +woofing +woofs +wooing +wool +woolen +woolens +woolgathering +woolie +woolier +woolies +wooliest +woollen +woollens +woollier +woollies +woolliest +woolliness +woolly +wooly +woos +woozier +wooziest +woozily +wooziness +woozy +word +wordage +wordbook +wordbooks +worded +wordier +wordiest +wordily +wordiness +wording +wordings +wordless +wordlessly +wordplay +words +wordy +wore +work +workable +workaday +workaholic +workaholics +workbench +workbenches +workbook +workbooks +workday +workdays +worked +worker +workers +workfare +workforce +workhorse +workhorses +workhouse +workhouses +working +workingman +workingmen +workings +workingwoman +workingwomen +workload +workloads +workman +workmanlike +workmanship +workmen +workout +workouts +workplace +workplaces +workroom +workrooms +works +worksheet +worksheets +workshop +workshops +workstation +workstations +worktable +worktables +workup +workups +workweek +workweeks +world +worldlier +worldliest +worldliness +worldly +worlds +worldview +worldviews +worldwide +worm +wormed +wormhole +wormholes +wormier +wormiest +worming +worms +wormwood +wormy +worn +worried +worrier +worriers +worries +worriment +worrisome +worry +worrying +worryingly +worrywart +worrywarts +worse +worsen +worsened +worsening +worsens +worship +worshiped +worshiper +worshipers +worshipful +worshiping +worshipped +worshipper +worshippers +worshipping +worships +worst +worsted +worsting +worsts +wort +worth +worthier +worthies +worthiest +worthily +worthiness +worthless +worthlessly +worthlessness +worthwhile +worthy +would +wouldst +wound +wounded +wounding +wounds +wove +woven +wowed +wowing +wows +wrack +wraith +wraiths +wrangle +wrangled +wrangler +wranglers +wrangles +wrangling +wrap +wraparound +wraparounds +wrapped +wrapper +wrappers +wrapping +wrappings +wraps +wrapt +wrasse +wrasses +wrath +wrathful +wrathfully +wreak +wreaked +wreaking +wreaks +wreath +wreathe +wreathed +wreathes +wreathing +wreaths +wreck +wreckage +wrecked +wrecker +wreckers +wrecking +wrecks +wren +wrench +wrenched +wrenches +wrenching +wrens +wrest +wrested +wresting +wrestle +wrestled +wrestler +wrestlers +wrestles +wrestling +wrests +wretch +wretched +wretcheder +wretchedest +wretchedly +wretchedness +wretches +wrier +wriest +wriggle +wriggled +wriggler +wrigglers +wriggles +wrigglier +wriggliest +wriggling +wriggly +wright +wrights +wring +wringer +wringers +wringing +wrings +wrinkle +wrinkled +wrinkles +wrinklier +wrinklies +wrinkliest +wrinkling +wrinkly +wrist +wristband +wristbands +wrists +wristwatch +wristwatches +writ +write +writer +writers +writes +writhe +writhed +writhes +writhing +writing +writings +writs +written +wrong +wrongdoer +wrongdoers +wrongdoing +wrongdoings +wronged +wronger +wrongest +wrongful +wrongfully +wrongfulness +wrongheaded +wrongheadedly +wrongheadedness +wronging +wrongly +wrongness +wrongs +wrote +wroth +wrought +wrung +wryer +wryest +wryly +wryness +wurst +wursts +wuss +wusses +wussier +wussies +wussiest +wussy +xenon +xenophobe +xenophobes +xenophobia +xenophobic +xerographic +xerography +xerox +xeroxed +xeroxes +xeroxing +xylem +xylophone +xylophones +xylophonist +xylophonists +yacht +yachted +yachting +yachts +yachtsman +yachtsmen +yachtswoman +yachtswomen +yack +yacked +yacking +yacks +yahoo +yahoos +yakked +yakking +yaks +yammer +yammered +yammerer +yammerers +yammering +yammers +yams +yang +yank +yanked +yanking +yanks +yapped +yapping +yaps +yard +yardage +yardages +yardarm +yardarms +yardman +yardmaster +yardmasters +yardmen +yards +yardstick +yardsticks +yarmelke +yarmelkes +yarmulke +yarmulkes +yarn +yarns +yarrow +yawed +yawing +yawl +yawls +yawn +yawned +yawner +yawners +yawning +yawns +yaws +yeah +yeahs +year +yearbook +yearbooks +yearlies +yearling +yearlings +yearlong +yearly +yearn +yearned +yearning +yearnings +yearns +years +yeas +yeast +yeastier +yeastiest +yeasts +yeasty +yegg +yeggs +yell +yelled +yelling +yellow +yellowed +yellower +yellowest +yellowing +yellowish +yellowness +yellows +yellowy +yells +yelp +yelped +yelping +yelps +yens +yeoman +yeomanry +yeomen +yeps +yeses +yeshiva +yeshivah +yeshivahs +yeshivas +yeshivoth +yessed +yessing +yesterday +yesterdays +yesteryear +yeti +yetis +yews +yield +yielded +yielding +yields +yikes +yipe +yipped +yippee +yipping +yips +yodel +yodeled +yodeler +yodelers +yodeling +yodelled +yodeller +yodellers +yodelling +yodels +yoga +yoghourt +yoghourts +yoghurt +yoghurts +yogi +yogin +yogins +yogis +yogurt +yogurts +yoke +yoked +yokel +yokels +yokes +yoking +yolk +yolked +yolks +yonder +yore +young +younger +youngest +youngish +youngster +youngsters +your +yours +yourself +yourselves +yous +youth +youthful +youthfully +youthfulness +youths +yowl +yowled +yowling +yowls +ytterbium +yttrium +yuan +yucca +yuccas +yuck +yucked +yuckier +yuckiest +yucking +yucks +yucky +yukked +yukking +yuks +yule +yuletide +yummier +yummiest +yummy +yuppie +yuppies +yuppy +yups +yurt +yurts +zanier +zanies +zaniest +zaniness +zany +zapped +zapper +zappers +zapping +zaps +zeal +zealot +zealotry +zealots +zealous +zealously +zealousness +zebra +zebras +zebu +zebus +zeds +zeitgeist +zeitgeists +zenith +zeniths +zephyr +zephyrs +zeppelin +zeppelins +zero +zeroed +zeroes +zeroing +zeros +zest +zestful +zestfully +zestfulness +zestier +zestiest +zests +zesty +zeta +zetas +zigzag +zigzagged +zigzagging +zigzags +zilch +zillion +zillions +zinc +zinced +zincing +zincked +zincking +zincs +zinfandel +zing +zinged +zinger +zingers +zingier +zingiest +zinging +zings +zingy +zinnia +zinnias +zipped +zipper +zippered +zippering +zippers +zippier +zippiest +zipping +zippy +zips +zircon +zirconium +zircons +zither +zithers +zits +zloty +zlotys +zodiac +zodiacal +zodiacs +zombi +zombie +zombies +zombis +zonal +zonally +zone +zoned +zones +zoning +zonked +zookeeper +zookeepers +zoological +zoologically +zoologist +zoologists +zoology +zoom +zoomed +zooming +zooms +zoophyte +zoophytes +zoophytic +zoos +zounds +zucchini +zucchinis +zwieback +zydeco +zygote +zygotes +zygotic +zymurgy diff --git a/apps/bee/metadata.json b/apps/bee/metadata.json new file mode 100644 index 000000000..cfc91155a --- /dev/null +++ b/apps/bee/metadata.json @@ -0,0 +1,15 @@ +{ "id": "bee", + "name": "Bee", + "shortName":"Bee", + "icon": "app.png", + "version":"0.03", + "description": "Spelling bee", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "tags": "game,text", + "storage": [ + {"name":"bee.app.js","url":"bee.app.js"}, + {"name":"bee.words","url":"bee_words_2of12"}, + {"name":"bee.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/beer/ChangeLog b/apps/beer/ChangeLog new file mode 100644 index 000000000..21ec45242 --- /dev/null +++ b/apps/beer/ChangeLog @@ -0,0 +1,3 @@ +0.01: New App! +0.02: Added adjustment for Bangle.js magnetometer heading fix + Bangle.js 2 compatibility diff --git a/apps/beer/app-icon.js b/apps/beer/app-icon.js index c700b3bd2..734985cb5 100644 --- a/apps/beer/app-icon.js +++ b/apps/beer/app-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwghC/AB0O/4AG8AXNgYXHmAXl94XH+AXNn4XH/wXW+YX/C6oWHAAIXN7sz9vdAAoXN9sznvuAAXf/vuC53jC4Xd7wXQ93jn3u9vv9vt7wXT/4tBAgIXQ7wvCC4PgC5sO6czIQJfBC6PumaPDC6wwCC50NYAJcBVgIDBCxrAFbgYXP7yoDF6TADL4YXPVAIXCRyAXC7wXW9zwBC6cNC9zABC4gWQC653CR4fQC6x3TF6gXXI4M9d6wAEC9EN73dAAZfQgczAAkwC/4XXAH4")) +require("heatshrink").decompress(atob("mEw4cA///wH9/++1P+u3//3/qv/gv+KHkJkmABxcBBwNJkmQCJYOByQCCCBUCCItJkARQkgQHggLBku25IRDJQ4LCtu27Mt2RKJCInbAQIRLpYROglt24OB6wSC7dwLQ4LB9u2EgfbsARJ8u2mwRO+u3CNJtHCJFpCINALJoRCpCiGBoMSdQcpegIRGyaPB+QRDkARIyQRBc4YRKyet23iCJxHB6QRBzOJCJ+dCJY1CpfMGphrCp2YNZlL54CBEZgLBAQoRBiTFFCNMvmQRPndiEcJHEyQQECJMpAYIRQyARQwAROI4IAGB4wCBNAoRmhIRHCA4A/AAo")) diff --git a/apps/beer/custom.html b/apps/beer/custom.html index a357ab378..f0895f93f 100644 --- a/apps/beer/custom.html +++ b/apps/beer/custom.html @@ -127,6 +127,8 @@ var img_nofix = require("heatshrink").decompress(atob("mUyxH+ACYhJDygtYGsqLVF8u02gziGBoyhQ5gwDGRozRGCQydGCgybGCwyZC5gAaGPQwnGRAwpGQ4xwGFYyFDKsrlYxYDCsBmUyg4yXLyUsFwMyq1WAgUsNCRjUmVXroAEq8yMbcllkskwCEkplDmQwDq0sC54xEHQ9RqQAGqIwCFgOBAASYBSgMBltRAA0sgJsOGJeBxAAGwMrgIXIloxOJYNSvl8CwIDCqMBlYxNC4wxQDIOCwVYDIIDBGJ9YwV8rADBwRJCSqAVCAYaVMC4oxCPYYxQSo4xMSpIxPY4T5HY54XIMbIxKgwXKfKjhEllWGJNWlgXJGLNXruCGI+CrtXGKP+GJB9HMZ6VO/wxJcI8lfJclfKAxKfJEAGJIXLGKSvBWYQZCMZbfEqTHBGJYyFfIo1DGJ4tDGJQwCGJB9IMZyVNGIYyEfJQxPfJgwEMgoZJgAxMltRAA0tGJQyEksslkmAQklGINXxDTBFwIDCq8rC4YACC4gwJMowAJldWAAwwBABowIGJ4AYGJIymGBQylGBgyjGBwyhGCAzeF6YycGCwzYF7IzVF7o1PDqYA==")); var img_fix = require("heatshrink").decompress(atob("mUyxH+ACYhJDygtYGsqLVF94zaDYkq6wAOlQyYJo2A63VAAIoC2m0GI16My5/H5/V64ABGQIwBGQ+rTKwWHkhiBGIYwDGQ3VZioVIqoiBGAJhEGRFPGSYTIYwQxCGA4yFqodJGKeqSgQwJGQmkGKQSJfAYwLGQfPDxQwRgHVfAi/EAA4xLGQwRLYwb5BABoxQCBcA43G5wABAgIAMEBgxQ0QxB54xB5gAG4xgBBYOiGJ4PMGInPGIhcCGIt4EJoxPvHM5oxBGAnO6xrCGoXMqgxdpwxD5qQFL4QADlQxdgAhBGILIDMYoADEBwwPgCHBfQzHDAAb4NACTIIAA74OACLIIMo7GOACQoBZAoHBHQPNA4QwggGiZBA5B54HBY0DIKMYtUGMMqFYLIGY4jGhZAr6FAAYwiZAgxIY0TIFfQgADvAfR/zISGJTGR/wxRkj6CGJBiSGKL6DGP4xOGSKVDGAwxRGAQxU5oxcGR75DGJEkGCYxPlXM5vPGA/MlQxUGR1OGIL4I5lOGCgyOqgxBShHMqgwVGJt4GJd4GKwyMvHG5vGABAxMGBQyM1mtABWsGC4yLGBYABGDAyKGKwwQGZKVUF6b/OABowWGbAvZGaovdGp4dTA")); +var W = g.getWidth(), H = g.getHeight(); + // https://github.com/Leaflet/Leaflet/blob/master/src/geo/projection/Projection.SphericalMercator.js function project(latlong) { var d = Math.PI / 180, @@ -170,32 +172,30 @@ Bangle.on('GPS', function(f) { Bangle.on('mag', function(m) { if (!Bangle.isLCDOn()) return; - var headingrad = m.heading*Math.PI/180; // in radians + var headingrad = (360-m.heading)*Math.PI/180; // in radians if (!isFinite(headingrad)) headingrad=0; if (nearest) - g.drawImage(img_fix,120,120,{ + g.drawImage(img_fix,W/2,H/2,{ rotate: (Math.PI/2)+headingrad-nearestangle, scale:3, }); else - g.drawImage(img_nofix,120,120,{ + g.drawImage(img_nofix,W/2,H/2,{ rotate: headingrad, scale:2, }); - g.clearRect(60,0,180,24); - g.setFontAlign(0,0); - g.setFont("6x8"); + g.clearRect(0,0,W,24).setFontAlign(0,0).setFont("6x8"); if (fix.fix) { - g.drawString(nearest ? nearest.name : "---",120,4); + g.drawString(nearest ? nearest.name : "---",W/2,4); g.setFont("6x8",2); - g.drawString(nearest ? Math.round(nearestdist)+"m" : "---",120,16); + g.drawString(nearest ? Math.round(nearestdist)+"m" : "---",W/2,16); } else { - g.drawString(fix.satellites+" satellites",120,4); + g.drawString(fix.satellites+" satellites",W/2,4); } }); Bangle.setCompassPower(1); Bangle.setGPSPower(1); -g.clear();`; +g.setColor("#fff").setBgColor("#000").clear();`; sendCustomizedApp({ storage:[ diff --git a/apps/beer/metadata.json b/apps/beer/metadata.json index cf69aee90..3a2421bd1 100644 --- a/apps/beer/metadata.json +++ b/apps/beer/metadata.json @@ -1,11 +1,11 @@ { "id": "beer", "name": "Beer Compass", - "version": "0.01", + "version": "0.02", "description": "Uploads all the pubs in an area onto your watch, so it can always point you at the nearest one", "icon": "app.png", "tags": "", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "custom": "custom.html", "storage": [ {"name":"beer.app.js"}, diff --git a/apps/berlinc/metadata.json b/apps/berlinc/metadata.json index 49601cbd3..85c42fc47 100644 --- a/apps/berlinc/metadata.json +++ b/apps/berlinc/metadata.json @@ -7,6 +7,7 @@ "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "screenshots": [{"url":"berlin-clock-screenshot.png"}], "storage": [ diff --git a/apps/bigdclock/ChangeLog b/apps/bigdclock/ChangeLog new file mode 100644 index 000000000..c92d139bb --- /dev/null +++ b/apps/bigdclock/ChangeLog @@ -0,0 +1,7 @@ +0.01: Initial version +0.02: setTimeout bug fix; no leading zero on date; lightmode; 12 hour format; cleanup +0.03: Internationalisation; bug fix - battery icon responds promptly to charging state +0.04: bug fix +0.05: proper fix for the race condition in queueDraw() +0.06: Tell clock widgets to hide. +0.07: Better battery graphic - now has green, yellow and red sections; battery status reflected in the bar across the middle of the screen; current battery state checked only once every 15 minutes, leading to longer-lasting battery charge diff --git a/apps/bigdclock/README.md b/apps/bigdclock/README.md new file mode 100644 index 000000000..71f4362fa --- /dev/null +++ b/apps/bigdclock/README.md @@ -0,0 +1,14 @@ +# Big Digit Clock + +There are a number of big digit clocks available for the Bangle, but this is +the first which shows all the essential information that a clock needs to show +in a manner that is easy to read by those with poor eyesight. + +The clock shows the time-of-day, the day-of-week and the day-of-month, as well +as an easy-to-see icon showing the current charge on the battery. + +![screenshot](./screenshot.png) + +## Creator + +Created by [Deirdre O'Byrne](https://github.com/deirdreobyrne) diff --git a/apps/bigdclock/bigdclock.app.js b/apps/bigdclock/bigdclock.app.js new file mode 100644 index 000000000..a8e2b38df --- /dev/null +++ b/apps/bigdclock/bigdclock.app.js @@ -0,0 +1,108 @@ +// + +Graphics.prototype.setFontOpenSans = function(scale) { + // Actual height 48 (50 - 3) + this.setFontCustom( + atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAAAAP8AAAAAAAAB/wAAAAAAAAH/gAAAAAAAAf+AAAAAAAAB/4AAAAAAAAH/gAAAAAAAAf8AAAAAAAAA/wAAAAAAAAB+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAPAAAAAAAAAH8AAAAAAAAD/wAAAAAAAA//AAAAAAAAf/8AAAAAAAP//wAAAAAAH///AAAAAAB///4AAAAAA///8AAAAAAf///AAAAAAH///gAAAAAD///wAAAAAB///4AAAAAAf//+AAAAAAP///AAAAAAH///gAAAAAA///4AAAAAAD//8AAAAAAAP/+AAAAAAAA//gAAAAAAAD/wAAAAAAAAP4AAAAAAAAA8AAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/gAAAAAAAP///AAAAAAH////gAAAAD/////gAAAAf/////gAAAH//////AAAA//////+AAAD//////8AAAf//////4AAD//4AH//gAAP/gAAAf/AAA/4AAAAf8AAH/AAAAA/4AAf4AAAAB/gAB/AAAAAD+AAH8AAAAAP4AAfwAAAAA/gAB/AAAAAD+AAH8AAAAAP4AAf4AAAAB/gAB/gAAAAH+AAD/gAAAB/wAAP/gAAAf/AAA//4AAf/8AAB///////gAAD//////8AAAH//////wAAAP/////+AAAAf/////wAAAA/////8AAAAAf////AAAAAAf///gAAAAAAB//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAHgAAAAAAAAA/AAAAAAAAAH+AAAAAAAAA/4AAAAAAAAD/AAAAAAAAAf8AAAAAAAAD/gAAAAAAAAf8AAAAAAAAB/gAAAAAAAAP8AAAAAAAAB/wAAAAAAAAP///////AAA///////8AAD///////wAAP///////AAA///////8AAD///////wAAP///////AAA///////8AAD///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAD8AAAOAAAAAfwAAB+AAAAD/AAAH8AAAAf8AAA/wAAAH/wAAH/AAAA//AAAf4AAAH/8AAD/gAAA//wAAP8AAAH//AAA/gAAA//8AAD+AAAH//wAAf4AAA/9/AAB/AAAH/n8AAH8AAA/8fwAAfwAAH/h/AAB/AAA/8H8AAH8AAH/gfwAAfwAA/8B/AAB/gAH/gH8AAH+AB/8AfwAAP8Af/gB/AAA////8AH8AAD////gAfwAAH///8AB/AAAf///gAH8AAA///8AAfwAAB///gAB/AAAD//4AAH8AAAH/+AAAfwAAAH/gAAB/AAAAAAAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAYAAAAB/gAAB4AAAAD+AAAPwAAAAP4AAA/gAAAAfwAAH+AAAAB/AAAf4AAAAH8AAD/AB+AAfwAAP8AH4AA/gAA/gAfgAD+AAH+AB+AAP4AAfwAH4AA/gAB/AAfgAD+AAH8AB+AAP4AAfwAH4AA/gAB/AA/wAD+AAH8AD/AAP4AAfwAP8AA/gAB/AA/wAD+AAH+AH/AAf4AAf4Af+AB/AAA/wH/8AP8AAD////4D/wAAP//+////AAAf//7///4AAB///P///gAAD//8f//8AAAP//h///gAAAf/8D//+AAAA//gH//wAAAA/4AP/8AAAAAAAAP/AAAAAAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAAAA/wAAAAAAAAH/AAAAAAAAB/8AAAAAAAAP/wAAAAAAAD//AAAAAAAAf/8AAAAAAAH//wAAAAAAA/+/AAAAAAAP/z8AAAAAAB/8PwAAAAAAf/g/AAAAAAD/4D8AAAAAAf/APwAAAAAH/4A/AAAAAA/+AD8AAAAAP/wAPwAAAAB/8AA/AAAAAf/gAD8AAAAD/4AAPwAAAA//AAA/AAAAD///////wAAP///////AAA///////8AAD///////wAAP///////AAA///////8AAD///////wAAP///////AAAAAAAA/AAAAAAAAAD8AAAAAAAAAPwAAAAAAAAA/AAAAAAAAAD8AAAAAAAAAPwAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAQAB/gAAAD//gAD+AAA////AAP8AAD///8AAfwAAP///wAB/AAA////AAH8AAD///8AAf4AAP///wAA/gAA////AAD+AAD/+H8AAP4AAP4AfwAA/gAA/gB+AAD+AAD+AH4AAP4AAP4AfwAA/gAA/gB/AAD+AAD+AH8AAP4AAP4AfwAB/gAA/gB/AAH+AAD+AH+AA/wAAP4Af8AD/AAA/gB/4A/8AAD+AD////gAAP4AP///+AAA/gAf///wAAD+AB////AAAP4AD///4AAA/gAH///AAAAAAAP//4AAAAAAAf/+AAAAAAAAf/gAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//AAAAAAAf///gAAAAAP////gAAAAD/////gAAAAf/////AAAAD/////+AAAA//////8AAAD//////4AAAf/8/4//gAAD/8H+Af/AAAP/AfgAf8AAB/wD+AA/wAAH+APwAB/gAA/wB/AAD+AAD+AH4AAP4AAP4AfgAA/gAB/gB+AAD+AAH8AH4AAP4AAfwAfgAA/gAB/AB/AAH+AAH8AH8AAf4AAfwAf4AD/AAB/AB/4A/8AAH8AH////wAAfwAP///+AAB/AA////4AAH8AB////AAAfwAD///4AAA/AAH///AAAAAAAP//4AAAAAAAP/+AAAAAAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP4AAAAAAAAA/gAAAAAAAAD+AAAAAAAAAP4AAAAAAAAA/gAAAAAAAAD+AAAAAAAAAP4AAAAADAAA/gAAAAB8AAD+AAAAAfwAAP4AAAAH/AAA/gAAAB/8AAD+AAAAf/wAAP4AAAP//AAA/gAAD//8AAD+AAA///wAAP4AAP//8AAA/gAD///AAAD+AB///wAAAP4Af//4AAAA/gH//+AAAAD+B///gAAAAP4f//wAAAAA/v//8AAAAAD////AAAAAAP///wAAAAAA///4AAAAAAD//+AAAAAAAP//gAAAAAAA//wAAAAAAAD/8AAAAAAAAP/AAAAAAAAA/wAAAAAAAAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAAAH/wAAAAH8AB//gAAAB/8AP//gAAAf/8B//+AAAB//4P//8AAAP//w///4AAB///n///gAAH//+////AAA/////gf8AAD/h//4AfwAAP4B//AB/gAB/gD/4AD+AAH8AP/gAP4AAfwAf8AA/gAB/AA/wAB+AAH4AD/AAH4AAfwAP8AAfgAB/AB/4AD+AAH8AH/gAP4AAfwA//AA/gAA/gH/+AH+AAD/h//8AfwAAP//+/4D/AAAf//7///8AAB///H///gAAD//4P//+AAAP//g///wAAAf/8B//+AAAA//gD//wAAAA/4AH/+AAAAAAAAH/wAAAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAAAD//AAAAAAAA///AAAAAAAH//+AAPwAAB///8AA/gAAP///4AD+AAA////wAP4AAH////AA/gAAf///8AD+AAD/4B/4AP4AAP+AB/gA/gAA/gAD+AD+AAH+AAP4AP4AAfwAAfgA/gAB/AAB+AD+AAH8AAH4AP4AAfwAAfgB/AAB/AAB+AH8AAH8AAH4A/wAAf4AA/AH+AAA/gAD8A/4AAD/gAfwH/gAAP/AD+B/8AAAf/g/w//gAAB//////+AAAD//////wAAAH/////+AAAAP/////wAAAAf////8AAAAA/////AAAAAA////wAAAAAAf//4AAAAAAAH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAfgAAAAP8AAD/AAAAA/4AAf8AAAAH/gAB/4AAAAf+AAH/gAAAB/4AAf+AAAAH/gAB/4AAAAf+AAH/AAAAA/wAAP8AAAAB+AAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='), + 46, + atob("EhklJSUlJSUlJSUlEg=="), + 64+(scale<<8)+(1<<16) + ); +}; + +var drawTimeout; +var lastBattCheck = 0; +var width = 0; + +function queueDraw(millis_now) { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function () { + drawTimeout = undefined; + draw(); + }, 60000 - (millis_now % 60000)); +} + +function draw() { + var date = new Date(); + var h = date.getHours(), + m = date.getMinutes(); + var d = date.getDate(); + var is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; + var dow = require("date_utils").dows(0,1)[date.getDay()]; + + if ((date.getTime() >= lastBattCheck + 15*60000) || Bangle.isCharging()) { + lastBattcheck = date.getTime(); + width = E.getBattery(); + width += width/2; + } + + g.reset(); + g.clear(); + + g.setFontOpenSans(); + g.setFontAlign(0, -1); + if (is12Hour) { + if (h > 12) h -= 12; + if (h == 0) h = 12; + g.drawString(h + ":" + ("0"+m).substr(-2), g.getWidth() / 2, 30); + } else { + g.drawString(("0"+h).substr(-2) + ":" + ("0"+m).substr(-2), g.getWidth() / 2, 30); + } + g.setFontAlign(1, -1); + g.drawString(d, g.getWidth() -6, 98); + g.setFont('Vector', 52); + g.setFontAlign(-1, -1); + g.drawString(dow.slice(0,2).toUpperCase(), 6, 103); + + g.fillRect(9,159,166,171); + g.fillRect(167,163,170,167); + if (Bangle.isCharging()) { + g.setColor(1,1,0); + g.fillRect(12,162,12+width,168); + } else { + g.setColor(1,0,0); + g.fillRect(12,162,57,168); + g.setColor(1,1,0); + g.fillRect(58,162,72,168); + g.setColor(0,1,0); + g.fillRect(73,162,162,168); + } + if (width < 150) { + g.setColor(g.theme.bg); + g.fillRect(12+width+1,162,162,168); + } + + if (Bangle.isCharging()) { + g.setColor(1,1,0); + } else if (width <= 45) { + g.setColor(1,0,0); + } else if (width <= 60) { + g.setColor(1,1,0); + } else { + g.setColor(0, 1, 0); + } + g.fillRect(0, 90, g.getWidth(), 94); + + // widget redraw + Bangle.drawWidgets(); + queueDraw(date.getTime()); +} + +Bangle.on('lcdPower', on => { + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +Bangle.on('charging', (charging) => { + draw(); +}); + +Bangle.setUI("clock"); + +Bangle.loadWidgets(); +draw(); + diff --git a/apps/bigdclock/bigdclock.icon.js b/apps/bigdclock/bigdclock.icon.js new file mode 100644 index 000000000..4aaecfa23 --- /dev/null +++ b/apps/bigdclock/bigdclock.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgJC/AAMD4F4AgN4g/D/4FB/E/AoUH/F/AoOAh4FCz4FD4EPAoUHAoOHwAFDx/AAoUfAol/g4RD/w1Cg/B/AFD4fwn4XC4fg8/wAoPH//P7AFE9wFE8YFEEwcf4+BwAFBiACBAoUwAQPAAQMgAQNAArIjFF4sYgEBAoUIAoIRChi3B8AFBg8Ah/wAoIVBjH8ZAXguF+AoSDBn7WEh4FEg4")) diff --git a/apps/bigdclock/bigdclock.png b/apps/bigdclock/bigdclock.png new file mode 100644 index 000000000..4da1a9010 Binary files /dev/null and b/apps/bigdclock/bigdclock.png differ diff --git a/apps/bigdclock/metadata.json b/apps/bigdclock/metadata.json new file mode 100644 index 000000000..30352ca1a --- /dev/null +++ b/apps/bigdclock/metadata.json @@ -0,0 +1,17 @@ +{ "id": "bigdclock", + "name": "Big digit clock containing just the essentials", + "shortName":"Big digit clk", + "version":"0.07", + "description": "A clock containing just the essentials, made as easy to read as possible for those of us that need glasses. It contains the time, the day-of-week, the day-of-month, and the current battery state-of-charge.", + "icon": "bigdclock.png", + "type": "clock", + "tags": "clock", + "allow_emulator":true, + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "screenshots": [ { "url":"screenshot.png" } ], + "storage": [ + {"name":"bigdclock.app.js","url":"bigdclock.app.js"}, + {"name":"bigdclock.img","url":"bigdclock.icon.js","evaluate":true} + ] +} diff --git a/apps/bigdclock/screenshot.png b/apps/bigdclock/screenshot.png new file mode 100644 index 000000000..acac53ea9 Binary files /dev/null and b/apps/bigdclock/screenshot.png differ diff --git a/apps/bikespeedo/ChangeLog b/apps/bikespeedo/ChangeLog new file mode 100644 index 000000000..10752ee2b --- /dev/null +++ b/apps/bikespeedo/ChangeLog @@ -0,0 +1,3 @@ +0.01: New App! +0.02: Barometer altitude adjustment setting +0.03: Use default Bangle formatter for booleans diff --git a/apps/bikespeedo/Hochrad120px.gif b/apps/bikespeedo/Hochrad120px.gif new file mode 100644 index 000000000..1952cf44f Binary files /dev/null and b/apps/bikespeedo/Hochrad120px.gif differ diff --git a/apps/bikespeedo/Hochrad120px.png b/apps/bikespeedo/Hochrad120px.png new file mode 100644 index 000000000..2c2d4e1ef Binary files /dev/null and b/apps/bikespeedo/Hochrad120px.png differ diff --git a/apps/bikespeedo/README.md b/apps/bikespeedo/README.md new file mode 100644 index 000000000..e4ce5ea5c --- /dev/null +++ b/apps/bikespeedo/README.md @@ -0,0 +1,16 @@ +## GPS speed, GPS heading, Compass heading, GPS altitude and Barometer altitude... + +![](Hochrad120px.png)...all taken from internal sources. + +#### To speed-up GPS reception it is strongly recommended to upload AGPS data with ["Assisted GPS Update"](https://banglejs.com/apps/?id=assistedgps) + +#### If "CALIB!" is shown on the display or the compass heading differs too much from GPS heading, compass calibration should be done with the ["Navigation Compass" App](https://banglejs.com/apps/?id=magnav) + +Permanently diverging Barometer Altitude values can be compensated in the settings menu. + +Please report bugs to https://github.com/espruino/BangleApps/issues/new?assignees=&labels=bug&template=bangle-bug-report-custom-form.yaml&title=%5BBike+Speedometer%5D+Short+description+of+bug + +**Credits:**
+Bike Speedometer App by github.com/HilmarSt
+Big parts of the software are based on github.com/espruino/BangleApps/tree/master/apps/speedalt
+Compass and Compass Calibration based on github.com/espruino/BangleApps/tree/master/apps/magnav diff --git a/apps/bikespeedo/Screenshot.png b/apps/bikespeedo/Screenshot.png new file mode 100644 index 000000000..fd27728e4 Binary files /dev/null and b/apps/bikespeedo/Screenshot.png differ diff --git a/apps/bikespeedo/app-icon.js b/apps/bikespeedo/app-icon.js new file mode 100644 index 000000000..411d644fd --- /dev/null +++ b/apps/bikespeedo/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgP/ABO/AokfAgf+r4FD3lPBQcZw4FC/nD+4FC/Pn+YFCBIP7GQ4aDEIMDAol/ApQRFuAFEv0/BoQXBx0HAoPgh/nn40C4fwEoP+n/4/BWC/weBBYP5BAM/C4Pz7/7z+f//n7/z5/f//vA4Pv5//AIPv8/n//d//Ou5yBDIOfu58Bz42B+Z8Bz/8AoPgv+/AoP7w0f3IFBnc/5+bL4Oyv/nEYP/+X/mYFC+n8mff8ln+v4vfd7tfsvzvfN7tPtv2vPn6H35vg/f36vX7vj/fz9vvznH+Z3B/0+5/3/l//iDBMwMf+KEBOAPBUoOCj///CNBUQQAEA=")) diff --git a/apps/bikespeedo/app.js b/apps/bikespeedo/app.js new file mode 100644 index 000000000..a62a429e5 --- /dev/null +++ b/apps/bikespeedo/app.js @@ -0,0 +1,554 @@ +// Bike Speedometer by https://github.com/HilmarSt +// Big parts of this software are based on https://github.com/espruino/BangleApps/tree/master/apps/speedalt +// Compass and Compass Calibration based on https://github.com/espruino/BangleApps/tree/master/apps/magnav + +const BANGLEJS2 = 1; +const screenH = g.getHeight(); +const screenYstart = 24; // 0..23 for widgets +const screenY_Half = screenH / 2 + screenYstart; +const screenW = g.getWidth(); +const screenW_Half = screenW / 2; +const fontFactorB2 = 2/3; +const colfg=g.theme.fg, colbg=g.theme.bg; +const col1=colfg, colUncertain="#88f"; // if (lf.fix) g.setColor(col1); else g.setColor(colUncertain); + +var altiGPS=0, altiBaro=0; +var hdngGPS=0, hdngCompass=0, calibrateCompass=false; + +/*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ +var KalmanFilter = (function () { + 'use strict'; + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; + } + + /** + * KalmanFilter + * @class + * @author Wouter Bulten + * @see {@link http://github.com/wouterbulten/kalmanjs} + * @version Version: 1.0.0-beta + * @copyright Copyright 2015-2018 Wouter Bulten + * @license MIT License + * @preserve + */ + var KalmanFilter = + /*#__PURE__*/ + function () { + /** + * Create 1-dimensional kalman filter + * @param {Number} options.R Process noise + * @param {Number} options.Q Measurement noise + * @param {Number} options.A State vector + * @param {Number} options.B Control vector + * @param {Number} options.C Measurement vector + * @return {KalmanFilter} + */ + function KalmanFilter() { + var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, + _ref$R = _ref.R, + R = _ref$R === void 0 ? 1 : _ref$R, + _ref$Q = _ref.Q, + Q = _ref$Q === void 0 ? 1 : _ref$Q, + _ref$A = _ref.A, + A = _ref$A === void 0 ? 1 : _ref$A, + _ref$B = _ref.B, + B = _ref$B === void 0 ? 0 : _ref$B, + _ref$C = _ref.C, + C = _ref$C === void 0 ? 1 : _ref$C; + + _classCallCheck(this, KalmanFilter); + + this.R = R; // noise power desirable + + this.Q = Q; // noise power estimated + + this.A = A; + this.C = C; + this.B = B; + this.cov = NaN; + this.x = NaN; // estimated signal without noise + } + /** + * Filter a new value + * @param {Number} z Measurement + * @param {Number} u Control + * @return {Number} + */ + + + _createClass(KalmanFilter, [{ + key: "filter", + value: function filter(z) { + var u = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; + + if (isNaN(this.x)) { + this.x = 1 / this.C * z; + this.cov = 1 / this.C * this.Q * (1 / this.C); + } else { + // Compute prediction + var predX = this.predict(u); + var predCov = this.uncertainty(); // Kalman gain + + var K = predCov * this.C * (1 / (this.C * predCov * this.C + this.Q)); // Correction + + this.x = predX + K * (z - this.C * predX); + this.cov = predCov - K * this.C * predCov; + } + + return this.x; + } + /** + * Predict next value + * @param {Number} [u] Control + * @return {Number} + */ + + }, { + key: "predict", + value: function predict() { + var u = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; + return this.A * this.x + this.B * u; + } + /** + * Return uncertainty of filter + * @return {Number} + */ + + }, { + key: "uncertainty", + value: function uncertainty() { + return this.A * this.cov * this.A + this.R; + } + /** + * Return the last filtered measurement + * @return {Number} + */ + + }, { + key: "lastMeasurement", + value: function lastMeasurement() { + return this.x; + } + /** + * Set measurement noise Q + * @param {Number} noise + */ + + }, { + key: "setMeasurementNoise", + value: function setMeasurementNoise(noise) { + this.Q = noise; + } + /** + * Set the process noise R + * @param {Number} noise + */ + + }, { + key: "setProcessNoise", + value: function setProcessNoise(noise) { + this.R = noise; + } + }]); + + return KalmanFilter; + }(); + + return KalmanFilter; + +}()); + + +//==================================== MAIN ==================================== + +var lf = {fix:0,satellites:0}; +var showMax = 0; // 1 = display the max values. 0 = display the cur fix +var canDraw = 1; +var time = ''; // Last time string displayed. Re displayed in background colour to remove before drawing new time. +var sec; // actual seconds for testing purposes + +var max = {}; +max.spd = 0; +max.alt = 0; +max.n = 0; // counter. Only start comparing for max after a certain number of fixes to allow kalman filter to have smoohed the data. + +var emulator = (process.env.BOARD=="EMSCRIPTEN" || process.env.BOARD=="EMSCRIPTEN2")?1:0; // 1 = running in emulator. Supplies test values; + +var wp = {}; // Waypoint to use for distance from cur position. +var SATinView = 0; + +function radians(a) { + return a*Math.PI/180; +} + +function distance(a,b){ + var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2)); + var y = radians(b.lat-a.lat); + + // Distance in selected units + var d = Math.sqrt(x*x + y*y) * 6371000; + d = (d/parseFloat(cfg.dist)).toFixed(2); + if ( d >= 100 ) d = parseFloat(d).toFixed(1); + if ( d >= 1000 ) d = parseFloat(d).toFixed(0); + + return d; +} + +function drawFix(dat) { + + if (!canDraw) return; + + g.clearRect(0,screenYstart,screenW,screenH); + + var v = ''; + var u=''; + + // Primary Display + v = (cfg.primSpd)?dat.speed.toString():dat.alt.toString(); + + // Primary Units + u = (cfg.primSpd)?cfg.spd_unit:dat.alt_units; + + drawPrimary(v,u); + + // Secondary Display + v = (cfg.primSpd)?dat.alt.toString():dat.speed.toString(); + + // Secondary Units + u = (cfg.primSpd)?dat.alt_units:cfg.spd_unit; + + drawSecondary(v,u); + + // Time + drawTime(); + + //Sats + if ( dat.age > 10 ) { + if ( dat.age > 90 ) dat.age = '>90'; + drawSats('Age:'+dat.age); + } + else if (!BANGLEJS2) { + drawSats('Sats:'+dat.sats); + } else { + if (lf.fix) { + drawSats('Sats:'+dat.sats); + } else { + drawSats('View:' + SATinView); + } + } + g.reset(); +} + + +function drawClock() { + if (!canDraw) return; + g.clearRect(0,screenYstart,screenW,screenH); + drawTime(); + g.reset(); +} + + +function drawPrimary(n,u) { + //if(emulator)console.log("\n1: " + n +" "+ u); + var s=40; // Font size + var l=n.length; + + if ( l <= 7 ) s=48; + if ( l <= 6 ) s=55; + if ( l <= 5 ) s=66; + if ( l <= 4 ) s=85; + if ( l <= 3 ) s=110; + + // X -1=left (default), 0=center, 1=right + // Y -1=top (default), 0=center, 1=bottom + g.setFontAlign(0,-1); // center, top + if (lf.fix) g.setColor(col1); else g.setColor(colUncertain); + if (BANGLEJS2) s *= fontFactorB2; + g.setFontVector(s); + g.drawString(n, screenW_Half - 10, screenYstart); + + // Primary Units + s = 35; // Font size + g.setFontAlign(1,-1,3); // right, top, rotate + g.setColor(col1); + if (BANGLEJS2) s = 20; + g.setFontVector(s); + g.drawString(u, screenW - 20, screenYstart + 2); +} + + +function drawSecondary(n,u) { + //if(emulator)console.log("2: " + n +" "+ u); + + if (calibrateCompass) hdngCompass = "CALIB!"; + else hdngCompass +="°"; + + g.setFontAlign(0,1); + g.setColor(col1); + + g.setFontVector(12).drawString("Altitude GPS / Barometer", screenW_Half - 5, screenY_Half - 10); + g.setFontVector(20); + g.drawString(n+" "+u+" / "+altiBaro+" "+u, screenW_Half, screenY_Half + 11); + + g.setFontVector(12).drawString("Heading GPS / Compass", screenW_Half - 10, screenY_Half + 26); + g.setFontVector(20); + g.drawString(hdngGPS+"° / "+hdngCompass, screenW_Half, screenY_Half + 47); +} + + +function drawTime() { + var x = 0, y = screenH; + g.setFontAlign(-1,1); // left, bottom + g.setFont("6x8", 2); + + g.setColor(colbg); + g.drawString(time,x+1,y); // clear old time + + time = require("locale").time(new Date(),1); + + g.setColor(colfg); // draw new time + g.drawString(time,x+2,y); +} + + +function drawSats(sats) { + + g.setColor(col1); + g.setFont("6x8", 2); + g.setFontAlign(1,1); //right, bottom + g.drawString(sats,screenW,screenH); + + g.setFontVector(18); + g.setColor(col1); + + if ( cfg.modeA == 1 ) { + if ( showMax ) { + g.setFontAlign(0,1); //centre, bottom + g.drawString('MAX',120,164); + } + } +} + +function onGPS(fix) { + + if ( emulator ) { + fix.fix = 1; + fix.speed = Math.random()*30; // calmed by Kalman filter if cfg.spdFilt + fix.alt = Math.random()*200 -20; // calmed by Kalman filter if cfg.altFilt + fix.lat = 50.59; // google.de/maps/@50.59,8.53,17z + fix.lon = 8.53; + fix.course = 365; + fix.satellites = sec; + fix.time = new Date(); + fix.smoothed = 0; + } + + var m; + + var sp = '---'; + var al = '---'; + var di = '---'; + var age = '---'; + + if (fix.fix) lf = fix; + + hdngGPS = lf.course; + if (isNaN(hdngGPS)) hdngGPS = "---"; + else if (0 == hdngGPS) hdngGPS = "0?"; + else hdngGPS = hdngGPS.toFixed(0); + + if (emulator) hdngCompass = hdngGPS; + if (emulator) altiBaro = lf.alt.toFixed(0); + + if (lf.fix) { + + if (BANGLEJS2 && !emulator) Bangle.removeListener('GPS-raw', onGPSraw); + + // Smooth data + if ( lf.smoothed !== 1 ) { + if ( cfg.spdFilt ) lf.speed = spdFilter.filter(lf.speed); + if ( cfg.altFilt ) lf.alt = altFilter.filter(lf.alt); + lf.smoothed = 1; + if ( max.n <= 15 ) max.n++; + } + + + // Speed + if ( cfg.spd == 0 ) { + m = require("locale").speed(lf.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units + sp = parseFloat(m[1]); + cfg.spd_unit = m[2]; + } + else sp = parseFloat(lf.speed)/parseFloat(cfg.spd); // Calculate for selected units + + if ( sp < 10 ) sp = sp.toFixed(1); + else sp = Math.round(sp); + if (parseFloat(sp) > parseFloat(max.spd) && max.n > 15 ) max.spd = parseFloat(sp); + + // Altitude + al = lf.alt; + al = Math.round(parseFloat(al)/parseFloat(cfg.alt)); + if (parseFloat(al) > parseFloat(max.alt) && max.n > 15 ) max.alt = parseFloat(al); + + // Distance to waypoint + di = distance(lf,wp); + if (isNaN(di)) di = 0; + + // Age of last fix (secs) + age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000)); + } + + if ( cfg.modeA == 1 ) { + if ( showMax ) + drawFix({ + speed:max.spd, + sats:lf.satellites, + alt:max.alt, + alt_units:cfg.alt_unit, + age:age, + fix:lf.fix + }); // Speed and alt maximums + else + drawFix({ + speed:sp, + sats:lf.satellites, + alt:al, + alt_units:cfg.alt_unit, + age:age, + fix:lf.fix + }); // Show speed/altitude + } +} + +function setButtons(){ + setWatch(_=>load(), BTN1); + +onGPS(lf); +} + + +function updateClock() { + if (!canDraw) return; + drawTime(); + g.reset(); + + if ( emulator ) { + max.spd++; max.alt++; + d=new Date(); sec=d.getSeconds(); + onGPS(lf); + } +} + + +// =Main Prog + +// Read settings. +let cfg = require('Storage').readJSON('bikespeedo.json',1)||{}; + +cfg.spd = 1; // Multiplier for speed unit conversions. 0 = use the locale values for speed +cfg.spd_unit = 'km/h'; // Displayed speed unit +cfg.alt = 1; // Multiplier for altitude unit conversions. (feet:'0.3048') +cfg.alt_unit = 'm'; // Displayed altitude units ('feet') +cfg.dist = 1000; // Multiplier for distnce unit conversions. +cfg.dist_unit = 'km'; // Displayed distnce units +cfg.modeA = 1; +cfg.primSpd = 1; // 1 = Spd in primary, 0 = Spd in secondary + +cfg.altDiff = cfg.altDiff==undefined?100:cfg.altDiff; +cfg.spdFilt = cfg.spdFilt==undefined?true:cfg.spdFilt; +cfg.altFilt = cfg.altFilt==undefined?false:cfg.altFilt; +// console.log("cfg.altDiff: " + cfg.altDiff); +// console.log("cfg.spdFilt: " + cfg.spdFilt); +// console.log("cfg.altFilt: " + cfg.altFilt); + +if ( cfg.spdFilt ) var spdFilter = new KalmanFilter({R: 0.1 , Q: 1 }); +if ( cfg.altFilt ) var altFilter = new KalmanFilter({R: 0.01, Q: 2 }); + +function onGPSraw(nmea) { + var nofGP = 0, nofBD = 0, nofGL = 0; + if (nmea.slice(3,6) == "GSV") { + // console.log(nmea.slice(1,3) + " " + nmea.slice(11,13)); + if (nmea.slice(0,7) == "$GPGSV,") nofGP = Number(nmea.slice(11,13)); + if (nmea.slice(0,7) == "$BDGSV,") nofBD = Number(nmea.slice(11,13)); + if (nmea.slice(0,7) == "$GLGSV,") nofGL = Number(nmea.slice(11,13)); + SATinView = nofGP + nofBD + nofGL; + } } +if(BANGLEJS2) Bangle.on('GPS-raw', onGPSraw); + +function onPressure(dat) { + altiBaro = Number(dat.altitude.toFixed(0)) + Number(cfg.altDiff); +} + +Bangle.setBarometerPower(1); // needs some time... +g.clearRect(0,screenYstart,screenW,screenH); +onGPS(lf); +Bangle.setGPSPower(1); +Bangle.on('GPS', onGPS); +Bangle.on('pressure', onPressure); + +Bangle.setCompassPower(1); +var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null; +if (!CALIBDATA) calibrateCompass = true; +function Compass_tiltfixread(O,S){ + "ram"; + //console.log(O.x+" "+O.y+" "+O.z); + var m = Bangle.getCompass(); + var g = Bangle.getAccel(); + m.dx =(m.x-O.x)*S.x; m.dy=(m.y-O.y)*S.y; m.dz=(m.z-O.z)*S.z; + var d = Math.atan2(-m.dx,m.dy)*180/Math.PI; + if (d<0) d+=360; + var phi = Math.atan(-g.x/-g.z); + var cosphi = Math.cos(phi), sinphi = Math.sin(phi); + var theta = Math.atan(-g.y/(-g.x*sinphi-g.z*cosphi)); + var costheta = Math.cos(theta), sintheta = Math.sin(theta); + var xh = m.dy*costheta + m.dx*sinphi*sintheta + m.dz*cosphi*sintheta; + var yh = m.dz*sinphi - m.dx*cosphi; + var psi = Math.atan2(yh,xh)*180/Math.PI; + if (psi<0) psi+=360; + return psi; +} +var Compass_heading = 0; +function Compass_newHeading(m,h){ + var s = Math.abs(m - h); + var delta = (m>h)?1:-1; + if (s>=180){s=360-s; delta = -delta;} + if (s<2) return h; + var hd = h + delta*(1 + Math.round(s/5)); + if (hd<0) hd+=360; + if (hd>360)hd-= 360; + return hd; +} +function Compass_reading() { + "ram"; + var d = Compass_tiltfixread(CALIBDATA.offset,CALIBDATA.scale); + Compass_heading = Compass_newHeading(d,Compass_heading); + hdngCompass = Compass_heading.toFixed(0); +} +if (!calibrateCompass) setInterval(Compass_reading,200); + +setButtons(); +if (emulator) setInterval(updateClock, 2000); +else setInterval(updateClock, 10000); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/bikespeedo/app.png b/apps/bikespeedo/app.png new file mode 100644 index 000000000..50f242b47 Binary files /dev/null and b/apps/bikespeedo/app.png differ diff --git a/apps/bikespeedo/metadata.json b/apps/bikespeedo/metadata.json new file mode 100644 index 000000000..80b91427c --- /dev/null +++ b/apps/bikespeedo/metadata.json @@ -0,0 +1,20 @@ +{ + "id": "bikespeedo", + "name": "Bike Speedometer (beta)", + "shortName": "Bike Speedometer", + "version": "0.03", + "description": "Shows GPS speed, GPS heading, Compass heading, GPS altitude and Barometer altitude from internal sources", + "icon": "app.png", + "screenshots": [{"url":"Screenshot.png"}], + "type": "app", + "tags": "tool,cycling,bicycle,outdoors,sport", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"bikespeedo.app.js","url":"app.js"}, + {"name":"bikespeedo.img","url":"app-icon.js","evaluate":true}, + {"name":"bikespeedo.settings.js","url":"settings.js"} + ], + "data": [{"name":"bikespeedo.json"}] +} diff --git a/apps/bikespeedo/settings.js b/apps/bikespeedo/settings.js new file mode 100644 index 000000000..f41524263 --- /dev/null +++ b/apps/bikespeedo/settings.js @@ -0,0 +1,46 @@ +(function(back) { + + let settings = require('Storage').readJSON('bikespeedo.json',1)||{}; + + function writeSettings() { + require('Storage').write('bikespeedo.json',settings); + } + + const appMenu = { + '': {'title': 'Bike Speedometer'}, + '< Back': back, + '< Load Bike Speedometer': ()=>{load('bikespeedo.app.js');}, + 'Barometer Altitude adjustment' : function() { E.showMenu(altdiffMenu); }, + 'Kalman Filters' : function() { E.showMenu(kalMenu); } + }; + + const altdiffMenu = { + '': { 'title': 'Altitude adjustment' }, + '< Back': function() { E.showMenu(appMenu); }, + 'Altitude delta': { + value: settings.altDiff || 100, + min: -200, + max: 200, + step: 10, + onchange: v => { + settings.altDiff = v; + writeSettings(); } + } + }; + + const kalMenu = { + '': {'title': 'Kalman Filters'}, + '< Back': function() { E.showMenu(appMenu); }, + 'Speed' : { + value : settings.spdFilt, + onchange : () => { settings.spdFilt = !settings.spdFilt; writeSettings(); } + }, + 'Altitude' : { + value : settings.altFilt, + onchange : () => { settings.altFilt = !settings.altFilt; writeSettings(); } + } + }; + + E.showMenu(appMenu); + +}); diff --git a/apps/binclock/ChangeLog b/apps/binclock/ChangeLog index dc4ed8308..7c31cc0d3 100644 --- a/apps/binclock/ChangeLog +++ b/apps/binclock/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Fixed bug where screen didn't clear so incorrect time displayed. 0.03: Update to use Bangle.setUI instead of setWatch +0.04: Tell clock widgets to hide. diff --git a/apps/binclock/app.js b/apps/binclock/app.js index f8cbe8dd5..d9c74e6ce 100644 --- a/apps/binclock/app.js +++ b/apps/binclock/app.js @@ -164,9 +164,6 @@ Bangle.on('lcdPower',on=>{ draw(); // draw immediately } }); -// Load widgets -Bangle.loadWidgets(); -Bangle.drawWidgets(); // Show launcher when button pressed Bangle.setUI("clockupdown", btn=>{ if (btn!=1) return; @@ -176,3 +173,6 @@ Bangle.setUI("clockupdown", btn=>{ displayTime = 0; } }); +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/binclock/metadata.json b/apps/binclock/metadata.json index d17045868..2ca2755a6 100644 --- a/apps/binclock/metadata.json +++ b/apps/binclock/metadata.json @@ -2,7 +2,7 @@ "id": "binclock", "name": "Binary Clock", "shortName": "Binary Clock", - "version": "0.03", + "version": "0.04", "description": "A binary clock with hours and minutes. BTN1 toggles a digital clock.", "icon": "app.png", "type": "clock", diff --git a/apps/binwatch/ChangeLog b/apps/binwatch/ChangeLog index 1e54f489c..e355155b3 100644 --- a/apps/binwatch/ChangeLog +++ b/apps/binwatch/ChangeLog @@ -2,3 +2,5 @@ 0.02: first running version for BangleJs2 0.03: corrected icon, added screen shot, extended description 0.04: corrected format of background image (raw binary) +0.05: move setUI() up before draw() as to not have a false positive 'sanity +check' when building on github. diff --git a/apps/binwatch/app.js b/apps/binwatch/app.js index 28d7a06a5..153bebb32 100644 --- a/apps/binwatch/app.js +++ b/apps/binwatch/app.js @@ -334,6 +334,7 @@ function setRuntimeValues(resolution) { var hour = 0, minute = 1, second = 50; var batVLevel = 20; +Bangle.setUI("clock"); function draw() { var d = new Date(); @@ -371,7 +372,6 @@ function draw() { } // Show launcher when button pressed -Bangle.setUI("clock"); setRuntimeValues(g.getWidth()); g.reset().clear(); Bangle.loadWidgets(); diff --git a/apps/binwatch/metadata.json b/apps/binwatch/metadata.json index 0b5fb2c72..0b4dbc697 100644 --- a/apps/binwatch/metadata.json +++ b/apps/binwatch/metadata.json @@ -3,7 +3,7 @@ "shortName":"BinWatch", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], - "version":"0.04", + "version":"0.05", "supports": ["BANGLEJS2"], "readme": "README.md", "allow_emulator":true, diff --git a/apps/blackjack/ChangeLog b/apps/blackjack/ChangeLog index 25b5f9195..8e468e9ad 100644 --- a/apps/blackjack/ChangeLog +++ b/apps/blackjack/ChangeLog @@ -1,2 +1,3 @@ 0.01: New game! BTN4- Hit card, BTN5- Stand -0.02: ignore buttons on pauses \ No newline at end of file +0.02: Ignore buttons on pauses +0.03: Support Bangle.js 2 diff --git a/apps/blackjack/appb2.js b/apps/blackjack/appb2.js new file mode 100644 index 000000000..c9907487d --- /dev/null +++ b/apps/blackjack/appb2.js @@ -0,0 +1,207 @@ +var Clubs = require("heatshrink").decompress(atob("j0ewcBkmSpICipEAiQLHwA3BBY8gBQMEEA1AJwQgGyAKChILGBQUCFgxwDJpEAO5AVCII44CAQI1GAAg1GAAZQCWxCDEAAqJBQYQAFRIJWCAApcCR4YADPoRWCgQdBPopfCwAdBTw47BcBAvBU44vDfBDUIRIbUHATuQ")); + +var Spades = require("heatshrink").decompress(atob("j0ewcBkmSpICuoALJIQILHpAKBJQ+QLIUJBYsgMoY1GBQcCBYmAPgkSEBEAgggIKApBDIg4KFHAZiCAAgsDBQw4DFitJFhQ4FTwplBgRoCSQoRBBYJ6EF4jgUwDUHAVOQA==")); + +var Hearts = require("heatshrink").decompress(atob("j0ewY96gMkyAEByVIBQcSpILBhMkBYkEyQLBAQYKCCIQLEEwQgCBYuAEBFJkBBCBYw4CEA44CgQLHIYQsHLJsAEBJEHSQhxENwQADMQoAEKAdAWowLCYJESXggAFGowA/AAQ")); + +var Diamonds = require("heatshrink").decompress(atob("j0ewY1ykgKJhIKJiVIEBOSoAKHpILBBQ+SBYOQBIsBCgILBwAKEgQgCAQIKEggICAQMgKwgUDAQI1GBY4IFLgoLGJpGSPoo4EMoxNIMoqSHiR6HLgizIPoLgfAFA")); + +var deck = []; +var player = {Hand:[]}; +var computer = {Hand:[]}; +var ctx = {ready:true}; + +function createDeck() { + var suits = ["Spades", "Hearts", "Diamonds", "Clubs"]; + var values = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"]; + + var dck = []; + for (var i = 0 ; i < values.length; i++) { + for(var x = 0; x < suits.length; x++) { + dck.push({ Value: values[i], Suit: suits[x] }); + } + } + return dck; +} + +function shuffle(a) { + var j, x, i; + for (i = a.length - 1; i > 0; i--) { + j = Math.floor(Math.random() * (i + 1)); + x = a[i]; + a[i] = a[j]; + a[j] = x; + } + return a; +} + +function EndGameMessdage(msg){ + ctx.ready = false; + g.clearRect(0,160,176,176); + g.setColor(255,255,255); + g.fillRect(0,160,176,176); + g.setColor(0,0,0); + g.drawString(msg, 12, 155); + setTimeout(function(){ + startGame(); + }, 2500); + +} + +function hitMe() { + if (!ctx.ready) return; + player.Hand.push(deck.pop()); + renderOnScreen(1); + var playerWeight = calcWeight(player.Hand, 0); + + if(playerWeight == 21) + EndGameMessdage('WINNER'); + else if(playerWeight > 21) + EndGameMessdage('LOSER'); +} + +function calcWeight(hand, hideCard) { + if(hideCard === 1) { + if (hand[0].Value == "J" || hand[0].Value == "Q" || hand[0].Value == "K") + return "10 +"; + else if (hand[0].Value == "A") + return "11 +"; + else + return parseInt(hand[0].Value) +" +"; + } + else { + var weight = 0; + for(i=0; i 21 || bangleWeight < playerWeight) + EndGameMessdage('WINNER'); + else if(bangleWeight > playerWeight) + EndGameMessdage('LOOSER'); +} + +function renderOnScreen(HideCard) { + const fontName = "6x8"; + + g.clear(); // clear screen + g.reset(); // default draw styles + g.setFont(fontName, 1); + + g.setColor(255,255,255); + g.fillRect(Bangle.appRect); + g.setColor(0,0,0); + + g.drawString('Hit', 176/4-10, 160); + g.drawString('Stand', 176/4+176/2-10, 160); + + g.setFont(fontName, 3); + for(i=0; i right; + + if(is_left){ + hitMe(); + + } else if(is_right){ + stand(); + } +}); +setWatch(startGame, BTN1, {repeat:true, edge:"falling"}); + +startGame(); diff --git a/apps/blackjack/metadata.json b/apps/blackjack/metadata.json index 331c64040..837b891bd 100644 --- a/apps/blackjack/metadata.json +++ b/apps/blackjack/metadata.json @@ -2,15 +2,16 @@ "id": "blackjack", "name": "Black Jack game", "shortName": "Black Jack game", - "version": "0.02", + "version": "0.03", "description": "Simple implementation of card game Black Jack", "icon": "blackjack.png", "tags": "game", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "screenshots": [{"url":"bangle1-black-jack-game-screenshot.png"}], "allow_emulator": true, "storage": [ - {"name":"blackjack.app.js","url":"blackjack.app.js"}, + {"name":"blackjack.app.js","url":"blackjack.app.js","supports": ["BANGLEJS"]}, + {"name":"blackjack.app.js","url":"appb2.js","supports": ["BANGLEJS2"]}, {"name":"blackjack.img","url":"blackjack-icon.js","evaluate":true} ] } diff --git a/apps/bledetect/ChangeLog b/apps/bledetect/ChangeLog index e52015f04..e9b98e08c 100644 --- a/apps/bledetect/ChangeLog +++ b/apps/bledetect/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Fixed issue with wrong device informations 0.03: Ensure manufacturer:undefined doesn't overflow screen +0.04: Set Bangle.js 2 compatible, show widgets diff --git a/apps/bledetect/bledetect.js b/apps/bledetect/bledetect.js index ca8699f9a..f3fc70e92 100644 --- a/apps/bledetect/bledetect.js +++ b/apps/bledetect/bledetect.js @@ -5,6 +5,7 @@ let menu = { function showMainMenu() { menu["< Back"] = () => load(); + Bangle.drawWidgets(); return E.showMenu(menu); } @@ -55,5 +56,6 @@ function waitMessage() { E.showMessage("scanning"); } +Bangle.loadWidgets(); scan(); waitMessage(); diff --git a/apps/bledetect/metadata.json b/apps/bledetect/metadata.json index f5e0ffb19..0c30fe8f6 100644 --- a/apps/bledetect/metadata.json +++ b/apps/bledetect/metadata.json @@ -2,11 +2,11 @@ "id": "bledetect", "name": "BLE Detector", "shortName": "BLE Detector", - "version": "0.03", + "version": "0.04", "description": "Detect BLE devices and show some informations.", "icon": "bledetect.png", "tags": "app,bluetooth,tool", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"bledetect.app.js","url":"bledetect.js"}, diff --git a/apps/blobclk/ChangeLog b/apps/blobclk/ChangeLog index 9c4ef5b7b..193eb5024 100644 --- a/apps/blobclk/ChangeLog +++ b/apps/blobclk/ChangeLog @@ -5,3 +5,4 @@ 0.04: Modified to account for changes in the behavior of Graphics.fillPoly 0.05: Slight increase to draw speed after LCD on 0.06: Update to use Bangle.setUI instead of setWatch, allow themes and different size screens +0.07: Tell clock widgets to hide. diff --git a/apps/blobclk/clock-blob.js b/apps/blobclk/clock-blob.js index c84b8a1e6..d23e18ff9 100644 --- a/apps/blobclk/clock-blob.js +++ b/apps/blobclk/clock-blob.js @@ -99,6 +99,10 @@ function startTimers() { Bangle.drawWidgets(); intervalRef = setInterval(redraw,1000); } + +// Show launcher when button pressed +Bangle.setUI("clock"); + Bangle.loadWidgets(); startTimers(); Bangle.on('lcdPower',function(on) { @@ -108,5 +112,3 @@ Bangle.on('lcdPower',function(on) { clearTimers(); } }); -// Show launcher when button pressed -Bangle.setUI("clock"); diff --git a/apps/blobclk/metadata.json b/apps/blobclk/metadata.json index 85d7deabe..3ae8de222 100644 --- a/apps/blobclk/metadata.json +++ b/apps/blobclk/metadata.json @@ -2,7 +2,7 @@ "id": "blobclk", "name": "Large Digit Blob Clock", "shortName": "Blob Clock", - "version": "0.06", + "version": "0.07", "description": "A clock with big digits", "icon": "clock-blob.png", "type": "clock", diff --git a/apps/boldclk/ChangeLog b/apps/boldclk/ChangeLog index c7a4ba7b4..0c6e8cb52 100644 --- a/apps/boldclk/ChangeLog +++ b/apps/boldclk/ChangeLog @@ -2,3 +2,5 @@ 0.03: Tweak for more efficient rendering, and firmware 2v06 0.04: Work with themes, smaller screens 0.05: Adjust hand lengths to be within 'tick' points +0.06: Removed "wake LCD on face-up"-feature: A watch-face should not set things like "wake LCD on face-up". +0.07: Tell clock widgets to hide. diff --git a/apps/boldclk/bold_clock.js b/apps/boldclk/bold_clock.js index 4358b2e29..763530a32 100644 --- a/apps/boldclk/bold_clock.js +++ b/apps/boldclk/bold_clock.js @@ -129,18 +129,11 @@ Bangle.on('lcdPower', (on) => { clearTimers(); } }); -Bangle.on('faceUp',function(up){ - //console.log("faceUp: " + up + " LCD: " + Bangle.isLCDOn()); - if (up && !Bangle.isLCDOn()) { - //console.log("faceUp and LCD off"); - clearTimers(); - Bangle.setLCDPower(true); - } -}); + +// Show launcher when button pressed +Bangle.setUI("clock"); g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); startTimers(); -// Show launcher when button pressed -Bangle.setUI("clock"); diff --git a/apps/boldclk/metadata.json b/apps/boldclk/metadata.json index 7e3941cb3..086203142 100644 --- a/apps/boldclk/metadata.json +++ b/apps/boldclk/metadata.json @@ -1,7 +1,7 @@ { "id": "boldclk", "name": "Bold Clock", - "version": "0.05", + "version": "0.07", "description": "Simple, readable and practical clock", "icon": "bold_clock.png", "screenshots": [{"url":"screenshot_bold.png"}], diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 4c3d3b930..780d9cc7d 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -46,3 +46,21 @@ 0.40: Bootloader now rebuilds for new firmware versions 0.41: Add Keyboard and Mouse Bluetooth HID option 0.42: Sort *.boot.js files lexically and by optional numeric priority, e.g. appname..boot.js +0.43: Fix Gadgetbridge handling with Programmable:off +0.44: Write .boot0 without ever having it all in RAM (fix Bangle.js 1 issues with BTHRM) +0.45: Fix 0.44 regression (auto-add semi-colon between each boot code chunk) +0.46: Fix no clock found error on Bangle.js 2 +0.47: Add polyfill for setUI with an object as an argument (fix regression for 2v12 devices after Layout module changed) +0.48: Workaround for BTHRM issues on Bangle.js 1 (write .boot files in chunks) +0.49: Store first found clock as a setting to speed up further boots +0.50: Allow setting of screen rotation + Remove support for 2v11 and earlier firmware +0.51: Remove patches for 2v10 firmware (BEEPSET and setUI) + Add patch to ensure that compass heading is corrected on pre-2v15.68 firmware + Ensure clock is only fast-loaded if it doesn't contain widgets +0.52: Ensure heading patch for pre-2v15.68 firmware applies to getCompass +0.53: Add polyfills for pre-2v15.135 firmware for Bangle.load and Bangle.showClock +0.54: Fix for invalid version comparison in polyfill +0.55: Add toLocalISOString polyfill for pre-2v15 firmwares + Only add boot info comments if settings.bootDebug was set + If settings.bootDebug is set, output timing for each section of .boot0 diff --git a/apps/boot/bootloader.js b/apps/boot/bootloader.js index 3cf885ac9..6e6466f48 100644 --- a/apps/boot/bootloader.js +++ b/apps/boot/bootloader.js @@ -1,8 +1,13 @@ // This runs after a 'fresh' boot -var clockApp=(require("Storage").readJSON("setting.json",1)||{}).clock; -if (clockApp) clockApp = require("Storage").read(clockApp); -if (!clockApp) { - clockApp = require("Storage").list(/\.info$/) +var s = require("Storage").readJSON("setting.json",1)||{}; +/* If were being called from JS code in order to load the clock quickly (eg from a launcher) +and the clock in question doesn't have widgets, force a normal 'load' as this will then +reset everything and remove the widgets. */ +if (global.__FILE__ && !s.clockHasWidgets) {load();throw "Clock has no widgets, can't fast load";} +// Otherwise continue to try and load the clock +var _clkApp = require("Storage").read(s.clock); +if (!_clkApp) { + _clkApp = require("Storage").list(/\.info$/) .map(file => { const app = require("Storage").readJSON(file,1); if (app && app.type == "clock") { @@ -11,9 +16,14 @@ if (!clockApp) { }) .filter(x=>x) .sort((a, b) => a.sortorder - b.sortorder)[0]; - if (clockApp) - clockApp = require("Storage").read(clockApp.src); + if (_clkApp){ + s.clock = _clkApp.src; + _clkApp = require("Storage").read(_clkApp.src); + s.clockHasWidgets = _clkApp.includes("Bangle.loadWidgets"); + require("Storage").writeJSON("setting.json", s); + } } -if (!clockApp) clockApp=`E.showMessage("No Clock Found");setWatch(()=>{Bangle.showLauncher();}, BTN2, {repeat:false,edge:"falling"});`; -eval(clockApp); -delete clockApp; +delete s; +if (!_clkApp) _clkApp=`E.showMessage("No Clock Found");setWatch(()=>{Bangle.showLauncher();}, global.BTN2||BTN, {repeat:false,edge:"falling"});`; +eval(_clkApp); +delete _clkApp; diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index 63424bfbf..112dfeba8 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -1,20 +1,28 @@ /* This rewrites boot0.js based on current settings. If settings changed then it recalculates, but this avoids us doing a whole bunch of reconfiguration most of the time. */ -E.showMessage("Updating boot0..."); -var s = require('Storage').readJSON('setting.json',1)||{}; -var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2 -var boot = ""; +{ // execute in our own scope so we don't have to free variables... +E.showMessage(/*LANG*/"Updating boot0..."); +let s = require('Storage').readJSON('setting.json',1)||{}; +const BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2 +const FWVERSION = parseFloat(process.env.VERSION.replace("v","").replace(/\.(\d\d)$/,".0$1")); +const DEBUG = s.bootDebug; // we can set this to enable debugging output in boot0 +let boot = "", bootPost = ""; +if (DEBUG) { + boot += "var _tm=Date.now()\n"; + bootPost += "delete _tm;"; +} if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed - var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT); + let CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT); boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`; } else { - var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT); + let CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT); boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`; } boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`; boot += `E.setFlags({pretokenise:1});\n`; boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`; +bootPost += `NRF.setServices(bleServices, bleServiceOptions);delete bleServices,bleServiceOptions;\n`; // executed after other boot code if (s.ble!==false) { if (s.HID) { // Human interface device if (s.HID=="joy") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA="));`; @@ -38,7 +46,7 @@ LoopbackA.setConsole(true);\n`; boot += ` Bluetooth.line=""; Bluetooth.on('data',function(d) { - var l = (Bluetooth.line + d).split("\n"); + var l = (Bluetooth.line + d).split(/[\\n\\r]/); Bluetooth.line = l.pop(); l.forEach(n=>Bluetooth.emit("line",n)); }); @@ -61,23 +69,6 @@ if (s.ble===false) boot += `if (!NRF.getSecurityStatus().connected) NRF.sleep(); if (s.timeout!==undefined) boot += `Bangle.setLCDTimeout(${s.timeout});\n`; if (!s.timeout) boot += `Bangle.setLCDPower(1);\n`; boot += `E.setTimeZone(${s.timezone});`; -// Set vibrate, beep, etc IF on older firmwares -if (!Bangle.F_BEEPSET) { - if (!s.vibrate) boot += `Bangle.buzz=Promise.resolve;\n` - if (s.beep===false) boot += `Bangle.beep=Promise.resolve;\n` - else if (s.beep=="vib" && !BANGLEJS2) boot += `Bangle.beep = function (time, freq) { - return new Promise(function(resolve) { - if ((0|freq)<=0) freq=4000; - if ((0|time)<=0) time=200; - if (time>5000) time=5000; - analogWrite(D13,0.1,{freq:freq}); - setTimeout(function() { - digitalWrite(D13,0); - resolve(); - }, time); - }); - };\n`; -} // Draw out of memory errors onto the screen boot += `E.on('errorFlag', function(errorFlags) { g.reset(1).setColor("#ff0000").setFont("6x8").setFontAlign(0,1).drawString(errorFlags,g.getWidth()/2,g.getHeight()-1).flip(); @@ -91,114 +82,37 @@ if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`; if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`; if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${E.toJS(s.passkey.toString())}, mitm:1, display:1});\n`; if (s.whitelist) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`; -// Pre-2v10 firmwares without a theme/setUI -delete g.theme; // deleting stops us getting confused by our own decl. builtins can't be deleted -if (!g.theme) { - boot += `g.theme={fg:-1,bg:0,fg2:-1,bg2:7,fgH:-1,bgH:0x02F7,dark:true};\n`; -} -delete Bangle.setUI; // deleting stops us getting confused by our own decl. builtins can't be deleted -if (!Bangle.setUI) { // assume this is just for F18 - Q3 should already have it - boot += `Bangle.setUI=function(mode, cb) { -if (Bangle.btnWatches) { - Bangle.btnWatches.forEach(clearWatch); - delete Bangle.btnWatches; -} -if (Bangle.swipeHandler) { - Bangle.removeListener("swipe", Bangle.swipeHandler); - delete Bangle.swipeHandler; -} -if (Bangle.touchHandler) { - Bangle.removeListener("touch", Bangle.touchHandler); - delete Bangle.touchHandler; -} -if (!mode) return; -else if (mode=="updown") { - Bangle.btnWatches = [ - setWatch(function() { cb(-1); }, BTN1, {repeat:1}), - setWatch(function() { cb(1); }, BTN3, {repeat:1}), - setWatch(function() { cb(); }, BTN2, {repeat:1}) - ]; -} else if (mode=="leftright") { - Bangle.btnWatches = [ - setWatch(function() { cb(-1); }, BTN1, {repeat:1}), - setWatch(function() { cb(1); }, BTN3, {repeat:1}), - setWatch(function() { cb(); }, BTN2, {repeat:1}) - ]; - Bangle.swipeHandler = d => {cb(d);}; - Bangle.on("swipe", Bangle.swipeHandler); - Bangle.touchHandler = d => {cb();}; - Bangle.on("touch", Bangle.touchHandler); -} else if (mode=="clock") { - Bangle.CLOCK=1; - Bangle.btnWatches = [ - setWatch(Bangle.showLauncher, BTN2, {repeat:1,edge:"falling"}) - ]; -} else if (mode=="clockupdown") { - Bangle.CLOCK=1; - Bangle.btnWatches = [ - setWatch(function() { cb(-1); }, BTN1, {repeat:1}), - setWatch(function() { cb(1); }, BTN3, {repeat:1}), - setWatch(Bangle.showLauncher, BTN2, {repeat:1,edge:"falling"}) - ]; -} else - throw new Error("Unknown UI mode"); -};\n`; -} -delete E.showScroller; // deleting stops us getting confused by our own decl. builtins can't be deleted -if (!E.showScroller) { // added in 2v11 - this is a limited functionality polyfill - boot += `E.showScroller = (function(a){function n(){g.reset();b>=l+c&&(c=1+b-l);bm||m>=a.c)break;var f=24+d*a.h;a.draw(m,{x:0,y:f,w:h,h:a.h});d+c==b&&g.setColor(g.theme.fg).drawRect(0,f,h-1,f+a.h-1).drawRect(1,f+1,h-2,f+a.h-2)}g.setColor(c?g.theme.fg:g.theme.bg);g.fillPoly([e,6,e-14,20,e+14,20]);g.setColor(a.c>l+c?g.theme.fg:g.theme.bg);g.fillPoly([e,k-7,e-14,k-21,e+14,k-21])}if(!a)return Bangle.setUI();var b=0,c=0,h=g.getWidth(), -k=g.getHeight(),e=h/2,l=Math.floor((k-48)/a.h);g.reset().clearRect(0,24,h-1,k-1);n();Bangle.setUI("updown",d=>{d?(b+=d,0>b&&(b=a.c-1),b>=a.c&&(b=0),n()):a.select(b)})});\n`; -} -delete g.imageMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted -if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfill - boot += `Graphics.prototype.imageMetrics=function(src) { - if (src[0]) return {width:src[0],height:src[1]}; - else if ('object'==typeof src) return { - width:("width" in src) ? src.width : src.getWidth(), - height:("height" in src) ? src.height : src.getHeight()}; - var im = E.toString(src); - return {width:im.charCodeAt(0), height:im.charCodeAt(1)}; -};\n`; -} -delete g.stringMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted -if (!g.stringMetrics) { // added in 2v11 - this is a limited functionality polyfill - boot += `Graphics.prototype.stringMetrics=function(txt) { - txt = txt.toString().split("\\n"); - return {width:Math.max.apply(null,txt.map(x=>g.stringWidth(x))), height:this.getFontHeight()*txt.length}; -};\n`; -} -delete g.wrapString; // deleting stops us getting confused by our own decl. builtins can't be deleted -if (!g.wrapString) { // added in 2v11 - this is a limited functionality polyfill - boot += `Graphics.prototype.wrapString=function(str, maxWidth) { - var lines = []; - for (var unwrappedLine of str.split("\\n")) { - var words = unwrappedLine.split(" "); - var line = words.shift(); - for (var word of words) { - if (g.stringWidth(line + " " + word) > maxWidth) { - lines.push(line); - line = word; - } else { - line += " " + word; - } - } - lines.push(line); - } - return lines; -};\n`; -} -delete Bangle.appRect; // deleting stops us getting confused by our own decl. builtins can't be deleted -if (!Bangle.appRect) { // added in 2v11 - polyfill for older firmwares - boot += `Bangle.appRect = ((y,w,h)=>({x:0,y:0,w:w,h:h,x2:w-1,y2:h-1}))(g.getWidth(),g.getHeight()); - (lw=>{ Bangle.loadWidgets = () => { lw(); Bangle.appRect = ((y,w,h)=>({x:0,y:y,w:w,h:h-y,x2:w-1,y2:h-(1+h)}))(global.WIDGETS?24:0,g.getWidth(),g.getHeight()); }; })(Bangle.loadWidgets);\n`; -} +if (s.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation +// ================================================== FIXING OLDER FIRMWARES +if (FWVERSION<215.068) // 2v15.68 and before had compass heading inverted. + boot += `Bangle.on('mag',e=>{if(!isNaN(e.heading))e.heading=360-e.heading;}); +Bangle.getCompass=(c=>(()=>{e=c();if(!isNaN(e.heading))e.heading=360-e.heading;return e;}))(Bangle.getCompass);`; +// deleting stops us getting confused by our own decl. builtins can't be deleted +// this is a polyfill without fastloading capability +delete Bangle.showClock; +if (!Bangle.showClock) boot += `Bangle.showClock = ()=>{load(".bootcde")};\n`; +delete Bangle.load; +if (!Bangle.load) boot += `Bangle.load = load;\n`; +let date = new Date(); +delete date.toLocalISOString; // toLocalISOString was only introduced in 2v15 +if (!date.toLocalISOString) boot += `Date.prototype.toLocalISOString = function() { + var o = this.getTimezoneOffset(); + var d = new Date(this.getTime() - o*60000); + var sign = o>0?"-":"+"; + o = Math.abs(o); + return d.toISOString().slice(0,-1)+sign+Math.floor(o/60).toString().padStart(2,0)+(o%60).toString().padStart(2,0); +};\n`; + +// show timings +if (DEBUG) boot += `print(".boot0",0|(Date.now()-_tm),"ms");_tm=Date.now();\n` +// ================================================== BOOT.JS // Append *.boot.js files // These could change bleServices/bleServiceOptions if needed -var getPriority = /.*\.(\d+)\.boot\.js$/; -require('Storage').list(/\.boot\.js/).sort((a,b)=>{ - var aPriority = a.match(getPriority); - var bPriority = b.match(getPriority); +let bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{ + let getPriority = /.*\.(\d+)\.boot\.js$/; + let aPriority = a.match(getPriority); + let bPriority = b.match(getPriority); if (aPriority && bPriority){ return parseInt(aPriority[1]) - parseInt(bPriority[1]); } else if (aPriority && !bPriority){ @@ -206,19 +120,54 @@ require('Storage').list(/\.boot\.js/).sort((a,b)=>{ } else if (!aPriority && bPriority){ return 1; } - return a > b; -}).forEach(bootFile=>{ + return a==b ? 0 : (a>b ? 1 : -1); +}); +// precalculate file size +let fileSize = boot.length + bootPost.length; +bootFiles.forEach(bootFile=>{ + // match the size of data we're adding below in bootFiles.forEach + if (DEBUG) fileSize += 2+bootFile.length+1; // `//${bootFile}\n` comment + fileSize += require('Storage').read(bootFile).length+2; // boot code plus ";\n" + if (DEBUG) fileSize += 48+E.toJS(bootFile).length; // `print(${E.toJS(bootFile)},0|(Date.now()-_tm),"ms");_tm=Date.now();\n` +}); +// write file in chunks (so as not to use up all RAM) +require('Storage').write('.boot0',boot,0,fileSize); +let fileOffset = boot.length; +bootFiles.forEach(bootFile=>{ // we add a semicolon so if the file is wrapped in (function(){ ... }() // with no semicolon we don't end up with (function(){ ... }()(function(){ ... }() // which would cause an error! - boot += "//"+bootFile+"\n"+require('Storage').read(bootFile)+";\n"; + // we write: + // "//"+bootFile+"\n"+require('Storage').read(bootFile)+";\n"; + // but we need to do this without ever loading everything into RAM as some + // boot files seem to be getting pretty big now. + if (DEBUG) { + require('Storage').write('.boot0',`//${bootFile}\n`,fileOffset); + fileOffset+=2+bootFile.length+1; + } + let bf = require('Storage').read(bootFile); + // we can't just write 'bf' in one go because at least in 2v13 and earlier + // Espruino wants to read the whole file into RAM first, and on Bangle.js 1 + // it can be too big (especially BTHRM). + let bflen = bf.length; + let bfoffset = 0; + while (bflen) { + let bfchunk = Math.min(bflen, 2048); + require('Storage').write('.boot0',bf.substr(bfoffset, bfchunk),fileOffset); + fileOffset+=bfchunk; + bfoffset+=bfchunk; + bflen-=bfchunk; + } + require('Storage').write('.boot0',";\n",fileOffset); + fileOffset+=2; + if (DEBUG) { + require('Storage').write('.boot0',`print(${E.toJS(bootFile)},0|(Date.now()-_tm),"ms");_tm=Date.now();\n`,fileOffset); + fileOffset += 48+E.toJS(bootFile).length + } }); -// update ble -boot += `NRF.setServices(bleServices, bleServiceOptions);delete bleServices,bleServiceOptions;\n`; -// write file -require('Storage').write('.boot0',boot); -delete boot; -E.showMessage("Reloading..."); -eval(require('Storage').read('.boot0')); +require('Storage').write('.boot0',bootPost,fileOffset); +E.showMessage(/*LANG*/"Reloading..."); +} // .bootcde should be run automatically after if required, since // we normally get called automatically from '.boot0' +eval(require('Storage').read('.boot0')); diff --git a/apps/boot/metadata.json b/apps/boot/metadata.json index 4cbfd9c59..455563a16 100644 --- a/apps/boot/metadata.json +++ b/apps/boot/metadata.json @@ -1,7 +1,7 @@ { "id": "boot", "name": "Bootloader", - "version": "0.42", + "version": "0.55", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "icon": "bootloader.png", "type": "bootloader", diff --git a/apps/bordle/ChangeLog b/apps/bordle/ChangeLog new file mode 100644 index 000000000..ddbd6239c --- /dev/null +++ b/apps/bordle/ChangeLog @@ -0,0 +1,3 @@ +0.01: New App +0.02: app keeps track of statistics now +0.03: Fix bug in valid word detection diff --git a/apps/bordle/README.md b/apps/bordle/README.md new file mode 100644 index 000000000..f15f1e6fa --- /dev/null +++ b/apps/bordle/README.md @@ -0,0 +1,17 @@ +# Bordle + +The Bangle version of a popular word guessing game. The goal is to guess a 5 letter word in 6 tries or less. After each guess, the letters in the guess are +marked in colors: yellow for a letter that appears in the to-be-guessed word, but in a different location and green for a letter in the correct position. + +Only words contained in the internal dictionary are allowed as valid guesses. At app launch, a target word is picked from the dictionary at random. + +On startup, a grid of 6 lines with 5 (empty) letter boxes is displayed. Swiping left or right at any time switches between grid view and keyboard view. +The keyboad was inspired by the 'Scribble' app (it is a simplified version using the layout library). The letter group "Z ..." contains the delete key and +the enter key. Hitting enter after the 5th letter will add the guess to the grid view and color mark it. + +The (English language) dictionary was derived from the the Unix ispell word list by filtering out plurals and past particples (and some hand editing) from all 5 letter words. +It is contained in the file 'wordlencr.txt' which contains one long string (no newline characters) of all the words concatenated. It would not be too difficult to swap it +out for a different language version. The keyboard currently only supports the 26 characters of the latin alphabet (no accents or umlauts). + + + diff --git a/apps/bordle/app-icon.js b/apps/bordle/app-icon.js new file mode 100644 index 000000000..64ccbc8a5 --- /dev/null +++ b/apps/bordle/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AA/TADwoIFkYyOF0owIF04wGUSqvVBZQtZGJYJIFzomKF0onIF07EKF0owLF9wNEnwACE6oZILxovbMBov/F/4v/C54uWF/4vKBQQLLF/4YPFwYMLF7AZGF5Y5KF5xJIFwoMJD44vaBhwvcLQpgHF8gGRF6xYNBpQvTXBoNOF65QJBIgvjBywvUV5YOOF64OIB54v/cQwAKB5ov/F84wKADYuIF+AwkFIwwnE45hmExCSlEpTEiERr3KADw+PF0ownUSoseA==")) diff --git a/apps/bordle/app.png b/apps/bordle/app.png new file mode 100644 index 000000000..633a83e4e Binary files /dev/null and b/apps/bordle/app.png differ diff --git a/apps/bordle/bordle.app.js b/apps/bordle/bordle.app.js new file mode 100644 index 000000000..07e954a6d --- /dev/null +++ b/apps/bordle/bordle.app.js @@ -0,0 +1,196 @@ +var Layout = require("Layout"); + +var gameState = 0; +var keyState = 0; +var keyStateIdx = 0; + +function buttonPushed(b) { + if (keyState==0) { + keyState++; + keyStateIdx = b; + if (b<6) { + for (i=1; i<=5; ++i) { + var c = String.fromCharCode(i+64+(b-1)*5); + layout["bt"+i.toString()].label = c; + layout["bt"+i.toString()].bgCol = wordle.keyColors[c]||g.theme.bg; + } + layout.bt6.label = "<"; + } + else { + layout.bt1.label = "Z"; + layout.bt1.bgCol = wordle.keyColors.Z||g.theme.bg; + layout.bt2.label = ""; + layout.bt4.label = ""; + layout.bt3.label = " "; + layout.bt5.label = ""; + layout.bt6.label = "<"; + } + } + else { // actual button pushed + inp = layout.input.label; + if (b!=6) { + if ((keyStateIdx<=5 || b<=1) && inp.length<5) inp += String.fromCharCode(b+(keyStateIdx-1)*5+64); + else if (layout.input.label.length>0 && b==2) inp = inp.slice(0,-1); + if (keyStateIdx==6 && b==5) { + wordle.drawStats(); + return; + } + layout.input.label = inp; + } + layout = getKeyLayout(inp); + keyState = 0; + if (inp.length==5 && keyStateIdx==6 && b==4) { + rc = wordle.addGuess(inp); + layout.input.label = ""; + layout.update(); + gameState = 0; + if (rc>0) return; + g.clear(); + wordle.render(); + return; + } + } + layout.update(); + g.clear(); + layout.render(); +} + +function getKeyLayout(text) { + return new Layout( { + type: "v", c: [ + {type:"txt", font:"6x8:2", id:"input", label:text, pad: 3}, + {type: "h", c: [ + {type:"btn", font:"6x8:2", id:"bt1", label:"ABCDE", cb: l=>buttonPushed(1), pad:4, filly:1, fillx:1 }, + {type:"btn", font:"6x8:2", id:"bt2", label:"FGHIJ", cb: l=>buttonPushed(2), pad:4, filly:1, fillx:1 }, + ]}, + {type: "h", c: [ + {type:"btn", font:"6x8:2", id:"bt3", label:"KLMNO", cb: l=>buttonPushed(3), pad:4, filly:1, fillx:1 }, + {type:"btn", font:"6x8:2", id:"bt4", label:"PQRST", cb: l=>buttonPushed(4), pad:4, filly:1, fillx:1 }, + ]}, + {type: "h", c: [ + {type:"btn", font:"6x8:2", id:"bt5", label:"UVWXY", cb: l=>buttonPushed(5), pad:4, filly:1, fillx:1 }, + {type:"btn", font:"6x8:2", id:"bt6", label:"Z ...", cb: l=>buttonPushed(6), pad:4, filly:1, fillx:1 }, + ]} + ]}); +} + +class Wordle { + constructor(word) { + this.word = word; + this.guesses = []; + this.guessColors = []; + this.keyColors = []; + this.nGuesses = -1; + if (word == "rnd") { + this.words = require("Storage").read("wordlencr.txt"); + i = Math.floor(Math.floor(this.words.length/5)*Math.random())*5; + this.word = this.words.slice(i, i+5).toUpperCase(); + } + console.log(this.word); + this.stats = require("Storage").readJSON("bordlestats.json") || {'1':0, '2':0, '3':0, '4':0, '5':0, '6':0, 'p':0, 'w':0, 's':0, 'ms':0}; + } + render(clear) { + h = g.getHeight(); + bh = Math.floor(h/6); + bbh = Math.floor(0.85*bh); + w = g.getWidth(); + bw = Math.floor(w/5); + bbw = Math.floor(0.85*bw); + if (clear) g.clear(); + g.setFont("Vector", Math.floor(bbh*0.95)).setFontAlign(0,0); + g.setColor(g.theme.fg); + for (i=0; i<6; ++i) { + for (j=0; j<5; ++j) { + if (i<=this.nGuesses) { + g.setColor(this.guessColors[i][j]).fillRect(j*bw+(bw-bbw)/2, i*bh+(bh-bbh)/2, (j+1)*bw-(bw-bbw)/2, (i+1)*bh-(bh-bbh)/2); + g.setColor(g.theme.fg).drawString(this.guesses[i][j], 2+j*bw+bw/2, 2+i*bh+bh/2); + } + g.setColor(g.theme.fg).drawRect(j*bw+(bw-bbw)/2, i*bh+(bh-bbh)/2, (j+1)*bw-(bw-bbw)/2, (i+1)*bh-(bh-bbh)/2); + } + } + } + addGuess(w) { + let idx = -1; + do{ + idx = this.words.indexOf(w.toLowerCase(), idx+1); + } + while(idx !== -1 && idx%5 !== 0); + if(idx%5 !== 0) { + E.showAlert(w+"\nis not a word", "Invalid word").then(function() { + layout = getKeyLayout(""); + wordle.render(true); + }); + return 1; + } + this.guesses.push(w); + this.nGuesses++; + this.guessColors.push([]); + correct = 0; + var sol = this.word; + for (i=0; iwordle.stats['ms']) wordle.stats['ms'] = wordle.stats['s']; + require("Storage").writeJSON("bordlestats.json", wordle.stats); + wordle.drawStats(); + }); + return 2; + } + if (this.nGuesses==5) { + E.showAlert("The word was\n"+this.word, "You lost!").then(function(){ + wordle.stats['p']++; wordle.stats['s'] = 0; + require("Storage").writeJSON("bordlestats.json", wordle.stats); + wordle.drawStats(); + }); + return 3; + } + } + drawStats() { + E.showMessage(" ", "Statistics"); + var max = 1; + for (i=1; i<=6; ++i) if (max20 ? 25 : 25+tw, 52+(i-0.5)*(h-52)/6); + } + g.setFontVector((h-40)/9).setColor("#fff").drawString("P:"+this.stats["p"]+" W:"+this.stats["w"]+" S:"+this.stats["s"]+" M:"+this.stats["ms"], 4, 34); + Bangle.setUI(); + Bangle.on("touch", (e) => { load(); }); + } +} + +wordle = new Wordle("rnd"); +layout = getKeyLayout(""); +wordle.render(true); + +Bangle.on('swipe', function (dir) { + if (dir==1 || dir==-1) { + g.clear(); + if (gameState==0) { + layout.render(); + gameState = 1; + } + else if (gameState==1) { + wordle.render(); + gameState = 0; + } + } +}); diff --git a/apps/bordle/metadata.json b/apps/bordle/metadata.json new file mode 100644 index 000000000..f6011f798 --- /dev/null +++ b/apps/bordle/metadata.json @@ -0,0 +1,15 @@ +{ "id": "bordle", + "name": "Bordle", + "shortName":"Bordle", + "icon": "app.png", + "version":"0.03", + "description": "Bangle version of a popular word search game", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "tags": "game, text", + "storage": [ + {"name":"bordle.app.js","url":"bordle.app.js"}, + {"name":"wordlencr.txt","url":"wordlencr.txt"}, + {"name":"bordle.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/bordle/wordlencr.txt b/apps/bordle/wordlencr.txt new file mode 100644 index 000000000..e9ca9c304 --- /dev/null +++ b/apps/bordle/wordlencr.txt @@ -0,0 +1 @@ +abackabaftabaseabashabateabbeyabbotabeamabhorabideablerabodeabortaboutaboveabuseabuzzabyssachooacingacornacridactoracuteadageadaptadderaddleadeptadieuadmanadmenadmitadobeadoptadoreadornadultaerieaffixafireafootafoulafteragainagapeagateagaveagentagileagingagismaglowagonyagreeaheadaislealarmalbumalderalertalgaealiasalibialienalignalikealiveallayalleyallotallowalloyaloftalohaalonealongaloofaloudalphaaltaralteramassamazeamberambleamebaamendamigoaminoamissamityamongamourampleamplyampulamuckamuseangelangerangleangryangstanimeanionankleannexannoyannulanodeanticanvilaortaapaceapartappalappleapplyapronapteraptlyarborardorarenaargonargotarguearisearmoraromaarosearrayarrowarsonartsyascotashenasideaskewaspenaspicassayassetasterastiratlasatollatoneatriaattaratticaudioauditaugeraughtaugurauraeauralavailavastavertavianavoidawaitawakeawardawareawashawfulawingawokeaxialaxingaxiomazurebabelbaconbadgebadlybagelbaggybaizebakerbalkybalmybalsabanalbandybanjobarerbargebaronbasalbaserbasicbasilbasinbasisbastebatchbathebatonbattybawdybayoubeachbeadybeardbeastbeauxbebopbeechbeefybefitbegetbeginbegunbeigebeingbelaybelchbeliebellebellybelowbenchberetberryberthberylbesetbesombesotbevelbiblebicepbidetbightbigotbikerbilgebillybimbobingebingobipedbirchbirthbisonbitchblackbladeblameblandblankblareblastblazebleakbleatbleedbleepblendblentblestblimpblindblingblinkblitzbloatblockblondbloodbloomblownbluerbluesbluffbluntblurbblurtblushboardboastbobbybogeyboggybogiebonerboneybongobonnybonusboobyboostboothbootyboozeboozyboraxborerborneboronbosombossybosunbotchboughboundbowelbowerboxerbracebraidbrainbrakebrandbrashbrassbravebravobrawlbrawnbreadbreakbreedbriarbribebrickbridebriefbrierbrinebringbrinkbrinybriskbroadbroilbrokebroodbrookbroombrothbrownbruntbrushbruskbrutebuddybudgebuggybuglebuildbuiltbulgebulgybulkybullybumpybunchbunnyburkaburlyburntburroburstbusbybushybutchbuttebuxombuyerbylawbywaycabalcabbycabincablecacaocachecaddycadetcadgecadrecageycairncalifcalvecalyxcamelcameocampycanalcandycannycanoecanoncantocapercaponcaratcaretcargocarolcarrycarvecastecatchcatercattycaulkcauseceasecedarcellicellochafechaffchainchairchalkchampchantchaptcharmchartcharychasechasmcheapcheatcheckcheekcheepcheerchestchewychickchidechiefchildchilechilichillchimechimpchinachirpchivechockchoirchokechompchordchorechosechuckchumpchunkchurlchurnchutecidercigarciliacinchcircacivetciviccivilclackclaimclampclangclankclashclaspclasscleanclearcleatcleftclerkclickcliffclimbclimeclingclinkcliptcloakclockclompclonecloseclothcloudcloutcloveclowncluckclumpclungclunkcoachcoastcobracoccicockycocoacodexcoliccoloncolorcombocomercometcomfycomiccommacondocongaconiccookycoralcornycorpscouchcoughcouldcountcoupecourtcovencovercovetcoveycowercoyercoylycozencrackcraftcrampcranecrankcrashcratecravecrawlcrazecrazycreakcreamcredocreekcreelcreepcrepecreptcrestcrickcriercrimecrimpcrispcroakcrocicrockcronecronycrookcrooncroupcrowdcrowncrudecruelcruetcrumbcrushcrustcryptcubiccubitcurercuriecuriocurlycurrycursecurstcurvecurvycushycutercutupcyclecynicdaddydaffydailydairydaisydallydancedandydatumdauntdavitdealtdeathdebitdebugdebutdecafdecaldecaydecordecoydecrydeferdeicedeifydeigndeismdeitydelaydeltadelvedemondemurdenimdensedepotdepthderbydeterdetoxdeucedevildhotidiarydiceydigitdillydimerdimlydinerdingodingydinkydiodedirerdirgedirtydiscoditchdittodittydivandiverdivotdivvydizzydjinndodgedoggydogiedogmadoilydoingdollydonordonutdopeydorkydottydoubtdoughdousedowdydoweldownydowrydowsedoyendozendraftdraindrakedramadrankdrapedrawldrawndreaddreamdrierdriftdrilldrilydrinkdrivedrolldronedrooldroopdrovedrowndruiddrunkdryaddryerdrylyduchydullydummydumpyduncedunnoduskydustyduvetdwarfdweebdwelldweltdyingeagereagleearlyeartheaseleateneaterebonyedemaedictedifyeerieegreteidereightejectekingelateelbowelderelectelegyelideeliteelopeeludeemailembedemberemceeemendemeryemojiemoteemptyenactendowendueenemaenemyenjoyennuienrolensueenterentryenureenvoyepochepoxyequalequiperaseerecterodeerroreruptessayesteretherethicevadeeventeveryevictevokeexactexaltexcelexertexileexistexpelextolextraexudeexulteyingeyriefablefacetfagotfaintfairyfaithfakerfakirfalsefancyfannyfarcefatalfattyfaultfaunafavorfeastfecalfeignfeintfelonfemurfenceferalferryfetalfetchfetidfeverfewerfiberfichefieldfiendfieryfifthfiftyfightfilchfiletfillyfilmyfilthfinalfinchfinerfinnyfiordfirstfirthfishyfitlyfiverfixerfizzyfjordflackflailflairflakeflakyflameflankflareflashflaskfleetfleshflickflierflingflintflirtfloatflockfloodfloorfloraflourflownflufffluidflukeflukyflumeflungflunkflushfluteflybyflyerfoamyfocalfocusfoggyfoistfoliofollyfonduforayforceforgeforgoforteforthfortyforumfoundfountfoyerfrackfrailframefrancfrankfraudfreakfreerfreshfriarfrierfrillfriskfrizzfrockfrondfrontfrostfrothfrownfrozefruitfrumpfryerfudgefuguefullyfungifunkyfunnyfurorfurryfurzefussyfustyfutonfuzzygabbygablegaffegailygamergameygamingammagamutgassygaudygaugegauntgauzegauzygavelgawkygayergaylygazergeckogeekygeesegelidgeniegeniigenregenusgeodegetupghostghoulgiantgiddygimmegimpygipsygirthgismogivengizmogladeglandglareglazegleamgleanglideglintgloatglobegloomgloryglossgloveglueyglyphgnarlgnashgnawngnomegoinggollygonadgonergonnagoodygooeygoofygoosegorgegorsegottagougegourdgoutygracegradegraftgrailgraingrandgrantgrapegraphgraspgrategravegravygrazegreatgrebegreedgreengreetgriefgrillgrimegrimygrindgripegristgroangroingroomgropegrossgroupgroutgrovegrowlgrowngruelgruffgruntguanoguardguavaguessguestguideguildguileguiltguisegulaggulchgullygumbogummygunnyguppygushygustogustygutsygypsyhabithaikuhairyhalerhalonhalvehandyhankyhappyhardyharpyharryharshhastehastyhatchhaterhaunthavenhavochazelheadyheardheartheathheaveheavyhedgeheftyheisthelixhellohelothencehennaheronhertzhikerhillyhingehippohippyhitchhoagyhoardhoaryhobbyhoganhoisthokeyhokumhollyhomerhomeyhomiehoneyhonorhoochhooeyhookyhordehornyhorsehorsyhotelhotlyhoundhousehovelhoverhowdyhubbyhuffyhugerhumanhumidhumorhunchhurryhuskyhussyhutchhydrahyenahyinghymenhypericiericilyicingidealidiomidiotidleridylliglooimageimbueimpelimplyinaneinaptinboxincurindexindueineptinertinferinfixingotinlayinletinnerinputinsetinterinureirateironyisletissueitchyivoryjabotjapanjauntjazzyjehadjellojellyjerkyjettyjeweljiffyjihadjimmyjinnijointjoistjokerjollyjoulejoustjudgejuicejuicyjulepjumbojumpyjuncojunkyjuntajurorkabobkapokkaputkaratkarmakayakkazookebabkebobketchkhakikickykiddokiddykindakinkykioskkittyklutzknackknavekneadkneelknellkneltknifeknockknollknownkoalakookykopekkronekudzulabellabialaborladenladlelagerlaitylamerlancelankylapellapselarchlargelargolarvalaserlassolatchlaterlatexlathelattelaughlaxerlaxlylayerleachleafyleakyleaptlearnleaseleashleastleaveledgeleechleeryleftylegalleggylegitlemmalemmelemonlemurleperletupleveelevelleverlibellicitliegeliferlightlikenlikerlilaclimbolimitlinenlinerlingolipidlisleliterlithelivenliverlividllamallanoloamyloathlobbylocallodgeloftylogicloginlogonlonerloonyloopylooselorryloserlottolotuslouselousyloverlowerlowlyloyallucidluckylucrelumpylunarlunchlungelupinlurchluridlustylyinglymphlynchlyricmacawmachomacromadammadlymagicmagmamaizemajormakermambomammamangamangemangomangymaniamanicmanlymannamanormansemaplemarchmariamarrymarshmasonmatchmattematzomauvemavenmavinmaximmaybemayormealymeantmeatymeccamedalmediamedicmelonmercymergemeritmerrymessymetalmetermetromiddymidgemidstmightmilchmilermilkymimicminceminerminimminormintymirthmisdomisermistymitermixermochamodalmodelmodemmogulmoiremoistmolarmoldymommamommymoneymonthmoochmoodymoosemoralmoraymoronmoseymossymotelmotifmotormottomoundmountmournmousemousymouthmovermoviemowermuckymuddymuftimuggymulchmultimummymunchmuralmurkymushymusicmuskymussymustymutermynahmyrrhnabobnachonacrenadirnaiadnaivenakednannynappynasalnastynatalnattynavalnavelneathneedyneighnerdynervenervynevernewelnewernewlynexusnicernichenieceniftynigganightnimbininjaninnyninthnippyniternoblenoisenoisynomadnoncenoosenorthnoseynotchnovelnowaynudernudgenursenuttynylonnymphoakenoasisoatenobeseoccuroceanocherochreoctaloctetoddlyodiumoffalofferoftenoldenolderoldieoliveomegaoniononsetoperaopineopiumopticorateorbitorderorganotherotteroughtounceoutdoouteroutgoovaryovertovoidovuleowingowletowneroxbowoxideozonepaddypadrepaeanpaganpagerpaintpalerpalmypalsypandapanelpanicpansypantypapalpapawpaperparchparkaparryparsepartypashapastapastepastypatchpatiopatsypattypausepayeepayerpeacepeachpearlpeasepecanpedalpeevepenalpencepenispennypeonypeppyperchperilperkypeskypetalpeterpettyphasephialphishphloxphonephonyphotophylapianopickypiecepietypiggypikerpilafpilotpinchpinkypintopinuppiouspiperpipitpiquepitchpithypitonpivotpixelpixiepizzaplaceplaidplainplaitplaneplankplantplateplazapleadpleatpluckplumbplumeplumpplunkplushpoachpointpoisepokerpokeypolarpoliopolkapolyppoochpoppapoppyporchposerpositpossepottypouchpoundpowerprankprateprawnpreenpriceprickpricyprideprimeprimpprintpriorprismprivyprizeprobepromoproneprongproofproseprosyproudproveprowlproxyprudeprunepsalmpshawpsychpubicpudgypuffypulpypulsepunchpupaepupalpupilpuppypureepurerpurgepursepushypussyputtypylonquackquaffquailquakequalmquarkquartquashquasiqueenqueerquellqueryquestqueuequickquietquillquiltquirequirkquitequoitquotaquotequothrabidracerradarradiiradioradonrainyraiserallyranchrandyrangerangyrapidrarerraspyratiorattyravelravenrawerrayonrazorreachreactreadyrealmrearmrebelrebutrecaprectarecurredidreedyreevereferrefitregalrehabreignrelaxrelayrelicremitrenalrenewreorgrepayrepelreplyreranrerunresetresinretchretryreuserevelrevuerheumrhinorhymeriderridgeriferriflerightrigidrigorrinseripenriperrisenriserriskyritzyrivalrivenriverrivetroachroastrobinrobotrockyrodeorogerrogueromanroomyroostrosinrotorrougeroughroundrouserouteroverrowdyrowerroyalrubleruddyruderrugbyruingrulerrumbarummyrumorrunnyrupeeruralrustysablesabresadlysafersaintsaladsallysalonsalsasaltysalvesalvosambasandysanersappysareesassysataysatinsatyrsaucesaucysaunasaversavorsavvyscaldscalescalpscalyscampscantscarescarfscaryscenescentscoldsconescoopscootscopescorescornscourscoutscowlscrapscrewscrubscubascuffscullsedansedgeseedysegueseizesemensennasensesepiaserveservosetupsevenseversewershackshadeshadyshaftshakeshakyshaleshallshaltshameshankshapeshardsharesharksharpshaveshawlsheafshearsheensheepsheersheetsheikshelfshellsherdshiftshillshineshinyshireshirkshirrshirtshoalshockshookshootshoreshortshoutshoveshownshowyshredshrewshrubshrugshuckshuntsibylsidlesiegesievesightsigmasilkysillysincesinewsingesirensirupsisalsissysitarsixthsixtysizerskateskeetskeinskierskiffskillskimpskirtskulkskullskunkslackslainslangslantslashslateslavesleeksleepsleetsleptsliceslickslideslilyslimeslimyslingslinksloopslopesloshslothslumpslungslunkslurpslushsmacksmallsmartsmashsmearsmellsmeltsmilesmirksmitesmithsmocksmokesmokysnacksnailsnakesnakysnaresnarlsneaksneersnidesniffsnipesnoopsnootsnoresnortsnoutsnowysnucksnuffsoapysobersoftysoggysolarsolidsolvesonarsonicsonnysoothsootysoppysorersorrysortasoughsoundsoupysousesouthsowerspacespacyspadespakespanksparesparkspasmspatespawnspeakspearspeckspeedspellspeltspendspentspicespicyspielspikespikyspillspiltspinespinyspirespitesplatsplaysplitspoilspokespoofspookspoolspoonspoorsporesportspoutsprayspreesprigspumesquatsquawsquidstackstaffstagestaidstainstairstakestalestalkstallstampstandstankstaphstarestarkstartstashstatestavesteadsteakstealsteamsteedsteelsteepsteerstentsternstickstiffstilestillstiltstingstinkstintstoatstockstoicstokestolestompstonestonystoodstoolstoopstorestorkstormstorystoutstovestrapstrawstraystrepstrewstripstropstrumstrutstuckstudystuffstumpstungstuntstylesuavesudsysuedesugarsuingsuitesulkysullysunnysupersurersurgesurlysushiswainswamiswampswankswardswarmswashswathswearsweatsweepsweetswellsweptswiftswillswineswingswipeswirlswishswoonswoopswordsworeswornswungsylphsynchsynodsyruptabbytabletabootacittackytaffytainttakentakertallytalontamertangotangytapertapirtardytarottarrytasertastetastytattytaunttaupeteachtearyteaseteenyteethtelextempotempttenettenontenortensetenthtepidtersetestythankthefttheirthemetherethesethetathickthiefthighthinethingthinkthirdthongthornthosethreethrewthrobthroethrowthrumthumbthumpthymetiaratibiatidaltigertighttildetimertimestimidtingetinnytipsytitantithetitletizzytoadytoasttodaytoddytokentonaltonertonictonnetoothtopaztopictoquetorchtorsitorsotortetotaltotemtouchtoughtoweltowertoxictoxintracetracktracttradetrailtraintraittramptrashtrawltreadtreattrendtriadtrialtribetricetricktriketrilltripetritetrolltromptrooptropetrouttrucetrucktrulytrunktrusttruthtrysttubbytubertuliptulletumidtummytumortunertunictunnytutortweaktweettwerktwerptwicetwilltwinetwirltwisttyingudderulcerulnaeultraumbelumberumiakuncleuncutunderundidundueunfitunifyunionuniteunityunmanunpinunsayunsetuntieuntilunzipupendupperupseturbanurineusageusherusingusualusurputteruvulavacuavaguevaletvalidvalorvaluevalvevapidvaporvaultvauntveganveldtvenomvenuevergeversevervevicarvideovigilvigorvillavinylviolaviperviralvirusvisitvisorvistavitalvividvixenvizorvocalvodkavoguevoicevomitvotervouchvowelvulvavyingwackowackywaderwaferwagerwageswagonwaistwaivewakenwaltzwanlywannawartywastewatchwaterwaverwaxenwearyweavewedgeweedyweepyweighweirdwelchwenchwhackwhalewharfwhealwheatwheelwherewhichwhiffwhilewhinewhinywhirlwhiskwhistwhitewhizzwholewhoopwhorewhosewidenwiderwidowwidthwieldwightwimpywincewinchwindywiperwiserwispywitchwittywokenwomanwomenwoodywooerwoolywoozywordyworldwormyworryworseworstworthwouldwoundwovenwrackwrathwreakwreckwrestwrierwringwristwritewrongwrotewrothxenonxylemyachtyahooyearnyeastyieldyodelyokelyoungyouthyuccayuckyyummyyuppyzebrazilchzippyzonal \ No newline at end of file diff --git a/apps/bowserWF/ChangeLog b/apps/bowserWF/ChangeLog new file mode 100644 index 000000000..dd2b05fb3 --- /dev/null +++ b/apps/bowserWF/ChangeLog @@ -0,0 +1,3 @@ +... +0.02: First update with ChangeLog Added +0.03: updated watch face to use the ClockFace library diff --git a/apps/bowserWF/app.js b/apps/bowserWF/app.js index e53d945cc..956c43602 100644 --- a/apps/bowserWF/app.js +++ b/apps/bowserWF/app.js @@ -1,102 +1,233 @@ var sprite = { - width : 47, height : 47, bpp : 3, - transparent : 1, - buffer : require("heatshrink").decompress(atob("kmSpICFn/+BAwCImV//VICJuT//SogRMpmT/2SCJtSyQDB/4RMymRkmX/gRLygDC3/piVhCJElAYf/pNIkgRIlIDCl/6pVBkIRIGwWJEYPypMJCI9KGwQRBLANIPRI2CGoPkyVCBwmeyVLTYNJom8yImBz4gEqV/6Vf+g2BPwf/IIq8C/+kyVRkgDBp/5CIX/+mkz/+y/9BIOf0v6///5LdCz+kCIOk34RBYQMSp5XBGQVk/pNBAQP/9IyBxGSv4yCk/1OIK8EC4QgEpM/JgJ+EGoIRBTApQCEYvplLOFXIIdBO4SqBeQJABGoeTDQMlk5WCAAPSYQLgEz4aBlM/9IgB/7CCcAvP/QsBiVfUwOJBgUiCIcmpAVCy/+pMAKwMkRgIRCp6VBAwW6qVOgmSgPkwgRDv53E6WSuEkyEPRgmf2VJv5HBl2SgAKBwEJRgnJiVKp/Sr/0y/yBQOQv56DKwVSv2STwO/DgWD/BADmaDByRoBYoQRCgFCCIf/+jgDNwOUAwMg/kSPQbODX4IJBAwUH8B6DsmRl5oBl7OBklMyV+gBoDycSxMpiVLZwS8EAQeYyjaByR6BBIJBDAQnEIgbFCogOFRgQDBr//I4L0EAQsxAYP//5WCGQ6MCAAKbCpKYEAQiMB//kIQOUyf+CJF/CIIEBTYOfcgQRHBQv/CJKnBpP8GRTCDJIPkGRQCB5I3C/n/EZUgA")) + width: 47, + height: 47, + bpp: 3, + transparent: 1, + buffer: require("heatshrink").decompress( + atob( + "kmSpICFn/+BAwCImV//VICJuT//SogRMpmT/2SCJtSyQDB/4RMymRkmX/gRLygDC3/piVhCJElAYf/pNIkgRIlIDCl/6pVBkIRIGwWJEYPypMJCI9KGwQRBLANIPRI2CGoPkyVCBwmeyVLTYNJom8yImBz4gEqV/6Vf+g2BPwf/IIq8C/+kyVRkgDBp/5CIX/+mkz/+y/9BIOf0v6///5LdCz+kCIOk34RBYQMSp5XBGQVk/pNBAQP/9IyBxGSv4yCk/1OIK8EC4QgEpM/JgJ+EGoIRBTApQCEYvplLOFXIIdBO4SqBeQJABGoeTDQMlk5WCAAPSYQLgEz4aBlM/9IgB/7CCcAvP/QsBiVfUwOJBgUiCIcmpAVCy/+pMAKwMkRgIRCp6VBAwW6qVOgmSgPkwgRDv53E6WSuEkyEPRgmf2VJv5HBl2SgAKBwEJRgnJiVKp/Sr/0y/yBQOQv56DKwVSv2STwO/DgWD/BADmaDByRoBYoQRCgFCCIf/+jgDNwOUAwMg/kSPQbODX4IJBAwUH8B6DsmRl5oBl7OBklMyV+gBoDycSxMpiVLZwS8EAQeYyjaByR6BBIJBDAQnEIgbFCogOFRgQDBr//I4L0EAQsxAYP//5WCGQ6MCAAKbCpKYEAQiMB//kIQOUyf+CJF/CIIEBTYOfcgQRHBQv/CJKnBpP8GRTCDJIPkGRQCB5I3C/n/EZUgA" + ) + ), }; const boxes = { - width : 122, height : 56, bpp : 3, - transparent : 1, - buffer : require("heatshrink").decompress(atob("kmZkmSpICPwgDBmQUQAQMJAYNkFiOSiQDB5JESAYQsSpADByYsSyBZBydt23bAR+wgFJkwUQAQNggGSposR23AgMkzZESwECpM2IiUAgmSFiW2gDlBFiVsgDlBFiXYgDNBL4MDWZy2FgEGWZy2FgENWZy2EL4MbWZpTBWwZfBXJpTCWwZiCWZpTBWwZiCWZsbWwhiCWZpWCWwTORWwgXRWwgXRWwZESWwZESWwZESWwYXRWwgXRW362/W362/W362/W362/W362/W362/W362/W362/W362/W362/WwuAgazOWwsAgyzOWwsAhqzOWwhfBjazNKYK2DL4K5NKYS2DMQSzNKYK2DMQSzNja2EMQSzNKwS2CZyK2EC6K2EC6K2DIiS2DIiS2DIiUAFoMAAFTkBFtckyAtrLgWSpICnLIIsqyVAgAsqpIA=")) + width: 122, + height: 56, + bpp: 3, + transparent: 1, + buffer: require("heatshrink").decompress( + atob( + "kmZkmSpICPwgDBmQUQAQMJAYNkFiOSiQDB5JESAYQsSpADByYsSyBZBydt23bAR+wgFJkwUQAQNggGSposR23AgMkzZESwECpM2IiUAgmSFiW2gDlBFiVsgDlBFiXYgDNBL4MDWZy2FgEGWZy2FgENWZy2EL4MbWZpTBWwZfBXJpTCWwZiCWZpTBWwZiCWZsbWwhiCWZpWCWwTORWwgXRWwgXRWwZESWwZESWwZESWwYXRWwgXRW362/W362/W362/W362/W362/W362/W362/W362/W362/W362/WwuAgazOWwsAgyzOWwsAhqzOWwhfBjazNKYK2DL4K5NKYS2DMQSzNKYK2DMQSzNja2EMQSzNKwS2CZyK2EC6K2EC6K2DIiS2DIiS2DIiUAFoMAAFTkBFtckyAtrLgWSpICnLIIsqyVAgAsqpIA=" + ) + ), }; const background = { - width : 176, height : 176, bpp : 3, - transparent : 5, - buffer : require("heatshrink").decompress(atob("kmSpIC/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/ATWAgEAIP1///8iRB8gf/AAOCIPdIIARBBoJB/+E4IP4ABghB9v4CB8BB5g/92//9pB7wP/97FEIO9IgDACAAn8iVBIOlHH4xBDnA+wyY9IAAmB/BB//5B/IOQ/OAARBup5B/yV/IP5B/IP5BRt5B7/wDC7aD8/w+B+3bBgP7IP5B7HYNt23/AQPfIPX/9oCC24IDINwCBIRAAHIOACBHI3+g4EC/l/4BByAQkA//wpED//4gGAhJB3pMAgQFBgEBH3AC/AX4C/AX4C/AX4C/AX4C/AUOAgBB/v//ghB9gf///gH3UgiVIIAJBBwRB5j+CIIf8uBB5//wIIXb//+hJB6o/92/7v5B7/0/97GCIPYAG4MgIP/BjkSIP34/hB//5B/AAQ+0IP5B/IP5BN7ZB97///wCBIPX93yAB2wCB+5B5tv//dt24CB35B5v/+n/t+P/I4PH8ESIO38gFA/+CgH/+EIgiD3gACCPoMAgQ+2AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/ASVIgAACgRB/IPY8GkAHBiRB/IPBLKgJB/IP5B/AQUAkmQghB/IP2AgEAyVAiRB/IP5BBpMAIP5B/IIUkgBB/IP5BpoAsBgJBOgEEIIoIBIP5BlyE27dt2EEIJ4CBBAlIgRBgpEAhu2IIO24ESQwxB/IJQhGkEJIL8GHwQCDgOweQpB/IKMkwAKJILVgAofYeQhBzsEAIKICLoESILmBQARBBtuwgZB3kA4B4ENIgJBcpMAIMYCDIOcAgEbHYgCGsEJkhEBE6cBIP5BZfYQ+JIIkDsEBIP5BVyEAIKtAHxgCDwBEBINk2IKCGCIKmSpECIP5BUkEBHyACD2BBUFoMJIP5BSpEbHyQCDIP5BXkmAIP5B/AQcAbKJB/ILH/AAP8hM/AgWSv4KCAAP+gmfAoXJk4ME//gpIEC8mTBgvwkgEC+QRDAAX4gVPAgP5kgsCLwWQh/kMIUf5LuFg4jBAoMBKAJ5EwF/AoUA/yFFoE/CI6RDgY+BCIQsDIP5B/IP5B/IP5B/IJ/AIJfghJBKv0EIJcAIJfwIP5BMhMAAAMEz5BGgmABoVJII9IBgUkII8kBgUSII8CoAMBhJB/IIsQoMAYoP/AAP4YpAMC/+BII9/BgXAYpAMC8DFIBgXwIIcCIP6DCgkQh/kCIRBIbQcBIJAFCgBBICI5BE/IRDFgQA=")) + width: 176, + height: 176, + bpp: 3, + transparent: 5, + buffer: require("heatshrink").decompress( + atob( + "kmSpIC/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/ATWAgEAIP1///8iRB8gf/AAOCIPdIIARBBoJB/+E4IP4ABghB9v4CB8BB5g/92//9pB7wP/97FEIO9IgDACAAn8iVBIOlHH4xBDnA+wyY9IAAmB/BB//5B/IOQ/OAARBup5B/yV/IP5B/IP5BRt5B7/wDC7aD8/w+B+3bBgP7IP5B7HYNt23/AQPfIPX/9oCC24IDINwCBIRAAHIOACBHI3+g4EC/l/4BByAQkA//wpED//4gGAhJB3pMAgQFBgEBH3AC/AX4C/AX4C/AX4C/AX4C/AUOAgBB/v//ghB9gf///gH3UgiVIIAJBBwRB5j+CIIf8uBB5//wIIXb//+hJB6o/92/7v5B7/0/97GCIPYAG4MgIP/BjkSIP34/hB//5B/AAQ+0IP5B/IP5BN7ZB97///wCBIPX93yAB2wCB+5B5tv//dt24CB35B5v/+n/t+P/I4PH8ESIO38gFA/+CgH/+EIgiD3gACCPoMAgQ+2AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/ASVIgAACgRB/IPY8GkAHBiRB/IPBLKgJB/IP5B/AQUAkmQghB/IP2AgEAyVAiRB/IP5BBpMAIP5B/IIUkgBB/IP5BpoAsBgJBOgEEIIoIBIP5BlyE27dt2EEIJ4CBBAlIgRBgpEAhu2IIO24ESQwxB/IJQhGkEJIL8GHwQCDgOweQpB/IKMkwAKJILVgAofYeQhBzsEAIKICLoESILmBQARBBtuwgZB3kA4B4ENIgJBcpMAIMYCDIOcAgEbHYgCGsEJkhEBE6cBIP5BZfYQ+JIIkDsEBIP5BVyEAIKtAHxgCDwBEBINk2IKCGCIKmSpECIP5BUkEBHyACD2BBUFoMJIP5BSpEbHyQCDIP5BXkmAIP5B/AQcAbKJB/ILH/AAP8hM/AgWSv4KCAAP+gmfAoXJk4ME//gpIEC8mTBgvwkgEC+QRDAAX4gVPAgP5kgsCLwWQh/kMIUf5LuFg4jBAoMBKAJ5EwF/AoUA/yFFoE/CI6RDgY+BCIQsDIP5B/IP5B/IP5B/IJ/AIJfghJBKv0EIJcAIJfwIP5BMhMAAAMEz5BGgmABoVJII9IBgUkII8kBgUSII8CoAMBhJB/IIsQoMAYoP/AAP4YpAMC/+BII9/BgXAYpAMC8DFIBgXwIIcCIP6DCgkQh/kCIRBIbQcBIJAFCgBBICI5BE/IRDFgQA=" + ) + ), }; numbersDims = { - width: 20, - height: 44 + width: 20, + height: 44, }; -const numbers = [ - require("heatshrink").decompress(atob("ikswcBkmSpIC/ARGQKYQIDAwUEBxMAAQNAgECpMgAQMkB4IOIAQQLCgEQBwQaBgEBB1oCBBwYCCiRWDCIRWEO5wOHAX4CnA=")), - require("heatshrink").decompress(atob("ikswcBkmSpIC/ARNIKYIIEwEAggOKNIQODyAHCBxQsWB3TUFgMgA4sSBwzU/AVA=")), - require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ8gKggIBAwkCBw+QCIQLCgIRCDQcQBwwyDDwUSCgVAAwIOBEwI7EpI7FBw4FDghZGHwgOEF4Y+CEYQ+DBxQADNAIAFNAIOFa/4CoA=")), - require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ8gKosSAwsBBw4aCoEAgQjEBoIpEBwtIBoIUEwEAggUDBwwyDDoWQA4ZWHhIIEJQoOCgI+EBwMQEAYOJO4oLBO4oRDJQrX/AU4")), - require("heatshrink").decompress(atob("ikswcBkmSpIC/ARNIKgQIDwAGBgQOJNQYOCyAHDBxEggB6BBwYDBiVABxIjBCIIODF4YOEAAkBV40QBwxiDNAosEB0IC/AUg")), - require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ5UFkmQAwkCBxIdGCIIIDBxAsTgAaEkEASooOBiQOVJQgOBiBKDBxMSJQwRBLIgRCBwjX/AVA=")), - require("heatshrink").decompress(atob("ikswcBkmSpIC/ARGQKgYICAwcCBxADBiQdDkEANYoOGEAYyEHYoOIHYqfFBxIdDBAMQFgZHCBysSFgwRBO46GFa/4CnA")), - require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ5VGiAGFgIOIDQUgBwUCEYQOJGQYNBHAlADQgOHwEAggUDpANBCgYpBBwmQAwJiGhIjDB1gC/AU4A=")), - require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ8gKYYICAwcEBxGQgAaDgVJgACBDQQOJgB6CBwcAiQODHa4AEhIRBpAHDiARBwAGCgIgCFIYOCFIYOHiQrEJQxlCBwzX/AVAA=")), - require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ8gKggIBAwkCBw+QCIQLCgIRCDQcQBzkSTAsBHYoOIL4gOCMooOENAYOCoA4EBwoqDgiGGF4gOEa/4CoA=")), +const numbers = [ + require("heatshrink").decompress( + atob( + "ikswcBkmSpIC/ARGQKYQIDAwUEBxMAAQNAgECpMgAQMkB4IOIAQQLCgEQBwQaBgEBB1oCBBwYCCiRWDCIRWEO5wOHAX4CnA=" + ) + ), + require("heatshrink").decompress( + atob("ikswcBkmSpIC/ARNIKYIIEwEAggOKNIQODyAHCBxQsWB3TUFgMgA4sSBwzU/AVA=") + ), + require("heatshrink").decompress( + atob( + "ikswcBkmSpIC/AQ8gKggIBAwkCBw+QCIQLCgIRCDQcQBwwyDDwUSCgVAAwIOBEwI7EpI7FBw4FDghZGHwgOEF4Y+CEYQ+DBxQADNAIAFNAIOFa/4CoA=" + ) + ), + require("heatshrink").decompress( + atob( + "ikswcBkmSpIC/AQ8gKosSAwsBBw4aCoEAgQjEBoIpEBwtIBoIUEwEAggUDBwwyDDoWQA4ZWHhIIEJQoOCgI+EBwMQEAYOJO4oLBO4oRDJQrX/AU4" + ) + ), + require("heatshrink").decompress( + atob( + "ikswcBkmSpIC/ARNIKgQIDwAGBgQOJNQYOCyAHDBxEggB6BBwYDBiVABxIjBCIIODF4YOEAAkBV40QBwxiDNAosEB0IC/AUg" + ) + ), + require("heatshrink").decompress( + atob( + "ikswcBkmSpIC/AQ5UFkmQAwkCBxIdGCIIIDBxAsTgAaEkEASooOBiQOVJQgOBiBKDBxMSJQwRBLIgRCBwjX/AVA=" + ) + ), + require("heatshrink").decompress( + atob( + "ikswcBkmSpIC/ARGQKgYICAwcCBxADBiQdDkEANYoOGEAYyEHYoOIHYqfFBxIdDBAMQFgZHCBysSFgwRBO46GFa/4CnA" + ) + ), + require("heatshrink").decompress( + atob( + "ikswcBkmSpIC/AQ5VGiAGFgIOIDQUgBwUCEYQOJGQYNBHAlADQgOHwEAggUDpANBCgYpBBwmQAwJiGhIjDB1gC/AU4A=" + ) + ), + require("heatshrink").decompress( + atob( + "ikswcBkmSpIC/AQ8gKYYICAwcEBxGQgAaDgVJgACBDQQOJgB6CBwcAiQODHa4AEhIRBpAHDiARBwAGCgIgCFIYOCFIYOHiQrEJQxlCBwzX/AVAA=" + ) + ), + require("heatshrink").decompress( + atob( + "ikswcBkmSpIC/AQ8gKggIBAwkCBw+QCIQLCgIRCDQcQBzkSTAsBHYoOIL4gOCMooOENAYOCoA4EBwoqDgiGGF4gOEa/4CoA=" + ) + ), ]; -digitPositions = [ // relative to the box - {x:13, y:6}, {x:32, y:6}, - {x:74, y:6}, {x:93, y:6}, +digitPositions = [ + // relative to the box + { x: 13, y: 6 }, + { x: 32, y: 6 }, + { x: 74, y: 6 }, + { x: 93, y: 6 }, ]; -var drawTimeout; const animation_duration = 1; // seconds -const animation_steps = 20; +const animation_steps = 20; const jump_height = 45; // top coordinate of the jump const seconds_per_minute = 60; -function draw() { - const now = new Date(); - g.drawImage(background, 0, 0); - var boxTL_x = 27; var boxTL_y = 29; - var sprite_TL_x = 72; var sprite_TL_y = 161 - sprite.height; - const seconds = now.getSeconds()%seconds_per_minute + now.getMilliseconds()/1000; - const hours = now.getHours(); - const minutes = now.getMinutes(); - - var time_advance = seconds / animation_duration; - - if (time_advance < 0.5) { - sprite_TL_y += (jump_height - sprite_TL_y) * time_advance * 2; - } else if (time_advance < 1) { - sprite_TL_y = jump_height + (sprite_TL_y-jump_height) * (time_advance-0.5) * 2; - } - const box_penetration = boxTL_y + boxes.height - sprite_TL_y; - if (box_penetration > 0) { - boxTL_y -= box_penetration; - } - g.drawImage(boxes, boxTL_x, boxTL_y); - g.drawImage(numbers[(hours / 10) >> 0], boxTL_x+digitPositions[0].x, boxTL_y+digitPositions[0].y); - g.drawImage(numbers[(hours % 10) >> 0], boxTL_x+digitPositions[1].x, boxTL_y+digitPositions[1].y); - g.drawImage(numbers[(minutes / 10) >> 0], boxTL_x+digitPositions[2].x, boxTL_y+digitPositions[2].y); - g.drawImage(numbers[(minutes % 10) >> 0], boxTL_x+digitPositions[3].x, boxTL_y+digitPositions[3].y); - g.drawImage(sprite, sprite_TL_x, sprite_TL_y); - Bangle.drawWidgets(); - - const timeout = time_advance <= 1? - animation_duration / animation_steps - : (seconds_per_minute - seconds); - setTimeout( _=>{ - drawTimeout = undefined; - draw(); - }, timeout * 1000); -} +const ClockFace = require("ClockFace"); +const clock = new ClockFace({ + precision: 60, // just once a minute -// Clear the screen once, at startup -g.setTheme({bg:"#00f",fg:"#fff",dark:true}).clear(); + init: function() { + // Clear the screen once, at startup + g.setTheme({ bg: "#00f", fg: "#fff", dark: true }).clear(); -Bangle.on('lcdPower',on=>{ - if (on) { - draw(); // draw immediately, queue redraw - } else { // stop draw timer - if (drawTimeout) { - clearTimeout(drawTimeout); - } - drawTimeout = undefined; - } + this.drawing = true; + + this.simpleDraw = function(now) { + var boxTL_x = 27; + var boxTL_y = 29; + var sprite_TL_x = 72; + var sprite_TL_y = 161 - sprite.height; + const seconds = + (now.getSeconds() % seconds_per_minute) + now.getMilliseconds() / 1000; + const hours = + this.is12Hour && now.getHours() > 12 + ? now.getHours() - 12 + : now.getHours(); + + const minutes = now.getMinutes(); + + g.drawImage(boxes, boxTL_x, boxTL_y); + g.drawImage( + numbers[(hours / 10) >> 0], + boxTL_x + digitPositions[0].x, + boxTL_y + digitPositions[0].y + ); + g.drawImage( + numbers[hours % 10 >> 0], + boxTL_x + digitPositions[1].x, + boxTL_y + digitPositions[1].y + ); + g.drawImage( + numbers[(minutes / 10) >> 0], + boxTL_x + digitPositions[2].x, + boxTL_y + digitPositions[2].y + ); + g.drawImage( + numbers[minutes % 10 >> 0], + boxTL_x + digitPositions[3].x, + boxTL_y + digitPositions[3].y + ); + }; + }, + + pause: function() { + this.drawing = false; + }, + + resume: function() { + this.drawing = true; + }, + + draw: function(now) { + if (!this.drawing) { + this.simpleDraw(now); + return; + } + g.drawImage(background, 0, 0); + var boxTL_x = 27; + var boxTL_y = 29; + var sprite_TL_x = 72; + var sprite_TL_y = 161 - sprite.height; + const seconds = + (now.getSeconds() % seconds_per_minute) + now.getMilliseconds() / 1000; + const hours = + this.is12Hour && now.getHours() > 12 + ? now.getHours() - 12 + : now.getHours(); + + const minutes = now.getMinutes(); + + var time_advance = seconds / animation_duration; + + if (time_advance < 0.5) { + sprite_TL_y += (jump_height - sprite_TL_y) * time_advance * 2; + } else if (time_advance < 1) { + sprite_TL_y = + jump_height + (sprite_TL_y - jump_height) * (time_advance - 0.5) * 2; + } + const box_penetration = boxTL_y + boxes.height - sprite_TL_y; + if (box_penetration > 0) { + boxTL_y -= box_penetration; + } + g.drawImage(boxes, boxTL_x, boxTL_y); + g.drawImage( + numbers[(hours / 10) >> 0], + boxTL_x + digitPositions[0].x, + boxTL_y + digitPositions[0].y + ); + g.drawImage( + numbers[hours % 10 >> 0], + boxTL_x + digitPositions[1].x, + boxTL_y + digitPositions[1].y + ); + g.drawImage( + numbers[(minutes / 10) >> 0], + boxTL_x + digitPositions[2].x, + boxTL_y + digitPositions[2].y + ); + g.drawImage( + numbers[minutes % 10 >> 0], + boxTL_x + digitPositions[3].x, + boxTL_y + digitPositions[3].y + ); + g.drawImage(sprite, sprite_TL_x, sprite_TL_y); + // Bangle.drawWidgets(); + + if (this.drawing) { + const timeout = + time_advance <= 1 ? animation_duration / animation_steps : -999; + if (timeout > 0) { + setTimeout((_) => { + this.draw(new Date()); + }, timeout * 1000); + } + } + }, + + update: function(date, changed) { + if (this.drawing && changed.m) { + this.draw(date); + } + }, }); -// Show launcher when middle button pressed -Bangle.setUI("clock"); -// Load widgets -Bangle.loadWidgets(); - -draw(); +clock.start(); diff --git a/apps/bowserWF/metadata.json b/apps/bowserWF/metadata.json index 22df2dea4..bba15e5df 100644 --- a/apps/bowserWF/metadata.json +++ b/apps/bowserWF/metadata.json @@ -1,14 +1,18 @@ -{ "id": "bowserWF", +{ + "id": "bowserWF", "name": "Bowser Watchface", - "shortName":"Bowser Watchface", - "version":"0.01", + "shortName": "Bowser Watchface", + "version": "0.03", "description": "Let bowser show you the time", "icon": "app.png", - "tags": "", - "supports" : ["BANGLEJS2"], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "allow_emulator": true, "readme": "README.md", "storage": [ - {"name":"bowserWF.app.js","url":"app.js"}, - {"name":"bowserWF.img","url":"app-icon.js","evaluate":true} - ] + { "name": "bowserWF.app.js", "url": "app.js" }, + { "name": "bowserWF.img", "url": "app-icon.js", "evaluate": true } + ], + "data": [{ "name": "bowserWF.json" }] } diff --git a/apps/bradbury/app-icon.js b/apps/bradbury/app-icon.js new file mode 100644 index 000000000..07c4f5582 --- /dev/null +++ b/apps/bradbury/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwcCkGSpEgwQCChICFkgCBgkQoMEyFJAoICByVBkgLBkkSpIaDEwWShEkFgcIBAIdCEYQCBAoQdBAoYsBC4Q7BpICBEYQCDF4Q7CEYYCCEYUSKYYUDyRlCJQQIBNYYvBMoQCBkgjBFgxxCL4REDFgaPEHYgmCIgosCNYZEEDoZ0CNwY7CIIYgDEYtB9+e/dg/4AB2EJkYEB/mC/fn33Ivvz598v4MB/0BgoRCyVHvmW7Mg2EA8uD/EAh/IkGP/8AgVLtkA5El+FJvoRBgmf4Mkh0HkEQo9kyEfkeQofsgf4kmPCIP+h/gwULkkCncEu/ZsmRI4cEv0H8ESpdgEwMjwXI9kTCIOANYkSEYOCncF+UAjuR/ED+FBg/3/f8RgNgiVPkYdBtkT/Egv0Il+AoMfI4PgyX7vkW799F4Nl//4//woH/+0Ztvx7Fs335sk//5EB/IRBhACB77CBpEkgEIgGQoDRBgEggVBgDdBgGAgPv317ku+5cj334t+OSoI+B8gCBtlx7dkuFfgvx4N8yPbvgOB8ACBR4MA9mf4Egz3IgeChEDwDOBx/AjuCoN8y/JgkX4ME2FBjuQn65BgMtwELkGOEYOO4Mh2EJh+Sh/jOIMd+3fskRcwMTEwOWo98gCSBwFJkm2pfgx3II4PBk++/aABhEfwEInpZBvkX7MkJQMl2FHfANBjgCBlmQhHsgwjB33IkeyBAOChMcEwM9+/ZsBHBboMJtv2hd9+FHZANBVoM7kGC/fv2FJ9+GEYOAh//+UIaIMBkkQpEAHwIIBoMgiFJBANJEAMIkGShEkwQIChIIBhIIBhIaCkmQpIFCgmSEwYpDEYwCCpAICBwUEiQdFEwIICyAIDHwQ7CEYYpCEYWSpA7FDocSEwojBCgIaDIgYCBNwR0BNYYjFEwZTDLgQjGOgYvBEYQ7ENYlJFgQCCDohuGTYpBFkhoCSoQICEYIA=")) diff --git a/apps/bradbury/app.js b/apps/bradbury/app.js new file mode 100644 index 000000000..147242689 --- /dev/null +++ b/apps/bradbury/app.js @@ -0,0 +1,115 @@ +require("Font7x11Numeric7Seg").add(Graphics); +require("Font5x9Numeric7Seg").add(Graphics); +require("Font8x12").add(Graphics); +require("FontDylex7x13").add(Graphics); +const X = 98, Y = 46; +var wizible = 0; + +function getImg() { + return require("heatshrink").decompress(atob("2GwwcCAoNBgmQpMkiACCoMkyALBAoMEyQCDkkSAoICCCIIXCCgQaCAQNJDQYUBDQIIBDQkgIwsShEkwUJkGSpACBBAQFCAQOCBAgFCyQXBDQQIDCIUIEYgOCpICBFgYXCII2W7ft237AQPbt++7fvBAIFBAQgRCAoNtCgIaDC4QaD7dtBAgUBDQYXC9+z5cEIIv279t+/fvoFBvoFC+/bBAe3CIQIBBwW2CIgCCCIYgFAQgjEAoN5sGAIAcF33JaIT4DAoTyDcYL1CAQgXBpIUDfYQCCC4T+CDoYgDDQQFBocMyBBDkeyagb4EBArpCpD1DfAoIDfAQLCDQwIGDQklwRBDLIJfEiffjv//H/AAPkgUB//+oEk+Pf/+B5/8+VHn/wh9/+ff/vx4f/+0f/+yFIIsCPoSMCWYSVDoBBCL48A8kQvEn+VA/i2Bn/yoMgh0BkvwhMFx0Bg6nBhkQh8Ej14h4XB9kSU4iSFQwMkGoWWhCDDagRQCiUPgED5Ej+EI/kAhF8+0BkH+LIOyCIOT4EAF4M8gVfgGG5EhRgNggFJGoIpBJQKJDAQQLBRgSDEKYRZCoNnOIXypaJBgPkjzFBn1AkeQv8/FgMcwFBnkArNsv/Bkl+v4vBVQQCBPQKzBRgoCCjEgIILOCKwQFBo8gwf4kf//50Bp/kz/IgEyhEj+UPskPPQOAoE8yE7GQInBx/4R4R6DfwQCGBYQCBIITOCagJNBo/gyEZg/wAoMCv5GB/MnR4KDBvEEi0IhkCQYMQp8Aj0BFgPBgECOgT+BQwJ9EAoQCEwEAJobOBQwM/kFx5E/+UH8mCv8gyf5kHygHlwVLklz5EjSQM8wP/BwPJkGX7IsBVQL7CO4SJFAoK5BBAUAI4SDDwfP9+eoH//0P/+f//gj//8OOvcsgV5/6JBgm+nNkgPH8kev8ETQMANALOBO4QFBPoa2CR4qDByBKCagTPBwAOBgEIVQJTBTAIIDDQIRBgMggQIBiQaGfAwCLRgWQoDUEpCeDgCSDAQLyCoEAAQIdCHIIjEAoS/BDQbsCyQCNX4dIJQaGCDRwCnRIbUDyTRBIOy8BQYTLDBYL7BAGUEIgI+CQYeCpMgIGYABiSDByUIkkSI4JKBgEB/4AW4/j+PHILECXgKDDpCDCgF+QehBBgDCBkkQI4VIgeAY2q/ByCDBySDCIPKDCgkQoKDCjg4tgcMmHDgACCQYuChMkQYJBujFhwwCBwQICpEEySDDAoKD1oaDDiSDDAQNIDI1z588+YCiQYoCBkCDCwSDCI4KDHgeevPnAUeAQYkQgaDDyFBkjIBkiDHjiAjAQXwQYwxBHAI+CiFBZYKDGg+f/4Ak+CDEAQSDCHwMgZAKDKIMyDEwCDDgg+BIgUkQYJBFQd0DQY9IAQSD1sCDDAQKDCySDvgKDEAQKDCySDChKDygKDCAQMgQYcIgkSI4MSQd8DQYkDQYcSYQJEBAQKDwkCDHgA+CiDLCQeCADmFDQYsgQAICCQd8hw0AsOCgFgQZESQeEMQZbCBQeR6BjFhw0YQYlIgmQoKDFgOwQdnBQYPDQYY+BkmChICBIIcP+yDqQAQCBgEgQYMEYQKDDAoKDxQAKDFiFBkCDBAQJBDAASDpgOCQwdgQYMAwUIkhEBkkSIIccuHAQd1DQY5EBQYnjx04QdMAgUAsOCgCDDyUIIgUEQYtxQdSABAQiDGhICBQeEhw0YsOAhCDCgmSAQOQoMkQeMMmHDQYICBQYQ+BQZUYQdAuCAAo4BHYMkZAKDGmKDriCDHiQ+ByUIQYvggU4QdEYsOGAQdgQYhEBI4SDEgKDrQASDFYQWCQY8AQejCBkmQoMEySD8kBEBQY0GjCD4kkSIIcEuKD0ySDJ8OOQdkIQYw7BZAUEQYkcgKDsoaDGYQKABQY3jwSDqgEGQwOAQYcEQYTIBAQKDyoKDGYQKABpKDF8EOhCDpsOGgACCQYkIkkQpMkiSDwgiACQYuQoMkyUIQY0AjCDnwCDCAQOAhCDCgCDEgiDFuPAgaDl/kAgcMmFDQY0QoKABhKDFsOOAgQAmQYsAQYUEyQCBIgKDFAFcDQAKDC4CDDyCDCpKDFaAIHBAT+Dx048YCDhCDBQAICCQYQ7BQYaJBIAIHDAQM/AokEj3x/mf/IIDz3JgEQv8/+VxtmX+MEj/BnkAuPAglx4cMQYYxBmAFBQYQkBkmSLoSDIn+DxBrDHwP48R0E+0OAoP6k+D/N/33YlAUCQYMIgEOgHgh0YsEGjCGBAoMArA2BQYMSI4KDJnnz4IIDjlx4mwqIID//P4EQp8Hifx4/y56DB6N8QYQaBAQNwQYUBAQPDQY8JkiDKwSDEyV5tGX5AIEh9gwX+QYPJ8+f/CYB5MgQYM48ICB8eOgEBw0AQYMIQYR9BhBBBQZYCRgAOLQYPHQYICBQYMMmFDQY0SoJpBpACCQYQAsQAMYsACCQYMAQYWQI4VJIN4AGgQ7ByCDFIPHIgA+BgmSoMkiFJkBB1iVAgkQQYTIByVJkmAIGcEy1IHYKDBIgKGDIgQCxkuSQYMSQYOShCGBJQQ1m5cs2QCKwUBkjCCiFJQwYC1gBBBAoJWBQYQCBf9gAIgVIYQRECJoKeBJQQFCyALBAQMkiSVBboICDNAgUDDQQOCBAQXFyQRDBAICBDQdBQAMJZYICCQwOSpCPEpICBLIIFBCIIFCDRwCFDQp6BCI5HEOgiJDBAJ0ETAaPFCIgaGSo4IDGpKDCOIZTDBAJWCOgRxCboQCBCgQCCBwICDOgqPCBAqeDCgoODdhDdBBYqPIBwSPDDQgCDfAQCCSQgyDEASJDQYLODOgZfBOIRWFL4TjERIYdDTAYOEBAICEC4o4FBwLaGAXNAgBuFAXMAAH4A/AAcB23btoC84ENIP/Yj/+7Ml/4AG9u27//+3/CgIJB/3bt4EBEAe3DAn2BAO/DoQJCGof/HYgXD/3JlpBDpdsz5CHAF/yrdk//4hvy/9kwf7v5lCTA9vQAJxB/YLFPoRxETYTCSt+WTAOeQYOX7cki1/QWv833bsmRQYOW7Mg31f9rvB/pWCCoR6HNBF9NYQXGRJAOCCQXbtm+5MkgX4jgFBgvy5//wEAAB8PQcPl2VIgG2vEM21Il+W5/8ICAABNYKYEcIf+SoKABBYI4Gtu/CgaMCsmSgNv+VYjmyoN83xBTgKDh8mArf8y14huShfl21bthBS/ZlCt59Bt7vBtowFBYIRCtoFBv4FBBYSGC9kW/8n2XYj+Qr8t+1/Qev83/Jtuz/EN+X5v+z/d8IKqGCAQO///9PQW274FE//tAoYCEDoNvyV/vueQYP+pdsz5NBQen/+Vbsn/QYO27MlcAWAIKEGdgP2dgSGCBAIABPQoOBAoO3SoftAQKbE5Mt2yDBNUQAc/BBBMoXfBALXCAQLyF27sIEIYRCAgJ3CEwQLDv6wBRIIADEAYFDQf6DChpuGAQ++BZP/C5VvOggFCCgV/Rgi5GQf6DDIIP7gAAUgz+C//t2APIn/tO4WABxEbPoKPDtu/IIX4IKsBMImDNQ/+o4EC/ixI/0HQZENZAJBVgBfBcwNsgVbfwQCD2VAAoVgiQLEBwcAAoX9BYfYQbv8CBQOC8AONQYxBXQYRiBthHB8FwBQNwg4CBgF/IJtvBwPtQccB4EcIIcA8eAQbEf+3YILXsIIkDx0AjgQBOIVgD5R9B//9AQKDE/5BVh6DFYoZBFQa4oCd4TjCKAPf9u3AoTaC74ZD+3bYorCCMoKJBGQP9DoJBLGQQjCtrFCJY4AUIId//EcZYSDZhrLDOgP+PQSJDBAtt34IBAoX/7dsGRZxBsAOKEwaVBAoPYQbx0NQakfZYRNBt4LDAon+R4qDDBwPbvkSpMkyQCEOgSYBIJanDHYZBBA4IWKABUPQYk/RhKDYAQJBVgHbv6DBtiDHkB0EsCDLUgNtH4IFB7BBYgJ6GOhaDW7BBXMoVsGRe275BLQYgCBQf6DDh6DXgHf/qDNtu3IJiDCt/27f//yD/QYUNZAKDW7d9MoJBLQaP/2xBDQf5BCZAJBW/Z0C9gQK/p0BsAOKt49C+3bRIPYQf4+BhpEBIKoiBOgVsB5ZxBQZYdCUgTFDQf/4jqDYMQP+QZjyBQZlt34+C/aGBQYX/ICsPQakJkmSpICEoCDIhrOCJQQFB/4ICAQ9/DAIFFIKATCAAnyQYIjC+3fGoPYQYQAaQbGQBwaDFIIKADt//AQQIDboYODIKQdB75BBBxW/F4iDwBxiDHO4T7EKAYCEQwgICQaH/sAFByUAYIMBkgOFSoRBE/4lKABUPQauAoEggEIAoKDM/BBVgCGCQZpBGkmAIIJQDUgSqDILMBQarFBQYYOFQYsNIgJBYMQNsQZaSBYpd//6AB/wCBYrSDWYoWQgMkQZcf/3YIKsAcYSDM/oOBsBQL+3bv6DC23YQb0CrZHCAQeyoCDDXoSSKQYsN3//IKsGHAd/wYoH/1HQYVsiVJkmSAQsDto4BLgiDCADnwKJE/BwaDJBwiDFAYJcCvoCBa4PfbQRrBO4IFBL4P7XgwdBt6ADBAQLDCgwCB9oFDF4KDjAEKDCKwwCFCIIFDSoQOD2//NYJoBAoYRHQwaeB3//96PGDoP/Qf6DCh50DMoOAgAAPgz7E356Dt4oCSQynE+wLCRIP//YXDQYfZkoGB/hAQgEBP8X+5MvQYMf/1Ltme7dsIKhxENAJuBPoKMFAQe/RIdv/wIDtvyrdk+yDB+X/t+zQe+X/ckj/4huXLIVbQaUAfAv//r+ER4r7BO4O2SQJ9B94IDXIO+7Mg2f4juSpMkyVfQevs23Jgvy/EcwAtChZBSQYR3FQAT1CBY6YE7f9BYll+1Il+WrEcQYdP/5HDABsPQcPl2VBvm2vEcBQfLMRO/cwZrFdgPbt/2CgXfOIttE4Pt/4dBSQv/74NBQYOShfl+1YjqDDr5vhACfsyFfluy/EfyxQB+1/bQRiCO4Vt3x9DMoVvBwW2eQRrBOIZ6BOIIXCBwYpBv4aBSoIIDtmC/Nv2XYgfy/d/2aC1AAOX5f9z/AgO+pdszzmEAQT1BeQZrDO4qGCDQ4CJCoILI+Vbsn/4EAv/ZkqC3cAPJl/+gEAh5oH350DeobjD76GCBYgFB/4FCDQIdCBYNvTAQFBv4RDAQ3/+BBBgE/QXAAC/g/BA=")); +} + +function draw() { + var d = new Date(); + var h = d.getHours() % 12 || 12, m = d.getMinutes(), yyyy = d.getFullYear(), mm = d.getMonth(), dd = d.getDate(); + var time = (""+h).substr(-2) + ":" + ("0"+m).substr(-2); + g.reset(); // Reset the state of the graphics library + g.clear(); + g.drawImage(getImg()); //load bg image + //TIME + g.setFont("7x11Numeric7Seg",2); + g.setFontAlign(1,1); + g.setColor(0,0,1); + g.drawString(time, 97, 53, false /*clear background*/); + g.setColor(0,0,0); + g.drawString(time, 96, 52, false /*clear background*/); + //SECONDS + g.setFont("7x11Numeric7Seg",1); + //g.setFont("5x9Numeric7Seg"); + g.setFontAlign(-1,1); // align right bottom + g.setColor(0,0,1); + g.drawString(("0"+d.getSeconds()).substr(-2), 100, 42, 0); + g.setColor(0,0,0); + g.drawString(("0"+d.getSeconds()).substr(-2), 99, 41, 0); + //DATE + g.setFont("5x9Numeric7Seg",1); + g.setFontAlign(1,1); + g.setColor(0,0,1); + g.drawString(yyyy+" "+("0"+mm)+" "+dd, 100, 65, 0); + g.setColor(0,0,0); + g.drawString(yyyy+" "+("0"+mm)+" "+dd, 99, 64, 0); + //BATTERY + g.setColor(0,0,1); + g.drawString(E.getBattery(), 137, 53, 0); + g.setColor(0,0,0); + g.drawString(E.getBattery(), 136, 52, 0); + //STEPS + g.setColor(0,0,1); + g.drawString(Bangle.getHealthStatus("day").steps, 137, 65, 0); + g.setColor(0,0,0); + g.drawString(Bangle.getHealthStatus("day").steps, 136, 64, 0); + //WEEK DAY + g.setFont("8x12"); + g.setColor(0,0,1); + if (d.getDay()==0) { + g.drawString("SU", 137, 43, 0); + g.setColor(0,0,0); + g.drawString("SU", 136, 42, 0); + } else if (d.getDay()==1) { + g.drawString("MO", 137, 43, 0); + g.setColor(0,0,0); + g.drawString("MO", 136, 42, 0); + } else if (d.getDay()==2) { + g.drawString("TU", 137, 43, 0); + g.setColor(0,0,0); + g.drawString("TU", 136, 42, 0); + } else if (d.getDay()==3) { + g.drawString("WE", 137, 43, 0); + g.setColor(0,0,0); + g.drawString("WE", 136, 42, 0); + } else if (d.getDay()==4) { + g.setFont("Dylex7x13"); + g.drawString("TH", 137, 43, 0); + g.setColor(0,0,0); + g.drawString("TH", 136, 42, 0); + } else if (d.getDay()==5) { + g.drawString("FR", 137, 43, 0); + g.setColor(0,0,0); + g.drawString("FR", 136, 42, 0); + } else { + g.drawString("SA", 137, 43, 0); + g.setColor(0,0,0); + g.drawString("SA", 136, 42, 0); + } + if(wizible==1){ + Bangle.drawWidgets(); + } +} + +// Clear the screen once, at startup +g.clear(); +// draw immediately at first +draw(); +var secondInterval = setInterval(draw, 1000); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (secondInterval) clearInterval(secondInterval); + secondInterval = undefined; + if (on) { + secondInterval = setInterval(draw, 1000); + draw(); // draw immediately + } +}); +// Show launcher when middle button pressed +Bangle.setUI("clock"); + +//Toggle Widgets +Bangle.loadWidgets(); +Bangle.on('touch', function(button) { + if(wizible==0){ + wizible=1; + } + else if(wizible==1){ + wizible=0; + } +}); diff --git a/apps/bradbury/app.png b/apps/bradbury/app.png new file mode 100644 index 000000000..f7141d15e Binary files /dev/null and b/apps/bradbury/app.png differ diff --git a/apps/bradbury/metadata.json b/apps/bradbury/metadata.json new file mode 100644 index 000000000..456daa381 --- /dev/null +++ b/apps/bradbury/metadata.json @@ -0,0 +1,14 @@ +{ "id": "bradbury", + "name": "Bradbury Watch", + "shortName":"Bradbury", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "version":"0.01", + "description": "A watch face based on the classic Seiko model worn by one of my favorite authors. I didn't follow the original lcd layout exactly, opting for larger font for more easily readable time, and adding date, battery level, and step count; read from the device. Tapping the screen toggles visibility of widgets.", + "type": "clock", + "supports":["BANGLEJS2"], + "storage": [ + {"name":"bradbury.app.js","url":"app.js"}, + {"name":"bradbury.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/bradbury/screenshot.png b/apps/bradbury/screenshot.png new file mode 100644 index 000000000..914266668 Binary files /dev/null and b/apps/bradbury/screenshot.png differ diff --git a/apps/bthrm/ChangeLog b/apps/bthrm/ChangeLog index 58d002f22..000c5e3f8 100644 --- a/apps/bthrm/ChangeLog +++ b/apps/bthrm/ChangeLog @@ -20,3 +20,24 @@ 0.07: Recorder icon only blue if values actually arive Adds some preset modes and a custom one Restructure the settings menu +0.08: Allow scanning for devices in settings +0.09: Misc Fixes and improvements (https://github.com/espruino/BangleApps/pull/1655) +0.10: Use default Bangle formatter for booleans +0.11: App now shows status info while connecting + Fixes to allow cached BluetoothRemoteGATTCharacteristic to work with 2v14.14 onwards (>1 central) +0.12: Fix HRM fallback handling + Use default boolean formatter in custom menu and directly apply config if useful + Allow recording unmodified internal HR + Better connection retry handling +0.13: Less time used during boot if disabled +0.14: Allow bonding (Debug menu) + Prevent mixing of BT and internal HRM events if both are enabled + Always use a grace period (default 0 ms) to decouple some connection steps + Device not found errors now utilize increasing timeouts +0.15: Fix recording internal sensor + Handle fallback to internal sensor consistently if BT bpm is 0 + Power internal sensor down if not needed for fallback +0.16: Set powerdownRequested correctly on BTHRM power on + Additional logging on errors + Add debug option for disabling active scanning +0.17: New GUI based on layout library diff --git a/apps/bthrm/README.md b/apps/bthrm/README.md index 42ad619bd..f4eaf43af 100644 --- a/apps/bthrm/README.md +++ b/apps/bthrm/README.md @@ -2,7 +2,7 @@ When this app is installed it overrides Bangle.js's build in heart rate monitor with an external Bluetooth one. -HRM is requested it searches on Bluetooth for a heart rate monitor, connects, and sends data back using the `Bangle.on('HRM'` event as if it came from the on board monitor. +HRM is requested it searches on Bluetooth for a heart rate monitor, connects, and sends data back using the `Bangle.on('HRM')` event as if it came from the on board monitor. This means it's compatible with many Bangle.js apps including: @@ -16,21 +16,36 @@ as that requires live sensor data (rather than just BPM readings). Just install the app, then install an app that uses the heart rate monitor. -Once installed it'll automatically try and connect to the first bluetooth -heart rate monitor it finds. +Once installed you will have to go into this app's settings while your heart rate monitor + is available for bluetooth pairing and scan for devices. -**To disable this and return to normal HRM, uninstall the app** +**To disable this and return to normal HRM, uninstall the app or change the settings** + +### Modes + +* Off - Internal HRM is used, no attempt on connecting to BT HRM. +* Default - Replaces internal HRM with BT HRM and falls back to internal HRM if no valid measurements received. +* Both - The BT HRM needs to be started explicitly by an app that wants to use it. BT HRM has its own event and is completely separated from the internal HRM. Apps not supporting the BT HRM will not see the BT HRM measurements. +* Custom - Combine low level settings as you see fit. ## Compatible Heart Rate Monitors This works with any heart rate monitor providing the standard Bluetooth -Heart Rate Service (`180D`) and characteristic (`2A37`). +Heart Rate Service (`180D`) and characteristic (`2A37`). It additionally supports +the location (`2A38`) characteristic and the Battery Service (`180F`), reporting +that information in the `BTHRM` event when they are available. So far it has been tested on: * CooSpo Bluetooth Heart Rate Monitor +* Polar H10 +* Polar OH1 * Wahoo TICKR X 2 +## Recorder plugin + +The recorder plugin can record the BT HRM event (blue) and the original unchanged HRM event (green). This is mainly useful for debugging purposes or comparing the BT with the internal HRM, as the resulting "merged" HRM can be recordet using the default HRM recorder. + ## Internals This replaces `Bangle.setHRMPower` with its own implementation. @@ -38,7 +53,6 @@ This replaces `Bangle.setHRMPower` with its own implementation. ## TODO * A widget to show connection state? -* Specify a specific device by address? ## Creator diff --git a/apps/bthrm/boot.js b/apps/bthrm/boot.js index 339f6f8c6..3e3d35737 100644 --- a/apps/bthrm/boot.js +++ b/apps/bthrm/boot.js @@ -1,555 +1 @@ -(function() { - var settings = Object.assign( - require('Storage').readJSON("bthrm.default.json", true) || {}, - require('Storage').readJSON("bthrm.json", true) || {} - ); - - var log = function(text, param){ - if (settings.debuglog){ - var logline = new Date().toISOString() + " - " + text; - if (param){ - logline += " " + JSON.stringify(param); - } - print(logline); - } - }; - - log("Settings: ", settings); - - if (settings.enabled){ - - function clearCache(){ - return require('Storage').erase("bthrm.cache.json"); - } - - function getCache(){ - return require('Storage').readJSON("bthrm.cache.json", true) || {}; - } - - function addNotificationHandler(characteristic){ - log("Setting notification handler: " + supportedCharacteristics[characteristic.uuid].handler); - characteristic.on('characteristicvaluechanged', supportedCharacteristics[characteristic.uuid].handler); - } - - function writeCache(cache){ - var oldCache = getCache(); - if (oldCache != cache) { - log("Writing cache"); - require('Storage').writeJSON("bthrm.cache.json", cache) - } else { - log("No changes, don't write cache"); - } - - } - - function characteristicsToCache(characteristics){ - log("Cache characteristics"); - var cache = getCache(); - if (!cache.characteristics) cache.characteristics = {}; - for (var c of characteristics){ - //"handle_value":16,"handle_decl":15 - log("Saving handle " + c.handle_value + " for characteristic: ", c); - cache.characteristics[c.uuid] = { - "handle": c.handle_value, - "uuid": c.uuid, - "notify": c.properties.notify, - "read": c.properties.read - }; - } - writeCache(cache); - } - - function characteristicsFromCache(){ - log("Read cached characteristics"); - var cache = getCache(); - if (!cache.characteristics) return []; - var restored = []; - for (var c in cache.characteristics){ - var cached = cache.characteristics[c]; - var r = new BluetoothRemoteGATTCharacteristic(); - log("Restoring characteristic ", cached); - r.handle_value = cached.handle; - r.uuid = cached.uuid; - r.properties = {}; - r.properties.notify = cached.notify; - r.properties.read = cached.read; - addNotificationHandler(r); - log("Restored characteristic: ", r); - restored.push(r); - } - return restored; - } - - log("Start"); - - var lastReceivedData={ - }; - - var serviceFilters = [{ - services: [ "180d" ] - }]; - - supportedServices = [ - "0x180d", "0x180f" - ]; - - var supportedCharacteristics = { - "0x2a37": { - //Heart rate measurement - handler: function (event){ - var dv = event.target.value; - var flags = dv.getUint8(0); - - var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit - - var sensorContact; - - if (flags & 2){ - sensorContact = (flags & 4) ? true : false; - } - - var idx = 2 + (flags&1); - - var energyExpended; - if (flags & 8){ - energyExpended = dv.getUint16(idx,1); - idx += 2; - } - var interval; - if (flags & 16) { - interval = []; - maxIntervalBytes = (dv.byteLength - idx); - log("Found " + (maxIntervalBytes / 2) + " rr data fields"); - for(var i = 0 ; i < maxIntervalBytes / 2; i++){ - interval[i] = dv.getUint16(idx,1); // in milliseconds - idx += 2 - } - } - - var location; - if (lastReceivedData && lastReceivedData["0x180d"] && lastReceivedData["0x180d"]["0x2a38"]){ - location = lastReceivedData["0x180d"]["0x2a38"]; - } - - var battery; - if (lastReceivedData && lastReceivedData["0x180f"] && lastReceivedData["0x180f"]["0x2a19"]){ - battery = lastReceivedData["0x180f"]["0x2a19"]; - } - - if (settings.replace){ - var newEvent = { - bpm: bpm, - confidence: (sensorContact || sensorContact === undefined)? 100 : 0, - src: "bthrm" - }; - - log("Emitting HRM: ", newEvent); - Bangle.emit("HRM", newEvent); - } - - var newEvent = { - bpm: bpm - }; - - if (location) newEvent.location = location; - if (interval) newEvent.rr = interval; - if (energyExpended) newEvent.energy = energyExpended; - if (battery) newEvent.battery = battery; - if (sensorContact) newEvent.contact = sensorContact; - - log("Emitting BTHRM: ", newEvent); - Bangle.emit("BTHRM", newEvent); - } - }, - "0x2a38": { - //Body sensor location - handler: function(data){ - if (!lastReceivedData["0x180d"]) lastReceivedData["0x180d"] = {}; - if (!lastReceivedData["0x180d"]["0x2a38"]) lastReceivedData["0x180d"]["0x2a38"] = data.target.value; - } - }, - "0x2a19": { - //Battery - handler: function (event){ - if (!lastReceivedData["0x180f"]) lastReceivedData["0x180f"] = {}; - if (!lastReceivedData["0x180f"]["0x2a19"]) lastReceivedData["0x180f"]["0x2a19"] = event.target.value.getUint8(0); - } - } - - }; - - var device; - var gatt; - var characteristics = []; - var blockInit = false; - var currentRetryTimeout; - var initialRetryTime = 40; - var maxRetryTime = 60000; - var retryTime = initialRetryTime; - - var connectSettings = { - minInterval: 7.5, - maxInterval: 1500 - }; - - function waitingPromise(timeout) { - return new Promise(function(resolve){ - log("Start waiting for " + timeout); - setTimeout(()=>{ - log("Done waiting for " + timeout); - resolve(); - }, timeout); - }); - } - - if (settings.enabled){ - Bangle.isBTHRMOn = function(){ - return (Bangle._PWR && Bangle._PWR.BTHRM && Bangle._PWR.BTHRM.length > 0); - }; - - Bangle.isBTHRMConnected = function(){ - return gatt && gatt.connected; - }; - } - - - if (settings.replace){ - var origIsHRMOn = Bangle.isHRMOn; - - Bangle.isHRMOn = function() { - if (settings.enabled && !settings.replace){ - return origIsHRMOn(); - } else if (settings.enabled && settings.replace){ - return Bangle.isBTHRMOn(); - } - return origIsHRMOn() || Bangle.isBTHRMOn(); - }; - } - - function clearRetryTimeout(){ - if (currentRetryTimeout){ - log("Clearing timeout " + currentRetryTimeout); - clearTimeout(currentRetryTimeout); - currentRetryTimeout = undefined; - } - } - - function retry(){ - log("Retry"); - - if (!currentRetryTimeout){ - - var clampedTime = retryTime < 100 ? 100 : retryTime; - - log("Set timeout for retry as " + clampedTime); - clearRetryTimeout(); - currentRetryTimeout = setTimeout(() => { - log("Retrying"); - currentRetryTimeout = undefined; - initBt(); - }, clampedTime); - - retryTime = Math.pow(retryTime, 1.1); - if (retryTime > maxRetryTime){ - retryTime = maxRetryTime; - } - } else { - log("Already in retry..."); - } - } - - var buzzing = false; - function onDisconnect(reason) { - log("Disconnect: " + reason); - log("GATT: ", gatt); - log("Characteristics: ", characteristics); - retryTime = initialRetryTime; - clearRetryTimeout(); - switchInternalHrm(); - blockInit = false; - if (settings.warnDisconnect && !buzzing){ - buzzing = true; - Bangle.buzz(500,0.3).then(()=>waitingPromise(4500)).then(()=>{buzzing = false;}); - } - if (Bangle.isBTHRMOn()){ - retry(); - } - } - - function createCharacteristicPromise(newCharacteristic){ - log("Create characteristic promise: ", newCharacteristic); - var result = Promise.resolve(); - if (newCharacteristic.properties.notify){ - result = result.then(()=>{ - log("Starting notifications for: ", newCharacteristic); - var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started for ", newCharacteristic)); - if (settings.gracePeriodNotification > 0){ - log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications"); - startPromise = startPromise.then(()=>{ - log("Wait after connect"); - waitingPromise(settings.gracePeriodNotification) - }); - } - return startPromise; - }); - } else if (newCharacteristic.read){ - result = result.then(()=>{ - readData(newCharacteristic); - log("Reading data for " + newCharacteristic); - return newCharacteristic.read().then((data)=>{ - supportedCharacteristics[newCharacteristic.uuid].handler(data); - }); - }); - } - return result.then(()=>log("Handled characteristic: ", newCharacteristic)); - } - - function attachCharacteristicPromise(promise, characteristic){ - return promise.then(()=>{ - log("Handling characteristic:", characteristic); - return createCharacteristicPromise(characteristic); - }); - } - - function createCharacteristicsPromise(newCharacteristics){ - log("Create characteristics promise: ", newCharacteristics); - var result = Promise.resolve(); - for (var c of newCharacteristics){ - if (!supportedCharacteristics[c.uuid]) continue; - log("Supporting characteristic: ", c); - characteristics.push(c); - if (c.properties.notify){ - addNotificationHandler(c); - } - - result = attachCharacteristicPromise(result, c); - } - return result.then(()=>log("Handled characteristics")); - } - - function createServicePromise(service){ - log("Create service promise: ", service); - var result = Promise.resolve(); - result = result.then(()=>{ - log("Handling service: " + service.uuid); - return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c)); - }); - return result.then(()=>log("Handled service" + service.uuid)); - } - - function attachServicePromise(promise, service){ - return promise.then(()=>createServicePromise(service)); - } - - var reUseCounter = 0; - - function initBt() { - log("initBt with blockInit: " + blockInit); - if (blockInit){ - retry(); - return; - } - - blockInit = true; - - if (reUseCounter > 10){ - log("Reuse counter to high"); - gatt=undefined; - reUseCounter = 0; - } - - var promise; - - if (!device){ - promise = NRF.requestDevice({ filters: serviceFilters }); - - if (settings.gracePeriodRequest){ - log("Add " + settings.gracePeriodRequest + "ms grace period after request"); - } - - promise = promise.then((d)=>{ - log("Got device: ", d); - d.on('gattserverdisconnected', onDisconnect); - device = d; - }); - - promise = promise.then(()=>{ - log("Wait after request"); - return waitingPromise(settings.gracePeriodRequest); - }); - - } else { - promise = Promise.resolve(); - log("Reuse device: ", device); - } - - promise = promise.then(()=>{ - if (gatt){ - log("Reuse GATT: ", gatt); - } else { - log("GATT is new: ", gatt); - characteristics = []; - var cachedName = getCache().name; - if (device.name != cachedName){ - log("Device name changed from " + cachedName + " to " + device.name + ", clearing cache"); - clearCache(); - } - var newCache = getCache(); - newCache.name = device.name; - writeCache(newCache); - gatt = device.gatt; - } - - return Promise.resolve(gatt); - }); - - promise = promise.then((gatt)=>{ - if (!gatt.connected){ - var connectPromise = gatt.connect(connectSettings); - if (settings.gracePeriodConnect > 0){ - log("Add " + settings.gracePeriodConnect + "ms grace period after connecting"); - connectPromise = connectPromise.then(()=>{ - log("Wait after connect"); - return waitingPromise(settings.gracePeriodConnect); - }); - } - return connectPromise; - } else { - return Promise.resolve(); - } - }); - - promise = promise.then(()=>{ - if (!characteristics || characteristics.length == 0){ - characteristics = characteristicsFromCache(); - } - }); - - promise = promise.then(()=>{ - var characteristicsPromise = Promise.resolve(); - if (characteristics.length == 0){ - characteristicsPromise = characteristicsPromise.then(()=>{ - log("Getting services"); - return gatt.getPrimaryServices(); - }); - - characteristicsPromise = characteristicsPromise.then((services)=>{ - log("Got services:", services); - var result = Promise.resolve(); - for (var service of services){ - if (!(supportedServices.includes(service.uuid))) continue; - log("Supporting service: ", service.uuid); - result = attachServicePromise(result, service); - } - if (settings.gracePeriodService > 0) { - log("Add " + settings.gracePeriodService + "ms grace period after services"); - result = result.then(()=>{ - log("Wait after services"); - return waitingPromise(settings.gracePeriodService) - }); - } - return result; - }); - - } else { - for (var characteristic of characteristics){ - characteristicsPromise = attachCharacteristicPromise(characteristicsPromise, characteristic, true); - } - } - - return characteristicsPromise; - }); - - promise = promise.then(()=>{ - log("Connection established, waiting for notifications"); - reUseCounter = 0; - characteristicsToCache(characteristics); - clearRetryTimeout(); - }).catch((e) => { - characteristics = []; - log("Error:", e); - onDisconnect(e); - }); - } - - Bangle.setBTHRMPower = function(isOn, app) { - // Do app power handling - if (!app) app="?"; - if (Bangle._PWR===undefined) Bangle._PWR={}; - if (Bangle._PWR.BTHRM===undefined) Bangle._PWR.BTHRM=[]; - if (isOn && !Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM.push(app); - if (!isOn && Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM = Bangle._PWR.BTHRM.filter(a=>a!=app); - isOn = Bangle._PWR.BTHRM.length; - // so now we know if we're really on - if (isOn) { - if (!Bangle.isBTHRMConnected()) initBt(); - } else { // not on - log("Power off for " + app); - if (gatt) { - if (gatt.connected){ - log("Disconnect with gatt: ", gatt); - gatt.disconnect().then(()=>{ - log("Successful disconnect"); - }).catch((e)=>{ - log("Error during disconnect", e); - }); - } - } - } - }; - - var origSetHRMPower = Bangle.setHRMPower; - - if (settings.startWithHrm){ - - Bangle.setHRMPower = function(isOn, app) { - log("setHRMPower for " + app + ": " + (isOn?"on":"off")); - if (settings.enabled){ - Bangle.setBTHRMPower(isOn, app); - } - if ((settings.enabled && !settings.replace) || !settings.enabled){ - origSetHRMPower(isOn, app); - } - }; - } - - - var fallbackInterval; - - function switchInternalHrm(){ - if (settings.allowFallback && !fallbackInterval){ - log("Fallback to HRM enabled"); - origSetHRMPower(1, "bthrm_fallback"); - fallbackInterval = setInterval(()=>{ - if (Bangle.isBTHRMConnected()){ - origSetHRMPower(0, "bthrm_fallback"); - clearInterval(fallbackInterval); - fallbackInterval = undefined; - log("Fallback to HRM disabled"); - } - }, settings.fallbackTimeout); - } - } - - if (settings.replace){ - log("Replace HRM event"); - if (Bangle._PWR && Bangle._PWR.HRM){ - for (var i = 0; i < Bangle._PWR.HRM.length; i++){ - var app = Bangle._PWR.HRM[i]; - log("Moving app " + app); - origSetHRMPower(0, app); - Bangle.setBTHRMPower(1, app); - if (Bangle._PWR.HRM===undefined) break; - } - } - switchInternalHrm(); - } - - E.on("kill", ()=>{ - if (gatt && gatt.connected){ - log("Got killed, trying to disconnect"); - var promise = gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect on kill", e)); - } - }); - } -})(); +if ((require('Storage').readJSON("bthrm.json", true) || {}).enabled != false) require("bthrm").enable(); diff --git a/apps/bthrm/bthrm.js b/apps/bthrm/bthrm.js index cc533eedd..b07e7bd37 100644 --- a/apps/bthrm/bthrm.js +++ b/apps/bthrm/bthrm.js @@ -1,80 +1,155 @@ -var btm = g.getHeight()-1; -var intervalInt; -var intervalBt; +const BPM_FONT_SIZE="19%"; +const VALUE_TIMEOUT=3000; -function clear(y){ - g.reset(); - g.clearRect(0,y,g.getWidth(),y+75); +var BODY_LOCS = { + 0: 'Other', + 1: 'Chest', + 2: 'Wrist', + 3: 'Finger', + 4: 'Hand', + 5: 'Earlobe', + 6: 'Foot', +}; + +var Layout = require("Layout"); + +function border(l,c) { + g.setColor(c).drawLine(l.x+l.w*0.05, l.y-4, l.x+l.w*0.95, l.y-4); } -function draw(y, type, event) { - clear(y); - var px = g.getWidth()/2; - var str = event.bpm + ""; - g.reset(); - g.setFontAlign(0,0); - g.setFontVector(40).drawString(str,px,y+20); - str = "Event: " + type; - if (type == "HRM") { - str += " Confidence: " + event.confidence; - g.setFontVector(12).drawString(str,px,y+40); - str = " Source: " + (event.src ? event.src : "internal"); - g.setFontVector(12).drawString(str,px,y+50); +function getRow(id, text, additionalInfo){ + let additional = []; + let l = { + type:"h", c: [ + { + type:"v", + width: g.getWidth()*0.4, + c: [ + {type:"txt", halign:1, font:"8%", label:text, id:id+"text" }, + {type:"txt", halign:1, font:BPM_FONT_SIZE, label:"--", id:id, bgCol: g.theme.bg } + ] + },{ + type:undefined, fillx:1 + },{ + type:"v", + valign: -1, + width: g.getWidth()*0.45, + c: additional + },{ + type:undefined, width:g.getWidth()*0.05 + } + ] + }; + for (let i of additionalInfo){ + let label = {type:"txt", font:"6x8", label:i + ":" }; + let value = {type:"txt", font:"6x8", label:"--", id:id + i }; + additional.push({type:"h", halign:-1, c:[ label, {type:undefined, fillx:1}, value ]}); } - if (type == "BTHRM"){ - if (event.battery) str += " Bat: " + (event.battery ? event.battery : ""); - g.setFontVector(12).drawString(str,px,y+40); - str= ""; - if (event.location) str += "Loc: " + event.location.toFixed(0) + "ms"; - if (event.rr && event.rr.length > 0) str += " RR: " + event.rr.join(","); - g.setFontVector(12).drawString(str,px,y+50); - str= ""; - if (event.contact) str += " Contact: " + event.contact; - if (event.energy) str += " kJoule: " + event.energy.toFixed(0); - g.setFontVector(12).drawString(str,px,y+60); - } - + + return l; } -var firstEventBt = true; -var firstEventInt = true; +var layout = new Layout( { + type:"v", c: [ + getRow("int", "INT", ["Confidence"]), + getRow("agg", "HRM", ["Confidence", "Source"]), + getRow("bt", "BT", ["Battery","Location","Contact", "RR", "Energy"]), + { type:undefined, height:8 } //dummy to protect debug output + ] +}, { + lazy:true +}); + +var int,agg,bt; +var firstEvent = true; + +function draw(){ + if (!(int || agg || bt)) return; + + if (firstEvent) { + g.clearRect(Bangle.appRect); + firstEvent = false; + } + + let now = Date.now(); + + if (int && int.time > (now - VALUE_TIMEOUT)){ + layout.int.label = int.bpm; + if (!isNaN(int.confidence)) layout.intConfidence.label = int.confidence; + } else { + layout.int.label = "--"; + layout.intConfidence.label = "--"; + } + + if (agg && agg.time > (now - VALUE_TIMEOUT)){ + layout.agg.label = agg.bpm; + if (!isNaN(agg.confidence)) layout.aggConfidence.label = agg.confidence; + if (agg.src) layout.aggSource.label = agg.src; + } else { + layout.agg.label = "--"; + layout.aggConfidence.label = "--"; + layout.aggSource.label = "--"; + } + + if (bt && bt.time > (now - VALUE_TIMEOUT)) { + layout.bt.label = bt.bpm; + if (!isNaN(bt.battery)) layout.btBattery.label = bt.battery + "%"; + if (bt.rr) layout.btRR.label = bt.rr.join(","); + if (!isNaN(bt.location)) layout.btLocation.label = BODY_LOCS[bt.location]; + if (bt.contact !== undefined) layout.btContact.label = bt.contact ? "Yes":"No"; + if (!isNaN(bt.energy)) layout.btEnergy.label = bt.energy.toFixed(0) + "kJ"; + } else { + layout.bt.label = "--"; + layout.btBattery.label = "--"; + layout.btRR.label = "--"; + layout.btLocation.label = "--"; + layout.btContact.label = "--"; + layout.btEnergy.label = "--"; + } + + layout.update(); + layout.render(); + let first = true; + for (let c of layout.l.c){ + if (first) { + first = false; + continue; + } + if (c.type && c.type == "h") + border(c,g.theme.fg); + } +} + + +// This can get called for the boot code to show what's happening +function showStatusInfo(txt) { + var R = Bangle.appRect; + g.reset().clearRect(R.x,R.y2-8,R.x2,R.y2).setFont("6x8"); + txt = g.wrapString(txt, R.w)[0]; + g.setFontAlign(0,1).drawString(txt, (R.x+R.x2)/2, R.y2); +} function onBtHrm(e) { - if (firstEventBt){ - clear(24); - firstEventBt = false; - } - draw(100, "BTHRM", e); - if (e.bpm == 0){ - Bangle.buzz(100,0.2); - } - if (intervalBt){ - clearInterval(intervalBt); - } - intervalBt = setInterval(()=>{ - clear(100); - }, 2000); + bt = e; + bt.time = Date.now(); } -function onHrm(e) { - if (firstEventInt){ - clear(24); - firstEventInt = false; - } - draw(24, "HRM", e); - if (intervalInt){ - clearInterval(intervalInt); - } - intervalInt = setInterval(()=>{ - clear(24); - }, 2000); +function onInt(e) { + int = e; + int.time = Date.now(); } +function onAgg(e) { + agg = e; + agg.time = Date.now(); +} var settings = require('Storage').readJSON("bthrm.json", true) || {}; Bangle.on('BTHRM', onBtHrm); -Bangle.on('HRM', onHrm); +Bangle.on('HRM_int', onInt); +Bangle.on('HRM', onAgg); + Bangle.setHRMPower(1,'bthrm'); if (!(settings.startWithHrm)){ @@ -86,10 +161,11 @@ Bangle.loadWidgets(); Bangle.drawWidgets(); if (Bangle.setBTHRMPower){ g.reset().setFont("6x8",2).setFontAlign(0,0); - g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 24); + g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2); + setInterval(draw, 1000); } else { g.reset().setFont("6x8",2).setFontAlign(0,0); - g.drawString("BTHRM disabled",g.getWidth()/2,g.getHeight()/2 + 32); + g.drawString("BTHRM disabled",g.getWidth()/2,g.getHeight()/2); } E.on('kill', ()=>Bangle.setBTHRMPower(0,'bthrm')); diff --git a/apps/bthrm/default.json b/apps/bthrm/default.json index 64e638b8a..79605b412 100644 --- a/apps/bthrm/default.json +++ b/apps/bthrm/default.json @@ -7,14 +7,16 @@ "allowFallback": true, "warnDisconnect": false, "fallbackTimeout": 10, - "custom_replace": false, + "custom_replace": true, "custom_debuglog": false, - "custom_startWithHrm": false, - "custom_allowFallback": false, + "custom_startWithHrm": true, + "custom_allowFallback": true, "custom_warnDisconnect": false, "custom_fallbackTimeout": 10, "gracePeriodNotification": 0, "gracePeriodConnect": 0, "gracePeriodService": 0, - "gracePeriodRequest": 0 + "gracePeriodRequest": 0, + "bonding": false, + "active": true } diff --git a/apps/bthrm/lib.js b/apps/bthrm/lib.js new file mode 100644 index 000000000..a792167ca --- /dev/null +++ b/apps/bthrm/lib.js @@ -0,0 +1,664 @@ +exports.enable = () => { + var settings = Object.assign( + require('Storage').readJSON("bthrm.default.json", true) || {}, + require('Storage').readJSON("bthrm.json", true) || {} + ); + + var log = function(text, param){ + if (global.showStatusInfo) + showStatusInfo(text); + if (settings.debuglog){ + var logline = new Date().toISOString() + " - " + text; + if (param) logline += ": " + JSON.stringify(param); + print(logline); + } + }; + + log("Settings: ", settings); + + if (settings.enabled){ + + var clearCache = function() { + return require('Storage').erase("bthrm.cache.json"); + }; + + var getCache = function() { + var cache = require('Storage').readJSON("bthrm.cache.json", true) || {}; + if (settings.btid && settings.btid === cache.id) return cache; + clearCache(); + return {}; + }; + + var addNotificationHandler = function(characteristic) { + log("Setting notification handler"/*supportedCharacteristics[characteristic.uuid].handler*/); + characteristic.on('characteristicvaluechanged', (ev) => supportedCharacteristics[characteristic.uuid].handler(ev.target.value)); + }; + + var writeCache = function(cache) { + var oldCache = getCache(); + if (oldCache !== cache) { + log("Writing cache"); + require('Storage').writeJSON("bthrm.cache.json", cache); + } else { + log("No changes, don't write cache"); + } + }; + + var characteristicsToCache = function(characteristics) { + log("Cache characteristics"); + var cache = getCache(); + if (!cache.characteristics) cache.characteristics = {}; + for (var c of characteristics){ + //"handle_value":16,"handle_decl":15 + log("Saving handle " + c.handle_value + " for characteristic: ", c); + cache.characteristics[c.uuid] = { + "handle": c.handle_value, + "uuid": c.uuid, + "notify": c.properties.notify, + "read": c.properties.read + }; + } + writeCache(cache); + }; + + var characteristicsFromCache = function(device) { + var service = { device : device }; // fake a BluetoothRemoteGATTService + log("Read cached characteristics"); + var cache = getCache(); + if (!cache.characteristics) return []; + var restored = []; + for (var c in cache.characteristics){ + var cached = cache.characteristics[c]; + var r = new BluetoothRemoteGATTCharacteristic(); + log("Restoring characteristic ", cached); + r.handle_value = cached.handle; + r.uuid = cached.uuid; + r.properties = {}; + r.properties.notify = cached.notify; + r.properties.read = cached.read; + r.service = service; + addNotificationHandler(r); + log("Restored characteristic: ", r); + restored.push(r); + } + return restored; + }; + + log("Start"); + + var lastReceivedData={ + }; + + var supportedServices = [ + "0x180d", // Heart Rate + "0x180f", // Battery + ]; + + var bpmTimeout; + + var supportedCharacteristics = { + "0x2a37": { + //Heart rate measurement + active: false, + handler: function (dv){ + var flags = dv.getUint8(0); + + var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit + supportedCharacteristics["0x2a37"].active = bpm > 0; + log("BTHRM BPM " + supportedCharacteristics["0x2a37"].active); + switchFallback(); + if (bpmTimeout) clearTimeout(bpmTimeout); + bpmTimeout = setTimeout(()=>{ + bpmTimeout = undefined; + supportedCharacteristics["0x2a37"].active = false; + startFallback(); + }, 3000); + + var sensorContact; + + if (flags & 2){ + sensorContact = !!(flags & 4); + } + + var idx = 2 + (flags&1); + + var energyExpended; + if (flags & 8){ + energyExpended = dv.getUint16(idx,1); + idx += 2; + } + var interval; + if (flags & 16) { + interval = []; + var maxIntervalBytes = (dv.byteLength - idx); + log("Found " + (maxIntervalBytes / 2) + " rr data fields"); + for(var i = 0 ; i < maxIntervalBytes / 2; i++){ + interval[i] = dv.getUint16(idx,1); // in milliseconds + idx += 2; + } + } + + var location; + if (lastReceivedData && lastReceivedData["0x180d"] && lastReceivedData["0x180d"]["0x2a38"]){ + location = lastReceivedData["0x180d"]["0x2a38"]; + } + + var battery; + if (lastReceivedData && lastReceivedData["0x180f"] && lastReceivedData["0x180f"]["0x2a19"]){ + battery = lastReceivedData["0x180f"]["0x2a19"]; + } + + if (settings.replace && bpm > 0){ + var repEvent = { + bpm: bpm, + confidence: (sensorContact || sensorContact === undefined)? 100 : 0, + src: "bthrm" + }; + + log("Emitting HRM_R(bt)", repEvent); + Bangle.emit("HRM_R", repEvent); + } + + var newEvent = { + bpm: bpm + }; + + if (location) newEvent.location = location; + if (interval) newEvent.rr = interval; + if (energyExpended) newEvent.energy = energyExpended; + if (battery) newEvent.battery = battery; + if (sensorContact) newEvent.contact = sensorContact; + + log("Emitting BTHRM", newEvent); + Bangle.emit("BTHRM", newEvent); + } + }, + "0x2a38": { + //Body sensor location + handler: function(dv){ + if (!lastReceivedData["0x180d"]) lastReceivedData["0x180d"] = {}; + lastReceivedData["0x180d"]["0x2a38"] = parseInt(dv.buffer, 10); + } + }, + "0x2a19": { + //Battery + handler: function (dv){ + if (!lastReceivedData["0x180f"]) lastReceivedData["0x180f"] = {}; + lastReceivedData["0x180f"]["0x2a19"] = dv.getUint8(0); + } + } + }; + + var device; + var gatt; + var characteristics = []; + var blockInit = false; + var currentRetryTimeout; + var initialRetryTime = 40; + var maxRetryTime = 60000; + var retryTime = initialRetryTime; + + var connectSettings = { + minInterval: 7.5, + maxInterval: 1500 + }; + + var waitingPromise = function(timeout) { + return new Promise(function(resolve){ + log("Start waiting for " + timeout); + setTimeout(()=>{ + log("Done waiting for " + timeout); + resolve(); + }, timeout); + }); + }; + + if (settings.enabled){ + Bangle.isBTHRMActive = function (){ + return supportedCharacteristics["0x2a37"].active; + }; + + Bangle.isBTHRMOn = function(){ + return (Bangle._PWR && Bangle._PWR.BTHRM && Bangle._PWR.BTHRM.length > 0); + }; + + Bangle.isBTHRMConnected = function(){ + return gatt && gatt.connected; + }; + } + + if (settings.replace){ + Bangle.origIsHRMOn = Bangle.isHRMOn; + + Bangle.isHRMOn = function() { + if (settings.enabled && !settings.replace){ + return Bangle.origIsHRMOn(); + } else if (settings.enabled && settings.replace){ + return Bangle.isBTHRMOn(); + } + return Bangle.origIsHRMOn() || Bangle.isBTHRMOn(); + }; + } + + var clearRetryTimeout = function(resetTime) { + if (currentRetryTimeout){ + log("Clearing timeout " + currentRetryTimeout); + clearTimeout(currentRetryTimeout); + currentRetryTimeout = undefined; + } + if (resetTime) { + log("Resetting retry time"); + retryTime = initialRetryTime; + } + }; + + var retry = function() { + log("Retry"); + + if (!currentRetryTimeout && !powerdownRequested){ + + var clampedTime = retryTime < 100 ? 100 : retryTime; + + log("Set timeout for retry as " + clampedTime); + clearRetryTimeout(); + currentRetryTimeout = setTimeout(() => { + log("Retrying"); + currentRetryTimeout = undefined; + initBt(); + }, clampedTime); + + retryTime = Math.pow(clampedTime, 1.1); + if (retryTime > maxRetryTime){ + retryTime = maxRetryTime; + } + } else { + log("Already in retry..."); + } + }; + + var buzzing = false; + var onDisconnect = function(reason) { + log("Disconnect: " + reason); + log("GATT", gatt); + log("Characteristics", characteristics); + + var retryTimeResetNeeded = true; + retryTimeResetNeeded &= reason != "Connection Timeout"; + retryTimeResetNeeded &= reason != "No device found matching filters"; + clearRetryTimeout(retryTimeResetNeeded); + supportedCharacteristics["0x2a37"].active = false; + if (!powerdownRequested) startFallback(); + blockInit = false; + if (settings.warnDisconnect && !buzzing){ + buzzing = true; + Bangle.buzz(500,0.3).then(()=>waitingPromise(4500)).then(()=>{buzzing = false;}); + } + if (Bangle.isBTHRMOn()){ + retry(); + } + }; + + var createCharacteristicPromise = function(newCharacteristic) { + log("Create characteristic promise", newCharacteristic); + var result = Promise.resolve(); + // For values that can be read, go ahead and read them, even if we might be notified in the future + // Allows for getting initial state of infrequently updating characteristics, like battery + if (newCharacteristic.readValue){ + result = result.then(()=>{ + log("Reading data", newCharacteristic); + return newCharacteristic.readValue().then((data)=>{ + if (supportedCharacteristics[newCharacteristic.uuid] && supportedCharacteristics[newCharacteristic.uuid].handler) { + supportedCharacteristics[newCharacteristic.uuid].handler(data); + } + }); + }); + } + if (newCharacteristic.properties.notify){ + result = result.then(()=>{ + log("Starting notifications", newCharacteristic); + var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic)); + + log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications"); + startPromise = startPromise.then(()=>{ + log("Wait after connect"); + return waitingPromise(settings.gracePeriodNotification); + }); + + return startPromise; + }); + } + return result.then(()=>log("Handled characteristic", newCharacteristic)); + }; + + var attachCharacteristicPromise = function(promise, characteristic) { + return promise.then(()=>{ + log("Handling characteristic:", characteristic); + return createCharacteristicPromise(characteristic); + }); + }; + + var createCharacteristicsPromise = function(newCharacteristics) { + log("Create characteristics promis ", newCharacteristics); + var result = Promise.resolve(); + for (var c of newCharacteristics){ + if (!supportedCharacteristics[c.uuid]) continue; + log("Supporting characteristic", c); + characteristics.push(c); + if (c.properties.notify){ + addNotificationHandler(c); + } + + result = attachCharacteristicPromise(result, c); + } + return result.then(()=>log("Handled characteristics")); + }; + + var createServicePromise = function(service) { + log("Create service promise", service); + var result = Promise.resolve(); + result = result.then(()=>{ + log("Handling service" + service.uuid); + return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c)); + }); + return result.then(()=>log("Handled service" + service.uuid)); + }; + + var attachServicePromise = function(promise, service) { + return promise.then(()=>createServicePromise(service)); + }; + + var initBt = function () { + log("initBt with blockInit: " + blockInit); + if (blockInit && !powerdownRequested){ + retry(); + return; + } + + blockInit = true; + + var promise; + var filters; + + if (!device){ + if (settings.btid){ + log("Configured device id", settings.btid); + filters = [{ id: settings.btid }]; + } else { + return; + } + log("Requesting device with filters", filters); + try { + promise = NRF.requestDevice({ filters: filters, active: settings.active }); + } catch (e){ + log("Error during initial request:", e); + onDisconnect(e); + return; + } + + if (settings.gracePeriodRequest){ + log("Add " + settings.gracePeriodRequest + "ms grace period after request"); + } + + promise = promise.then((d)=>{ + log("Got device", d); + d.on('gattserverdisconnected', onDisconnect); + device = d; + }); + + promise = promise.then(()=>{ + log("Wait after request"); + return waitingPromise(settings.gracePeriodRequest); + }); + } else { + promise = Promise.resolve(); + log("Reuse device", device); + } + + promise = promise.then(()=>{ + if (gatt){ + log("Reuse GATT", gatt); + } else { + log("GATT is new", gatt); + characteristics = []; + var cachedId = getCache().id; + if (device.id !== cachedId){ + log("Device ID changed from " + cachedId + " to " + device.id + ", clearing cache"); + clearCache(); + } + var newCache = getCache(); + newCache.id = device.id; + writeCache(newCache); + gatt = device.gatt; + } + + return Promise.resolve(gatt); + }); + + promise = promise.then((gatt)=>{ + if (!gatt.connected){ + log("Connecting..."); + var connectPromise = gatt.connect(connectSettings).then(function() { + log("Connected."); + }); + log("Add " + settings.gracePeriodConnect + "ms grace period after connecting"); + connectPromise = connectPromise.then(()=>{ + log("Wait after connect"); + return waitingPromise(settings.gracePeriodConnect); + }); + return connectPromise; + } else { + return Promise.resolve(); + } + }); + + if (settings.bonding){ + promise = promise.then(() => { + log(JSON.stringify(gatt.getSecurityStatus())); + if (gatt.getSecurityStatus()['bonded']) { + log("Already bonded"); + return Promise.resolve(); + } else { + log("Start bonding"); + return gatt.startBonding() + .then(() => log("Security status" + gatt.getSecurityStatus())); + } + }); + } + + promise = promise.then(()=>{ + if (!characteristics || characteristics.length === 0){ + characteristics = characteristicsFromCache(device); + } + }); + + promise = promise.then(()=>{ + var characteristicsPromise = Promise.resolve(); + if (characteristics.length === 0){ + characteristicsPromise = characteristicsPromise.then(()=>{ + log("Getting services"); + return gatt.getPrimaryServices(); + }); + + characteristicsPromise = characteristicsPromise.then((services)=>{ + log("Got services", services); + var result = Promise.resolve(); + for (var service of services){ + if (!(supportedServices.includes(service.uuid))) continue; + log("Supporting service", service.uuid); + result = attachServicePromise(result, service); + } + log("Add " + settings.gracePeriodService + "ms grace period after services"); + result = result.then(()=>{ + log("Wait after services"); + return waitingPromise(settings.gracePeriodService); + }); + return result; + }); + } else { + for (var characteristic of characteristics){ + characteristicsPromise = attachCharacteristicPromise(characteristicsPromise, characteristic, true); + } + } + + return characteristicsPromise; + }); + + return promise.then(()=>{ + log("Connection established, waiting for notifications"); + characteristicsToCache(characteristics); + clearRetryTimeout(true); + }).catch((e) => { + characteristics = []; + log("Error:", e); + onDisconnect(e); + }); + }; + + var powerdownRequested = false; + + Bangle.setBTHRMPower = function(isOn, app) { + // Do app power handling + if (!app) app="?"; + if (Bangle._PWR===undefined) Bangle._PWR={}; + if (Bangle._PWR.BTHRM===undefined) Bangle._PWR.BTHRM=[]; + if (isOn && !Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM.push(app); + if (!isOn && Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM = Bangle._PWR.BTHRM.filter(a=>a!==app); + isOn = Bangle._PWR.BTHRM.length; + // so now we know if we're really on + if (isOn) { + powerdownRequested = false; + switchFallback(); + if (!Bangle.isBTHRMConnected()) initBt(); + } else { // not on + log("Power off for " + app); + powerdownRequested = true; + clearRetryTimeout(true); + stopFallback(); + if (gatt) { + if (gatt.connected){ + log("Disconnect with gatt", gatt); + try{ + gatt.disconnect().then(()=>{ + log("Successful disconnect"); + }).catch((e)=>{ + log("Error during disconnect promise", e); + }); + } catch (e){ + log("Error during disconnect attempt", e); + } + } + } + } + }; + + if (settings.replace){ + // register a listener for original HRM events and emit as HRM_int + Bangle.on("HRM", (o) => { + let e = Object.assign({},o); + log("Emitting HRM_int", e); + Bangle.emit("HRM_int", e); + if (fallbackActive){ + // if fallback to internal HRM is active, emit as HRM_R to which everyone listens + o.src = "int"; + log("Emitting HRM_R(int)", o); + Bangle.emit("HRM_R", o); + } + }); + + // force all apps wanting to listen to HRM to actually get events for HRM_R + Bangle.on = ( o => (name, cb) => { + o = o.bind(Bangle); + if (name == "HRM") o("HRM_R", cb); + else o(name, cb); + })(Bangle.on); + + Bangle.removeListener = ( o => (name, cb) => { + o = o.bind(Bangle); + if (name == "HRM") o("HRM_R", cb); + else o(name, cb); + })(Bangle.removeListener); + } else { + Bangle.on("HRM", (o)=>{ + o.src = "int"; + let e = Object.assign({},o); + log("Emitting HRM_int", e); + Bangle.emit("HRM_int", e); + }); + } + + Bangle.origSetHRMPower = Bangle.setHRMPower; + + if (settings.startWithHrm){ + Bangle.setHRMPower = function(isOn, app) { + log("setHRMPower for " + app + ": " + (isOn?"on":"off")); + if (settings.enabled){ + Bangle.setBTHRMPower(isOn, app); + if (Bangle._PWR && Bangle._PWR.HRM && Object.keys(Bangle._PWR.HRM).length == 0) { + Bangle._PWR.BTHRM = []; + Bangle.setBTHRMPower(0); + if (!isOn) stopFallback(); + } + } + if ((settings.enabled && !settings.replace) || !settings.enabled){ + Bangle.origSetHRMPower(isOn, app); + } + }; + } + + var fallbackActive = false; + var inSwitch = false; + + var stopFallback = function(){ + if (fallbackActive){ + Bangle.origSetHRMPower(0, "bthrm_fallback"); + fallbackActive = false; + log("Fallback to HRM disabled"); + } + }; + + var startFallback = function(){ + if (!fallbackActive && settings.allowFallback) { + fallbackActive = true; + Bangle.origSetHRMPower(1, "bthrm_fallback"); + log("Fallback to HRM enabled"); + } + }; + + var switchFallback = function() { + log("Check falling back to HRM"); + if (!inSwitch){ + inSwitch = true; + if (Bangle.isBTHRMActive()){ + stopFallback(); + } else { + startFallback(); + } + } + inSwitch = false; + }; + + if (settings.replace){ + log("Replace HRM event"); + if (Bangle._PWR && Bangle._PWR.HRM){ + for (var i = 0; i < Bangle._PWR.HRM.length; i++){ + var app = Bangle._PWR.HRM[i]; + log("Moving app " + app); + Bangle.origSetHRMPower(0, app); + Bangle.setBTHRMPower(1, app); + if (Bangle._PWR.HRM===undefined) break; + } + } + } + + E.on("kill", ()=>{ + if (gatt && gatt.connected){ + log("Got killed, trying to disconnect"); + try { + gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect promise on kill", e)); + } catch (e) { + log("Error during disconnnect on kill", e) + } + } + }); + } +}; diff --git a/apps/bthrm/metadata.json b/apps/bthrm/metadata.json index 1c21269e2..fea274ff3 100644 --- a/apps/bthrm/metadata.json +++ b/apps/bthrm/metadata.json @@ -2,11 +2,12 @@ "id": "bthrm", "name": "Bluetooth Heart Rate Monitor", "shortName": "BT HRM", - "version": "0.07", + "version": "0.17", "description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.", "icon": "app.png", + "screenshots": [{"url":"screen.png"}], "type": "app", - "tags": "health,bluetooth", + "tags": "health,bluetooth,hrm,bthrm", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ @@ -15,6 +16,7 @@ {"name":"bthrm.0.boot.js","url":"boot.js"}, {"name":"bthrm.img","url":"app-icon.js","evaluate":true}, {"name":"bthrm.settings.js","url":"settings.js"}, + {"name":"bthrm","url":"lib.js"}, {"name":"bthrm.default.json","url":"default.json"} ] } diff --git a/apps/bthrm/recorder.js b/apps/bthrm/recorder.js index 21345a907..fcfed47c3 100644 --- a/apps/bthrm/recorder.js +++ b/apps/bthrm/recorder.js @@ -32,8 +32,42 @@ Bangle.removeListener('BTHRM', onHRM); if (Bangle.setBTRHMPower) Bangle.setBTHRMPower(0,"recorder"); }, - draw : (x,y) => g.setColor((bpm != "")?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y) + draw : (x,y) => g.setColor((Bangle.isBTHRMActive && Bangle.isBTHRMActive())?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y) }; - } + }; + recorders.hrmint = function() { + var active = false; + var bpmTimeout; + var bpm = "", bpmConfidence = ""; + function onHRM(h) { + bpmConfidence = h.confidence; + bpm = h.bpm; + if (h.bpm > 0){ + active = true; + if (bpmTimeout) clearTimeout(bpmTimeout); + bpmTimeout = setTimeout(()=>{ + active = false; + },3000); + } + } + return { + name : "HR int", + fields : ["Int Heartrate", "Int Confidence"], + getValues : () => { + var r = [bpm,bpmConfidence]; + bpm = ""; bpmConfidence = ""; + return r; + }, + start : () => { + Bangle.on('HRM_int', onHRM); + if (Bangle.origSetHRMPower) Bangle.origSetHRMPower(1,"recorder"); + }, + stop : () => { + Bangle.removeListener('HRM_int', onHRM); + if (Bangle.origSetHRMPower) Bangle.origSetHRMPower(0,"recorder"); + }, + draw : (x,y) => g.setColor(( Bangle.origIsHRMOn && Bangle.origIsHRMOn() && active)?"#0f0":"#8f8").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y) + }; + }; }) diff --git a/apps/bthrm/screen.png b/apps/bthrm/screen.png new file mode 100644 index 000000000..6b6b85227 Binary files /dev/null and b/apps/bthrm/screen.png differ diff --git a/apps/bthrm/settings.js b/apps/bthrm/settings.js index beefb00e9..459ed29fc 100644 --- a/apps/bthrm/settings.js +++ b/apps/bthrm/settings.js @@ -5,105 +5,169 @@ require('Storage').writeJSON(FILE, s); readSettings(); } - + function readSettings(){ settings = Object.assign( require('Storage').readJSON("bthrm.default.json", true) || {}, require('Storage').readJSON(FILE, true) || {} ); } - + var FILE="bthrm.json"; var settings; readSettings(); - var mainmenu = { - '': { 'title': 'Bluetooth HRM' }, - '< Back': back, - 'Mode': { - value: 0 | settings.mode, - min: 0, - max: 3, - format: v => ["Off", "Default", "Both", "Custom"][v], - onchange: v => { - settings.mode = v; - switch (v){ - case 0: - writeSettings("enabled",false); - break; - case 1: - writeSettings("enabled",true); - writeSettings("replace",true); - writeSettings("debuglog",false); - writeSettings("startWithHrm",true); - writeSettings("allowFallback",true); - writeSettings("fallbackTimeout",10); - break; - case 2: - writeSettings("enabled",true); - writeSettings("replace",false); - writeSettings("debuglog",false); - writeSettings("startWithHrm",false); - writeSettings("allowFallback",false); - break; - case 3: - writeSettings("enabled",true); - writeSettings("replace",settings.custom_replace); - writeSettings("debuglog",settings.custom_debuglog); - writeSettings("startWithHrm",settings.custom_startWithHrm); - writeSettings("allowFallback",settings.custom_allowFallback); - writeSettings("fallbackTimeout",settings.custom_fallbackTimeout); - break; + function applyCustomSettings(){ + writeSettings("enabled",true); + writeSettings("replace",settings.custom_replace); + writeSettings("startWithHrm",settings.custom_startWithHrm); + writeSettings("allowFallback",settings.custom_allowFallback); + writeSettings("fallbackTimeout",settings.custom_fallbackTimeout); + } + + function buildMainMenu(){ + var mainmenu = { + '': { 'title': 'Bluetooth HRM' }, + '< Back': back, + 'Mode': { + value: 0 | settings.mode, + min: 0, + max: 3, + format: v => ["Off", "Default", "Both", "Custom"][v], + onchange: v => { + settings.mode = v; + switch (v){ + case 0: + writeSettings("enabled",false); + break; + case 1: + writeSettings("enabled",true); + writeSettings("replace",true); + writeSettings("startWithHrm",true); + writeSettings("allowFallback",true); + writeSettings("fallbackTimeout",10); + break; + case 2: + writeSettings("enabled",true); + writeSettings("replace",false); + writeSettings("startWithHrm",false); + writeSettings("allowFallback",false); + break; + case 3: + applyCustomSettings(); + break; + } + writeSettings("mode",v); } - writeSettings("mode",v); } - }, - 'Custom Mode': function() { E.showMenu(submenu_custom); }, - 'Debug': function() { E.showMenu(submenu_debug); } - }; - + }; + + if (settings.btname || settings.btid){ + var name = "Clear " + (settings.btname || settings.btid); + mainmenu[name] = function() { + E.showPrompt("Clear current device?").then((r)=>{ + if (r) { + writeSettings("btname",undefined); + writeSettings("btid",undefined); + } + E.showMenu(buildMainMenu()); + }); + }; + } + + mainmenu["BLE Scan"] = ()=> createMenuFromScan(); + mainmenu["Custom Mode"] = function() { E.showMenu(submenu_custom); }; + mainmenu.Debug = function() { E.showMenu(submenu_debug); }; + return mainmenu; + } + var submenu_debug = { '' : { title: "Debug"}, - '< Back': function() { E.showMenu(mainmenu); }, + '< Back': function() { E.showMenu(buildMainMenu()); }, 'Alert on disconnect': { value: !!settings.warnDisconnect, - format: v => settings.warnDisconnect ? "On" : "Off", onchange: v => { writeSettings("warnDisconnect",v); } }, 'Debug log': { value: !!settings.debuglog, - format: v => settings.debuglog ? "On" : "Off", onchange: v => { writeSettings("debuglog",v); } }, + 'Use bonding': { + value: !!settings.bonding, + onchange: v => { + writeSettings("bonding",v); + } + }, + 'Use active scanning': { + value: !!settings.active, + onchange: v => { + writeSettings("active",v); + } + }, 'Grace periods': function() { E.showMenu(submenu_grace); } }; - + + function createMenuFromScan(){ + E.showMenu(); + E.showMessage("Scanning for 4 seconds"); + + var submenu_scan = { + '< Back': function() { E.showMenu(buildMainMenu()); } + }; + NRF.findDevices(function(devices) { + submenu_scan[''] = { title: `Scan (${devices.length} found)`}; + if (devices.length === 0) { + E.showAlert("No devices found") + .then(() => E.showMenu(buildMainMenu())); + return; + } else { + devices.forEach((d) => { + print("Found device", d); + var shown = (d.name || d.id.substr(0, 17)); + submenu_scan[shown] = function () { + E.showPrompt("Set " + shown + "?").then((r) => { + if (r) { + writeSettings("btid", d.id); + // Store the name for displaying later. Will connect by ID + if (d.name) { + writeSettings("btname", d.name); + } + } + E.showMenu(buildMainMenu()); + }); + }; + }); + } + E.showMenu(submenu_scan); + }, { timeout: 4000, active: true, filters: [{services: [ "180d" ]}]}); + } + var submenu_custom = { '' : { title: "Custom mode"}, - '< Back': function() { E.showMenu(mainmenu); }, + '< Back': function() { E.showMenu(buildMainMenu()); }, 'Replace HRM': { value: !!settings.custom_replace, - format: v => settings.custom_replace ? "On" : "Off", onchange: v => { writeSettings("custom_replace",v); + if (settings.mode == 3) applyCustomSettings(); } }, 'Start w. HRM': { value: !!settings.custom_startWithHrm, - format: v => settings.custom_startWithHrm ? "On" : "Off", onchange: v => { writeSettings("custom_startWithHrm",v); + if (settings.mode == 3) applyCustomSettings(); } }, 'HRM Fallback': { value: !!settings.custom_allowFallback, - format: v => settings.custom_allowFallback ? "On" : "Off", onchange: v => { writeSettings("custom_allowFallback",v); + if (settings.mode == 3) applyCustomSettings(); } }, 'Fallback Timeout': { @@ -114,10 +178,11 @@ format: v=>v+"s", onchange: v => { writeSettings("custom_fallbackTimout",v*1000); + if (settings.mode == 3) applyCustomSettings(); } }, }; - + var submenu_grace = { '' : { title: "Grace periods"}, '< Back': function() { E.showMenu(submenu_debug); }, @@ -162,51 +227,6 @@ } } }; - - var submenu = { - '' : { title: "Grace periods"}, - '< Back': function() { E.showMenu(mainmenu); }, - 'Request': { - value: settings.gracePeriodRequest, - min: 0, - max: 3000, - step: 100, - format: v=>v+"ms", - onchange: v => { - writeSettings("gracePeriodRequest",v); - } - }, - 'Connect': { - value: settings.gracePeriodConnect, - min: 0, - max: 3000, - step: 100, - format: v=>v+"ms", - onchange: v => { - writeSettings("gracePeriodConnect",v); - } - }, - 'Notification': { - value: settings.gracePeriodNotification, - min: 0, - max: 3000, - step: 100, - format: v=>v+"ms", - onchange: v => { - writeSettings("gracePeriodNotification",v); - } - }, - 'Service': { - value: settings.gracePeriodService, - min: 0, - max: 3000, - step: 100, - format: v=>v+"ms", - onchange: v => { - writeSettings("gracePeriodService",v); - } - } - }; - - E.showMenu(mainmenu); -}) + + E.showMenu(buildMainMenu()); +}); diff --git a/apps/bthrv/ChangeLog b/apps/bthrv/ChangeLog index e144fd8f9..eefadac78 100644 --- a/apps/bthrv/ChangeLog +++ b/apps/bthrv/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Write available data on reset or kill +0.03: Buzz short on every finished measurement and longer if all are done diff --git a/apps/bthrv/app.js b/apps/bthrv/app.js index 067c84f56..fbd0e2d05 100644 --- a/apps/bthrv/app.js +++ b/apps/bthrv/app.js @@ -75,7 +75,6 @@ function write(){ data += "," + rrMax + "," + rrMin + ","+rrNumberOfValues; data += "\n"; file.write(data); - Bangle.buzz(500); } function onBtHrm(e) { @@ -87,6 +86,11 @@ function onBtHrm(e) { if (currentSlot <= hrvSlots.length && (Date.now() - startingTime) > (hrvSlots[currentSlot] * 1000) && !hrvValues[hrvSlots[currentSlot]]){ hrvValues[hrvSlots[currentSlot]] = hrv; currentSlot++; + if (currentSlot == hrvSlots.length){ + Bangle.buzz(500) + } else { + Bangle.buzz(50); + } } } diff --git a/apps/bthrv/metadata.json b/apps/bthrv/metadata.json index 183008034..7c57be682 100644 --- a/apps/bthrv/metadata.json +++ b/apps/bthrv/metadata.json @@ -2,7 +2,7 @@ "id": "bthrv", "name": "Bluetooth Heart Rate variance calculator", "shortName": "BT HRV", - "version": "0.02", + "version": "0.03", "description": "Calculates HRV from a a BT HRM with interval data", "icon": "app.png", "type": "app", diff --git a/apps/ncfrun/ChangeLog b/apps/btmultimeter/ChangeLog similarity index 100% rename from apps/ncfrun/ChangeLog rename to apps/btmultimeter/ChangeLog diff --git a/apps/btmultimeter/README.md b/apps/btmultimeter/README.md new file mode 100644 index 000000000..80fcdcf50 --- /dev/null +++ b/apps/btmultimeter/README.md @@ -0,0 +1,32 @@ +# Bluetooth Multimeter + +Connect to compatible a Bluetooth Multimeters and display the result on your wrist! + +## Compatible Bluetooth meters + +Only the OWON is supported right now - feel free to add support for more! + +### OWON OW18E + +Available [on Amazon](https://www.amazon.co.uk/Bluetooth-Multimeter-Multimeters-Voltmeter-Resistance/dp/B08NJT38SF/ref=sr_1_1) + +Turn the meter on, and long-press the Hz/Duty/Delta/Bluetooth button on the right hand side. Now run the app. + +## Usage + +The app currently only displays the current reading from the volt meter. + +If the app fails to connect you'll need to reload it to reconnect. + +To exit the app, long-press the button. + + +## Future functionality... + +* Logging +* Graphs +* More than one meter + +## Creator + +Gordon Williams (please file issues via GitHub) diff --git a/apps/btmultimeter/app-icon.js b/apps/btmultimeter/app-icon.js new file mode 100644 index 000000000..815929fd1 --- /dev/null +++ b/apps/btmultimeter/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4kA///z3vy067fWlP7/t1r3f33vrU07tdxHKn1mnUEv92DgO75xUwmcziIAIiAWJgYXLiIuLC5dwC6xIJCwP/swAHsIXM5GZAA/HC4kCC42I7oAG3OXC4sgC4/e9v+939AQP7C40iC429C4Pu/wCC9YXHGAYXCzoXC3wCCC5AwDC4WZC4M+909AQM7C5AwCC4ef/tfn/dAQQXIDAQXDAA4XHJIYXBzNVAA9XC4K8CR4QwCC4Mzi93AA1xC4JBCC4QwCC4URABIXBCgaqDC5MWC4YRCI4hfC1VEmMX93u8MR/Pd6J3CCQJ3DR4YXF84XBR4ilDbAYXFagM3iP1R4YQBCwbXER4KHBRYaWBR4TWFYoQXCsKPKCIQuEC452BC6MxiPju8xiYZEI5gXBiLDHO5k+FgKSBCYM+C4inJAAMWmlEolDi8TC4jXJAAQWBAANBUoIXCCQQCBDgQXCn0z+gXDokRjwXCCIQXCDoQuHAAJlBC4ZBBC4kiC4ekC4lBI4ioDYQYXD1QXJXIR3DL4fjn4XBGIdBbQQXFYAYvE0gXEF5DXGC4IYBC4WhC5IFCC4dKC4QDB+KPDC4guCX4n6GAIXCmK/CuAXEAgQvEPAIXC14JDmAXDFwYXEeAKpCBAgXECwYXFeQVDC5AAFC400AwoXQAAwXJgYXWGBoWJAF4A==")) diff --git a/apps/btmultimeter/app.js b/apps/btmultimeter/app.js new file mode 100644 index 000000000..11bcca9fb --- /dev/null +++ b/apps/btmultimeter/app.js @@ -0,0 +1,105 @@ +var decoded; +var gatt; + + +function decode(d) { + var value = d.getUint16(4,1); + if (value&32768) + value = -(value&32767); + var flags = d.getUint8(0); + var flags2 = d.getUint8(1); + // mv dc 27,240 "11xxx" + // mv ac 95,240 "1011xxx" + // v dc 36,240 "100xxx" 36(2dp) 35(20dp) + // v ac 100,240 "1100xxx" 100(2dp) 99(20dp) 97(2000dp) + // ohms 55,241 "110xxx" + // beep 231,242 "11100xxx" + // diode 167,242 "10100xxx" + // capac 76,241 "1001xxx" + // hz 162,241 "10100xxx" + // temp 33,242 "100xxx" + // ncv 96,243 "1100xxx" + // uA 146,240 "10010xxx" + // ma 155,240 "10011xxx" + // A 163,240 "10100xxx" + var dp = flags&7; + var range = (flags>>3)&7; + value *= Math.pow(10, -dp); + var isAC = !!(flags&64); + var mode = "?", units = ""; + if (flags2==240) { + if (flags&128) { + mode = "current"; + units = ["","nA","uA","mA","A","kA","MA",""][range]; + } else { + mode = "voltage"; + units = ["","nV","uV","mV","V","kV","MV",""][range] + " " + (isAC?"AC":"DC"); + } + } else if (flags2==241) { + if (isAC) { + mode = "capacitance"; + units = ["","nF","uF","mF","F","kF","MF",""][range]; + } else if (flags&128) { + mode = "frequency"; + units = "Hz"; + } else { + mode = "resistance"; + units = ["","nOhm","uOhm","mOhm","Ohm","kOhm","MOhm",""][range]; + } + } else if (flags2==242) { + if (flags&128) mode = isAC ? "continuity" : "diode"; + else { + mode = "temperature"; + units = isAC ? "F" : "C"; + } + } else if (flags2==243) mode = "ncv"; + //console.log(mode+" "+value+" "+units,new Uint8Array(d.buffer).slice()); + decoded = { + value : value, + mode : mode, // current/voltage/capacitance/frequency/resistance/temperature + units : units, // eg 'mA' + raw : new Uint8Array(d.buffer).slice(), + }; + updateDisplay(decoded); +} + +function updateDisplay(d) { + var mode = d.mode; + mode = mode.substr(0,1).toUpperCase()+mode.substr(1); + var s = d.value.toString(); + + var R = Bangle.appRect; + g.reset().clearRect(R); + g.setFont("12x20").setFontAlign(-1,-1).drawString(mode, R.x, R.y); + g.setFont("12x20").setFontAlign(1,1).drawString(d.units, R.x+R.w-1, R.y+R.h-1); + var fontSize = 80; + g.setFont("Vector",fontSize).setFontAlign(0,0); + while (g.stringWidth(s) > R.w-20) { + fontSize -= 2; + g.setFont("Vector", fontSize); + } + g.drawString(s, R.x+R.w/2, R.y+R.h/2); +} + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +E.showMessage(/*LANG*/"Connecting..."); + +NRF.requestDevice({ filters: [{ name: 'BDM' }] }).then(function(device) { + return device.gatt.connect(); +}).then(function(g) { + gatt = g; + return gatt.getPrimaryService(0xFFF0); +}).then(function(service) { + return service.getCharacteristic(0xFFF4); +}).then(function(c) { + c.on('characteristicvaluechanged', function(event) { + d = event.target.value; + decode(d); + }); + return c.startNotifications(); +}).then(function() { + E.showMessage(/*LANG*/"Connected."); +}).catch(function(e) { + E.showMessage(e.toString()); +}); diff --git a/apps/btmultimeter/app.png b/apps/btmultimeter/app.png new file mode 100644 index 000000000..e9e75c76e Binary files /dev/null and b/apps/btmultimeter/app.png differ diff --git a/apps/btmultimeter/metadata.json b/apps/btmultimeter/metadata.json new file mode 100644 index 000000000..3a9a72063 --- /dev/null +++ b/apps/btmultimeter/metadata.json @@ -0,0 +1,15 @@ +{ "id": "btmultimeter", + "name": "Bluetooth Multimeter", + "shortName":"BT Meter", + "version":"0.01", + "description": "Connect to compatible a Bluetooth Multimeters and display the result on your wrist!", + "icon": "app.png", + "tags": "bluetooth,tool", + "screenshots" : [ { "url":"screenshot.png" } ], + "supports" : ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"btmultimeter.app.js","url":"app.js"}, + {"name":"btmultimeter.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/btmultimeter/screenshot.png b/apps/btmultimeter/screenshot.png new file mode 100644 index 000000000..dfd64eabf Binary files /dev/null and b/apps/btmultimeter/screenshot.png differ diff --git a/apps/bwclk/ChangeLog b/apps/bwclk/ChangeLog new file mode 100644 index 000000000..546c83894 --- /dev/null +++ b/apps/bwclk/ChangeLog @@ -0,0 +1,23 @@ +0.01: New App. +0.02: Use build in function for steps and other improvements. +0.03: Adapt colors based on the theme of the user. +0.04: Steps can be hidden now such that the time is even larger. +0.05: Included icons for information. +0.06: Design and usability improvements. +0.07: Improved positioning. +0.08: Select the color of widgets correctly. Additional settings to hide colon. +0.09: Larger font size if colon is hidden to improve readability further. +0.10: HomeAssistant integration if HomeAssistant is installed. +0.11: Performance improvements. +0.12: Implements a 2D menu. +0.13: Clicks < 24px are for widgets, if fullscreen mode is disabled. +0.14: Adds humidity to weather data. +0.15: Added option for a dynamic mode to show widgets only if unlocked. +0.16: You can now show your agenda if your calendar is synced with Gadgetbridge. +0.17: Fix - Step count was no more shown in the menu. +0.18: Set timer for an agenda entry by simply clicking in the middle of the screen. Only one timer can be set. +0.19: Fix - Compatibility with "Digital clock widget" +0.20: Better handling of async data such as getPressure. +0.21: On the default menu the week of year can be shown. +0.22: Use the new clkinfo module for the menu. +0.23: Feedback of apps after run is now optional and decided by the corresponding clkinfo. \ No newline at end of file diff --git a/apps/bwclk/README.md b/apps/bwclk/README.md new file mode 100644 index 000000000..d869fa2cf --- /dev/null +++ b/apps/bwclk/README.md @@ -0,0 +1,49 @@ +# BW Clock +A very minimalistic clock. + +![](screenshot.png) + +## Features +The BW clock implements features that are exposed by other apps through the `clkinfo` module. +For example, if you install the HomeAssistant app, this menu item will be shown if you click right +and additionally allows you to send triggers directly from the clock (select triggers via up/down and +send via click center). Here are examples of other apps that are integrated: + +- Bangle data such as steps, heart rate, battery or charging state. +- Show agenda entries. A timer for an agenda entry can also be set by simply clicking in the middle of the screen. This can be used to not forget a meeting etc. Note that only one agenda-timer can be set at a time. *Requirement: Gadgetbridge calendar sync enabled* +- Weather temperature as well as the wind speed can be shown. *Requirement: Weather app* +- HomeAssistant triggers can be executed directly. *Requirement: HomeAssistant app* + +Note: If some apps are not installed (e.gt. weather app), then this menu item is hidden. + +## Settings +- Screen: Normal (widgets shown), Dynamic (widgets shown if unlocked) or Full (widgets are hidden). +- Enable/disable lock icon in the settings. Useful if fullscreen mode is on. +- The colon (e.g. 7:35 = 735) can be hidden in the settings for an even larger time font to improve readability further. +- Your bangle uses the sys color settings so you can change the color too. + +## Menu structure +2D menu allows you to display lots of different data including data from 3rd party apps and it's also possible to control things e.g. to trigger HomeAssistant. + +Simply click left / right to go through the menu entries such as Bangle, Weather etc. +and click up/down to move into this sub-menu. You can then click in the middle of the screen +to e.g. send a trigger via HomeAssistant once you selected it. The actions really depend +on the app that provide this sub-menu through the `clkinfo` module. + +``` + Bangle -- Agenda -- Weather -- HomeAssistant + | | | | + Battery Entry 1 Temperature Trigger1 + | | | | + Steps ... ... ... + | + ... +``` + + +## Thanks to +- Thanks to Gordon Williams not only for the great BangleJs, but specifically also for the implementation of `clkinfo` which simplified the BWClock a lot and moved complexety to the apps where it should be located. +- Icons created by Flaticon + +## Creator +[David Peer](https://github.com/peerdavid) diff --git a/apps/bwclk/app-icon.js b/apps/bwclk/app-icon.js new file mode 100644 index 000000000..1df0fa6a5 --- /dev/null +++ b/apps/bwclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgIcah0EgEB/H8iFsAoOY4kMBYMDhmGgXkAoUGiWkAoQQBoAFCjgnCAoM4hgFDuEI+wpC8EKyg1C/0eAoMAsEAiQvBAAeAApQAB/4Ao+P4v/wn0P8Pgn/wnkH4Pjv/j/nn9PH//n/nj/IFF4F88AXBAoM88EcAoPHj//jlDAoOf/+Y+YFHjnnjAjBEIIjD+BHDO9IALA==")) diff --git a/apps/bwclk/app.js b/apps/bwclk/app.js new file mode 100644 index 000000000..7dcca9d75 --- /dev/null +++ b/apps/bwclk/app.js @@ -0,0 +1,466 @@ +/************************************************ + * Includes + */ +const locale = require('locale'); +const storage = require('Storage'); +const clock_info = require("clock_info"); + + +/************************************************ + * Globals + */ +const SETTINGS_FILE = "bwclk.setting.json"; +const W = g.getWidth(); +const H = g.getHeight(); +var lock_input = false; + + +/************************************************ + * Settings + */ +let settings = { + screen: "Normal", + showLock: true, + hideColon: false, + menuPosX: 0, + menuPosY: 0, +}; + +let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; +for (const key in saved_settings) { + settings[key] = saved_settings[key] +} + +/************************************************ + * Assets + */ +// Manrope font +Graphics.prototype.setLargeFont = function(scale) { + // Actual height 47 (48 - 2) + this.setFontCustom( + atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAD/AAAAAAAAA/wAAAAAAAAP8AAAAAAAAD/AAAAAAAAA/wAAAAAAAAP8AAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAD/AAAAAAAAP/wAAAAAAAf/8AAAAAAB///AAAAAAH///wAAAAAf///8AAAAB/////AAAAH////8AAAAP////wAAAA/////AAAAB////+AAAAA////4AAAAAP///gAAAAAD//+AAAAAAA//4AAAAAAAP/gAAAAAAAD/AAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///+AAAAAB////8AAAAB/////wAAAA/////+AAAA//////wAAAf/////+AAAH//////wAAD//////+AAB/+AAAf/gAAf+AAAA/8AAH/AAAAH/AAD/gAAAA/4AA/wAAAAH+AAP8AAAAB/gAD+AAAAAf4AA/gAAAAH+AAP4AAAAA/gAD+AAAAAf4AA/wAAAAH+AAP8AAAAB/gAD/AAAAA/4AA/4AAAAP+AAH/AAAAH/AAB/4AAAH/wAAP/wAAP/4AAD//////+AAAf//////AAAD//////gAAAf/////wAAAD/////4AAAAf////4AAAAB////4AAAAAB///gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAH/AAAAAAAAD/gAAAAAAAA/4AAAAAAAAf8AAAAAAAAH+AAAAAAAAD/gAAAAAAAB/wAAAAAAAAf8AAAAAAAAP///////AAD///////wAA///////8AAP///////AAD///////wAA///////8AAP///////AAD///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAB/AAAA/gAAA/wAAA/4AAAf8AAAf+AAAP/AAAP/gAAH/wAAH/4AAD/8AAD/+AAB//AAA//gAA//wAAf/AAAP/8AAH/AAAH//AAD/gAAD//wAA/wAAB//8AAP8AAA///AAD/AAAf+fwAA/gAAP/n8AAP4AAH/x/AAD+AAD/4fwAA/gAB/8H8AAP8AAf+B/AAD/AAP/AfwAA/4AH/gH8AAH/AH/wB/AAB/8H/4AfwAAP///8AH8AAD////AB/AAAf///gAfwAAD///wAH8AAAf//4AB/AAAD//4AAfwAAAP/8AAH8AAAAf4AAB/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAADgAAAfwAAAB+AAAH8AAAAfwAAB/AAAAH+AAAfwAAAB/wAAH8AAAA/+AAB/AAAAP/gAAfwA4AA/8AAH8AfgAH/AAB/AP8AA/4AAfwD/gAH+AAH8B/4AB/gAB/A/8AAf4AAfwf/AAD+AAH8P/wAA/gAB/H/8AAf4AAfz//gAH+AAH8//4AB/gAB/f//AA/4AAf/+/4Af8AAH//P/AP/AAB//j////gAAf/wf///4AAH/4H///8AAB/8A///+AAAf+AH///AAAH/AA///gAAB/gAD//wAAAfwAAP/wAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAH/wAAAAAAAH/8AAAAAAAH//AAAAAAAH//wAAAAAAH//8AAAAAAH///AAAAAAH///wAAAAAH///8AAAAAP//9/AAAAAP//8fwAAAAP//4H8AAAAP//4B/AAAAP//4AfwAAAP//4AH8AAAD//4AB/AAAA//4AAfwAAAP/4AAH8AAAD/wAAB/AAAA/wAAAfwAAAPwAH////AADwAB////wAAwAAf///8AAAAAH////AAAAAB////wAAAAAf///8AAAAAH////AAAAAA////wAAAAAAAfwAAAAAAAAH8AAAAAAAAB/AAAAAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAGAHwAAAB///gB+AAAH///8AfwAAB////AP+AAAf///wD/wAAH///+A/+AAB////gP/gAAf///4A/8AAH/8P8AH/AAB/AD+AA/4AAfwA/gAH+AAH8AfwAB/gAB/AH8AAf4AAfwB/AAH+AAH8AfwAB/gAB/AH8AAf4AAfwB/gAH+AAH8Af4AB/gAB/AH/AA/wAAfwB/4Af8AAH8AP/AP/AAB/AD////gAAfwAf///wAAH8AD///8AAB/AA///+AAAfwAH///AAAAAAA///gAAAAAAD//gAAAAAAAP/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///4AAAAAH////wAAAAH/////AAAAD/////4AAAB//////AAAA//////4AAAf//////AAAP//////4AAD/8D/w/+AAB/4B/wD/wAAf8A/wAf8AAP+AP4AD/gAD/AD+AAf4AA/wB/AAH+AAP4AfwAB/gAD+AH8AAf4AA/gB/AAH+AAP4AfwAB/gAD+AH+AAf4AA/wB/gAH+AAP8Af8AD/gAD/gH/gB/wAAf8A/8A/8AAH/AP///+AAB/gB////gAAPwAP///wAAB4AD///4AAAMAAf//8AAAAAAD//+AAAAAAAP/+AAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAAAH8AAAAAAAAB/AAAAAAAAAfwAAAAAAAAH8AAAAAAAAB/AAAAABwAAfwAAAAB8AAH8AAAAD/AAB/AAAAD/wAAfwAAAH/8AAH8AAAH//AAB/AAAP//wAAfwAAP//8AAH8AAf//+AAB/AAf//8AAAfwA///8AAAH8A///4AAAB/A///4AAAAfx///wAAAAH9///wAAAAB////gAAAAAf///gAAAAAH///AAAAAAB///AAAAAAAf/+AAAAAAAH/+AAAAAAAB/8AAAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAf/AAAAAP+Af/8AAAAP/4P//wAAAP//P//+AAAH//////wAAB//////8AAA///////gAAf//////8AAH////gP/AAD/wf/wA/wAA/4D/4AP+AAP8Af8AB/gAD/AH/AAf4AA/gA/wAH+AAP4AP4AA/gAD+AD/AAP4AA/gA/wAH+AAP8Af8AB/gAD/AH/AAf4AA/4D/4AP+AAP/B//AH/AAB////4D/wAAf//////8AAD//////+AAAf//////AAAH//////wAAA//8///4AAAD/+D//8AAAAP+Af/8AAAAAAAB/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/gAAAAAAAB//AAAAAAAB//8AAAAAAB///gAAgAAA///8AAcAAAf///gAPAAAH///8AH4AAD////AD/AAB/+H/4B/wAAf+Af+Af8AAP+AB/wD/gAD/gAf8Af4AA/wAD/AH+AAP8AA/wB/gAD+AAH8AP4AA/gAB/AD+AAP4AAfwB/gAD+AAH8Af4AA/wAD/AH+AAP8AA/gD/gAD/gAf4A/wAAf8AP8A/8AAH/gH/Af/AAA///////gAAP//////wAAB//////8AAAP/////+AAAB//////AAAAP/////AAAAA/////gAAAAD////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wA/wAAAAAP8AP8AAAAAD/AD/AAAAAA/wA/wAAAAAP8AP8AAAAAD/AD/AAAAAA/wA/wAAAAAP8AP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='), + 46, + atob("ExspGyUkJiQnISYnFQ=="), + 62+(scale<<8)+(1<<16) + ); + return this; +}; + +Graphics.prototype.setMediumFont = function(scale) { + // Actual height 41 (42 - 2) + this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAB/AAAAAAAP/AAAAAAD//AAAAAA///AAAAAP///AAAAB///8AAAAf///AAAAH///wAAAB///+AAAAH///gAAAAH//4AAAAAH/+AAAAAAH/wAAAAAAH8AAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAH////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gAAH+AAD+AAAD/AAH8AAAB/AAH4AAAA/gAH4AAAAfgAH4AAAAfgAPwAAAAfgAPwAAAAfgAPwAAAAfgAHwAAAAfgAH4AAAAfgAH4AAAA/gAH8AAAA/AAD+AAAD/AAD/gAAH/AAB/////+AAB/////8AAA/////4AAAf////wAAAH////gAAAB///+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAAfwAAAAAAA/gAAAAAAA/AAAAAAAB/AAAAAAAD+AAAAAAAD8AAAAAAAH8AAAAAAAH//////AAH//////AAH//////AAH//////AAH//////AAH//////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4AAA/AAAP4AAB/AAAf4AAD/AAA/4AAD/AAB/4AAH/AAD/4AAP/AAH/AAAf/AAH8AAA//AAH4AAB//AAP4AAD//AAPwAAH+/AAPwAAP8/AAPwAAf4/AAPwAA/4/AAPwAA/w/AAPwAB/g/AAPwAD/A/AAP4AH+A/AAH8AP8A/AAH/A/4A/AAD///wA/AAD///gA/AAB///AA/AAA//+AA/AAAP/8AA/AAAD/wAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAH4AAAHwAAH4AAAH4AAH4AAAH8AAH4AAAP+AAH4AAAH+AAH4A4AB/AAH4A+AA/AAH4B/AA/gAH4D/AAfgAH4H+AAfgAH4P+AAfgAH4f+AAfgAH4/+AAfgAH5/+AAfgAH5//AAfgAH7+/AA/gAH/8/gB/AAH/4f4H/AAH/wf//+AAH/gP//8AAH/AH//8AAH+AD//wAAH8AB//gAAD4AAf+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAAAAD/AAAAAAAP/AAAAAAB//AAAAAAH//AAAAAAf//AAAAAB///AAAAAH///AAAAAf/8/AAAAB//w/AAAAH/+A/AAAA//4A/AAAD//gA/AAAH/+AA/AAAH/4AA/AAAH/gAA/AAAH+AAA/AAAHwAAA/AAAHAAf///AAEAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAP/AHgAAH///AP4AAH///gP8AAH///gP8AAH///gP+AAH///gD/AAH/A/AB/AAH4A/AA/gAH4A+AAfgAH4B+AAfgAH4B+AAfgAH4B8AAfgAH4B8AAfgAH4B+AAfgAH4B+AAfgAH4B+AA/gAH4B/AA/AAH4A/gD/AAH4A/4H+AAH4Af//+AAH4AP//8AAH4AP//4AAHwAD//wAAAAAB//AAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAD////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gP4H+AAD/AfgD/AAH8A/AB/AAH8A/AA/gAH4B+AAfgAH4B+AAfgAPwB8AAfgAPwB8AAfgAPwB+AAfgAPwB+AAfgAH4B+AAfgAH4B/AA/gAH8B/AB/AAH+A/wD/AAD+A/8P+AAB8Af//+AAB4AP//8AAAwAH//4AAAAAD//gAAAAAA//AAAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAHAAPwAAAA/AAPwAAAD/AAPwAAAf/AAPwAAB//AAPwAAP//AAPwAA//8AAPwAH//wAAPwAf/+AAAPwB//4AAAPwP//AAAAPw//8AAAAP3//gAAAAP//+AAAAAP//wAAAAAP//AAAAAAP/4AAAAAAP/gAAAAAAP+AAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAH+A//gAAAf/h//4AAA//z//8AAB/////+AAD/////+AAD///+H/AAH+H/4B/AAH8B/wA/gAH4A/gAfgAH4A/gAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAH4A/gAfgAH4A/gAfgAH8B/wA/gAH/H/4B/AAD///+H/AAD/////+AAB/////+AAA//z//8AAAf/h//4AAAH+A//gAAAAAAH+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAD/8AAAAAAP/+AAAAAAf//AAcAAA///gA8AAB///wB+AAD/x/4B/AAD+AP4B/AAH8AH8A/gAH4AH8A/gAH4AD8AfgAP4AD8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAH4AD8AfgAH4AD4A/gAH8AH4B/AAD+APwD/AAD/g/wP+AAB/////+AAA/////8AAAf////4AAAP////wAAAH////AAAAA///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DxcjFyAfISAiHCAiEg=="), 54+(scale<<8)+(1<<16)); + return this; +}; + +Graphics.prototype.setSmallFont = function(scale) { + // Actual height 28 (27 - 0) + this.setFontCustom( + atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/+cB//5wH//nAAAAAAAAAAAAAAAAAAAB8AAAHwAAAfAAAAAAAAAAAAAfAAAB8AAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAQcAADhwAAOHBAA4c8ADh/wAP/+AB/+AA//wAH+HAAe4cMBDh/wAOP/AA//wAP/wAH/3AAf4cABzhwAAOHAAA4cAADgAAAOAAAAAAAAAAAAAAAAAAAAwAH8HwA/4PgD/geAePA8BwcBw/BwH78DgfvwOB+HA4HAeBwcA8HDgB4f+ADg/wAGB+AAAAAAAAAAAAAAAH4AAA/wBwHngPAcOB4Bw4PAHDh4AcOPAA/x4AD/PAADx4AAAPAAAB5wAAPPwAB5/gAPOPAB4wcAPDBwB4MHAPA4cA4B/gBAH8AAAHAAAAAAAAAAAAAPAAHD/AB/f+AP/x4B4+DwHB4HAcDwcBwHhwHAPHAcAccB4A5wDgB+AGA/4AAH/AAAf+AAAA8AAABgAAAAAfAAAB8AAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/8AD//+A/+/+H4AD98AAB3gAADIAAAAAAAAAAAAAIAAABwAAAXwAAHPwAB8P8D/gP//4AH/8AAAAAAAAAAAAAAAAAAAAAAAAHAAAAcwAAA/gAAb8AAB/gAAH+AAAD+AAAOwAABxAAADAAAAAAAAAAAAAADAAAAMAAAAwAAADAAAAMAAAAwAAB//AAH/8AAAwAAADAAAAMAAAAwAAADAAAAMAAAAAAAAAAAAAABwAAAHIAAAfgAAB8AAAAAAAAAAAAAAAAAAAAAwAAADAAAAMAAAAwAAADAAAAMAAAAwAAADAAAAMAAAAAAAAAAAAAAAAAAAAAAABwAAAHAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAA/wAA//AA//AA//AAH/AAAfAAABAAAAAAAAAAAAAAAAAAAf/wAH//wA///gDgAOAcAAcBwABwHAAHAcAAcBwABwHgAPAPAB4Af//AA//4AA/+AAAAAAAAAAAAAAAAMAAABwAAAOAAAB4AAAH///Af//8B///wAAAAAAAAAAAAAAAAAAAAwAcAPADwB8AfAPAB8B4APwHAB/AcAPcBwB5wHAPHAcB4cA8PBwD/4HAH/AcAHwBwAAAAAAAAAAAAGAHAAcAcAB4BwYDwHDwHAceAcBz4BwHfgHAf3AcB+eDwHw/+AeB/wBwD+AAAAAAAAAAAAAAAAABwAAAfAAAP8AAD/wAA/nAAP4cAD+BwAfgHAB4AcAEA//AAD/8AAP/wAABwAAAHAAAAMAAAAAAAAAAAAAEAH/w4Af/D4B/8HgHDgPAcOAcBw4BwHDgHAcOAcBw8DwHB4eAcH/wBgP+AAAPwAAAAAAAAAAAAAAAB//AAf//AD//+AOHB4Bw4BwHDgHAcOAcBw4BwHDgHAcPA8A4eHgDh/8AEB/gAAD4AAAAAAAAAABwAAAHAAAAcAAMBwADwHAB/AcA/4BwP8AHH/AAd/gAB/wAAH8AAAeAAAAAAAAAAAAAAAEAAPD+AB/f8AP//4B4+DwHDwHAcHAcBwcBwHBwHAcPAcB/+DgD//+AH5/wACB8AAAAAAAAAAAAAAAAEAAAD+AAAf+DAD74OAODw8BwHBwHAOHAcA4cBwDBwHAcHAeBw8A+ePgB//8AD//gAB/wAAAAAAAAAAAAAAAAAAAAAHBwAAcHAABwcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AcgDgB+AOAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAeAAAD8AAAf4AADzwAAeHgADwPAAGAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADGAAAMYAAAxgAADGAAAMYAAAxgAADGAAAMYAAAxgAADGAAAMYAAAxgAADGAAAMYAAAxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABACAAOAcAA8DgAB4cAABzgAAD8AAAHgAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAHgAAA+AAAHgAAAcAAABwD5wHAfnAcD8cBweAAHzwAAP+AAAfwAAAcAAAAAAAAAAAAAAAAAAB/AAA//AAH/+AA8A8AHAA4A4ABwDg+HAcH8OBw444GDBhgYMGGBgwYYHDjjgcP8OBw/44DgDhAOAGAAeAYAA+HgAB/8AAB/gAAAAAAAAAAAAABAAAA8AAAfwAAP/AAH/gAD/4AB/zgAf4OAB8A4AHwDgAf4OAA/84AAP/gAAH/AAAD/gAAB/AAAA8AAAAQAAAAAAAAAB///wH///Af//8BwOBwHA4HAcDgcBwOBwHA4HAcDgcBweBwHj4HAP/58Afz/gAcH8AAAPAAAAAAAH/AAB//AAf//AB4A8APAB4B4ADwHAAHAcAAcBwABwHAAHAcAAcBwABwHAAHAOAA4A8AHgB8B8ADwHgADAYAAAAAAAAAAAAAAAH///Af//8B///wHAAHAcAAcBwABwHAAHAcAAcBwABwHAAHAeAA8A8AHgB8B+AD//gAH/8AAD/AAAAAAAAAAAAAAAAf//8B///wH///AcDgcBwOBwHA4HAcDgcBwOBwHA4HAcDgcBwOBwHAAHAcAAcAAAAAAAAAAAAAAB///wH///Af//8BwOAAHA4AAcDgABwOAAHA4AAcDgABwOAAHAAAAcAAAAAAAAAP/AAB//AAf//AB4A8APAB4B4ADwHAAHAcAAcBwABwHAAHAcAAcBwGBwHgYPAOBg4A+GPgB4f8ADh/gAAH4AAAAAAAAAAAAAAAH///Af//8B///wAA4AAADgAAAOAAAA4AAADgAAAOAAAA4AAADgAAAOAAAA4AAf//8B///wH///AAAAAAAAAAAAAAAAAAAB///wH///Af//8AAAAAAAAAAAABgAAAHgAAAeAAAA8AAABwAAAHAAAAcAAABwH///Af//4B///AAAAAAAAAAAAAAAAAAAAf//8B///wH///AAHgAAA/AAAH+AAA88AAHh8AA8D4AHgDwA8AHgHgAPAYAAcBAAAwAAABAAAAAAAAAAH///Af//8B///wAAAHAAAAcAAABwAAAHAAAAcAAABwAAAHAAAAcAAABwAAAAAAAAAAAAAAH///Af//8B///wB/AAAB/AAAA/AAAA/gAAA/gAAA/gAAA/AAAD8AAA/AAAfwAAH8AAB/AAAfgAAP4AAB///wH///Af//8AAAAAAAAAAAAAAAAAAAH///Af//8B///wD8AAAD4AAAH4AAAHwAAAPwAAAPgAAAPgAAAfAAAAfAAAA/Af//8B///wH///AAAAAAAAAAAH/AAB//AAf//AB4A8APAB4B4ADwHAAHAcAAcBwABwHAAHAcAAcBwABwHAAHAOAA4A8AHgB+D8AD//gAH/8AAD+AAAAAAAAAAAH///Af//8B///wHAOAAcA4ABwDgAHAOAAcA4ABwDgAHAeAAeBwAA+fAAD/4AAD/AAADgAAAAAAAAf8AAH/8AB//8AHgDwA8AHgHgAPAcAAcBwABwHAAHAcAAcBwABwHAAnAcAHcA4AfgDwA+AH4P4AP//wAf/3AAP4AAAAAAAAAAAf//8B///wH///AcA4ABwDgAHAOAAcA4ABwDgAHAOAAcB+AB4H+AD59/AP/h8AP8BwAOABAAAAAAAAAAAAAwAD4HwA/4fAD/geAePA8BwcBwHBwHAcDgcBwOBwHA4HAcDgcA4HDwD4eeAHw/4AOD/AAIDwAAAAABwAAAHAAAAcAAABwAAAHAAAAcAAABwAAAH///Af//8B///wHAAAAcAAABwAAAHAAAAcAAABwAAAGAAAAAAAAAAAAAH//wAf//gB///AAAAeAAAA8AAABwAAAHAAAAcAAABwAAAHAAAAcAAADgAAAeAf//wB//+AH//gAAAAAAAAAAGAAAAfAAAB/gAAB/wAAA/4AAAf8AAAP/AAAH8AAADwAAA/AAAf8AAP+AAP/AAH/gAB/wAAH4AAAcAAABAAAAHwAAAf4AAA/+AAAP/gAAH/wAAB/wAAA/AAAf8AAf/AAP/gAP/gAB/gAAH4AAAf+AAAf/AAAH/wAAB/8AAAfwAAB/AAB/8AA/+AA/+AAf+AAB/AAAHAAAAAAAAAAAAQGAADAeAA8B8AHwD8B+AD4PgAH74AAH/AAAPwAAA/gAAP/gAD8fAAfA/AH4A+AeAA8BwABwEAABAQAAABwAAAHwAAAPwAAAfwAAAfgAAAfgAAAf/wAB//AAf/8AH8AAA/AAAPwAAB8AAAHAAAAQAAAAAAAAAAABAcAAcBwADwHAA/AcAP8BwD/wHAfnAcH4cBx+BwHPwHAf8AcB/ABwH4AHAeAAcBgABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/////////gAAAOAAAA4AAADAAAAAAAAAAAAAAAAAAAAAAAeAAAB/gAAH/4AAB/+AAAf/gAAH/AAAB8AAAAQAAAAAAAAAAAAAAOAAAA4AAADgAAAP/////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAADgAAAcAAADgAAAcAAADgAAAcAAAB4AAADwAAADgAAAHAAAAOAAAAYAAAAAAAAAAAAAAAAAAAAGAAAAYAAABgAAAGAAAAYAAABgAAAGAAAAYAAABgAAAGAAAAYAAABgAAAGAAAAYAAABgAAAGAAAAYAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAADj+AAef8AD5xwAOGHAA44MADjgwAOOHAA44YADjDgAH/8AAf/8AAf/wAAAAAAAAAAAAAAAAAAAf//8B///wH//+AAcA4ADgBwAOAHAA4AcADgBwAOAHAA8A8AB8PgAD/8AAH/gAAH4AAAAAAAAAAAAH4AAB/4AAP/wAB4HgAPAPAA4AcADgBwAOAHAA4AcADgBwAPAPAAeB4AA4HAABgYAAAAAAAAAAAAfgAAH/gAB//gAHgeAA8A8ADgBwAOAHAA4AcADgBwAOAHAAcA4B///wH///Af//8AAAAAAAAAAAAAAAAH4AAB/4AAP/wAB7HgAPMPAA4wcADjBwAOMHAA4wcADjBwAPMPAAfx4AA/HAAB8YAAAwAAAAAAAAAAAAwAAADAAAB///AP//8B///wHMAAAYwAABjAAAGMAAAAAAAAAPwAAD/wMA//w4DwPHgeAePBwA4cHADhwcAOHBwA4cHADhwOAcPB///4H///Af//wAAAAAAAAAAAAAAAAAAB///wH///AAf/8ABwAAAOAAAA4AAADgAAAOAAAA4AAADwAAAH//AAP/8AAf/wAAAAAAAAAAAAAAAAAAAc//8Bz//wHP//AAAAAAAAAAAAAAHAAAAcAAAH+f///5///7H//8AAAAAAAAAAAAAAH///Af//8B///wAAPAAAB+AAAP8AAB54AAfDwAD4HgAOAPAAwAcACAAwAAAAAAAAAB///wH///Af//8AAAAAAAAAAAAAAAAAAAAP//AA//8AB//wAHAAAA4AAADgAAAOAAAA4AAAD4AAAH//AAP/8AB//wAHAAAA4AAADgAAAOAAAA4AAADwAAAH//AAP/8AAf/wAAAAAAAAAAAAAAAP//AA//8AB//wAHAAAA4AAADgAAAOAAAA4AAADgAAAPAAAAf/8AA//wAB//AAAAAAAAAAAAAAAAB+AAAf+AAD/8AAeB4ADwDwAOAHAA4AcADgBwAOAHAA4AcADwDwAHw+AAP/wAAf+AAAfgAAAAAAAAAAAAAAAB///8H///wP///A4BwAHADgAcAOABwA4AHADgAcAOAB4B4AD4fAAH/4AAP/AAAPwAAAAAAAAAAAAPwAAD/wAA//wADwPAAeAeABwA4AHADgAcAOABwA4AHADgAOAcAB///8H///wf///AAAAAAAAAAAAAAAAAAAD//wAP//AAf/8ABwAAAOAAAA4AAADgAAAOAAAAAAAAAYGAAD4cAAfx4AD3DwAOOHAA44cADjhwAOGHAA4ccADxzwAHj+AAOP4AAYOAAAAAAAwAAADAAAAMAAAP//wA///gD///AAwAcADABwAMAHAAwAcADAAwAAAAAAAAAAD/gAAP/4AA//4AAA/gAAAPAAAAcAAABwAAAHAAAAcAAABwAAAOAA//8AD//wAP//AAAAAAAAAAAIAAAA4AAAD8AAAH+AAAH/AAAD/gAAB/AAAB8AAA/wAAf8AAP+AAD/AAAPgAAAwAAAAAAAAIAAAA8AAAD/AAAH/gAAD/wAAA/wAAA/AAAf8AAP+AAP+AAA/AAAD+AAAH/AAAD/gAAA/wAAA/AAAf8AAP/AAP/AAA/gAADgAAAAAAAAAAEADAAwAOAHAA+B8AB8PgAB74AAD/AAAH4AAA/wAAHvgAB8PgAPgfAA4AcADAAwAAABABAAAAHAAAAfgAAA/wAAA/wAwAf4fAAP/8AAP/AAB/gAA/wAAf4AAP+AAB/AAAHgAAAQAAAAAAEADAAwAOAPAA4B8ADgPwAOD/AA4ecADnxwAO8HAA/gcAD8BwAPAHAA4AcACAAwAAAAAAAAAAAAAAAAAAAAAAAAAA8AB////f//////n/+AAAA4AAADgAAAAAAAAAAAAAAAAAH///Af//8B///wAAAAAAAAAAAAAAAAAAA4AAADgAAAOAAAA//5/9////z////AAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAB8AAAHwAAAcAAABwAAAHgAAAOAAAA8AAABwAAAHAAAB8AAAHwAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAYAAABgAAAGAAAAYAAABgAAAGAAAAYAAABgAAAGAAAAGAAABwAAAOAAABwAAAHAAAAcAAAA4AAABwAAABgAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAOAB4B4ADwPAAHh4AAPPAAAf4AAA/AAAB4AAAPwAAB/gAAPPAAB4eAAPA8AB4B4AHADgAIAEAAAAAAADAAAAMAAAAwAAADAAAAMAAAAwAAHDDgA8MPADww8AGDBgAAMAAAAwAAADAAAAMAAAAwAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADn//gOf/+A5//4AAAAAAAAAAAAAAAAAAAD/AAA//AAH/+AA+B8ADgBwAOAHAHwAPgfAA+B8AD4A4AcADwDwAHgeAAOBwAAQCAAAAAAAAAAAADgcAAOBwAA4HAD//8A///wD///AeDgcBwOBwHA4HAcDgcB4GBwD4AHAHgAcAOAAAAAAAAAAAAAMAGAB7+8AD//gAHx8AAcBwADgDgAOAOAA4A4ADgDgAOAOAA4A4ABwHAAHg8AA//4AH//wAMOGAAAAAAQAAABwAAAHwMYAPwxgAfjGAAfsYAAf7gAAf/wAB//AAf/8AH7GAA/MYAPwxgB8DGAHAAAAQAAAAAAAAAAAAAf/D/5/8P/n/w/+AAAAAAAAAAAAAAAAAAAABwAAffhwD//Hgf+cfBzwwcGHDhwYcOHBxw4cHDhxwfOPvA8//4Bx//AADwwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAP/wAB//gAPAPAB4AOAPDw8A4/xwHH/jgc4HOBzgc4HMAzgcwDOBzgc4HPDjgOcOeA4whwBwAOAHwD4APw/AAf/4AAf+AAAPAAAAAAAATgAAD/AAANsAAA2wAADTAAAP8AAAfwAAAAAAAAAAAAAAAAAAgAAAPAAAB+AAAOeAADw8AAOIwAADxAAAfgAADngAA8PAADgMAAEAQAAAAAAAAAAABgAAAGAAAAYAAABgAAAGAAAAYAAABgAAAGAAAAYAAABgAAAGAAAAYAAAB+AAAH4AAAAAAAAAAAAAAAAAAAAD8AAA/8AAHh4AAYDgAD/3AAN/MAA0QwADRjAAN/MAA7hwABwOAADhwAAH+AAAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAD/AAAeeAABw4AAGDgAAYOAABw4AAH/AAAP8AAAfAAAAAAAAAAAAAAAAAAAwYAADBgAAMGAAAwYAADBgAAMGAAP+YAA/5gAD/mAAAwYAADBgAAMGAAAwYAADBgAAAAAAAAAAAAAAAMDAABwcAAPDwAAwPAADB8AAMOwAA5zAAB+MAADwwAAAAAAAAAAAIBAAAwGAADMcAANwwAA/DAAD8MAAO/wAAx+AAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf///B///8H//AAAAeAAAA4AAADgAAAOAAAA4AAADgAAAcAB//gAH//gAf/+AAAAAAAAAAAAAAAAAAAAP4AAB/4AAP/gAB//AAH/8AAf/wAB//AAH///8f///x////AAAAAAAAAB////H///8f///wAAAAAAAAAAAAAAABAAAAOAAAB4AAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAzAAAPMAAA/wAAAeAAAAAAAAAAAAAAAAAAAIAAABgAAAMAAAA//AAD/8AAAAAAAAAAAAAAAAAAAAAA8AAAP4AAAwwAADDAAAMMAAA5wAAB+AAADwAAAAAAAAAAAAAAAAAMAwAA8HAAB44AAD/AAAD4AADGMAAOBwAAeOAAA/wAAA+AAABgAAAAAAAAAAAAAAABAAAAMAAABwAAAH/8CAf/wcAAAHgAAA8AAAHgAAB4AAAPAAAB4AAAeAAADwAAA+AAAHgCAA8A8APAfwB4H7AHB+MAAHAwAAQ/wAAD/AAAAwAAADAAAAAAAAAAAAAAAAAAAAEAAAAwAAAHAAAAf/wIB//BwAAAeAAADwAAAeAAAHgAAA8AAAHgAAB4AAAPAAAD4AAAeAAADwAAA8GAwHg4HAcHA8AAYHwABg7AAGHMAAf4wAA/DAAA4MAAAAAAAAAAYBgABgHAAGMOAAZwYABvBgAH8OCAe/wcBx+HgABg8AAAHgAAB4AAAPAAAB4AAAeAAADwAAA+AAAHgHAA8B8APAfwB4HzADB8MAAHAwAAQ/wAAD/AAAAwAAAAAAAAAAAAAAAAAA4AAAP4AAB/wAAPHgABwOA4/A4Dn4DgOfAOAAAA4AAAHgAAB8AAAHgAAAYAAAAAAAAEAAADwAAB/AAA/8AAf+AAP/gAH/OAB/g4AHwDgAfAOAB/g4AD/zgAA/+AAAf8AAAP+AAAH8AAADwAAABAAAAEAAADwAAB/AAA/8AAf+AAP/gAH/OAB/g4AHwDgAfAOAB/g4AD/zgAA/+AAAf8AAAP+AAAH8AAADwAAABAAAAEAAADwAAB/AAA/8AAf+AAP/gAH/OAB/g4AHwDgAfAOAB/g4AD/zgAA/+AAAf8AAAP+AAAH8AAADwAAABAAAAEAAADwAAB/AAA/8AAf+AAP/gAH/OAB/g4AHwDgAfAOAB/g4AD/zgAA/+AAAf8AAAP+AAAH8AAADwAAABAAAAEAAADwAAB/AAA/8AAf+AAP/gAH/OAB/g4AHwDgAfAOAB/g4AD/zgAA/+AAAf8AAAP+AAAH8AAADwAAABAAAAAAAAABwAAA/AAAf8AAP/AAH/wfD/nD+/wcMb4BwxvgHD+/wcHx/5wEAf/AAAP+AAAH/AAAD8AAABwAAAAAAAEAAADwAAB/AAA/8AAf+AAP/gAH/OAB/g4AHwDgAcAOABwA4AHADgAf//8B///wHA4HAcDgcBwOBwHA4HAcDgcBwOBwHA4HAcDgcBwOBwHAAHAYAAMAAAAAAAAAAA/4AAP/4AD//4APAHgB4APAPAAeA4AA4DgADg+AAPz4AA//gAD/+AAOe4AA4BwAHAHgA8APgPgAeA8AAYDAAAAAAAAAAAAAAAAf//8B///wH///AcDgcBwOBwHA4HAcDgcBwOBwHA4HAcDgcBwOBwHAAHAcAAcAAAAAAAAAAAAAAB///wH///Af//8BwOBwHA4HAcDgcBwOBwHA4HAcDgcBwOBwHA4HAcAAcBwABwAAAAAAAAAAAAAAH///Af//8B///wHA4HAcDgcBwOBwHA4HAcDgcBwOBwHA4HAcDgcBwABwHAAHAAAAAAAAAAAAAAAf//8B///wH///AcDgcBwOBwHA4HAcDgcBwOBwHA4HAcDgcBwOBwHAAHAcAAcAAAAAAAAAAAAAAB///wH///Af//8AAAAAAAAAAAAAAAAAAAH///Af//8B///wAAAAAAAAAAAAAAAAAAAf//8B///wH///AAAAAAAAAAAAAAAAAAAB///wH///Af//8AAAAAAAAAAABgAAAGAAH///Af//8B///wHAYHAcBgcBwGBwHAYHAcBgcBwABwHAAHAeAA8A8AHgB+D8AD//gAH/8AAD+AAAAAAAAAAAAAAAAf//8B///wH///APwAAAPgAAAfgAAAfAAAA/AAAA+AAAA+AAAB8AAAB8AAAD8B///wH///Af//8AAAAAAAAAAAf8AAH/8AB//8AHgDwA8AHgHgAPAcAAcBwABwHAAHAcAAcBwABwHAAHAcAAcA4ADgDwAeAH4PwAP/+AAf/wAAP4AAAAAAAH/AAB//AAf//AB4A8APAB4B4ADwHAAHAcAAcBwABwHAAHAcAAcBwABwHAAHAOAA4A8AHgB+D8AD//gAH/8AAD+AAAAAAAB/wAAf/wAH//wAeAPADwAeAeAA8BwABwHAAHAcAAcBwABwHAAHAcAAcBwABwDgAOAPAB4Afg/AA//4AB//AAA/gAAAAAAAf8AAH/8AB//8AHgDwA8AHgHgAPAcAAcBwABwHAAHAcAAcBwABwHAAHAcAAcA4ADgDwAeAH4PwAP/+AAf/wAAP4AAAAAAAH/AAB//AAf//AB4A8APAB4B4ADwHAAHAcAAcBwABwHAAHAcAAcBwABwHAAHAOAA4A8AHgB+D8AD//gAH/8AAD+AAAAAAAAAAAAGDgAA8eAAB7wAAD+AAAHwAAAfAAAD+AAAe8AADw4AAGBAAAAAAAAAAAAAAAAAf8MAH//4B///AHgD4A8AfgHgD/AcAecBwDxwHAeHAcDwcBw+BwHHgHAc8AcA/gDgD8AeAH4PwA//+AH//wAMP4AAAAAAAAAAAf//AB//+AH//8AAAB4AAADwAAAHAAAAcAAABwAAAHAAAAcAAABwAAAOAAAB4B///AH//4Af/+AAAAAAAAAAAAAAAAAAAAH//wAf//gB///AAAAeAAAA8AAABwAAAHAAAAcAAABwAAAHAAAAcAAADgAAAeAf//wB//+AH//gAAAAAAAAAAAAAAAAAAAB//8AH//4Af//wAAAHgAAAPAAAAcAAABwAAAHAAAAcAAABwAAAHAAAA4AAAHgH//8Af//gB//4AAAAAAAAAAAAAAAAAAAAf//AB//+AH//8AAAB4AAADwAAAHAAAAcAAABwAAAHAAAAcAAABwAAAOAAAB4B///AH//4Af/+AAAAAAAAAAAQAAABwAAAHwAAAPwAAAfwAAAfgAAAfgAAAf/wAB//AAf/8AH8AAA/AAAPwAAB8AAAHAAAAQAAAAAAAAAAAAAf//8B///wH///ABwHAAHAcAAcBwABwHAAHAcAAcBwABwHAAHg8AAP/gAAf8AAA/gAAAAAAAAAAAAAAAA///AP//8A///wHgAAAcAAcBwABwHBwHAcHAcB4+BwD/4PAH954APn/gAAP8AAAOAAAAAAAAAAAAAD4AAcfwADz/gAfOOCBww4PHHBg+ccGAZxw4AHHDAAcYcAA//gAD//gAD/+AAAAAAAAAAAAAAAAAPgABx/AAPP+AB844AHDDgAccGAZxwYPnHDg8ccMDBxhwAD/+AAP/+AAP/4AAAAAAAAAAAAAAAAA+AAHH8AA8/4BnzjgOcMOBxxwYOHHBg4ccOBxxwwDnGHAGP/4AA//4AA//gAAAAAAAAAAAAAAAAHwAA4/gAHn/A8+ccDzhhwMOODA444MBjjhwHOOGAM4w4Dx//AOH//AAH/8AAAAAAAAAAAAAAAAAfAADj+AAef8Bz5xwHOGHAc44MADjgwAOOHAY44YBzjDgHH/8AAf/8AAf/wAAAAAAAAAAAAAAAAAfAADj+AAef8AD5xweOGHD844MMzjgwzOOHD844YHjjDgAH/8AAf/8AAf/wAAAAAAAAAAAAAAAAHwAAx/gAHn/AAc4cADjhwAOMDAA4wcADjBwAOMHAA4w4AB//AAH/4AAP/wAB/fgAPMPAA4wcADjBwAOMHAA4wcADjBwAPMPAAfx4AA/HAAB8YAAAAAAAAAAAA/AAAP/AAB/+AAPA8AB4B4AHADgwcAPzBwA/8HADngcAOMB4B4ADwPAAHA4AAMDAAAAAAAAAAAAA/AAAP/AAB/+AAPY8AB5h4OHGDg+cYOB5xg4AnGDgAcYOAB5h4AD+PAAH44AAPjAAAGAAAAAAAAAAAAAPwAAD/wAAf/gAD2PAAeYeABxg4AHGDgOcYOD5xg4OHGDggeYeAA/jwAB+OAAD4wAABgAAAAAAAAAAAAD8AAA/8AAH/4AY9jwDnmHgecYODhxg4OHGDg8cYOB5xg4BnmHgCP48AAfjgAA+MAAAYAAAAAAAAAAAAB+AAAf+AAD/8Acex4BzzDwHOMHAA4wcADjBwAOMHAc4wcBzzDwGH8eAAPxwAAfGAAAMAAAAAAAAAAAOAAAA+f/+A5//4An//gAAAAAAAAAAAAAAAAAAAJ//4Dn//g+f/+DgAAAAAAAAMAAABwAAAOP//Aw//8Dj//wHAAAAMAAABwAAAHAAAAA//8AD//wAP//AcAAABwAAAAAAAAAA/gAAP/AAB//AAPA8AA4A4DDgDgPMAOA/wA4D7ADgPOAOB+8B4C/+/AA//4AB//AAAHAAAAAAAAAAAAP//AA//8Bx//wPHAAAw4AADjgAAGOAAAc4AAAzgAAPPAAA4f/8AA//wAB//AAAAAAAAAAAAAAAAA/AAAP/AAB/+AAPA8CB4B4OHADg+cAOA5wA4AHADgAcAOAB4B4AD4fAAH/4AAP/AAAPwAAAAAAAAAAAAPwAAD/wAAf/gADwPAAeAeABwA4AnADgecAOD5wA4OHADgAeAeAA+HwAB/+AAD/wAAD8AAAAAAAAAAAAD8AAA/8AAH/4AY8DwDngHgecAODhwA4OHADg8cAOB5wA4BngHgCPh8AAf/gAA/8AAA/AAAAAAAAAAAAB+AAAf+AAD/8AceB4DzwDwMOAHA44AcBjgBwHOAHAM4AcDzwDwOHw+AAP/wAAf+AAAfgAAAAAAAAAAAAfgAAH/gAA//AHHgeAc8A8BzgBwAOAHAA4AcADgBwHOAHAc8A8Bh8PgAD/8AAH/gAAH4AAAAAAAAAAAAMAAAAwAAADAAAAMAAAAwAAADAAADtwAAO3AAA7cAAAMAAAAwAAADAAAAMAAAAAAAAAAAAAH5gAB//AAP/4AB4PgAPB/AA4PcADh5wAOPHAA54cADvBwAP4PAAfD4AB//AAP/4AAZ+AAAAAAAAAAAAf8AAB//AAH//AAAH8AAAB4OAADg+AAOB4AA4AgADgAAAOAAABwAH//gAf/+AB//4AAAAAAAAAAAAAAAH/AAAf/wAB//wAAB/AAAAeAAAA4BgADgeAAOD4AA4MAADgAAAcAB//4AH//gAf/+AAAAAAAAAAAAAAAB/wAAH/8AAf/8AYAfwDgAHgcAAODgAA4OAADg8AAOB4AA4BgAHACf/+AB//4AH//gAAAAAAAAAAAAAAA/4AAD/+AAP/+AcAP4BwADwHAAHAAAAcAAABwAAAHAcAAcBwADgGP//AA//8AD//wAAAAAAAAAABAAAAHAAAAfgAAA/wAAA/wAAAf4cAAP/zgAP/+AB/jgA/wAAf4AAP+AAB/AAAHgAAAQAAAAAAAAAAAA//////////////A4BwAHADgAcAOABwA4AHADgAcAOAB4B4AD4fAAH/4AAP/AAAPwAAAAAABAAAAHAAAAfgAAw/wADg/wA+Af4fAAP/8AAP/AAB/g4A/wDgf4AOP+AAB/AAAHgAAAQAAA=='), + 32, + atob("BgkMGhEZEgYMDAwQCAwICxILEBAREBEOEREJCREVEQ8ZEhEUExAOFBQHDREPGBMUERQSEhEUERsREBIMCwwTEg4QERAREQoREQcHDgcYEREREQoPDBEPFg8PDwwIDBMcCgoAAAAAAAAAAAAAACERESEAAAAAAAAAAAAAAAAhIQAGCRAQEhAIDw8XCQ8RABIODRELCw4REwcLCQoPHBscDxISEhISEhoUEBAQEAcHBwcTExQUFBQUDhQUFBQUEBEREBAQEBAQGhARERERBwcHBxAREREREREPEREREREPEQ8="), + 28+(scale<<8)+(1<<16) + ); + return this; +}; + +Graphics.prototype.setMiniFont = function(scale) { + // Actual height 16 (15 - 0) + this.setFontCustom( + atob('AAAAAAAAAAAAAP+w/5AAAAAA4ADgAOAA4AAAAAAAAAABgBmAGbAb8D+A+YDZ8B/wf4D5gJmAGQAQAAAAAAAeOD8cMwzxj/GPMYwc/Az4AAAAAHAA+DDIYMjA+YBzAAYADeA7MHMw4zDD4ADAAAAz4H/wzjDHMMMwwbBj4APgADAAAAAA4ADgAAAAAAAAAAfwH/54B+ABAAAAAOABeAcf/gfwAAAAACAAaAD4APgAOABgAAAAAAACAAIAAgA/wAMAAgACAAAAAAAAPAA4AAAAAAIAAgACAAIAAgAAAAAAADAAMAAAAAAAcAfwf4D4AIAAAAA/wH/gwDDAMMAwwDB/4D/AAAAAAGAAwAD/8P/wAAAAAHAw8HDA8MHww7DnMH4wGBAAAMBgyHDcMPww/DDv4MfAAAAAAAHgD+A+YPhgwGAH8AfwAEAAAAAA/GD8cMwwzDDMMM5wx+ABgAAAP8B/4MwwzDDMMMwwx+ADwAAAgADAAMBwwfDPgP4A8ADAAAAAe+D/8M4wxjDGMP5wf+ABwAAAfAB+cMYwwjDCMMYwf+A/wAAAAAAAAAxgBCAAAAAAAAAYPBA4AAAAAAAAAgAHAA+AHMAYYAAAAAAAAAAAAAAJAAkACQAJAAkACQAJAAkAAAAAAAAAAAAAABhgHMAPgAcAAgAAAAAAAABgAOAAwbDDsMYA/AA4AAAAAAAD4A/wGBgxzDPsMyQjJDPkM+wYIBxgD+AAAAAAABAA8A/gf8DwwODA/sAfwAHwADAAAP/w//DGMMYwxjDOMP9we+ABwA8AP8Bw4MAwwDDAMMAwwDDgcHDgMMAAAAAA//D/8MAwwDDAMMAw4HB/4D/AAAAAAP/w//DGMMYwxjDGMMQwgBAAAP/w//DDAMMAwwDDAMAADwA/wHDgwDDAMMAwwDDCMOJwc+ADwAAA//D/8AMAAwADAAMAAwD/8P/wAAAAAP/w//AAAABgAHAAMAAwAHD/4P+AAAAAAP/w//AOAB+AOcBw4MBwgDAAEAAA//D/8AAwADAAMAAwADAAAP/w//A8AA8AA+AA8AHwB8AeAHgA//D/8AAAAAD/8P/wcAAcAA8AA4AB4P/w//AAAA8AP8Bw4MAwwDDAMMAwwDDgcH/gP8AAAAAA//D/8MMAwwDDAMYA7gB8ABgADwA/wHDgwDDAMMAwwDDA8ODwf/A/8AAAAAD/8P/wwwDDAMMAx4Dv4HxwEBAAAHjg/HDMMMYwxjDGMONwc+ABwMAAwADAAMAA//D/8MAAwADAAIAAAAD/wP/gAHAAMAAwADAAMAHg/8AAAMAA+AA/AAfgAPAA8AfgPwD4AMAAwAD4AD+AA/AA8A/g/gDwAP4AH8AB8APwH8D8AMAAgBDAMPDgO8APAB8AOcDw8MAwgBCAAOAAeAAeAAfwH/B4AOAAwAAAAMAwwPDB8Mew3jD4MPAwwDAAAAAAAAB//3//QABAAAAAAADgAP4AH+AB8AAQAABAAEAAf/9//wAAAAAAAAAAGAAwAGAAwABgADAAGAAAAAAAAAQABAAEAAQABAAEAAQABAAEAAQABAAAAAAAAAAAAAAAAAAAAAAAQA3wHbAZMBswGzAf4A/wAAAAAP/w//AYMBgwGDAYMA/gB8AAAAEAD+Ae8BgwGDAYMBgwDGAAAAMAD+Ae8BgwGDAYMBhw//D/8AAAAYAP4B/wGTAZMBkwGTAP4AcAEAAYAP/w//CQAJAAAwAP4hz3GDMQMxAzGHcf/h/8AAAAAP/w//AYABgAGAAYAA/wB/AAAAAA3/Df8AAAAAOf/9//AAAAAP/w//ADgAfADGAYMBAQAAD/8P/wAAAAAB/wH/AYABgAGAAf8A/wGAAYABgAH/AP8AAAAAAf8B/wGAAYABgAGAAP8AfwAAADAA/gHvAYMBgwGDAYMA/gB8AAAAAAH/8f/xgwGDAYMBgwD+AHwAAAAwAP4B7wGDAYMBgwGHAf/x//AAAAAB/wH/AYABgAEAAAAA5gHzAbMBkwGbAd8AzgEAAYAP/wf/AQMBAwAAAAAB/gH/AAMAAwADAAcB/wH/AAABAAHgAPwAHwAPAH4B8AGAAQAB8AB+AA8APwHwAeAA/AAPAD8B+AHAAQEBgwHOAHwAOAD+AccBAwAAAQAB4AD4EB/wB8A/APgBwAAAAAEBgwGPAZ8B8wHjAcMBAQAAAAAABgf/9/n2AAAAAAAP/w//AAAEAAYAB/nz//AGAAAAAAAAAAAAcABgAGAAcAAwAHAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'), + 32, + atob("AwUHDwoOCwQHBwcJBAcEBgoGCQkKCQoICQoFBQoMCgkPCgoMCwkICwsECAoIDgsMCgwKCgoLCg8KCQoHBgcLCwgJCgkKCQYKCgQECAQOCgoKCgYIBwoIDAkJCAcEBwsQ"), + 16+(scale<<8)+(1<<16) + ); + return this; +}; + +function imgLock(){ + return { + width : 16, height : 16, bpp : 1, + transparent : 0, + buffer : E.toArrayBuffer(atob("A8AH4A5wDDAYGBgYP/w//D/8Pnw+fD58Pnw//D/8P/w=")) + } +} + + +/************************************************ + * Menu + */ +// Custom bwItems menu - therefore, its added here and not in a clkinfo.js file. +var bwItems = { + name: null, + img: null, + items: [ + { name: "WeekOfYear", + get: () => ({ text: "Week " + weekOfYear(), img: null}), + show: function() { bwItems.items[0].emit("redraw"); }, + hide: function () {} + }, + ] +}; + +function weekOfYear() { + var date = new Date(); + date.setHours(0, 0, 0, 0); + // Thursday in current week decides the year. + date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7); + // January 4 is always in week 1. + var week1 = new Date(date.getFullYear(), 0, 4); + // Adjust to Thursday in week 1 and count number of weeks from date to week1. + return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000 + - 3 + (week1.getDay() + 6) % 7) / 7); +} + + +// Load menu +var menu = clock_info.load(); +menu = menu.concat(bwItems); + + +// Ensure that our settings are still in range (e.g. app uninstall). Otherwise reset the position it. +if(settings.menuPosX >= menu.length || settings.menuPosY > menu[settings.menuPosX].items.length ){ + settings.menuPosX = 0; + settings.menuPosY = 0; +} + +// Set draw functions for each item +menu.forEach((menuItm, x) => { + menuItm.items.forEach((item, y) => { + function drawItem() { + // For the clock, we have a special case, as we don't wanna redraw + // immediately when something changes. Instead, we update data each minute + // to save some battery etc. Therefore, we hide (and disable the listener) + // immedeately after redraw... + item.hide(); + + // After drawing the item, we enable inputs again... + lock_input = false; + + var info = item.get(); + drawMenuItem(info.text, info.img); + } + + item.on('redraw', drawItem); + }) +}); + + +function canRunMenuItem(){ + if(settings.menuPosY == 0){ + return false; + } + + var menuEntry = menu[settings.menuPosX]; + var item = menuEntry.items[settings.menuPosY-1]; + return item.run !== undefined; +} + + +function runMenuItem(){ + if(settings.menuPosY == 0){ + return; + } + + var menuEntry = menu[settings.menuPosX]; + var item = menuEntry.items[settings.menuPosY-1]; + try{ + var ret = item.run(); + if(ret){ + Bangle.buzz(300, 0.6); + } + } catch (ex) { + // Simply ignore it... + } +} + + +/************************************************ + * Draw + */ +function draw() { + // Queue draw again + queueDraw(); + + // Draw clock + drawDate(); + drawMenuAndTime(); + drawLock(); + drawWidgets(); +} + + +function drawDate(){ + // Draw background + var y = H/5*2 + (isFullscreen() ? 0 : 8); + g.reset().clearRect(0,0,W,y); + + // Draw date + y = parseInt(y/2)+4; + y += isFullscreen() ? 0 : 8; + var date = new Date(); + var dateStr = date.getDate(); + dateStr = ("0" + dateStr).substr(-2); + g.setMediumFont(); // Needed to compute the width correctly + var dateW = g.stringWidth(dateStr); + + g.setSmallFont(); + var dayStr = locale.dow(date, true); + var monthStr = locale.month(date, 1); + var dayW = Math.max(g.stringWidth(dayStr), g.stringWidth(monthStr)); + var fullDateW = dateW + 10 + dayW; + + g.setFontAlign(-1,0); + g.drawString(dayStr, W/2 - fullDateW/2 + 10 + dateW, y-12); + g.drawString(monthStr, W/2 - fullDateW/2 + 10 + dateW, y+11); + + g.setMediumFont(); + g.setColor(g.theme.fg); + g.drawString(dateStr, W/2 - fullDateW / 2, y+2); +} + + +function drawTime(y, smallText){ + // Draw background + var date = new Date(); + + // Draw time + g.setColor(g.theme.bg); + g.setFontAlign(0,0); + + var hours = String(date.getHours()); + var minutes = date.getMinutes(); + minutes = minutes < 10 ? String("0") + minutes : minutes; + var colon = settings.hideColon ? "" : ":"; + var timeStr = hours + colon + minutes; + + // Set y coordinates correctly + y += parseInt((H - y)/2) + 5; + + // Show large or small time depending on info entry + if(smallText){ + y -= 15; + g.setMediumFont(); + } else { + g.setLargeFont(); + } + g.drawString(timeStr, W/2, y); +} + +function drawMenuItem(text, image){ + // First clear the time region + var y = H/5*2 + (isFullscreen() ? 0 : 8); + + g.setColor(g.theme.fg); + g.fillRect(0,y,W,H); + + // Draw menu text + var hasText = (text != null && text != ""); + if(hasText){ + g.setFontAlign(0,0); + + // For multiline text we show an even smaller font... + text = String(text); + if(text.split('\n').length > 1){ + g.setMiniFont(); + } else { + g.setSmallFont(); + } + + var imgWidth = image == null ? 0 : 24; + var strWidth = g.stringWidth(text); + g.setColor(g.theme.fg).fillRect(0, 149-14, W, H); + g.setColor(g.theme.bg).drawString(text, W/2 + imgWidth/2 + 2, 149+3); + + if(image != null){ + var scale = imgWidth / image.width; + g.drawImage(image, W/2 + -strWidth/2-4 - parseInt(imgWidth/2), 149 - parseInt(imgWidth/2), {scale: scale}); + } + } + + // Draw time + drawTime(y, hasText); +} + + +function drawMenuAndTime(){ + var menuEntry = menu[settings.menuPosX]; + + // The first entry is the overview... + if(settings.menuPosY == 0){ + drawMenuItem(menuEntry.name, menuEntry.img); + return; + } + + // Draw item if needed + lock_input = true; + var item = menuEntry.items[settings.menuPosY-1]; + item.show(); +} + + +function drawLock(){ + if(settings.showLock && Bangle.isLocked()){ + g.setColor(g.theme.fg); + g.drawImage(imgLock(), W-16, 2); + } +} + + +function drawWidgets(){ + if(isFullscreen()){ + for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} + } else { + Bangle.drawWidgets(); + } +} + + +function isFullscreen(){ + var s = settings.screen.toLowerCase(); + if(s == "dynamic"){ + return Bangle.isLocked() + } else { + return s == "full" + } +} + + + +/************************************************ + * Listener + */ +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +Bangle.on('lock', function(isLocked) { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + + if(!isLocked && settings.screen.toLowerCase() == "dynamic"){ + // If we have to show the widgets again, we load it from our + // cache and not through Bangle.loadWidgets as its much faster! + for (let wd of WIDGETS) {wd.draw=wd._draw;wd.area=wd._area;} + } + + draw(); +}); + +Bangle.on('charging',function(charging) { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + + // Jump to battery + settings.menuPosX = 0; + settings.menuPosY = 1; + draw(); +}); + +Bangle.on('touch', function(btn, e){ + var widget_size = isFullscreen() ? 0 : 20; // Its not exactly 24px -- empirically it seems that 20 worked better... + var left = parseInt(g.getWidth() * 0.22); + var right = g.getWidth() - left; + var upper = parseInt(g.getHeight() * 0.22) + widget_size; + var lower = g.getHeight() - upper; + + var is_upper = e.y < upper; + var is_lower = e.y > lower; + var is_left = e.x < left && !is_upper && !is_lower; + var is_right = e.x > right && !is_upper && !is_lower; + var is_center = !is_upper && !is_lower && !is_left && !is_right; + + if(lock_input){ + return; + } + + if(is_lower){ + Bangle.buzz(40, 0.6); + settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1); + + drawMenuAndTime(); + } + + if(is_upper){ + if(e.y < widget_size){ + return; + } + + Bangle.buzz(40, 0.6); + settings.menuPosY = settings.menuPosY-1; + settings.menuPosY = settings.menuPosY < 0 ? menu[settings.menuPosX].items.length : settings.menuPosY; + + drawMenuAndTime(); + } + + if(is_right){ + Bangle.buzz(40, 0.6); + settings.menuPosX = (settings.menuPosX+1) % menu.length; + settings.menuPosY = 0; + drawMenuAndTime(); + } + + if(is_left){ + Bangle.buzz(40, 0.6); + settings.menuPosY = 0; + settings.menuPosX = settings.menuPosX-1; + settings.menuPosX = settings.menuPosX < 0 ? menu.length-1 : settings.menuPosX; + drawMenuAndTime(); + } + + if(is_center){ + if(canRunMenuItem()){ + runMenuItem(); + } + } +}); + + +E.on("kill", function(){ + try{ + storage.write(SETTINGS_FILE, settings); + } catch(ex){ + // If this fails, we still kill the app... + } +}); + + +/************************************************ + * Startup Clock + */ + +// The upper part is inverse i.e. light if dark and dark if light theme +// is enabled. In order to draw the widgets correctly, we invert the +// dark/light theme as well as the colors. +g.setTheme({bg:g.theme.fg,fg:g.theme.bg, dark:!g.theme.dark}).clear(); + +// Show launcher when middle button pressed +Bangle.setUI("clock"); + +// Load widgets and draw clock the first time +Bangle.loadWidgets(); + +// Cache draw function for dynamic screen to hide / show widgets +// Bangle.loadWidgets() could also be called later on but its much slower! +for (let wd of WIDGETS) {wd._draw=wd.draw; wd._area=wd.area;} + +// Draw first time +draw(); diff --git a/apps/bwclk/app.png b/apps/bwclk/app.png new file mode 100644 index 000000000..5073f0ed0 Binary files /dev/null and b/apps/bwclk/app.png differ diff --git a/apps/bwclk/metadata.json b/apps/bwclk/metadata.json new file mode 100644 index 000000000..fa0f7b01f --- /dev/null +++ b/apps/bwclk/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "bwclk", + "name": "BW Clock", + "version": "0.23", + "description": "A very minimalistic clock to mainly show date and time.", + "readme": "README.md", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}, {"url":"screenshot_4.png"}], + "type": "clock", + "tags": "clock,clkinfo", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"bwclk.app.js","url":"app.js"}, + {"name":"bwclk.img","url":"app-icon.js","evaluate":true}, + {"name":"bwclk.settings.js","url":"settings.js"} + ] +} diff --git a/apps/bwclk/screenshot.png b/apps/bwclk/screenshot.png new file mode 100644 index 000000000..3a75f13d1 Binary files /dev/null and b/apps/bwclk/screenshot.png differ diff --git a/apps/bwclk/screenshot_2.png b/apps/bwclk/screenshot_2.png new file mode 100644 index 000000000..31bf6373e Binary files /dev/null and b/apps/bwclk/screenshot_2.png differ diff --git a/apps/bwclk/screenshot_3.png b/apps/bwclk/screenshot_3.png new file mode 100644 index 000000000..8d982cac4 Binary files /dev/null and b/apps/bwclk/screenshot_3.png differ diff --git a/apps/bwclk/screenshot_4.png b/apps/bwclk/screenshot_4.png new file mode 100644 index 000000000..83de5c2ce Binary files /dev/null and b/apps/bwclk/screenshot_4.png differ diff --git a/apps/bwclk/settings.js b/apps/bwclk/settings.js new file mode 100644 index 000000000..116253fda --- /dev/null +++ b/apps/bwclk/settings.js @@ -0,0 +1,50 @@ +(function(back) { + const SETTINGS_FILE = "bwclk.setting.json"; + + // initialize with default settings... + const storage = require('Storage') + let settings = { + screen: "Normal", + showLock: true, + hideColon: false, + }; + let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; + for (const key in saved_settings) { + settings[key] = saved_settings[key] + } + + function save() { + storage.write(SETTINGS_FILE, settings) + } + + var screenOptions = ["Normal", "Dynamic", "Full"]; + E.showMenu({ + '': { 'title': 'BW Clock' }, + '< Back': back, + 'Screen': { + value: 0 | screenOptions.indexOf(settings.screen), + min: 0, max: 2, + format: v => screenOptions[v], + onchange: v => { + settings.screen = screenOptions[v]; + save(); + }, + }, + 'Show Lock': { + value: settings.showLock, + format: () => (settings.showLock ? 'Yes' : 'No'), + onchange: () => { + settings.showLock = !settings.showLock; + save(); + }, + }, + 'Hide Colon': { + value: settings.hideColon, + format: () => (settings.hideColon ? 'Yes' : 'No'), + onchange: () => { + settings.hideColon = !settings.hideColon; + save(); + }, + } + }); + }) diff --git a/apps/calclock/ChangeLog b/apps/calclock/ChangeLog new file mode 100644 index 000000000..5c1b7c4bc --- /dev/null +++ b/apps/calclock/ChangeLog @@ -0,0 +1,5 @@ +0.01: Initial version +0.02: More compact rendering & app icon +0.03: Tell clock widgets to hide. +0.04: Improve current time readability in light theme. +0.05: Show calendar colors & improved all day events. diff --git a/apps/calclock/README.md b/apps/calclock/README.md new file mode 100644 index 000000000..2b4e93a0c --- /dev/null +++ b/apps/calclock/README.md @@ -0,0 +1,9 @@ +# Calendar Clock - Your day at a glance + +This clock shows a chronological view of your current and future events. +It uses events synced from Gadgetbridge to achieve this. + +The current time and date is highlighted in cyan. + +## Screenshot +![](screenshot.png) diff --git a/apps/calclock/calclock-icon.js b/apps/calclock/calclock-icon.js new file mode 100644 index 000000000..9d5514d80 --- /dev/null +++ b/apps/calclock/calclock-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwgpm5gAB4AVRhgWCAAQWWDCARC/4ACJR4uB54WDAAP8DBotFGIgXLFwv4GAouQC4gwMLooXF/gXJOowXGJBIXBCIgXQxgXLMAIXXMAmIC5OIx4XJhH/wAXIxnIC78IxGIHoIABI44MBC4wQBEQIDB5gXGPAJgEC6IxBC5oABC4wwDa4YTCxAWD5nPDAzvGFYgAB5AXWJBK+GcAq5CGBIuBC5X4GBIJBdoQXB/GIx4CDPJAuEC5JoCDAgWBFwYXJxCBIFwYXKYwoACCwZ3IPQoWIC5YABGYIABCwpHKAQYMBCwwX/C5QAMC8R3/R/4XNhAXNwAXHgGIABgWIAFwA==")) diff --git a/apps/calclock/calclock.js b/apps/calclock/calclock.js new file mode 100644 index 000000000..5a13a202f --- /dev/null +++ b/apps/calclock/calclock.js @@ -0,0 +1,134 @@ +var calendar = []; +var current = []; +var next = []; + +function updateCalendar() { + calendar = require("Storage").readJSON("android.calendar.json",true)||[]; + calendar = calendar.filter(e => isActive(e) || getTime() <= e.timestamp); + calendar.sort((a,b) => a.timestamp - b.timestamp); + + current = calendar.filter(isActive); + next = calendar.filter(e=>!isActive(e)); +} + +function isActive(event) { + var timeActive = getTime() - event.timestamp; + return timeActive >= 0 && timeActive <= event.durationInSeconds; +} +function zp(str) { + return ("0"+str).substr(-2); +} + +function drawEventHeader(event, y) { + var x = 0; + var time = isActive(event) ? new Date() : new Date(event.timestamp * 1000); + + //Don't need to know what time the event is at if its all day + if (isActive(event) || !event.allDay) { + g.setFont("Vector", 24); + var timeStr = zp(time.getHours()) + ":" + zp(time.getMinutes()); + g.drawString(timeStr, 0, y); + y += 3; + x = 13*timeStr.length+5; + } + + g.setFont("12x20", 1); + + if (isActive(event)) { + g.drawString(zp(time.getDate())+". " + require("locale").month(time,1),x,y); + } else { + var offset = 0-time.getTimezoneOffset()/1440; + var days = Math.floor((time.getTime()/1000)/86400+offset)-Math.floor(getTime()/86400+offset); + if(days > 0 || event.allDay) { + var daysStr = days===1?/*LANG*/"tomorrow":/*LANG*/"in "+days+/*LANG*/" days"; + g.drawString(daysStr,x,y); + } + } + y += 21; + return y; +} + +function drawEventBody(event, y) { + g.setFont("12x20", 1); + var lines = g.wrapString(event.title, g.getWidth()-15); + var yStart = y; + if (lines.length > 2) { + lines = lines.slice(0,2); + lines[1] = lines[1].slice(0,-3)+"..."; + } + g.drawString(lines.join('\n'),10,y); + y+=20 * lines.length; + if(event.location) { + g.drawImage(atob("DBSBAA8D/H/nDuB+B+B+B3Dn/j/B+A8A8AYAYAYAAAAAAA=="),10,y); + g.drawString(event.location,25,y); + y+=20; + } + if (event.color) { + var oldColor = g.getColor(); + g.setColor("#"+(0x1000000+Number(event.color)).toString(16).padStart(6,"0")); + g.fillRect(0,yStart,5,y-3); + g.setColor(oldColor); + } + y+=5; + return y; +} + +function drawEvent(event, y) { + y = drawEventHeader(event, y); + y = drawEventBody(event, y); + return y; +} + +var curEventHeight = 0; + +function drawCurrentEvents(y) { + g.setColor(g.theme.dark ? "#0ff" : "#00f"); + g.clearRect(0,y,g.getWidth()-5,y+curEventHeight); + curEventHeight = y; + + if(current.length === 0) { + y = drawEvent({timestamp: getTime(), durationInSeconds: 100}, y); + } else { + y = drawEventHeader(current[0],y); + for (var e of current) { + y = drawEventBody(e,y); + } + } + curEventHeight = y-curEventHeight; + return y; +} + +function drawFutureEvents(y) { + g.setColor(g.theme.fg); + for (var e of next) { + y = drawEvent(e, y); + if(y>g.getHeight())break; + } + return y; +} + +function fullRedraw() { + g.clearRect(0,24,g.getWidth()-5,g.getHeight()); + updateCalendar(); + var y = 30; + y = drawCurrentEvents(y); + drawFutureEvents(y); +} + +function redraw() { + g.reset(); + if (current.find(e=>!isActive(e)) || next.find(isActive)) { + fullRedraw(); + } else { + drawCurrentEvents(30); + } +} + +g.clear(); +fullRedraw(); +var minuteInterval = setInterval(redraw, 60 * 1000); + +Bangle.setUI("clock"); +Bangle.loadWidgets(); +Bangle.drawWidgets(); + diff --git a/apps/calclock/calclock.png b/apps/calclock/calclock.png new file mode 100644 index 000000000..5f953c1ee Binary files /dev/null and b/apps/calclock/calclock.png differ diff --git a/apps/calclock/location.png b/apps/calclock/location.png new file mode 100644 index 000000000..619e55775 Binary files /dev/null and b/apps/calclock/location.png differ diff --git a/apps/calclock/metadata.json b/apps/calclock/metadata.json new file mode 100644 index 000000000..be0a1bdd8 --- /dev/null +++ b/apps/calclock/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "calclock", + "name": "Calendar Clock", + "shortName": "CalClock", + "version": "0.05", + "description": "Show the current and upcoming events synchronized from Gadgetbridge", + "icon": "calclock.png", + "type": "clock", + "tags": "clock agenda", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"calclock.app.js","url":"calclock.js"}, + {"name":"calclock.img","url":"calclock-icon.js","evaluate":true} + ], + "screenshots": [{"url":"screenshot.png"}] +} diff --git a/apps/calclock/screenshot.patch b/apps/calclock/screenshot.patch new file mode 100644 index 000000000..3fdbf79d1 --- /dev/null +++ b/apps/calclock/screenshot.patch @@ -0,0 +1,32 @@ +diff --git a/apps/calclock/calclock.js b/apps/calclock/calclock.js +index cb8c6100e..2092c1a4e 100644 +--- a/apps/calclock/calclock.js ++++ b/apps/calclock/calclock.js +@@ -3,9 +3,24 @@ var current = []; + var next = []; + + function updateCalendar() { +- calendar = require("Storage").readJSON("android.calendar.json",true)||[]; +- calendar = calendar.filter(e => isActive(e) || getTime() <= e.timestamp); +- calendar.sort((a,b) => a.timestamp - b.timestamp); ++ calendar = [ ++ { ++ t: "calendar", ++ id: 2, type: 0, timestamp: getTime(), durationInSeconds: 200, ++ title: "Capture Screenshot", ++ description: "Capture Screenshot", ++ location: "", ++ calName: "", ++ color: -7151168, allDay: true }, ++ { ++ t: "calendar", ++ id: 7186, type: 0, timestamp: getTime() + 2000, durationInSeconds: 100, ++ title: "Upload to BangleApps", ++ description: "", ++ location: "", ++ calName: "", ++ color: -509406, allDay: false } ++ ]; + + current = calendar.filter(isActive); + next = calendar.filter(e=>!isActive(e)); diff --git a/apps/calclock/screenshot.png b/apps/calclock/screenshot.png new file mode 100644 index 000000000..8b2e39784 Binary files /dev/null and b/apps/calclock/screenshot.png differ diff --git a/apps/calculator/ChangeLog b/apps/calculator/ChangeLog index a08a0f5a7..2e1ace7bf 100644 --- a/apps/calculator/ChangeLog +++ b/apps/calculator/ChangeLog @@ -3,3 +3,5 @@ 0.03: Support for different screen sizes and touchscreen 0.04: Display current operation on LHS 0.05: Grid positioning and swipe controls to switch between numbers, operators and special (for Bangle.js 2) +0.06: Bangle.js 2: Exit with a short press of the physical button +0.07: Bangle.js 2: Exit by pressing upper left corner of the screen diff --git a/apps/calculator/README.md b/apps/calculator/README.md index b25d355bf..62f6cef24 100644 --- a/apps/calculator/README.md +++ b/apps/calculator/README.md @@ -12,12 +12,20 @@ Basic calculator reminiscent of MacOs's one. Handy for small calculus. ## Controls +Bangle.js 1 - UP: BTN1 - DOWN: BTN3 - LEFT: BTN4 - RIGHT: BTN5 - SELECT: BTN2 +Bangle.js 2 +- Swipes to change visible buttons +- Click physical button to exit +- Press upper left corner of screen to exit (where the red back button would be) ## Creator + +## Contributors +[thyttan](https://github.com/thyttan) diff --git a/apps/calculator/app.js b/apps/calculator/app.js index 40953254e..d9a89a989 100644 --- a/apps/calculator/app.js +++ b/apps/calculator/app.js @@ -3,6 +3,8 @@ * * Original Author: Frederic Rousseau https://github.com/fredericrous * Created: April 2020 + * + * Contributors: thyttan https://github.com/thyttan */ g.clear(); @@ -402,43 +404,42 @@ if (process.env.HWVERSION==1) { swipeEnabled = false; drawGlobal(); } else { // touchscreen? - selected = "NONE"; + selected = "NONE"; swipeEnabled = true; prepareScreen(numbers, numbersGrid, COLORS.DEFAULT); prepareScreen(operators, operatorsGrid, COLORS.OPERATOR); prepareScreen(specials, specialsGrid, COLORS.SPECIAL); drawNumbers(); - Bangle.on('touch',(n,e)=>{ - for (var key in screen) { - if (typeof screen[key] == "undefined") break; - var r = screen[key].xy; - if (e.x>=r[0] && e.y>=r[1] && - e.x{ + for (var key in screen) { + if (typeof screen[key] == "undefined") break; + var r = screen[key].xy; + if (e.x>=r[0] && e.y>=r[1] && e.x { - if (!e.b) { - if (lastX > 50) { // right + }, + swipe : (LR, UD) => { + if (LR == 1) { // right drawSpecials(); - } else if (lastX < -50) { // left + } + if (LR == -1) { // left drawOperators(); - } else if (lastY > 50) { // down - drawNumbers(); - } else if (lastY < -50) { // up + } + if (UD == 1) { // down + drawNumbers(); + } + if (UD == -1) { // up drawNumbers(); } - lastX = 0; - lastY = 0; - } else { - lastX = lastX + e.dx; - lastY = lastY + e.dy; } }); + } - displayOutput(0); diff --git a/apps/calculator/metadata.json b/apps/calculator/metadata.json index 3d1310859..1674b7843 100644 --- a/apps/calculator/metadata.json +++ b/apps/calculator/metadata.json @@ -2,12 +2,13 @@ "id": "calculator", "name": "Calculator", "shortName": "Calculator", - "version": "0.05", + "version": "0.07", "description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.", "icon": "calculator.png", "screenshots": [{"url":"screenshot_calculator.png"}], "tags": "app,tool", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "storage": [ {"name":"calculator.app.js","url":"app.js"}, {"name":"calculator.img","url":"calculator-icon.js","evaluate":true} diff --git a/apps/calendar/ChangeLog b/apps/calendar/ChangeLog index beba4ed95..db455679c 100644 --- a/apps/calendar/ChangeLog +++ b/apps/calendar/ChangeLog @@ -4,3 +4,9 @@ 0.04: Add setting to switch color schemes. On Bangle 2 non-dithering colors will be used by default. Use localized names for months and days of the week (Language app needed). 0.05: Update calendar weekend colors for start on Sunday 0.06: Use larger font for dates +0.07: Fix off-by-one-error on previous month +0.08: Do not register as watch, manually start clock on button + read start of week from system settings +0.09: Fix scope of let variables +0.10: Use default Bangle formatter for booleans +0.11: Fix off-by-one-error on next year diff --git a/apps/calendar/calendar.js b/apps/calendar/calendar.js index 62702e349..f8785e52c 100644 --- a/apps/calendar/calendar.js +++ b/apps/calendar/calendar.js @@ -16,10 +16,15 @@ const white = "#ffffff"; const red = "#d41706"; const blue = "#0000ff"; const yellow = "#ffff00"; +let bgColor = color4; +let bgColorMonth = color1; +let bgColorDow = color2; +let bgColorWeekend = color3; +let fgOtherMonth = gray1; +let fgSameMonth = white; let settings = require('Storage').readJSON("calendar.json", true) || {}; -if (settings.startOnSun === undefined) - settings.startOnSun = false; +let startOnSun = ((require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0) === 0; if (settings.ndColors === undefined) if (process.env.HWVERSION == 2) { settings.ndColors = true; @@ -28,19 +33,12 @@ if (settings.ndColors === undefined) } if (settings.ndColors === true) { - let bgColor = white; - let bgColorMonth = blue; - let bgColorDow = black; - let bgColorWeekend = yellow; - let fgOtherMonth = blue; - let fgSameMonth = black; -} else { - let bgColor = color4; - let bgColorMonth = color1; - let bgColorDow = color2; - let bgColorWeekend = color3; - let fgOtherMonth = gray1; - let fgSameMonth = white; + bgColor = white; + bgColorMonth = blue; + bgColorDow = black; + bgColorWeekend = yellow; + fgOtherMonth = blue; + fgSameMonth = black; } function getDowLbls(locale) { @@ -50,14 +48,14 @@ function getDowLbls(locale) { case "de_AT": case "de_CH": case "de_DE": - if (settings.startOnSun) { + if (startOnSun) { dowLbls = ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"]; } else { dowLbls = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]; } break; case "nl_NL": - if (settings.startOnSun) { + if (startOnSun) { dowLbls = ["zo", "ma", "di", "wo", "do", "vr", "za"]; } else { dowLbls = ["ma", "di", "wo", "do", "vr", "za", "zo"]; @@ -66,14 +64,14 @@ function getDowLbls(locale) { case "fr_BE": case "fr_CH": case "fr_FR": - if (settings.startOnSun) { + if (startOnSun) { dowLbls = ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"]; } else { dowLbls = ["Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"]; } break; case "sv_SE": - if (settings.startOnSun) { + if (startOnSun) { dowLbls = ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"]; } else { dowLbls = ["Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"]; @@ -81,21 +79,21 @@ function getDowLbls(locale) { break; case "it_CH": case "it_IT": - if (settings.startOnSun) { + if (startOnSun) { dowLbls = ["Do", "Lu", "Ma", "Me", "Gi", "Ve", "Sa"]; } else { dowLbls = ["Lu", "Ma", "Me", "Gi", "Ve", "Sa", "Do"]; } break; case "oc_FR": - if (settings.startOnSun) { + if (startOnSun) { dowLbls = ["dg", "dl", "dm", "dc", "dj", "dv", "ds"]; } else { dowLbls = ["dl", "dm", "dc", "dj", "dv", "ds", "dg"]; } break; default: - if (settings.startOnSun) { + if (startOnSun) { dowLbls = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]; } else { dowLbls = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]; @@ -110,7 +108,7 @@ function drawCalendar(date) { g.clearRect(0, 0, maxX, maxY); g.setBgColor(bgColorMonth); g.clearRect(0, 0, maxX, headerH); - if (settings.startOnSun){ + if (startOnSun){ g.setBgColor(bgColorWeekend); g.clearRect(0, headerH + rowH, colW, maxY); g.setBgColor(bgColorDow); @@ -150,7 +148,7 @@ function drawCalendar(date) { }); date.setDate(1); - const dow = date.getDay() + (settings.startOnSun ? 1 : 0); + const dow = date.getDay() + (startOnSun ? 1 : 0); const dowNorm = dow === 0 ? 7 : dow; const monthMaxDayMap = { @@ -171,7 +169,7 @@ function drawCalendar(date) { let days = []; let nextMonthDay = 1; let thisMonthDay = 51; - let prevMonthDay = monthMaxDayMap[month > 0 ? month - 1 : 11] - dowNorm; + let prevMonthDay = monthMaxDayMap[month > 0 ? month - 1 : 11] - dowNorm + 1; for (let i = 0; i < colN * (rowN - 1) + 1; i++) { if (i < dowNorm) { days.push(prevMonthDay); @@ -228,19 +226,18 @@ drawCalendar(date); clearWatch(); Bangle.on("touch", area => { const month = date.getMonth(); - let prevMonth; if (area == 1) { let prevMonth = month > 0 ? month - 1 : 11; if (prevMonth === 11) date.setFullYear(date.getFullYear() - 1); date.setMonth(prevMonth); } else { - let prevMonth = month < 11 ? month + 1 : 0; - if (prevMonth === 0) date.setFullYear(date.getFullYear() + 1); - date.setMonth(month + 1); + let nextMonth = month < 11 ? month + 1 : 0; + if (nextMonth === 0) date.setFullYear(date.getFullYear() + 1); + date.setMonth(nextMonth); } drawCalendar(date); }); // Show launcher when button pressed -Bangle.setUI("clock"); // TODO: ideally don't set 'clock' mode +setWatch(() => load(), process.env.HWVERSION === 2 ? BTN : BTN3, { repeat: false, edge: "falling" }); // No space for widgets! diff --git a/apps/calendar/metadata.json b/apps/calendar/metadata.json index 5531c03c3..88f20026d 100644 --- a/apps/calendar/metadata.json +++ b/apps/calendar/metadata.json @@ -1,7 +1,7 @@ { "id": "calendar", "name": "Calendar", - "version": "0.06", + "version": "0.11", "description": "Simple calendar", "icon": "calendar.png", "screenshots": [{"url":"screenshot_calendar.png"}], diff --git a/apps/calendar/settings.js b/apps/calendar/settings.js index 3c8f7d8e8..54ed50a64 100644 --- a/apps/calendar/settings.js +++ b/apps/calendar/settings.js @@ -1,8 +1,6 @@ (function (back) { var FILE = "calendar.json"; var settings = require('Storage').readJSON(FILE, true) || {}; - if (settings.startOnSun === undefined) - settings.startOnSun = false; if (settings.ndColors === undefined) if (process.env.HWVERSION == 2) { settings.ndColors = true; @@ -17,17 +15,8 @@ E.showMenu({ "": { "title": "Calendar" }, "< Back": () => back(), - 'Start Sunday': { - value: settings.startOnSun, - format: v => v ? "Yes" : "No", - onchange: v => { - settings.startOnSun = v; - writeSettings(); - } - }, 'B2 Colors': { value: settings.ndColors, - format: v => v ? "Yes" : "No", onchange: v => { settings.ndColors = v; writeSettings(); diff --git a/apps/calibration/ChangeLog b/apps/calibration/ChangeLog new file mode 100644 index 000000000..64bff2b31 --- /dev/null +++ b/apps/calibration/ChangeLog @@ -0,0 +1,3 @@ +0.01: New App! +0.02: Use fractional numbers and scale the points to keep working consistently on whole screen +0.03: Use default Bangle formatter for booleans diff --git a/apps/calibration/README.md b/apps/calibration/README.md new file mode 100644 index 000000000..ed1a29d9e --- /dev/null +++ b/apps/calibration/README.md @@ -0,0 +1,12 @@ +# Banglejs - Touchscreen calibration +A simple calibration app for the touchscreen + +## Usage + +Once lauched touch the cross that appear on the screen to make +another spawn elsewhere. + +Each new touch on the screen will help to calibrate the offset +of your finger on the screen. After four or more inputs, press +the button to save the calibration and close the application. Quality +of the calibration gets better with every touch on a cross. diff --git a/apps/calibration/app-icon.js b/apps/calibration/app-icon.js new file mode 100644 index 000000000..af66c3f68 --- /dev/null +++ b/apps/calibration/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkB/4AJ+EPBhQXg+BBDCyJaGGR5zIDBoQEL4QYOLYR3GBIouJR5AYBGBILBU5QMGFwgiFX4wwIEI4XGGBAgHd44+HD44XHNw4XWM5IIHCIoXWV5IXICQgXvLxAAKCYYXh5nMC6n8C4PPC5MAAA8PC4ZxBACAXOI653hU5zvJABASEC5PwHI4XcMBIXICIoXXJBAXHCAwXXJBAXHB5AfGC4ygJEAwXGQ5BoIQxoiDBYgXECwIuIBgb5ECIQJFGBQmCC4QHEDBwAFCxoYICx5ZELZoZJFiIXpA=")) \ No newline at end of file diff --git a/apps/calibration/app.js b/apps/calibration/app.js new file mode 100644 index 000000000..049430d45 --- /dev/null +++ b/apps/calibration/app.js @@ -0,0 +1,151 @@ +class BanglejsApp { + constructor() { + this.maxSamples = 16; + this.target = { + xMin: Math.floor(0.1 * g.getWidth()), + xMax: Math.floor(0.9 * g.getWidth()), + yMin: Math.floor(0.1 * g.getHeight()), + yMax: Math.floor(0.9 * g.getHeight()), + }; + this.x = 0; + this.y = 0; + this.step = 0; + this.settings = { + xoffset: [0], + yoffset: [0], + xMaxActual: [this.target.xMax], + yMaxActual: [this.target.yMax], + }; + } + + load_settings() { + let settings = require('Storage').readJSON('calibration.json', true) || {active: false}; + + console.log('loaded settings:'); + console.log(settings); + + return settings; + } + + getMedian(array){ + array.sort(); + let i = Math.floor(array.length/2); + if ( array.length % 2 && array.length > 1 ){ + return (array[i]+array[i+1])/2; + } else { + return array[i]; + } + } + + getMedianSettings(){ + let medianSettings = { + xoffset: this.getMedian(this.settings.xoffset), + yoffset: this.getMedian(this.settings.yoffset) + }; + + medianSettings.xscale = this.target.xMax / (medianSettings.xoffset + this.getMedian(this.settings.xMaxActual)); + medianSettings.yscale = this.target.yMax / (medianSettings.yoffset + this.getMedian(this.settings.yMaxActual)); + return medianSettings; + } + + save_settings() { + let settingsToSave = this.getMedianSettings(); + settingsToSave.active = true; + settingsToSave.reload = false; + require('Storage').writeJSON('calibration.json', settingsToSave); + + console.log('saved settings:', settingsToSave); + } + + explain() { + /* + * TODO: + * Present how to use the application + * + */ + } + + drawTarget() { + switch (this.step){ + case 0: + this.x = this.target.xMin; + this.y = this.target.yMin; + break; + case 1: + this.x = this.target.xMax; + this.y = this.target.yMin; + break; + case 2: + this.x = this.target.xMin; + this.y = this.target.yMax; + break; + case 3: + this.x = this.target.xMax; + this.y = this.target.yMax; + break; + } + + g.clearRect(0, 0, g.getWidth(), g.getHeight()); + g.setColor(g.theme.fg); + g.drawLine(this.x, this.y - 5, this.x, this.y + 5); + g.drawLine(this.x - 5, this.y, this.x + 5, this.y); + g.setFont('Vector', 10); + let medianSettings = this.getMedianSettings(); + g.drawString('current offset: ' + medianSettings.xoffset.toFixed(3) + ', ' + medianSettings.yoffset.toFixed(3), 2, (g.getHeight()/2)-6); + g.drawString('current scale: ' + medianSettings.xscale.toFixed(3) + ', ' + medianSettings.yscale.toFixed(3), 2, (g.getHeight()/2)+6); + } + + setOffset(xy) { + switch (this.step){ + case 0: + this.settings.xoffset.push(this.x - xy.x); + this.settings.yoffset.push(this.y - xy.y); + break; + case 1: + this.settings.xMaxActual.push(xy.x); + this.settings.yoffset.push(this.y - xy.y); + break; + case 2: + this.settings.xoffset.push(this.x - xy.x); + this.settings.yMaxActual.push(xy.y); + break; + case 3: + this.settings.xMaxActual.push(xy.x); + this.settings.yMaxActual.push(xy.y); + break; + } + + for (let c in this.settings){ + if (this.settings[c].length > this.maxSamples) this.settings[c] = this.settings[c].slice(1, this.maxSamples); + } + } + + nextStep() { + this.step++; + if ( this.step == 4 ) this.step = 0; + } +} + + +E.srand(Date.now()); + +calibration = new BanglejsApp(); +calibration.load_settings(); +Bangle.disableCalibration = true; + +function touchHandler (btn, xy){ + if (xy) calibration.setOffset(xy); + calibration.nextStep(); + calibration.drawTarget(); +} + +let modes = { + mode : 'custom', + btn : function(n) { + calibration.save_settings(this.settings); + load(); + }, + touch : touchHandler, +}; +Bangle.setUI(modes); +calibration.drawTarget(); diff --git a/apps/calibration/boot.js b/apps/calibration/boot.js new file mode 100644 index 000000000..03b17a03a --- /dev/null +++ b/apps/calibration/boot.js @@ -0,0 +1,14 @@ +let cal_settings = require('Storage').readJSON("calibration.json", true) || {active: false}; +Bangle.on('touch', function(button, xy) { + // do nothing if the calibration is deactivated + if (cal_settings.active === false || Bangle.disableCalibration) return; + + // reload the calibration offset at each touch event /!\ bad for the flash memory + if (cal_settings.reload === true) { + cal_settings = require('Storage').readJSON("calibration.json", true); + } + + // apply the calibration offset + xy.x = E.clip(Math.round((xy.x + (cal_settings.xoffset || 0)) * (cal_settings.xscale || 1)),0,g.getWidth()); + xy.y = E.clip(Math.round((xy.y + (cal_settings.yoffset || 0)) * (cal_settings.yscale || 1)),0,g.getHeight()); +}); diff --git a/apps/calibration/calibration.png b/apps/calibration/calibration.png new file mode 100644 index 000000000..3fb44beee Binary files /dev/null and b/apps/calibration/calibration.png differ diff --git a/apps/calibration/metadata.json b/apps/calibration/metadata.json new file mode 100644 index 000000000..f428bd538 --- /dev/null +++ b/apps/calibration/metadata.json @@ -0,0 +1,17 @@ +{ "id": "calibration", + "name": "Touchscreen Calibration", + "shortName":"Calibration", + "icon": "calibration.png", + "version":"0.03", + "description": "(NOT RECOMMENDED) A simple calibration app for the touchscreen. Please use the Touchscreen Calibration in the Settings app instead.", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "tags": "tool", + "storage": [ + {"name":"calibration.app.js","url":"app.js"}, + {"name":"calibration.boot.js","url":"boot.js"}, + {"name":"calibration.settings.js","url":"settings.js"}, + {"name":"calibration.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"calibration.json"}] +} diff --git a/apps/calibration/settings.js b/apps/calibration/settings.js new file mode 100644 index 000000000..08c728d96 --- /dev/null +++ b/apps/calibration/settings.js @@ -0,0 +1,22 @@ +(function(back) { + var FILE = "calibration.json"; + var settings = Object.assign({ + active: true, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + E.showMenu({ + "" : { "title" : "Calibration" }, + "< Back" : () => back(), + 'Active': { + value: !!settings.active, + onchange: v => { + settings.active = v; + writeSettings(); + } + }, + }); +}) \ No newline at end of file diff --git a/apps/cassioWatch/ChangeLog b/apps/cassioWatch/ChangeLog new file mode 100644 index 000000000..1180554ff --- /dev/null +++ b/apps/cassioWatch/ChangeLog @@ -0,0 +1,13 @@ +0.0: Main App. +0.1: Performance Fixes. +0.2: Correct Screen Clear and Draw. +0.3: Minor Fixes. +0.4: Clear Old Time on Screen Before Draw new Time +0.5: Update Date and Time to use Native date Funcions +0.6: Add Settings Page +0.7: Update Rocket Sequences Scope to not use memory all time +0.8: Update Some Variable Scopes to not use memory until need +0.9: Remove ESLint spaces +0.10: Show daily steps, heartrate and the temperature if weather information is available. +0.11: Tell clock widgets to hide. +0.12: Swipe down to see widgets, step counter now just uses getHealthStatus diff --git a/apps/cassioWatch/README.md b/apps/cassioWatch/README.md new file mode 100644 index 000000000..6c13cdcac --- /dev/null +++ b/apps/cassioWatch/README.md @@ -0,0 +1,12 @@ +# cassioWatch + +![Screenshot](screens/screen_night.png) ![Screenshot](screens/screen_day.png) + +Clock with Space Cassio Watch Style. + +It displays current temperature,day,steps,battery.heartbeat and weather. + + +**To-do**: + +* Align and change size of some elements diff --git a/apps/cassioWatch/app.js b/apps/cassioWatch/app.js new file mode 100644 index 000000000..19dd883d2 --- /dev/null +++ b/apps/cassioWatch/app.js @@ -0,0 +1,156 @@ +const storage = require('Storage'); + +require("Font6x12").add(Graphics); +require("Font8x12").add(Graphics); +require("Font7x11Numeric7Seg").add(Graphics); + +function bigThenSmall(big, small, x, y) { + g.setFont("7x11Numeric7Seg", 2); + g.drawString(big, x, y); + x += g.stringWidth(big); + g.setFont("8x12"); + g.drawString(small, x, y); +} + +function getBackgroundImage() { + return require("heatshrink").decompress(atob("2GwwkGIf4AfgMRkUiiIHCiMRiAMDAwYCCBAYVDAHMv/4ACkBIBAgPxBgM/BYXyAoICBCowA5gRADKQUDKAYMCmYCBiBXBCo4A5J4MxiMSKQUf+YBBBgSiBgc/kBXBBAMyCoK2CK/btCiUhfAJLCkBkDiMQgBXDCoUvNAJX+AAU/+MB/8wAQIAC+cQK5hoDgIEBBIQFEAYIPHBIgBBAQQIDBwZXSKIMxgJaBgEjmZYCmBXLgLBBkkAgUhiMxBIM0iMSCoMRkZECkQJEichBINDiETAgISBiQTDK6MvJAXzVIQrBBYMCK5E/K4kwGIJXFgdAMgQQBiYiCDgU0HQSlCgMikIEBEAMTDYJXQ+UikYDBj6nCAAMTWoJ6BK4oVEK4c0oQ+BK4MjAgMDJoJXHNYJXHBwa0BohcDY4QAKgJQE+LzBNwJVBkQMEkBXBCoyvFJAVAKISaBiMiHQRIDkVBoSyCK5CvBAgavNDAJAC+cQn5DCgSpBl4MDgBXBgCsBCoYoMLAKREgIKDBJIdKK5oA/AH4A/AH4A/ADUBIH4APiAFEi1mAGUADrkRKwUGK2ZXes1gK2xXfD8A3/K/4AWgxX/ACtga2AwIHLkAgCwvJw6RcDgIABK+w4cK/I4dsEGP5BXtSAQ6BV/5XSG4RX/K6Y3fK+42CK/5XTGwcGK/5XSVwY5cK+o1DAAayYsAhDsCv4K7BTBK4YeYK7CyFVzJXFFIpXtVwYiYK/rmZKYYDDELJXXG4YiaK/Y0aKgQAEK+gkdKt5XGKzqv5GTpX6ETlgK4xWrKTyxKVthXmAGRX/K/5X/AH5X/K/4gBAH4A/AFz/uAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHNggEGHfEAgAEHKyQXVK0qTCAggbUK+6SDAApzXK/5BRDYZX3KxBBSYqxXngyvaV25XEd4ZCSsAcBAoRZ2dQZXBLwgaQCIYeCAGirCS4YGCDSJXCC6ZaodYICBZzSw4S4I+XDgSv4K4rzCK/47RAQTMaWHI9YV3TscV3aVagByBK3SwCSqyt8AAQ+XK/4A/AH4A/AH4A3gAA/AH4AuZbdggwc3ADpX/K/5XxsEAgA+XK/o8BgBX/K64/WK/4/XK/5X/K/5XvgBX/K64cYHrw4CSTFggCuXK4oDCEQJXYDS6ScDgg4CPKyRCAAZX0HAgBDK+LlYK4oeBAwZ9aK+lgAoQGBgyvzDIIDBK66sCG4JXYCwIBDK7ADCK+xZCHwJXzGoQ8BK7DpBAAaSXSgRXZO4okCK+IaXV4oABEILSWSYjRCHSo3BDSxXEAAIcBAISvyKawcIAYIGCK/4cUH4YlaHS0AHgI1XOg5YBPrY6WHgRXfAGRXDHzBX8VoJX/K68ADjRX6sBX/K/5X/K8wdcK/UAG7B0iKzZYbK/BWDAH4A/hWpzWhIf4ASgOpzIAB0EAhhH/AB8ZzGJ1WazMA4pH/AB+pxOZxOpzVMqA2ugUzmcgD7cKVYOqzGqpnRFw8ykchK8kviEBmQFBgMiFocSCAcSkUQAgMikRsHhWqxOq0Ut4mqBw0DC4IxBD4wpBHAQMCA4cCGJIAFj8hDIQuBkMTCwU/AYQJBiUxFoPxiIVDK4kyxUz4cxl+KK5MfDQXyD4UCmMSmAEBAQQHDgMTmIxHAAqpBmaqCFwMDEYZRBgEjCQQBB+USK5E/ns/0Uzwc6K48ykYkCK4IfCc4I4CK4QHEBAYAMiICBmYuDmQEBh8iAgRXCLISvJO4MqwcklEiK5CADV4oaBV4oHEK6Eve4JNCbwRfCiMTFoMDkMRSAJXCD49azWp0UqzWayJXIQwcAO4cCkMCFIJOCA4XxK6KPBkR6DTwYyBAwYPEAggfFzORpWK1OZyAOHJ4QfERAUSEgQxIIIgAr1URWIOZzOgGtwAhgMZzWq1OaIv4ASKgOqzTkvAEmq1WgFtQA==")); +} + +function getRocketSequences() { + return { + 1: require("heatshrink").decompress(atob("qFGwkCkQAiiEBEkUgKQhPhE8ogCE8YhCiQoEE7pKEPIgncTQ4neEwpQCPoh1eJYYwCJ7QmHKAh1hZIpOjPAUBJ0ZQCTzEhExZ1lPAZ1kKDQmOJ65O2E65OPOy5O2E64mPOyxO/J2wnPJyx2QJ35O/J2khE0p2POq52PEy4nOiQnlOrEhiSfMJrEggQnLJzB1CPBQmZkInMEzBQDPBImbPBR1ZEoRMCZYImhgQgEE0BzFKAgmaDwLDFKAbqdYQwHBOrcgDgLBFJrsiiRNGYbpLBY4Ymhd4omkkUhE0pQEEwUBJjrHBd4QmCdzoiBDwYrCPLyZHF4QnagQeCE8UgJwYniJwgnIOzwfFO0wJCJzMQE4gyFEzR2FBQombkInDQI4AakAnBTYS+ZE5BMDE0LEES7YnLE0R3FAEQA=")), + 2: require("heatshrink").decompress(atob("qFGwkCkQAikMAgIliKYon/AA0gEAQniEwIhCAgYndEIjqBE8CaGKogmgKAp1fKAgncExBQBBQR1gKAp7BJ0IndExR4CE0idaOpYnbExqeYJxxPYEx0BJ0x2XExx2XJ20QE6xONJi5OPGwJOlBwLFkLoLFlBwJOkOwJOlE4JOkTjBOOE/52Pdi5OPEy7FnE5wmXE5xOZT5gmYEoMiiB1lgR4KTLAkDPBJ1WIAYDDKA4mWJwchDwYEDTjQiDJQh4GYLAhHFosSJy6OCTIxaEEywbBKYwjEEzMgUQxQFBogAURwZOGOjTKJdTYnOEryfHE0JQEfIpQgYQMAgJLeAgrtfTI4ndgSaFE4h0bdQkSZQpOfEAgIBO0AnEdrh2FJAb1EdbInEBIpObOwhOEEzYnFXzZ2HE4QlhE4QlDFMKcDYooniO0QnDT0YnCE0ciA")), + 3: require("heatshrink").decompress(atob("qFGwkEogAjiMUEkVAKYgnhPYolgOQIniOYZ4FOcLqBE8CaGKojpgKAomhEYUQE7gmHKAIxCE0QkCPYR1gZIgnZExR4CJ0idmE7ZONYzImNgEUJ0p3YJRh2ZJJwnXOpQhBdkpaETsMEGQhOhE7jFLUYpOfTzgmKE4hOiE4hOigEUJ0rvCEywnPEqx2OTjBOOE7ImOTsqeZE5zFYoJOmT5kBJzEAih4LdK5mBAQInKOqoYDEgR4JEypHDEYbxJOq5ABdgZ7CEzZOEJQgnGihOYEIzJFTionCKYxWGEy9ADAYnGUIYmWog/EdBFAEy7KIKAwnjKwLqWE5pMeT48CVQpQfgMjKEtEiAnfEQJQCgJSCTcB6FJzkEdYcUE8FAdQghDOzonKTjh2EZAidcDoInHJzodBOwx/BE8JxcOwsAOwQmhJgSXDObwnFEwUUO0LFGE8aeiE4YmiokQE0tE")), + 4: require("heatshrink").decompress(atob("qFGwkCkQAjiMSEkRTFE/4AGkMAgQCBE8MgEIYEDE7whDdQIngTQxVEE0ChFTjxQFE7jnFKAgxCOsBQFZgJ1gE7wmKPAROkTrTEHGAwnYiBHJFAaeXOoyXBEQZPac5AsFgJOhAoh2XJwwnFKoROdE4J9GJzwnIiQmVkInPAC0QE5AJFE64mHY5DFdE4SBEYr5JDJ0hKDJ0jCZJxoACgInmKLAmOTq5OOEy5OPTsxOYE5wmXO5wlYkAnMOqshiRNCgR4LOC8CkJCCEzxHDAgYnJOqpAEDoZ4HEyodDEQpQHdCsQOwwFHEyzoCPYzJGEy0gEwaZGA4acVEQSjHKAomXkQYEYAwlZeRKYDE8gjCYa7zJEwcCkImfKAb4FAD0hdTh4LgRSBOcR0CJz0gYYrrgN4QnEYrxOEE4bEeiAnGF4J2idL6VDE8ohBE0gnFE0J0BE4QGBiROgdIQABgJ2hJoTtjYgZSEE8ScgE4omikUQTcQADA=")), + 5: require("heatshrink").decompress(atob("qFGwkCkQAikMAgIliKYonhiAnjkEATIIniEwIhCAgYndEIhQFYUZVEE0BQFOr5QEeQQmiKAL1DOr5QEE7ROCDgZVEAoInZDwchFQQoDPAJOdEQYrBdrZFDOYwncEJDsDVIpOXgJxEE4pObEAgGFgJOaE48BaIhOZJ5ZObY5ROcE441CE6xOGPAwtCJzpGCJ0hHDkI1DJzwoEJzInLFg52dUo5O/J35OzE54mWOx4mXJxx1XE54mXkUhExkSJzCfMOrAlBPBiZXgQDBAQQmgJgh4JOqoYEFYwmaDoZzEFgh1YDgkiiAFEKAroXJJAGFiQmVkCNDTIz5EJy57HKAomXkQYEJoqaYeRadEJrAnJEQUAgJPiAoYmeT4cCkAnBE0BKCJkT1EkDCeJYYiDOkLDFFL5wBE4guCPDhEBEwQiDY70CkInDiQnCJzkhOwhKDdzp2Idb4nEE0B0Bdo4niE0J0CeYhOhgESUYYnidsgnEE0KeCE0gnDE0ciA")), + 6: require("heatshrink").decompress(atob("qFGwkCkQA/ABEgKQZPhEwgABEsAoGJkBxBE8JKEAowAbJIhQEgLDiPooAdKA4ncTZAndSwhQEFoInaJQkSKAwlZdgwnfSgYADE4h1ZDwInlcggnIOzAdCE8i7EY5J3XDgYhGd4pOZEI52bSYwGCOAJ2bYIodEOzZOFFAjFcEwwAIE6xOHABBO/J34ndEyx2PJ00BJ00SJ0p1XE54mXOxxO/J5wmYgQnMOrB2BPBgkWiJ1CPBbBYAYR4KiTAXRwIrFTjgZDJYZ4IEyoiEIwrDcEJJQFOqwiBDARxFFwgmXkAYDEogsBF4QmXEQJ7GUYYkBEzDKJAgYmdEQbKFEzonEKYgngJwgmfZggmjKQghgiBRGkBzeTgUikJRgc47LDErTnDEAkQJzkCJwYnEJzonEJIaddOwhJEJzgdBE4hYEJzieJADgnEE0KUCXzoAGkJLEiB2hOgQDBT0TsDT0YmlE4YmjkQ=")), + 7: require("heatshrink").decompress(atob("qFGwkCkQAhkIpBiQlhkBSEJ8InlEIIoFE7whEE8pQFE7giBJQoneI4MCTYhQDE7YdCYYondEQYnEPwZ1bE5BQCJzonHkR2ZEAkBE4pNBE7zHFYrYhFUgonaXAQeEEwruZEYcgiROHJ7AfDAwxOeAAURiAmHE65HIOzwmOJ35OPE6xOPO35O/J35O/J1gnPEyx2PEy5OOOq5OnE5xOYO5omZgJQMJrQnLiQnagR4JOq5nCDgZ1fEYRLDE5DoZkUQNoZ4GOrJKGAoomXOw7lCAwYmYDgJSEAAUBA4QDBJzB6FOQrDXJwTJFdLjJKE9jDYZRAmkKAwmhKAgmiKAYmBkApdJIgjCKYIncOQYvJYTovGE84lagR2DE4xOakBOEgJXFOjYnEJAbtdOwggEkAmbDgInDE0B0BE4QgcE5AkiXYbpCOLonGYo4nhPMYnCUEgnBY0kiA==")), + 8: require("heatshrink").decompress(atob("qFGwkCkQA/ABBSEJ8MgE4kBEsBPFE7xMCOIJ3hOYgFEE7rCGE70gE4pQBiAndYQwjBUohOZD4ZQFE7YkBE5AICYbZ2GE7sggJRCAA8iYzZOITroALE7EhExh4CAC0QExpPXOponZExx2XJ24nWdh52XdhzF/Yu5O/J35O0E55OXOx5O/J2omXE5x1XO54mYgQnMJrR4LOrciiAmiJgR4KEzIjDPBAlYiAiEeI51YkEBE4J5CD4KceTQQcBJgRQFdTZDCJIjDcNIqhGdTQmCkByFTTInDKgoAEE7ZEEJwhPdE1R1FE0InEE0R3DEwTGcDwomEE7hKFPYqafE8ROCE5DJbE5B/IEqh2ED4gnCJrMCJwgnEiB2bE4qeFEzUggQmIBQLEaEQImHLIImaE4YfcOw4lEFMLECS7onJO8wmkE4QljAAIA==")), + }; +} + +let rocketSequence = 1; +let settings = storage.readJSON("cassioWatch.settings.json", true) || {}; +let rocketSpeed = settings.rocketSpeed || 700; +delete settings; + +// schedule a draw for the next minute +let rocketInterval; +var drawTimeout; +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + + +function clearIntervals() { + if (rocketInterval) clearInterval(rocketInterval); + rocketInterval = undefined; + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; +} + +function drawClock() { + g.setFont("7x11Numeric7Seg", 3); + g.clearRect(80, 57, 170, 96); + g.setColor(0, 255, 255); + g.drawRect(80, 57, 170, 96); + g.fillRect(80, 57, 170, 96); + g.setColor(0, 0, 0); + g.drawString(require("locale").time(new Date(), 1), 70, 60); + g.setFont("8x12", 2); + g.drawString(require("locale").dow(new Date(), 2).toUpperCase(), 18, 130); + g.setFont("8x12"); + g.drawString(require("locale").month(new Date(), 2).toUpperCase(), 80, 126); + g.setFont("8x12", 2); + const time = new Date().getDate(); + g.drawString(time < 10 ? "0" + time : time, 78, 137); +} + +function drawBattery() { + bigThenSmall(E.getBattery(), "%", 135, 21); +} + +function drawRocket() { + let Rocket = getRocketSequences(); + g.clearRect(5, 62, 63, 115); + g.setColor(0, 255, 255); + g.drawRect(5, 62, 63, 115); + g.fillRect(5, 62, 63, 115); + g.drawImage(Rocket[rocketSequence], 5, 65, { scale: 0.7 }); + g.setColor(0, 0, 0); + rocketSequence = rocketSequence + 1; + if(rocketSequence > 8) rocketSequence = 1; +} + +function getTemperature(){ + try { + var weatherJson = storage.readJSON('weather.json'); + var weather = weatherJson.weather; + return Math.round(weather.temp-273.15); + } catch(ex) { + print(ex) + return "?" + } +} + +function getSteps() { + var steps = Bangle.getHealthStatus("day").steps; + steps = Math.round(steps/1000); + return steps + "k"; +} + + +function draw() { + queueDraw(); + + g.clear(1); + g.setColor(0, 255, 255); + g.fillRect(0, 0, g.getWidth(), g.getHeight()); + let background = getBackgroundImage(); + g.drawImage(background, 0, 0, { scale: 1 }); + g.setColor(0, 0, 0); + g.setFont("6x12"); + g.drawString("Launching Process", 30, 20); + g.setFont("8x12"); + g.drawString("ACTIVATE", 40, 35); + + g.setFontAlign(0,-1); + g.setFont("8x12", 2); + g.drawString(getTemperature(), 155, 132); + g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), 109, 98); + g.drawString(getSteps(), 158, 98); + + g.setFontAlign(-1,-1); + drawClock(); + drawRocket(); + drawBattery(); +} + +Bangle.on("lcdPower", (on) => { + if (on) { + draw(); + } else { + clearIntervals(); + } +}); + + +Bangle.on("lock", (locked) => { + clearIntervals(); + draw(); + if (!locked) { + rocketInterval = setInterval(drawRocket, rocketSpeed); + } +}); + +Bangle.setUI("clock"); + +// Load widgets, but don't show them +Bangle.loadWidgets(); +require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe +g.clear(1); +draw(); diff --git a/apps/cassioWatch/app.png b/apps/cassioWatch/app.png new file mode 100644 index 000000000..3f9bbb36e Binary files /dev/null and b/apps/cassioWatch/app.png differ diff --git a/apps/cassioWatch/icon.js b/apps/cassioWatch/icon.js new file mode 100644 index 000000000..4e4428f88 --- /dev/null +++ b/apps/cassioWatch/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("lkswkGswAHtGIxGGBhATJAAYXNCYoWOFIwWNChQWKBYWYCxIqTxGJFgwnDnACBkUiCwuYFQo9ECYIAClAsJxIUElAUDwQWEyxAHBwITBmczmUiCwprHCgMjmUhiIYBA4JCGIAeCwQUBCYIXBiUyFggVCFQsziMjmdCkcxiZbBiJCEFQZUBmND93uolEochFgpWEIAUUCgIACp00iZVBzIVEAoJABmUeConu8cRiNEoMZNwIrCzEiiUxpwVF901IwNN6JuBtGJlAVBkchCg3umkSiMNCoIAEQIJWFkgCB8UYinUjIVFxEhKwszDAUU7tRCg2hilTH4xVB6kRzAUGBQIVECYVEorEDAAeBptBiUenw7BCYQACieCCosd6MYwUVBwNUAQYvBeYOJCYVoxVeoK5BAAq0C8MjiM4LALFCilNCAQpCN4lBmUoFQQVCxTlBiMieI3uqUoagIVDRAeCkclCgvlkciFYdmsxwDlESmTdE8lRmSCECosiFgMhqgUDkZABBwWYCoNpIIYXBmchiKLBkYqBNggVBNwIsECwMikQUBlEiBgWJCoRCEEQITDNIJrEIAQABZYOYnIWBFoQUGIAZCCTYYWCAAQUExIUDFggNDAA5AEFg4AIIAhvGEYU4ChgWHAAoUIWYpdFFRIWHChxEICZoWFFBIA==")) diff --git a/apps/cassioWatch/metadata.json b/apps/cassioWatch/metadata.json new file mode 100644 index 000000000..5ac4502fd --- /dev/null +++ b/apps/cassioWatch/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "cassioWatch", + "name": "Cassio Watch", + "description": "Animated Clock with Space Cassio Watch Style", + "screenshots": [{ "url": "screens/screen_night.png" },{ "url": "screens/screen_day.png" }], + "icon": "app.png", + "version": "0.12", + "type": "clock", + "tags": "clock, weather, cassio, retro", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "readme": "README.md", + "storage": [ + { "name": "cassioWatch.app.js", "url": "app.js" }, + {"name":"cassioWatch.settings.js","url":"settings.js"}, + { "name": "cassioWatch.img", "url": "icon.js", "evaluate": true } + ] +} diff --git a/apps/cassioWatch/screens/screen_day.png b/apps/cassioWatch/screens/screen_day.png new file mode 100644 index 000000000..ba150b4f7 Binary files /dev/null and b/apps/cassioWatch/screens/screen_day.png differ diff --git a/apps/cassioWatch/screens/screen_night.png b/apps/cassioWatch/screens/screen_night.png new file mode 100644 index 000000000..4055e0943 Binary files /dev/null and b/apps/cassioWatch/screens/screen_night.png differ diff --git a/apps/cassioWatch/settings.js b/apps/cassioWatch/settings.js new file mode 100644 index 000000000..b07c6c58f --- /dev/null +++ b/apps/cassioWatch/settings.js @@ -0,0 +1,24 @@ +(function(back) { + var FILE = "cassioWatch.settings.json"; + var settings = Object.assign({ + rocketSpeed: 700, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + + E.showMenu({ + "" : { "title" : "Cassio Watch" }, + "< Back" : () => back(), + 'Rocket Speed': { + value: 0|settings.rocketSpeed, + min: 100, max: 60000, + onchange: v => { + settings.rocketSpeed = v; + writeSettings(); + } + }, + }); + }) \ No newline at end of file diff --git a/apps/chimer/ChangeLog b/apps/chimer/ChangeLog new file mode 100644 index 000000000..01bd00a0a --- /dev/null +++ b/apps/chimer/ChangeLog @@ -0,0 +1,2 @@ +0.01: Initial Creation +0.02: Fixed some sleep bugs. Added a sleep mode toggle \ No newline at end of file diff --git a/apps/chimer/README.MD b/apps/chimer/README.MD new file mode 100644 index 000000000..a78c677f2 --- /dev/null +++ b/apps/chimer/README.MD @@ -0,0 +1,11 @@ +# Chimer - For the BangleJS + +A fork of [Hour Chime](https://github.com/espruino/BangleApps/tree/master/apps/widchime) that adds extra features such as: + +- Buzz or beep on every 60, 30 or 15 minutes. +- Repeat Chime up to 3 times +- Set hours to disable chime + +Setting the hours you don't want your watch to chime for is done by setting the hour you want it to stop, and the hour you want it to start. + +Hours range from 0 - 23. diff --git a/apps/chimer/icon.txt b/apps/chimer/icon.txt new file mode 100644 index 000000000..cc969bc81 --- /dev/null +++ b/apps/chimer/icon.txt @@ -0,0 +1,2 @@ + +widget.png: "https://icons8.com/icon/114436/alarm" \ No newline at end of file diff --git a/apps/chimer/metadata.json b/apps/chimer/metadata.json new file mode 100644 index 000000000..d5bc04950 --- /dev/null +++ b/apps/chimer/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "chimer", + "name": "Chimer", + "version": "0.02", + "description": "A fork of Hour Chime that adds extra features such as: \n - Buzz or beep on every 60, 30 or 15 minutes. \n - Reapeat Chime up to 3 times \n - Set hours to disable chime", + "icon": "widget.png", + "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS", "BANGLEJS2"], + "readme": "README.MD", + "storage": [ + { "name": "chimer.wid.js", "url": "widget.js" }, + { "name": "chimer.settings.js", "url": "settings.js" } + ], + "data": [{ "name": "chimer.json" }] +} diff --git a/apps/chimer/settings.js b/apps/chimer/settings.js new file mode 100644 index 000000000..55160c9be --- /dev/null +++ b/apps/chimer/settings.js @@ -0,0 +1,94 @@ +/** + * @param {function} back Use back() to return to settings menu + */ + +(function (back) { + // default to buzzing + var FILE = "chimer.json"; + var settings = {}; + const chimes = ["Off", "Buzz", "Beep", "Both"]; + const frequency = ["60 min", "30 min", "15 min", "1 min"]; + + var showMainMenu = () => { + E.showMenu({ + "": { title: "Chimer" }, + "< Back": () => back(), + "Chime Type": { + value: settings.type, + min: 0, + max: 2, // both is just silly + format: (v) => chimes[v], + onchange: (v) => { + settings.type = v; + writeSettings(settings); + }, + }, + Frequency: { + value: settings.freq, + min: 0, + max: 2, + format: (v) => frequency[v], + onchange: (v) => { + settings.freq = v; + writeSettings(settings); + }, + }, + Repetition: { + value: settings.repeat, + min: 1, + max: 5, + format: (v) => v, + onchange: (v) => { + settings.repeat = v; + writeSettings(settings); + }, + }, + "Sleep Mode": { + value: !!settings.sleep, + onchange: (v) => { + settings.sleep = v; + writeSettings(settings); + }, + }, + "Sleep Start": { + value: settings.start, + min: 0, + max: 23, + format: (v) => v, + onchange: (v) => { + settings.start = v; + writeSettings(settings); + }, + }, + "Sleep End": { + value: settings.end, + min: 0, + max: 23, + format: (v) => v, + onchange: (v) => { + settings.end = v; + writeSettings(settings); + }, + }, + }); + }; + + var readSettings = () => { + var settings = require("Storage").readJSON(FILE, 1) || { + type: 1, + freq: 0, + repeat: 1, + sleep: true, + start: 6, + end: 22, + }; + return settings; + }; + + var writeSettings = (settings) => { + require("Storage").writeJSON(FILE, settings); + }; + + settings = readSettings(); + showMainMenu(); +}); diff --git a/apps/chimer/widget.js b/apps/chimer/widget.js new file mode 100644 index 000000000..18358df9e --- /dev/null +++ b/apps/chimer/widget.js @@ -0,0 +1,134 @@ +(function () { + // 0: off, 1: buzz, 2: beep, 3: both + var FILE = "chimer.json"; + + var readSettings = () => { + var settings = require("Storage").readJSON(FILE, 1) || { + type: 1, + freq: 0, + repeat: 1, + sleep: true, + start: 6, + end: 22, + }; + return settings; + }; + + var settings = readSettings(); + + function sleep(milliseconds) { + const date = Date.now(); + let currentDate = null; + do { + currentDate = Date.now(); + } while (currentDate - date < milliseconds); + } + + function chime() { + for (var i = 0; i < settings.repeat; i++) { + if (settings.type === 1) { + Bangle.buzz(100); + } else if (settings.type === 2) { + Bangle.beep(); + } else { + return; + } + sleep(150); + } + } + + let lastHour = new Date().getHours(); + let lastMinute = new Date().getMinutes(); // don't chime when (re)loaded at a whole hour + function check() { + const now = new Date(), + h = now.getHours(), + m = now.getMinutes(), + s = now.getSeconds(), + ms = now.getMilliseconds(); + if ( + (settings.sleep && h > settings.end) || + (settings.sleep && h >= settings.end && m !== 0) || + (settings.sleep && h < settings.start) + ) { + var mLeft = 60 - m, + sLeft = mLeft * 60 - s, + msLeft = sLeft * 1000 - ms; + setTimeout(check, msLeft); + return; + } + if (settings.freq === 1) { + if ((m !== lastMinute && m === 0) || (m !== lastMinute && m === 30)) + chime(); + lastHour = h; + lastMinute = m; + // check again in 30 minutes + switch (true) { + case m / 30 >= 1: + var mLeft = 30 - (m - 30), + sLeft = mLeft * 60 - s, + msLeft = sLeft * 1000 - ms; + break; + case m / 30 < 1: + var mLeft = 30 - m, + sLeft = mLeft * 60 - s, + msLeft = sLeft * 1000 - ms; + break; + } + setTimeout(check, msLeft); + } else if (settings.freq === 2) { + if ( + (m !== lastMinute && m === 0) || + (m !== lastMinute && m === 15) || + (m !== lastMinute && m === 30) || + (m !== lastMinute && m === 45) + ) + chime(); + lastHour = h; + lastMinute = m; + // check again in 15 minutes + switch (true) { + case m / 15 >= 3: + var mLeft = 15 - (m - 45), + sLeft = mLeft * 60 - s, + msLeft = sLeft * 1000 - ms; + break; + case m / 15 >= 2: + var mLeft = 15 - (m - 30), + sLeft = mLeft * 60 - s, + msLeft = sLeft * 1000 - ms; + break; + case m / 15 >= 1: + var mLeft = 15 - (m - 15), + sLeft = mLeft * 60 - s, + msLeft = sLeft * 1000 - ms; + break; + case m / 15 < 1: + var mLeft = 15 - m, + sLeft = mLeft * 60 - s, + msLeft = sLeft * 1000 - ms; + break; + } + setTimeout(check, msLeft); + } else if (settings.freq === 3) { + if (m !== lastMinute) chime(); + lastHour = h; + lastMinute = m; + // check again in 1 minute + + var mLeft = 1, + sLeft = mLeft * 60 - s, + msLeft = sLeft * 1000 - ms; + setTimeout(check, msLeft); + } else { + if (h !== lastHour && m === 0) chime(); + lastHour = h; + // check again in 60 minutes + var mLeft = 60 - m, + sLeft = mLeft * 60 - s, + msLeft = sLeft * 1000 - ms; + setTimeout(check, msLeft); + } + } + + check(); +})(); diff --git a/apps/chimer/widget.png b/apps/chimer/widget.png new file mode 100644 index 000000000..14edf4150 Binary files /dev/null and b/apps/chimer/widget.png differ diff --git a/apps/choozi/ChangeLog b/apps/choozi/ChangeLog index 5560f00bc..03f7ef832 100644 --- a/apps/choozi/ChangeLog +++ b/apps/choozi/ChangeLog @@ -1 +1,3 @@ 0.01: New App! +0.02: Support Bangle.js 2 +0.03: Fix bug for Bangle.js 2 where g.flip was not being called. diff --git a/apps/choozi/appb2.js b/apps/choozi/appb2.js new file mode 100644 index 000000000..5f217f638 --- /dev/null +++ b/apps/choozi/appb2.js @@ -0,0 +1,207 @@ +/* Choozi - Choose people or things at random using Bangle.js. + * Inspired by the "Chwazi" Android app + * + * James Stanley 2021 + */ + +var colours = ['#ff0000', '#ff8080', '#00ff00', '#80ff80', '#0000ff', '#8080ff', '#ffff00', '#00ffff', '#ff00ff', '#ff8000', '#ff0080', '#8000ff', '#0080ff']; + +var stepAngle = 0.18; // radians - resolution of polygon +var gapAngle = 0.035; // radians - gap between segments +var perimMin = 80; // px - min. radius of perimeter +var perimMax = 87; // px - max. radius of perimeter + +var segmentMax = 70; // px - max radius of filled-in segment +var segmentStep = 5; // px - step size of segment fill animation +var circleStep = 4; // px - step size of circle fill animation + +// rolling ball animation: +var maxSpeed = 0.08; // rad/sec +var minSpeed = 0.001; // rad/sec +var animStartSteps = 300; // how many steps before it can start slowing? +var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate +var ballSize = 3; // px - ball radius +var ballTrack = 75; // px - radius of ball path + +var centreX = 88; // px - centre of screen +var centreY = 88; // px - centre of screen + +var fontSize = 50; // px + +var radians = 2*Math.PI; // radians per circle + +var defaultN = 3; // default value for N +var minN = 2; +var maxN = colours.length; +var N; +var arclen; + +// https://www.frankmitchell.org/2015/01/fisher-yates/ +function shuffle (array) { + var i = 0 + , j = 0 + , temp = null; + + for (i = array.length - 1; i > 0; i -= 1) { + j = Math.floor(Math.random() * (i + 1)); + temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } +} + +// draw an arc between radii minR and maxR, and between +// angles minAngle and maxAngle +function arc(minR, maxR, minAngle, maxAngle) { + var step = stepAngle; + var angle = minAngle; + var inside = []; + var outside = []; + var c, s; + while (angle < maxAngle) { + c = Math.cos(angle); + s = Math.sin(angle); + inside.push(centreX+c*minR); // x + inside.push(centreY+s*minR); // y + // outside coordinates are built up in reverse order + outside.unshift(centreY+s*maxR); // y + outside.unshift(centreX+c*maxR); // x + angle += step; + } + c = Math.cos(maxAngle); + s = Math.sin(maxAngle); + inside.push(centreX+c*minR); + inside.push(centreY+s*minR); + outside.unshift(centreY+s*maxR); + outside.unshift(centreX+c*maxR); + + var vertices = inside.concat(outside); + g.fillPoly(vertices, true); +} + +// draw the arc segments around the perimeter +function drawPerimeter() { + g.clear(); + for (var i = 0; i < N; i++) { + g.setColor(colours[i%colours.length]); + var minAngle = (i/N)*radians; + arc(perimMin,perimMax,minAngle,minAngle+arclen); + } +} + +// animate a ball rolling around and settling at "target" radians +function animateChoice(target) { + var angle = 0; + var speed = 0; + var oldx = -10; + var oldy = -10; + var decelFromAngle = -1; + var allowDecel = false; + for (var i = 0; true; i++) { + angle = angle + speed; + if (angle > radians) angle -= radians; + if (i < animStartSteps || (speed < maxSpeed && !allowDecel)) { + speed = speed + accel; + if (speed > maxSpeed) { + speed = maxSpeed; + /* when we reach max speed, we know how long it takes + * to accelerate, and therefore how long to decelerate, so + * we can work out what angle to start decelerating from */ + if (decelFromAngle < 0) { + decelFromAngle = target-angle; + while (decelFromAngle < 0) decelFromAngle += radians; + while (decelFromAngle > radians) decelFromAngle -= radians; + } + } + } else { + if (!allowDecel && (angle < decelFromAngle) && (angle+speed >= decelFromAngle)) allowDecel = true; + if (allowDecel) speed = speed - accel; + if (speed < minSpeed) speed = minSpeed; + if (speed == minSpeed && angle < target && angle+speed >= target) return; + } + + var r = i/2; + if (r > ballTrack) r = ballTrack; + var x = centreX+Math.cos(angle)*r; + var y = centreY+Math.sin(angle)*r; + g.setColor('#000000'); + g.fillCircle(oldx,oldy,ballSize+1); + g.setColor('#ffffff'); + g.fillCircle(x, y, ballSize); + oldx=x; + oldy=y; + g.flip(); + } +} + +// choose a winning segment and animate its selection +function choose() { + var chosen = Math.floor(Math.random()*N); + var minAngle = (chosen/N)*radians; + var maxAngle = minAngle + arclen; + animateChoice((minAngle+maxAngle)/2); + g.setColor(colours[chosen%colours.length]); + for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep) + arc(i, perimMax, minAngle, maxAngle); + arc(0, perimMax, minAngle, maxAngle); + for (var r = 1; r < segmentMax; r += circleStep) + g.fillCircle(centreX,centreY,r); + g.fillCircle(centreX,centreY,segmentMax); +} + +// draw the current value of N in the middle of the screen, with +// up/down arrows +function drawN() { + g.setColor(g.theme.fg); + g.setFont("Vector",fontSize); + g.drawString(N,centreX-g.stringWidth(N)/2+4,centreY-fontSize/2); + if (N < maxN) + g.fillPoly([centreX-6,centreY-fontSize/2-7, centreX+6,centreY-fontSize/2-7, centreX, centreY-fontSize/2-14]); + if (N > minN) + g.fillPoly([centreX-6,centreY+fontSize/2+5, centreX+6,centreY+fontSize/2+5, centreX, centreY+fontSize/2+12]); +} + +// update number of segments, with min/max limit, "arclen" update, +// and screen reset +function setN(n) { + N = n; + if (N < minN) N = minN; + if (N > maxN) N = maxN; + arclen = radians/N - gapAngle; + drawPerimeter(); +} + +// save N to choozi.txt +function writeN() { + var file = require("Storage").open("choozi.txt","w"); + file.write(N); +} + +// load N from choozi.txt +function readN() { + var file = require("Storage").open("choozi.txt","r"); + var n = file.readLine(); + if (n !== undefined) setN(parseInt(n)); + else setN(defaultN); +} + +shuffle(colours); // is this really best? +Bangle.setLCDTimeout(0); // keep screen on +readN(); +drawN(); + +setWatch(() => { + writeN(); + drawPerimeter(); + choose(); +}, BTN1, {repeat:true}); + +Bangle.on('touch', function(zone,e) { + if(e.x>+88){ + setN(N-1); + drawN(); + }else{ + setN(N+1); + drawN(); + } +}); diff --git a/apps/choozi/metadata.json b/apps/choozi/metadata.json index b75ef062a..79af76fa2 100644 --- a/apps/choozi/metadata.json +++ b/apps/choozi/metadata.json @@ -1,16 +1,17 @@ { "id": "choozi", "name": "Choozi", - "version": "0.01", + "version": "0.03", "description": "Choose people or things at random using Bangle.js.", "icon": "app.png", "tags": "tool", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "allow_emulator": true, "screenshots": [{"url":"bangle1-choozi-screenshot1.png"},{"url":"bangle1-choozi-screenshot2.png"}], "storage": [ - {"name":"choozi.app.js","url":"app.js"}, + {"name":"choozi.app.js","url":"app.js","supports": ["BANGLEJS"]}, + {"name":"choozi.app.js","url":"appb2.js","supports": ["BANGLEJS2"]}, {"name":"choozi.img","url":"app-icon.js","evaluate":true} ] } diff --git a/apps/chronowid/ChangeLog b/apps/chronowid/ChangeLog index ed230b737..08a9ac828 100644 --- a/apps/chronowid/ChangeLog +++ b/apps/chronowid/ChangeLog @@ -4,3 +4,4 @@ 0.04: Change to 7 segment font, move to top widget bar Better auto-update behaviour, less RAM used 0.05: Fix error running app on new firmwares (fix #1140) +0.06: Use default Bangle formatter for booleans diff --git a/apps/chronowid/app.js b/apps/chronowid/app.js index ab363ed17..b0ee7625a 100644 --- a/apps/chronowid/app.js +++ b/apps/chronowid/app.js @@ -79,7 +79,6 @@ function showMenu() { }, 'Timer on': { value: settingsChronowid.started, - format: v => v ? "On" : "Off", onchange: v => { settingsChronowid.started = v; updateSettings(); diff --git a/apps/chronowid/metadata.json b/apps/chronowid/metadata.json index 7cb32709f..69a5d3a2e 100644 --- a/apps/chronowid/metadata.json +++ b/apps/chronowid/metadata.json @@ -2,7 +2,7 @@ "id": "chronowid", "name": "Chrono Widget", "shortName": "Chrono Widget", - "version": "0.05", + "version": "0.06", "description": "Chronometer (timer) which runs as widget.", "icon": "app.png", "tags": "tool,widget", diff --git a/apps/circlesclock/ChangeLog b/apps/circlesclock/ChangeLog index 7165f8521..cb5248e32 100644 --- a/apps/circlesclock/ChangeLog +++ b/apps/circlesclock/ChangeLog @@ -20,3 +20,21 @@ Color depending on value (green -> red, red -> green) option Good HRM value will not be overwritten so fast anymore 0.10: Use roboto font for time, date and day of week and center align them +0.11: New color option: foreground color + Improve performance, reduce memory usage + Small optical adjustments +0.12: Allow configuration of update interval +0.13: Load step goal from Bangle health app as fallback + Memory optimizations +0.14: Support to show big weather info +0.15: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16 +0.16: Fix const error + Use widget_utils if available +0.17: Load circles from clkinfo +0.18: Improved clkinfo handling and using it for the weather circle +0.19: Remove old code and fixing clkinfo handling (fix HRM and other items that change) + Remove settings for what is displayed and instead allow circles to be changed by swiping +0.20: Add much faster circle rendering (250ms -> 40ms) + Add fast load capability +0.21: Remade all icons without a palette for dark theme + Now re-adds widgets if they were hidden when fast-loading diff --git a/apps/circlesclock/README.md b/apps/circlesclock/README.md index aa429d5ec..7f6a2585c 100644 --- a/apps/circlesclock/README.md +++ b/apps/circlesclock/README.md @@ -5,28 +5,41 @@ A clock with three or four circles for different data at the bottom in a probabl By default the time, date and day of week is shown. It can show the following information (this can be configured): + * Steps * Steps distance * Heart rate (automatically updates when screen is on and unlocked) * Battery (including charging status and battery low warning) - * Weather (requires [weather app](https://banglejs.com/apps/#weather)) + * Weather (requires [OWM weather provider](https://banglejs.com/apps/?id=owmweather)) * Humidity or wind speed as circle progress * Temperature inside circle * Condition as icon below circle - * Time and progress until next sunrise or sunset (requires [my location app](https://banglejs.com/apps/#mylocation)) - * Temperature, air pressure or altitude from internal pressure sensor + * Big weather icon next to clock + * Altitude from internal pressure sensor + * Active alarms (if `Alarm` app installed) + * Sunrise or sunset (if `Sunrise Clockinfo` app installed) +To change what is shown: -The color of each circle can be configured. The following colors are available: +* Unlock the watch +* Tap on the circle to change (a border is drawn around it) +* Swipe up/down to change the guage within the given group +* Swipe left/right to change the group (eg. between standard Bangle.js and Alarms/etc) + +Data is provided by ['Clock Info'](http://www.espruino.com/Bangle.js+Clock+Info) +so any apps that implement this feature can add extra information to be displayed. + +The color of each circle can be configured from `Settings -> Apps -> Circles Clock`. The following colors are available: * Basic colors (red, green, blue, yellow, magenta, cyan, black, white) * Color depending on value (green -> red, red -> green) - ## Screenshots ![Screenshot dark theme](screenshot-dark.png) ![Screenshot light theme](screenshot-light.png) ![Screenshot dark theme with four circles](screenshot-dark-4.png) ![Screenshot light theme with four circles](screenshot-light-4.png) +![Screenshot light theme with big weather enabled](screenshot-light-with-big-weather.png) + ## Ideas * Show compass heading @@ -35,4 +48,5 @@ The color of each circle can be configured. The following colors are available: Marco ([myxor](https://github.com/myxor)) ## Icons -Icons taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0 +Most of the icons are taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0 except the big weather icons which are from +[icons8](https://icons8.com/icon/set/weather/small--static--black) diff --git a/apps/circlesclock/app.js b/apps/circlesclock/app.js index 903c7bdb2..444040ef0 100644 --- a/apps/circlesclock/app.js +++ b/apps/circlesclock/app.js @@ -1,25 +1,3 @@ -const locale = require("locale"); -const storage = require("Storage"); -const SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js"); - -const shoesIcon = atob("EBCBAAAACAAcAB4AHgAeABwwADgGeAZ4AHgAMAAAAHAAIAAA"); -const heartIcon = atob("EBCBAAAAAAAeeD/8P/x//n/+P/w//B/4D/AH4APAAYAAAAAA"); -const powerIcon = atob("EBCBAAAAA8ADwA/wD/AP8A/wD/AP8A/wD/AP8A/wD/AH4AAA"); -const temperatureIcon = atob("EBCBAAAAAYADwAJAAkADwAPAA8ADwAfgB+AH4AfgA8ABgAAA"); - -const weatherCloudy = atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA"); -const weatherSunny = atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA"); -const weatherMoon = atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA"); -const weatherPartlyCloudy = atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA"); -const weatherRainy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA"); -const weatherPartlyRainy = atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA"); -const weatherSnowy = atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA"); -const weatherFoggy = atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA"); -const weatherStormy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA"); - -const sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA"); -const sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA"); - Graphics.prototype.setFontRobotoRegular50NumericOnly = function(scale) { // Actual height 39 (40 - 2) this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAB8AAAAAAAfAAAAAAAPwAAAAAAB8AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAA4AAAAAAB+AAAAAAD/gAAAAAD/4AAAAAH/4AAAAAP/wAAAAAP/gAAAAAf/gAAAAAf/AAAAAA/+AAAAAB/+AAAAAB/8AAAAAD/4AAAAAH/4AAAAAD/wAAAAAA/wAAAAAAPgAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///wAAAB////gAAA////8AAA/////gAAP////8AAH8AAA/gAB8AAAD4AA+AAAAfAAPAAAADwADwAAAA8AA8AAAAPAAPAAAADwADwAAAA8AA8AAAAPAAPgAAAHwAB8AAAD4AAfwAAD+AAD/////AAA/////wAAH////4AAAf///4AAAB///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAPgAAAAAADwAAAAAAB8AAAAAAAfAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAPAAAAAAAH/////wAB/////8AA//////AAP/////wAD/////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAfgAADwAAP4AAB8AAH+AAA/AAD/gAAfwAB/AAAf8AAfAAAP/AAPgAAH7wAD4AAD88AA8AAB+PAAPAAA/DwADwAAfg8AA8AAPwPAAPAAH4DwADwAH8A8AA+AD+APAAPwB/ADwAB/D/gA8AAf//gAPAAD//wADwAAf/wAA8AAD/4AAPAAAHwAADwAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAADgAAAHwAA+AAAD8AAP4AAB/AAD/AAA/wAA/wAAf4AAD+AAHwAAAPgAD4APAB8AA+ADwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA8AH4APAAPgD+AHwAB8B/wD4AAf7/+B+AAD//v//AAA//x//wAAD/4P/4AAAf8B/4AAAAYAH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAHwAAAAAAH8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAB/vAAAAAB/jwAAAAA/g8AAAAA/wPAAAAAfwDwAAAAf4A8AAAAf4APAAAAP8ADwAAAP8AA8AAAH8AAPAAAD/////8AA//////AAP/////wAD/////8AA//////AAAAAAPAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAB/APwAAH//wD+AAD//8A/wAA///AH+AAP//wAPgAD/B4AB8AA8A+AAfAAPAPAADwADwDwAA8AA8A8AAPAAPAPAADwADwD4AA8AA8A+AAPAAPAPwAHwADwD8AD4AA8AfwD+AAPAH///AADwA///wAA8AH//4AAPAAf/4AAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAD//+AAAAD///4AAAD////AAAB////4AAA/78D/AAAfw8AH4AAPweAA+AAD4PgAHwAB8DwAA8AAfA8AAPAAHgPAADwAD4DwAA8AA+A8AAPAAPAPgAHwADwD4AB8AA8AfgA+AAPAH+B/gAAAA///wAAAAH//4AAAAA//8AAAAAH/8AAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAA8AAAABAAPAAAABwADwAAAB8AA8AAAB/AAPAAAB/wADwAAD/8AA8AAD/8AAPAAD/4AADwAD/4AAA8AD/4AAAPAH/wAAADwH/wAAAA8H/wAAAAPH/wAAAAD3/gAAAAA//gAAAAAP/gAAAAAD/gAAAAAA/AAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwA/4AAAH/Af/AAAH/8P/4AAD//n//AAA//7//4AAfx/+A+AAHwD+AHwAD4AfgB8AA8AHwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA+AH4AfAAHwD+AHwAB/D/4D4AAP/+/n+AAD//n//AAAf/w//gAAB/wH/wAAAHwA/4AAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAD/8AAAAAD//wAAAAB//+AAAAA///wAAAAf4H+APAAH4AfgDwAD8AB8A8AA+AAfAPAAPAADwDwADwAA8B8AA8AAPAfAAPAADwHgADwAA8D4AA+AAeB+AAHwAHg/AAB+ADwfgAAP8D4/4AAD////8AAAf///8AAAB///+AAAAP//+AAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAOAAAB8AAHwAAAfgAD8AAAH4AA/AAAB8AAHwAAAOAAA4AAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("DRUcHBwcHBwcHBwcDA=="), 50+(scale<<8)+(1<<16)); @@ -32,48 +10,47 @@ Graphics.prototype.setFontRobotoRegular21 = function(scale) { return this; }; -const SETTINGS_FILE = "circlesclock.json"; +{ +let clock_info = require("clock_info"); +let locale = require("locale"); +let storage = require("Storage"); + +let SETTINGS_FILE = "circlesclock.json"; let settings = Object.assign( storage.readJSON("circlesclock.default.json", true) || {}, storage.readJSON(SETTINGS_FILE, true) || {} ); -// Load step goal from pedometer widget as fallback + //TODO deprecate this (and perhaps use in the clkinfo module) +// Load step goal from health app and pedometer widget as fallback if (settings.stepGoal == undefined) { - const d = storage.readJSON("wpedom.json", true) || {}; - settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000; + let d = storage.readJSON("health.json", true) || {}; + settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.stepGoal : undefined; + + if (settings.stepGoal == undefined) { + d = storage.readJSON("wpedom.json", true) || {}; + settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000; + } } -/* - * Read location from myLocation app - */ -function getLocation() { - return storage.readJSON("mylocation.json", 1) || undefined; -} -let location = getLocation(); - +let drawTimeout; const showWidgets = settings.showWidgets || false; const circleCount = settings.circleCount || 3; +const showBigWeather = settings.showBigWeather || false; -let hrtValue; +let hrtValue; //TODO deprecate this let now = Math.round(new Date().getTime() / 1000); - // layout values: -const colorFg = g.theme.dark ? '#fff' : '#000'; -const colorBg = g.theme.dark ? '#000' : '#fff'; -const colorGrey = '#808080'; -const colorRed = '#ff0000'; -const colorGreen = '#008000'; -const colorBlue = '#0000ff'; -const colorYellow = '#ffff00'; -const widgetOffset = showWidgets ? 24 : 0; -const dowOffset = circleCount == 3 ? 22 : 24; // dow offset relative to date -const h = g.getHeight() - widgetOffset; -const w = g.getWidth(); -const hOffset = (circleCount == 3 ? 34 : 30) - widgetOffset; -const h1 = Math.round(1 * h / 5 - hOffset); -const h2 = Math.round(3 * h / 5 - hOffset); -const h3 = Math.round(8 * h / 8 - hOffset - 3); // circle y position +let colorFg = g.theme.dark ? '#fff' : '#000'; +let colorBg = g.theme.dark ? '#000' : '#fff'; +let widgetOffset = showWidgets ? 24 : 0; +let dowOffset = circleCount == 3 ? 20 : 22; // dow offset relative to date +let h = g.getHeight() - widgetOffset; +let w = g.getWidth(); +let hOffset = (circleCount == 3 ? 34 : 30) - widgetOffset; +let h1 = Math.round(1 * h / 5 - hOffset); +let h2 = Math.round(3 * h / 5 - hOffset); +let h3 = Math.round(8 * h / 8 - hOffset - 3); // circle middle y position /* * circle x positions @@ -87,469 +64,130 @@ const h3 = Math.round(8 * h / 8 - hOffset - 3); // circle y position * | (1) (2) (3) (4) | * => circles start at 1,3,5,7 / 8 */ -const parts = circleCount * 2; -const circlePosX = [ +let parts = circleCount * 2; +let circlePosX = [ Math.round(1 * w / parts), // circle1 Math.round(3 * w / parts), // circle2 Math.round(5 * w / parts), // circle3 Math.round(7 * w / parts), // circle4 ]; -const radiusOuter = circleCount == 3 ? 25 : 20; -const radiusInner = circleCount == 3 ? 20 : 15; -const circleFontSmall = circleCount == 3 ? "Vector:14" : "Vector:10"; -const circleFont = circleCount == 3 ? "Vector:15" : "Vector:11"; -const circleFontBig = circleCount == 3 ? "Vector:16" : "Vector:12"; -const iconOffset = circleCount == 3 ? 6 : 8; -const defaultCircleTypes = ["steps", "hr", "battery", "weather"]; +let radiusOuter = circleCount == 3 ? 25 : 20; +let radiusBorder = radiusOuter+3; // absolute border of circles +let radiusInner = circleCount == 3 ? 20 : 15; +let circleFontSmall = circleCount == 3 ? "Vector:14" : "Vector:10"; +let circleFont = circleCount == 3 ? "Vector:15" : "Vector:11"; +let circleFontBig = circleCount == 3 ? "Vector:16" : "Vector:12"; +let iconOffset = circleCount == 3 ? 6 : 8; -function draw() { - g.clear(true); - if (!showWidgets) { - /* - * we are not drawing the widgets as we are taking over the whole screen - * so we will blank out the draw() functions of each widget and change the - * area to the top bar doesn't get cleared. - */ - if (WIDGETS && typeof WIDGETS === "object") { - for (let wd of WIDGETS) { - wd.draw = () => {}; - wd.area = ""; - } - } - } else { - Bangle.drawWidgets(); - } +let draw = function() { + let R = Bangle.appRect; + g.reset().clearRect(R.x,R.y, R.x2, h3-(radiusBorder+1)); g.setColor(colorBg); g.fillRect(0, widgetOffset, w, h2 + 22); // time g.setFontRobotoRegular50NumericOnly(); - g.setFontAlign(0, -1); g.setColor(colorFg); - g.drawString(locale.time(new Date(), 1), w / 2, h1 + 8); + if (!showBigWeather) { + g.setFontAlign(0, -1); + g.drawString(locale.time(new Date(), 1), w / 2, h1 + 6); + } + else { + g.setFontAlign(-1, -1); + g.drawString(locale.time(new Date(), 1), 2, h1 + 6); + } now = Math.round(new Date().getTime() / 1000); // date & dow g.setFontRobotoRegular21(); - g.setFontAlign(0, 0); - g.drawString(locale.date(new Date()), w / 2, h2); - g.drawString(locale.dow(new Date()), w / 2, h2 + dowOffset); - - drawCircle(1); - drawCircle(2); - drawCircle(3); - if (circleCount >= 4) drawCircle(4); -} - -function drawCircle(index) { - let type = settings['circle' + index]; - if (!type) type = defaultCircleTypes[index - 1]; - const w = getCircleXPosition(type); - - switch (type) { - case "steps": - drawSteps(w); - break; - case "stepsDist": - drawStepsDistance(w); - break; - case "hr": - drawHeartRate(w); - break; - case "battery": - drawBattery(w); - break; - case "weather": - drawWeather(w); - break; - case "sunprogress": - case "sunProgress": - drawSunProgress(w); - break; - case "temperature": - drawTemperature(w); - break; - case "pressure": - drawPressure(w); - break; - case "altitude": - drawAltitude(w); - break; - case "empty": - // we draw nothing here - return; - } -} - -// serves as cache for quicker lookup of circle positions -let circlePositionsCache = []; -/* - * Looks in the following order if a circle with the given type is somewhere visible/configured - * 1. circlePositionsCache - * 2. settings - * 3. defaultCircleTypes - * - * In case 2 and 3 the circlePositionsCache will be updated - */ -function getCirclePosition(type) { - if (circlePositionsCache[type] >= 0) { - return circlePositionsCache[type]; - } - for (let i = 1; i <= circleCount; i++) { - const setting = settings['circle' + i]; - if (setting == type) { - circlePositionsCache[type] = i - 1; - return i - 1; - } - } - for (let i = 0; i < defaultCircleTypes.length; i++) { - if (type == defaultCircleTypes[i] && (!settings || settings['circle' + (i + 1)] == undefined)) { - circlePositionsCache[type] = i; - return i; - } - } - return undefined; -} - -function getCircleXPosition(type) { - const circlePos = getCirclePosition(type); - if (circlePos != undefined) { - return circlePosX[circlePos]; - } - return undefined; -} - -function isCircleEnabled(type) { - return getCirclePosition(type) != undefined; -} - -function getCircleColor(type) { - const pos = getCirclePosition(type); - const color = settings["circle" + (pos + 1) + "color"]; - if (color && color != "") return color; -} - -function getCircleIconColor(type, color, percent) { - const pos = getCirclePosition(type); - const colorizeIcon = settings["circle" + (pos + 1) + "colorizeIcon"] == true; - if (colorizeIcon) { - return getGradientColor(color, percent); + if (!showBigWeather) { + g.setFontAlign(0, 0); + g.drawString(locale.date(new Date()), w / 2, h2); + g.drawString(locale.dow(new Date()), w / 2, h2 + dowOffset); } else { - return ""; + g.setFontAlign(-1, 0); + g.drawString(locale.date(new Date()), 2, h2); + g.drawString(locale.dow(new Date()), 2, h2 + dowOffset, 1); } + + // weather + if (showBigWeather) { + let weather = getWeather(); + let tempString = weather ? locale.temp(weather.temp - 273.15) : undefined; + g.setFontAlign(1, 0); + if (tempString) g.drawString(tempString, w, h2); + + let code = weather ? weather.code : -1; + let icon = getWeatherIconByCode(code, true); + if (icon) g.drawImage(icon, w - 48, h1, {scale:0.75}); + } + + queueDraw(); } -function getGradientColor(color, percent) { +let getCircleColor = function(index) { + let color = settings["circle" + index + "color"]; + if (color && color != "") return color; + return g.theme.fg; +} + +let getGradientColor = function(color, percent) { if (isNaN(percent)) percent = 0; if (percent > 1) percent = 1; - const colorList = [ + let colorList = [ '#00FF00', '#80FF00', '#FFFF00', '#FF8000', '#FF0000' ]; + if (color == "fg") { + color = colorFg; + } if (color == "green-red") { - const colorIndex = Math.round(colorList.length * percent); + let colorIndex = Math.round(colorList.length * percent); return colorList[Math.min(colorIndex, colorList.length) - 1] || "#00ff00"; } if (color == "red-green") { - const colorIndex = colorList.length - Math.round(colorList.length * percent); + let colorIndex = colorList.length - Math.round(colorList.length * percent); return colorList[Math.min(colorIndex, colorList.length)] || "#ff0000"; } return color; } -function getImage(graphic, color) { - if (!color || color == "") { - return graphic; +let getCircleIconColor = function(index, color, percent) { + let colorizeIcon = settings["circle" + index + "colorizeIcon"] == true; + if (colorizeIcon) { + return getGradientColor(color, percent); } else { - return { - width: 16, - height: 16, - bpp: 1, - transparent: 0, - buffer: E.toArrayBuffer(graphic), - palette: new Uint16Array([colorBg, g.toColor(color)]) - }; + return g.theme.fg; } } -function drawSteps(w) { - if (!w) w = getCircleXPosition("steps"); - const steps = getSteps(); - - drawCircleBackground(w); - - const color = getCircleColor("steps"); - - let percent; - const stepGoal = settings.stepGoal; - if (stepGoal > 0) { - percent = steps / stepGoal; - if (stepGoal < steps) percent = 1; - drawGauge(w, h3, percent, color); - } - +let drawEmpty = function(img, w, color) { + drawGauge(w, h3, 0, color); drawInnerCircleAndTriangle(w); - - writeCircleText(w, shortValue(steps)); - - g.drawImage(getImage(shoesIcon, getCircleIconColor("steps", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); + writeCircleText(w, "?"); + if(img) + g.setColor(getGradientColor(color, 0)) + .drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24}); } -function drawStepsDistance(w) { - if (!w) w = getCircleXPosition("stepsDistance"); - const steps = getSteps(); - const stepDistance = settings.stepLength; - const stepsDistance = Math.round(steps * stepDistance); - +let drawCircle = function(index, item, data) { + var w = circlePosX[index-1]; drawCircleBackground(w); - - const color = getCircleColor("stepsDistance"); - - let percent; - const stepDistanceGoal = settings.stepDistanceGoal; - if (stepDistanceGoal > 0) { - percent = stepsDistance / stepDistanceGoal; - if (stepDistanceGoal < stepsDistance) percent = 1; - drawGauge(w, h3, percent, color); - } - - drawInnerCircleAndTriangle(w); - - writeCircleText(w, shortValue(stepsDistance)); - - g.drawImage(getImage(shoesIcon, getCircleIconColor("stepsDistance", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); -} - -function drawHeartRate(w) { - if (!w) w = getCircleXPosition("hr"); - - drawCircleBackground(w); - - const color = getCircleColor("hr"); - - let percent; - if (hrtValue != undefined) { - const minHR = settings.minHR; - const maxHR = settings.maxHR; - percent = (hrtValue - minHR) / (maxHR - minHR); - if (isNaN(percent)) percent = 0; - drawGauge(w, h3, percent, color); - } - - drawInnerCircleAndTriangle(w); - - writeCircleText(w, hrtValue != undefined ? hrtValue : "-"); - - g.drawImage(getImage(heartIcon, getCircleIconColor("hr", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); -} - -function drawBattery(w) { - if (!w) w = getCircleXPosition("battery"); - const battery = E.getBattery(); - - drawCircleBackground(w); - - let color = getCircleColor("battery"); - - let percent; - if (battery > 0) { - percent = battery / 100; - drawGauge(w, h3, percent, color); - } - - drawInnerCircleAndTriangle(w); - - if (Bangle.isCharging()) { - color = colorGreen; - } else { - if (settings.batteryWarn != undefined && battery <= settings.batteryWarn) { - color = colorRed; - } - } - writeCircleText(w, battery + '%'); - - g.drawImage(getImage(powerIcon, getCircleIconColor("battery", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); -} - -function drawWeather(w) { - if (!w) w = getCircleXPosition("weather"); - const weather = getWeather(); - const tempString = weather ? locale.temp(weather.temp - 273.15) : undefined; - const code = weather ? weather.code : -1; - - drawCircleBackground(w); - - const color = getCircleColor("weather"); - let percent; - const data = settings.weatherCircleData; - switch (data) { - case "humidity": - const humidity = weather ? weather.hum : undefined; - if (humidity >= 0) { - percent = humidity / 100; - drawGauge(w, h3, percent, color); - } - break; - case "wind": - if (weather) { - const wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/); - if (wind[1] >= 0) { - if (wind[2] == "kmh") { - wind[1] = windAsBeaufort(wind[1]); - } - // wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale) - percent = wind[1] / 12; - drawGauge(w, h3, percent, color); - } - } - break; - case "empty": - break; - } - - drawInnerCircleAndTriangle(w); - - writeCircleText(w, tempString ? tempString : "?"); - - if (code > 0) { - const icon = getWeatherIconByCode(code); - if (icon) g.drawImage(getImage(icon, getCircleIconColor("weather", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); - } else { - g.drawString("?", w, h3 + radiusOuter); - } -} - - -function drawSunProgress(w) { - if (!w) w = getCircleXPosition("sunprogress"); - const percent = getSunProgress(); - - drawCircleBackground(w); - - const color = getCircleColor("sunprogress"); - + const color = getCircleColor(index); + //drawEmpty(info? info.img : null, w, color); + var img = data.img; + var percent = 1; //fill up if no range + var txt = ""+data.text; + if (txt.endsWith(" bpm")) txt=txt.slice(0,-4); // hack for heart rate - remove the 'bpm' text + if(item.hasRange) percent = (data.v-data.min) / (data.max-data.min); + if(data.short) txt = data.short; drawGauge(w, h3, percent, color); - drawInnerCircleAndTriangle(w); - - let icon = sunSetDown; - let text = "?"; - const times = getSunData(); - if (times != undefined) { - const sunRise = Math.round(times.sunrise.getTime() / 1000); - const sunSet = Math.round(times.sunset.getTime() / 1000); - if (!isDay()) { - // night - if (now > sunRise) { - // after sunRise - const upcomingSunRise = sunRise + 60 * 60 * 24; - text = formatSeconds(upcomingSunRise - now); - } else { - text = formatSeconds(sunRise - now); - } - icon = sunSetUp; - } else { - // day, approx sunrise tomorrow: - text = formatSeconds(sunSet - now); - icon = sunSetDown; - } - } - - writeCircleText(w, text); - - g.drawImage(getImage(icon, getCircleIconColor("sunprogress", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); -} - -function drawTemperature(w) { - if (!w) w = getCircleXPosition("temperature"); - - getPressureValue("temperature").then((temperature) => { - drawCircleBackground(w); - - const color = getCircleColor("temperature"); - - let percent; - if (temperature) { - const min = -40; - const max = 85; - percent = (temperature - min) / (max - min); - drawGauge(w, h3, percent, color); - } - - drawInnerCircleAndTriangle(w); - - if (temperature) - writeCircleText(w, locale.temp(temperature)); - - g.drawImage(getImage(temperatureIcon, getCircleIconColor("temperature", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); - - }); -} - -function drawPressure(w) { - if (!w) w = getCircleXPosition("pressure"); - - getPressureValue("pressure").then((pressure) => { - drawCircleBackground(w); - - const color = getCircleColor("pressure"); - - let percent; - if (pressure && pressure > 0) { - const minPressure = 950; - const maxPressure = 1050; - percent = (pressure - minPressure) / (maxPressure - minPressure); - drawGauge(w, h3, percent, color); - } - - drawInnerCircleAndTriangle(w); - - if (pressure) - writeCircleText(w, Math.round(pressure)); - - g.drawImage(getImage(temperatureIcon, getCircleIconColor("pressure", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); - - }); -} - -function drawAltitude(w) { - if (!w) w = getCircleXPosition("altitude"); - - getPressureValue("altitude").then((altitude) => { - drawCircleBackground(w); - - const color = getCircleColor("altitude"); - - let percent; - if (altitude) { - const min = 0; - const max = 10000; - percent = (altitude - min) / (max - min); - drawGauge(w, h3, percent, color); - } - - drawInnerCircleAndTriangle(w); - - if (altitude) - writeCircleText(w, locale.distance(Math.round(altitude))); - - g.drawImage(getImage(temperatureIcon, getCircleIconColor("altitude", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); - - }); -} - -/* - * wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale) - */ -function windAsBeaufort(windInKmh) { - const beaufort = [2, 6, 12, 20, 29, 39, 50, 62, 75, 89, 103, 118]; - let l = 0; - while (l < beaufort.length && beaufort[l] < windInKmh) { - l++; - } - return l; + writeCircleText(w, txt); + g.setColor(getCircleIconColor(index, color, percent)) + .drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24}); } @@ -557,8 +195,22 @@ function windAsBeaufort(windInKmh) { * Choose weather icon to display based on weather conditition code * https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2 */ -function getWeatherIconByCode(code) { - const codeGroup = Math.round(code / 100); +let getWeatherIconByCode = function(code, big) { + let codeGroup = Math.round(code / 100); + if (big == undefined) big = false; + + // weather icons: + let weatherCloudy = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAf/4AAAAAAAD//4AAAAAAA///wAAAAAAH+B/gAAAAAA/AA/AAAAAAH4AB+AAAAAAfAAD8AAAAAD4AAH/wAAAAfAAAP/wAAAB4AAAf/gAAAHgAAB//AAAB+AAACB+AAAfwAAAAB8AAH/AAAAAD4AA/8AAAAAHgAD8AAAAAAeAAfAAAAAAA8AB4AAAAAADwAPgAAAAAAPAA8AAAAAAA8APwAAAAAADwB/AAAAAAAPAP8AAAAAAB8B+AAAAAAAH4PgAAAAAAAHx8AAAAAAAAPngAAAAAAAAeeAAAAAAAAB7wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA94AAAAAAAAHngAAAAAAAAefAAAAAAAAD4+AAAAAAAAfB+AAAAAAAH4D/////////AH////////4AP////////AAH///////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA"); + let weatherSunny = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAMAA8AAwAAAB4ADwAHgAAAHwAPAA+AAAAPgA8AHwAAAAfADwA+AAAAA+AfgHwAAAAB8P/w+AAAAAD7//3wAAAAAH///+AAAAAAP+B/wAAAAAAfgB+AAAAAAD4AB8AAAAAAPAADwAAAAAB8AAPgAAAAAHgAAeAAAAAAeAAB4AAAAADwAADwAAAP//AAAP//AA//8AAA//8AD//wAAD//wAP//AAAP//AAAA8AAA8AAAAAB4AAHgAAAAAHgAAeAAAAAAfAAD4AAAAAA8AAPAAAAAAD4AB8AAAAAAH4AfgAAAAAA/4H/AAAAAAH///+AAAAAA+//98AAAAAHw//D4AAAAA+AfgHwAAAAHwA8APgAAAA+ADwAfAAAAHwAPAA+AAAAeAA8AB4AAAAwADwADAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA"); + let weatherMoon = big ? atob("QECBAAAGAAAADwAAAA+AAAAPAAAAD8AAAA8AAAAP4AAADwAAAAfwDwD/8AAAB/gPAP/wAAAH+A8A//AAAAf8DwD/8AAAB/4AAA8AAAAHvgAADwAAAAeeAAAPAAAAB54AAA8AAAAHjwAAAAAAAA+PDgAAAAAADw8PgAAAAAAfDw/AAAAAAB4PD+AAAAAAPg8D8AAAAAB8HwH4AAAAAfg+APwAAPAH8H4Af/AA///g/gA//AD//8H4AB/+AH//AfAAH/8Af/wD4AAIH4A/gAPAAAAHwB/AB8AAAAPgD/AfgAAAAeAH//+AAAAB4AP//4AAAADwAP//AAAAAPAAH/8AAAAA8AAAAAAAAADwAAAAAAAAAPAHAAAAAAAA8A/gAAAAAAHwH4AAAAAAAfg+AAAAAAAAfHwAAAAAAAA+eAAAAAAAAB54AAAAAAAAHvAAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD3gAAAAAAAAeeAAAAAAAAB58AAAAAAAAPj4AAAAAAAB8H4AAAAAAAfgP////////8Af////////gA////////8AAf//////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA"); + let weatherPartlyCloudy = big ? atob("QECBAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAcADwAOAAAAB4APAB4AAAAHwA8APgAAAAPgH4B8AAAAAfD/8PgAAAAA+//98AAAAAB////gQAAAAD/gf8DgAAAAH4AfgfAAAAA+AA/B+AAAADwAP8D8AAAAfAB/gH/wAAB4APwAP/wAAHgB+AAf/gAA8AHwAB//AP/wA+AACB+A//AHwAAAB8D/8AeAAAAD4P/wB4AAAAHgAPAfgAAAAeAAeH8AAAAA8AB5/wAAAADwAH3/AAAAAPAAP/AAAAAA8AA/wAAAAADwBB+AAAAAAPAOD4AAAAAB8B4HAAAAAAH4PgMAAAAAAHx8AAAAAAAAPngAAAAAAAAeeAAAAAAAAB7wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA94AAAAAAAAHngAAAAAAAAefAAAAAAAAD4+AAAAAAAAfB+AAAAAAAH4D/////////AH////////4AP////////AAH///////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA"); + let weatherRainy = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAf/4AAAAAAAD//4AAAAAAA///wAAAAAAH+B/gAAAAAA/AA/AAAAAAH4AB+AAAAAAfAAD8AAAAAD4AAH/wAAAAfAAAP/wAAAB4AAAf/gAAAHgAAB//AAAB+AAACB+AAAfwAAAAB8AAH/AAAAAD4AA/8AAAAAHgAD8AAAAAAeAAfAAAAAAA8AB4AAAAAADwAPgAAAAAAPAA8AAAAAAA8APwAAAAAADwB/AAAAAAAPAP8AAAAAAB8B+ADwAPAAH4PgAPAA8AAHx8AA8ADwAAPngADwAPAAAeeAAAAAAAAB7wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAA8PDw8AD/AADw8PDwAP8AAPDw8PAA94AA8PDw8AHngAAAAAAAAefAAAAAAAAD4+AAAAAAAAfB+AAAAAAAH4D/8PDw8PD/AH/w8PDw8P4AP/Dw8PDw/AAH8PDw8PDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8ADwAAAAAADwAPAAAAAAAPAA8AAAAAAA8ADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA"); + let weatherPartlyRainy = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAf/4AAAAAAAD//4AAAAAAA///wAAAAAAH+B/gAAAAAA/AA/AAAAAAH4AB+AAAAAAfAAD8AAAAAD4AAH/wAAAAfAAAP/wAAAB4AAAf/gAAAHgAAB//AAAB+AAACB+AAAfwAAAAB8AAH/AAAAAD4AA/8AAAAAHgAD8AAAAAAeAAfAAAAAAA8AB4AAAAAADwAPgAAAAAAPAA8AAAAAAA8APwAAAAAADwB/AAAAAAAPAP8AAAAAAB8B+AAAAPAAH4PgAAAA8AAHx8AAAADwAAPngAAAAPAAAeeAAAAA8AAB7wAAAADwAAD/AAAAAPAAAP8AAAAA8AAA/wAAAPDwAAD/AAAA8PAAAP8AAADw8AAA94AAAPDwAAHngAAA8PAAAefAAADw8AAD4+AAAPDwAAfB+AAA8PAAH4D///Dw8P//AH//8PDw//4AP//w8PD//AAH//Dw8P/gAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA"); + let weatherSnowy = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAf/4AAAAAAAD//4AAAAAAA///wAAAAAAH+B/gAAAAAA/AA/AAAAAAH4AB+AAAAAAfAAD8AAAAAD4AAH/wAAAAfAAAP/wAAAB4AAAf/gAAAHgAAB//AAAB+AAACB+AAAfwAAAAB8AAH/AAAAAD4AA/8AAAAAHgAD8AAAAAAeAAfAAAAAAA8AB4AAAAAADwAPgAAAAAAPAA8AAAAAAA8APwAAAAAADwB/AAAAAAAPAP8AAAAAAB8B+AAAAA8AH4PgAAAADwAHx8AAAAAPAAPngAAAAA8AAeeAAPAA//AB7wAA8AD/8AD/AADwAP/wAP8AAPAA//AA/wAP/wAPAAD/AA//AA8AAP8AD/8ADwAA94AP/wAPAAHngADwAAAAAefAAPAAAAAD4+AA8AAAAAfB+ADwAAAAH4D/8AAPAP//AH/wAA8A//4AP/AADwD//AAH8AAPAP/gAAAAAP/wAAAAAAAA//AAAAAAAAD/8AAAAAAAAP/wAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA"); + let weatherFoggy = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAMAA8AAwAAAB4ADwAHgAAAHwAPAA+AAAAPgA8AHwAAAAfADwA+AAAAA+AfgHwAAAAB8P/w+AAAAAD7//3wAAAAAH///+AAAAAAP+B/wAAAAAAfgB+AAAAAAD4AB8AAAAAAPAADwAAAAAB8AAPgAAAAAHgAAeAAAAAAeAAB4AAAAADwAADwAAAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AA///wAA8AAAD///AAHgAAAP//8AAeAAAA///wAD4AAAAAAAAAPAAAAAAAAAB8AAAAAAAAAfgAAAAAAAAH/AAAAA///w/+AAAAD///D98AAAAP//8PD4AAAA///wgHwAAAAAAAAAPgAAAAAAAAAfAAAAAAAAAA+AAAAAAAAAB4AAD///DwADAAAP//8PAAAAAA///w8AAAAAD///DwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA"); + let weatherStormy = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAf/4AAAAAAAD//4AAAAAAA///wAAAAAAH+B/gAAAAAA/AA/AAAAAAH4AB+AAAAAAfAAD8AAAAAD4AAH/wAAAAfAAAP/wAAAB4AAAf/gAAAHgAAB//AAAB+AAACB+AAAfwAAAAB8AAH/AAAAAD4AA/8AAAAAHgAD8AAAAAAeAAfAAAAAAA8AB4AAAAAADwAPgAAAAAAPAA8AAAAAAA8APwAAAAAADwB/AAAAAAAPAP8AAAAAAB8B+AAAAAAAH4PgAAAAAAAHx8AAAAAAAAPngAAAAAAAAeeAAAAA/wAB7wAAAAH+AAD/AAAAAf4AAP8AAAAD/AAA/wAAAAP4AAD/AAAAB/gAAP8AAAAH8AAA94AAAA/wAAHngAAAD+AAAefAAAAfwAAD4+AAAB/AAAfB+AAAP4AAH4D///g//w//AH//+H/+D/4AP//wf/wf/AAH//D//D/gAAAAAAD4AAAAAAAAAfAAAAAAAAAB8AAAAAAAAAPgAAAAAAAAA8AAAAAAAAAHwAAAAAAAAAeAAAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA"); + let unknown = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAH/+AAAAAAAB//+AAAAAAAP//8AAAAAAB/gf4AAAAAAPwAPwAAAAAB+AAfgAAAAAPwAA+AAAAAA+B+B8AAAAAHwf+D4AAAAAfD/8HgAAAAB4P/4eAAAAAPh8Ph8AAAAA+HgfDwAAAAD/8A8PAAAAAP/wDw8AAAAA//APDwAAAAB/4A8PAAAAAAAAHw8AAAAAAAB+HwAAAAAAAfweAAAAAAAH+B4AAAAAAA/wPgAAAAAAH8B8AAAAAAA/APgAAAAAAD4B+AAAAAAAfAfwAAAAAAB4H+AAAAAAAPh/gAAAAAAA8H8AAAAAAADw/AAAAAAAAPDwAAAAAAAA//AAAAAAAAD/8AAAAAAAAP/wAAAAAAAAf+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/gAAAAAAAA//AAAAAAAAD/8AAAAAAAAP/wAAAAAAAA8PAAAAAAAADw8AAAAAAAAPDwAAAAAAAA8PAAAAAAAAD/8AAAAAAAAP/wAAAAAAAA//AAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : undefined; + switch (codeGroup) { case 2: return weatherStormy; @@ -586,7 +238,9 @@ function getWeatherIconByCode(code) { case 8: switch (code) { case 800: - return isDay() ? weatherSunny : weatherMoon; + var hr = (new Date()).getHours(); + var isDay = (hr>6) && (hr<=18); // fixme we don't want to include ALL of suncalc just to choose one icon + return isDay ? weatherSunny : weatherMoon; case 801: return weatherPartlyCloudy; case 802: @@ -594,84 +248,24 @@ function getWeatherIconByCode(code) { default: return weatherCloudy; } - default: - return undefined; - } -} - - -function isDay() { - const times = getSunData(); - if (times == undefined) return true; - const sunRise = Math.round(times.sunrise.getTime() / 1000); - const sunSet = Math.round(times.sunset.getTime() / 1000); - - return (now > sunRise && now < sunSet); -} - -function formatSeconds(s) { - if (s > 60 * 60) { // hours - return Math.round(s / (60 * 60)) + "h"; - } - if (s > 60) { // minutes - return Math.round(s / 60) + "m"; - } - return "<1m"; -} - -function getSunData() { - if (location != undefined && location.lat != undefined) { - // get today's sunlight times for lat/lon - return SunCalc ? SunCalc.getTimes(new Date(), location.lat, location.lon) : undefined; - } - return undefined; -} - -/* - * Calculated progress of the sun between sunrise and sunset in percent - * - * Taken from rebble app and modified - */ -function getSunProgress() { - const times = getSunData(); - if (times == undefined) return 0; - const sunRise = Math.round(times.sunrise.getTime() / 1000); - const sunSet = Math.round(times.sunset.getTime() / 1000); - - if (isDay()) { - // during day - const dayLength = sunSet - sunRise; - if (now > sunRise) { - return (now - sunRise) / dayLength; - } else { - return (sunRise - now) / dayLength; - } - } else { - // during night - if (now < sunRise) { - const prevSunSet = sunSet - 60 * 60 * 24; - return 1 - (sunRise - now) / (sunRise - prevSunSet); - } else { - const upcomingSunRise = sunRise + 60 * 60 * 24; - return (upcomingSunRise - now) / (upcomingSunRise - sunSet); - } + default: + return unknown; } } /* * Draws the background and the grey circle */ -function drawCircleBackground(w) { - g.clearRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3); +let drawCircleBackground = function(w) { // Draw rectangle background: g.setColor(colorBg); - g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3); + g.fillRect(w - radiusBorder, h3 - radiusBorder, w + radiusBorder, g.getHeight()-1); // Draw grey background circle: - g.setColor(colorGrey); + g.setColor('#808080'); // grey g.fillCircle(w, h3, radiusOuter); } -function drawInnerCircleAndTriangle(w) { +let drawInnerCircleAndTriangle = function(w) { // Draw inner circle g.setColor(colorBg); g.fillCircle(w, h3, radiusInner); @@ -679,38 +273,39 @@ function drawInnerCircleAndTriangle(w) { g.fillPoly([w, h3, w - 15, h3 + radiusOuter + 5, w + 15, h3 + radiusOuter + 5]); } -function radians(a) { - return a * Math.PI / 180; -} - /* * This draws the actual gauge consisting out of lots of little filled circles */ -function drawGauge(cx, cy, percent, color) { - const offset = 15; - const end = 360 - offset; - const radius = radiusInner + (circleCount == 3 ? 3 : 2); - const size = radiusOuter - radiusInner - 2; +let drawGauge = function(cx, cy, percent, color) { + let offset = 15; + let end = 360 - offset; + let radius = radiusOuter+1; if (percent <= 0) return; // no gauge needed if (percent > 1) percent = 1; - const startRotation = -offset; - const endRotation = startRotation - ((end - offset) * percent); + let startRotation = -offset; + let endRotation = startRotation - ((end - offset) * percent); color = getGradientColor(color, percent); g.setColor(color); - - for (let i = startRotation; i > endRotation - size; i -= size) { - x = cx + radius * Math.sin(radians(i)); - y = cy + radius * Math.cos(radians(i)); - g.fillCircle(x, y, size); - } + // convert to radians + startRotation *= Math.PI / 180; + let amt = Math.PI / 10; + endRotation = (endRotation * Math.PI / 180) - amt; + // all we need to draw is an arc, because we'll fill the center + let poly = [cx,cy]; + for (let r = startRotation; r > endRotation; r -= amt) + poly.push( + cx + radius * Math.sin(r), + cy + radius * Math.cos(r) + ); + g.fillPoly(poly); } -function writeCircleText(w, content) { +let writeCircleText = function(w, content) { if (content == undefined) return; - const font = String(content).length > 4 ? circleFontSmall : String(content).length > 3 ? circleFont : circleFontBig; + let font = String(content).length > 4 ? circleFontSmall : String(content).length > 3 ? circleFont : circleFontBig; g.setFont(font); g.setFontAlign(0, 0); @@ -718,119 +313,55 @@ function writeCircleText(w, content) { g.drawString(content, w, h3); } -function shortValue(v) { - if (isNaN(v)) return '-'; - if (v <= 999) return v; - if (v >= 1000 && v < 10000) { - v = Math.floor(v / 100) * 100; - return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; - } - if (v >= 10000) { - v = Math.floor(v / 1000) * 1000; - return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; - } -} - -function getSteps() { - if (Bangle.getHealthStatus) { - return Bangle.getHealthStatus("day").steps; - } - if (WIDGETS && WIDGETS.wpedom !== undefined) { - return WIDGETS.wpedom.getSteps(); - } - return 0; -} - -function getWeather() { - const jsonWeather = storage.readJSON('weather.json'); +let getWeather=function() { + let jsonWeather = storage.readJSON('weather.json'); return jsonWeather && jsonWeather.weather ? jsonWeather.weather : undefined; } -function enableHRMSensor() { - Bangle.setHRMPower(1, "circleclock"); - if (hrtValue == undefined) { - hrtValue = '...'; - drawHeartRate(); +g.clear(1); // clear the whole screen + +Bangle.setUI({ + mode : "clock", + remove : function() { + // Called to unload all of the clock app (allowing for 'fast load') + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + clockInfoMenu.forEach(c => c.remove()); + delete Graphics.prototype.setFontRobotoRegular50NumericOnly; + delete Graphics.prototype.setFontRobotoRegular21; + if (!showWidgets) require("widget_utils").show(); } -} +}); -let pressureLocked = false; -let pressureCache; - -function getPressureValue(type) { - return new Promise((resolve) => { - if (Bangle.getPressure) { - if (!pressureLocked) { - pressureLocked = true; - if (pressureCache && pressureCache[type]) { - resolve(pressureCache[type]); - } - Bangle.getPressure().then(function(d) { - pressureLocked = false; - if (d) { - pressureCache = d; - if (d[type]) { - resolve(d[type]); - } - } - }).catch(() => {}); - } else { - if (pressureCache && pressureCache[type]) { - resolve(pressureCache[type]); - } - } - } +let clockInfoDraw = (itm, info, options) => { + //print("Draw",itm.name,options); + drawCircle(options.circlePosition, itm, info); + if (options.focus) g.reset().drawRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1) +}; +let clockInfoItems = require("clock_info").load(); +let clockInfoMenu = []; +for(var i=0;i= (settings.confidence)) { - hrtValue = hrm.bpm; - if (Bangle.isLCDOn()) { - drawHeartRate(); - } - } - // Let us wait before we overwrite "good" HRM values: - if (Bangle.isLCDOn()) { - if (timerHrm) clearTimeout(timerHrm); - timerHrm = setTimeout(() => { - hrtValue = '...'; - drawHeartRate(); - }, settings.hrmValidity * 1000); - } - } -}); - -Bangle.on('charging', function(charging) { - if (isCircleEnabled("battery")) drawBattery(); -}); - -if (isCircleEnabled("hr")) { - enableHRMSensor(); + }, queueMillis - (Date.now() % queueMillis)); } - -Bangle.setUI("clock"); -Bangle.loadWidgets(); - -// schedule a draw for the next minute -setTimeout(function() { - // draw every 60 seconds - setInterval(draw,60000); -}, 60000 - (Date.now() % 60000)); - draw(); +} diff --git a/apps/circlesclock/default.json b/apps/circlesclock/default.json index cb6bfcff8..ad409b992 100644 --- a/apps/circlesclock/default.json +++ b/apps/circlesclock/default.json @@ -1,18 +1,8 @@ { - "minHR": 40, - "maxHR": 200, - "confidence": 0, - "stepGoal": 10000, - "stepDistanceGoal": 8000, - "stepLength": 0.8, "batteryWarn": 30, "showWidgets": false, "weatherCircleData": "humidity", "circleCount": 3, - "circle1": "hr", - "circle2": "steps", - "circle3": "battery", - "circle4": "weather", "circle1color": "green-red", "circle2color": "#0000ff", "circle3color": "red-green", @@ -21,5 +11,15 @@ "circle2colorizeIcon": true, "circle3colorizeIcon": true, "circle4colorizeIcon": false, + "updateInterval": 60, + "showBigWeather": false, + + "minHR": 40, + "maxHR": 200, + "confidence": 0, + "stepGoal": 10000, + "stepDistanceGoal": 8000, + "stepLength": 0.8, "hrmValidity": 60 + } diff --git a/apps/circlesclock/metadata.json b/apps/circlesclock/metadata.json index 3279ec2cf..7b4c25532 100644 --- a/apps/circlesclock/metadata.json +++ b/apps/circlesclock/metadata.json @@ -1,7 +1,7 @@ { "id": "circlesclock", "name": "Circles clock", "shortName":"Circles clock", - "version":"0.10", + "version":"0.21", "description": "A clock with three or four circles for different data at the bottom in a probably familiar style", "icon": "app.png", "screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}], diff --git a/apps/circlesclock/screenshot-light-with-big-weather.png b/apps/circlesclock/screenshot-light-with-big-weather.png new file mode 100644 index 000000000..d1d569247 Binary files /dev/null and b/apps/circlesclock/screenshot-light-with-big-weather.png differ diff --git a/apps/circlesclock/settings.js b/apps/circlesclock/settings.js index bec539376..5c5ea4f27 100644 --- a/apps/circlesclock/settings.js +++ b/apps/circlesclock/settings.js @@ -1,6 +1,7 @@ (function(back) { const SETTINGS_FILE = "circlesclock.json"; const storage = require('Storage'); + const clock_info = require("clock_info"); let settings = Object.assign( storage.readJSON("circlesclock.default.json", true) || {}, storage.readJSON(SETTINGS_FILE, true) || {} @@ -11,11 +12,10 @@ storage.write(SETTINGS_FILE, settings); } - const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude"]; - const namesCircleTypes = ["empty", "steps", "distance", "heart", "battery", "weather", "sun", "temperature", "pressure", "altitude"]; - - const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", "#00ffff", "#fff", "#000", "green-red", "red-green"]; - const namesColors = ["default", "red", "green", "blue", "yellow", "magenta", "cyan", "white", "black", "green->red", "red->green"]; + const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", + "#00ffff", "#fff", "#000", "green-red", "red-green", "fg"]; + const namesColors = ["default", "red", "green", "blue", "yellow", "magenta", + "cyan", "white", "black", "green->red", "red->green", "foreground"]; const weatherData = ["empty", "humidity", "wind"]; @@ -34,8 +34,6 @@ /*LANG*/'circle 2': ()=>showCircleMenu(2), /*LANG*/'circle 3': ()=>showCircleMenu(3), /*LANG*/'circle 4': ()=>showCircleMenu(4), - /*LANG*/'heartrate': ()=>showHRMenu(), - /*LANG*/'steps': ()=>showStepMenu(), /*LANG*/'battery warn': { value: settings.batteryWarn, min: 10, @@ -56,96 +54,32 @@ min: 0, max: 2, format: v => weatherData[v], onchange: x => save('weatherCircleData', weatherData[x]), + }, + /*LANG*/'update interval': { + value: settings.updateInterval, + min: 0, + max : 3600, + step: 30, + format: x => { + return x + 's'; + }, + onchange: x => save('updateInterval', x), + }, + //TODO deprecated local icons, may disappear in future + /*LANG*/'legacy weather icons': { + value: !!settings.legacyWeatherIcons, + format: () => (settings.legacyWeatherIcons ? 'Yes' : 'No'), + onchange: x => save('legacyWeatherIcons', x), + }, + /*LANG*/'show big weather': { + value: !!settings.showBigWeather, + format: () => (settings.showBigWeather ? 'Yes' : 'No'), + onchange: x => save('showBigWeather', x), } }; E.showMenu(menu); } - function showHRMenu() { - let menu = { - '': { 'title': /*LANG*/'Heartrate' }, - /*LANG*/'< Back': ()=>showMainMenu(), - /*LANG*/'minimum': { - value: settings.minHR, - min: 0, - max : 250, - step: 5, - format: x => { - return x + " bpm"; - }, - onchange: x => save('minHR', x), - }, - /*LANG*/'maximum': { - value: settings.maxHR, - min: 20, - max : 250, - step: 5, - format: x => { - return x + " bpm"; - }, - onchange: x => save('maxHR', x), - }, - /*LANG*/'min. confidence': { - value: settings.confidence, - min: 0, - max : 100, - step: 10, - format: x => { - return x + "%"; - }, - onchange: x => save('confidence', x), - }, - /*LANG*/'valid period': { - value: settings.hrmValidity, - min: 10, - max : 600, - step: 10, - format: x => { - return x + "s"; - }, - onchange: x => save('hrmValidity', x), - }, - }; - E.showMenu(menu); - } - - function showStepMenu() { - let menu = { - '': { 'title': /*LANG*/'Steps' }, - /*LANG*/'< Back': ()=>showMainMenu(), - /*LANG*/'goal': { - value: settings.stepGoal, - min: 2000, - max : 50000, - step: 2000, - format: x => { - return x; - }, - onchange: x => save('stepGoal', x), - }, - /*LANG*/'distance goal': { - value: settings.stepDistanceGoal, - min: 2000, - max : 30000, - step: 1000, - format: x => { - return x; - }, - onchange: x => save('stepDistanceGoal', x), - }, - /*LANG*/'step length': { - value: settings.stepLength, - min: 0.1, - max : 1.5, - step: 0.01, - format: x => { - return x; - }, - onchange: x => save('stepLength', x), - } - }; - E.showMenu(menu); - } function showCircleMenu(circleId) { const circleName = "circle" + circleId; const colorKey = circleName + "color"; @@ -154,12 +88,6 @@ const menu = { '': { 'title': /*LANG*/'Circle ' + circleId }, /*LANG*/'< Back': ()=>showMainMenu(), - /*LANG*/'data': { - value: valuesCircleTypes.indexOf(settings[circleName]), - min: 0, max: valuesCircleTypes.length - 1, - format: v => namesCircleTypes[v], - onchange: x => save(circleName, valuesCircleTypes[x]), - }, /*LANG*/'color': { value: valuesColors.indexOf(settings[colorKey]) || 0, min: 0, max: valuesColors.length - 1, @@ -175,6 +103,5 @@ E.showMenu(menu); } - showMainMenu(); }); diff --git a/apps/cliock/metadata.json b/apps/cliock/metadata.json index c5d3fa49e..2df48892e 100644 --- a/apps/cliock/metadata.json +++ b/apps/cliock/metadata.json @@ -9,6 +9,7 @@ "type": "clock", "tags": "clock,cli,command,bash,shell", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"cliock.app.js","url":"app.js"}, diff --git a/apps/nato/changelog.txt b/apps/clkinfocal/ChangeLog similarity index 100% rename from apps/nato/changelog.txt rename to apps/clkinfocal/ChangeLog diff --git a/apps/clkinfocal/app.png b/apps/clkinfocal/app.png new file mode 100644 index 000000000..ed79cd884 Binary files /dev/null and b/apps/clkinfocal/app.png differ diff --git a/apps/clkinfocal/clkinfo.js b/apps/clkinfocal/clkinfo.js new file mode 100644 index 000000000..a7949cda4 --- /dev/null +++ b/apps/clkinfocal/clkinfo.js @@ -0,0 +1,32 @@ +(function() { + require("Font4x8Numeric").add(Graphics); + return { + name: "Bangle", + items: [ + { name : "Date", + get : () => { + let d = new Date(); + let g = Graphics.createArrayBuffer(24,24,1,{msb:true}); + g.drawImage(atob("FhgBDADAMAMP/////////////////////8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAP///////"),1,0); + g.setFont("6x15").setFontAlign(0,0).drawString(d.getDate(),11,17); + return { + text : require("locale").dow(d,1).toUpperCase(), + img : g.asImage("string") + }; + }, + show : function() { + this.interval = setTimeout(()=>{ + this.emit("redraw"); + this.interval = setInterval(()=>{ + this.emit("redraw"); + }, 86400000); + }, 86400000 - (Date.now() % 86400000)); + }, + hide : function() { + clearInterval(this.interval); + this.interval = undefined; + } + } + ] + }; +}) diff --git a/apps/clkinfocal/metadata.json b/apps/clkinfocal/metadata.json new file mode 100644 index 000000000..6d6dd63fc --- /dev/null +++ b/apps/clkinfocal/metadata.json @@ -0,0 +1,12 @@ +{ "id": "clkinfocal", + "name": "Calendar Clockinfo", + "version":"0.01", + "description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays the day of the month in the icon, and the weekday", + "icon": "app.png", + "type": "clkinfo", + "tags": "clkinfo,calendar", + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"clkinfocal.clkinfo.js","url":"clkinfo.js"} + ] +} diff --git a/apps/clkinfosunrise/ChangeLog b/apps/clkinfosunrise/ChangeLog new file mode 100644 index 000000000..86e7a7fa8 --- /dev/null +++ b/apps/clkinfosunrise/ChangeLog @@ -0,0 +1,4 @@ +0.01: New App! +0.02: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps + Add a 'time' clockinfo that also displays a percentage of day left +0.03: Change 3rd mode to show the time to next sunrise/sunset time (not actual time) diff --git a/apps/clkinfosunrise/app.png b/apps/clkinfosunrise/app.png new file mode 100644 index 000000000..a1d53946d Binary files /dev/null and b/apps/clkinfosunrise/app.png differ diff --git a/apps/clkinfosunrise/clkinfo.js b/apps/clkinfosunrise/clkinfo.js new file mode 100644 index 000000000..22c507f34 --- /dev/null +++ b/apps/clkinfosunrise/clkinfo.js @@ -0,0 +1,76 @@ +(function() { + // get today's sunlight times for lat/lon + var sunrise, sunset, date; + var SunCalc = require("suncalc"); // from modules folder + const locale = require("locale"); + + function calculate() { + var location = require("Storage").readJSON("mylocation.json",1)||{}; + location.lat = location.lat||51.5072; + location.lon = location.lon||0.1276; // London + date = new Date(Date.now()); + var times = SunCalc.getTimes(date, location.lat, location.lon); + sunrise = times.sunrise; + sunset = times.sunset; + /* do we want to re-calculate this every day? Or we just assume + that 'show' will get called once a day? */ + } + + function show() { + this.interval = setTimeout(()=>{ + this.emit("redraw"); + this.interval = setInterval(()=>{ + this.emit("redraw"); + }, 60000); + }, 60000 - (Date.now() % 60000)); + } + function hide() { + clearInterval(this.interval); + this.interval = undefined; + } + + return { + name: "Bangle", + items: [ + { name : "Sunrise", + get : () => { calculate(); + return { text : locale.time(sunrise,1), + img : atob("GBiBAAAAAAAAAAAAAAAYAAA8AAB+AAD/AAAAAAAAAAAAAAAYAAAYAAQYIA4AcAYAYAA8AAB+AAD/AAH/gD///D///AAAAAAAAAAAAA==") }}, + show : show, hide : hide + }, { name : "Sunset", + get : () => { calculate(); + return { text : locale.time(sunset,1), + img : atob("GBiBAAAAAAAAAAAAAAB+AAA8AAAYAAAYAAAAAAAAAAAAAAAYAAAYAAQYIA4AcAYAYAA8AAB+AAD/AAH/gD///D///AAAAAAAAAAAAA==") }}, + show : show, hide : hide + }, { name : "Sunrise/set", // Time in day (uses v/min/max to show percentage through day) + hasRange : true, + get : () => { + calculate(); + let day = true; + let d = date.getTime(); + let dayLength = sunset.getTime()-sunrise.getTime(); + let timeUntil, timeTotal; + if (d < sunrise.getTime()) { + day = false; // early morning + timePast = sunrise.getTime()-d; + timeTotal = 86400000-dayLength; + } else if (d > sunset.getTime()) { + day = false; // evening + timePast = d-sunset.getTime(); + timeTotal = 86400000-dayLength; + } else { // day! + timePast = d-sunrise.getTime(); + timeTotal = dayLength; + } + let v = Math.round(100 * timePast / timeTotal); + let minutesTo = (timeTotal-timePast)/60000; + return { text : (minutesTo>90) ? (Math.round(minutesTo/60)+"h") : (Math.round(minutesTo)+"m"), + v : v, min : 0, max : 100, + img : day ? atob("GBiBAAAYAAAYAAAYAAgAEBwAOAx+MAD/AAH/gAP/wAf/4Af/4Of/5+f/5wf/4Af/4AP/wAH/gAD/AAx+MBwAOAgAEAAYAAAYAAAYAA==") : atob("GBiBAAfwAA/8AAP/AAH/gAD/wAB/wAB/4AA/8AA/8AA/8AAf8AAf8AAf8AAf8AA/8AA/8AA/4AB/4AB/wAD/wAH/gAf/AA/8AAfwAA==") + } + }, + show : show, hide : hide + } + ] + }; +}) diff --git a/apps/clkinfosunrise/metadata.json b/apps/clkinfosunrise/metadata.json new file mode 100644 index 000000000..d130c6453 --- /dev/null +++ b/apps/clkinfosunrise/metadata.json @@ -0,0 +1,12 @@ +{ "id": "clkinfosunrise", + "name": "Sunrise Clockinfo", + "version":"0.03", + "description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays sunrise and sunset based on the location from the 'My Location' app", + "icon": "app.png", + "type": "clkinfo", + "tags": "clkinfo,sunrise", + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"sunrise.clkinfo.js","url":"clkinfo.js"} + ] +} diff --git a/apps/clockcal/ChangeLog b/apps/clockcal/ChangeLog new file mode 100644 index 000000000..27d4fc7f4 --- /dev/null +++ b/apps/clockcal/ChangeLog @@ -0,0 +1,6 @@ +0.01: Initial upload +0.02: Added scrollable calendar and swipe gestures +0.03: Configurable drag gestures +0.04: Use default Bangle formatter for booleans +0.05: Improved colors (connected vs disconnected) +0.06: Tell clock widgets to hide. diff --git a/apps/clockcal/README.md b/apps/clockcal/README.md new file mode 100644 index 000000000..d30205be0 --- /dev/null +++ b/apps/clockcal/README.md @@ -0,0 +1,32 @@ +# Clock & Calendar by Michael + +This is my "Hello World". I first made this watchface almost 10 years ago for my original Pebble and Pebble Time and I missed this so much, that I had to write it for the BangleJS2. +I know that it seems redundant because there already **is** a *time&cal*-app, but it didn't fit my style. + +|Screenshot|description| +|:--:|:-| +|![locked screen](screenshot.png)|locked: triggers only one minimal update/min| +|![unlocked screen](screenshot2.png)|unlocked: smaller clock, but with seconds| +|![big calendar](screenshot3.png)|swipe up for big calendar, (up down to scroll, left/right to exit)| + +## Configurable Features +- Number of calendar rows (weeks) +- Buzz on connect/disconnect (I know, this should be an extra widget, but for now, it is included) +- Clock Mode (24h/12h). (No am/pm indicator) +- First day of the week +- Red Saturday/Sunday +- Swipe/Drag gestures to launch features or apps. + +## Auto detects your message/music apps: +- swiping down will search your files for an app with the string "message" in its filename and launch it. (configurable) +- swiping right will search your files for an app with the string "music" in its filename and launch it. (configurable) + +## Feedback +The clock works for me in a 24h/MondayFirst/WeekendFree environment but is not well-tested with other settings. +So if something isn't working, please tell me: https://github.com/foostuff/BangleApps/issues + +## Planned features: + - Internal lightweight music control, because switching apps has a loading time. + - Clean up settings + - Maybe am/pm indicator for 12h-users + - Step count (optional) diff --git a/apps/clockcal/app-icon.js b/apps/clockcal/app-icon.js new file mode 100644 index 000000000..5bab7853e --- /dev/null +++ b/apps/clockcal/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkECqMCkQACiEDkIXQuUnkUBkESiYXPgN/u8jgEx/8vC6E3k9xiH//8/C6BHCPQMSL6EDO4cgaf4A/ACEC+YFDl4FEAAM/+ISHbIIECh4FB+QWEA4PwCQsfC4gVBkYGDgP/mQ4CCQk/iAXEAQTiCgMiDQQSFiATDBgQXCgILBEQkQBwYrEC4sPLQRpCBwoXECgUCC4oSBAggXHNQRfDV4X/JgQXJBIIXFgYuDC5QKBiE/C4f/bwgXJmanGJgoSDiTQBmQMBE4JYBfwJ5BBYMiYQISEB4IAB+KdCAgfwAwTrCn4SDiczAAMwGwMTmR0CmECBgRSBCQwA/AGsBgEQAgYABAwcHu93s4GBqAXEmLrCiYICmICBj4XEgvABIMMqECiIXCgQXCegLYBC4NwF4VcAQNV4EPkEhF4REBgYXCiQvCu4UCAQMFJYRfKgxGBuxfGLgkjFgMCkMBmEjgEigZaBI4XFMYcRC4kBmRhBkMQgI5DF4MFgAXCLARfCFoIvDkZmBhnF4sA5gvDYghfEHIQJDAAhQBIAPwVQMTgQvCNIMhAwJfBR4MMU4JRB+RJBiUQgUDVwMgYwMBgcwX4amBqBQBiTqBgUQh8RmJhCL4IvC4HMR4ZaEAgIBBL4LBDL5EBmI5BkQvBXwIGBmMPMwMvkEFR4VcR4UgU4MSC4UQmIJBn7dBiQNBqoXBPYNQh8Q+MB+MvgEvG4JyBj8A+RkBhlQd4ZHBiBYCL4bBELxEAA==")) \ No newline at end of file diff --git a/apps/clockcal/app.js b/apps/clockcal/app.js new file mode 100644 index 000000000..58ddd7ef5 --- /dev/null +++ b/apps/clockcal/app.js @@ -0,0 +1,311 @@ +Bangle.setUI("clock"); +Bangle.loadWidgets(); + +var s = Object.assign({ + CAL_ROWS: 4, //number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets. + BUZZ_ON_BT: true, //2x slow buzz on disconnect, 2x fast buzz on connect. Will be extra widget eventually + MODE24: true, //24h mode vs 12h mode + FIRSTDAY: 6, //First day of the week: mo, tu, we, th, fr, sa, su + REDSUN: true, // Use red color for sunday? + REDSAT: true, // Use red color for saturday? + DRAGDOWN: "[AI:messg]", + DRAGRIGHT: "[AI:music]", + DRAGLEFT: "[ignore]", + DRAGUP: "[calend.]" +}, require('Storage').readJSON("clockcal.json", true) || {}); + +const h = g.getHeight(); +const w = g.getWidth(); +const CELL_W = w / 7; +const CELL2_W = w / 8;//full calendar +const CELL_H = 15; +const CAL_Y = h - s.CAL_ROWS * CELL_H; +const DEBUG = false; +var state = "watch"; +var monthOffset = 0; + +/* + * Calendar features + */ +function drawFullCalendar(monthOffset) { + addMonths = function (_d, _am) { + var ay = 0, m = _d.getMonth(), y = _d.getFullYear(); + while ((m + _am) > 11) { ay++; _am -= 12; } + while ((m + _am) < 0) { ay--; _am += 12; } + n = new Date(_d.getTime()); + n.setMonth(m + _am); + n.setFullYear(y + ay); + return n; + }; + monthOffset = (typeof monthOffset == "undefined") ? 0 : monthOffset; + state = "calendar"; + var start = Date().getTime(); + const months = ['Jan.', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec.']; + const monthclr = ['#0f0', '#f0f', '#00f', '#ff0', '#0ff', '#fff']; + if (typeof dayInterval !== "undefined") clearTimeout(dayInterval); + if (typeof secondInterval !== "undefined") clearTimeout(secondInterval); + if (typeof minuteInterval !== "undefined") clearTimeout(minuteInterval); + d = addMonths(Date(), monthOffset); + tdy = Date().getDate() + "." + Date().getMonth(); + newmonth = false; + c_y = 0; + g.reset(); + g.setBgColor(0); + g.clear(); + var prevmonth = addMonths(d, -1); + const today = prevmonth.getDate(); + var rD = new Date(prevmonth.getTime()); + rD.setDate(rD.getDate() - (today - 1)); + const dow = (s.FIRSTDAY + rD.getDay()) % 7; + rD.setDate(rD.getDate() - dow); + var rDate = rD.getDate(); + bottomrightY = c_y - 3; + clrsun = s.REDSUN ? '#f00' : '#fff'; + clrsat = s.REDSUN ? '#f00' : '#fff'; + var fg = [clrsun, '#fff', '#fff', '#fff', '#fff', '#fff', clrsat]; + for (var y = 1; y <= 11; y++) { + bottomrightY += CELL_H; + bottomrightX = -2; + for (var x = 1; x <= 7; x++) { + bottomrightX += CELL2_W; + rMonth = rD.getMonth(); + rDate = rD.getDate(); + if (tdy == rDate + "." + rMonth) { + caldrawToday(rDate); + } else if (rDate == 1) { + caldrawFirst(rDate); + } else { + caldrawNormal(rDate, fg[rD.getDay()]); + } + if (newmonth && x == 7) { + caldrawMonth(rDate, monthclr[rMonth % 6], months[rMonth], rD); + } + rD.setDate(rDate + 1); + } + } + delete addMonths; + if (DEBUG) console.log("Calendar performance (ms):" + (Date().getTime() - start)); +} +function caldrawMonth(rDate, c, m, rD) { + g.setColor(c); + g.setFont("Vector", 18); + g.setFontAlign(-1, 1, 1); + drawyear = ((rMonth % 11) == 0) ? String(rD.getFullYear()).substr(-2) : ""; + g.drawString(m + drawyear, bottomrightX, bottomrightY - CELL_H, 1); + newmonth = false; +} +function caldrawToday(rDate) { + g.setFont("Vector", 16); + g.setFontAlign(1, 1); + g.setColor('#0f0'); + g.fillRect(bottomrightX - CELL2_W + 1, bottomrightY - CELL_H - 1, bottomrightX, bottomrightY - 2); + g.setColor('#000'); + g.drawString(rDate, bottomrightX, bottomrightY); +} +function caldrawFirst(rDate) { + g.flip(); + g.setFont("Vector", 16); + g.setFontAlign(1, 1); + bottomrightY += 3; + newmonth = true; + g.setColor('#0ff'); + g.fillRect(bottomrightX - CELL2_W + 1, bottomrightY - CELL_H - 1, bottomrightX, bottomrightY - 2); + g.setColor('#000'); + g.drawString(rDate, bottomrightX, bottomrightY); +} +function caldrawNormal(rDate, c) { + g.setFont("Vector", 16); + g.setFontAlign(1, 1); + g.setColor(c); + g.drawString(rDate, bottomrightX, bottomrightY);//100 +} +function drawMinutes() { + if (DEBUG) console.log("|-->minutes"); + var d = new Date(); + var hours = s.MODE24 ? d.getHours().toString().padStart(2, ' ') : ((d.getHours() + 24) % 12 || 12).toString().padStart(2, ' '); + var minutes = d.getMinutes().toString().padStart(2, '0'); + var textColor = NRF.getSecurityStatus().connected ? '#99f' : '#fff'; + var size = 50; + var clock_x = (w - 20) / 2; + if (dimSeconds) { + size = 65; + clock_x = 4 + (w / 2); + } + g.setBgColor(0); + g.setColor(textColor); + g.setFont("Vector", size); + g.setFontAlign(0, 1); + g.drawString(hours + ":" + minutes, clock_x, CAL_Y - 10, 1); + var nextminute = (61 - d.getSeconds()); + if (typeof minuteInterval !== "undefined") clearTimeout(minuteInterval); + minuteInterval = setTimeout(drawMinutes, nextminute * 1000); +} + +function drawSeconds() { + if (DEBUG) console.log("|--->seconds"); + var d = new Date(); + g.setColor(); + g.fillRect(w - 31, CAL_Y - 36, w - 3, CAL_Y - 19); + g.setBgColor(0); + g.setColor('#fff'); + g.setFont("Vector", 24); + g.setFontAlign(1, 1); + g.drawString(" " + d.getSeconds().toString().padStart(2, '0'), w, CAL_Y - 13); + if (typeof secondInterval !== "undefined") clearTimeout(secondInterval); + if (!dimSeconds) secondInterval = setTimeout(drawSeconds, 1000); +} + +function drawWatch() { + if (DEBUG) console.log("CALENDAR"); + monthOffset = 0; + state = "watch"; + var d = new Date(); + g.reset(); + g.setBgColor(0); + g.clear(); + drawMinutes(); + if (!dimSeconds) drawSeconds(); + const dow = (s.FIRSTDAY + d.getDay()) % 7; //MO=0, SU=6 + const today = d.getDate(); + var rD = new Date(d.getTime()); + rD.setDate(rD.getDate() - dow); + var rDate = rD.getDate(); + g.setFontAlign(1, 1); + for (var y = 1; y <= s.CAL_ROWS; y++) { + for (var x = 1; x <= 7; x++) { + bottomrightX = x * CELL_W - 2; + bottomrightY = y * CELL_H + CAL_Y; + g.setFont("Vector", 16); + var fg = ((s.REDSUN && rD.getDay() == 0) || (s.REDSAT && rD.getDay() == 6)) ? '#f00' : '#fff'; + if (y == 1 && today == rDate) { + g.setColor('#0f0'); + g.fillRect(bottomrightX - CELL_W + 1, bottomrightY - CELL_H - 1, bottomrightX, bottomrightY - 2); + g.setColor('#000'); + g.drawString(rDate, bottomrightX, bottomrightY); + } + else { + g.setColor(fg); + g.drawString(rDate, bottomrightX, bottomrightY); + } + rD.setDate(rDate + 1); + rDate = rD.getDate(); + } + } + Bangle.drawWidgets(); + + var nextday = (3600 * 24) - (d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds() + 1); + if (DEBUG) console.log("Next Day:" + (nextday / 3600)); + if (typeof dayInterval !== "undefined") clearTimeout(dayInterval); + dayInterval = setTimeout(drawWatch, nextday * 1000); +} + +function BTevent() { + drawMinutes(); + if (s.BUZZ_ON_BT) { + var interval = (NRF.getSecurityStatus().connected) ? 100 : 500; + Bangle.buzz(interval); + setTimeout(function () { Bangle.buzz(interval); }, interval * 3); + } +} +function action(a) { + g.reset(); + if (typeof secondInterval !== "undefined") clearTimeout(secondInterval); + if (DEBUG) console.log("action:" + a); + switch (a) { + case "[ignore]": + break; + case "[calend.]": + drawFullCalendar(); + break; + case "[AI:music]": + l = require("Storage").list(RegExp("music.*app.js")); + if (l.length > 0) { + load(l[0]); + } else E.showAlert("Music app not found", "Not found").then(drawWatch); + break; + case "[AI:messg]": + l = require("Storage").list(RegExp("message.*app.js")); + if (l.length > 0) { + load(l[0]); + } else E.showAlert("Message app not found", "Not found").then(drawWatch); + break; + default: + l = require("Storage").list(RegExp(a + ".app.js")); + if (l.length > 0) { + load(l[0]); + } else E.showAlert(a + ": App not found", "Not found").then(drawWatch); + break; + } +} +function input(dir) { + Bangle.buzz(100, 1); + if (DEBUG) console.log("swipe:" + dir); + switch (dir) { + case "r": + if (state == "calendar") { + drawWatch(); + } else { + action(s.DRAGRIGHT); + } + break; + case "l": + if (state == "calendar") { + drawWatch(); + } else { + action(s.DRAGLEFT); + } + break; + case "d": + if (state == "calendar") { + monthOffset--; + drawFullCalendar(monthOffset); + } else { + action(s.DRAGDOWN); + } + break; + case "u": + if (state == "calendar") { + monthOffset++; + drawFullCalendar(monthOffset); + } else { + action(s.DRAGUP); + } + break; + default: + if (state == "calendar") { + drawWatch(); + } + break; + + } +} + +let drag; +Bangle.on("drag", e => { + if (!drag) { + drag = { x: e.x, y: e.y }; + } else if (!e.b) { + const dx = e.x - drag.x, dy = e.y - drag.y; + var dir = "t"; + if (Math.abs(dx) > Math.abs(dy) + 20) { + dir = (dx > 0) ? "r" : "l"; + } else if (Math.abs(dy) > Math.abs(dx) + 20) { + dir = (dy > 0) ? "d" : "u"; + } + drag = null; + input(dir); + } +}); + +//register events +Bangle.on('lock', locked => { + if (typeof secondInterval !== "undefined") clearTimeout(secondInterval); + dimSeconds = locked; //dim seconds if lock=on + drawWatch(); +}); +NRF.on('connect', BTevent); +NRF.on('disconnect', BTevent); + +dimSeconds = Bangle.isLocked(); +drawWatch(); + diff --git a/apps/clockcal/app.png b/apps/clockcal/app.png new file mode 100644 index 000000000..2e2e4461e Binary files /dev/null and b/apps/clockcal/app.png differ diff --git a/apps/clockcal/metadata.json b/apps/clockcal/metadata.json new file mode 100644 index 000000000..872211495 --- /dev/null +++ b/apps/clockcal/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "clockcal", + "name": "Clock & Calendar", + "version": "0.06", + "description": "Clock with Calendar", + "readme":"README.md", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"},{"url":"screenshot2.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"clockcal.app.js","url":"app.js"}, + {"name":"clockcal.settings.js","url":"settings.js"}, + {"name":"clockcal.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"clockcal.json"}] +} diff --git a/apps/clockcal/screenshot.png b/apps/clockcal/screenshot.png new file mode 100644 index 000000000..fcfde0c4a Binary files /dev/null and b/apps/clockcal/screenshot.png differ diff --git a/apps/clockcal/screenshot2.png b/apps/clockcal/screenshot2.png new file mode 100644 index 000000000..98acfa9a0 Binary files /dev/null and b/apps/clockcal/screenshot2.png differ diff --git a/apps/clockcal/screenshot3.png b/apps/clockcal/screenshot3.png new file mode 100644 index 000000000..ab34f4306 Binary files /dev/null and b/apps/clockcal/screenshot3.png differ diff --git a/apps/clockcal/settings.js b/apps/clockcal/settings.js new file mode 100644 index 000000000..d4cc4df68 --- /dev/null +++ b/apps/clockcal/settings.js @@ -0,0 +1,124 @@ +(function (back) { + var FILE = "clockcal.json"; + defaults={ + CAL_ROWS: 4, //number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets. + BUZZ_ON_BT: true, //2x slow buzz on disconnect, 2x fast buzz on connect. Will be extra widget eventually + MODE24: true, //24h mode vs 12h mode + FIRSTDAY: 6, //First day of the week: mo, tu, we, th, fr, sa, su + REDSUN: true, // Use red color for sunday? + REDSAT: true, // Use red color for saturday? + DRAGDOWN: "[AI:messg]", + DRAGRIGHT: "[AI:music]", + DRAGLEFT: "[ignore]", + DRAGUP: "[calend.]" + }; + settings = Object.assign(defaults, require('Storage').readJSON(FILE, true) || {}); + + actions = ["[ignore]","[calend.]","[AI:music]","[AI:messg]"]; + require("Storage").list(RegExp(".app.js")).forEach(element => actions.push(element.replace(".app.js",""))); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + menu = { + "": { "title": "Clock & Calendar" }, + "< Back": () => back(), + 'Buzz(dis)conn.?': { + value: settings.BUZZ_ON_BT, + onchange: v => { + settings.BUZZ_ON_BT = v; + writeSettings(); + } + }, + '#Calendar Rows': { + value: settings.CAL_ROWS, + min: 0, max: 6, + onchange: v => { + settings.CAL_ROWS = v; + writeSettings(); + } + }, + 'Clock mode': { + value: settings.MODE24, + format: v => v ? "24h" : "12h", + onchange: v => { + settings.MODE24 = v; + writeSettings(); + } + }, + 'First Day': { + value: settings.FIRSTDAY, + min: 0, max: 6, + format: v => ["Sun", "Sat", "Fri", "Thu", "Wed", "Tue", "Mon"][v], + onchange: v => { + settings.FIRSTDAY = v; + writeSettings(); + } + }, + 'Red Saturday?': { + value: settings.REDSAT, + onchange: v => { + settings.REDSAT = v; + writeSettings(); + } + }, + 'Red Sunday?': { + value: settings.REDSUN, + onchange: v => { + settings.REDSUN = v; + writeSettings(); + } + }, + 'Drag Up ': { + min:0, max:actions.length-1, + value: actions.indexOf(settings.DRAGUP), + format: v => actions[v], + onchange: v => { + settings.DRAGUP = actions[v]; + writeSettings(); + } + }, + 'Drag Right': { + min:0, max:actions.length-1, + value: actions.indexOf(settings.DRAGRIGHT), + format: v => actions[v], + onchange: v => { + settings.DRAGRIGHT = actions[v]; + writeSettings(); + } + }, + 'Drag Down': { + min:0, max:actions.length-1, + value: actions.indexOf(settings.DRAGDOWN), + format: v => actions[v], + onchange: v => { + settings.DRGDOWN = actions[v]; + writeSettings(); + } + }, + 'Drag Left': { + min:0, max:actions.length-1, + value: actions.indexOf(settings.DRAGLEFT), + format: v => actions[v], + onchange: v => { + settings.DRAGLEFT = actions[v]; + writeSettings(); + } + }, + 'Load deafauls?': { + value: 0, + min: 0, max: 1, + format: v => ["No", "Yes"][v], + onchange: v => { + if (v == 1) { + settings = defaults; + writeSettings(); + load(); + } + } + }, + }; + // Show the menu + E.showMenu(menu); +}); diff --git a/apps/cogclock/15x32.png b/apps/cogclock/15x32.png new file mode 100644 index 000000000..0af326e71 Binary files /dev/null and b/apps/cogclock/15x32.png differ diff --git a/apps/cogclock/ChangeLog b/apps/cogclock/ChangeLog new file mode 100644 index 000000000..f4bfe77a5 --- /dev/null +++ b/apps/cogclock/ChangeLog @@ -0,0 +1,3 @@ +0.01: New clock +0.02: Use ClockFace library, add settings +0.03: Use ClockFace_menu.addSettingsFile diff --git a/apps/cogclock/app.js b/apps/cogclock/app.js new file mode 100644 index 000000000..d24031684 --- /dev/null +++ b/apps/cogclock/app.js @@ -0,0 +1,111 @@ +Graphics.prototype.setFont15x32N = function() { + this.setFontCustom(atob( + // 15x32.png, converted using http://ebfc.mattbrailsford.com/ + "/////oAAAAKAAAACgAAAAoAAAAKAAAACgf//AoEAAQKB//8CgAAAAoAAAAKAAAACgAAAAoAAAAL////+/wAB/oEAAQKBAAECgf//AoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAAC////AgAAAQIAAAH+/w///oEIAAKBCAACgQgAAoEIAAKBCAACgQg/AoEIIQKB+CECgAAhAoAAIQKAACECgAAhAoAAIQL//+H+/w/h/oEIIQKBCCECgQghAoEIIQKBCCECgQghAoEIIQKB+D8CgAAAAoAAAAKAAAACgAAAAoAAAAL////+///gAIAAIACAACAAgAAgAIAAIAD/+CAAAAggAAAIIAAACD/+//gAAoAAAAKAAAACgAAAAoAAAAL////+///h/oAAIQKAACECgAAhAoAAIQKAACECgfghAoEIIQKBCD8CgQgAAoEIAAKBCAACgQgAAoEIAAL/D//+/////oAAAAKAAAACgAAAAoAAAAKAAAACgfg/AoEIIQKBCD8CgQgAAoEIAAKBCAACgQgAAoEIAAL/D//+/wAAAIEAAACBAAAAgQAAAIEAAACBAAAAgQAAAIH///6AAAACgAAAAoAAAAKAAAACgAAAAoAAAAL////+/////oAAAAKAAAACgAAAAoAAAAKAAAACgfg/AoEIIQKB+D8CgAAAAoAAAAKAAAACgAAAAoAAAAL////+///h/oAAIQKAACECgAAhAoAAIQKAACECgfghAoEIIQKB+D8CgAAAAoAAAAKAAAACgAAAAoAAAAL////+" + ), "0".charCodeAt(0), 15, 32); +}; + +/** + * Add coordinates for nth tooth to vertices + * @param {array} poly Array to add points to + * @param {number} n Tooth number + */ +function addTooth(poly, n) { + const + tau = Math.PI*2, arc = tau/clock.teeth, + e = arc*clock.edge, p = arc*clock.point, s = (arc-(e+p))/2; // edge,point,slopes + const sin = Math.sin, cos = Math.cos, + x = clock.x, y = clock.y, + r2 = clock.r2, r3 = clock.r3; + let r = (n-1)*arc+e/2; // rads + poly.push(x+r2*sin(r), y-r2*cos(r)); + r += s; + poly.push(x+r3*sin(r), y-r3*cos(r)); + r += p; + poly.push(x+r3*sin(r), y-r3*cos(r)); + r += s; + poly.push(x+r2*sin(r), y-r2*cos(r)); +} + +/** + * @param {number} n Tooth number to fill (1-based) + * @param col Fill color + */ +function fillTooth(n, col) { + if (!n) return; // easiest to check here + let poly = []; + addTooth(poly, n); + g.setColor(col).fillPoly(poly) + .setColor(g.theme.fg2).drawPoly(poly); // fillPoly colored over the outline +} + +const ClockFace = require("ClockFace"); +const clock = new ClockFace({ + precision: 1, + settingsFile: "cogclock.settings.json", + init: function() { + this.r1 = 84; // inner radius + this.r3 = Math.min(Bangle.appRect.w/2, Bangle.appRect.h/2); // outer radius + this.r2 = (this.r1*3+this.r3*2)/5; + this.teeth = 12; + this.edge = 0.45; + this.point = 0.35; // as fraction of arc + this.x = Bangle.appRect.x+Bangle.appRect.w/2; + this.y = Bangle.appRect.y+Bangle.appRect.h/2; + }, + draw: function(d) { + const x = this.x, y = this.y; + g.setColor(g.theme.bg2).fillCircle(x, y, this.r2) // fill cog + .setColor(g.theme.bg).fillCircle(x, y, this.r1) // clear center + .setColor(g.theme.fg2).drawCircle(x, y, this.r1); // draw inner border + let poly = []; // complete teeth outline + for(let t = 1; t<=this.teeth; t++) { + fillTooth(t, g.theme.bg2); + addTooth(poly, t); + } + g.drawPoly(poly, true); // draw outer border + if (!this.showDate) { + // draw top/bottom rectangles (instead of year/date) + g.reset() + .fillRect(x-30, y-60, x+29, y-33).clearRect(x-28, y-58, x+27, y-33) + .fillRect(x-30, y+60, x+29, y+30).clearRect(x-28, y+58, x+27, y+30); + } + this.tooth = 0; + this.update(d, {s: 1, m: 1, h: 1, d: 1}); + }, + update: function(d, c) { + g.reset(); + const pad2 = num => (num<10 ? "0" : "")+num, + year = d.getFullYear(), + date = pad2(d.getDate())+pad2(d.getMonth()), + time = pad2(d.getHours())+pad2(d.getMinutes()), + tooth = Math.round(d.getSeconds()/60*this.teeth); + const x = this.x, y = this.y; + if (c.m) { + g.setFont("15x32N:2").setFontAlign(0, 0) // center middle + .drawString(time, x, y, true); + } + if (this.showDate) { + if (c.d) { + g.setFont("15x32N").setFontAlign(0, -1) // center top + .drawString(year, x, y+32, true) + .setFont("15x32N").setFontAlign(0, 1) // center bottom + .drawString(date, x, y-32, true); + } + } + + if (tooth!==this.tooth) { + if (tooth>this.tooth) { + for(let t = this.tooth; t<=tooth; t++) { // fill missing teeth + fillTooth(t, g.theme.fg2); + } + } else { + for(let t = this.tooth; t>tooth; t--) { // erase extraneous teeth + fillTooth(t, g.theme.bg2); + } + } + } + this.tooth = tooth; + } +}); +clock.start(); diff --git a/apps/cogclock/icon.js b/apps/cogclock/icon.js new file mode 100644 index 000000000..899cfc7c1 --- /dev/null +++ b/apps/cogclock/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/ACcikQXpCQUCC4MgBAgqMCQIXEAgQXNBwIDCAggXOABAXLHwQAHMYQXmmczI5oiCBwUjCwIABmQXDEgJ0KCwMwCwMDDAgyGLYoWBgAXBgAYBMZIXEkYWBC4YYBGAh7FFwgHCC4YEBPRIwCFwYXFGAaqHC56oIIwgXFJAbUJLwpgHI4qPDIwpIFR4wWDLwa6BAAQHDVIYYCC/gYCC453MPIR3HU5gADd5bXHC4rvJMAYAECwJeCd5MjGAjVDC4ZHGNARIFGAgNDFw5IJUogwFC4gwBDAhGBBghIFBQhhBbYguEPAweCDAgACCwZACNg5LFXQYsIC5QAFdg4XcCxJHNBwYTEC6A+BJYQEEC5YYBMYhbCCxo0GCaIXbAHgA=")) \ No newline at end of file diff --git a/apps/cogclock/icon.png b/apps/cogclock/icon.png new file mode 100644 index 000000000..8520fcf5d Binary files /dev/null and b/apps/cogclock/icon.png differ diff --git a/apps/cogclock/metadata.json b/apps/cogclock/metadata.json new file mode 100644 index 000000000..29000b589 --- /dev/null +++ b/apps/cogclock/metadata.json @@ -0,0 +1,20 @@ +{ + "id": "cogclock", + "name": "Cog Clock", + "version": "0.03", + "description": "A cross-shaped clock inside a cog", + "icon": "icon.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "allow_emulator": true, + "storage": [ + {"name":"cogclock.app.js","url":"app.js"}, + {"name":"cogclock.settings.js","url":"settings.js"}, + {"name":"cogclock.img","url":"icon.js","evaluate":true} + ], + "data": [ + {"name": "cogclock.settings.json"} + ] +} diff --git a/apps/cogclock/screenshot.png b/apps/cogclock/screenshot.png new file mode 100644 index 000000000..f49709aef Binary files /dev/null and b/apps/cogclock/screenshot.png differ diff --git a/apps/cogclock/settings.js b/apps/cogclock/settings.js new file mode 100644 index 000000000..a91b033d0 --- /dev/null +++ b/apps/cogclock/settings.js @@ -0,0 +1,10 @@ +(function(back) { + let menu = { + "": {"title": /*LANG*/"Cog Clock"}, + /*LANG*/"< Back": back, + }; + require("ClockFace_menu").addSettingsFile(menu, "cogclock.settings.json", [ + "showDate", "loadWidgets" + ]); + E.showMenu(menu); +}); diff --git a/apps/color_catalog/Changelog b/apps/color_catalog/ChangeLog similarity index 100% rename from apps/color_catalog/Changelog rename to apps/color_catalog/ChangeLog diff --git a/apps/colorful_clock/ChangeLog b/apps/colorful_clock/ChangeLog new file mode 100644 index 000000000..54ee389e3 --- /dev/null +++ b/apps/colorful_clock/ChangeLog @@ -0,0 +1,3 @@ +... +0.03: First update with ChangeLog Added +0.04: Tell clock widgets to hide. diff --git a/apps/colorful_clock/app.js b/apps/colorful_clock/app.js index afc6b321f..ba6272e9b 100644 --- a/apps/colorful_clock/app.js +++ b/apps/colorful_clock/app.js @@ -3,6 +3,8 @@ let outerRadius = Math.min(CenterX,CenterY) * 0.9; + Bangle.setUI('clock'); + Bangle.loadWidgets(); /**** updateClockFaceSize ****/ @@ -241,7 +243,3 @@ refreshDisplay(); } }); - - Bangle.loadWidgets(); - - Bangle.setUI('clock'); diff --git a/apps/colorful_clock/metadata.json b/apps/colorful_clock/metadata.json index 5b6dbe87e..237acf81c 100644 --- a/apps/colorful_clock/metadata.json +++ b/apps/colorful_clock/metadata.json @@ -1,7 +1,7 @@ { "id": "colorful_clock", "name": "Colorful Analog Clock", "shortName":"Colorful Clock", - "version":"0.03", + "version":"0.04", "description": "a colorful analog clock", "icon": "app-icon.png", "type": "clock", diff --git a/apps/compass/ChangeLog b/apps/compass/ChangeLog index 4bb7838ac..cb1c6d463 100644 --- a/apps/compass/ChangeLog +++ b/apps/compass/ChangeLog @@ -3,3 +3,6 @@ 0.03: Eliminate flickering 0.04: Fix for Bangle.js 2 and themes 0.05: Fix bearing not clearing correctly (visible in single or double digit bearings) +0.06: Add button for force compass calibration +0.07: Use 360-heading to output the correct heading value (fix #1866) +0.08: Added adjustment for Bangle.js magnetometer heading fix diff --git a/apps/compass/README.md b/apps/compass/README.md new file mode 100644 index 000000000..d60192f73 --- /dev/null +++ b/apps/compass/README.md @@ -0,0 +1,29 @@ +# Compass + +This app uses Bangle.js's built-in magnetometer as a compass. + +## Usage + +Hold your Bangle.js **face up** (so the display is parallel to the ground), +and the red arrow will point north, with the heading in degrees printed at +the top of the screen. + +This compass app does not include tilt compensation - so much like a real +compass you should always keep it face up when taking a reading. + +The first time you run the compass after your Bangle has booted (or if +you move to an area with a substantially different magnetic field) you will +need to recalibrate your compass (even if a heading is shown). + +## Calibration + +Press the button next to the `RESET` label on the screen. The North/South marker +will disappear and a message will appear asking you to rotate the watch 360 degrees. + +* Hold the watch face up, so the display is parallel to the ground +* Rotate it around slowly, all 360 degrees (with the display still parallel to the ground) +* The `Uncalibrated` message will disappear before you have finished rotating the full 360 degrees - but you should still complete the full rotation in order for the compass to work properly. + +Once you've rotated the full 360 degrees your compass should now work fine, +and calibration is stored between runs of the app. However if you go near +to a strong magnet you may still need to recalibrate. diff --git a/apps/compass/compass.js b/apps/compass/compass.js index 65ad83c4f..9a7aec2fc 100644 --- a/apps/compass/compass.js +++ b/apps/compass/compass.js @@ -20,7 +20,7 @@ ag.setColor(1).fillCircle(AGM,AGM,AGM-1,AGM-1); ag.setColor(0).fillCircle(AGM,AGM,AGM-11,AGM-11); function arrow(r,c) { - r=r*Math.PI/180; + r=(360-r)*Math.PI/180; var p = Math.PI/2; ag.setColor(c).fillPoly([ AGM+AGH*Math.sin(r), AGM-AGH*Math.cos(r), @@ -38,7 +38,7 @@ Bangle.on('mag', function(m) { if (!wasUncalibrated) { g.clearRect(0,24,W,48); g.setFontAlign(0,-1).setFont("6x8"); - g.drawString("Uncalibrated\nturn 360° around",M,24+4); + g.drawString(/*LANG*/"Uncalibrated\nturn 360° around",M,24+4); wasUncalibrated = true; } } else { @@ -64,7 +64,12 @@ Bangle.on('mag', function(m) { oldHeading = m.heading; }); -g.clear(); +g.clear(1); +g.setFont("6x8").setFontAlign(0,0,3).drawString(/*LANG*/"RESET", g.getWidth()-5, g.getHeight()/2); +setWatch(function() { + Bangle.resetCompass(); +}, (process.env.HWVERSION==2) ? BTN1 : BTN2, {repeat:true}); + Bangle.loadWidgets(); Bangle.drawWidgets(); Bangle.setCompassPower(1); diff --git a/apps/compass/metadata.json b/apps/compass/metadata.json index 318d90c86..1a614e1f8 100644 --- a/apps/compass/metadata.json +++ b/apps/compass/metadata.json @@ -1,12 +1,13 @@ { "id": "compass", "name": "Compass", - "version": "0.05", + "version": "0.08", "description": "Simple compass that points North", "icon": "compass.png", "screenshots": [{"url":"screenshot_compass.png"}], "tags": "tool,outdoors", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "storage": [ {"name":"compass.app.js","url":"compass.js"}, {"name":"compass.img","url":"compass-icon.js","evaluate":true} diff --git a/apps/configurable_clock/ChangeLog b/apps/configurable_clock/ChangeLog new file mode 100644 index 000000000..9d55c1a91 --- /dev/null +++ b/apps/configurable_clock/ChangeLog @@ -0,0 +1,3 @@ +... +0.02: First update with ChangeLog Added +0.03: Tell clock widgets to hide. diff --git a/apps/configurable_clock/app.js b/apps/configurable_clock/app.js index 157d57741..45c86c7e9 100644 --- a/apps/configurable_clock/app.js +++ b/apps/configurable_clock/app.js @@ -5,6 +5,7 @@ let ScreenWidth = g.getWidth(), CenterX; let ScreenHeight = g.getHeight(), CenterY, outerRadius; + Bangle.setUI('clock'); Bangle.loadWidgets(); /**** updateClockFaceSize ****/ @@ -1377,4 +1378,3 @@ } }); - Bangle.setUI('clock'); diff --git a/apps/configurable_clock/metadata.json b/apps/configurable_clock/metadata.json index 28feae7e4..687a5b212 100644 --- a/apps/configurable_clock/metadata.json +++ b/apps/configurable_clock/metadata.json @@ -1,7 +1,7 @@ { "id": "configurable_clock", "name": "Configurable Analog Clock", "shortName":"Configurable Clock", - "version":"0.02", + "version":"0.03", "description": "an analog clock with several kinds of faces, hands and colors to choose from", "icon": "app-icon.png", "type": "clock", diff --git a/apps/contourclock/ChangeLog b/apps/contourclock/ChangeLog index 0b6709d24..387340d5b 100644 --- a/apps/contourclock/ChangeLog +++ b/apps/contourclock/ChangeLog @@ -4,3 +4,7 @@ 0.22: Changed timing code, original "Nunito" Font is back! 0.23: Customizer! Unused fonts no longer take up precious memory. 0.24: Added previews to the customizer. +0.25: Fixed a bug that would let widgets change the color of the clock. +0.26: Time formatted to locale +0.27: Fixed the timing code, which sometimes did not update for one minute +0.28: More config options for cleaner look, enabled fast loading diff --git a/apps/contourclock/README.md b/apps/contourclock/README.md new file mode 100644 index 000000000..3341439da --- /dev/null +++ b/apps/contourclock/README.md @@ -0,0 +1,6 @@ +# New Features: +- Fast load! (only works if your launcher uses widgets) +- widgets, date and weekday are individually configurable +- you can hide widgets, date and weekday for a cleaner look when the watch is locked + +Contact me for bug reports or feature requests: ContourClock@gmx.de diff --git a/apps/contourclock/app.js b/apps/contourclock/app.js index a5440845d..8efa406c6 100644 --- a/apps/contourclock/app.js +++ b/apps/contourclock/app.js @@ -1,29 +1,64 @@ -var digits = []; -var drawTimeout; -var fontName=""; -var settings = require('Storage').readJSON("contourclock.json", true) || {}; -if (settings.fontIndex==undefined) { - settings.fontIndex=0; - require('Storage').writeJSON("myapp.json", settings); -} +{ + let digits = []; + let drawTimeout; + let fontName=""; + let settings = require('Storage').readJSON("contourclock.json", true) || {}; + if (settings.fontIndex==undefined) { + settings.fontIndex=0; + settings.widgets=true; + settings.hide=false; + settings.weekday=true; + settings.hideWhenLocked=false; + settings.date=true; require('Storage').writeJSON("myapp.json", settings); + } -function draw() { - var date = new Date(); - // Draw day of the week - g.setFont("Teletext10x18Ascii"); - g.clearRect(0,138,g.getWidth()-1,176); - g.setFontAlign(0,1).drawString(require("locale").dow(date).toUpperCase(),g.getWidth()/2,g.getHeight()-18); - // Draw Date - g.setFontAlign(0,1).drawString(require('locale').date(new Date(),1),g.getWidth()/2,g.getHeight()); - require('contourclock').drawClock(settings.fontIndex); -} + let queueDraw = function() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + queueDraw(); + }, 60000 - (Date.now() % 60000)); + }; -require("FontTeletext10x18Ascii").add(Graphics); -Bangle.setUI("clock"); -g.clear(); -Bangle.loadWidgets(); -Bangle.drawWidgets(); -draw(); -setTimeout(function() { - setInterval(draw,60000); -}, 60000 - Date.now() % 60000); + let draw = function() { + var date = new Date(); + // Draw day of the week + g.reset(); + if ((!settings.hideWhenLocked) || (!Bangle.isLocked())) { + // Draw day of the week + g.setFont("Teletext10x18Ascii"); + g.clearRect(0,138,g.getWidth()-1,176); + if (settings.weekday) g.setFontAlign(0,1).drawString(require("locale").dow(date).toUpperCase(),g.getWidth()/2,g.getHeight()-18); + // Draw Date + if (settings.date) g.setFontAlign(0,1).drawString(require('locale').date(new Date(),1),g.getWidth()/2,g.getHeight()); + } + require('contourclock').drawClock(settings.fontIndex); + }; + + require("FontTeletext10x18Ascii").add(Graphics); + g.clear(); + + draw(); + if (settings.hideWhenLocked) Bangle.on('lock', function (locked) { + if (!locked) require("widget_utils").show(); + else { + g.clear(); + if (settings.hide) require("widget_utils").swipeOn(); + else require("widget_utils").hide(); + } + draw(); + }); + Bangle.setUI({mode:"clock", remove:function() { + if (drawTimeout) clearTimeout(drawTimeout); + if (settings.widgets && settings.hide) require("widget_utils").show(); + g.reset(); + g.clear(); + }}); + if (settings.widgets) { + Bangle.loadWidgets(); + if (settings.hide) require("widget_utils").swipeOn(); + else Bangle.drawWidgets(); + } + queueDraw(); +} diff --git a/apps/contourclock/contourclock.settings.js b/apps/contourclock/contourclock.settings.js index a12538fc5..f2a75d9b5 100644 --- a/apps/contourclock/contourclock.settings.js +++ b/apps/contourclock/contourclock.settings.js @@ -1,43 +1,73 @@ (function(back) { - Bangle.removeAllListeners('drag'); Bangle.setUI(""); var settings = require('Storage').readJSON('contourclock.json', true) || {}; if (settings.fontIndex==undefined) { - settings.fontIndex=0; + settings.fontIndex=0; + settings.widgets=true; + settings.hide=false; + settings.weekday=true; + settings.date=true; + settings.hideWhenLocked=false; require('Storage').writeJSON("myapp.json", settings); } - savedIndex=settings.fontIndex; - saveListener = setWatch(function() { //save changes and return to settings menu - require('Storage').writeJSON('contourclock.json', settings); - Bangle.removeAllListeners('swipe'); - Bangle.removeAllListeners('lock'); - clearWatch(saveListener); - g.clear(); - back(); - }, BTN, { repeat:false, edge:'falling' }); - lockListener = Bangle.on('lock', function () { //discard changes and return to clock - settings.fontIndex=savedIndex; - require('Storage').writeJSON('contourclock.json', settings); - Bangle.removeAllListeners('swipe'); - Bangle.removeAllListeners('lock'); - clearWatch(saveListener); - g.clear(); - load(); - }); - swipeListener = Bangle.on('swipe', function (direction) { - var fontName = require('contourclock').drawClock(settings.fontIndex+direction); - if (fontName) { - settings.fontIndex+=direction; - g.clearRect(0,0,g.getWidth()-1,16); - g.setFont('6x8:2x2').setFontAlign(0,-1).drawString(fontName,g.getWidth()/2,0); - } else { - require('contourclock').drawClock(settings.fontIndex); - } - }); - g.reset(); - g.clear(); - g.setFont('6x8:2x2').setFontAlign(0,-1); - g.drawString(require('contourclock').drawClock(settings.fontIndex),g.getWidth()/2,0); - g.drawString('Swipe - change',g.getWidth()/2,g.getHeight()-36); - g.drawString('BTN - save',g.getWidth()/2,g.getHeight()-18); + function mainMenu() { + E.showMenu({ + "" : { "title" : "ContourClock" }, + "< Back" : () => back(), + 'Widgets': { + value: (settings.widgets !== undefined ? settings.widgets : true), + onchange : v => {settings.widgets=v; require('Storage').writeJSON('contourclock.json', settings);} + }, + 'hide Widgets': { + value: (settings.hide !== undefined ? settings.hide : false), + onchange : v => {settings.hide=v; require('Storage').writeJSON('contourclock.json', settings);} + }, + 'Weekday': { + value: (settings.weekday !== undefined ? settings.weekday : true), + onchange : v => {settings.weekday=v; require('Storage').writeJSON('contourclock.json', settings);} + }, + 'Date': { + value: (settings.date !== undefined ? settings.date : true), + onchange : v => {settings.date=v; require('Storage').writeJSON('contourclock.json', settings);} + }, + 'Hide when locked': { + value: (settings.hideWhenLocked !== undefined ? settings.hideWhenLocked : false), + onchange : v => {settings.hideWhenLocked=v; require('Storage').writeJSON('contourclock.json', settings);} + }, + 'set Font': () => fontMenu() + }); + } + function fontMenu() { + Bangle.setUI(""); + savedIndex=settings.fontIndex; + saveListener = setWatch(function() { //save changes and return to settings menu + require('Storage').writeJSON('contourclock.json', settings); + Bangle.removeAllListeners('swipe'); + Bangle.removeAllListeners('lock'); + mainMenu(); + }, BTN, { repeat:false, edge:'falling' }); + lockListener = Bangle.on('lock', function () { //discard changes and return to clock + settings.fontIndex=savedIndex; + require('Storage').writeJSON('contourclock.json', settings); + Bangle.removeAllListeners('swipe'); + Bangle.removeAllListeners('lock'); + mainMenu(); + }); + swipeListener = Bangle.on('swipe', function (direction) { + var fontName = require('contourclock').drawClock(settings.fontIndex+direction); + if (fontName) { + settings.fontIndex+=direction; + g.clearRect(0,g.getHeight()-36,g.getWidth()-1,g.getHeight()-36+16); + g.setFont('6x8:2x2').setFontAlign(0,-1).drawString(fontName,g.getWidth()/2,g.getHeight()-36); + } else { + require('contourclock').drawClock(settings.fontIndex); + } + }); + g.reset(); + g.clearRect(0,24,g.getWidth()-1,g.getHeight()-1); + g.setFont('6x8:2x2').setFontAlign(0,-1); + g.drawString(require('contourclock').drawClock(settings.fontIndex),g.getWidth()/2,g.getHeight()-36); + g.drawString('Button to save',g.getWidth()/2,g.getHeight()-18); + } + mainMenu(); }) diff --git a/apps/contourclock/lib.js b/apps/contourclock/lib.js index 65a4622f4..c4f927953 100644 --- a/apps/contourclock/lib.js +++ b/apps/contourclock/lib.js @@ -1,3 +1,11 @@ +var is12; +function getHours(d) { + var h = d.getHours(); + if (is12===undefined) is12 = (require('Storage').readJSON('setting.json',1)||{})["12hour"]; + if (!is12) return h; + return (h%12==0) ? 12 : h%12; +} + exports.drawClock = function(fontIndex) { var digits = []; fontFile=require("Storage").read("contourclock-"+Math.abs(parseInt(fontIndex+0.5))+".json"); @@ -15,8 +23,8 @@ exports.drawClock = function(fontIndex) { var y = g.getHeight()/2-digits[0].height/2; var date = new Date(); g.clearRect(0,38,g.getWidth()-1,138); - d1=parseInt(date.getHours()/10); - d2=parseInt(date.getHours()%10); + d1=parseInt(getHours(date)/10); + d2=parseInt(getHours(date)%10); d3=10; d4=parseInt(date.getMinutes()/10); d5=parseInt(date.getMinutes()%10); diff --git a/apps/contourclock/metadata.json b/apps/contourclock/metadata.json index a5d764f2d..6b2b51991 100644 --- a/apps/contourclock/metadata.json +++ b/apps/contourclock/metadata.json @@ -1,9 +1,10 @@ { "id": "contourclock", "name": "Contour Clock", "shortName" : "Contour Clock", - "version":"0.24", + "version":"0.28", "icon": "app.png", - "description": "A Minimalist clockface with large Digits. Now with more fonts!", + "readme": "README.md", + "description": "A Minimalist clockface with large Digits.", "screenshots" : [{"url":"cc-screenshot-1.png"},{"url":"cc-screenshot-2.png"}], "tags": "clock", "custom": "custom.html", diff --git a/apps/coretemp/ChangeLog b/apps/coretemp/ChangeLog index ad6f0742d..7386bbc35 100644 --- a/apps/coretemp/ChangeLog +++ b/apps/coretemp/ChangeLog @@ -1,3 +1,4 @@ 0.01: New app 0.02: Cleanup interface and add settings, widget, add skin temp reporting. 0.03: Move code for recording to this app +0.04: Use default Bangle formatter for booleans diff --git a/apps/coretemp/metadata.json b/apps/coretemp/metadata.json index cb12624ae..87cb42722 100644 --- a/apps/coretemp/metadata.json +++ b/apps/coretemp/metadata.json @@ -1,7 +1,7 @@ { "id": "coretemp", "name": "CoreTemp", - "version": "0.03", + "version": "0.04", "description": "Display CoreTemp device sensor data", "icon": "coretemp.png", "type": "app", diff --git a/apps/coretemp/settings.js b/apps/coretemp/settings.js index 3fc2dfbf2..23ea09167 100644 --- a/apps/coretemp/settings.js +++ b/apps/coretemp/settings.js @@ -35,7 +35,6 @@ const menu = { '< Back' : back, 'Enabled' : { value : !!s.enabled, - format : v => v ? "Yes" : "No", onchange : v => { s.enabled = v; updateSettings(); diff --git a/apps/counter/ChangeLog b/apps/counter/ChangeLog index f3f1c4eac..8402b3467 100644 --- a/apps/counter/ChangeLog +++ b/apps/counter/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Added decrement and touch functions 0.03: Set color - ensures widgets don't end up coloring the counter's text +0.04: Adopted for BangleJS 2 diff --git a/apps/counter/counter.js b/apps/counter/counter.js index 3e0687944..0054ada6d 100644 --- a/apps/counter/counter.js +++ b/apps/counter/counter.js @@ -1,45 +1,104 @@ var counter = 0; +const BANGLEJS2 = process.env.HWVERSION == 2; + +if (BANGLEJS2) { + var drag; + var y = 45; + var x = 5; +} else { + var y = 100; + var x = 25; +} function updateScreen() { - g.clearRect(0, 50, 250, 150); - g.setColor(0xFFFF); + if (BANGLEJS2) { + g.clearRect(0, 50, 250, 130); + } else { + g.clearRect(0, 50, 250, 150); + } + g.setBgColor(g.theme.bg).setColor(g.theme.fg); g.setFont("Vector",40).setFontAlign(0,0); g.drawString(Math.floor(counter), g.getWidth()/2, 100); - g.drawString('-', 45, 100); - g.drawString('+', 185, 100); + if (!BANGLEJS2) { + g.drawString('-', 45, 100); + g.drawString('+', 185, 100); + } } -// add a count by using BTN1 or BTN5 -setWatch(() => { - counter += 1; - updateScreen(); -}, BTN1, {repeat:true}); +if (BANGLEJS2) { + setWatch(() => { + counter = 0; + updateScreen(); + }, BTN1, {repeat:true}); + Bangle.on("drag", e => { + if (!drag) { // start dragging + drag = {x: e.x, y: e.y}; + } else if (!e.b) { // released + const dx = e.x-drag.x, dy = e.y-drag.y; + drag = null; + if (Math.abs(dx)>Math.abs(dy)+10) { + // horizontal + if (dx < dy) { + //console.log("left " + dx + " " + dy); + } else { + //console.log("right " + dx + " " + dy); + } + } else if (Math.abs(dy)>Math.abs(dx)+10) { + // vertical + if (dx < dy) { + //console.log("down " + dx + " " + dy); + if (counter > 0) counter -= 1; + updateScreen(); + } else { + //console.log("up " + dx + " " + dy); + counter += 1; + updateScreen(); + } + } else { + //console.log("tap " + e.x + " " + e.y); + } + } + }); + } else { -setWatch(() => { - counter += 1; - updateScreen(); -}, BTN5, {repeat:true}); + // add a count by using BTN1 or BTN5 + setWatch(() => { + counter += 1; + updateScreen(); + }, BTN1, {repeat:true}); + + setWatch(() => { + counter += 1; + updateScreen(); + }, BTN5, {repeat:true}); + + // subtract a count by using BTN3 or BTN4 + setWatch(() => { + if (counter > 0) counter -= 1; + updateScreen(); + }, BTN4, {repeat:true}); + + setWatch(() => { + if (counter > 0) counter -= 1; + updateScreen(); + }, BTN3, {repeat:true}); + + // reset by using BTN2 + setWatch(() => { + counter = 0; + updateScreen(); + }, BTN2, {repeat:true}); +} -// subtract a count by using BTN3 or BTN4 -setWatch(() => { - counter -= 1; - updateScreen(); -}, BTN4, {repeat:true}); - -setWatch(() => { - counter -= 1; - updateScreen(); -}, BTN3, {repeat:true}); - -// reset by using BTN2 -setWatch(() => { - counter = 0; - updateScreen(); -}, BTN2, {repeat:true}); g.clear(1).setFont("6x8"); -g.drawString('Tap right or BTN1 to increase\nTap left or BTN3 to decrease\nPress BTN2 to reset.', 25, 200); +g.setBgColor(g.theme.bg).setColor(g.theme.fg); +if (BANGLEJS2) { + g.drawString('Swipe up to increase\nSwipe down to decrease\nPress button to reset.', x, 100 + y); +} else { + g.drawString('Tap right or BTN1 to increase\nTap left or BTN3 to decrease\nPress BTN2 to reset.', x, 100 + y); +} Bangle.loadWidgets(); Bangle.drawWidgets(); diff --git a/apps/counter/metadata.json b/apps/counter/metadata.json index e455fda95..daba58d39 100644 --- a/apps/counter/metadata.json +++ b/apps/counter/metadata.json @@ -1,11 +1,11 @@ { "id": "counter", "name": "Counter", - "version": "0.03", + "version": "0.04", "description": "Simple counter", "icon": "counter_icon.png", "tags": "tool", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS", "BANGLEJS2"], "screenshots": [{"url":"bangle1-counter-screenshot.png"}], "allow_emulator": true, "storage": [ diff --git a/apps/crowclk/ChangeLog b/apps/crowclk/ChangeLog index 5560f00bc..1c4f6f43b 100644 --- a/apps/crowclk/ChangeLog +++ b/apps/crowclk/ChangeLog @@ -1 +1,4 @@ 0.01: New App! +0.02: Removed "wake LCD on face-up"-feature: A watch-face should not set things like "wake LCD on face-up". +0.03: Fix the clock for dark mode. +0.04: Tell clock widgets to hide. diff --git a/apps/crowclk/crow_clock.js b/apps/crowclk/crow_clock.js index 97e5e61d9..7e608ef19 100644 --- a/apps/crowclk/crow_clock.js +++ b/apps/crowclk/crow_clock.js @@ -76,7 +76,7 @@ function draw_clock(){ // g.drawLine(clock_center.x - radius, clock_center.y, clock_center.x + radius, clock_center.y); // g.drawLine(clock_center.x, clock_center.y - radius, clock_center.x, clock_center.y + radius); - g.setColor(g.theme.fg); + g.setColor(g.theme.dark ? g.theme.bg : g.theme.fg); let ticks = [0, 90, 180, 270]; ticks.forEach((item)=>{ let agl = item+180; @@ -92,13 +92,13 @@ function draw_clock(){ let minute_agl = minute_angle(date); g.drawImage(hour_hand, hour_pos_x(hour_agl), hour_pos_y(hour_agl), {rotate:hour_agl*p180}); // g.drawImage(minute_hand, minute_pos_x(minute_agl), minute_pos_y(minute_agl), {rotate:minute_agl*p180}); // - g.setColor(g.theme.fg); + g.setColor(g.theme.dark ? g.theme.bg : g.theme.fg); g.fillCircle(clock_center.x, clock_center.y, 6); - g.setColor(g.theme.bg); + g.setColor(g.theme.dark ? g.theme.fg : g.theme.bg); g.fillCircle(clock_center.x, clock_center.y, 3); // draw minute ticks. Takes long time to draw! - g.setColor(g.theme.fg); + g.setColor(g.theme.dark ? g.theme.bg : g.theme.fg); for (var i=0; i<60; i++){ let agl = i*6+180; g.drawImage(tick1.asImage(), rotate_around_x(big_wheel_x(i*6), agl, tick1), rotate_around_y(big_wheel_y(i*6), agl, tick1), {rotate:agl*p180}); @@ -133,21 +133,12 @@ Bangle.on('lcdPower', (on) => { clearTimers(); } }); -Bangle.on('faceUp',function(up){ - //console.log("faceUp: " + up + " LCD: " + Bangle.isLCDOn()); - if (up && !Bangle.isLCDOn()) { - //console.log("faceUp and LCD off"); - clearTimers(); - Bangle.setLCDPower(true); - } -}); - g.clear(); +// Show launcher when button pressed +Bangle.setUI("clock"); Bangle.loadWidgets(); Bangle.drawWidgets(); startTimers(); -// Show launcher when button pressed -Bangle.setUI("clock"); diff --git a/apps/crowclk/metadata.json b/apps/crowclk/metadata.json index 365393e6c..265a0398b 100644 --- a/apps/crowclk/metadata.json +++ b/apps/crowclk/metadata.json @@ -1,7 +1,7 @@ { "id": "crowclk", "name": "Crow Clock", - "version": "0.01", + "version": "0.04", "description": "A simple clock based on Bold Clock that has MST3K's Crow T. Robot for a face", "icon": "crow_clock.png", "screenshots": [{"url":"screenshot_crow.png"}], diff --git a/apps/cscsensor/ChangeLog b/apps/cscsensor/ChangeLog index 8f23fa9f3..a98be5c0f 100644 --- a/apps/cscsensor/ChangeLog +++ b/apps/cscsensor/ChangeLog @@ -5,3 +5,4 @@ 0.05: Add cadence sensor support 0.06: Now read wheel rev as well as cadence sensor Improve connection code +0.07: Make Bangle.js 2 compatible diff --git a/apps/cscsensor/README.md b/apps/cscsensor/README.md index 9740fd9cf..3828e8e3e 100644 --- a/apps/cscsensor/README.md +++ b/apps/cscsensor/README.md @@ -11,9 +11,9 @@ Currently the app displays the following data: - total distance traveled - an icon with the battery status of the remote sensor -Button 1 resets all measurements except total distance traveled. The latter gets preserved by being written to storage every 0.1 miles and upon exiting the app. -If the watch app has not received an update from the sensor for at least 10 seconds, pushing button 3 will attempt to reconnect to the sensor. -Button 2 switches between the display for cycling speed and cadence. +Button 1 (swipe up on Bangle.js 2) resets all measurements except total distance traveled. The latter gets preserved by being written to storage every 0.1 miles and upon exiting the app. +If the watch app has not received an update from the sensor for at least 10 seconds, pushing button 3 (swipe down on Bangle.js 2) will attempt to reconnect to the sensor. +Button 2 (tap on Bangle.js 2) switches between the display for cycling speed and cadence. Values displayed are imperial or metric (depending on locale), cadence is in RPM, the wheel circumference can be adjusted in the global settings app. diff --git a/apps/cscsensor/cscsensor.app.js b/apps/cscsensor/cscsensor.app.js index e2af0db16..4ebe7d57e 100644 --- a/apps/cscsensor/cscsensor.app.js +++ b/apps/cscsensor/cscsensor.app.js @@ -7,6 +7,11 @@ const SETTINGS_FILE = 'cscsensor.json'; const storage = require('Storage'); const W = g.getWidth(); const H = g.getHeight(); +const yStart = 48; +const rowHeight = (H-yStart)/6; +const yCol1 = W/2.7586; +const fontSizeLabel = W/12.632; +const fontSizeValue = W/9.2308; class CSCSensor { constructor() { @@ -22,7 +27,6 @@ class CSCSensor { this.speed = 0; this.maxSpeed = 0; this.lastSpeed = 0; - this.qUpdateScreen = true; this.lastRevsStart = -1; this.qMetric = !require("locale").speed(1).toString().endsWith("mph"); this.speedUnit = this.qMetric ? "km/h" : "mph"; @@ -49,6 +53,7 @@ class CSCSensor { toggleDisplayCadence() { this.showCadence = !this.showCadence; this.screenInit = true; + g.setBgColor(0, 0, 0); } setBatteryLevel(level) { @@ -63,14 +68,16 @@ class CSCSensor { } drawBatteryIcon() { - g.setColor(1, 1, 1).drawRect(10, 55, 20, 75).fillRect(14, 53, 16, 55).setColor(0).fillRect(11, 56, 19, 74); + g.setColor(1, 1, 1).drawRect(10*W/240, yStart+0.029167*H, 20*W/240, yStart+0.1125*H) + .fillRect(14*W/240, yStart+0.020833*H, 16*W/240, yStart+0.029167*H) + .setColor(0).fillRect(11*W/240, yStart+0.033333*H, 19*W/240, yStart+0.10833*H); if (this.batteryLevel!=-1) { if (this.batteryLevel<25) g.setColor(1, 0, 0); else if (this.batteryLevel<50) g.setColor(1, 0.5, 0); else g.setColor(0, 1, 0); - g.fillRect(11, 74-18*this.batteryLevel/100, 19, 74); + g.fillRect(11*W/240, (yStart+0.10833*H)-18*this.batteryLevel/100, 19*W/240, yStart+0.10833*H); } - else g.setFontVector(14).setFontAlign(0, 0, 0).setColor(0xffff).drawString("?", 16, 66); + else g.setFontVector(W/17.143).setFontAlign(0, 0, 0).setColor(0xffff).drawString("?", 16*W/240, yStart+0.075*H); } updateScreenRevs() { @@ -88,36 +95,36 @@ class CSCSensor { for (var i=0; i<6; ++i) { if ((i&1)==0) g.setColor(0, 0, 0); else g.setColor(0x30cd); - g.fillRect(0, 48+i*32, 86, 48+(i+1)*32); + g.fillRect(0, yStart+i*rowHeight, yCol1-1, yStart+(i+1)*rowHeight); if ((i&1)==1) g.setColor(0); else g.setColor(0x30cd); - g.fillRect(87, 48+i*32, 239, 48+(i+1)*32); - g.setColor(0.5, 0.5, 0.5).drawRect(87, 48+i*32, 239, 48+(i+1)*32).drawLine(0, 239, 239, 239);//.drawRect(0, 48, 87, 239); - g.moveTo(0, 80).lineTo(30, 80).lineTo(30, 48).lineTo(87, 48).lineTo(87, 239).lineTo(0, 239).lineTo(0, 80); + g.fillRect(yCol1, yStart+i*rowHeight, H-1, yStart+(i+1)*rowHeight); + g.setColor(0.5, 0.5, 0.5).drawRect(yCol1, yStart+i*rowHeight, H-1, yStart+(i+1)*rowHeight).drawLine(0, H-1, W-1, H-1); + g.moveTo(0, yStart+0.13333*H).lineTo(30*W/240, yStart+0.13333*H).lineTo(30*W/240, yStart).lineTo(yCol1, yStart).lineTo(yCol1, H-1).lineTo(0, H-1).lineTo(0, yStart+0.13333*H); } - g.setFontAlign(1, 0, 0).setFontVector(19).setColor(1, 1, 0); - g.drawString("Time:", 87, 66); - g.drawString("Speed:", 87, 98); - g.drawString("Ave spd:", 87, 130); - g.drawString("Max spd:", 87, 162); - g.drawString("Trip:", 87, 194); - g.drawString("Total:", 87, 226); + g.setFontAlign(1, 0, 0).setFontVector(fontSizeLabel).setColor(1, 1, 0); + g.drawString("Time:", yCol1, yStart+rowHeight/2+0*rowHeight); + g.drawString("Speed:", yCol1, yStart+rowHeight/2+1*rowHeight); + g.drawString("Avg spd:", yCol1, yStart+rowHeight/2+2*rowHeight); + g.drawString("Max spd:", yCol1, yStart+rowHeight/2+3*rowHeight); + g.drawString("Trip:", yCol1, yStart+rowHeight/2+4*rowHeight); + g.drawString("Total:", yCol1, yStart+rowHeight/2+5*rowHeight); this.drawBatteryIcon(); this.screenInit = false; } - g.setFontAlign(-1, 0, 0).setFontVector(26); - g.setColor(0x30cd).fillRect(88, 49, 238, 79); - g.setColor(0xffff).drawString(dmins+":"+dsecs, 92, 66); - g.setColor(0).fillRect(88, 81, 238, 111); - g.setColor(0xffff).drawString(dspeed+" "+this.speedUnit, 92, 98); - g.setColor(0x30cd).fillRect(88, 113, 238, 143); - g.setColor(0xffff).drawString(avespeed + " " + this.speedUnit, 92, 130); - g.setColor(0).fillRect(88, 145, 238, 175); - g.setColor(0xffff).drawString(maxspeed + " " + this.speedUnit, 92, 162); - g.setColor(0x30cd).fillRect(88, 177, 238, 207); - g.setColor(0xffff).drawString(ddist + " " + this.distUnit, 92, 194); - g.setColor(0).fillRect(88, 209, 238, 238); - g.setColor(0xffff).drawString(tdist + " " + this.distUnit, 92, 226); + g.setFontAlign(-1, 0, 0).setFontVector(fontSizeValue); + g.setColor(0x30cd).fillRect(yCol1+1, 49+rowHeight*0, 238, 47+1*rowHeight); + g.setColor(0xffff).drawString(dmins+":"+dsecs, yCol1+5, 50+rowHeight/2+0*rowHeight); + g.setColor(0).fillRect(yCol1+1, 49+rowHeight*1, 238, 47+2*rowHeight); + g.setColor(0xffff).drawString(dspeed+" "+this.speedUnit, yCol1+5, 50+rowHeight/2+1*rowHeight); + g.setColor(0x30cd).fillRect(yCol1+1, 49+rowHeight*2, 238, 47+3*rowHeight); + g.setColor(0xffff).drawString(avespeed + " " + this.speedUnit, yCol1+5, 50+rowHeight/2+2*rowHeight); + g.setColor(0).fillRect(yCol1+1, 49+rowHeight*3, 238, 47+4*rowHeight); + g.setColor(0xffff).drawString(maxspeed + " " + this.speedUnit, yCol1+5, 50+rowHeight/2+3*rowHeight); + g.setColor(0x30cd).fillRect(yCol1+1, 49+rowHeight*4, 238, 47+5*rowHeight); + g.setColor(0xffff).drawString(ddist + " " + this.distUnit, yCol1+5, 50+rowHeight/2+4*rowHeight); + g.setColor(0).fillRect(yCol1+1, 49+rowHeight*5, 238, 47+6*rowHeight); + g.setColor(0xffff).drawString(tdist + " " + this.distUnit, yCol1+5, 50+rowHeight/2+5*rowHeight); } updateScreenCadence() { @@ -125,21 +132,21 @@ class CSCSensor { for (var i=0; i<2; ++i) { if ((i&1)==0) g.setColor(0, 0, 0); else g.setColor(0x30cd); - g.fillRect(0, 48+i*32, 86, 48+(i+1)*32); + g.fillRect(0, yStart+i*rowHeight, yCol1-1, yStart+(i+1)*rowHeight); if ((i&1)==1) g.setColor(0); else g.setColor(0x30cd); - g.fillRect(87, 48+i*32, 239, 48+(i+1)*32); - g.setColor(0.5, 0.5, 0.5).drawRect(87, 48+i*32, 239, 48+(i+1)*32).drawLine(0, 239, 239, 239);//.drawRect(0, 48, 87, 239); - g.moveTo(0, 80).lineTo(30, 80).lineTo(30, 48).lineTo(87, 48).lineTo(87, 239).lineTo(0, 239).lineTo(0, 80); + g.fillRect(yCol1, yStart+i*rowHeight, H-1, yStart+(i+1)*rowHeight); + g.setColor(0.5, 0.5, 0.5).drawRect(yCol1, yStart+i*rowHeight, H-1, yStart+(i+1)*rowHeight).drawLine(0, H-1, W-1, H-1); + g.moveTo(0, yStart+0.13333*H).lineTo(30*W/240, yStart+0.13333*H).lineTo(30*W/240, yStart).lineTo(yCol1, yStart).lineTo(yCol1, H-1).lineTo(0, H-1).lineTo(0, yStart+0.13333*H); } - g.setFontAlign(1, 0, 0).setFontVector(19).setColor(1, 1, 0); - g.drawString("Cadence:", 87, 98); + g.setFontAlign(1, 0, 0).setFontVector(fontSizeLabel).setColor(1, 1, 0); + g.drawString("Cadence:", yCol1, yStart+rowHeight/2+1*rowHeight); this.drawBatteryIcon(); this.screenInit = false; } - g.setFontAlign(-1, 0, 0).setFontVector(26); - g.setColor(0).fillRect(88, 81, 238, 111); - g.setColor(0xffff).drawString(Math.round(this.cadence), 92, 98); + g.setFontAlign(-1, 0, 0).setFontVector(fontSizeValue); + g.setColor(0).fillRect(yCol1+1, 49+rowHeight*1, 238, 47+2*rowHeight); + g.setColor(0xffff).drawString(Math.round(this.cadence), yCol1+5, 50+rowHeight/2+1*rowHeight); } updateScreen() { @@ -163,45 +170,45 @@ class CSCSensor { } this.lastCrankRevs = crankRevs; this.lastCrankTime = crankTime; - } - // wheel revolution - var wheelRevs = event.target.value.getUint32(1, true); - var dRevs = (this.lastRevs>0 ? wheelRevs-this.lastRevs : 0); - if (dRevs>0) { - qChanged = true; - this.totaldist += dRevs*this.wheelCirc/63360.0; - if ((this.totaldist-this.settings.totaldist)>0.1) { - this.settings.totaldist = this.totaldist; - storage.writeJSON(SETTINGS_FILE, this.settings); + } else { + // wheel revolution + var wheelRevs = event.target.value.getUint32(1, true); + var dRevs = (this.lastRevs>0 ? wheelRevs-this.lastRevs : 0); + if (dRevs>0) { + qChanged = true; + this.totaldist += dRevs*this.wheelCirc/63360.0; + if ((this.totaldist-this.settings.totaldist)>0.1) { + this.settings.totaldist = this.totaldist; + storage.writeJSON(SETTINGS_FILE, this.settings); + } } - } - this.lastRevs = wheelRevs; - if (this.lastRevsStart<0) this.lastRevsStart = wheelRevs; - var wheelTime = event.target.value.getUint16(5, true); - var dT = (wheelTime-this.lastTime)/1024; - var dBT = (Date.now()-this.lastBangleTime)/1000; - this.lastBangleTime = Date.now(); - if (dT<0) dT+=64; - if (Math.abs(dT-dBT)>3) dT = dBT; - this.lastTime = wheelTime; - this.speed = this.lastSpeed; - if (dRevs>0 && dT>0) { - this.speed = (dRevs*this.wheelCirc/63360.0)*3600/dT; - this.speedFailed = 0; - this.movingTime += dT; - } - else { - this.speedFailed++; - qChanged = false; - if (this.speedFailed>3) { - this.speed = 0; - qChanged = (this.lastSpeed>0); + this.lastRevs = wheelRevs; + if (this.lastRevsStart<0) this.lastRevsStart = wheelRevs; + var wheelTime = event.target.value.getUint16(5, true); + var dT = (wheelTime-this.lastTime)/1024; + var dBT = (Date.now()-this.lastBangleTime)/1000; + this.lastBangleTime = Date.now(); + if (dT<0) dT+=64; + if (Math.abs(dT-dBT)>3) dT = dBT; + this.lastTime = wheelTime; + this.speed = this.lastSpeed; + if (dRevs>0 && dT>0) { + this.speed = (dRevs*this.wheelCirc/63360.0)*3600/dT; + this.speedFailed = 0; + this.movingTime += dT; + } else if (!this.showCadence) { + this.speedFailed++; + qChanged = false; + if (this.speedFailed>3) { + this.speed = 0; + qChanged = (this.lastSpeed>0); + } } + this.lastSpeed = this.speed; + if (this.speed>this.maxSpeed && (this.movingTime>3 || this.speed<20) && this.speed<50) this.maxSpeed = this.speed; } - this.lastSpeed = this.speed; - if (this.speed>this.maxSpeed && (this.movingTime>3 || this.speed<20) && this.speed<50) this.maxSpeed = this.speed; } - if (qChanged && this.qUpdateScreen) this.updateScreen(); + if (qChanged) this.updateScreen(); } } @@ -253,9 +260,9 @@ E.on('kill',()=>{ }); NRF.on('disconnect', connection_setup); // restart if disconnected Bangle.setUI("updown", d=>{ - if (d<0) { mySensor.reset(); g.clearRect(0, 48, W, H); mySensor.updateScreen(); } - if (d==0) { if (Date.now()-mySensor.lastBangleTime>10000) connection_setup(); } - if (d>0) { mySensor.toggleDisplayCadence(); g.clearRect(0, 48, W, H); mySensor.updateScreen(); } + if (d<0) { mySensor.reset(); g.clearRect(0, yStart, W, H); mySensor.updateScreen(); } + else if (d>0) { if (Date.now()-mySensor.lastBangleTime>10000) connection_setup(); } + else { mySensor.toggleDisplayCadence(); g.clearRect(0, yStart, W, H); mySensor.updateScreen(); } }); Bangle.loadWidgets(); diff --git a/apps/cscsensor/metadata.json b/apps/cscsensor/metadata.json index af338c59e..4006789ef 100644 --- a/apps/cscsensor/metadata.json +++ b/apps/cscsensor/metadata.json @@ -2,11 +2,11 @@ "id": "cscsensor", "name": "Cycling speed sensor", "shortName": "CSCSensor", - "version": "0.06", + "version": "0.07", "description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch", "icon": "icons8-cycling-48.png", "tags": "outdoors,exercise,ble,bluetooth", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"cscsensor.app.js","url":"cscsensor.app.js"}, diff --git a/apps/custom/custom.html b/apps/custom/custom.html index 684f813ae..307f2fd2f 100644 --- a/apps/custom/custom.html +++ b/apps/custom/custom.html @@ -16,12 +16,12 @@

Type your javascript code here

-

Then click

+

Then click  

diff --git a/apps/cycling/ChangeLog b/apps/cycling/ChangeLog new file mode 100644 index 000000000..ec66c5568 --- /dev/null +++ b/apps/cycling/ChangeLog @@ -0,0 +1 @@ +0.01: Initial version diff --git a/apps/cycling/README.md b/apps/cycling/README.md new file mode 100644 index 000000000..7ba8ee224 --- /dev/null +++ b/apps/cycling/README.md @@ -0,0 +1,34 @@ +# Cycling +> Displays data from a BLE Cycling Speed and Cadence sensor. + +*This is a fork of the CSCSensor app using the layout library and separate module for CSC functionality. It also drops persistence of total distance on the Bangle, as this information is also persisted on the sensor itself. Further, it allows configuration of display units (metric/imperial) independent of chosen locale. Finally, multiple sensors can be used and wheel circumference can be configured for each sensor individually.* + +The following data are displayed: +- curent speed +- moving time +- average speed +- maximum speed +- trip distance +- total distance + +Other than in the original version of the app, total distance is not stored on the Bangle, but instead is calculated from the CWR (cumulative wheel revolutions) reported by the sensor. This metric is, according to the BLE spec, an absolute value that persists throughout the lifetime of the sensor and never rolls over. + +**Cadence / Crank features are currently not implemented** + +## Usage +Open the app and connect to a CSC sensor. + +Upon first connection, close the app afain and enter the settings app to configure the wheel circumference. The total circumference is (cm + mm) - it is split up into two values for ease of configuration. Check the status screen inside the Cycling app while connected to see the address of the currently connected sensor (if you need to differentiate between multiple sensors). + +Inside the Cycling app, use button / tap screen to: +- cycle through screens (if connected) +- reconnect (if connection aborted) + +## TODO +* Sensor battery status +* Implement crank events / show cadence +* Bangle.js 1 compatibility +* Allow setting CWR on the sensor (this is a feature intended by the BLE CSC spec, in case the sensor is replaced or transferred to a different bike) + +## Development +There is a "mock" version of the `blecsc` module, which can be used to test features in the emulator. Check `blecsc-emu.js` for usage. diff --git a/apps/cycling/blecsc-emu.js b/apps/cycling/blecsc-emu.js new file mode 100644 index 000000000..ca5058545 --- /dev/null +++ b/apps/cycling/blecsc-emu.js @@ -0,0 +1,111 @@ +// UUID of the Bluetooth CSC Service +const SERVICE_UUID = "1816"; +// UUID of the CSC measurement characteristic +const MEASUREMENT_UUID = "2a5b"; + +// Wheel revolution present bit mask +const FLAGS_WREV_BM = 0x01; +// Crank revolution present bit mask +const FLAGS_CREV_BM = 0x02; + +/** + * Fake BLECSC implementation for the emulator, where it's hard to test + * with actual hardware. Generates "random" wheel events (no crank). + * + * To upload as a module, paste the entire file in the console using this + * command: require("Storage").write("blecsc-emu",``); + */ +class BLECSCEmulator { + constructor() { + this.timeout = undefined; + this.interval = 500; + this.ccr = 0; + this.lwt = 0; + this.handlers = { + // value + // disconnect + // wheelEvent + // crankEvent + }; + } + + getDeviceAddress() { + return 'fa:ke:00:de:vi:ce'; + } + + /** + * Callback for the GATT characteristicvaluechanged event. + * Consumers must not call this method! + */ + onValue(event) { + // Not interested in non-CSC characteristics + if (event.target.uuid != "0x" + MEASUREMENT_UUID) return; + + // Notify the generic 'value' handler + if (this.handlers.value) this.handlers.value(event); + + const flags = event.target.value.getUint8(0, true); + // Notify the 'wheelEvent' handler + if ((flags & FLAGS_WREV_BM) && this.handlers.wheelEvent) this.handlers.wheelEvent({ + cwr: event.target.value.getUint32(1, true), // cumulative wheel revolutions + lwet: event.target.value.getUint16(5, true), // last wheel event time + }); + + // Notify the 'crankEvent' handler + if ((flags & FLAGS_CREV_BM) && this.handlers.crankEvent) this.handlers.crankEvent({ + ccr: event.target.value.getUint16(7, true), // cumulative crank revolutions + lcet: event.target.value.getUint16(9, true), // last crank event time + }); + } + + /** + * Register an event handler. + * + * @param {string} event value|disconnect + * @param {function} handler handler function that receives the event as its first argument + */ + on(event, handler) { + this.handlers[event] = handler; + } + + fakeEvent() { + this.interval = Math.max(50, Math.min(1000, this.interval + Math.random()*40-20)); + this.lwt = (this.lwt + this.interval) % 0x10000; + this.ccr++; + + var buffer = new ArrayBuffer(8); + var view = new DataView(buffer); + view.setUint8(0, 0x01); // Wheel revolution data present bit + view.setUint32(1, this.ccr, true); // Cumulative crank revolutions + view.setUint16(5, this.lwt, true); // Last wheel event time + + this.onValue({ + target: { + uuid: "0x2a5b", + value: view, + }, + }); + + this.timeout = setTimeout(this.fakeEvent.bind(this), this.interval); + } + + /** + * Find and connect to a device which exposes the CSC service. + * + * @return {Promise} + */ + connect() { + this.timeout = setTimeout(this.fakeEvent.bind(this), this.interval); + return Promise.resolve(true); + } + + /** + * Disconnect the device. + */ + disconnect() { + if (!this.timeout) return; + clearTimeout(this.timeout); + } +} + +exports = BLECSCEmulator; diff --git a/apps/cycling/blecsc.js b/apps/cycling/blecsc.js new file mode 100644 index 000000000..7a47108e5 --- /dev/null +++ b/apps/cycling/blecsc.js @@ -0,0 +1,150 @@ +const SERVICE_UUID = "1816"; +// UUID of the CSC measurement characteristic +const MEASUREMENT_UUID = "2a5b"; + +// Wheel revolution present bit mask +const FLAGS_WREV_BM = 0x01; +// Crank revolution present bit mask +const FLAGS_CREV_BM = 0x02; + +/** + * This class communicates with a Bluetooth CSC peripherial using the Espruino NRF library. + * + * ## Usage: + * 1. Register event handlers using the \`on(eventName, handlerFunction)\` method + * You can subscribe to the \`wheelEvent\` and \`crankEvent\` events or you can + * have raw characteristic values passed through using the \`value\` event. + * 2. Search and connect to a BLE CSC peripherial by calling the \`connect()\` method + * 3. To tear down the connection, call the \`disconnect()\` method + * + * ## Events + * - \`wheelEvent\` - the peripharial sends a notification containing wheel event data + * - \`crankEvent\` - the peripharial sends a notification containing crank event data + * - \`value\` - the peripharial sends any CSC characteristic notification (including wheel & crank event) + * - \`disconnect\` - the peripherial ends the connection or the connection is lost + * + * Each event can only have one handler. Any call to \`on()\` will + * replace a previously registered handler for the same event. + */ +class BLECSC { + constructor() { + this.device = undefined; + this.ccInterval = undefined; + this.gatt = undefined; + this.handlers = { + // wheelEvent + // crankEvent + // value + // disconnect + }; + } + + getDeviceAddress() { + if (!this.device || !this.device.id) + return '00:00:00:00:00:00'; + return this.device.id.split(" ")[0]; + } + + checkConnection() { + if (!this.device) + console.log("no device"); + // else + // console.log("rssi: " + this.device.rssi); + } + + /** + * Callback for the GATT characteristicvaluechanged event. + * Consumers must not call this method! + */ + onValue(event) { + // Not interested in non-CSC characteristics + if (event.target.uuid != "0x" + MEASUREMENT_UUID) return; + + // Notify the generic 'value' handler + if (this.handlers.value) this.handlers.value(event); + + const flags = event.target.value.getUint8(0, true); + // Notify the 'wheelEvent' handler + if ((flags & FLAGS_WREV_BM) && this.handlers.wheelEvent) this.handlers.wheelEvent({ + cwr: event.target.value.getUint32(1, true), // cumulative wheel revolutions + lwet: event.target.value.getUint16(5, true), // last wheel event time + }); + + // Notify the 'crankEvent' handler + if ((flags & FLAGS_CREV_BM) && this.handlers.crankEvent) this.handlers.crankEvent({ + ccr: event.target.value.getUint16(7, true), // cumulative crank revolutions + lcet: event.target.value.getUint16(9, true), // last crank event time + }); + } + + /** + * Callback for the NRF disconnect event. + * Consumers must not call this method! + */ + onDisconnect(event) { + console.log("disconnected"); + if (this.ccInterval) + clearInterval(this.ccInterval); + + if (!this.handlers.disconnect) return; + this.handlers.disconnect(event); + } + + /** + * Register an event handler. + * + * @param {string} event wheelEvent|crankEvent|value|disconnect + * @param {function} handler function that will receive the event as its first argument + */ + on(event, handler) { + this.handlers[event] = handler; + } + + /** + * Find and connect to a device which exposes the CSC service. + * + * @return {Promise} + */ + connect() { + // Register handler for the disconnect event to be passed throug + NRF.on('disconnect', this.onDisconnect.bind(this)); + + // Find a device, then get the CSC Service and subscribe to + // notifications on the CSC Measurement characteristic. + // NRF.setLowPowerConnection(true); + return NRF.requestDevice({ + timeout: 5000, + filters: [{ services: [SERVICE_UUID] }], + }).then(device => { + this.device = device; + this.device.on('gattserverdisconnected', this.onDisconnect.bind(this)); + this.ccInterval = setInterval(this.checkConnection.bind(this), 2000); + return device.gatt.connect(); + }).then(gatt => { + this.gatt = gatt; + return gatt.getPrimaryService(SERVICE_UUID); + }).then(service => { + return service.getCharacteristic(MEASUREMENT_UUID); + }).then(characteristic => { + characteristic.on('characteristicvaluechanged', this.onValue.bind(this)); + return characteristic.startNotifications(); + }); + } + + /** + * Disconnect the device. + */ + disconnect() { + if (this.ccInterval) + clearInterval(this.ccInterval); + + if (!this.gatt) return; + try { + this.gatt.disconnect(); + } catch { + // + } + } +} + +exports = BLECSC; diff --git a/apps/cycling/cycling.app.js b/apps/cycling/cycling.app.js new file mode 100644 index 000000000..268284a29 --- /dev/null +++ b/apps/cycling/cycling.app.js @@ -0,0 +1,453 @@ +const Layout = require('Layout'); +const storage = require('Storage'); + +const SETTINGS_FILE = 'cycling.json'; +const SETTINGS_DEFAULT = { + sensors: {}, + metric: true, +}; + +const RECONNECT_TIMEOUT = 4000; +const MAX_CONN_ATTEMPTS = 2; + +class CSCSensor { + constructor(blecsc, display) { + // Dependency injection + this.blecsc = blecsc; + this.display = display; + + // Load settings + this.settings = storage.readJSON(SETTINGS_FILE, true) || SETTINGS_DEFAULT; + this.wheelCirc = undefined; + + // CSC runtime variables + this.movingTime = 0; // unit: s + this.lastBangleTime = Date.now(); // unit: ms + this.lwet = 0; // last wheel event time (unit: s/1024) + this.cwr = -1; // cumulative wheel revolutions + this.cwrTrip = 0; // wheel revolutions since trip start + this.speed = 0; // unit: m/s + this.maxSpeed = 0; // unit: m/s + this.speedFailed = 0; + + // Other runtime variables + this.connected = false; + this.failedAttempts = 0; + this.failed = false; + + // Layout configuration + this.layout = 0; + this.display.useMetricUnits(true); + this.deviceAddress = undefined; + this.display.useMetricUnits((this.settings.metric)); + } + + onDisconnect(event) { + console.log("disconnected ", event); + + this.connected = false; + this.wheelCirc = undefined; + + this.setLayout(0); + this.display.setDeviceAddress("unknown"); + + if (this.failedAttempts >= MAX_CONN_ATTEMPTS) { + this.failed = true; + this.display.setStatus("Connection failed after " + MAX_CONN_ATTEMPTS + " attempts."); + } else { + this.display.setStatus("Disconnected"); + setTimeout(this.connect.bind(this), RECONNECT_TIMEOUT); + } + } + + loadCircumference() { + if (!this.deviceAddress) return; + + // Add sensor to settings if not present + if (!this.settings.sensors[this.deviceAddress]) { + this.settings.sensors[this.deviceAddress] = { + cm: 223, + mm: 0, + }; + storage.writeJSON(SETTINGS_FILE, this.settings); + } + + const high = this.settings.sensors[this.deviceAddress].cm || 223; + const low = this.settings.sensors[this.deviceAddress].mm || 0; + this.wheelCirc = (10*high + low) / 1000; + } + + connect() { + this.connected = false; + this.setLayout(0); + this.display.setStatus("Connecting"); + console.log("Trying to connect to BLE CSC"); + + // Hook up events + this.blecsc.on('wheelEvent', this.onWheelEvent.bind(this)); + this.blecsc.on('disconnect', this.onDisconnect.bind(this)); + + // Scan for BLE device and connect + this.blecsc.connect() + .then(function() { + this.failedAttempts = 0; + this.failed = false; + this.connected = true; + this.deviceAddress = this.blecsc.getDeviceAddress(); + console.log("Connected to " + this.deviceAddress); + + this.display.setDeviceAddress(this.deviceAddress); + this.display.setStatus("Connected"); + + this.loadCircumference(); + + // Switch to speed screen in 2s + setTimeout(function() { + this.setLayout(1); + this.updateScreen(); + }.bind(this), 2000); + }.bind(this)) + .catch(function(e) { + this.failedAttempts++; + this.onDisconnect(e); + }.bind(this)); + } + + disconnect() { + this.blecsc.disconnect(); + this.reset(); + this.setLayout(0); + this.display.setStatus("Disconnected"); + } + + setLayout(num) { + this.layout = num; + if (this.layout == 0) { + this.display.updateLayout("status"); + } else if (this.layout == 1) { + this.display.updateLayout("speed"); + } else if (this.layout == 2) { + this.display.updateLayout("distance"); + } + } + + reset() { + this.connected = false; + this.failed = false; + this.failedAttempts = 0; + this.wheelCirc = undefined; + } + + interact(d) { + // Only interested in tap / center button + if (d) return; + + // Reconnect in failed state + if (this.failed) { + this.reset(); + this.connect(); + } else if (this.connected) { + this.setLayout((this.layout + 1) % 3); + } + } + + updateScreen() { + var tripDist = this.cwrTrip * this.wheelCirc; + var avgSpeed = this.movingTime > 3 ? tripDist / this.movingTime : 0; + + this.display.setTotalDistance(this.cwr * this.wheelCirc); + this.display.setTripDistance(tripDist); + this.display.setSpeed(this.speed); + this.display.setAvg(avgSpeed); + this.display.setMax(this.maxSpeed); + this.display.setTime(Math.floor(this.movingTime)); + } + + onWheelEvent(event) { + // Calculate number of revolutions since last wheel event + var dRevs = (this.cwr > 0 ? event.cwr - this.cwr : 0); + this.cwr = event.cwr; + + // Increment the trip revolutions counter + this.cwrTrip += dRevs; + + // Calculate time delta since last wheel event + var dT = (event.lwet - this.lwet)/1024; + var now = Date.now(); + var dBT = (now-this.lastBangleTime)/1000; + this.lastBangleTime = now; + if (dT<0) dT+=64; // wheel event time wraps every 64s + if (Math.abs(dT-dBT)>3) dT = dBT; // not sure about the reason for this + this.lwet = event.lwet; + + // Recalculate current speed + if (dRevs>0 && dT>0) { + this.speed = dRevs * this.wheelCirc / dT; + this.speedFailed = 0; + this.movingTime += dT; + } else { + this.speedFailed++; + if (this.speedFailed>3) { + this.speed = 0; + } + } + + // Update max speed + if (this.speed>this.maxSpeed + && (this.movingTime>3 || this.speed<20) + && this.speed<50 + ) this.maxSpeed = this.speed; + + this.updateScreen(); + } +} + +class CSCDisplay { + constructor() { + this.metric = true; + this.fontLabel = "6x8"; + this.fontSmall = "15%"; + this.fontMed = "18%"; + this.fontLarge = "32%"; + this.currentLayout = "status"; + this.layouts = {}; + this.layouts.speed = new Layout({ + type: "v", + c: [ + { + type: "h", + id: "speed_g", + fillx: 1, + filly: 1, + pad: 4, + bgCol: "#fff", + c: [ + {type: undefined, width: 32, halign: -1}, + {type: "txt", id: "speed", label: "00.0", font: this.fontLarge, bgCol: "#fff", col: "#000", width: 122}, + {type: "txt", id: "speed_u", label: " km/h", font: this.fontLabel, col: "#000", width: 22, r: 90}, + ] + }, + { + type: "h", + id: "time_g", + fillx: 1, + pad: 4, + bgCol: "#000", + height: 36, + c: [ + {type: undefined, width: 32, halign: -1}, + {type: "txt", id: "time", label: "00:00", font: this.fontMed, bgCol: "#000", col: "#fff", width: 122}, + {type: "txt", id: "time_u", label: "mins", font: this.fontLabel, bgCol: "#000", col: "#fff", width: 22, r: 90}, + ] + }, + { + type: "h", + id: "stats_g", + fillx: 1, + bgCol: "#fff", + height: 36, + c: [ + { + type: "v", + pad: 4, + bgCol: "#fff", + c: [ + {type: "txt", id: "max_l", label: "MAX", font: this.fontLabel, col: "#000"}, + {type: "txt", id: "max", label: "00.0", font: this.fontSmall, bgCol: "#fff", col: "#000", width: 69}, + ], + }, + { + type: "v", + pad: 4, + bgCol: "#fff", + c: [ + {type: "txt", id: "avg_l", label: "AVG", font: this.fontLabel, col: "#000"}, + {type: "txt", id: "avg", label: "00.0", font: this.fontSmall, bgCol: "#fff", col: "#000", width: 69}, + ], + }, + {type: "txt", id: "stats_u", label: " km/h", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 22, r: 90}, + ] + }, + ], + }); + this.layouts.distance = new Layout({ + type: "v", + bgCol: "#fff", + c: [ + { + type: "h", + id: "tripd_g", + fillx: 1, + pad: 4, + bgCol: "#fff", + height: 32, + c: [ + {type: "txt", id: "tripd_l", label: "TRP", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 36}, + {type: "txt", id: "tripd", label: "0", font: this.fontMed, bgCol: "#fff", col: "#000", width: 118}, + {type: "txt", id: "tripd_u", label: "km", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 22, r: 90}, + ] + }, + { + type: "h", + id: "totald_g", + fillx: 1, + pad: 4, + bgCol: "#fff", + height: 32, + c: [ + {type: "txt", id: "totald_l", label: "TTL", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 36}, + {type: "txt", id: "totald", label: "0", font: this.fontMed, bgCol: "#fff", col: "#000", width: 118}, + {type: "txt", id: "totald_u", label: "km", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 22, r: 90}, + ] + }, + ], + }); + this.layouts.status = new Layout({ + type: "v", + c: [ + { + type: "h", + id: "status_g", + fillx: 1, + bgCol: "#fff", + height: 100, + c: [ + {type: "txt", id: "status", label: "Bangle Cycling", font: this.fontSmall, bgCol: "#fff", col: "#000", width: 176, wrap: 1}, + ] + }, + { + type: "h", + id: "addr_g", + fillx: 1, + pad: 4, + bgCol: "#fff", + height: 32, + c: [ + { type: "txt", id: "addr_l", label: "ADDR", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 36 }, + { type: "txt", id: "addr", label: "unknown", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 140 }, + ] + }, + ], + }); + } + + updateLayout(layout) { + this.currentLayout = layout; + + g.clear(); + this.layouts[layout].update(); + this.layouts[layout].render(); + Bangle.drawWidgets(); + } + + renderIfLayoutActive(layout, node) { + if (layout != this.currentLayout) return; + this.layouts[layout].render(node); + } + + useMetricUnits(metric) { + this.metric = metric; + + // console.log("using " + (metric ? "metric" : "imperial") + " units"); + + var speedUnit = metric ? "km/h" : "mph"; + this.layouts.speed.speed_u.label = speedUnit; + this.layouts.speed.stats_u.label = speedUnit; + + var distanceUnit = metric ? "km" : "mi"; + this.layouts.distance.tripd_u.label = distanceUnit; + this.layouts.distance.totald_u.label = distanceUnit; + + this.updateLayout(this.currentLayout); + } + + convertDistance(meters) { + if (this.metric) return meters / 1000; + return meters / 1609.344; + } + + convertSpeed(mps) { + if (this.metric) return mps * 3.6; + return mps * 2.23694; + } + + setSpeed(speed) { + this.layouts.speed.speed.label = this.convertSpeed(speed).toFixed(1); + this.renderIfLayoutActive("speed", this.layouts.speed.speed_g); + } + + setAvg(speed) { + this.layouts.speed.avg.label = this.convertSpeed(speed).toFixed(1); + this.renderIfLayoutActive("speed", this.layouts.speed.stats_g); + } + + setMax(speed) { + this.layouts.speed.max.label = this.convertSpeed(speed).toFixed(1); + this.renderIfLayoutActive("speed", this.layouts.speed.stats_g); + } + + setTime(seconds) { + var time = ''; + var hours = Math.floor(seconds/3600); + if (hours) { + time += hours + ":"; + this.layouts.speed.time_u.label = " hrs"; + } else { + this.layouts.speed.time_u.label = "mins"; + } + + time += String(Math.floor((seconds%3600)/60)).padStart(2, '0') + ":"; + time += String(seconds % 60).padStart(2, '0'); + + this.layouts.speed.time.label = time; + this.renderIfLayoutActive("speed", this.layouts.speed.time_g); + } + + setTripDistance(distance) { + this.layouts.distance.tripd.label = this.convertDistance(distance).toFixed(1); + this.renderIfLayoutActive("distance", this.layouts.distance.tripd_g); + } + + setTotalDistance(distance) { + distance = this.convertDistance(distance); + if (distance >= 1000) { + this.layouts.distance.totald.label = String(Math.round(distance)); + } else { + this.layouts.distance.totald.label = distance.toFixed(1); + } + this.renderIfLayoutActive("distance", this.layouts.distance.totald_g); + } + + setDeviceAddress(address) { + this.layouts.status.addr.label = address; + this.renderIfLayoutActive("status", this.layouts.status.addr_g); + } + + setStatus(status) { + this.layouts.status.status.label = status; + this.renderIfLayoutActive("status", this.layouts.status.status_g); + } +} + +var BLECSC; +if (process.env.BOARD === "EMSCRIPTEN" || process.env.BOARD === "EMSCRIPTEN2") { + // Emulator + BLECSC = require("blecsc-emu"); +} else { + // Actual hardware + BLECSC = require("blecsc"); +} +var blecsc = new BLECSC(); +var display = new CSCDisplay(); +var sensor = new CSCSensor(blecsc, display); + +E.on('kill',()=>{ + sensor.disconnect(); +}); + +Bangle.setUI("updown", d => { + sensor.interact(d); +}); + +Bangle.loadWidgets(); +sensor.connect(); diff --git a/apps/cycling/cycling.icon.js b/apps/cycling/cycling.icon.js new file mode 100644 index 000000000..12c597956 --- /dev/null +++ b/apps/cycling/cycling.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH/OAAIuuGFYuEGFQv/ADOlwV8wK/qwN8AAelGAguiFogACWsulFw6SERcwAFSISLnSMuAFZWCGENWllWLRSZC0vOAAovWmUslkyvbqJwIuHGC4uBAARiDdAwueL4YACMQLmfX5IAFqwwoMIowpMQ4wpGIcywDiYAA2IAAgwGq2kFwIvGC5YtPDJIuCF4gXPFxQHLF44XQFxAKOF4oXRBg4LOFwYvEEag7OBgReQNZzLNF5IXPBJlXq4vVC5Qv8R9TXQFwbvYJBgLlNbYXRBoYOEA44XfCAgAFCxgXYDI4VPC7IA/AH4A/AH4AWA")) diff --git a/apps/cycling/icons8-cycling-48.png b/apps/cycling/icons8-cycling-48.png new file mode 100644 index 000000000..0bc83859f Binary files /dev/null and b/apps/cycling/icons8-cycling-48.png differ diff --git a/apps/cycling/metadata.json b/apps/cycling/metadata.json new file mode 100644 index 000000000..cb4260bb2 --- /dev/null +++ b/apps/cycling/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "cycling", + "name": "Bangle Cycling", + "shortName": "Cycling", + "version": "0.01", + "description": "Display live values from a BLE CSC sensor", + "icon": "icons8-cycling-48.png", + "tags": "outdoors,exercise,ble,bluetooth", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"cycling.app.js","url":"cycling.app.js"}, + {"name":"cycling.settings.js","url":"settings.js"}, + {"name":"blecsc","url":"blecsc.js"}, + {"name":"cycling.img","url":"cycling.icon.js","evaluate": true} + ] +} diff --git a/apps/cycling/settings.js b/apps/cycling/settings.js new file mode 100644 index 000000000..76303379d --- /dev/null +++ b/apps/cycling/settings.js @@ -0,0 +1,57 @@ +// This file should contain exactly one function, which shows the app's settings +/** + * @param {function} back Use back() to return to settings menu + */ +(function(back) { + const storage = require('Storage') + const SETTINGS_FILE = 'cycling.json' + + // Set default values and merge with stored values + let settings = Object.assign({ + metric: true, + sensors: {}, + }, (storage.readJSON(SETTINGS_FILE, true) || {})); + + const menu = { + '': { 'title': 'Cycling' }, + '< Back': back, + 'Units': { + value: settings.metric, + format: v => v ? 'metric' : 'imperial', + onchange: (metric) => { + settings.metric = metric; + storage.writeJSON(SETTINGS_FILE, settings); + }, + }, + } + + const sensorMenus = {}; + for (var addr of Object.keys(settings.sensors)) { + // Define sub menu + sensorMenus[addr] = { + '': { title: addr }, + '< Back': () => E.showMenu(menu), + 'cm': { + value: settings.sensors[addr].cm, + min: 80, max: 240, step: 1, + onchange: (v) => { + settings.sensors[addr].cm = v; + storage.writeJSON(SETTINGS_FILE, settings); + }, + }, + '+ mm': { + value: settings.sensors[addr].mm, + min: 0, max: 9, step: 1, + onchange: (v) => { + settings.sensors[addr].mm = v; + storage.writeJSON(SETTINGS_FILE, settings); + }, + }, + }; + + // Add entry to main menu + menu[addr] = () => E.showMenu(sensorMenus[addr]); + } + + E.showMenu(menu); +}) diff --git a/apps/daisy/ChangeLog b/apps/daisy/ChangeLog new file mode 100644 index 000000000..61a09a18d --- /dev/null +++ b/apps/daisy/ChangeLog @@ -0,0 +1,9 @@ +0.01: first release +0.02: added settings menu to change color +0.03: fix metadata.json to allow setting as clock +0.04: added heart rate which is switched on when cycled to it through up/down touch on rhs +0.05: changed text to uppercase, just looks better, removed colons on text +0.06: better contrast for light theme, use fg color instead of dithered for ring +0.07: Use default Bangle formatter for booleans +0.08: fix idle timer always getting set to true +0.09: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps diff --git a/apps/daisy/README.md b/apps/daisy/README.md new file mode 100644 index 000000000..491ed697f --- /dev/null +++ b/apps/daisy/README.md @@ -0,0 +1,33 @@ +# Daisy ![](app.png) + + *A beautiful digital clock with large ring guage, idle timer and a + cyclic information line that includes, day, date, steps, battery, + sunrise and sunset times* + +Written by: [Hugh Barney](https://github.com/hughbarney) For support +and discussion please post in the [Bangle JS +Forum](http://forum.espruino.com/microcosms/1424/) + +* Derived from [The Ring](https://banglejs.com/apps/?id=thering) proof of concept and the [Pastel clock](https://banglejs.com/apps/?q=pastel) +* Includes the [Lazybones](https://banglejs.com/apps/?q=lazybones) Idle warning timer +* Touch the top right/top left to cycle through the info display (Day, Date, Steps, Sunrise, Sunset, Heart Rate) +* The heart rate monitor is turned on only when Heart rate is selected and will take a few seconds to settle +* The heart value is displayed in RED if the confidence value is less than 50% +* NOTE: The heart rate monitor of Bangle JS 2 is not very accurate when moving about. +See [#1248](https://github.com/espruino/BangleApps/issues/1248) +* Uses mylocation.json from MyLocation app to calculate sunrise and sunset times for your location +* If your Sunrise, Sunset times look odd make sure you have setup your location using +[MyLocation](https://banglejs.com/apps/?id=mylocation) +* The screen is updated every minute to save battery power +* Uses the [BloggerSansLight](https://www.1001fonts.com/rounded-fonts.html?page=3) font, which if free for commercial use + +## Future Development +* Use mini icons in the information line rather that text +* Add weather icons as per Pastel clock +* Add a lock icon to the screen + +## Screenshots +![](screenshot_daisy1.png) +![](screenshot_daisy3.png) + +It is worth looking at the real thing though as the screenshots do not do it justice. diff --git a/apps/daisy/app-icon.js b/apps/daisy/app-icon.js new file mode 100644 index 000000000..b577b3d5e --- /dev/null +++ b/apps/daisy/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///pf+sdR0n8CAkCwAcJhNgBI8KwALBgWgCo8NqAZHhNYktYloLKkoLHqwLByoLEgXoBYsrK4UDq0CqulrVVwGV4AXCquCBYYEBC4UC6tiBYeJq57DytayttvNW0tWHgclq2VtNpAYNYKQgiBBYIkBKgUK9Q8B9Nq1Nrqug1WgCoOqytq1/61NW1XVsALBq2ttbMB9N62olBKQNVtNvBYP61dVKgWlqtY34LB/wGBvQ8CEgIKBAAOVq7NDGwILD2/qBQWqAAILDAwUAlIzB1YLD9X1q+oytVqtbBYflA4NeBZVWlQjJ9A7LKZhrLQZS9Bqvq16bGWZXgZY2JZYcK1TjC9WrcYOAcYL7FpL7EAAMlq219NrRYNYBYeVrWV9t7q2lqwKCFwNi6utvVXxLuCBYWCGAIuBAgILCgdegVXBYPVwG1C4eohNWktYyvglYLCKgVeBYO1KQgLCrElvElBY94loBBBY/ghtghILGhSsBsECRgQZHBI5XCJ4kAA=")) diff --git a/apps/daisy/app.js b/apps/daisy/app.js new file mode 100644 index 000000000..c99b19228 --- /dev/null +++ b/apps/daisy/app.js @@ -0,0 +1,554 @@ +var SunCalc = require("suncalc"); // from modules folder +const storage = require('Storage'); +const locale = require("locale"); +const SETTINGS_FILE = "daisy.json"; +const LOCATION_FILE = "mylocation.json"; +const h = g.getHeight(); +const w = g.getWidth(); +let settings; +let location; + +// variable for controlling idle alert +let lastStep = getTime(); +let warned = 0; +let idle = false; +let IDLE_MINUTES = 26; + +let pal1; // palette for 0-40% +let pal2; // palette for 50-100% +const infoLine = (3*h/4) - 6; +const infoWidth = 56; +const infoHeight = 11; +var drawingSteps = false; + +function log_debug(o) { + //print(o); +} + +var hrmImg = require("heatshrink").decompress(atob("i0WgIKHgPh8Ef5/g///44CBz///1///5A4PnBQk///wA4PBA4MDA4MH/+Ah/8gEP4EAjw0GA")); + +// https://www.1001fonts.com/rounded-fonts.html?page=3 +Graphics.prototype.setFontBloggerSansLight46 = function(scale) { + // Actual height 46 (45 - 0) + this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAAAAAA/AAAAAAAAPwAAAAAAAD4AAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAH/gAAAAAAP/wAAAAAAf/gAAAAAAf/AAAAAAA//AAAAAAB/+AAAAAAD/8AAAAAAH/4AAAAAAH/wAAAAAAP/gAAAAAAf/gAAAAAA//AAAAAAB/+AAAAAAA/8AAAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///8AAAAP////4AAAP/////AAAH/////4AAD+AAAB/AAA8AAAAHwAAeAAAAA+AAHgAAAAHgADwAAAAB4AA8AAAAAPAAPAAAAADwADwAAAAA8AA8AAAAAPAAPAAAAADwAB4AAAAB4AAeAAAAAeAAHwAAAAPgAA/AAAAPwAAH/////4AAA/////8AAAH////+AAAAf///+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAPAAAAAAAAHwAAAAAAAB4AAAAAAAA+AAAAAAAAfAAAAAAAAHgAAAAAAAD4AAAAAAAB8AAAAAAAAeAAAAAAAAPgAAAAAAADwAAAAAAAB//////4AAf//////AAH//////gAA//////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAD4AAHAAAAD+AAD4AAAB/gAA8AAAB/4AAfAAAA/+AAHgAAAf3gAB4AAAPx4AA8AAAH4eAAPAAAD4HgADwAAB8B4AA8AAA+AeAAPAAAfAHgADwAAPgB4AA8AAHwAeAAHgAD4AHgAB4AD8AB4AAfAB+AAeAAD8B/AAHgAAf//gAB4AAH//wAAeAAAf/wAAHgAAB/wAAA4AAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AADgAAAAPAAB4AAAADwAAeAAAAA+AAHgAAAAHgAB4ABgAB4AAeAA8AAeAAHgA/AADwAB4AfwAA8AAeAP8AAPAAHgH/AADwAB4H7wAA8AAeD48AAPAAHh8PAAHgAB5+BwAB4AAe/AeAA+AAH/AHwAfAAB/gA/AfgAAfwAH//wAAHwAA//4AAA4AAH/8AAAAAAAf4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAD+AAAAAAAD/gAAAAAAH/4AAAAAAH/+AAAAAAP/ngAAAAAP/h4AAAAAf/AeAAAAAf/AHgAAAA/+AB4AAAA/+AAeAAAB/8AAHgAAA/8AAB4AAAP4AAAeAAAB4AAAHgAAAAAAAB4AAAAAAAAeAAAAAAP///4AAAAH////AAAAA////gAAAAP///4AAAAAAB4AAAAAAAAeAAAAAAAAHgAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAD4AA8AAD///gAPAAB///4AD4AAf//+AAeAAH+APAAHgAB4AHgAA4AAeAB4AAOAAHgAcAADwAB4AHAAA8AAeADwAAPAAHgAcAADwAB4AHAAA8AAeAB4AAeAAHgAeAAHgAB4AHwAD4AAeAA+AB8AAHgAP4B+AAB4AB///gAAOAAP//gAABAAA//wAAAAAAD/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/gAAAAAB///4AAAAD////wAAAD////+AAAB/////4AAA/gPgB/AAAfgDwAHwAAPgA8AA+AADwAeAAHgAB4AHgAB4AAeAB4AAfAAHgAeAADwABwAHgAA8AAcAB4AAPAAHAAeAAHwAB4AHgAB4AAeAB8AAeAAHgAPAAPgAB4AD8APwAAOAAfwP4AADgAD//8AAAAAAf/+AAAAAAB/+AAAAAAAH8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAB4AAAAAAAAeAAAAAAAAHgAAAAAAAB4AAAAA4AAeAAAAB/AAHgAAAB/wAB4AAAB/4AAeAAAD/4AAHgAAD/wAAB4AAH/wAAAeAAH/gAAAHgAP/gAAAB4AP/AAAAAeAf/AAAAAHgf+AAAAAB4/+AAAAAAe/8AAAAAAH/8AAAAAAB/4AAAAAAAf4AAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/gAAAA/AB/+AAAA/8B//wAAA//gf/+AAAf/8PgPgAAH4fngB8AAD4B/wAPgAA8AP8AB4AAeAB+AAeAAHgAfgADwAB4ADwAA8AAcAA8AAPAAHAAPAADwAB4ADwAA8AAeAB+AAPAAHgAfgAHgAB8AP8AB4AAPgH/AA+AAD8H54AfAAAf/8fgPwAAD/+D//4AAAf/Af/8AAAB/AD/+AAAAAAAP+AAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwAAAAAAAf/wAAAAAAf/+AAAAAAP//4AAwAAH//+AAeAAD+APwAHgAA+AA+AB4AAfAAHgAOAAHgAB4ADwAB4AAPAA8AAeAADwAPAAHgAA8ADwAB4AAPAA8AAeAADwAPAAHgAA8AHgAB8AAeAB4AAPgAHgA+AAD8ADwA/AAAfwA8A/gAAD/wef/wAAAf////4AAAB////4AAAAH///wAAAAAD/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AB4AAAAAfgA/AAAAAH4APwAAAAB+AD4AAAAAPAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DRAcHBwcHBwcHBwcDQ=="), 56+(scale<<8)+(1<<16)); + return this; +}; + +Graphics.prototype.setFontRoboto20 = function(scale) { + // Actual height 21 (20 - 0) + this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAH/zA/+YAAAAAAAHwAAwAAHwAA+AAAAAAAAAAAQACDAAYbADP4B/8A/zAGYZADH4A/+A/7AHYYADCAAAAAAAQAeHgH4eBzgwMMHnhw88GGBw4wHj+AcPgAAAAAAAAAAB4AA/gAGMAAwhwGMcAfuABzgABzgAc+AOMYBhBAAMYAB/AAHwAAAAAHwD5+A/8YGPDAw8YGPzA/HYD4fAADwAB/AAOYAABAAAAHwAA4AAAAAAAAAAH/gD//B8A+cAA7AADAAAAAAAYAAbwAHHgHwf/4A/8AAAAEAABiAAGwAA8AA/AAH+AAGwAByAAEAAAAAAAMAABgAAMAABgAH/wA/+AAMAABgAAMAABgAAAAAAAIAAfAADwAAAABgAAMAABgAAMAABgAAAAAAAAAAAAADAAAYAAAAAAAAADgAB8AB+AA+AA+AA/AAHAAAgAAAAAAB8AB/8Af/wHAHAwAYGADAwAYHAHAf/wB/8AAAAAAAAAAABgAAcAADAAAYAAH//A//4AAAAAAAAAAAAAAAAAAAAABwDAeA4HAPAwHYGBzAwcYHHDAfwYB8DAAAYAAAAAAABgOAcBwHADAwwYGGDAwwYHPHAf/wB58AAAAAAAAADAAB4AAfAAPYAHjAB4YA8DAH//A//4AAYAADAAAAAAAAAEMA/xwH+HAxgYGMDAxgYGODAw/4GD+AAHAAAAAAAAAf8AP/wD2HA5wYGMDAxgYGOHAA/wAD8AAAAAAAAAAAGAAAwAAGADAwB4GB+Aw+AGfAA/gAHwAAwAAAAAAADAB5+Af/wHPDAwwYGGDAwwYHPHAfvwB58AAAAAAAAAAAB+AAf4AHDjAwMYGBjAwM4HDOAf/gB/4AAAAAAAAAAAAYDADAYAAAAAAAAAAYDAfAYHwAAAABAAAcAADgAA+AAGwAB3AAMYABjgAYMAAAAAAAAAAAAAAABmAAMwABmAAMwABmAAMwABmAAMwAAiAAAAAAAAAYMADjgAMYAB3AAGwAA2AADgAAcAABAAAAAAAAAMAADgAA4AAGBzAweYGHAA/wAD8AAEAAAAwAB/4A/PwOAGDgAYYPxmH/Mw4ZmMDMxgZmM+Mx/5mHDAYAIDgDAPBwAf8AAMAAAAAAAYAAfAAPwAP4AH+AH4wA8GAH4wAP2AAPwAAfwAAfAAAYAAAAAAAAAAA//4H//AwwYGGDAwwYGGDAwwYH/HAf/wB58AAAAADAAH/AD/+AcBwHADAwAYGADAwAYGADA4A4DweAODgAAAAAAAAAAAAAAH//A//4GADAwAYGADAwAYGADAYAwD4+AP/gAfwAAAAAAAAAAAH//A//4GDDAwYYGDDAwYYGDDAwYYGCDAgAYAAAAAAAH//A//4GDAAwYAGDAAwYAGDAAwYAGAAAAAAAAAAH/AD/8AcBwHAHAwAYGADAwYYGDDA4YYDz/AOfwAAAAAAAAAAA//4H//A//4ADAAAYAADAAAYAADAAAYAADAA//4H//AAAAAAAAAAAAAAA//4H//AAAAAAAAABAAAeAAB4AADAAAYAADAAAYAAHA//wH/8AAAAAAAAAAAAAAA//4H//AAcAAPAAD4AA/wAOPADg8A4B4GAHAgAYAAAAAAAH//A//4AADAAAYAADAAAYAADAAAYAADAAAAAAAA//4H//A+AAB+AAD8AAD8AAH4AAPAAH4AH4AD8AD8AA+AAH//A//4AAAAAAAH//A//4H//AeAAB8AADwAAPgAAeAAA8AADwH//A//4AAAAAAAAAAAH/AB/8AeDwHAHAwAYGADAwAYGADA4A4DweAP/gA/4AAAAAAAAAAAH//A//4GBgAwMAGBgAwMAGBgAwcAH/AAfwAA8AAAAAA/4AP/gDgOA4A4GADAwAYGADAwAYHAHgeD+B/8wD+GAAAAAAAAAAA//4H//AwYAGDAAwYAGDgAweAHH8Afz4B8HAAAIAAYAPDwD8OA5w4GGDAwwYGHDAwYYHDnAePwBw8AAAAGAAAwAAGAAAwAAGAAA//4H//AwAAGAAAwAAGAAAwAAAAAAAAAH/4A//wAAPAAAYAADAAAYAADAAAYAAPA//wH/8AAAAAAAAgAAHAAA/AAB/AAD+AAD+AAD4AAfAAfwAfwAfwAH4AA4AAEAAA+AAH/AAH/gAD/AAD4AD+AH+AH8AA+AAH+AAD+AAD/AAD4AH/AP/AH+AA8AAAAAAAAAGADA4A4HweAPPgA/wAB8AAfwAPvgDweA8B4GADAAAIGAAA4AAHwAAPgAAfAAA/4AH/AD4AB8AA+AAHgAAwAAAAAAAAAGADAwB4GAfAwPYGDzAx4YGeDA/AYHwDA4AYGADAAAAAAAA///3//+wAA2AAGAAAGAAA+AAD8AAD8AAD4AAH4AAHgAAMAAAAwAA2AAG///3//+AAAAAAAAAAAOAAHwAD4AA8AAD8AADwAAGAAAAAAABgAAMAABgAAMAABgAAMAABgAAMAABgAAAEAAAwAADAAAIAAAAAAAAAAEeABn4Ad3ADMYAZjADMYAZmAB/4AP/AAAAAAAA//4H//ABgwAYDADAYAYDADg4AP+AA/gABwAAAAAAAAA/gAP+ADg4AYDADAYAYDADAYAOOABxwAAAAAEAAH8AB/wAcHADAYAYDADAYAcDA//4H//AAAAAAAAAAAAH8AB/wAdnADMYAZjADMYAZjAB84AHmAAMAAMAABgAB//gf/8HMAAxgAGIAAAAAAH8IB/zAcHMDAZgYDMDAZgcHcD//Af/wAAAAAAAAAAH//A//4AMAADAAAYAADAAAcAAD/4AP/AAAAAAAAAAAGf/Az/4AAAAAAAAAAMz//mf/4AAAAAAAAAAH//A//4ABwAAeAAH4ABzwAcPACAYAABAAAAAAAA//4H//AAAAAAAAAAAAf/AD/4AMAADAAAYAADAAAcAAD/4AP/ABgAAYAADAAAYAADgAAP/AA/4AAAAAAAAf/AD/4AMAADAAAYAADAAAcAAD/4AP/AAAAAAAAAAAAH8AB/wAcHADAYAYDADAYAYDADx4AP+AA/gAAAAAAAAf/8D//gYDADAYAYDADAYAcHAB/wAH8AAEAAAAAAEAAH8AB/wAcHADAYAYDADAYAYDAD//gf/8AAAAAAAAAAAf/AD/4AcAADAAAYAACAAAAEAB5wAfnADMYAZjADGYAYzADn4AOeAAAAAAAADAAAYAAf/wD//ADAYAYDAAAAAAAAD/gAf/AAA4AADAAAYAADAAAwAf/AD/4AAAAAAAAYAAD4AAP4AAP4AAPAAH4AH4AD8AAcAAAAAAQAADwAAf4AAf4AAPAAP4AP4ADwAAfgAA/gAA/AAD4AH+AD+AAeAAAAAAAAACAYAcHADzwAH8AAfAAH8ADx4AcHACAIAcAMD4BgP4MAP/AAPwAP4AP4AD4AAcAAAAAAAAADAYAYHADD4AY7ADOYAfjADwYAcDADAYAAAAADAAA4AH//B/v8cABzAACAAAH//w//+AAAAAAACAACcAAx/n+H//AA4AAHAAAAAAAAAAAAAOAADgAAYAADAAAcAABgAAGAAAwAAGAADwAAcAAAAA"), 32, atob("BQUHDQwPDQQHBwkMBAYGCQwMDAwMDAwMDAwFBAsMCwoTDg0ODgwMDg8GDA0LEg8ODQ4NDA0ODRMNDQ0GCQYJCQYLDAsMCwcMDAUFCwUSDAwMDAcLBwwKEAoKCgcFBw4A"), 21+(scale<<8)+(1<<16)); + return this; +}; + +function assignPalettes() { + if (g.theme.dark) { + // palette for 0-40% + pal1 = new Uint16Array([g.theme.bg, g.toColor(settings.gy), g.toColor(settings.fg), g.toColor("#00f")]); + // palette for 50-100% + pal2 = new Uint16Array([g.theme.bg, g.toColor(settings.fg), g.toColor(settings.gy), g.toColor("#00f")]); + } else { + // palette for 0-40% + pal1 = new Uint16Array([g.theme.bg, g.theme.fg, g.toColor(settings.fg), g.toColor("#00f")]); + // palette for 50-100% + pal2 = new Uint16Array([g.theme.bg, g.toColor(settings.fg), g.theme.fg, g.toColor("#00f")]); + } +} + +function setSmallFont20() { + g.setFontRoboto20(); +} + +function setLargeFont() { + g.setFontBloggerSansLight46(1); +} + +function setSmallFont() { + g.setFont('Vector', 16); +} + +function getSteps() { + try { + return Bangle.getHealthStatus("day").steps; + } catch (e) { + if (WIDGETS.wpedom !== undefined) + return WIDGETS.wpedom.getSteps(); + else + return 0; + } +} + +/////////////// sunrise / sunset ///////////////////////////// + +function loadSettings() { + settings = require("Storage").readJSON(SETTINGS_FILE,1)||{}; + settings.gy = settings.gy||'#020'; + settings.fg = settings.fg||'#0f0'; + settings.idle_check = (settings.idle_check === undefined ? true : settings.idle_check); + assignPalettes(); +} + +// requires the myLocation app +function loadLocation() { + location = require("Storage").readJSON(LOCATION_FILE,1)||{}; + location.lat = location.lat||51.5072; + location.lon = location.lon||0.1276; + location.location = location.location||"London"; +} + +function extractTime(d){ + var h = d.getHours(), m = d.getMinutes(); + return(("0"+h).substr(-2) + ":" + ("0"+m).substr(-2)); +} + +var sunRise = "00:00"; +var sunSet = "00:00"; +var drawCount = 0; + +function updateSunRiseSunSet(now, lat, lon, line){ + // get today's sunlight times for lat/lon + var times = SunCalc.getTimes(new Date(), lat, lon); + + // format sunrise time from the Date object + sunRise = extractTime(times.sunrise); + sunSet = extractTime(times.sunset); +} + +const infoData = { + ID_DATE: { calc: () => {var d = (new Date()).toString().split(" "); return d[2] + ' ' + d[1] + ' ' + d[3];} }, + ID_DAY: { calc: () => {var d = require("locale").dow(new Date()).toLowerCase(); return d[0].toUpperCase() + d.substring(1);} }, + ID_SR: { calc: () => 'SUNRISE ' + sunRise }, + ID_SS: { calc: () => 'SUNSET ' + sunSet }, + ID_STEP: { calc: () => 'STEPS ' + getSteps() }, + ID_BATT: { calc: () => 'BATTERY ' + E.getBattery() + '%' }, + ID_HRM: { calc: () => hrmCurrent } +}; + +const infoList = Object.keys(infoData).sort(); +let infoMode = infoList[0]; + +function nextInfo() { + let idx = infoList.indexOf(infoMode); + if (idx > -1) { + if (idx === infoList.length - 1) infoMode = infoList[0]; + else infoMode = infoList[idx + 1]; + } + // power HRM on/off accordingly + Bangle.setHRMPower(infoMode == "ID_HRM" ? 1 : 0); + resetHrm(); +} + +function prevInfo() { + let idx = infoList.indexOf(infoMode); + if (idx > -1) { + if (idx === 0) infoMode = infoList[infoList.length - 1]; + else infoMode = infoList[idx - 1]; + } + // power HRM on/off accordingly + Bangle.setHRMPower(infoMode == "ID_HRM" ? 1 : 0); + resetHrm(); +} + +function clearInfo() { + g.setColor(g.theme.bg); + //g.setColor(g.theme.fg); + g.fillRect((w/2) - infoWidth, infoLine - infoHeight, (w/2) + infoWidth, infoLine + infoHeight); +} + +function drawInfo() { + clearInfo(); + g.setColor(g.theme.fg); + setSmallFont(); + g.setFontAlign(0,0); + + if (infoMode == "ID_HRM") { + clearInfo(); + g.setColor('#f00'); // red + drawHeartIcon(); + } else { + g.drawString((infoData[infoMode].calc().toUpperCase()), w/2, infoLine); + } +} + +function drawHeartIcon() { + g.drawImage(hrmImg, (w/2) - infoHeight - 20, infoLine - infoHeight); +} + +function drawHrm() { + if (idle) return; // dont draw while prompting + var d = new Date(); + clearInfo(); + g.setColor(d.getSeconds()&1 ? '#f00' : g.theme.bg); + drawHeartIcon(); + setSmallFont(); + g.setFontAlign(-1,0); // left + g.setColor(hrmConfidence >= 50 ? g.theme.fg : '#f00'); + g.drawString(hrmCurrent, (w/2) + 10, infoLine); +} + +function draw() { + if (!idle) + drawClock(); + else + drawIdle(); + queueDraw(); +} + +function drawClock() { + var date = new Date(); + var timeStr = require("locale").time(date,1); + var da = date.toString().split(" "); + var time = da[4].substr(0,5); + var hh = da[4].substr(0,2); + var mm = da[4].substr(3,2); + var steps = getSteps(); + var p_steps = Math.round(100*(steps/10000)); + + g.reset(); + g.setColor(g.theme.bg); + g.fillRect(0, 0, w, h); + g.drawImage(getGaugeImage(p_steps), 0, 0); + setLargeFont(); + + g.setColor(settings.fg); + g.setFontAlign(1,0); // right aligned + g.drawString(hh, (w/2) - 1, h/2); + + g.setColor(g.theme.fg); + g.setFontAlign(-1,0); // left aligned + g.drawString(mm, (w/2) + 1, h/2); + + drawInfo(); + + // recalc sunrise / sunset every hour + if (drawCount % 60 == 0) + updateSunRiseSunSet(new Date(), location.lat, location.lon); + drawCount++; +} + +function drawSteps() { + if (drawingSteps) return; + drawingSteps = true; + clearInfo(); + setSmallFont(); + g.setFontAlign(0,0); + g.setColor(g.theme.fg); + g.drawString('STEPS ' + getSteps(), w/2, (3*h/4) - 4); + drawingSteps = false; +} + +///////////////// GAUGE images ///////////////////////////////////// + +var hrmCurrent = "--"; +var hrmConfidence = 0; + +function resetHrm() { + hrmCurrent = "--"; + hrmConfidence = 0; + if (infoMode == "ID_HRM") { + clearInfo(); + g.setColor('#f00'); // red + drawHeartIcon(); + } +} + +Bangle.on('HRM', function(hrm) { + hrmCurrent = hrm.bpm; + hrmConfidence = hrm.confidence; + log_debug("HRM=" + hrm.bpm + " (" + hrm.confidence + ")"); + if (infoMode == "ID_HRM" ) drawHrm(); +}); + + +///////////////// GAUGE images ///////////////////////////////////// + + +// putting into 1 function like this, rather than individual variables +// reduces ram usage from 70%-13% +function getGaugeImage(p) { + // p0 + if (p < 2) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVAAVUFUgpDAAdAFMEBFQ4ABqBVnLMQqLLLzWEABLgbVgohEGopYaiofDBihWVHJpYYDgYPbKx1ACJhYZIwT4OcAZWYHyRYUIgQXQH4RqOThCXUYRpCHNyQVVQQTwVQiSZWIQSEQNgSYSIYiEQQSyEUCQLDSOAyCnQiSCYQiSCYQiSCZDaDARObKuBSZwcaVzR0QFYKuZWAYNZWCJJKMoKuaWAahKBhiwTJRSudURorBFTgfMVzqjDO5DaeZ5jaeJhhiKbi4rIbT4hLqoriPI7afUpS5BbTwiKFdZgIADSmHFYIqgbgIrGcgIriEYwzHADZ7HRY4rdaYrjHADcBFYoGBFcgkEGQwAeFYqKHFbzUEcQ4AdiorwiorlEogxFAD59FWoorhoArDqArjgIr/FbYwFAEJSDFf4rXgornqgrDFUkAior/Ff4rGAYYAjKYYr/Ff4r/FbdVFdFAFYNQFcsBFf4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/FbdUFcsFFYUVFdADBFf4r/Ff4rbAYYAjKYYr/Ff4rFoArkqorCgIrnqAr/FbIEFAEBSFFf4rYqgrjgorEiormAocVAogAfEooxFFcB9EFdq1DAD9VFYkBFctQFYoGEADokHFcp8FRQoAdag7iFFb4HFioHGADYjHGY4rcPYyLHADbTHcYNQFT4iIFdZgIADKmJqrcgiorIBIIrhMKIAXUpIrBbjzaBFZAKKbS5MJFcKkJbj4fLBYLcdqorKbjzPMbjxKNMhauTURawdJJorBWDShBFZiRBWDQcOHRyuPOhorBWDIbPWDRzQSYKEYIwLLOHgSEXDIJyPQjD2SQjCCQQjSCRCYY/QN4xDRQiyCSQgjdSCqqECLCRWBYyiECISBWCYqgXCLCBWCQSYYEIhxqCeChYFThoQCKypYEIxgPPLB4cKFQZWXDoosIBhhYWcArWDKzYhHABA1EADArNoArcFhgqeWQysgLJxVfcBLWdAH4A5A")) + }; + + // p2 + if (p >= 2 && p < 4) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette: pal1, + buffer : require("heatshrink").decompress(atob("AH4A/ADNUFE8FqtVq2q1AqkFIIrDAAOAFMEBFQYrE1WgKsYrGLL4qFFY2pqDWeFZdUVkAhCAQMKFYdVLDUVFQYMHlWq0oMJKyoOJlQrCLDBWDB5clB5xWOoARMCARYWKwT4OgpYXKwY+SLChECC6A/CNRycIS6jCNIQ5uSCqqCCeCqESTKxCCQiBsCTCRDEQiCCWQigSBYaRwGQU6ESQTCESQTCESQTIbQYCJzZVwKTODjSuaOiArBVzKwDBrKwRJJRlBVzSwDUJQMMWCZKKVzqiNFYIqcD5iudUYZ3IbTzPMbTxMMMRTcXFZDafEJdVFcR5HbT6lKXILaeERQrrMBAAaUw4rBFUDcBFYzkBFcQjGGY4AbPY6LHFbrTFcY4AbgIrFAwIrkEggyGADwrFRQ4reagjiHADsVFeEVFcolEGIoAfPoq1FFcNAFYdQFccBFf4rbGAoAhKQYr/Fa8FFc9UFYYqkgEVFf4r/FYwDDAEZTDFf4r/Ff4rbqorooArBqArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlgorCioroAYIr/Ff4r/FbYDDAEZTDFf4r/FYtAFclVFYUBFc9QFf4rZAgoAgKQor/FbFUFccFFYkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHI")) + }; + + // p4 + if (p >= 4 && p < 7) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVqtW1WoFUgpBFYYABwApggIqDFYmq0BVjFY2loAqjFY1VqDWeFZdUVkAhEhQrDLDcVFQYMHlQrCBhBWVHJpYYDgYPbKx1ACJhYZIwT4OgpYXKwY+SLChECC6A/CNRycIS6jCNIQ5uSCqqCCeCqESTKxCCQiBsCTCRDEQiCCWQigSBYaRwGQU6ESQTCESQTCESQTIbQYCJzZVwKTODjSuaOiArBVzKwDBrKwRJJRlBVzSwDUJQMMWCZKKVzqiNFYIqcD5iudUYZ3IbTzPMbTxMMMRTcXFZDafEJdVFcR5HbT6lKXILaeERQrrMBAAaUw4rBFUDcBFYzkBFcQjGGY4AbPY6LHFbrTFcY4AbgIrFAwIrkEggyGADwrFRQ4reagjiHADsVFeEVFcolEGIoAfPoq1FFcNAFYdQFccBFf4rbGAoAhKQYr/Fa8FFc9UFYYqkgEVFf4r/FYwDDAEZTDFf4r/Ff4rbqorooArBqArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlgorCioroAYIr/Ff4r/FbYDDAEZTDFf4r/FYtAFclVFYUBFc9QFf4rZAgoAgKQor/FbFUFccFFYkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHI")) + }; + + // p7 + if (p >= 7 && p < 10) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVqtW1WoFUgpBFYYABwApggIqDFYmq0BVjFYxZfFQorGLLrWCFZbgbVgtUBQcKLD8VFQYMHlQsDKzoOJFgZYYKwYPLFgWlKzVACJgrCqBWYawgAJcAOlNBhWMCZ8qFYJYUgoqBC6ECFYJqOAApWSS4jCNQQ5uSCqqCCeCqESFQKZUIQSEQNgSYSIYiEQQSyEUCQLDSOAyCnQiSCYQiSCYQiSCZDaDARObKuBSZwcaVzR0QFYKuZWAYNZWCJJKMoKuaWAahKBhiwTJRSudURorBFTgfMVzqjDO5DaeZ5jaeJhhiKbi4rIbT4hLqoriPI7afUpS5BbTwiKFdZgIADSmHFYIqgbgIrGcgIriEYwzHADZ7HRY4rdaYrjHADcBFYoGBFcgkEGQwAeFYqKHFbzUEcQ4AdiorwiorlEogxFAD59FWoorhoArDqArjgIr/FbYwFAEJSDFf4rXgornqgrDFUkAior/Ff4rGAYYAjKYYr/Ff4r/FbdVFdFAFYNQFcsBFf4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/FbdUFcsFFYUVFdADBFf4r/Ff4rbAYYAjKYYr/Ff4rFoArkqorCgIrnqAr/FbIEFAEBSFFf4rYqgrjgorEiormAocVAogAfEooxFFcB9EFdq1DAD9VFYkBFctQFYoGEADokHFcp8FRQoAdag7iFFb4HFioHGADYjHGY4rcPYyLHADbTHcYNQFT4iIFdZgIADKmJqrcgiorIBIIrhMKIAXUpIrBbjzaBFZAKKbS5MJFcKkJbj4fLBYLcdqorKbjzPMbjxKNMhauTURawdJJorBWDShBFZiRBWDQcOHRyuPOhorBWDIbPWDRzQSYKEYIwLLOHgSEXDIJyPQjD2SQjCCQQjSCRCYY/QN4xDRQiyCSQgjdSCqqECLCRWBYyiECISBWCYqgXCLCBWCQSYYEIhxqCeChYFThoQCKypYEIxgPPLB4cKFQZWXDoosIBhhYWcArWDKzYhHABA1EADArNoArcFhgqeWQysgLJxVfcBLWdAH4A5A==")) + }; + + // p10 + if (p >= 10 && p < 20) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVqtW1WoFUgpBFYYABwApggIqDFYmq0BVjFYxZfFQorGLLrWCFZbgbVgtUBQcKLD8VFQYMHlQsDKzoOJFgZYYKwYPLFgZWaoARMLDJWCawgAJcAZWYCZ6FCLCkFFQNQCZ8CFYOoFaZWSLAmAQShWQLAiESQQRtTLAOkQSdUFacK1WloCCSCaAAEFYKaQQSyEC0pvQirZTbomlIh6CYZAZFOQTBxDQhyCYOQhoPQS4bQHaBzaVwKTODjSuaOiArBVzKwDBrKwRJJRlBVzSwDUJQMMWCZKKVzqiNFYIqcD5iudUYZ3IbTzPMbTxMMMRTcXFZDafEJdVFcR5HbT6lKXILaeERQrrMBAAaUw4rBFUDcBFYzkBFcQjGGY4AbPY6LHFbrTFcY4AbgIrFAwIrkEggyGADwrFRQ4reagjiHADsVFeEVFcolEGIoAfPoq1FFcNAFYdQFccBFf4rbGAoAhKQYr/Fa8FFc9UFYYqkgEVFf4r/FYwDDAEZTDFf4r/Ff4rbqorooArBqArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlgorCioroAYIr/Ff4r/FbYDDAEZTDFf4r/FYtAFclVFYUBFc9QFf4rZAgoAgKQor/FbFUFccFFYkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHI")) + }; + + // p20 + if (p >= 20 && p < 30) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVqtW1WoFUgpBFYYABwApggIqDFYmq0BVjFYxZfFQorGLLrWCFZbgbVgtUBQcKLD8VFQYMHlQsDKzoOJFgZYYKwYPLFgZWaoARMLDJWCawgAJcAZWYCZ6FCLCkFFQNQCZ8CFYOoFaZWSLAmAQShWQLAiESQQRtTLAKESFQNUFacKQiSCCoArTgCESQSyEUirZTboyCnQiSCYQiSCYQiSCZQgeAVxwqYQgSwMVwNUFbMKWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbhdVFcTcHbT7cDFY0BbT7cD0ArxgtVoArfgGq1ArHFUDcBFY0VFceqFY1UFcMKFY1VFcmAFYtQFcMCFYsBFcugFYtAFcMAFYsFFcuoFYoqigEqFeEVFcuqFYlUFccKFYlVFc2AFYdQFccCFf4AWgNVoAEGAERSDFf4rXgornqgrDFUkAior/Ff4rGAYYAjKYYr/Ff4r/FbdVFdFAFYNQFcsBFf4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/FbdUFcsFFYUVFdADBFf4r/Ff4rbAYYAjKYYr/Ff4rFoArkqorCgIrnqAr/FbIEFAEBSFFf4rYqgrjgorEiormAocVAogAfEooxFFcB9EFdq1DAD9VFYkBFctQFYoGEADokHFcp8FRQoAdag7iFFb4HFioHGADYjHGY4rcPYyLHADbTHcYNQFT4iIFdZgIADKmJqrcgiorIBIIrhMKIAXUpIrBbjzaBFZAKKbS5MJFcKkJbj4fLBYLcdqorKbjzPMbjxKNMhauTURawdJJorBWDShBFZiRBWDQcOHRyuPOhorBWDIbPWDRzQSYKEYIwLLOHgSEXDIJyPQjD2SQjCCQQjSCRCYY/QN4xDRQiyCSQgjdSCqqECLCRWBYyiECISBWCYqgXCLCBWCQSYYEIhxqCeChYFThoQCKypYEIxgPPLB4cKFQZWXDoosIBhhYWcArWDKzYhHABA1EADArNoArcFhgqeWQysgLJxVfcBLWdAH4A5A=")) + }; + + // p30 + if (p >= 30 && p < 40) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVqtW1WoFUgpBFYYABwApggIqDFYmq0BVjFYxZfFQorGLLrWCFZbgbVgtUBQcKLD8VFQYMHlQsDKzoOJFgZYYKwYPLFgZWaoARMLDJWCawgAJcAZWYCZ6FCLCkFFQNQCZ8CFYOoFaZWSLAmAQShWQLAiESQQRtTLAKESFQNUFacKQiSCCoArTgCESQSyEUirZTboyCnQiSCYQiSCYQiSCZQgeAVxwqYQgSwMVwNUFbMKWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbhdVFcTcHbT7cDFY0BbT7cD0ArxgtVoArfgGq1ArHFUDcBFY0VFceqFY1UFcMKFY1VFcmAFYtQFcMCFYsBFcugFYtAFcMAFYsFFcuoFYoqigEqFeEVFcuqFYlUFccKFYlVFc2AFYdQFccCFf4rbgNVoArjgGq0Ar/FbMFFc+oFYYqkgEqFf4r/FY0VqgrlhWqFf4r/Ff4rdqorowArBqArlgQr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlhQrCioroAYIr/Ff4r/FbcFqorllWoFf4r/FY9AFcmqFYUBFc+gFf4rZgFVqAqjgWqwAr/FbdUFccFawkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHI")) + }; + + // p40 + if (p >= 40 && p < 50) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVqtW1WoFUgpBFYYABwApggIqDFYmq0BVjFYxZfFQorGLLrWCFZbgbVgtUBQcKLD8VFQYMHlQsDKzoOJFgZYYKwYPLFgZWaoARMLDJWCawgAJcAZWYCZ6FCLCkFFQNQCZ8CFYOoFaZWSLAmAQShWQLAiESQQRtTLAKESFQNUFacKQiSCCoArTgCESQSyEUirZTboyCnQiSCYQiSCYQiSCZQgeAVxwqYQgSwMVwNUFbMKWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbhdVFcTcHbT7cDFY0BbT7cD0ArxgtVoArfgGq1ArHFUDcBFY0VFceqFY1UFcMKFY1VFcmAFYtQFcMCFYsBFcugFYtAFcMAFYsFFcuoFYoqigEqFeEVFcuqFYlUFccKFYlVFc2AFYdQFccCFf4rbgNVoArjgGq0Ar/FbMFFc+oFYYqkgEqFf4r/FY0VqgrlhWqFf4r/Ff4rdqorowArBqArlgQr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlhQrCioroAYIr/Ff4r/FbcFqorllWoFf4r/FY9AFcmqFYUBFc+gFf4rZgFVqAqjgWqwAr/FbdUFccKFYkVFcwFDitVFccqFYkFFcuoFeNAFcWqFYkBFcugFYtQFUMCFYsAFcuAFYtUFcMKFY0VFcgHFitVFcMqFY0FFceoFY9AFcGqFY0BqtQFT8C1WgFeMAqtUFb8K1WAFY7cglQrIioriBI8FqtAFb2q1ArJbjzaBFZEBbj7aB0ALIFcLaHbkLaJFYbcd1QrKbjzaKbkDaLbgSwcVwLaJWD6uLFYawaVwIrMbgKwaVwLaKbgawaVwLaLbgawZQQLaLWDiuOWAaEYQQKuMWAiEXKwKuNQjUBQR6EaiqCPQjVVQSATCqtUFSZvB1WACiSEUY4KCQQgjdSCqqECLCRWBYyiECISBWCYqgXCLCBWCQSYYEIhxqCeChYFThoQCKypYEIxgPPLB4cKFQZWXDoosIBhhYWcArWDKzYhHABA1EADArNoArcFhgqeWQysgLJxVfcBLWdAH4A5A")) + }; + + // p50 + if (p >= 50 && p < 60) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal2, + buffer : require("heatshrink").decompress(atob("AH4A/AH4AChWq1WpqtUFUgpBFYYABoApggQqDFYlVqBVjFYxZfFQorGLLrWCFZbgbVguoBQcFLD8qFQYMHiosDKzoOJFgZYYKwYPLFgZWawARMLDJWCawgAJcAZWYCZ6FCLCkKFQOgCZ8BFYNUFaZWSLAlAQShWQLAiESQQRtTLAKESFQOoFacFQiSCCwArTgCESQSyEUlTZTboyCnQiSCYQiSCYQiSCZQgdAVxwqYQgSwMVwOoFbMFWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbheqFcTcHbT7cDFY0CbT7cDqArxhWqwArfgFVqgrHFUDcBFY0qFcdVFY2oFcMFFY2qFclAFYugFcMBFYsCFctQFYuAFcMAFYsKFctUFYoqigEVFeEqFctVFYmoFccFFYmqFc1AFYegFccBFf4rbgWqwArjgFVqAr/FbMKFc9UFYYqkgEVFf4r/FY0q1ArlgtVFf4r/Ff4rd1QrooArB0ArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rb1ArlgorClQroAYIr/Ff4r/FbcK1QrlitUFf4r/FY+AFclVFYUCFc9QFf4rZgGq0AqjgNVoAr/FbeoFccFFYkqFcwFDlWqFccVFYkKFctUFeOAFcVVFYkCFctQFYugFUMBFYsAFctAFYuoFcMFFY0qFcgHFlWqFcMVFY0KFcdUFY+AFcFVFY0C1WgFT8BqtQFeMA1WoFb8FqtAFY7cgiorIlQriBI8K1WAFb1VqgrJbjzaBFZECbj7aBqALIFcLaHbkLaJFYbcdqorKbjzaKbkDaLbgSwcVwLaJWD6uLFYawaVwIrMbgKwaVwLaKbgawaVwLaLbgawZQQLaLWDiuOWAaEYQQKuMWAiEXKwKuNQjSCQQjSCQQjSCRAAIrB1AqTgorBoAUQQiyCSQgjdSbISCRQgZYSKwKCSQghYQKwSCSQghYQKwSCTAAMqFYOoCJsFFQNVFShYEwARMFQRWVLAiFMQIRWWLAosKFQZWXLAosIFQZWYLAzgFawZWbAAMKFgmq1IoEAANUFTQABFZtAFbgsFFYwqeWQorFVjZZJFYhVfcAwrCazoA/AHI")) + }; + + // p60 + if (p >= 60 && p < 70) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal2, + buffer : require("heatshrink").decompress(atob("AH4A/AH4AChWq1WpqtUFUgpBFYYABoApggQqDFYlVqBVjFYxZfFQorGLLrWCFZbgbVguoBQcFLD8qFQYMHiosDKzoOJFgZYYKwYPLFgZWawARMLDJWCawgAJcAZWYCZ6FCLCkKFQOgCZ8BFYNUFaZWSLAlAQShWQLAiESQQRtTLAKESFQOoFacFQiSCCwArTgCESQSyEUlTZTboyCnQiSCYQiSCYQiSCZQgdAVxwqYQgSwMVwOoFbMFWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbheqFcTcHbT7cDFY0CbT7cDqArxhWqwArfgFVqgrHFUDcBFY0qFcdVFY2oFcMFFY2qFclAFYugFcMBFYsCFctQFYuAFcMAFYsKFctUFYoqigEVFeEqFctVFYmoFccFFYmqFc1AFYegFccBFf4rbgWqwArjgFVqAr/FbMKFc9UFYYqkgEVFf4r/FY0q1ArlgtVFf4r/Ff4rd1QrooArB0ArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rb1ArlgorClQroAYIr/Ff4r/FbcK1QrlitUFf4r/FY+AFclVFYUCFc9QFf4rZgGq0AqjgNVoAr/FbeoFccFFYkqFcwFDlWqFccVFYkKFctUFeOAFcVVFYkCFctQFYugFUMBFYsAFctAFYuoFcMFFY0qFcgHFlWqFcMVFY0KFcdUFY+AFcFVFY0C1WgFT8BqtQFeMA1WoFb8FqtAFY7cgiorIlQriBI8K1WAFb1VqgrJbjzaBFZECbj7aBqALIFcLaHbkLaJFYbcdqorKbjzaKbkDaLbgSwcVwLaJWD6uLFYawaVwIrMbgKwaVwLaKbgawaVwLaLbgawZQQLaLWDiuOWAaEYQQKuMWAelNBqCLVxqEC0oRPQS6EC0oSQQSyECFYKEVQSIABFYI/QAAcFFYJDRCgSCmYYjdSCqqYCLCRWBYyiECISBWCYqgXCLCBWCQSYYEIhxqCeChYFThoQCKypYEIxgPPLB4cKFQZWXDoosIBhhYWcArWDKzYhHABA1EADArNoArcFhgqeWQysgLJxVfcBLWdAH4A5A")) + }; + + // p70 + if (p >= 70 && p < 80) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal2, + buffer : require("heatshrink").decompress(atob("AH4A/AH4AChWq1WpqtUFUgpBFYYABoApggQqDFYlVqBVjFYxZfFQorGLLrWCFZbgbVguoBQcFLD8qFQYMHiosDKzoOJFgZYYKwYPLFgZWawARMLDJWCawgAJcAZWYCZ6FCLCkKFQOgCZ8BFYNUFaZWSLAlAQShWQLAiESQQRtTLAKESFQOoFacFQiSCCwArTgCESQSyEUlTZTboyCnQiSCYQiSCYQiSCZQgdAVxwqYQgSwMVwOoFbMFWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbheqFcTcHbT7cDFY0CbT7cDqArxhWqwArfgFVqgrHFUDcBFY0qFcdVFY2oFcMFFY2qFclAFYugFcMBFYsCFctQFYuAFcMAFYsKFctUFYoqigEVFeEqFctVFYmoFccFFYmqFc1AFYegFccBFf4rbgWqwArjgFVqAr/FbMKFc9UFYYqkgEVFf4r/FY0q1ArlgtVFf4r/Ff4rd1QrooArB0ArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rb1ArlgorClQroAYIr/Ff4r/FbcK1QrlitUFf4r/FY+AFclVFYUCFc9QFf4rZAgoAggNVoAr/FbdUFccFFYkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHIA=")) + }; + + // p80 + if (p >= 80 && p < 90) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal2, + buffer : require("heatshrink").decompress(atob("AH4A/AH4AChWq1WpqtUFUgpBFYYABoApggQqDFYlVqBVjFYxZfFQorGLLrWCFZbgbVguoBQcFLD8qFQYMHiosDKzoOJFgZYYKwYPLFgZWawARMLDJWCawgAJcAZWYCZ6FCLCkKFQOgCZ8BFYNUFaZWSLAlAQShWQLAiESQQRtTLAKESFQOoFacFQiSCCwArTgCESQSyEUlTZTboyCnQiSCYQiSCYQiSCZQgdAVxwqYQgSwMVwOoFbMFWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbheqFcTcHbT7cDFY0CbT7cDqArxhWqwArfgFVqgrHFUDcBFY0qFcdVFY2oFcMFFY2qFclAFYugFcMBFYsCFctQFYuAFcMAFYsKFctUFYoqigEVFeEqFctVFYmoFccFFYmqFc1AcIdQFccBFf4rbGAoAhKQYr/Fa8FFc9UFYYqkgEVFf4r/FYwDDAEZTDFf4r/Ff4rbqorooArBqArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlgorCioroAYIr/Ff4r/FbYDDAEZTDFf4r/FYtAFclVFYUBFc9QFf4rZAgoAgKQor/FbFUFccFFYkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHIA=")) + }; + + // p90 + if (p >= 90 && p < 100) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal2, + buffer : require("heatshrink").decompress(atob("AH4A/AH4AChWq1WpqtUFUgpBFYYABoApggQqDFYlVqBVjFYxZfFQorGLLrWCFZbgbVguoBQcFLD8qFQYMHiosDKzoOJFgZYYKwYPLFgZWawARMLDJWCawgAJcAZWYCZ6FCLCkKFQOgCZ8BFYNUFaZWSLAlAQShWQLAiESQQRtTLAKESquq1ArTgqESNgOqwArTIYKERH4KCUQigSBbKTdGCKKCVQiTCCFSyERCALBQQjAPBoArXDZ7ARObKuBSZwcaVzR0QFYKuZWAYNZWCJJKMoKuaWAahKBhiwTJRSudURorBFTgfMVzqjDO5DaeZ5jaeJhhiKbi4rIbT4hLqoriPI7afUpS5BbTwiKFdZgIADSmHFYIqgbgIrGcgIriEYwzHADZ7HRY4rdaYrjHADcBFYoGBFcgkEGQwAeFYqKHFbzUEcQ4AdiorwiorlEogxFAD59FWoorhoArDqArjgIr/FbYwFAEJSDFf4rXgornqgrDFUkAior/Ff4rGAYYAjKYYr/Ff4r/FbdVFdFAFYNQFcsBFf4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/FbdUFcsFFYUVFdADBFf4r/Ff4rbAYYAjKYYr/Ff4rFoArkqorCgIrnqAr/FbIEFAEBSFFf4rYqgrjgorEiormAocVAogAfEooxFFcB9EFdq1DAD9VFYkBFctQFYoGEADokHFcp8FRQoAdag7iFFb4HFioHGADYjHGY4rcPYyLHADbTHcYNQFT4iIFdZgIADKmJqrcgiorIBIIrhMKIAXUpIrBbjzaBFZAKKbS5MJFcKkJbj4fLBYLcdqorKbjzPMbjxKNMhauTURawdJJorBWDShBFZiRBWDQcOHRyuPOhorBWDIbPWDRzQSYKEYIwLLOHgSEXDIJyPQjD2SQjCCQQjSCRCYY/QN4xDRQiyCSQgjdSCqqECLCRWBYyiECISBWCYqgXCLCBWCQSYYEIhxqCeChYFThoQCKypYEIxgPPLB4cKFQZWXDoosIBhhYWcArWDKzYhHABA1EADArNoArcFhgqeWQysgLJxVfcBLWdAH4A5A")) + }; + + // p100 + return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal2, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVAAVUFUgpDAAdAFMEBFQ4ABqBVnLMQqLFjzWEABLgbVgohEGoqyaiofDBihWVHJpYYDgYPbKxz5NLDJGCfBzgDKzA+SLChECC6A/CNRycIS6jCNIQ5uSCqqCCeCqESTKxCCQiBsCTCRDEQiCCWQigSBYaRwGQU6ESQTCESQTCESQTIbQYCJzZVwKTODjSuaOiArBVzKwDBrKwRJJRlBVzSwDUJQMMWCZKKVzqiNFYIqcD5iudUYZ3IbTzPMbTxMMMRTcXFZDafEJdVFcR5HbT6lKXILaeERQrrMBAAaUw4rBFUDcBFYzkBFcQjGGY4AbPY6LHFbrTFcY4AbgIrFAwIrkEggyGADwrFRQ4reagjiHADsVFeEVFcolEGIoAfPoq1FFcNAFYdQFccBFf4rbGAoAhKQYr/Fa8FFc9UFYYqkgEVFf4r/FYwDDAEZTDFf4r/Ff4rbqorooArBqArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlgorCioroAYIr/Ff4r/FbYDDAEZTDFf4r/FYtAFclVFYUBFc9QFf4rZAgoAgKQor/FbFUFccFFYkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHIA=")) + }; +} + +///////////////// IDLE TIMER ///////////////////////////////////// + +function drawIdle() { + let mins = Math.round((getTime() - lastStep) / 60); + g.reset(); + g.setColor(g.theme.bg); + g.fillRect(Bangle.appRect); + g.setColor(g.theme.fg); + setSmallFont20(); + g.setFontAlign(0, 0); + g.drawString('Last step was', w/2, (h/3)); + g.drawString(mins + ' minutes ago', w/2, 20+(h/3)); + dismissBtn.draw(); +} + +/////////////// BUTTON CLASS /////////////////////////////////////////// + +// simple on screen button class +function BUTTON(name,x,y,w,h,c,f,tx) { + this.name = name; + this.x = x; + this.y = y; + this.w = w; + this.h = h; + this.color = c; + this.callback = f; + this.text = tx; +} + +// if pressed the callback +BUTTON.prototype.check = function(x,y) { + //console.log(this.name + ":check() x=" + x + " y=" + y +"\n"); + + if (x>= this.x && x<= (this.x + this.w) && y>= this.y && y<= (this.y + this.h)) { + log_debug(this.name + ":callback\n"); + this.callback(); + return true; + } + return false; +}; + +BUTTON.prototype.draw = function() { + g.setColor(this.color); + g.fillRect(this.x, this.y, this.x + this.w, this.y + this.h); + g.setColor("#000"); // the icons and boxes are drawn black + setSmallFont20(); + g.setFontAlign(0, 0); + g.drawString(this.text, (this.x + this.w/2), (this.y + this.h/2)); + g.drawRect(this.x, this.y, (this.x + this.w), (this.y + this.h)); +}; + +function dismissPrompt() { + idle = false; + warned = false; + lastStep = getTime(); + Bangle.buzz(100); + draw(); +} + +var dismissBtn = new BUTTON("big",0, 3*h/4 ,w, h/4, "#0ff", dismissPrompt, "Dismiss"); + +Bangle.on('touch', function(button, xy) { + var x = xy.x; + var y = xy.y; + // adjust for outside the dimension of the screen + // http://forum.espruino.com/conversations/371867/#comment16406025 + if (y > h) y = h; + if (y < 0) y = 0; + if (x > w) x = w; + if (x < 0) x = 0; + + if (idle && dismissBtn.check(x, y)) return; +}); + +// if we get a step then we are not idle +Bangle.on('step', s => { + lastStep = getTime(); + // redraw if we had been idle + if (idle == true) { + dismissPrompt(); + } + idle = false; + warned = 0; + + if (infoMode == "ID_STEP") drawSteps(); +}); + +function checkIdle() { + log_debug("checkIdle()"); + if (!settings.idle_check) { + idle = false; + warned = false; + return; + } + + let hour = (new Date()).getHours(); + let active = (hour >= 9 && hour < 21); + //let active = true; + let dur = getTime() - lastStep; + + if (active && dur > IDLE_MINUTES * 60) { + drawIdle(); + if (warned++ < 3) { + buzzer(warned); + log_debug("checkIdle: warned=" + warned); + Bangle.setLocked(false); + } + idle = true; + } else { + idle = false; + warned = 0; + } +} + +// timeout for multi-buzzer +var buzzTimeout; + +// n buzzes +function buzzer(n) { + log_debug("buzzer n=" + n); + + if (n-- < 1) return; + Bangle.buzz(250); + + if (buzzTimeout) clearTimeout(buzzTimeout); + buzzTimeout = setTimeout(function() { + buzzTimeout = undefined; + buzzer(n); + }, 500); +} + +/////////////////////////////////////////////////////////////////////////////// + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + checkIdle(); + draw(); + }, 60000 - (Date.now() % 60000)); +} + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +Bangle.setUI("clockupdown", btn=> { + if (btn<0) prevInfo(); + if (btn>0) nextInfo(); + draw(); +}); + +loadSettings(); +loadLocation(); + +g.clear(); +Bangle.loadWidgets(); +/* + * we are not drawing the widgets as we are taking over the whole screen + * so we will blank out the draw() functions of each widget and change the + * area to the top bar doesn't get cleared. + */ +for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} +draw(); diff --git a/apps/daisy/app.png b/apps/daisy/app.png new file mode 100644 index 000000000..89252e5df Binary files /dev/null and b/apps/daisy/app.png differ diff --git a/apps/daisy/metadata.json b/apps/daisy/metadata.json new file mode 100644 index 000000000..0bad50151 --- /dev/null +++ b/apps/daisy/metadata.json @@ -0,0 +1,18 @@ +{ "id": "daisy", + "name": "Daisy", + "version":"0.09", + "dependencies": {"mylocation":"app"}, + "description": "A beautiful digital clock with large ring guage, idle timer and a cyclic information line that includes, day, date, steps, battery, sunrise and sunset times", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS2"], + "screenshots": [{"url":"screenshot_daisy3.png"}], + "readme": "README.md", + "storage": [ + {"name":"daisy.app.js","url":"app.js"}, + {"name":"daisy.img","url":"app-icon.js","evaluate":true}, + {"name":"daisy.settings.js","url":"settings.js"} + ], + "data": [{"name":"daisy.json"}] +} diff --git a/apps/daisy/screenshot_daisy1.png b/apps/daisy/screenshot_daisy1.png new file mode 100644 index 000000000..afef3a424 Binary files /dev/null and b/apps/daisy/screenshot_daisy1.png differ diff --git a/apps/daisy/screenshot_daisy2.png b/apps/daisy/screenshot_daisy2.png new file mode 100644 index 000000000..3636f3766 Binary files /dev/null and b/apps/daisy/screenshot_daisy2.png differ diff --git a/apps/daisy/screenshot_daisy3.png b/apps/daisy/screenshot_daisy3.png new file mode 100644 index 000000000..b5d55a037 Binary files /dev/null and b/apps/daisy/screenshot_daisy3.png differ diff --git a/apps/daisy/settings.js b/apps/daisy/settings.js new file mode 100644 index 000000000..6397a81f4 --- /dev/null +++ b/apps/daisy/settings.js @@ -0,0 +1,50 @@ +(function(back) { + const SETTINGS_FILE = "daisy.json"; + + // initialize with default settings... + let s = {'gy' : '#020', + 'fg' : '#0f0', + 'color': 'Green', + 'check_idle' : true}; + + // ...and overwrite them with any saved values + // This way saved values are preserved if a new version adds more settings + const storage = require('Storage') + let settings = storage.readJSON(SETTINGS_FILE, 1) || s; + const saved = settings || {} + for (const key in saved) { + s[key] = saved[key] + } + + function save() { + settings = s + storage.write(SETTINGS_FILE, settings) + } + + var color_options = ['Green','Orange','Cyan','Purple','Red','Blue']; + var fg_code = ['#0f0','#ff0','#0ff','#f0f','#f00','#00f']; + var gy_code = ['#020','#220','#022','#202','#200','#002']; + + E.showMenu({ + '': { 'title': 'Daisy Clock' }, + '< Back': back, + 'Colour': { + value: 0 | color_options.indexOf(s.color), + min: 0, max: 5, + format: v => color_options[v], + onchange: v => { + s.color = color_options[v]; + s.fg = fg_code[v]; + s.gy = gy_code[v]; + save(); + }, + }, + 'Idle Warning': { + value: !!s.idle_check, + onchange: v => { + s.idle_check = v; + save(); + }, + } + }); +}) diff --git a/apps/dane_tcr/ChangeLog b/apps/dane_tcr/ChangeLog index 4f6fe2edc..69424b1f4 100644 --- a/apps/dane_tcr/ChangeLog +++ b/apps/dane_tcr/ChangeLog @@ -4,4 +4,5 @@ 0.04: Move code to Arwes Module 0.05: Add icon 0.06: remove app image as it is unused -0.07: Bump version number for change to apps.json causing 404 on upload \ No newline at end of file +0.07: Bump version number for change to apps.json causing 404 on upload +0.08: Use default Bangle formatter for booleans diff --git a/apps/dane_tcr/app.js b/apps/dane_tcr/app.js index aa25379d3..ce75c55cb 100644 --- a/apps/dane_tcr/app.js +++ b/apps/dane_tcr/app.js @@ -244,7 +244,7 @@ function run(){ Bangle.setLCDMode(); g.clear(); g.flip(); - E.showMessage("Loading..."); + E.showMessage(/*LANG*/"Loading..."); load(app.src); } diff --git a/apps/dane_tcr/metadata.json b/apps/dane_tcr/metadata.json index 817d0c59b..5527c846d 100644 --- a/apps/dane_tcr/metadata.json +++ b/apps/dane_tcr/metadata.json @@ -2,7 +2,7 @@ "id": "dane_tcr", "name": "DANE Touch Launcher", "shortName": "DANE Toucher", - "version": "0.07", + "version": "0.08", "description": "Touch enable left to right launcher in the style of the DANE Watchface", "icon": "app.png", "type": "launch", diff --git a/apps/dane_tcr/settings.js b/apps/dane_tcr/settings.js index 9d28d1b30..46988ec26 100644 --- a/apps/dane_tcr/settings.js +++ b/apps/dane_tcr/settings.js @@ -41,7 +41,6 @@ }, "Animation" : { value : settings.animation, - format : v => v?"On":"Off", onchange : saveChange('animation') }, "Frame rate" : { @@ -51,7 +50,6 @@ }, "Debug" : { value : settings.debug, - format : v => v?"On":"Off", onchange : saveChange('debug') }, '< Back': back diff --git a/apps/deko/Building_Typeface.ttf b/apps/deko/Building_Typeface.ttf new file mode 100644 index 000000000..d5a3933ab Binary files /dev/null and b/apps/deko/Building_Typeface.ttf differ diff --git a/apps/deko/ChangeLog b/apps/deko/ChangeLog new file mode 100644 index 000000000..9db0e26c5 --- /dev/null +++ b/apps/deko/ChangeLog @@ -0,0 +1 @@ +0.01: first release diff --git a/apps/deko/README.md b/apps/deko/README.md new file mode 100644 index 000000000..91e83bd23 --- /dev/null +++ b/apps/deko/README.md @@ -0,0 +1,10 @@ +# Deko Clock + +A simple clock with an Art Deko font + +The font was obtained from https://dafonttop.com/building.font and is free for personal use + + +![](screenshot.png) + +Written by: [Hugh Barney](https://github.com/hughbarney) For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/) diff --git a/apps/deko/app-icon.js b/apps/deko/app-icon.js new file mode 100644 index 000000000..06f93e2ef --- /dev/null +++ b/apps/deko/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwIdah/wAof//4ECgYFB4AFBg4FB8AFBj/wh/4AoM/wEB/gFBvwCEBAU/AQP4gfAj8AgPwAoMPwED8AFBg/AAYIBDA4ngg4TB4EBApkPKgJSBJQIFTMgIFCJIIFDKoIFEvgFBGoMAnw7DP4IFEh+BAoItBg+DNIQwBMIaeCKoKxCPoIzCEgKVHUIqtFXIrFFaIrdFdIwAV")) diff --git a/apps/deko/app.js b/apps/deko/app.js new file mode 100644 index 000000000..8ae2c1d31 --- /dev/null +++ b/apps/deko/app.js @@ -0,0 +1,64 @@ +Graphics.prototype.setFontBuildingTypeface = function(scale) { + // Actual height 100 (102 - 3) + this.setFontCustom( + atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf+AAAAAAAAAAAAAAAAAAAf+AAAAAAAAAAAAAAAAAAAf+AAAAAAAAAAAAAAAAAAAf+AAAAAAAAAAAAAAAAAAAf+AAAAAAAAAAAAAAAAAAAf+AAAAAAAAAAAAAAAAAAAf+AAAAAAAAAAAAAAAAAAAf+AAAAAAAAAAAAAAAAAAAf+AAAAAAAAAAAAAAAAAAAf+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgAAAAAAAAAAAAAAAAAAH/gAAAAAAAAAAAAAAAAAH//gAAAAAAAAAAAAAAAAD///gAAAAAAAAAAAAAAAD////gAAAAAAAAAAAAAAD/////gAAAAAAAAAAAAAB//////gAAAAAAAAAAAAB///////gAAAAAAAAAAAB////////gAAAAAAAAAAA////////4AAAAAAAAAAA////////4AAAAAAAAAAAf///////8AAAAAAAAAAAf///////8AAAAAAAAAAAf///////8AAAAAAAAAAAP///////+AAAAAAAAAAAP///////+AAAAAAAAAAAP///////+AAAAAAAAAAAH////////AAAAAAAAAAAAH///////AAAAAAAAAAAAAH//////AAAAAAAAAAAAAAH/////gAAAAAAAAAAAAAAH////gAAAAAAAAAAAAAAAH///wAAAAAAAAAAAAAAAAH//wAAAAAAAAAAAAAAAAAH/wAAAAAAAAAAAAAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////AAAAAAAD/////////////wAAAAAAP/////////////4AAAAAAP/////////////8AAAAAAf/////////////+AAAAAAf/////////////+AAAAAA///////////////AAAAAA///////////////AAAAAA///////////////AAAAAA/4AAAAAAAAAAAH/AAAAAA/4AAAAAAAAAAAH/AAAAAA/4AAAAAAAAAAAH/AAAAAA/4AAAAAAAAAAAH/AAAAAA/4AAAAAAAAAAAH/AAAAAA/4AAAAAAAAAAAH/AAAAAA/4AAAAAAAAAAAH/AAAAAA/4AAAAAAAAAAAH/AAAAAA/4AAAAAAAAAAAH/AAAAAA/4AAAAAAAAAAAH/AAAAAA/4AAAAAAAAAAAH/AAAAAA/4AAAAAAAAAAAH/AAAAAA///////////////AAAAAA///////////////AAAAAA///////////////AAAAAA///////////////AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAP/////////////8AAAAAAH/////////////4AAAAAAD/////////////wAAAAAAA/////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP4AAAAAAAAAAAAAAAAAAAf4AAAAAAAAAAAAAAAAAAA/4AAAAAAAAAAAAAAAAAAB/4AAAAAAAAAAAAAAAAAAD/4AAAAAAAAAAAAAAAAAAH/4AAAAAAAAAAAAAAAAAAf/4AAAAAAAAAAAAAAAAAAf/////////////+AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/wAAf///////+AAAAAAB//wAB////////+AAAAAAH//wAD////////+AAAAAAH//wAH////////+AAAAAAP//wAP////////+AAAAAAf//wAP////////+AAAAAAf//wAP////////+AAAAAAf//wAf////////+AAAAAAf//wAf////////+AAAAAAf8AAAf8AAAAAAP+AAAAAAf8AAAf8AAAAAAP+AAAAAAf8AAAf8AAAAAAP+AAAAAAf8AAAf8AAAAAAP+AAAAAAf8AAAf8AAAAAAP+AAAAAAf8AAAf8AAAAAAP+AAAAAAf8AAAf8AAAAAAP+AAAAAAf8AAAf8AAAAAAP+AAAAAAf8AAAf8AAAAAAP+AAAAAAf8AAAf8AAAAAAP+AAAAAAf8AAAf8AAAAAAP+AAAAAAf8AAAf8AAAAAAP+AAAAAAf/////8AAAA///+AAAAAAf/////8AAAA///+AAAAAAf/////8AAAA///+AAAAAAf/////8AAAA///+AAAAAAP/////8AAAA///+AAAAAAH/////8AAAA///+AAAAAAH/////8AAAA///+AAAAAAB/////8AAAA///+AAAAAAAf////8AAAA///+AAAAAAAAAAAAAAAAA///+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//gAAAAAAAf//+AAAAAAf//wAAAAAAA///+AAAAAAf//wAAAAAAA///+AAAAAAf//wAAAAAAA///+AAAAAAf//wAAAAAAA///+AAAAAAf//wAAAAAAA///+AAAAAAf//wAAAAAAA///+AAAAAAf//wAAAAAAA///+AAAAAAf//wAAAAAAA///+AAAAAAf//wAAAAAAA///+AAAAAAf8AAAAAAAAAAAP+AAAAAAf8AAAAAAAAAAAP+AAAAAAf8AAAAAAAAAAAP+AAAAAAf8AAAAEAAAAAAP+AAAAAAf8AAAB8AAAAAAP+AAAAAAf8AAAP8AAAAAAP+AAAAAAf8AAD/8AAAAAAP+AAAAAAf8AA//8AAAAAAP+AAAAAAf8AP//8AAAAAAP+AAAAAAf8B///8AAAAAAP+AAAAAAf8f///8AAAAAAP+AAAAAAf/////8AAAAAAP+AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAf///8P////////+AAAAAAf///gH////////+AAAAAAf//4AD////////+AAAAAAf/+AAB////////+AAAAAAf/gAAA////////+AAAAAAfwAAAAD///////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/gAAAAAAAAAAAAAAAAAB//gAAAAAAAAAAAAAAAAAH//gAAAAAAAAAAAAAAAAA///gAAAAAAAAAAAAAAAAH///gAAAAAAAAAAAAAAAAf///gAAAAAAAAAAAAAAAD////gAAAAAAAAAAAAAAAf////gAAAAAAAAAAAAAAB/////gAAAAAAAAAAAAAAP///z/gAAAAAAAAAAAAAB///+D/gAAAAAAAAAAAAAH///wD/gAAAAAAAAAAAAA///+AD/gAAAAAAAAAAAAH///wAD/gAAAAAAAAAAAAf//+AAD/gAAAAAAAAAAAD///wAAD/gAAAAAAAAAAAf///AAAD/gAAAAAAAAAAB///4AAAD/gAAAAAAAAAAP///AAAAD/gAAAAAAAAAB///4AAAAD/gAAAAAAAAAH///AAAAAD/gAAAAAAAAAf/////////////+AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAAAAAAAAAAD/gAAAAAAAAAAAAAAAAAAD/gAAAAAAAAAAAAAAAAAAD/gAAAAAAAAAAAAAAAAAAD/gAAAAAAAAAAAAAAAAAAD/gAAAAAAAAAAAAAAAAAAD/gAAAAAAAAAAAAAAAAAAD/gAAAAAAAAAAAAAAAAAAD/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//+AAAAAAAf/////8AAAA///wAAAAAAf/////8AAAA///4AAAAAAf/////8AAAA///8AAAAAAf/////8AAAA///+AAAAAAf/////8AAAA///+AAAAAAf/////8AAAA///+AAAAAAf/////8AAAA////AAAAAAf/////8AAAA////AAAAAAf/////8AAAA////AAAAAAf8AAAf8AAAAAAH/AAAAAAf8AAAf8AAAAAAH/AAAAAAf8AAAf8AAAAAAH/AAAAAAf8AAAf8AAAAAAH/AAAAAAf8AAAf8AAAAAAH/AAAAAAf8AAAf8AAAAAAH/AAAAAAf8AAAf8AAAAAAH/AAAAAAf8AAAf8AAAAAAH/AAAAAAf8AAAf8AAAAAAH/AAAAAAf8AAAf8AAAAAAH/AAAAAAf8AAAf8AAAAAAH/AAAAAAf//wAf8AAAAAAH/AAAAAAf//wAf/////////AAAAAAf//wAf/////////AAAAAAf//wAf/////////AAAAAAf//wAf////////+AAAAAAf//wAP////////+AAAAAAf//wAP////////8AAAAAAf//wAH////////8AAAAAAf//wAD////////wAAAAAAf//wAA////////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////AAAAAAAD/////////////wAAAAAAP/////////////4AAAAAAP/////////////8AAAAAAf/////////////+AAAAAAf/////////////+AAAAAA///////////////AAAAAA///////////////AAAAAA///////////////AAAAAA/4AAAf8AAAAAAH/AAAAAA/4AAAf8AAAAAAH/AAAAAA/4AAAf8AAAAAAH/AAAAAA/4AAAf8AAAAAAH/AAAAAA/4AAAf8AAAAAAH/AAAAAA/4AAAf8AAAAAAH/AAAAAA/4AAAf8AAAAAAH/AAAAAA/4AAAf8AAAAAAH/AAAAAA/4AAAf8AAAAAAH/AAAAAA/4AAAf8AAAAAAH/AAAAAA/4AAAf8AAAAAAH/AAAAAA///wAf8AAAAAAH/AAAAAA///wAf/////////AAAAAA///wAf/////////AAAAAA///wAf/////////AAAAAAf//wAf/////////AAAAAAf//wAP////////+AAAAAAP//wAP////////+AAAAAAH//wAH////////8AAAAAAB//wAD////////4AAAAAAAf/wAB////////wAAAAAAAAAAAAf///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//wAAAAAAAAAAAAAAAAAf//wAAAAAAAAAAAAAAAAAf//wAAAAAAAAAAeAAAAAAf//wAAAAAAAAAP+AAAAAAf//wAAAAAAAAD/+AAAAAAf//wAAAAAAAA//+AAAAAAf//wAAAAAAAf//+AAAAAAf//wAAAAAAH///+AAAAAAf//wAAAAAD////+AAAAAAf8AAAAAAA/////+AAAAAAf8AAAAAAP/////+AAAAAAf8AAAAAH//////8AAAAAAf8AAAAB///////AAAAAAAf8AAAAf//////wAAAAAAAf8AAAP//////4AAAAAAAAf8AAD//////+AAAAAAAAAf8AB///////gAAAAAAAAAf8Af//////4AAAAAAAAAAf8H//////+AAAAAAAAAAAf////////gAAAAAAAAAAAf///////wAAAAAAAAAAAAf//////8AAAAAAAAAAAAAf//////AAAAAAAAAAAAAAf/////wAAAAAAAAAAAAAAf////8AAAAAAAAAAAAAAAf////AAAAAAAAAAAAAAAAf///wAAAAAAAAAAAAAAAAf//4AAAAAAAAAAAAAAAAAf/+AAAAAAAAAAAAAAAAAAf/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///gf//////+AAAAAAAB////4////////wAAAAAAH////9////////4AAAAAAP/////////////8AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAf/////////////+AAAAAA///////////////AAAAAA///////////////AAAAAA//////4AAAAAAH/AAAAAA/4AAA/4AAAAAAH/AAAAAA/4AAA/4AAAAAAH/AAAAAA/4AAA/4AAAAAAH/AAAAAA/4AAA/4AAAAAAH/AAAAAA/4AAA/4AAAAAAH/AAAAAA/4AAA/4AAAAAAH/AAAAAA/4AAA/4AAAAAAH/AAAAAA/4AAA/4AAAAAAH/AAAAAA/4AAA/4AAAAAAH/AAAAAA/4AAA/4AAAAAAH/AAAAAA/4AAA/4AAAAAAH/AAAAAA///////////////AAAAAA///////////////AAAAAA///////////////AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAP/////////////+AAAAAAP/////////////8AAAAAAH////9////////4AAAAAAB////4////////gAAAAAAAH///gP//////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///////+AAAAAAAAAAAAD////////gAD//AAAAAAAP////////wAD//wAAAAAAP////////4AD//4AAAAAAf////////8AD//8AAAAAAf////////8AD//+AAAAAA/////////+AD//+AAAAAA/////////+AD///AAAAAA/////////+AD///AAAAAA/4AAAAAAP+AD///AAAAAA/4AAAAAAP+AAAH/AAAAAA/4AAAAAAP+AAAH/AAAAAA/4AAAAAAP+AAAH/AAAAAA/4AAAAAAP+AAAH/AAAAAA/4AAAAAAP+AAAH/AAAAAA/4AAAAAAP+AAAH/AAAAAA/4AAAAAAP+AAAH/AAAAAA/4AAAAAAP+AAAH/AAAAAA/4AAAAAAP+AAAH/AAAAAA/4AAAAAAP+AAAH/AAAAAA/4AAAAAAP+AAAH/AAAAAA/4AAAAAAP+AAAH/AAAAAA///////////////AAAAAA///////////////AAAAAA///////////////AAAAAAf/////////////+AAAAAAf/////////////+AAAAAAP/////////////8AAAAAAP/////////////4AAAAAAD/////////////wAAAAAAA/////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAAAf+AAAAAAAAAAAAAD/wAAAf+AAAAAAAAAAAAAD/wAAAf+AAAAAAAAAAAAAD/wAAAf+AAAAAAAAAAAAAD/wAAAf+AAAAAAAAAAAAAD/wAAAf+AAAAAAAAAAAAAD/wAAAf+AAAAAAAAAAAAAD/wAAAf+AAAAAAAAAAAAAD/wAAAf+AAAAAAAAAAAAAD/wAAAf+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='), + 46, + atob("FCYpGigoKigoJykoFA=="), + 126+(scale<<8)+(1<<16) + ); + return this; +}; + + + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + + +function draw() { + var x = g.getWidth()/2; + var y = g.getHeight()/2; + g.reset(); + var date = new Date(); + var timeStr = require("locale").time(date,1); + var dateStr = require("locale").date(date).toUpperCase(); + // draw time + g.setFontAlign(0,0).setFont("BuildingTypeface"); + g.clearRect(0, 24, g.getWidth(), y+35); // clear the background + g.drawString(timeStr,x,y); + // draw date + y += 60; + g.setFontAlign(0,0).setFont("6x8",2); + g.clearRect(0,y-8,g.getWidth(),y+8); // clear the background + g.drawString(dateStr,x,y); + // queue draw in one minute + queueDraw(); +} + +// Clear the screen once, at startup +g.clear(); +// draw immediately at first, queue update +draw(); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); +// Show launcher when middle button pressed +Bangle.setUI("clock"); +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/deko/app.png b/apps/deko/app.png new file mode 100644 index 000000000..6f11e7019 Binary files /dev/null and b/apps/deko/app.png differ diff --git a/apps/deko/metadata.json b/apps/deko/metadata.json new file mode 100644 index 000000000..9bdd15429 --- /dev/null +++ b/apps/deko/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "deko", + "name": "Deko Clock", + "version": "0.01", + "description": "Clock with Art Deko font", + "readme": "README.md", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"deko.app.js","url":"app.js"}, + {"name":"deko.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/deko/screenshot.png b/apps/deko/screenshot.png new file mode 100644 index 000000000..91ce2ea38 Binary files /dev/null and b/apps/deko/screenshot.png differ diff --git a/apps/demoapp/metadata.json b/apps/demoapp/metadata.json index df6554ef5..2fb30f718 100644 --- a/apps/demoapp/metadata.json +++ b/apps/demoapp/metadata.json @@ -12,6 +12,5 @@ "storage": [ {"name":"demoapp.app.js","url":"app.js"}, {"name":"demoapp.img","url":"app-icon.js","evaluate":true} - ], - "sortorder": -9 + ] } diff --git a/apps/diceroll/ChangeLog b/apps/diceroll/ChangeLog new file mode 100644 index 000000000..89dff4011 --- /dev/null +++ b/apps/diceroll/ChangeLog @@ -0,0 +1 @@ +0.01: App created \ No newline at end of file diff --git a/apps/diceroll/app-icon.js b/apps/diceroll/app-icon.js new file mode 100644 index 000000000..4d6e7da16 --- /dev/null +++ b/apps/diceroll/app-icon.js @@ -0,0 +1 @@ +E.toArrayBuffer(atob("ICABAAAAAAAAAAAAAAAAAAHAAAAP8AAAfn4AA/APwA+DwfAPg8HwD+AH8Az4HzAMPnwwDAfgMAwBgDAMCYAwDA2YMAwhmDAMIZAwDCGDMA2BgzAMgYAwDAGAMA8BgPADwYPAAPGPgAB9ngAAH/gAAAfgAAABgAAAAAAAAAAAAAAAAAA=")) diff --git a/apps/diceroll/app.js b/apps/diceroll/app.js new file mode 100644 index 000000000..d514ce92f --- /dev/null +++ b/apps/diceroll/app.js @@ -0,0 +1,108 @@ +var init_message = true; +var acc_data; +var die_roll = 1; +var selected_die = 0; +var roll = 0; +const dices = [4, 6, 10, 12, 20]; + +g.setFontAlign(0,0); + +Bangle.on('touch', function(button, xy) { + // Change die if not rolling + if(roll < 1){ + if(selected_die <= 3){ + selected_die++; + }else{ + selected_die = 0; + } + } + //Disable initial message + init_message = false; +}); + +function rect(){ + x1 = g.getWidth()/2 - 35; + x2 = g.getWidth()/2 + 35; + y1 = g.getHeight()/2 - 35; + y2 = g.getHeight()/2 + 35; + g.drawRect(x1, y1, x2, y2); +} + +function pentagon(){ + x1 = g.getWidth()/2; + y1 = g.getHeight()/2 - 50; + x2 = g.getWidth()/2 - 50; + y2 = g.getHeight()/2 - 10; + x3 = g.getWidth()/2 - 30; + y3 = g.getHeight()/2 + 30; + x4 = g.getWidth()/2 + 30; + y4 = g.getHeight()/2 + 30; + x5 = g.getWidth()/2 + 50; + y5 = g.getHeight()/2 - 10; + g.drawPoly([x1, y1, x2, y2, x3, y3, x4, y4, x5, y5], true); +} + +function triangle(){ + x1 = g.getWidth()/2; + y1 = g.getHeight()/2 - 57; + x2 = g.getWidth()/2 - 50; + y2 = g.getHeight()/2 + 23; + x3 = g.getWidth()/2 + 50; + y3 = g.getHeight()/2 + 23; + g.drawPoly([x1, y1, x2, y2, x3, y3], true); +} + +function drawDie(variant) { + if(variant == 1){ + //Rect, 6 + rect(); + }else if(variant == 3){ + //Pentagon, 12 + pentagon(); + }else{ + //Triangle, 4, 10, 20 + triangle(); + } +} + +function initMessage(){ + g.setFont("6x8", 2); + g.drawString("Dice-n-Roll", g.getWidth()/2, 20); + g.drawString("Shake to roll", g.getWidth()/2, 60); + g.drawString("Tap to change", g.getWidth()/2, 80); + g.drawString("Tap to start", g.getWidth()/2, 150); +} + +function rollDie(){ + acc_data = Bangle.getAccel(); + if(acc_data.diff > 0.3){ + roll = 3; + } + //Mange the die "roll" by chaning the number a few times + if(roll > 0){ + g.drawString("Rolling!", g.getWidth()/2, 150); + die_roll = Math.abs(E.hwRand()) % dices[selected_die] + 1; + roll--; + } + //Draw dice graphics + drawDie(selected_die); + //Draw dice number + g.setFontAlign(0,0); + g.setFont("Vector", 45); + g.drawString(die_roll, g.getWidth()/2, g.getHeight()/2); + //Draw selected die in right corner + g.setFont("6x8", 2); + g.drawString(dices[selected_die], g.getWidth()-15, 15); +} + +function main() { + g.clear(); + if(init_message){ + initMessage(); + }else{ + rollDie(); + } + Bangle.setLCDPower(1); +} + +var interval = setInterval(main, 300); \ No newline at end of file diff --git a/apps/diceroll/app.png b/apps/diceroll/app.png new file mode 100644 index 000000000..b695b7080 Binary files /dev/null and b/apps/diceroll/app.png differ diff --git a/apps/diceroll/diceroll_screenshot.png b/apps/diceroll/diceroll_screenshot.png new file mode 100644 index 000000000..71024edbb Binary files /dev/null and b/apps/diceroll/diceroll_screenshot.png differ diff --git a/apps/diceroll/metadata.json b/apps/diceroll/metadata.json new file mode 100644 index 000000000..81a2f8bfd --- /dev/null +++ b/apps/diceroll/metadata.json @@ -0,0 +1,14 @@ +{ "id": "diceroll", + "name": "Dice-n-Roll", + "shortName":"Dice-n-Roll", + "icon": "app.png", + "version":"0.01", + "description": "A dice app with a few different dice.", + "screenshots": [{"url":"diceroll_screenshot.png"}], + "tags": "game", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"diceroll.app.js","url":"app.js"}, + {"name":"diceroll.img","url":"app-icon.js","evaluate":true} + ] + } \ No newline at end of file diff --git a/apps/dinoClock/README.md b/apps/dinoClock/README.md new file mode 100644 index 000000000..7568731d9 --- /dev/null +++ b/apps/dinoClock/README.md @@ -0,0 +1,17 @@ +# dinoClock + +Watchface with T-Rex Dinosaur from Chrome. +It displays current temperature and weather. + +**Warning**: Element position and styles can change in the future. + +Based on the [Weather Clock](https://github.com/espruino/BangleApps/tree/master/apps/weatherClock). + +# Requirements + +**This clock requires Gadgetbridge and the weather app in order to get weather data!** + +See the [Bangle.js Gadgetbridge documentation](https://www.espruino.com/Gadgetbridge) for instructions on setting up Gadgetbridge and weather. + +![Screenshot](screens/screen1.png) + diff --git a/apps/dinoClock/app.js b/apps/dinoClock/app.js new file mode 100644 index 000000000..82192d234 --- /dev/null +++ b/apps/dinoClock/app.js @@ -0,0 +1,219 @@ +const storage = require('Storage'); +const locale = require("locale"); + + + + +// add modifiied 4x5 numeric font +(function(graphics) { + graphics.prototype.setFont4x5NumPretty = function() { + this.setFontCustom(atob("IQAQDJgH4/An4QXr0Fa/BwnwdrcH63BCHwfr8Ha/"),45,atob("AwIEBAQEBAQEBAQEBA=="),5); + }; +})(Graphics); + +// add font for days of the week +(function(graphics) { + graphics.prototype.setFontDoW = function() { + this.setFontCustom(atob("///////ADgB//////+AHAD//////gAAAH//////4D8B+A///////4AcAOAH//////4AcAOAAAAAB//////wA4AcAP//////wAAAAAAAA//////4AcAP//////wA4Af//////gAAAH//////5z85+c/OfnOAA4AcAOAH//////4AcAOAAAAAB//////wcAOAHB//////wAAAAAAAA///////ODnBzg5wc4AAAAD//////84OcH//8/+fAAAAAAAAAAAAA/z/5/8/OfnPz/5/8/wAAAD//////84OcH//////AAAAAAAAAAAAA/z/5/8/OfnPz/5/8/wAAAD//////gBwA///////AAAAAAAAAAAAA"),48,24,13); + }; +})(Graphics); + + +const SUN = 1; +const PART_SUN = 2; +const CLOUD = 3; +const SNOW = 4; +const RAIN = 5; +const STORM = 6; +const ERR = 7; + +/** +Choose weather icon based on weather const +Weather icons from https://icons8.com/icon/set/weather/ios-glyphs +Error icon from https://icons8.com/icon/set/error-cloud/ios-glyphs +**/ +function weatherIcon(weather) { + switch (weather) { + case SUN: + return atob("Hh4BAAAAAAAMAAAAMAAAAMAAAAMAABgMBgBwADgA4AHAAY/GAAB/gAAD/wAAH/4AAP/8AAP/8AfP/8+fP/8+AP/8AAP/8AAH/4AAD/wAAB/gAAY/GAA4AHABwADgBgMBgAAMAAAAMAAAAMAAAAMAAAAAAAA="); + case PART_SUN: + return atob("Hh4BAAAAAAAAAAAMAAAAMAAAEMIAAOAcAAGAYAAAeAAAA/AAAB/gAA5/gAA5/g+AB+D/gA4H/wAR//wGD//4OD//4EH//4AH//4Af//+Af//+A////A////A////A///+Af//+AH//4AAAAAAAAAAAAAAAA="); + case CLOUD: + return atob("Hh4BAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAf+AAA//AAB//gAf//gB///wB///wD///wD///wP///8f///+f///+////////////////////f///+f///+P///8D///wAAAAAAAAAAAAAAAAAAAAAAAAAA="); + case SNOW: + return atob("Hh4BAAAAAAAAAAAAAAAAAHwAAAf8AAA/+AAH/+AAf//AAf8/AA/8/AB/gHgH/wP4H/wP4P/gH8P/8/8P/8/8P///4H///4B///gAAAAAAMAAAAMAAAB/gGAA/AfgA/AfgB/gfgAMAfgAMAGAAAAAAAAAAAA="); + case RAIN: + return atob("Hh4BAAAAAAAAAAAAAAAAAHwAAAf8AAA/+AAH/+AAf//AAf//AA///AB///gH///4H///4P///8P///8P///8P///4H///4B///gAAAAAAAAAABgBgABgBgABhhhgABgBgABgBgAAAAAAAAAAAAAAAAAAAAA="); + case STORM: + return atob("Hh4BAAAAAAAAAAAAAAAAAHwAAAf8AAA/+AAH/+AAf//AAf//AA///AB///gH///4H/x/4P/g/8P/k/8P/E/8P/M/4H+MP4B+cHgAAfgAAA/gABg/AABgHAABgGBgAAGBgAAEBgAAEAAAAAAAAAAAAAAAAAA="); + case ERR: + default: + return atob("Hh4BAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAf+AAA//AAB//gAf//gB///wB/z/wD/z/wD/z/wP/z/8f/z/+f/z/+//z//////////////z//f/z/+f///+P///8D///wAAAAAAAAAAAAAAAAAAAAAAAAAA="); + } +} + + +/** +Choose weather icon to display based on condition. +Based on function from the Bangle weather app so it should handle all of the conditions +sent from gadget bridge. +*/ +function chooseIcon(condition) { + condition = condition.toLowerCase(); + if (condition.includes("thunderstorm")) return weatherIcon(STORM); + if (condition.includes("freezing")||condition.includes("snow")|| + condition.includes("sleet")) { + return weatherIcon(SNOW); + } + if (condition.includes("drizzle")|| + condition.includes("shower")) { + return weatherIcon(RAIN); + } + if (condition.includes("rain")) return weatherIcon(RAIN); + if (condition.includes("clear")) return weatherIcon(SUN); + if (condition.includes("few clouds")) return weatherIcon(PART_SUN); + if (condition.includes("scattered clouds")) return weatherIcon(CLOUD); + if (condition.includes("clouds")) return weatherIcon(CLOUD); + if (condition.includes("mist") || + condition.includes("smoke") || + condition.includes("haze") || + condition.includes("sand") || + condition.includes("dust") || + condition.includes("fog") || + condition.includes("ash") || + condition.includes("squalls") || + condition.includes("tornado")) { + return weatherIcon(CLOUD); + } + return weatherIcon(CLOUD); +} + +/* +* Choose weather icon to display based on weather conditition code +* https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2 +*/ +function chooseIconByCode(code) { + const codeGroup = Math.round(code / 100); + switch (codeGroup) { + case 2: return weatherIcon(STORM); + case 3: return weatherIcon(RAIN); + case 5: return weatherIcon(RAIN); + case 6: return weatherIcon(SNOW); + case 7: return weatherIcon(CLOUD); + case 8: + switch (code) { + case 800: return weatherIcon(SUN); + case 801: return weatherIcon(PART_SUN); + default: return weatherIcon(CLOUD); + } + default: return weatherIcon(CLOUD); + } +} + +/** +Get weather stored in json file by weather app. +*/ +function getWeather() { + let jsonWeather = storage.readJSON('weather.json'); + return jsonWeather; +} + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + },60000-(Date.now()%60000)); +} + +// only draw the first time +function drawBg() { + var bgImg = require("heatshrink").decompress(atob("2E7wINKn///+AEaIVUgIUB//wCs/5CtRXrCvMD8AVTg4LFCv4VZ/iSLCrwWMCrMOAQMPCp7cBCojjFCo/xFgIVQgeHCopABCpcH44Vuh/AQQX/wAV7+F/Cq/nCsw/CCqyvRCvgODCqfAgEDCp4QCSIIVQgIOBDQgGDABX/NgIECCp8HCrM/CgP4CqKaCCqSfCCqq1BCqBuB54VqgYVG/gCECp0BwgCDCp8HgYCDCo/wCo0MgHAjACBj7rDABS1Bv4lBv4rPAAsPCo3+gbbPJAIVFiAXMFZ2AUQsAuAQHiOAgJeEA")); + g.reset(); + g.drawImage(bgImg,0,101); +} + +function square(x,y,w,e) { + g.setColor("#000").fillRect(x,y,x+w,y+w); + g.setColor("#fff").fillRect(x+e,y+e,x+w-e,y+w-e); +} + +function draw() { + var d = new Date(); + var h = d.getHours(), m = d.getMinutes(); + h = ("0"+h).substr(-2); + m = ("0"+m).substr(-2); + + var day = d.getDate(), mon = d.getMonth(), dow = d.getDay(); + day = ("0"+day).substr(-2); + mon = ("0"+(mon+1)).substr(-2); + dow = ((dow+6)%7).toString(); + date = day+"."+mon; + + var weatherJson = getWeather(); + var wIcon; + var temp; + if(weatherJson && weatherJson.weather){ + var currentWeather = weatherJson.weather; + temp = locale.temp(currentWeather.temp-273.15).match(/^(\D*\d*)(.*)$/); + const code = currentWeather.code||-1; + if (code > 0) { + wIcon = chooseIconByCode(code); + } else { + wIcon = chooseIcon(currentWeather.txt); + } + }else{ + temp = ""; + wIcon = weatherIcon(ERR); + } + g.reset(); + g.clearRect(22,35,153,75); + g.setFont("4x5NumPretty",8); + g.fillRect(84,42,92,49); + g.fillRect(84,60,92,67); + g.drawString(h,22,35); + g.drawString(m,98,35); + + g.clearRect(22,95,22+4*2*4+2*4,95+2*5); + g.setFont("4x5NumPretty",2); + g.drawString(date,22,95); + + g.clearRect(22,79,22+24,79+13); + g.setFont("DoW"); + g.drawString(dow,22,79); + + g.drawImage(wIcon,126,81); + + g.clearRect(108,114,176,114+4*5); + if (temp != "") { + var tempWidth; + const mid=126+15; + if (temp[1][0]=="-") { + // do not account for - when aligning + const minusWidth=3*4; + tempWidth = minusWidth+(temp[1].length-1)*4*4; + x = mid-Math.round((tempWidth-minusWidth)/2)-minusWidth; + } else { + tempWidth = temp[1].length*4*4; + x = mid-Math.round(tempWidth/2); + } + g.setFont("4x5NumPretty",4); + g.drawString(temp[1],x,114); + square(x+tempWidth,114,6,2); + } + + // queue draw in one minute + queueDraw(); +} + +g.clear(); +drawBg(); +Bangle.setUI("clock"); // Show launcher when middle button pressed +Bangle.loadWidgets(); +Bangle.drawWidgets(); +draw(); + diff --git a/apps/dinoClock/app.png b/apps/dinoClock/app.png new file mode 100644 index 000000000..c05276ee3 Binary files /dev/null and b/apps/dinoClock/app.png differ diff --git a/apps/dinoClock/icon.js b/apps/dinoClock/icon.js new file mode 100644 index 000000000..2410dad14 --- /dev/null +++ b/apps/dinoClock/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgJC/AAVh/E/hgFC/O/AoMB8EZwc8AoUYgYFBgFgjAXDAowXBAo8B/ARBn4FGAAsBmAFE2ADBhwFEj4VEn+AgPvAontgfwv+ABIMCMwIVCgf4FIWAAoN3sAFCwERoEB0MHwF3gEF0MPwFEAoW/4ALD/4tCg/hAoYhB/5ZDwF+Aok0gEIkEf/4AB8eMBoM2bkw=")) diff --git a/apps/dinoClock/metadata.json b/apps/dinoClock/metadata.json new file mode 100644 index 000000000..a61ce122b --- /dev/null +++ b/apps/dinoClock/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "dinoClock", + "name": "Dino Clock", + "description": "Clock with dino from Chrome", + "screenshots": [{"url":"screens/screen1.png"}], + "icon": "app.png", + "version": "0.01", + "type": "clock", + "tags": "clock, weather, dino, trex, chrome", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "readme": "README.md", + "storage": [ + {"name":"dinoClock.app.js","url":"app.js"}, + {"name":"dinoClock.img","url":"icon.js","evaluate":true} + ] +} diff --git a/apps/dinoClock/screens/screen1.png b/apps/dinoClock/screens/screen1.png new file mode 100644 index 000000000..ca4386449 Binary files /dev/null and b/apps/dinoClock/screens/screen1.png differ diff --git a/apps/distortclk/ChangeLog b/apps/distortclk/ChangeLog new file mode 100644 index 000000000..4c7291526 --- /dev/null +++ b/apps/distortclk/ChangeLog @@ -0,0 +1,2 @@ +0.01: New face! +0.02: Improved clock diff --git a/apps/distortclk/README.md b/apps/distortclk/README.md new file mode 100644 index 000000000..8c7c433c1 --- /dev/null +++ b/apps/distortclk/README.md @@ -0,0 +1,17 @@ +# Distort Watchface +Was playing around with custom fonts and made something with it +Made for Bangle.js 2 + +![screenshot (3)](https://user-images.githubusercontent.com/44651387/157507228-100452bf-94a6-476f-aec6-d13d5dad86d5.png) + +## Features + +Has a dark mode + +## Requests + +If you have any issues or would like to suggest a feature, click here to send a message -> [here](https://github.com/elykittytee/BangleApps/issues/new?title=Poketch%20Clock%20Bug). + +## Creator + +Eleanor Tayam diff --git a/apps/distortclk/app-icon.js b/apps/distortclk/app-icon.js new file mode 100644 index 000000000..c375de96e --- /dev/null +++ b/apps/distortclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("j0ewkBiIAxHIQMJiBJEIxAaCAIQfHDgIUFDwwNCHYgVFiAVBHYgIDEghKCCIQGCFYoaDAYgORGIJ2DBwYIBHgQOPgAOIPIYOGAgQOFFgh7DHZQeDBwhoFQgh3JEAgOFFoqkHYRzgOfx4bCJ4gNGSIaJEABA7EAGA")) diff --git a/apps/distortclk/app.js b/apps/distortclk/app.js new file mode 100644 index 000000000..a9fdd1ef2 --- /dev/null +++ b/apps/distortclk/app.js @@ -0,0 +1,65 @@ +Graphics.prototype.setFontSixCaps = function(scale) { + // Actual height 60 (59 - 0) + this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMzMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMzMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0VniarM3u4AAAAAAAAAAAAAAAAAAAAAAAAAAABEZoiqzN3u////////8AAAAAAAAAAAAAAAAAAAJFZ4iqzN3u////////////////8AAAAAAAAAAARGZ4mrzd7v////////////////////////8AAAAAAAqszd7v////////////////////////////7u3MoAAAAAAA//////////////////////////7t3MqohmRCAAAAAAAAAA//////////////////7t3MqohmRAAAAAAAAAAAAAAAAAAA/////////+7dzKqYdlQwAAAAAAAAAAAAAAAAAAAAAAAAAA/+7dzKqIZkQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZkQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWazMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMqVAAAAAAAAAa7//////////////////////////////////+oQAAAAAAC/////////////////////////////////////+wAAAAAAf//////////////////////////////////////3AAAAAA3///7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u///9AAAAAA///GREREREREREREREREREREREREREREREREbP//AAAAAA//8wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///AAAAAA///GREREREREREREREREREREREREREREREREbP//AAAAAA3///7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u///9AAAAAAf//////////////////////////////////////3AAAAAAC/////////////////////////////////////+wAAAAAAAa7//////////////////////////////////+oQAAAAAAAAWazMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMqVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACqoAAAAAAA//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAA//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAA//3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3/8AAAAAAA//////////////////////////////////////8AAAAAAA//////////////////////////////////////8AAAAAAA//////////////////////////////////////8AAAAAAA3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADu4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAes3d3d3d0AAAAAAAAAAAAAAAAAAAAAAAADVorAAAAAAAA8////////8AAAAAAAAAAAAAAAAAAAAlaKze///wAAAAAAHf////////8AAAAAAAAAAAAAAAFGis3v///////wAAAAAAn/////////8AAAAAAAAAAARom83v///////////wAAAAAA7//+3d3d3d0AAAAAAEV5rN7//////////////v/wAAAAAA//xTAAAAAAAAA1eKze//////////////7cqXVP/wAAAAAA//UAAAAAFGis3v/////////////+3Kl2QAAAAP/wAAAAAA//6XZoq83v/////////////+26hkEAAAAAAAAP/wAAAAAA3//////////////////tyoZSAAAAAAAAAAAAAP/wAAAAAAb//////////////tuoZAAAAAAAAAAAAAAAAAAP/wAAAAAACv/////////tuXUwAAAAAAAAAAAAAAAAAAAAAP/wAAAAAAAI3////9yoZAAAAAAAAAAAAAAAAAAAAAAAAAAP/wAAAAAAAAJ4qodRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKqgAAAAAAAAWazMzMzMzMzAAAAAAAAAAAAAzMzMzMzMzMqVAAAAAAAAAa7//////////wAAAAAAAAAAAA///////////+oQAAAAAADP///////////wAAAAAAAAAAAA/////////////AAAAAAAf////////////wAAAAiIgAAAAA/////////////3AAAAAA3///7u7u7u7u7gAAAA//8AAAAA7u7u7u7u7u///9AAAAAA//11RERERERERAAAAA//8AAAAAREREREREREV9//AAAAAA//QAAAAAAAAAAAAAAB//8QAAAAAAAAAAAAAAAE//AAAAAA//11RERERERERERERa//+lREREREREREREREV9//AAAAAA3///7u7u7u7u7u7u7/////7u7u7u7u7u7u7u///9AAAAAAf//////////////////////////////////////3AAAAAAC/////////////////+q//////////////////+wAAAAAAAa7///////////////0i3////////////////+oQAAAAAAAAWazMzMzMzMzMzMyoIAKKzMzMzMzMzMzMzMqVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkRGZniIqqoAAAAAAAAAAAAAAAAAAAAAAkRGZniIqqrMzd3u7v//////8AAAAAAAAAAAAAAAqqrMzd3u7v////////////////////8AAAAAAAAAAAAAAA//////////////////////////////8AAAAAAAAAAAAAAA//////////////////////////////8AAAAAAAAAAAAAAA///////////////+7t3czKqpiHZm//8AAAAAAAAAAAAAAA//7u3dzMuqqIhmZUQwAAAAAAAAAA//8AAAAAAAAAAAAAAAZmREAAAAAAAAAARERERERERERERE//9EREREREQAAAAAAAAAAAAAAAAAAAAA7u7u7u7u7u7u7u///u7u7u7u4AAAAAAAAAAAAAAAAAAAAA////////////////////////8AAAAAAAAAAAAAAAAAAAAA////////////////////////8AAAAAAAAAAAAAAAAAAAAA////////////////////////8AAAAAAAAAAAAAAAAAAAAAzMzMzMzMzMzMzMzMzMzMzMzMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARERERERERERERERERERAAABEREREREREREREAAAAAAAAAA7u7u7u7u7u7u7u7u7u7gAADu7u7u7u7u7u7u2TAAAAAAAA///////////////////wAAD//////////////+YAAAAAAA///////////////////wAAD///////////////4wAAAAAA///////////////////wAAD///////////////+gAAAAAA//zMzMzMzMzMzMzM3//AAADMzMzMzMzMzMzM7//wAAAAAA//AAAAAAAAAAAAAG//cAAAAAAAAAAAAAAAAAKf/wAAAAAA//AAAAAAAAAAAAAN//UAAAAAAAAAAAAAAAAAB//wAAAAAA//AAAAAAAAAAAAAP//6qqqqqqqqqqqqqqqqqv//wAAAAAA//AAAAAAAAAAAAAP///////////////////////AAAAAAA//AAAAAAAAAAAAAN//////////////////////9AAAAAAA//AAAAAAAAAAAAAF7/////////////////////gAAAAAAA//AAAAAAAAAAAAAAWu//////////////////7GAAAAAAAAZmAAAAAAAAAAAAAAADZmZmZmZmZmZmZmZmZmQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADREREREREREREREREREREREREREREREREMAAAAAAAAAADne7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7ZMAAAAAAABd////////////////////////////////////1QAAAAAALv/////////////////////////////////////iAAAAAAr//////////////////////////////////////6AAAAAA///szMzMzMzMzMzMzP//zMzMzMzMzMzMzMzM3///AAAAAA//ogAAAAAAAAAAAAC//5AAAAAAAAAAAAAAAACf//AAAAAA//cAAAAAAAAAAAAAD//zAAAAAAAAAAAAAAAABf//AAAAAA//+6qqqqqqqqAAAAD//8qqqqqqqqqqqqqqqqrv//AAAAAAz///////////AAAAD//////////////////////8AAAAAAT///////////AAAADv/////////////////////0AAAAAACP//////////AAAAB/////////////////////+AAAAAAAAGzv////////AAAAAGzv/////////////////sYAAAAAAAAABGZmZmZmZmAAAAAABGZmZmZmZmZmZmZmZmZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQAAAAAAA//AAAAAAAAAAAAAAAAAAAAAAAAACRGZ4iqrM3e4AAAAAAA//AAAAAAAAAAAAAAACRGZ4iqrM3e7v////////8AAAAAAA//AAAAACRGZ4iqrM3e7v//////////////////8AAAAAAA//iqrM3e7v////////////////////////////8AAAAAAA//////////////////////////////////7u3cwAAAAAAA///////////////////////+7t3MyqmIZmRCAAAAAAAAAA/////////////u3dzLqoiGZUQgAAAAAAAAAAAAAAAAAAAA//7t3cyqqIhmVEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZlRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWazMzMzMqphjAAAAAAAAAARniqrMzMzMzMqVAAAAAAAAAa7//////////+yWEAAAAVi97////////////+oQAAAAAAC///////////////2VAGrf////////////////+wAAAAAAf////////////////+vP///////////////////3AAAAAA3///7u7u7u7/////////////////7u7u7u7u///9AAAAAA///GRERERERmis3////////typhmREREREREbP//AAAAAA//8wAAAAAAAAAAJ8/////8hgAAAAAAAAAAAAA///AAAAAA///GRERERERWis3////////typhmREREREREbP//AAAAAA3///7u7u7u7/////////////////7u7u7u7u///9AAAAAAf////////////////+vP///////////////////3AAAAAAC///////////////2VAGrf////////////////+wAAAAAAAa7//////////+yWIAAAAVi97////////////+oQAAAAAAAAWazMzMzMqphjAAAAAAAAAARniqrMzMzMzMqVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1ZmZmZmZmZmZmZmZmQgAAAAAAZmZmZmZmYwAAAAAAAAAFvv////////////////7rUAAAAA/////////rUAAAAAAAB+////////////////////9gAAAA//////////9wAAAAAAP//////////////////////gAAAA///////////zAAAAAAv//////////////////////wAAAA///////////7AAAAAA///7qqqqqqqqqqqqqqqq3//wAAAAqqqqqqqqrP//AAAAAA//9wAAAAAAAAAAAAAAAAT//wAAAAAAAAAAAAAH//AAAAAA//9wAAAAAAAAAAAAAAAAn//AAAAAAAAAAAAAAZ//AAAAAA///8zMzMzMzMzMzMzMzM///MzMzMzMzMzMzMzf//AAAAAAv//////////////////////////////////////7AAAAAAP//////////////////////////////////////zAAAAAABu////////////////////////////////////5gAAAAAAAEre7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7aQAAAAAAAAAAUREREREREREREREREREREREREREREREREQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMzMwAAAAAAAAAAAzMzAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//8AAAAAAAAAAA///wAAAAAAAAAAAAAAAAAAAAAAAAAAAP//8AAAAAAAAAAA///wAAAAAAAAAAAAAAAAAAAAAAAAAAAP//8AAAAAAAAAAA///wAAAAAAAAAAAAAAAAAAAAAAAAAAAP//8AAAAAAAAAAA///wAAAAAAAAAAAAAAAAAAAAAAAAAAAMzMwAAAAAAAAAAAzMzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("CAwODQ4PDw8QDA8QCA=="), 69+(scale<<8)+(4<<16)); + return this; +}; + +const offset = 25; +const width = g.getWidth(); +const height = g.getHeight(); + +var drawTimeout; +var fgTime = 0xf800; +var bgTime = 0x3333ff; +var dayDate = 0x000; + +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +function time() { + require("Font4x5").add(Graphics); + var d = new Date(); + var day = d.getDate(); + var time = require("locale").time(d,1); + var date = require("locale").date(d); + var mo = require("date_utils").month(d.getMonth()+1,0); + + g.setFontAlign(0,0); + g.setFontSixCaps(2).setColor(fgTime).drawString(time, width/2, height/2+10); + + g.setFont("4x5",2); + g.setFontAlign(0,0); + g.setColor(dayDate).drawString(mo,width-55, height-16); + g.drawString(day,width-10, height-16); +} + +function draw() { + g.setColor(bgTime).fillRect(0,40,width,height-offset); + time(); + queueDraw(); +} + +//program start +g.clear(); // Clear the screen once, at startup + +if (g.theme.dark==true){ + dayDate = 0xffff; +} +else { + dayDate=0x000; +} + +draw(); // draw immediately at first + + + +// Show launcher when middle button pressed +Bangle.setUI("clock"); +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/distortclk/app.png b/apps/distortclk/app.png new file mode 100644 index 000000000..b82a0913e Binary files /dev/null and b/apps/distortclk/app.png differ diff --git a/apps/distortclk/metadata.json b/apps/distortclk/metadata.json new file mode 100644 index 000000000..125dac590 --- /dev/null +++ b/apps/distortclk/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "distortclk", + "name": "Distort Clock", + "shortName":"Distort Clock", + "version": "0.02", + "description": "A clockface", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "readme":"README.md", + "storage": [ + {"name":"distortclk.app.js","url":"app.js"}, + {"name":"distortclk.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/distortclk/screenshot.png b/apps/distortclk/screenshot.png new file mode 100644 index 000000000..3207b4e1e Binary files /dev/null and b/apps/distortclk/screenshot.png differ diff --git a/apps/dotmatrixclock/ChangeLog b/apps/dotmatrixclock/ChangeLog index 7ab9e14a9..12edf33a3 100644 --- a/apps/dotmatrixclock/ChangeLog +++ b/apps/dotmatrixclock/ChangeLog @@ -1 +1,2 @@ 0.01: Create dotmatrix clock app +0.02: Added adjustment for Bangle.js magnetometer heading fix diff --git a/apps/dotmatrixclock/app.js b/apps/dotmatrixclock/app.js index ba34d4885..493a3c43f 100644 --- a/apps/dotmatrixclock/app.js +++ b/apps/dotmatrixclock/app.js @@ -186,7 +186,7 @@ function drawCompass(lastHeading) { 'NW' ]; const cps = Bangle.getCompass(); - let angle = cps.heading; + let angle = 360-cps.heading; let heading = angle? directions[Math.round(((angle %= 360) < 0 ? angle + 360 : angle) / 45) % 8]: "-- "; @@ -351,4 +351,4 @@ Bangle.on('faceUp', (up) => { setSensors(1); resetDisplayTimeout(); } -}); \ No newline at end of file +}); diff --git a/apps/dotmatrixclock/metadata.json b/apps/dotmatrixclock/metadata.json index 3425dc1b2..fdfb5271f 100644 --- a/apps/dotmatrixclock/metadata.json +++ b/apps/dotmatrixclock/metadata.json @@ -1,7 +1,7 @@ { "id": "dotmatrixclock", "name": "Dotmatrix Clock", - "version": "0.01", + "version": "0.02", "description": "A clear white-on-blue dotmatrix simulated clock", "icon": "dotmatrixclock.png", "type": "clock", diff --git a/apps/doztime/ChangeLog b/apps/doztime/ChangeLog index 6c4a25b26..77d82eff9 100644 --- a/apps/doztime/ChangeLog +++ b/apps/doztime/ChangeLog @@ -2,3 +2,6 @@ 0.02: added emulator capability and display of widgets 0.03: bug of advancing time fixed; doztime now correct within ca. 1 second 0.04: changed time colour from slightly off white to pure white +0.05: extraneous comments and code removed + display improved + now supports Adjust Clock widget, if installed diff --git a/apps/doztime/app-bangle2.js b/apps/doztime/app-bangle2.js index b77e5201a..8a315118f 100644 --- a/apps/doztime/app-bangle2.js +++ b/apps/doztime/app-bangle2.js @@ -1,23 +1,23 @@ // Positioning values for graphics buffers const g_height = 80; // total graphics height -const g_x_off = 0; // position from left was 16, then 8 here -const g_y_off = (184 - g_height)/2; // vertical center for graphics region was 240 +const g_x_off = 0; // position from left +const g_y_off = (180 - g_height)/2; // vertical center for graphics region const g_width = 240 - 2 * g_x_off; // total graphics width -const g_height_d = 28; // height of date region was 32 +const g_height_d = 28; // height of date region const g_y_off_d = 0; // y position of date region within graphics region -const spacing = 0; // space between date and time in graphics region +const spacing = 6; // space between date and time in graphics region const g_y_off_t = g_y_off_d + g_height_d + spacing; // y position of time within graphics region -const g_height_t = 44; // height of time region was 48 +const g_height_t = 44; // height of time region // Other vars const A1 = [30,30,30,30,31,31,31,31,31,31,30,30]; const B1 = [30,30,30,30,30,31,31,31,31,31,30,30]; const B2 = [30,30,30,30,31,31,31,31,31,30,30,30]; const timeColour = "#ffffff"; -const dateColours = ["#ff0000","#ffa500","#ffff00","#00b800","#8383ff","#ff00ff","#ff0080"]; //blue was 0000ff -const calen10 = {"size":26,"pt0":[18-g_x_off,16],"step":[16,0],"dx":-4.5,"dy":-4.5}; // positioning for usual calendar line ft w 32, 32-g, step 20 -const calen7 = {"size":26,"pt0":[48-g_x_off,16],"step":[16,0],"dx":-4.5,"dy":-4.5}; // positioning for S-day calendar line ft w 32, 62-g, step 20 -const time5 = {"size":42,"pt0":[39-g_x_off,24],"step":[26,0],"dx":-6.5,"dy":-6.5}; // positioning for lull time line ft w 48, 64-g, step 30 +const dateColours = ["#ff0000","#ff8000","#ffff00","#00ff00","#0080ff","#ff00ff","#ffffff"]; +const calen10 = {"size":26,"pt0":[18-g_x_off,16],"step":[16,0],"dx":-4.5,"dy":-4.5}; // positioning for usual calendar line +const calen7 = {"size":26,"pt0":[48-g_x_off,16],"step":[16,0],"dx":-4.5,"dy":-4.5}; // positioning for S-day calendar line +const time5 = {"size":42,"pt0":[39-g_x_off,24],"step":[26,0],"dx":-6.5,"dy":-6.5}; // positioning for lull time line const time6 = {"size":42,"pt0":[26-g_x_off,24],"step":[26,0],"dx":-6.5,"dy":-6.5}; // positioning for twinkling time line ft w 48, 48-g, step 30 const baseYear = 11584; const baseDate = Date(2020,11,21); // month values run from 0 to 11 @@ -42,28 +42,25 @@ var g_t = Graphics.createArrayBuffer(g_width,g_height_t,1,{'msb':true}); g.clear(); // start with blank screen g.flip = function() { - g.setBgColor(0,0,0); + g.setBgColor(0,0,0); g.setColor(dateColour); - g.drawImage( - { - width:g_width, - height:g_height_d, - buffer:g_d.buffer - }, g_x_off, g_y_off + g_y_off_d); - g.setColor(timeColour2); - g.drawImage( - { - width:g_width, - height:g_height_t, - buffer:g_t.buffer - }, g_x_off, g_y_off + g_y_off_t); + g.drawImage( + { + width:g_width, + height:g_height_d, + buffer:g_d.buffer + }, g_x_off, g_y_off + g_y_off_d); + g.setColor(timeColour2); + g.drawImage( + { + width:g_width, + height:g_height_t, + buffer:g_t.buffer + }, g_x_off, g_y_off + g_y_off_t); }; -setWatch(function(){ modeTime(); }, BTN, {repeat:true} ); //was BTN1 -setWatch(function(){ Bangle.showLauncher(); }, BTN, { repeat: false, edge: "falling" }); //was BTN2 -//setWatch(function(){ modeWeather(); }, BTN3, {repeat:true}); -//setWatch(function(){ toggleTimeDigits(); }, BTN4, {repeat:true}); -//setWatch(function(){ toggleDateFormat(); }, BTN5, {repeat:true}); +setWatch(function(){ modeTime(); }, BTN, {repeat:true} ); +setWatch(function(){ Bangle.showLauncher(); }, BTN, { repeat: false, edge: "falling" }); Bangle.on('touch', function(button, xy) { //from Gordon Williams if (button==1) toggleTimeDigits(); @@ -71,10 +68,10 @@ Bangle.on('touch', function(button, xy) { //from Gordon Williams }); function buildSequence(targ){ - for(let i=0;i n > dt)-1; - let year = baseYear+parseInt(index/12); - let month = index % 12; - let day = parseInt((dt-sequence[index])/86400000); - let colour = dateColours[day % 6]; - if(day==30){ colour=dateColours[6]; } - return({"year":year,"month":month,"day":day,"colour":colour}); + let index = sequence.findIndex(n => n > dt)-1; + let year = baseYear+parseInt(index/12); + let month = index % 12; + let day = parseInt((dt-sequence[index])/86400000); + let colour = dateColours[day % 6]; + if(day==30){ colour=dateColours[6]; } + return({"year":year,"month":month,"day":day,"colour":colour}); } function toggleTimeDigits(){ - addTimeDigit = !addTimeDigit; - modeTime(); + addTimeDigit = !addTimeDigit; + modeTime(); } function toggleDateFormat(){ - dateFormat = !dateFormat; - modeTime(); + dateFormat = !dateFormat; + modeTime(); } function formatDate(res,dateFormat){ - let yyyy = res.year.toString(12); - calenDef = calen10; - if(!dateFormat){ //ordinal format - let mm = ("0"+(res.month+1).toString(12)).substr(-2); - let dd = ("0"+(res.day+1).toString(12)).substr(-2); - if(res.day==30){ - calenDef = calen7; - let m = ((res.month+1).toString(12)).substr(-2); - return(yyyy+"-"+"S"+m); // ordinal format - } - return(yyyy+"-"+mm+"-"+dd); - } - let m = res.month.toString(12); // cardinal format - let w = parseInt(res.day/6); - let d = res.day%6; - //return(yyyy+"-"+res.month+"-"+w+"-"+d); - return(yyyy+"-"+m+"-"+w+"-"+d); + let yyyy = res.year.toString(12); + calenDef = calen10; + if(!dateFormat){ //ordinal format + let mm = ("0"+(res.month+1).toString(12)).substr(-2); + let dd = ("0"+(res.day+1).toString(12)).substr(-2); + if(res.day==30){ + calenDef = calen7; + let m = ((res.month+1).toString(12)).substr(-2); + return(yyyy+"-"+"S"+m); // ordinal format + } + return(yyyy+"-"+mm+"-"+dd); + } + let m = res.month.toString(12); // cardinal format + let w = parseInt(res.day/6); + let d = res.day%6; + //return(yyyy+"-"+res.month+"-"+w+"-"+d); + return(yyyy+"-"+m+"-"+w+"-"+d); } function writeDozTime(text,def){ - let pts = def.pts; - let x=def.pt0[0]; - let y=def.pt0[1]; - g_t.clear(); + let pts = def.pts; + let x=def.pt0[0]; + let y=def.pt0[1]; + g_t.clear(); g_t.setFont("Vector",def.size); - for(let i in text){ - if(text[i]=="a"){ g_t.setFontAlign(0,0,2); g_t.drawString("2",x+2+def.dx,y+1+def.dy); } //+1s are new - else if(text[i]=="b"){ g_t.setFontAlign(0,0,2); g_t.drawString("3",x+2+def.dx,y+1+def.dy); } //+1s are new - else{ g_t.setFontAlign(0,0,0); g_t.drawString(text[i],x,y); } - x = x+def.step[0]; - y = y+def.step[1]; - } + for(let i in text){ + if(text[i]=="a"){ g_t.setFontAlign(0,0,2); g_t.drawString("2",x+2+def.dx,y+1+def.dy); } + else if(text[i]=="b"){ g_t.setFontAlign(0,0,2); g_t.drawString("3",x+2+def.dx,y+1+def.dy); } + else{ g_t.setFontAlign(0,0,0); g_t.drawString(text[i],x,y); } + x = x+def.step[0]; + y = y+def.step[1]; + } } function writeDozDate(text,def,colour){ - - dateColour = colour; - let pts = def.pts; - let x=def.pt0[0]; - let y=def.pt0[1]; - g_d.clear(); - g_d.setFont("Vector",def.size); - for(let i in text){ - if(text[i]=="a"){ g_d.setFontAlign(0,0,2); g_d.drawString("2",x+2+def.dx,y+1+def.dy); } //+1s new - else if(text[i]=="b"){ g_d.setFontAlign(0,0,2); g_d.drawString("3",x+2+def.dx,y+1+def.dy); } //+1s new - else{ g_d.setFontAlign(0,0,0); g_d.drawString(text[i],x,y); } - x = x+def.step[0]; - y = y+def.step[1]; - } + + dateColour = colour; + let pts = def.pts; + let x=def.pt0[0]; + let y=def.pt0[1]; + g_d.clear(); + g_d.setFont("Vector",def.size); + for(let i in text){ + if(text[i]=="a"){ g_d.setFontAlign(0,0,2); g_d.drawString("2",x+2+def.dx,y+1+def.dy); } + else if(text[i]=="b"){ g_d.setFontAlign(0,0,2); g_d.drawString("3",x+2+def.dx,y+1+def.dy); } + else{ g_d.setFontAlign(0,0,0); g_d.drawString(text[i],x,y); } + x = x+def.step[0]; + y = y+def.step[1]; + } } +Bangle.loadWidgets(); +//for malaire's Adjust Clock widget, if used +function adjustedNow() { + return WIDGETS.adjust ? new Date(WIDGETS.adjust.now()) : new Date(); +} +Bangle.drawWidgets(); + // Functions for time mode function drawTime() { - let dt = new Date(); - let date = ""; - let timeDef; - let x = 0; - dt.setDate(dt.getDate()); - if(addTimeDigit){ - x = - 10368*dt.getHours()+172.8*dt.getMinutes()+2.88*dt.getSeconds()+0.00288*dt.getMilliseconds(); - let msg = "00000"+Math.floor(x).toString(12); - let time = msg.substr(-5,3)+"."+msg.substr(-2); - let wait = 347*(1-(x%1)); - timeDef = time6; - } else { - x = - 864*dt.getHours()+14.4*dt.getMinutes()+0.24*dt.getSeconds()+0.00024*dt.getMilliseconds(); - let msg = "0000"+Math.floor(x).toString(12); - let time = msg.substr(-4,3)+"."+msg.substr(-1); - let wait = 4167*(1-(x%1)); - timeDef = time5; - } - if(lastX > x){ res = getDate(dt); } // calculate date once at start-up and once when turning over to a new day - date = formatDate(res,dateFormat); - if(dt x){ res = getDate(dt); } // calculate date once at start-up and once when turning over to a new day + date = formatDate(res,dateFormat); + if(dt2200)) { - } else { - // We have a GPS time. Set time - setTime(g.time.getTime()/1000); - } - }); - Bangle.setGPSPower(1,"time"); - setTimeout(fixTime, 10*60*1000); // every 10 minutes -} -// Start time fixing with GPS on next 10 minute interval -setTimeout(fixTime, ((60-(new Date()).getMinutes()) % 10) * 60 * 1000); diff --git a/apps/doztime/metadata.json b/apps/doztime/metadata.json index d206cb0c3..a05bf1470 100644 --- a/apps/doztime/metadata.json +++ b/apps/doztime/metadata.json @@ -1,9 +1,9 @@ { "id": "doztime", - "name": "Dozenal Time", - "shortName": "Dozenal Time", - "version": "0.04", - "description": "A dozenal Holocene calendar and dozenal diurnal clock", + "name": "Dozenal Digital Time", + "shortName": "Dozenal Digital", + "version": "0.05", + "description": "A dozenal Holocene calendar and dozenal diurnal digital clock", "icon": "app.png", "type": "clock", "tags": "clock", diff --git a/apps/dragboard/ChangeLog b/apps/dragboard/ChangeLog new file mode 100644 index 000000000..265094e87 --- /dev/null +++ b/apps/dragboard/ChangeLog @@ -0,0 +1,6 @@ +0.01: New App! +0.02: Added some missing code. +0.03: Made the code shorter and somewhat more readable by writing some functions. Also made it work as a library where it returns the text once finished. The keyboard is now made to exit correctly when the 'back' event is called. The keyboard now uses theme colors correctly, although it still looks best with dark theme. The numbers row is now solidly green - except for highlights. +0.04: Now displays the opened text string at launch. +0.05: Now scrolls text when string gets longer than screen width. +0.06: The code is now more reliable and the input snappier. Widgets will be drawn if present. diff --git a/apps/dragboard/README.md b/apps/dragboard/README.md new file mode 100644 index 000000000..8960e5749 --- /dev/null +++ b/apps/dragboard/README.md @@ -0,0 +1,16 @@ +Swipe along the red field and release to select a letter. + +Do the same for green field to select number or punctuation. + +Release on left or right part of black field for backspace or space. + +Swiping between the different fields is possible! + +The drag in Dragboard is a nod to the javascript 'drag' event, which is used to select the characters. Also, you can't help but feel somewhat glamorous and risque when this is your keyboard! + +Known bugs: +- Initially developed for use with dark theme set on Bangle.js 2 - that is still the preferred way to view it although it now works with other themes. +- When repeatedly doing 'del' on an empty text-string, the letter case is changed back and forth between upper and lower case. + +To do: +- Possibly provide a dragboard.settings.js file diff --git a/apps/dragboard/app.png b/apps/dragboard/app.png new file mode 100644 index 000000000..26e751896 Binary files /dev/null and b/apps/dragboard/app.png differ diff --git a/apps/dragboard/lib.js b/apps/dragboard/lib.js new file mode 100644 index 000000000..220f075d7 --- /dev/null +++ b/apps/dragboard/lib.js @@ -0,0 +1,246 @@ +exports.input = function(options) { + options = options||{}; + var text = options.text; + if ("string"!=typeof text) text=""; + + var R = Bangle.appRect; + var BGCOLOR = g.theme.bg; + var HLCOLOR = g.theme.fg; + var ABCCOLOR = g.toColor(1,0,0);//'#FF0000'; + var NUMCOLOR = g.toColor(0,1,0);//'#00FF00'; + var BIGFONT = '6x8:3'; + var BIGFONTWIDTH = parseInt(BIGFONT.charAt(0)*parseInt(BIGFONT.charAt(-1))); + var SMALLFONT = '6x8:1'; + var SMALLFONTWIDTH = parseInt(SMALLFONT.charAt(0)*parseInt(SMALLFONT.charAt(-1))); + + var ABC = 'abcdefghijklmnopqrstuvwxyz'.toUpperCase(); + var ABCPADDING = ((R.x+R.w)-6*ABC.length)/2; + + var NUM = ' 1234567890!?,.- '; + var NUMHIDDEN = ' 1234567890!?,.- '; + var NUMPADDING = ((R.x+R.w)-6*NUM.length)/2; + + var rectHeight = 40; + + var delSpaceLast; + + function drawAbcRow() { + g.clear(); + try { // Draw widgets if they are present in the current app. + if (WIDGETS) Bangle.drawWidgets(); + } catch (_) {} + g.setFont(SMALLFONT); + g.setColor(ABCCOLOR); + g.setFontAlign(-1, -1, 0); + g.drawString(ABC, ABCPADDING, (R.y+R.h)/2); + g.fillRect(0, (R.y+R.h)-26, (R.x+R.w), (R.y+R.h)); + } + + function drawNumRow() { + g.setFont(SMALLFONT); + g.setColor(NUMCOLOR); + g.setFontAlign(-1, -1, 0); + g.drawString(NUM, NUMPADDING, (R.y+R.h)/4); + + g.fillRect(NUMPADDING, (R.y+R.h)-rectHeight*4/3, (R.x+R.w)-NUMPADDING, (R.y+R.h)-rectHeight*2/3); + } + + function updateTopString() { + g.setColor(BGCOLOR); + g.fillRect(0,4+20,176,13+20); + g.setColor(0.2,0,0); + var rectLen = text.length<27? text.length*6:27*6; + g.fillRect(3,4+20,5+rectLen,13+20); + g.setColor(0.7,0,0); + g.fillRect(rectLen+5,4+20,rectLen+10,13+20); + g.setColor(1,1,1); + g.setFontAlign(-1, -1, 0); + g.drawString(text.length<=27? text.substr(-27, 27) : '<- '+text.substr(-24,24), 5, 5+20); + } + + var abcHL; + var abcHLPrev = -10; + var numHL; + var numHLPrev = -10; + var type = ''; + var typePrev = ''; + var largeCharOffset = 6; + + function resetChars(char, HLPrev, typePadding, heightDivisor, rowColor) { + "ram"; + // Small character in list + g.setColor(rowColor); + g.setFont(SMALLFONT); + g.setFontAlign(-1, -1, 0); + g.drawString(char, typePadding + HLPrev*6, (R.y+R.h)/heightDivisor); + // Large character + g.setColor(BGCOLOR); + g.fillRect(0,(R.y+R.h)/3,176,(R.y+R.h)/3+24); + //g.drawString(charSet.charAt(HLPrev), typePadding + HLPrev*6 -largeCharOffset, (R.y+R.h)/3);; //Old implementation where I find the shape and place of letter to remove instead of just a rectangle. + // mark in the list + } + function showChars(char, HL, typePadding, heightDivisor) { + "ram"; + // mark in the list + g.setColor(HLCOLOR); + g.setFont(SMALLFONT); + g.setFontAlign(-1, -1, 0); + if (char != 'del' && char != 'space') g.drawString(char, typePadding + HL*6, (R.y+R.h)/heightDivisor); + // show new large character + g.setFont(BIGFONT); + g.drawString(char, typePadding + HL*6 -largeCharOffset, (R.y+R.h)/3); + g.setFont(SMALLFONT); + } + + function initDraw() { + //var R = Bangle.appRect; // To make sure it's properly updated. Not sure if this is needed. + drawAbcRow(); + drawNumRow(); + updateTopString(); + } + initDraw(); + //setTimeout(initDraw, 0); // So Bangle.appRect reads the correct environment. It would draw off to the side sometimes otherwise. + + function changeCase(abcHL) { + g.setColor(BGCOLOR); + g.setFontAlign(-1, -1, 0); + g.drawString(ABC, ABCPADDING, (R.y+R.h)/2); + if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) ABC = ABC.toLowerCase(); + else ABC = ABC.toUpperCase(); + g.setColor(ABCCOLOR); + g.drawString(ABC, ABCPADDING, (R.y+R.h)/2); + } + return new Promise((resolve,reject) => { + // Interpret touch input + Bangle.setUI({ + mode: 'custom', + back: ()=>{ + Bangle.setUI(); + g.clearRect(Bangle.appRect); + resolve(text); + }, + drag: function(event) { + "ram"; + // ABCDEFGHIJKLMNOPQRSTUVWXYZ + // Choose character by draging along red rectangle at bottom of screen + if (event.y >= ( (R.y+R.h) - 12 )) { + // Translate x-position to character + if (event.x < ABCPADDING) { abcHL = 0; } + else if (event.x >= 176-ABCPADDING) { abcHL = 25; } + else { abcHL = Math.floor((event.x-ABCPADDING)/6); } + + // Datastream for development purposes + //print(event.x, event.y, event.b, ABC.charAt(abcHL), ABC.charAt(abcHLPrev)); + + // Unmark previous character and mark the current one... + // Handling switching between letters and numbers/punctuation + if (typePrev != 'abc') resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); + + if (abcHL != abcHLPrev) { + resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + showChars(ABC.charAt(abcHL), abcHL, ABCPADDING, 2); + } + // Print string at top of screen + if (event.b == 0) { + text = text + ABC.charAt(abcHL); + updateTopString(); + + // Autoswitching letter case + if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) changeCase(abcHL); + } + // Update previous character to current one + abcHLPrev = abcHL; + typePrev = 'abc'; + } + + // 12345678901234567890 + // Choose number or puctuation by draging on green rectangle + else if ((event.y < ( (R.y+R.h) - 12 )) && (event.y > ( (R.y+R.h) - 52 ))) { + // Translate x-position to character + if (event.x < NUMPADDING) { numHL = 0; } + else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; } + else { numHL = Math.floor((event.x-NUMPADDING)/6); } + + // Datastream for development purposes + //print(event.x, event.y, event.b, NUM.charAt(numHL), NUM.charAt(numHLPrev)); + + // Unmark previous character and mark the current one... + // Handling switching between letters and numbers/punctuation + if (typePrev != 'num') resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + + if (numHL != numHLPrev) { + resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); + showChars(NUM.charAt(numHL), numHL, NUMPADDING, 4); + } + // Print string at top of screen + if (event.b == 0) { + g.setColor(HLCOLOR); + // Backspace if releasing before list of numbers/punctuation + if (event.x < NUMPADDING) { + // show delete sign + showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4); + delSpaceLast = 1; + text = text.slice(0, -1); + updateTopString(); + //print(text); + } + // Append space if releasing after list of numbers/punctuation + else if (event.x > (R.x+R.w)-NUMPADDING) { + //show space sign + showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4); + delSpaceLast = 1; + text = text + ' '; + updateTopString(); + //print(text); + } + // Append selected number/punctuation + else { + text = text + NUMHIDDEN.charAt(numHL); + updateTopString(); + + // Autoswitching letter case + if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) changeCase(); + } + } + // Update previous character to current one + numHLPrev = numHL; + typePrev = 'num'; + } + + // Make a space or backspace by swiping right or left on screen above green rectangle + else if (event.y > 20+4) { + if (event.b == 0) { + g.setColor(HLCOLOR); + if (event.x < (R.x+R.w)/2) { + resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); + + // show delete sign + showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4); + delSpaceLast = 1; + + // Backspace and draw string upper right corner + text = text.slice(0, -1); + updateTopString(); + if (text.length==0) changeCase(abcHL); + //print(text, 'undid'); + } + else { + resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); + + //show space sign + showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4); + delSpaceLast = 1; + + // Append space and draw string upper right corner + text = text + NUMHIDDEN.charAt(0); + updateTopString(); + //print(text, 'made space'); + } + } + } + } + }); + }); +}; diff --git a/apps/dragboard/metadata.json b/apps/dragboard/metadata.json new file mode 100644 index 000000000..64b6dbe18 --- /dev/null +++ b/apps/dragboard/metadata.json @@ -0,0 +1,14 @@ +{ "id": "dragboard", + "name": "Dragboard", + "version":"0.06", + "description": "A library for text input via swiping keyboard", + "icon": "app.png", + "type":"textinput", + "tags": "keyboard", + "supports" : ["BANGLEJS2"], + "screenshots": [{"url":"screenshot.png"}], + "readme": "README.md", + "storage": [ + {"name":"textinput","url":"lib.js"} + ] +} diff --git a/apps/dragboard/screenshot.png b/apps/dragboard/screenshot.png new file mode 100644 index 000000000..dbe27f408 Binary files /dev/null and b/apps/dragboard/screenshot.png differ diff --git a/apps/drinkcounter/ChangeLog b/apps/drinkcounter/ChangeLog new file mode 100644 index 000000000..d8d174c4c --- /dev/null +++ b/apps/drinkcounter/ChangeLog @@ -0,0 +1,4 @@ +0.10: Initial release - still work in progress +0.15: Added settings and calculations +0.20: Added status saving +0.25: Adopted for Bangle.js 1 - kind of \ No newline at end of file diff --git a/apps/drinkcounter/README.md b/apps/drinkcounter/README.md new file mode 100644 index 000000000..5638ee066 --- /dev/null +++ b/apps/drinkcounter/README.md @@ -0,0 +1,15 @@ +# Drink Counter + +Counts drinks you had for science. Calculates BAC. + +## Usage + +Swipe left/right to select drink. Swipe up/down to add/remove drinks. + +## Important notes + +No warranty whatsoever. Use at your own risk. Calculations might be wrong. Do not drink and drive - even if BAC is low. + +## Creator + +Hank - contact at http://forum.espruino.com diff --git a/apps/drinkcounter/app.js b/apps/drinkcounter/app.js new file mode 100644 index 000000000..323d9fb41 --- /dev/null +++ b/apps/drinkcounter/app.js @@ -0,0 +1,291 @@ +g.reset().clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +require("Font8x16").add(Graphics); + +const BANGLEJS2 = process.env.HWVERSION == 2; +const SETTINGSFILE = "drinkcounter.json"; +setting = require("Storage").readJSON("setting.json",1); +E.setTimeZone(setting.timezone); // timezone = 1 for MEZ, = 2 for MESZ +var _12hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]||false; +var ampm = "AM"; +let drag; + +var icoBeer = require("heatshrink").decompress(atob("lEoxH+AG2BAAoecEpAoWC4fXAAIGGAAowTDxAmJE4YGGE5QeJE5QHHE7owJE0pQKE7pQJE86fnE5QJSE5YUHBAIJQYxIpFAAvGBBAJIExYoGDgIACBBApFExonCDYoAOFSAnbFJYnE6vVDYYFHAwakQE4YaFAoQGJEIYoME7QoEE7ogFE/4neTBgntY84n/E+7HUE64mDE8IAFEw4nDTBifIE9gmId7gALE5IGCAooGDE6gASE8yaME7gmOFIgAREqIAhA==")); +var icoCocktail = require("heatshrink").decompress(atob("lEoxH+AH4AJtgABEkgmiEiXGAAIllAAiXeEAPXAQQDCFBYmTEgYqDFBZNWAIZRME6IfBEAYuEE5J2UwIAaJ5QncFBB3DB4YGCACQnKTQgoXE5bIEE6qfKPAZRFA4MUABgmNPAonBCgQnPExgpFPIgoNEyBSF4wGBFBgmSABCjJTZwoXEzwoHE0AoFE0QnCFAQmhKAonjFAInCE0Qn/E/4n/E/4n/wInDFEAhBEwQoDFLYdCEwooEFTAjHAAwoYIYgAMPDglT")); +var icoShot = require("heatshrink").decompress(atob("lEoxH+AH4A/AH4A/AH4AqwIAgE+HXADRPME8ZQM5AnSZBQkGAAYngEYonfJA5QQE8zGJFAYfKFBwmKE4iYIE7rpIeYgAJE5woEEpQKHTxhQIIpJaHJxgn/E8zGQZBAnQYxxQRFQYnlFgon5FCYmDE6LjHZRQmPE5AAOE/4njFCTGQKCwmRKAgATE54oWEyAqTDZY")); +var icoReset = require("heatshrink").decompress(atob("j0egILI8ACBh4DC/4DBh4DCv8f4ED8EPwEPEQMAvEAnkB4EA+AKBCAM8DYOA8EB//HwED/wXBg/wnAOC+EAjkDDoMgg+AJoRFCEIIAB/kHgEB/l8FwP/DYIDBC4MD/ASBgYeCAAw")); +var drawTimeout; +var activeDrink = 0; +var drinks = [0,0,0]; +const maxDrinks = 2; // 3 drinks +var firstDrinkTime = null; +var firstDrinkTimeTime = null; + +var confBeerSize; +var confSex; +var confWeight; +var confWeightUnit; + + +// Load Status =============== +var drinkStatus = require("Storage").open("drinkcounter.status.json", "r"); +var test = drinkStatus.read(drinkStatus.getLength()); +if(test!== undefined) { + drinkStatus = JSON.parse(test); + //console.log("read status: " + test); + for (let i = 0; i <= maxDrinks; i++) { + drinks[i] = drinkStatus.drinks[i]; + } + firstDrinkTime = Date.parse(drinkStatus.firstDrinkTime); + //console.log("read firstDrinkTime: " + firstDrinkTime); + if (firstDrinkTime) firstDrinkTimeTime = require("locale").time(new Date(firstDrinkTime), 1); + //console.log("read firstDrinkTimeTime: " + firstDrinkTimeTime); +} else { + drinkStatus = { + drinks: [0,0,0] + }; + //console.log("no status file - applying default"); +} +// Load Status =============== + + +var drinksAlcohol = [12,16,5.6]; // in gramm +// Beer: 0.3L 12g - 0.5L 20g +// Radler: 0.3L 6g - 0.5L 10g +// Wine: 0.2L 16g +// Jäger Shot: 0.02L 5.6g + +// sex: Women 60 - Men 70 (Percent) +// Formula: Alcohol in g /(Body weight in kg x sex) – (0,15 x Hours) = bac per mille +// Example: 5 Beer (0.3L=12g), 80KG, Male (70%), 5 hours +// (5 * 12) / (80 / 100 * 70) - (0.15 * 5) + +function drawBac(){ + if (firstDrinkTime) { + var sum_drinks = (drinks[0] * drinksAlcohol[0]) + (drinks[1] * drinksAlcohol[1]) + (drinks[2] * drinksAlcohol[2]); + + if (confSex == "male") { + sex = 70; + } else { + sex = 60; + } + var weight = confWeight; + + if (confWeightUnit == "US Pounds") { + weight = weight * 0.45359237; + } + var currentTime = new Date(); + var time_diff = Math.floor(((currentTime - firstDrinkTime) % 86400000) / 3600000); // in hours! + //console.log("currentTime: " + currentTime) + //console.log("firstDrinkTime: " + firstDrinkTime) + + //console.log("timediff: " + time_diff); + ebac = Math.round( ((sum_drinks) / (weight / 100 * sex) - (0.15 * time_diff) ) * 100) / 100; + + //console.log("BAC: " + ebac + " weight: " + confWeight + " weightInKilo: " + weight + " Unit: " + confWeightUnit); + //console.log("sum_drinks: " + sum_drinks); + g.clearRect(0,34 + 20 + 8,176,34 + 20 + 20 + 8); //Clear + g.setFontAlign(0,0).setFont("8x16").setColor(g.theme.fg).drawString("BAC: " + ebac, 90, 74); + } +} + + +// Load settings +function loadMySettings() { + // Helper function default setting + function def (value, def) {return value !== undefined ? value : def;} + + var settings = require('Storage').readJSON(SETTINGSFILE, true) || {}; + confBeerSize = def(settings.beerSize, "0.3L"); + confSex = def(settings.sex, "male"); + confWeight = def(settings.weight, 80); + confWeightUnit = def(settings.weightUnit, "Kilo"); + //console.log("Read config - weight: " + confWeight); +} + + +function updateTime(){ + var d = require("locale").time(new Date(), 1); + + //console.log(d); + var time = d.split(":"); + var hours = time[0]; + var minutes = time[1]; + if (_12hour){ + //do 12 hour stuff + if (hours > 12) { + ampm = "PM"; + hours = hours - 12; + if (hours < 10) hours = doublenum(hours); + } else { + ampm = "AM"; + } + } else { + ampm = ""; + } + g.setBgColor(g.theme.bg).clearRect(0,24,176,44); //Clear + g.setFontAlign(0,0); // center font + g.setBgColor(g.theme.bg).setColor(g.theme.fg); + g.setFont("8x16").drawString("Time: " + hours + ":" + minutes + " " + ampm,90,34); + queueDrawTime(); +} + +function queueDrawTime() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + updateTime(); + }, 20000 - (Date.now() % 20000)); +} + + +function updateDrinks(){ + g.setBgColor(g.theme.bg).clearRect(0,145,176,176); //Clear + for (let i = 0; i <= maxDrinks; i++) { + if (i == activeDrink) { + g.setColor(g.theme.fg).fillRect((40 * (i + 1)) - 40 ,145,(40 * (i + 1)),176); + g.setColor(g.theme.bg); + } else { + g.setColor(g.theme.fg); + } + g.setFont("Vector",20).drawString(drinks[i], (40 * (i + 1)) - 20, 160); + g.setColor(g.theme.fg); + drinkStatus.drinks[i] = drinks[i]; + } + + g.setBgColor(g.theme.bg).setColor(g.theme.fg); + if (BANGLEJS2) { + g.drawImage(icoReset,145,145); + } + + drinkStatus.firstDrinkTime = firstDrinkTime; + settings_file = require("Storage").open("drinkcounter.status.json", "w"); + settings_file.write(JSON.stringify(drinkStatus)); + + drawBac(); +} + +function updateFirstDrinkTime(){ + if (firstDrinkTime){ + g.setFont("8x16"); + g.setFontAlign(0,0).drawString("1st drink @ " + firstDrinkTimeTime, 90, 34 + 20 ); + } +} + +function addDrink(){ + if (!firstDrinkTime){ + firstDrinkTime = new Date(); + firstDrinkTimeTime = require("locale").time(new Date(), 1); + //console.log("init drinking! " + firstDrinkTime); + } + drinks[activeDrink] = drinks[activeDrink] + 1; + updateFirstDrinkTime(); + updateDrinks(); +} + +function removeDrink(){ + if (drinks[activeDrink] > 0) drinks[activeDrink] = drinks[activeDrink] - 1; + updateDrinks(); + + if ((!BANGLEJS2) && (drinks[0] == 0) && (drinks[1] == 0) && (drinks[2] == 0)) { + resetDrinksFn() + } +} + +function previousDrink(){ + if (activeDrink > 0) activeDrink = activeDrink - 1; + updateDrinks(); +} + +function nextDrink(){ + if (activeDrink < maxDrinks) activeDrink = activeDrink + 1; + updateDrinks(); +} + +function showDrinks() { + g.setBgColor(g.theme.bg); + g.drawImage(icoBeer,0,100); + g.drawImage(icoCocktail,40,100); + g.drawImage(icoShot,80,100); +} + +function resetDrinksFn() { + g.clearRect(0,34,176,176); //Clear + resetDrinks = E.showPrompt("Reset drinks?", { + title: "Confirm", + buttons: { Yes: true, No: false }, + }); + resetDrinks.then((confirm) => { + if (confirm) { + for (let i = 0; i <= maxDrinks; i++) { + drinks[i] = 0; + } + //console.log("reset to default"); + } + //console.log("reset " + confirm); + firstDrinkTime = null; + showDrinks(); + updateDrinks(); + updateTime(); + updateFirstDrinkTime(); + }); +} + + +function initDragEvents() { + +if (BANGLEJS2) { + Bangle.on("drag", e => { + if (!drag) { // start dragging + drag = {x: e.x, y: e.y}; + } else if (!e.b) { // released + const dx = e.x-drag.x, dy = e.y-drag.y; + drag = null; + if (Math.abs(dx)>Math.abs(dy)+10) { + // horizontal + if (dx < dy) { + //console.log("left " + dx + " " + dy); + previousDrink(); + } else { + //console.log("right " + dx + " " + dy); + nextDrink(); + } + } else if (Math.abs(dy)>Math.abs(dx)+10) { + // vertical + if (dx < dy) { + //console.log("down " + dx + " " + dy); + removeDrink(); + } else { + //console.log("up " + dx + " " + dy); + addDrink(); + } + } else { + //console.log("tap " + e.x + " " + e.y); + if (e.x > 145 && e.y > 145) { + resetDrinksFn(); + } + } + } + }); + } else { + setWatch(addDrink, BTN1, { repeat: true, debounce:50 }); + setWatch(removeDrink, BTN3, { repeat: true, debounce:50 }); + setWatch(previousDrink, BTN4, { repeat: true, debounce:50 }); + setWatch(nextDrink, BTN5, { repeat: true, debounce:50 }); + } +} + + +loadMySettings(); +showDrinks(); + + +if (drawTimeout) clearTimeout(drawTimeout); +drawTimeout = undefined; +updateTime(); +queueDrawTime(); +initDragEvents(); +updateDrinks(); +updateFirstDrinkTime(); + diff --git a/apps/drinkcounter/drinkcounter-icon.js b/apps/drinkcounter/drinkcounter-icon.js new file mode 100644 index 000000000..e7b95f9ef --- /dev/null +++ b/apps/drinkcounter/drinkcounter-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AAWBAAomkFpAweD4fXAAIGGAAo4bExAuJF4YGGF6QmJF5QHHF8o4JF1pgSF7pgRF96/vF5QJSF6YcHBAIJQdyIxFAAvGBBAJIFyYwGEgIACBBAxFFyovCEYoAOGTAvbGKYvE6vVEYYFHAwbEYF4YiFAoQGJFIYwUF7QwEF8ooFF/4v2XBgv1d94v/F/7vsF64uDF9IAFFx4vDXBi/IF+guQR6wvCFSIvOAwQFFAwYvcACQvuXSgvcFywxEACItZAH4A/AH4AlA==")) diff --git a/apps/drinkcounter/drinkcounter.png b/apps/drinkcounter/drinkcounter.png new file mode 100644 index 000000000..91a0cd4ad Binary files /dev/null and b/apps/drinkcounter/drinkcounter.png differ diff --git a/apps/drinkcounter/metadata.json b/apps/drinkcounter/metadata.json new file mode 100644 index 000000000..2b8d7fe71 --- /dev/null +++ b/apps/drinkcounter/metadata.json @@ -0,0 +1,24 @@ +{ + "id": "drinkcounter", + "name": "Drink Counter", + "shortName": "Drink Counter", + "version": "0.25", + "description": "Counts drinks you had for science. Calculates blood alcohol content (BAC)", + "allow_emulator":true, + "icon": "drinkcounter.png", + "type": "app", + "tags": "health", + "screenshots": [{"url":"screenshot_drnkcnt.png"}], + "supports": ["BANGLEJS", "BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"drinkcounter.app.js","url":"app.js"}, + {"name":"drinkcounter.img","url":"drinkcounter-icon.js","evaluate":true}, + {"name":"drinkcounter.settings.js","url":"settings.js"} + ], + "data": [ + {"name":"drinkcounter.settings.json"}, + {"name":"drinkcounter.json"}, + {"name":"drinkcounter.status.json"} + ] +} \ No newline at end of file diff --git a/apps/drinkcounter/screenshot_drnkcnt.png b/apps/drinkcounter/screenshot_drnkcnt.png new file mode 100644 index 000000000..7547eb63f Binary files /dev/null and b/apps/drinkcounter/screenshot_drnkcnt.png differ diff --git a/apps/drinkcounter/settings.js b/apps/drinkcounter/settings.js new file mode 100644 index 000000000..336229b73 --- /dev/null +++ b/apps/drinkcounter/settings.js @@ -0,0 +1,58 @@ +(function(back) { + var FILE = "drinkcounter.json"; + var settings = Object.assign({ + secondsOnUnlock: false, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + // Helper method which uses int-based menu item for set of string values + function stringItems(startvalue, writer, values) { + return { + value: (startvalue === undefined ? 0 : values.indexOf(startvalue)), + format: v => values[v], + min: 0, + max: values.length - 1, + wrap: true, + step: 1, + onchange: v => { + writer(values[v]); + writeSettings(); + } + }; + } + + // Helper method which breaks string set settings down to local settings object + function stringInSettings(name, values) { + return stringItems(settings[name], v => settings[name] = v, values); + } + + var mainmenu = { + "": { + "title": "Drink counter" + }, + "< Back": () => back(), + + "Beer size": stringInSettings("beerSize", ["0.3L", "0.5L"]), + + + "Sex": stringInSettings("sex", ["male", "female"]), + + 'Weight': { + value: 80|settings.weight, + min: 40, max: 500, + onchange: v => { + settings.weight = v; + writeSettings(); + } + }, + "Weight unit": stringInSettings("weightUnit", ["Kilo", "US Pounds"]) + + + }; + + E.showMenu(mainmenu); + +}); diff --git a/apps/dtlaunch/ChangeLog b/apps/dtlaunch/ChangeLog index 556472eaa..044b8c35f 100644 --- a/apps/dtlaunch/ChangeLog +++ b/apps/dtlaunch/ChangeLog @@ -6,3 +6,21 @@ 0.06: Adds settings page (hide clocks or launchers) 0.07: Adds setting for directly launching app on touch for Bangle 2 0.08: Optimize line wrapping for Bangle 2 +0.09: fix the trasparent widget bar if there are no widgets for Bangle 2 +0.10: added "one click exit" setting for Bangle 2 +0.11: Fix bangle.js 1 white icons not displaying +0.12: On Bangle 2 change to swiping up/down to move between pages as to match page indicator. Swiping from left to right now loads the clock. +0.13: Added swipeExit setting so that left-right to exit is an option +0.14: Don't move pages when doing exit swipe - Bangle 2. +0.15: 'Swipe to exit'-code is slightly altered to be more reliable - Bangle 2. +0.16: Use default Bangle formatter for booleans +0.17: Bangle 2: Fast loading on exit to clock face. Added option for exit to +clock face by timeout. +0.18: Bangle 2: Move interactions inside setUI. Replace "one click exit" with +back-functionality through setUI, adding the red back button as well. Hardware +button to exit is no longer an option. +0.19: Bangle 2: Utilize new Bangle.load(), Bangle.showClock() functions to +facilitate 'fast switching' of apps where available. +0.20: Bangle 2: Revert use of Bangle.load() to classic load() calls since +widgets would still be loaded when they weren't supposed to. + diff --git a/apps/dtlaunch/README.md b/apps/dtlaunch/README.md index bea20ef65..1835bc842 100644 --- a/apps/dtlaunch/README.md +++ b/apps/dtlaunch/README.md @@ -27,8 +27,8 @@ Bangle 2: ## Controls- Bangle 2 -**Touch** - icon to select, scond touch launches app +**Touch** - icon to select, second touch launches app -**Swipe Left** - move to next page of app icons +**Swipe Left/Up** - move to next page of app icons -**Swipe Right** - move to previous page of app icons +**Swipe Right/Down** - move to previous page of app icons diff --git a/apps/dtlaunch/app-b1.js b/apps/dtlaunch/app-b1.js index ec0569127..ed9cc778e 100644 --- a/apps/dtlaunch/app-b1.js +++ b/apps/dtlaunch/app-b1.js @@ -48,6 +48,7 @@ function draw_icon(p,n,selected) { var x = (n%3)*80; var y = n>2?130:40; (selected?g.setColor(0.3,0.3,0.3):g.setColor(0,0,0)).fillRect(x,y,x+79,y+89); + g.setColor(g.theme.fg); g.drawImage(s.read(apps[p*6+n].icon),x+10,y+10,{scale:1.25}); g.setColor(-1).setFontAlign(0,-1,0).setFont("6x8",1); var txt = apps[p*6+n].name.split(" "); diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js index 96e562add..a7a318c18 100644 --- a/apps/dtlaunch/app-b2.js +++ b/apps/dtlaunch/app-b2.js @@ -1,56 +1,59 @@ -/* Desktop launcher -* -*/ +{ // must be inside our own scope here so that when we are unloaded everything disappears -var settings = Object.assign({ - showClocks: true, - showLaunchers: true, - direct: false, -}, require('Storage').readJSON("dtlaunch.json", true) || {}); + /* Desktop launcher + * + */ -var s = require("Storage"); -var apps = s.list(/\.info$/).map(app=>{ - var a=s.readJSON(app,1); - return a && { - name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src - };}).filter( - app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type)); + let settings = Object.assign({ + showClocks: true, + showLaunchers: true, + direct: false, + swipeExit: false, + timeOut: "Off" + }, require('Storage').readJSON("dtlaunch.json", true) || {}); -apps.sort((a,b)=>{ - var n=(0|a.sortorder)-(0|b.sortorder); - if (n) return n; // do sortorder first - if (a.nameb.name) return 1; - return 0; -}); -apps.forEach(app=>{ + let s = require("Storage"); + var apps = s.list(/\.info$/).map(app=>{ + let a=s.readJSON(app,1); + return a && { + name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src + };}).filter( + app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type)); + + apps.sort((a,b)=>{ + let n=(0|a.sortorder)-(0|b.sortorder); + if (n) return n; // do sortorder first + if (a.nameb.name) return 1; + return 0; + }); + apps.forEach(app=>{ if (app.icon) app.icon = s.read(app.icon); // should just be a link to a memory area }); -var Napps = apps.length; -var Npages = Math.ceil(Napps/4); -var maxPage = Npages-1; -var selected = -1; -var oldselected = -1; -var page = 0; -const XOFF = 24; -const YOFF = 30; + let Napps = apps.length; + let Npages = Math.ceil(Napps/4); + let maxPage = Npages-1; + let selected = -1; + let oldselected = -1; + let page = 0; + const XOFF = 24; + const YOFF = 30; -function draw_icon(p,n,selected) { - var x = (n%2)*72+XOFF; - var y = n>1?72+YOFF:YOFF; + let drawIcon= function(p,n,selected) { + let x = (n%2)*72+XOFF; + let y = n>1?72+YOFF:YOFF; (selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+11,y+3,x+60,y+52); g.clearRect(x+12,y+4,x+59,y+51); g.setColor(g.theme.fg); try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){} g.setFontAlign(0,-1,0).setFont("6x8",1); - var txt = apps[p*4+n].name.replace(/([a-z])([A-Z])/g, "$1 $2").split(" "); - var lineY = 0; - var line = ""; - while (txt.length > 0){ - var c = txt.shift(); - + let txt = apps[p*4+n].name.replace(/([a-z])([A-Z])/g, "$1 $2").split(" "); + let lineY = 0; + let line = ""; + while (txt.length > 0){ + let c = txt.shift(); if (c.length + 1 + line.length > 13){ if (line.length > 0){ g.drawString(line.trim(),x+36,y+54+lineY*8); @@ -62,68 +65,91 @@ function draw_icon(p,n,selected) { } } g.drawString(line.trim(),x+36,y+54+lineY*8); -} + }; -function drawPage(p){ + let drawPage = function(p){ g.reset(); g.clearRect(0,24,175,175); - var O = 88+YOFF/2-12*(Npages/2); - for (var j=0;j{ + Bangle.loadWidgets(); + drawPage(0); + + let swipeListenerDt = function(dirLeftRight, dirUpDown){ + updateTimeoutToClock(); selected = 0; oldselected=-1; - if (dir<0){ - ++page; if (page>maxPage) page=0; - drawPage(page); - } else { - --page; if (page<0) page=maxPage; - drawPage(page); - } -}); + if(settings.swipeExit && dirLeftRight==1) Bangle.showClock(); + if (dirUpDown==-1||dirLeftRight==-1){ + ++page; if (page>maxPage) page=0; + drawPage(page); + } else if (dirUpDown==1||(dirLeftRight==1 && !settings.swipeExit)){ + --page; if (page<0) page=maxPage; + drawPage(page); + } + }; -function isTouched(p,n){ + let isTouched = function(p,n){ if (n<0 || n>3) return false; - var x1 = (n%2)*72+XOFF; var y1 = n>1?72+YOFF:YOFF; - var x2 = x1+71; var y2 = y1+81; + let x1 = (n%2)*72+XOFF; let y1 = n>1?72+YOFF:YOFF; + let x2 = x1+71; let y2 = y1+81; return (p.x>x1 && p.y>y1 && p.x{ - var i; + let touchListenerDt = function(_,p){ + updateTimeoutToClock(); + let i; for (i=0;i<4;i++){ - if((page*4+i)=0 || settings.direct) { - if (selected!=i && !settings.direct){ - draw_icon(page,selected,false); - } else { - load(apps[page*4+i].src); - } - } - selected=i; - break; + if((page*4+i)=0 || settings.direct) { + if (selected!=i && !settings.direct){ + drawIcon(page,selected,false); + } else { + load(apps[page*4+i].src); } + } + selected=i; + break; } + } } if ((i==4 || (page*4+i)>Napps) && selected>=0) { - draw_icon(page,selected,false); - selected=-1; + drawIcon(page,selected,false); + selected=-1; } -}); + }; -Bangle.loadWidgets(); -Bangle.drawWidgets(); -drawPage(0); + Bangle.setUI({ + mode : 'custom', + back : Bangle.showClock, + swipe : swipeListenerDt, + touch : touchListenerDt, + remove : ()=>{if (timeoutToClock) clearTimeout(timeoutToClock);} + }); + + // taken from Icon Launcher with minor alterations + let timeoutToClock; + const updateTimeoutToClock = function(){ + if (settings.timeOut!="Off"){ + let time=parseInt(settings.timeOut); //the "s" will be trimmed by the parseInt + if (timeoutToClock) clearTimeout(timeoutToClock); + timeoutToClock = setTimeout(Bangle.showClock,time*1000); + } + }; + updateTimeoutToClock(); + +} // end of app scope diff --git a/apps/dtlaunch/metadata.json b/apps/dtlaunch/metadata.json index 6cd1dbe73..b69a1a5e6 100644 --- a/apps/dtlaunch/metadata.json +++ b/apps/dtlaunch/metadata.json @@ -1,7 +1,7 @@ { "id": "dtlaunch", "name": "Desktop Launcher", - "version": "0.08", + "version": "0.20", "description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.", "screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}], "icon": "icon.png", diff --git a/apps/dtlaunch/settings-b1.js b/apps/dtlaunch/settings-b1.js index f3101da16..fe5546edb 100644 --- a/apps/dtlaunch/settings-b1.js +++ b/apps/dtlaunch/settings-b1.js @@ -15,7 +15,6 @@ "< Back" : () => back(), 'Show clocks': { value: settings.showClocks, - format: v => v?"On":"Off", onchange: v => { settings.showClocks = v; writeSettings(); @@ -23,7 +22,6 @@ }, 'Show launchers': { value: settings.showLaunchers, - format: v => v?"On":"Off", onchange: v => { settings.showLaunchers = v; writeSettings(); diff --git a/apps/dtlaunch/settings-b2.js b/apps/dtlaunch/settings-b2.js index 7f667d213..24959df8c 100644 --- a/apps/dtlaunch/settings-b2.js +++ b/apps/dtlaunch/settings-b2.js @@ -4,39 +4,57 @@ var settings = Object.assign({ showClocks: true, showLaunchers: true, - direct: false + direct: false, + swipeExit: false, + timeOut: "Off" }, require('Storage').readJSON(FILE, true) || {}); function writeSettings() { require('Storage').writeJSON(FILE, settings); } + const timeOutChoices = [/*LANG*/"Off", "10s", "15s", "20s", "30s"]; + E.showMenu({ "" : { "title" : "Desktop launcher" }, - "< Back" : () => back(), - 'Show clocks': { + /*LANG*/"< Back" : () => back(), + /*LANG*/'Show clocks': { value: settings.showClocks, - format: v => v?"On":"Off", onchange: v => { settings.showClocks = v; writeSettings(); } }, - 'Show launchers': { + /*LANG*/'Show launchers': { value: settings.showLaunchers, - format: v => v?"On":"Off", onchange: v => { settings.showLaunchers = v; writeSettings(); } }, - 'Direct launch': { + /*LANG*/'Direct launch': { value: settings.direct, - format: v => v?"On":"Off", onchange: v => { settings.direct = v; writeSettings(); } + }, + /*LANG*/'Swipe Exit': { + value: settings.swipeExit, + onchange: v => { + settings.swipeExit = v; + writeSettings(); + } + }, + /*LANG*/'Time Out': { // Adapted from Icon Launcher + value: timeOutChoices.indexOf(settings.timeOut), + min: 0, + max: timeOutChoices.length-1, + format: v => timeOutChoices[v], + onchange: v => { + settings.timeOut = timeOutChoices[v]; + writeSettings(); + } } }); -}) +}); diff --git a/apps/dvdbounce/ChangeLog b/apps/dvdbounce/ChangeLog new file mode 100644 index 000000000..6d1dc4ce4 --- /dev/null +++ b/apps/dvdbounce/ChangeLog @@ -0,0 +1 @@ +0.01: Created the app. The logo bounces and buzz when it hits the angles. diff --git a/apps/dvdbounce/README.md b/apps/dvdbounce/README.md new file mode 100644 index 000000000..50f3ef0e9 --- /dev/null +++ b/apps/dvdbounce/README.md @@ -0,0 +1,9 @@ +# Bouncing DVD logo + +Have you ever wanted to admire the bouncing DVD logo on your watch? Now you can! Let's hope it touches an angle. + +![Gif of the DVD logo bouncing around](screenshot.gif) + +## Creator + +I'm [TrinTragula](https://github.com/TrinTragula) on Github. Feel free to reach me. \ No newline at end of file diff --git a/apps/dvdbounce/dvdbounce-icon.js b/apps/dvdbounce/dvdbounce-icon.js new file mode 100644 index 000000000..0625c2394 --- /dev/null +++ b/apps/dvdbounce/dvdbounce-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4A/AH4A/ACWIAA4MJwAXPhALKC5AlCBZYADE4gXELQ4SDC4gCBC4IDCAwYTEBAghCBYYCEC5YUFAooOIBBAKDMwwIFCwQIDNIZtGTRANEEIiyMVYrYHLIq0GQA4OGABIPPAA77GC8CDGABAfFBAbpCAIIfCAQqmJhAXDhB4CCIMIEgIYETAoaCC5zYHIIRQDDATAKPJasWAH4A/AH4A/AE4")) \ No newline at end of file diff --git a/apps/dvdbounce/dvdbounce.app.js b/apps/dvdbounce/dvdbounce.app.js new file mode 100644 index 000000000..39037df34 --- /dev/null +++ b/apps/dvdbounce/dvdbounce.app.js @@ -0,0 +1,108 @@ +// The DVD logo +var dvdLogo = require("heatshrink").decompress(atob("3dTwIFC/4AG/ALCgYJEwAcDj4XHBgYJF4AJCg4WH8AMCn4KFF4YWH/wLCh4KJIpA7CgIKG+BnHOg1/BYxGCCw4LDHQ/8IpQ6CQBBRBIpBpDBY4uCKA6jDUQy8FTIn3EQYIBGYSQDBYTVF/x0DKITMGFwh4D96jDKILuDDoSvDEAn9AQIeDB4glCwA5ELwW/FIRNB/x2BVQSvDHIgIB+ZvCNwWPOwZ7DAYKEFDwJoBAYPhHgYeC8ADCP4QEB85YCEQRFELoIqBBA5oFL4SMEwBFEBAWfDwQFB4K1EJoPwIooIB+InCHIQ8EOgYIHA4PAIQP8IogqCBYQIFw4TBAoRICIoQiB/AaDBAfwJAOAUwTrEFQZFHAQPwCYPwCIIxBWIZFIwICBSIIKBIo4IHFYXgSwRFKBARFCFYLlC8DDCRZTaDOIPPHgngaIhFFBoQfB/YhCIojRDBAg/B/gaCGYRFEHgaUED4IEBD4JBDIAIDBLgQEBIoYbC4AMCHAQcBG4IbCAgJFDCQRlBJIIzCIogJCBAgWCD4IICHARFDCwQIEIAY7DGYRmBEAIWCBAJOCNwYfBPAQSBEIWDMog8DAAQfBUYYzEAATkCBAq+CGgQzEYQgIGD4Q3CGYJnDKYhFGCwLtEVAjQDIow2BawY8HNQQIFCwR0CfYgWFBAoWDEAJ5CIogWCIooHCEAR5CJQQWFIoYPCOgg0BHgiJBIooHDUYaCCIoRLCABl/CB4AFg5MFACBNGAB8fQYYARgL/CACc/CysHLisAuAWVhgWVgL/BCyn/WyX/AAx4Mv4VHAARLJFZAAEbA5VBABwWFh4WPJAs/CZoODOA3A/8H/k//BNBK4N/AQQPB/wWF/kf4P/w4jBg4+BKIMAgYuC/BEF4P4n/w//gEIPwCYJYBCAYLBT4f4n0P/0f/k+g/+FoPwn4uDHQYACwZFB4ZHB8A2BKAQYBCAXwW4nwuF//BCBFgJ3CwZ/BCAR1BUIkEJYJHCFgINB+F/GgIAC4BLENgXhI4J5B8BfCI4KMEFwY0BKoK6BvwSDYoIoDCAIuE8APBXQMPwJaC/kPDALqEGgf8NAK6NAoLpDTYS6CCAKCCAoK+BCwhYBMYQiBXQOALQQ2BAQQWFXggAMGoIAECx6gBAArVEABJDDAAhQDABCTBABIYJ4AVKAAbCDChYA=")); + +// Screen width +const WIDTH = g.getWidth(); +// Screen height +const HEIGHT = g.getHeight(); +// dvd logo image width +const IMG_WIDTH = 94; +/// dvd logo image height +const IMG_HEIGHT = 42; + +// Assign a random X and Y initial speed between 1.5 and 1 +var speedX = 1.5 - Math.random() / 2; +var speedY = 1.5 - Math.random() / 2; +// The logo X and Y position +var posX = 0; +var posY = 0; + +// The current logo color +var currentColor = "#ff00ff"; + +// Get a random value between "ff" and "00" +function getHexColor() { + if (Math.round(Math.random())) { + return "ff"; + } else { + return "00"; + } +} + +// Get a new 8 bit color +function getNewColor() { + return "#" + getHexColor() + getHexColor() + getHexColor(); +} + +// Change the dvd logo color on impact +// Only allow colors different from the current one +// and different from the bg +function changeColor() { + var newColor = getNewColor(); + while (newColor == currentColor || newColor == "#000000") { + newColor = getNewColor(); + } + currentColor = newColor; + g.setColor(newColor); +} + +// Draw the logo +function draw() { + // Move it + posX += speedX; + posY += speedY; + + var collisions = 0; + // Collision detection + if (posX <= 0) { + speedX = -speedX; + posX = 0; + collisions++; + } + if (posY <= 0) { + speedY = -speedY; + posY = 0; + collisions++; + } + if (posX >= (WIDTH - IMG_WIDTH)) { + speedX = -speedX; + posX = WIDTH - IMG_WIDTH; + collisions++; + } + if (posY >= (HEIGHT - IMG_HEIGHT)) { + speedY = -speedY; + posY = HEIGHT - IMG_HEIGHT; + collisions++; + } + + // If we detected 2 collisions, we touched an angle, HURRAY! + if (collisions > 1) { + Bangle.buzz(); + } + + // Change logo color on collision + if (collisions > 0) { + changeColor(); + } + + // Actually draw the logo + g.clear(); + g.drawImage(dvdLogo, posX, posY, { + scale: 0.5 + }); + setTimeout(function () { + draw(); + }, 15); +} + +// Set the background to black +g.setBgColor(0, 0, 0); +// Start from purple +g.setColor(currentColor); +// Clear the screen +g.clear(); +// Start drawing +draw(); + +// Exit on button press +setWatch(Bangle.showLauncher, BTN, { repeat: false, edge: "falling" }); diff --git a/apps/dvdbounce/dvdbounce.png b/apps/dvdbounce/dvdbounce.png new file mode 100644 index 000000000..a44e7a4ba Binary files /dev/null and b/apps/dvdbounce/dvdbounce.png differ diff --git a/apps/dvdbounce/metadata.json b/apps/dvdbounce/metadata.json new file mode 100644 index 000000000..f1c3e8343 --- /dev/null +++ b/apps/dvdbounce/metadata.json @@ -0,0 +1,31 @@ +{ + "id": "dvdbounce", + "name": "Bouncing DVD logo", + "shortName": "Bouncing DVD", + "version": "0.01", + "description": "Have you ever wanted to admire the bouncing DVD logo on your watch? Now you can! Let's hope it touches an angle.", + "icon": "dvdbounce.png", + "tags": "game", + "supports": [ + "BANGLEJS", + "BANGLEJS2" + ], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + { + "name": "dvdbounce.app.js", + "url": "dvdbounce.app.js" + }, + { + "name": "dvdbounce.img", + "url": "dvdbounce-icon.js", + "evaluate": true + } + ], + "screenshots": [ + { + "url": "screenshot.gif" + } + ] +} \ No newline at end of file diff --git a/apps/dvdbounce/screenshot.gif b/apps/dvdbounce/screenshot.gif new file mode 100644 index 000000000..6c82438bc Binary files /dev/null and b/apps/dvdbounce/screenshot.gif differ diff --git a/apps/edisonsball/ChangeLog b/apps/edisonsball/ChangeLog new file mode 100644 index 000000000..b71b8bb0d --- /dev/null +++ b/apps/edisonsball/ChangeLog @@ -0,0 +1,2 @@ +0.01: Initial version +0.02: Added BangleJS Two diff --git a/apps/edisonsball/README.md b/apps/edisonsball/README.md index b8e9ec106..a3b013b6d 100644 --- a/apps/edisonsball/README.md +++ b/apps/edisonsball/README.md @@ -1,4 +1,4 @@ -The application is based on a technique that Thomas Edison used to prevent falling asleep using a steel ball. Essentially the app starts with a display that shows the current HR value that the watch alarm is set to and this can be adjusted with buttons 1 and 3. This HR settng should be the approximate value you want the alarm to trigger and so you should ideally know both what your HR is currently and what your heartrate normally is during sleep. For your current HR according to the watch, you can simply use the HR monitor available in the Espruino app loader, and then from that you can choose a lower value as the target for the alarm and adjust as required. +The application is based on a technique that Thomas Edison used to prevent falling asleep using a steel ball. Essentially the app starts with a display that shows the current HR value that the watch alarm is set to and this can be adjusted with buttons 1 and 3 (Mapped to top touch and bottom touch on Bangle 2). This HR settng should be the approximate value you want the alarm to trigger and so you should ideally know both what your HR is currently and what your heartrate normally is during sleep. For your current HR according to the watch, you can simply use the HR monitor available in the Espruino app loader, and then from that you can choose a lower value as the target for the alarm and adjust as required. When you press the middle button on the side, the HR monitor starts, the alarm will trigger when your heart rate average drops to the limit you’ve set and has a certain level of steadiness that is determined by a assessing the variance over several readings - the sensitivity of this variance can be adjusted in a variable in the app's code under 'ADVANCED SETTINGS' if needed. The code also has a basic logging function which shows, in a CSV file, when you started the HR tracker and when the alarm was triggered. diff --git a/apps/edisonsball/app.js b/apps/edisonsball/app.js index 557155c9a..2aa317829 100644 --- a/apps/edisonsball/app.js +++ b/apps/edisonsball/app.js @@ -3,6 +3,8 @@ var lower_limit_BPM = 49; var upper_limit_BPM = 140; var deviation_threshold = 3; +var ISBANGLEJS1 = process.env.HWVERSION==1; + var target_heartrate = 70; var heartrate_set; @@ -33,25 +35,39 @@ function btn2Pressed() { } function update_target_HR(){ - g.clear(); - g.setColor("#00ff7f"); - g.setFont("6x8", 4); - g.setFontAlign(0,0); // center font + if (process.env.HWVERSION==1) { + g.setColor("#00ff7f"); + g.setFont("6x8", 4); + g.setFontAlign(0,0); // center font - g.drawString(target_heartrate, 120,120); - g.setFont("6x8", 2); - g.setFontAlign(-1,-1); - g.drawString("-", 220, 200); - g.drawString("+", 220, 40); - g.drawString("GO", 210, 120); - - g.setColor("#ffffff"); - g.setFontAlign(0,0); // center font - g.drawString("target HR", 120,90); - - g.setFont("6x8", 1); - g.drawString("if unsure, start with 7-10%\n less than waking average and\n adjust as required", 120,170); + g.drawString(target_heartrate, 120,120); + g.setFont("6x8", 2); + g.setFontAlign(-1,-1); + g.drawString("-", 220, 200); + g.drawString("+", 220, 40); + g.drawString("GO", 210, 120); + + g.setColor("#ffffff"); + g.setFontAlign(0,0); // center font + g.drawString("target HR", 120,90); + + g.setFont("6x8", 1); + g.drawString("if unsure, start with 7-10%\n less than waking average and\n adjust as required", 120,170); + } else { + g.setFont("6x8", 4); + g.setFontAlign(0,0); // center font + g.drawString(target_heartrate, 88,88); + g.setFont("6x8", 2); + g.setFontAlign(-1,-1); + g.drawString("-", 160, 160); + g.drawString("+", 160, 10); + g.drawString("GO", 150, 88); + g.setFontAlign(0,0); // center font + g.drawString("target HR", 88,65); + g.setFont("6x8", 1); + g.drawString("if unsure, start with 7-10%\n less than waking average and\n adjust as required", 88,150); + } g.setFont("6x8",3); g.flip(); @@ -105,8 +121,13 @@ function checkHR() { average_HR = average(HR_samples).toFixed(0); stdev_HR = getStandardDeviation (HR_samples).toFixed(1); - g.drawString("HR: " + average_HR, 120,100); - g.drawString("STDEV: " + stdev_HR, 120,160); + if (ISBANGLEJS1) { + g.drawString("HR: " + average_HR, 120,100); + g.drawString("STDEV: " + stdev_HR, 120,160); + } else { + g.drawString("HR: " + average_HR, 88,60); + g.drawString("STDEV: " + stdev_HR, 88,90); + } HR_samples = []; if(average_HR < target_heartrate && stdev_HR < deviation_threshold){ @@ -131,12 +152,26 @@ function checkHR() { update_target_HR(); -setWatch(btn1Pressed, BTN1, {repeat:true}); -setWatch(btn2Pressed, BTN2, {repeat:true}); -setWatch(btn3Pressed, BTN3, {repeat:true}); +if (ISBANGLEJS1) { + // Bangle 1 + setWatch(btn1Pressed, BTN1, {repeat:true}); + setWatch(btn2Pressed, BTN2, {repeat:true}); + setWatch(btn3Pressed, BTN3, {repeat:true}); +} else { + // Bangle 2 + setWatch(btn2Pressed, BTN1, { repeat: true }); + Bangle.on('touch', function(zone, e) { + if (e.y < g.getHeight() / 2) { + btn1Pressed(); + } + if (e.y > g.getHeight() / 2) { + btn3Pressed(); + } + }); +} + Bangle.on('HRM',function(hrm) { - if(trigger_count < 2){ if (firstBPM) firstBPM=false; // ignore the first one as it's usually rubbish diff --git a/apps/edisonsball/metadata.json b/apps/edisonsball/metadata.json index f429c7b67..dfeb4451e 100644 --- a/apps/edisonsball/metadata.json +++ b/apps/edisonsball/metadata.json @@ -2,11 +2,11 @@ "id": "edisonsball", "name": "Edison's Ball", "shortName": "Edison's Ball", - "version": "0.01", + "version": "0.02", "description": "Hypnagogia/Micro-Sleep alarm for experimental use in exploring sleep transition and combating drowsiness", "icon": "app-icon.png", - "tags": "", - "supports": ["BANGLEJS"], + "tags": "sleep,hyponagogia,quick,nap", + "supports": ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"edisonsball.app.js","url":"app.js"}, diff --git a/apps/entonclk/ChangeLog b/apps/entonclk/ChangeLog new file mode 100644 index 000000000..62e2d0c20 --- /dev/null +++ b/apps/entonclk/ChangeLog @@ -0,0 +1 @@ +0.1: New App! \ No newline at end of file diff --git a/apps/entonclk/README.md b/apps/entonclk/README.md new file mode 100644 index 000000000..8c788c7a5 --- /dev/null +++ b/apps/entonclk/README.md @@ -0,0 +1,9 @@ +Enton - Enhanced Anton Clock + +This clock face is based on the 'Anton Clock'. + +Things I changed: + +- The main font for the time is now Audiowide +- Removed the written out day name and replaced it with steps and bpm +- Changed the date string to a (for me) more readable string \ No newline at end of file diff --git a/apps/entonclk/app-icon.js b/apps/entonclk/app-icon.js new file mode 100644 index 000000000..9993b0871 --- /dev/null +++ b/apps/entonclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkE/4A/AH4A/AH4A/AH4Aw+cikf/mQDCAAIFBAwQDBBYgXCgEDAQIABn4JBkAFBgIKDgQwFmMD+UCmcgl/zEIMzmcQmYKBmYiCAAfxC4QrBl8wBwcgkYsGC4sAiMAF4UxiIGBn8QAgMSC48wgMRiEDBAISCiYcFC48v//yC4PzgJAGiAXIiczPgPzC4JyBmf/AYQXI+KcCj8wmYFCgEjAYQ3G+cjbQIABJIMzAoUin7XIADpSEK4rWGI4MhmRJBn8j+U/d4MimUTkUzIw5dBl4UBMgIXBAgMyLYKOBmQXHiSbCDgMyl8z+UjmJ1BHgJbHCgM/IYQABAgQJBYYYA/AH4AtaQU/mTvBBozWBd44KBkUSkLnBEo8jkcvBI0/CgMiDAIXHHYIXImUzJQJHH+Y+Bn6Z/ABQA==")) \ No newline at end of file diff --git a/apps/entonclk/app.js b/apps/entonclk/app.js new file mode 100644 index 000000000..69fdea479 --- /dev/null +++ b/apps/entonclk/app.js @@ -0,0 +1,67 @@ +Graphics.prototype.setFontAudiowide = function() { + // Actual height 33 (36 - 4) + var widths = atob("CiAsESQjJSQkHyQkDA=="); + var font = atob("AAAAAAAAAAAAAAAAAAAAAPAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAADgAAAAAAHgAAAAAAfgAAAAAA/gAAAAAD/gAAAAAH/gAAAAAf/AAAAAB/8AAAAAD/4AAAAAP/gAAAAAf/AAAAAB/8AAAAAD/4AAAAAP/gAAAAAf+AAAAAB/8AAAAAH/wAAAAAP/gAAAAA/+AAAAAB/8AAAAAD/wAAAAAD/gAAAAAD+AAAAAAD4AAAAAADwAAAAAADAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAA//+AAAAB///AAAAH///wAAAP///4AAAf///8AAA////+AAA/4AP+AAB/gAD/AAB/AA9/AAD+AB+/gAD+AD+/gAD+AD+/gAD8AH+fgAD8AP8fgAD8AP4fgAD8Af4fgAD8A/wfgAD8A/gfgAD8B/gfgAD8D/AfgAD8D+AfgAD8H+AfgAD8P8AfgAD8P4AfgAD8f4AfgAD8/wAfgAD8/gAfgAD+/gA/gAD+/AA/gAB/eAB/AAB/sAD/AAB/wAH/AAA////+AAAf///8AAAP///4AAAH///wAAAD///gAAAA//+AAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//gAAAAH//gAAAAP//gAD8Af//gAD8A///gAD8B///gAD8B///gAD8B/AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+D+AfgAD//+AfgAD//+AfgAB//8AfgAA//4AfgAAf/wAfgAAP/gAfgAAB8AAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+B+A/gAD/////gAB/////AAB/////AAA////+AAAf///8AAAP///4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//4AAAAD//8AAAAD//+AAAAD//+AAAAD//+AAAAD//+AAAAD//+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//AAfgAD//wAfgAD//4AfgAD//8AfgAD//8AfgAD//+AfgAD8D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B/A/gAD8B///gAD8B///gAD8A///AAD8A///AAAAAf/+AAAAAP/4AAAAAD/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///AAAAH///wAAAf///8AAAf///8AAA////+AAB/////AAB/h+H/AAD/B+B/gAD+B+A/gAD+B+A/gAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B/A/gAD8B///gAD8B///gAD8A///AAAAAf//AAAAAf/+AAAAAH/4AAAAAB/gAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAgAD8AAABgAD8AAAHgAD8AAAfgAD8AAA/gAD8AAD/gAD8AAP/gAD8AA//gAD8AB//AAD8AH/8AAD8Af/wAAD8A//AAAD8D/+AAAD8P/4AAAD8f/gAAAD9//AAAAD//8AAAAD//wAAAAD//gAAAAD/+AAAAAD/4AAAAAD/wAAAAAD/AAAAAAD8AAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAH/4AAAAAP/8AAAH+f/+AAAf////AAA/////gAB/////gAB///A/gAD//+AfgAD//+AfgAD+D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+D+AfgAD//+AfgAD//+AfgAB///A/gAB/////gAA/////AAAP////AAAD+f/+AAAAAP/8AAAAAH/4AAAAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/AAAAAAf/wAAAAA//4AAAAB//8AAAAB//8AfgAD//+AfgAD/D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+B+A/gAD+B+A/gAD/B+B/gAB/////AAB/////AAA////+AAAf///8AAAP///4AAAH///wAAAB///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAPAAAA/AAfgAAA/AAfgAAA/AAfgAAA/AAfgAAAeAAPAAAAAAAAAAAAAAAAAAAAAAAAAA"); + var scale = 1; // size multiplier for this font + g.setFontCustom(font, 46, widths, 48+(scale<<8)+(1<<16)); +}; + +function getSteps() { + var steps = 0; + try{ + if (WIDGETS.wpedom !== undefined) { + steps = WIDGETS.wpedom.getSteps(); + } else if (WIDGETS.activepedom !== undefined) { + steps = WIDGETS.activepedom.getSteps(); + } else { + steps = Bangle.getHealthStatus("day").steps; + } + } catch(ex) { + // In case we failed, we can only show 0 steps. + return "?"; + } + + return Math.round(steps); +} + +{ // must be inside our own scope here so that when we are unloaded everything disappears + // we also define functions using 'let fn = function() {..}' for the same reason. function decls are global +let drawTimeout; + + +// Actually draw the watch face +let draw = function() { + var x = g.getWidth() / 2; + var y = g.getHeight() / 2; + g.reset().clearRect(Bangle.appRect); // clear whole background (w/o widgets) + var date = new Date(); + var timeStr = require("locale").time(date, 1); // Hour and minute + g.setFontAlign(0, 0).setFont("Audiowide").drawString(timeStr, x, y); + var dateStr = require("locale").date(date, 1).toUpperCase(); + g.setFontAlign(0, 0).setFont("6x8", 2).drawString(dateStr, x, y+28); + g.setFontAlign(0, 0).setFont("6x8", 2); + g.drawString(getSteps(), 50, y+70); + g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), g.getWidth() -37, y + 70); + + // queue next draw + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +}; + +// Show launcher when middle button pressed +Bangle.setUI({ + mode : "clock", + remove : function() { + // Called to unload all of the clock app + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + delete Graphics.prototype.setFontAnton; + }}); +// Load widgets +Bangle.loadWidgets(); +draw(); +setTimeout(Bangle.drawWidgets,0); +} diff --git a/apps/entonclk/app.png b/apps/entonclk/app.png new file mode 100644 index 000000000..5b634de5a Binary files /dev/null and b/apps/entonclk/app.png differ diff --git a/apps/entonclk/metadata.json b/apps/entonclk/metadata.json new file mode 100644 index 000000000..7e4947406 --- /dev/null +++ b/apps/entonclk/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "entonclk", + "name": "Enton Clock", + "version": "0.1", + "description": "A simple clock using the Audiowide font. ", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "readme":"README.md", + "storage": [ + {"name":"entonclk.app.js","url":"app.js"}, + {"name":"entonclk.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/entonclk/screenshot.png b/apps/entonclk/screenshot.png new file mode 100644 index 000000000..0905c6fc8 Binary files /dev/null and b/apps/entonclk/screenshot.png differ diff --git a/apps/espruinoctrl/README.md b/apps/espruinoctrl/README.md index a7bca662c..7b2e434e7 100644 --- a/apps/espruinoctrl/README.md +++ b/apps/espruinoctrl/README.md @@ -17,7 +17,7 @@ showing available Espruino devices is popped up. device being connected to. Use this if you want to print data - eg: `print(E.getBattery())` When done, click 'Upload'. Your changes will be saved to local storage -so they'll be remembered next time you upload from the same device.s +so they'll be remembered next time you upload from the same device. ## Usage diff --git a/apps/espruinoctrl/app-icon.js b/apps/espruinoctrl/app-icon.js index 70d2dd062..3f9572f72 100644 --- a/apps/espruinoctrl/app-icon.js +++ b/apps/espruinoctrl/app-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwhH+AH4A/AH4A/AH4AFwIuuAAIllAAYIGF041IF34AKqwuuAANXF9QuCAANdGHqQgGBwvdGCIud5mjGB4udAAIwPFz3MSR61VFxQwNci4vGeh4uXGAguHGBK3WGA4AIegtXc69dGBxoBGAouWO4IwNe4gwZa4YwLFwikEFzAwLFwwwCFzQwKFw68YGB4AdF5AwmF5IwlF5QwkF5Yw/F8IwEL9WBB4IuuADwuzGxAugFAgliGBYutAH4A/AH4A/ADA=")) +require("heatshrink").decompress(atob("mEw4UA///muVt9TgH+Jf4AQgILKgtABI9VqkVqAgHqoABC48FBYQKGhEVBQNUBY0qyoLJ1WlEZMq1ILJhWqBZMC1QwCBY0PGAYLGn/qGAQLG/4wDBIkggf8GARfF1ED+BhCTQgTBgfAMISaF1WAAYM61SBG0ADB/wLFgNq1EAHoIcDXYVaCYMP+EqC4kVqwTBn/AhDqFqowBn72HqowCBZAwCBZAwCBZAwCBZIwIiowKBYVWC5VUkAvJXYiaDBYS7FTQVUgr2HC4IgHAAYgHAH4AJA==")) diff --git a/apps/espruinoctrl/metadata.json b/apps/espruinoctrl/metadata.json index 5798c7842..5107bc6ae 100644 --- a/apps/espruinoctrl/metadata.json +++ b/apps/espruinoctrl/metadata.json @@ -5,8 +5,8 @@ "version": "0.01", "description": "Send commands to other Espruino devices via the Bluetooth UART interface. Customisable commands!", "icon": "app.png", - "tags": "", - "supports": ["BANGLEJS"], + "tags": "tool,bluetooth", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "custom": "custom.html", "storage": [ diff --git a/apps/espruinoprog/ChangeLog b/apps/espruinoprog/ChangeLog new file mode 100644 index 000000000..6fdcad1d6 --- /dev/null +++ b/apps/espruinoprog/ChangeLog @@ -0,0 +1,4 @@ +0.01: New App! +0.02: Add 'pre' code that can erase the device + Wait more between sending code snippets + Now force use of 'Storage' (assume 2v00 or later) diff --git a/apps/espruinoprog/README.md b/apps/espruinoprog/README.md new file mode 100644 index 000000000..aef4cccad --- /dev/null +++ b/apps/espruinoprog/README.md @@ -0,0 +1,43 @@ +# Espruino Programmer + +Finds Bluetooth devices with a specific name (eg `Puck.js`), connects and uploads code. Great for programming many devices at once! + +**WARNING:** This will reprogram **any matching Espruino device within range** while +the app is running. Unless you are careful to remove other devices from the area or +turn them off, you could find some of your devices unexpectedly get programmed! + +## Customising + +Click on the Customise button in the app loader to set up the programmer. + +* First you need to choose the kind of devices you want to upload to. This is +the text that should match the Bluetooth advertising name. So `Puck.js` for Puck.js +devices, or `Bangle.js` for Bangles. +* In the next box, you have code to run before the upload of the main code. By default +the code `require("Storage").list().forEach(f=>require("Storage").erase(f));reset();` will +erase all files on the device and reset it. +* Now paste in the code you want to write to the device. This is automatically +written to flash (`.bootcde`). See https://www.espruino.com/Saving#save-on-send-to-flash- +for more information. +* Now enter the code that should be sent **after** programming. This code +should make the device so it doesn't advertise on Bluetooth with the Bluetooth +name you entered for the first item. It may also help if it indicates to you that +the device is programmed properly. + * You could turn advertising off with `NRF.sleep()` + * You could change the advertising name with `NRF.setAdvertising({},{name:"Ok"});` + * On a Bangle, you could turn it off with `Bangle.off()` +* Finally scroll down and click `Upload` +* Now you can run the new `Programmer` app on the Bangle. + +## Usage + +Just run the app, and as soon as it starts it'll start scanning for +devices to upload to! + +To stop scanning, long-press the button to return to the clock. + +## Notes + +* This assumes the device being written to is at least version 2v00 of Espruino +* Currently, code is not minified before upload (so you need to supply pre-minified + code if you want that) diff --git a/apps/espruinoprog/app-icon.js b/apps/espruinoprog/app-icon.js new file mode 100644 index 000000000..532c60eea --- /dev/null +++ b/apps/espruinoprog/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4cA/4AB7wJB8/5uX+7uUgH41lSKf4AKpMkyQCCggEDAQVtCAMCCNWUx9JufSrmkCJeKqsiytICJtFkWRCJWAEaARCI5BkEoAGBymJ9eSvXkCJZ9JCLI1DyM9uQRLNYWRpRZMR5ARWAwSPCuWR9MuCJZZIgARGPouTCIcSA4OQAoMW7dt2wCEEZECCI1oCJAADrZyBAAcDuQRByOABQkKDAvbtwRBxu24AMFAAcGCIY/B7AQIhpOC3MjKIVsCJe3jYRCwiPEkARBQg227ieDAQO0CJPhCKHJCK1N0ARI28JCIjUDEY4OBzWRfAoRG3ARBygRH3oPBswRB4QjFfAYgCt9pYoJoEkmbCJONCI1ACJGSiQRE7TXDCIuQEYkmCIhpDEYSCFCIj2DCIOTrYRE6ARDAH4AHA")) diff --git a/apps/espruinoprog/app.js b/apps/espruinoprog/app.js new file mode 100644 index 000000000..58fac4a0b --- /dev/null +++ b/apps/espruinoprog/app.js @@ -0,0 +1,100 @@ +var uart; // require("ble_uart") +var device; // BluetoothDevice +var uploadTimeout; // a timeout used during upload - if we disconnect, kill this +Bangle.loadWidgets(); + +var json = require("Storage").readJSON("espruinoprog.json",1); +/*var json = { // for example + namePrefix : "Puck.js ", + code : "E.setBootCode('digitalPulse(LED2,1,100);')", + post : "LED.set();NRF.sleep()", +};*/ + +if ("object" != typeof json) { + E.showAlert("JSON not found","Programmer").then(() => load()); + throw new Error("JSON not found"); + // stops execution +} + +// Set up terminal +var R = Bangle.appRect; +var termg = Graphics.createArrayBuffer(R.w, R.h, 1, {msb:true}); +termg.setFont("6x8"); +var term; + +function showTerminal() { + E.showMenu(); // clear anything that was drawn + if (term) term.print(""); // redraw terminal +} + +function scanAndConnect() { + termg.clear(); + term = require("VT100").connect(termg, { + charWidth : 6, + charHeight : 8 + }); + term.print = str => { + for (var i of str) term.char(i); + g.reset().drawImage(termg,R.x,R.y); + }; + term.print(`\r\nScanning...\r\n`); + NRF.requestDevice({ filters: [{ namePrefix: json.namePrefix }] }).then(function(dev) { + term.print(`Found ${dev.name||dev.id.substr(0,17)}\r\n`); + device = dev; + + term.print(`Connect to ${dev.name||dev.id.substr(0,17)}...\r\n`); + device.removeAllListeners(); + device.on('gattserverdisconnected', function(reason) { + if (!uart) return; + term.print(`\r\nDISCONNECTED (${reason})\r\n`); + uart = undefined; + device = undefined; + if (uploadTimeout) clearTimeout(uploadTimeout); + uploadTimeout = undefined; + setTimeout(scanAndConnect, 1000); + }); + require("ble_uart").connect(device).then(function(u) { + uart = u; + term.print("Connected...\r\n"); + uart.removeAllListeners(); + uart.on('data', function(d) { term.print(d); }); + term.print("Upload initial...\r\n"); + uart.write((json.pre||"")+"\n").then(() => { + term.print("\r\Done.\r\n"); + uploadTimeout = setTimeout(function() { + uploadTimeout = undefined; + term.print("\r\nUpload Code...\r\n"); + uart.write((json.code||"")+"\n").then(() => { + term.print("\r\Done.\r\n"); + // main upload completed - wait a bit + uploadTimeout = setTimeout(function() { + uploadTimeout = undefined; + term.print("\r\Upload final...\r\n"); + // now upload the code to run after... + uart.write((json.post||"")+"\n").then(() => { + term.print("\r\nDone.\r\n"); + // now wait and disconnect (if not already done!) + uploadTimeout = setTimeout(function() { + uploadTimeout = undefined; + term.print("\r\nDisconnecting...\r\n"); + if (uart) uart.disconnect(); + }, 500); + }); + }, 2000); + }); + }, 2000); + }); + }); + }).catch(err => { + if (err.toString().startsWith("No device found")) { + // expected - try again + scanAndConnect(); + } else + term.print(`\r\ERROR ${err.toString()}\r\n`); + }); +} + +// now start +Bangle.drawWidgets(); +showTerminal(); +scanAndConnect(); diff --git a/apps/espruinoprog/app.png b/apps/espruinoprog/app.png new file mode 100644 index 000000000..b2b435f04 Binary files /dev/null and b/apps/espruinoprog/app.png differ diff --git a/apps/espruinoprog/custom.html b/apps/espruinoprog/custom.html new file mode 100644 index 000000000..a12189707 --- /dev/null +++ b/apps/espruinoprog/custom.html @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + +

Upload code to devices with names starting with:

+

+

Enter the code to send before upload here:

+

+

Enter your program to upload here:

+

+

Enter the code to send after upload here:

+

+

Then click  

+

Click here to reset to defaults.

+ + + + diff --git a/apps/espruinoprog/metadata.json b/apps/espruinoprog/metadata.json new file mode 100644 index 000000000..7371e005d --- /dev/null +++ b/apps/espruinoprog/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "espruinoprog", + "name": "Espruino Programmer", + "shortName": "Programmer", + "version": "0.02", + "description": "Finds Bluetooth devices with a specific name (eg 'Puck.js'), connects and uploads code. Great for programming many devices at once!", + "icon": "app.png", + "tags": "tool,bluetooth", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "custom": "custom.html", + "storage": [ + {"name":"espruinoprog.app.js","url":"app.js"}, + {"name":"espruinoprog.img","url":"app-icon.js","evaluate":true} + ], "data": [ + {"name":"espruinoprog.json"} + ] +} diff --git a/apps/espruinoterm/ChangeLog b/apps/espruinoterm/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/espruinoterm/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/espruinoterm/README.md b/apps/espruinoterm/README.md new file mode 100644 index 000000000..df26d59a0 --- /dev/null +++ b/apps/espruinoterm/README.md @@ -0,0 +1,22 @@ +# Espruino Terminal + +Send commands to other Espruino devices via the Bluetooth UART interface and +see the result on a terminal. + +## Customising + +Once installed and you're connected to the Bangle you can click the button next to the app in the app loader +to change the commands (they will be read from the device). + +When done, click `Save to Bangle.js` and your changes will be saved to the same device. + +## Usage + +* Load the app and after a few seconds you'll see a menu with Espruino devices +in the vicinity. +* Tap on the device you want to connect to +* A terminal will pop up showing `Connecting...` and then `Connected` +* Now tap on the right (or press the button) to bring up a menu with options for commands, or the option to disconnect. + +You can also choose `Custom` in which case a keyboard (using the currently installed text input method) will +be displayed and you can enter the command you would like to send. diff --git a/apps/espruinoterm/app-icon.js b/apps/espruinoterm/app-icon.js new file mode 100644 index 000000000..f566aedf7 --- /dev/null +++ b/apps/espruinoterm/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwcCpMkyQC/AVW//4AK/oR/COD8LCP4R/CK8DCKNsCKFt2BHPhu2CJ8BCKAjQI4OQNaIUB23bsCPMCJzp/CP4Rf/4AKCKwC/AVIA==")) diff --git a/apps/espruinoterm/app.js b/apps/espruinoterm/app.js new file mode 100644 index 000000000..348190db4 --- /dev/null +++ b/apps/espruinoterm/app.js @@ -0,0 +1,101 @@ +var uart; // require("ble_uart") +var device; // BluetoothDevice +var customCommand = ""; +// Set up terminal +Bangle.loadWidgets(); +var R = Bangle.appRect; +var termg = Graphics.createArrayBuffer(R.w, R.h, 1, {msb:true}); +var termVisible = false; +termg.setFont("6x8"); +term = require("VT100").connect(termg, { + charWidth : 6, + charHeight : 8 +}); +term.print = str => { + for (var i of str) term.char(i); + if (termVisible) g.reset().drawImage(termg,R.x,R.y).setFont("6x8").setFontAlign(0,-1,1).drawString("MORE",R.w-1,(R.y+R.y2)/2); +}; + +function showConnectMenu() { + termVisible = false; + var m = { "" : {title:"Devices"} }; + E.showMessage("Scanning..."); + NRF.findDevices(devices => { + devices.forEach(dev=>{ + m[dev.name||dev.id.substr(0,17)] = ()=>{ + connectTo(dev); + }; + }); + m["< Back"] = () => showConnectMenu(); + E.showMenu(m); + },{filters:[ + { namePrefix: 'Puck.js' }, + { namePrefix: 'Pixl.js' }, + { namePrefix: 'MDBT42Q' }, + { namePrefix: 'Bangle.js' }, + { namePrefix: 'Espruino' }, + { services: [ "6e400001-b5a3-f393-e0a9-e50e24dcca9e" ] } + ],active:true,timeout:4000}); +} + +function showOptionsMenu() { + if (!uart) showConnectMenu(); + termVisible = false; + var menu = {"":{title:/*LANG*/"Options"}, + "< Back" : () => showTerminal(), + }; + var json = require("Storage").readJSON("espruinoterm.json",1); + if (Array.isArray(json)) { + json.forEach(j => { menu[j.title] = () => sendCommand(j.cmd); }); + } else { + Object.assign(menu,{ + "Version" : () => sendCommand("process.env.VERSION"), + "Battery" : () => sendCommand("E.getBattery()"), + "Flash LED" : () => sendCommand("LED.set();setTimeout(()=>LED.reset(),1000);") + }); + } + menu[/*LANG*/"Custom"] = () => { require("textinput").input({text:customCommand}).then(result => { + customCommand = result; + sendCommand(customCommand); + })}; + menu[/*LANG*/"Disconnect"] = () => { showTerminal(); term.print("\r\nDisconnecting...\r\n"); uart.disconnect(); } + + E.showMenu(menu); +} + +function showTerminal() { + E.showMenu(); + Bangle.setUI({ + mode : "custom", + btn : n=> { showOptionsMenu(); }, + touch : (n,e) => { if (n==2) showOptionsMenu(); } + }); + termVisible = true; + term.print(""); // redraw terminal +} + +function sendCommand(cmd) { + showTerminal(); + uart.write(cmd+"\n"); +} + +function connectTo(dev) { + device = dev; + showTerminal(); + term.print(`\r\nConnect to ${dev.name||dev.id.substr(0,17)}...\r\n`); + device.on('gattserverdisconnected', function(reason) { + term.print(`\r\nDISCONNECTED (${reason})\r\n`); + uart = undefined; + device = undefined; + setTimeout(showConnectMenu, 1000); + }); + require("ble_uart").connect(device).then(function(u) { + uart = u; + term.print("Connected...\r\n"); + uart.on('data', function(d) { term.print(d); }); + }); +} + +// now start +Bangle.drawWidgets(); +showConnectMenu(); diff --git a/apps/espruinoterm/app.json b/apps/espruinoterm/app.json new file mode 100644 index 000000000..72a12e635 --- /dev/null +++ b/apps/espruinoterm/app.json @@ -0,0 +1,5 @@ +[ + {"title":"Version", "cmd":"process.env.VERSION"}, + {"title":"Battery", "cmd":"E.getBattery()"}, + {"title":"Flash LED", "cmd":"LED.set();setTimeout(()=>LED.reset(),1000);"} +] diff --git a/apps/espruinoterm/app.png b/apps/espruinoterm/app.png new file mode 100644 index 000000000..e9a8c3758 Binary files /dev/null and b/apps/espruinoterm/app.png differ diff --git a/apps/espruinoterm/interface.html b/apps/espruinoterm/interface.html new file mode 100644 index 000000000..660b3a86c --- /dev/null +++ b/apps/espruinoterm/interface.html @@ -0,0 +1,104 @@ + + + + + + + + +

Enter the menu items you'd like to see appear in the app below. When finished, click `Save to Bangle.js` to save the JavaScript back.

+
+ + + + + + + + + + +
TitleCommand
+
+

+ + + + + + diff --git a/apps/espruinoterm/metadata.json b/apps/espruinoterm/metadata.json new file mode 100644 index 000000000..25e6183e1 --- /dev/null +++ b/apps/espruinoterm/metadata.json @@ -0,0 +1,20 @@ +{ + "id": "espruinoterm", + "name": "Espruino Terminal", + "shortName": "Espruino Term", + "version": "0.01", + "description": "Send commands to other Espruino devices via the Bluetooth UART interface, and see the result on a VT100 terminal. Customisable commands!", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "tags": "tool,bluetooth", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "interface": "interface.html", + "dependencies": {"textinput":"type"}, + "storage": [ + {"name":"espruinoterm.app.js","url":"app.js"}, + {"name":"espruinoterm.img","url":"app-icon.js","evaluate":true} + ],"data": [ + {"name":"espruinoterm.json","url":"app.json"} + ] +} diff --git a/apps/espruinoterm/screenshot.png b/apps/espruinoterm/screenshot.png new file mode 100644 index 000000000..cce881a37 Binary files /dev/null and b/apps/espruinoterm/screenshot.png differ diff --git a/apps/f9lander/ChangeLog b/apps/f9lander/ChangeLog new file mode 100644 index 000000000..a13f2a313 --- /dev/null +++ b/apps/f9lander/ChangeLog @@ -0,0 +1,2 @@ +0.01: New App! +0.02: Add lightning diff --git a/apps/f9lander/README.md b/apps/f9lander/README.md new file mode 100644 index 000000000..16202f166 --- /dev/null +++ b/apps/f9lander/README.md @@ -0,0 +1,33 @@ +# F9 Lander + +Land a Falcon 9 booster on a drone ship. + +## Game play + +Attempt to land your Falcon 9 booster on a drone ship before running out of fuel. +A successful landing requires: + * setting down on the ship + * the booster has to be mostly vertical + * the landing speed cannot be too high + +## Controls + +The angle of the booster is controlled by tilting the watch side-to-side. The +throttle level is controlled by tilting the watch forward and back: + * screen horizontal (face up) means no throttle + * screen vertical corresponds to full throttle + +The fuel burn rate is proportional to the throttle level. + +## Creators +Liam Kl. B. + +Marko Kl. B. + +## Screenshots + +![](f9lander_screenshot1.png) + +![](f9lander_screenshot2.png) + +![](f9lander_screenshot3.png) diff --git a/apps/f9lander/app-icon.js b/apps/f9lander/app-icon.js new file mode 100644 index 000000000..572768a28 --- /dev/null +++ b/apps/f9lander/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwcA/4AD/P8yVJkgCCye27dt2wRE//kCIuSuwRIBwgCCpwRQpIRRnYRQkmdCIvPCJICBEZ4RG/IRP/15CJ/z5IRPz4RM/gQB/n+BxICCn/z/P/BxQCDz7mIAX4Cq31/CJ+ebpiYE/IR/CNP/5IROnn//4jP5DFQ5sJCKAjPk3oCMMk4QRQAX4Ckn7jBAA/5CK8nCJPJNHA")) diff --git a/apps/f9lander/app.js b/apps/f9lander/app.js new file mode 100644 index 000000000..2f17a5bd5 --- /dev/null +++ b/apps/f9lander/app.js @@ -0,0 +1,178 @@ +const falcon9 = Graphics.createImage(` + xxxxx + xxxxx xxxxx + x x + x x + xxxxx + xxxxx + xxxxx + xxxxx + xxxxx + xxxxx + xxxxx + xxxxx + xxxxx + xxxxx + xxxxx + xxxxx + xxxxx + xxxxx + xxxxxxxxx + xx xxxxx xx +xx xx`); + +const droneShip = Graphics.createImage(` +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +`); + +const droneX = Math.floor(Math.random()*(g.getWidth()-droneShip.width-40) + 20) +const cloudOffs = Math.floor(Math.random()*g.getWidth()/2); + +const oceanHeight = g.getHeight()*0.1; + +const targetY = g.getHeight()-oceanHeight-falcon9.height/2; + +var booster = { x : g.getWidth()/4 + Math.random()*g.getWidth()/2, + y : 20, + vx : 0, + vy : 0, + mass : 100, + fuel : 100 }; + +var exploded = false; +var nExplosions = 0; +var landed = false; +var lightning = 0; + +var settings = require("Storage").readJSON('f9settings.json', 1) || {}; + +const gravity = 4; +const dt = 0.1; +const fuelBurnRate = 20*(176/g.getHeight()); +const maxV = 12; + +function flameImageGen (throttle) { + var str = " xxx \n xxx \n"; + str += "xxxxx\n".repeat(throttle); + str += " xxx \n x \n"; + return Graphics.createImage(str); +} + +function drawFalcon(x, y, throttle, angle) { + g.setColor(1, 1, 1).drawImage(falcon9, x, y, {rotate:angle}); + if (throttle>0 || lightning>0) { + var flameImg = flameImageGen(throttle); + var r = falcon9.height/2 + flameImg.height/2-1; + var xoffs = -Math.sin(angle)*r; + var yoffs = Math.cos(angle)*r; + if (Math.random()>0.7) g.setColor(1, 0.5, 0); + else g.setColor(1, 1, 0); + if (throttle>0) g.drawImage(flameImg, x+xoffs, y+yoffs, {rotate:angle}); + if (lightning>1 && lightning<30) { + for (var i=0; i<6; ++i) { + var r = Math.random()*6; + var x = Math.random()*5 - xoffs; + var y = Math.random()*5 - yoffs; + g.setColor(1, Math.random()*0.5+0.5, 0).fillCircle(booster.x+x, booster.y+y, r); + } + } + } +} + +function drawLightning() { + var c = {x:cloudOffs+50, y:30}; + var dx = c.x-booster.x; + var dy = c.y-booster.y; + var m1 = {x:booster.x+0.6*dx+Math.random()*20, y:booster.y+0.6*dy+Math.random()*10}; + var m2 = {x:booster.x+0.4*dx+Math.random()*20, y:booster.y+0.4*dy+Math.random()*10}; + g.setColor(1, 1, 1).drawLine(c.x, c.y, m1.x, m1.y).drawLine(m1.x, m1.y, m2.x, m2.y).drawLine(m2.x, m2.y, booster.x, booster.y); +} + +function drawBG() { + if (lightning==1) { + g.setBgColor(1, 1, 1).clear(); + Bangle.buzz(200); + return; + } + g.setBgColor(0.2, 0.2, 1).clear(); + g.setColor(0, 0, 1).fillRect(0, g.getHeight()-oceanHeight, g.getWidth()-1, g.getHeight()-1); + g.setColor(0.5, 0.5, 1).fillCircle(cloudOffs+34, 30, 15).fillCircle(cloudOffs+60, 35, 20).fillCircle(cloudOffs+75, 20, 10); + g.setColor(1, 1, 0).fillCircle(g.getWidth(), 0, 20); + g.setColor(1, 1, 1).drawImage(droneShip, droneX, g.getHeight()-oceanHeight-1); +} + +function showFuel() { + g.setColor(0, 0, 0).setFont("4x6:2").setFontAlign(-1, -1, 0).drawString("Fuel: "+Math.abs(booster.fuel).toFixed(0), 4, 4); +} + +function renderScreen(input) { + drawBG(); + showFuel(); + drawFalcon(booster.x, booster.y, Math.floor(input.throttle*12), input.angle); + if (lightning>1 && lightning<6) drawLightning(); +} + +function getInputs() { + var accel = Bangle.getAccel(); + var a = Math.PI/2 + Math.atan2(accel.y, accel.x); + var t = (1+accel.z); + if (t > 1) t = 1; + if (t < 0) t = 0; + if (booster.fuel<=0) t = 0; + if (lightning>0 && lightning<20) t = 0; + return {throttle: t, angle: a}; +} + +function epilogue(str) { + g.setFont("Vector", 24).setFontAlign(0, 0, 0).setColor(0, 0, 0).drawString(str, g.getWidth()/2, g.getHeight()/2).flip(); + g.setFont("Vector", 16).drawString("<= again exit =>", g.getWidth()/2, g.getHeight()/2+20); + clearInterval(stepInterval); + Bangle.on("swipe", (d) => { if (d>0) load(); else load('f9lander.app.js'); }); +} + +function gameStep() { + if (exploded) { + if (nExplosions++ < 15) { + var r = Math.random()*25; + var x = Math.random()*30 - 15; + var y = Math.random()*30 - 15; + g.setColor(1, Math.random()*0.5+0.5, 0).fillCircle(booster.x+x, booster.y+y, r); + if (nExplosions==1) Bangle.buzz(600); + } + else epilogue("You crashed!"); + } + else { + var input = getInputs(); + if (booster.y >= targetY) { + if (Math.abs(booster.x-droneX-droneShip.width/2)40) && Math.random()>0.98) lightning = 1; + booster.x += booster.vx*dt; + booster.y += booster.vy*dt; + booster.vy += gravity*dt; + booster.fuel -= input.throttle*dt*fuelBurnRate; + booster.vy += -Math.cos(input.angle)*input.throttle*gravity*3*dt; + booster.vx += Math.sin(input.angle)*input.throttle*gravity*3*dt; + renderScreen(input); + } + } +} + +var stepInterval; +Bangle.setLCDTimeout(0); +renderScreen({angle:0, throttle:0}); +g.setFont("Vector", 24).setFontAlign(0, 0, 0).setColor(0, 0, 0).drawString("Swipe to start", g.getWidth()/2, g.getHeight()/2); +Bangle.on("swipe", () => { + stepInterval = setInterval(gameStep, Math.floor(1000*dt)); + Bangle.removeListener("swipe"); +}); diff --git a/apps/f9lander/f9lander.png b/apps/f9lander/f9lander.png new file mode 100644 index 000000000..f03cc1645 Binary files /dev/null and b/apps/f9lander/f9lander.png differ diff --git a/apps/f9lander/f9lander_screenshot1.png b/apps/f9lander/f9lander_screenshot1.png new file mode 100644 index 000000000..ea7d8a834 Binary files /dev/null and b/apps/f9lander/f9lander_screenshot1.png differ diff --git a/apps/f9lander/f9lander_screenshot2.png b/apps/f9lander/f9lander_screenshot2.png new file mode 100644 index 000000000..a2f13d6c7 Binary files /dev/null and b/apps/f9lander/f9lander_screenshot2.png differ diff --git a/apps/f9lander/f9lander_screenshot3.png b/apps/f9lander/f9lander_screenshot3.png new file mode 100644 index 000000000..61b8be82f Binary files /dev/null and b/apps/f9lander/f9lander_screenshot3.png differ diff --git a/apps/f9lander/metadata.json b/apps/f9lander/metadata.json new file mode 100644 index 000000000..1db777099 --- /dev/null +++ b/apps/f9lander/metadata.json @@ -0,0 +1,16 @@ +{ "id": "f9lander", + "name": "Falcon9 Lander", + "shortName":"F9lander", + "version":"0.02", + "description": "Land a rocket booster", + "icon": "f9lander.png", + "screenshots" : [ { "url":"f9lander_screenshot1.png" }, { "url":"f9lander_screenshot2.png" }, { "url":"f9lander_screenshot3.png" }], + "readme": "README.md", + "tags": "game", + "supports" : ["BANGLEJS", "BANGLEJS2"], + "storage": [ + {"name":"f9lander.app.js","url":"app.js"}, + {"name":"f9lander.img","url":"app-icon.js","evaluate":true}, + {"name":"f9lander.settings.js", "url":"settings.js"} + ] +} diff --git a/apps/f9lander/settings.js b/apps/f9lander/settings.js new file mode 100644 index 000000000..0f9fba302 --- /dev/null +++ b/apps/f9lander/settings.js @@ -0,0 +1,36 @@ +// This file should contain exactly one function, which shows the app's settings +/** + * @param {function} back Use back() to return to settings menu + */ +const boolFormat = v => v ? /*LANG*/"On" : /*LANG*/"Off"; +(function(back) { + const SETTINGS_FILE = 'f9settings.json' + // initialize with default settings... + let settings = { + 'lightning': false, + } + // ...and overwrite them with any saved values + // This way saved values are preserved if a new version adds more settings + const storage = require('Storage') + const saved = storage.readJSON(SETTINGS_FILE, 1) || {} + for (const key in saved) { + settings[key] = saved[key]; + } + // creates a function to safe a specific setting, e.g. save('color')(1) + function save(key) { + return function (value) { + settings[key] = value; + storage.write(SETTINGS_FILE, settings); + } + } + const menu = { + '': { 'title': 'OpenWind' }, + '< Back': back, + 'Lightning': { + value: settings.lightning, + format: boolFormat, + onchange: save('lightning'), + } + } + E.showMenu(menu); +}) diff --git a/apps/fastload/ChangeLog b/apps/fastload/ChangeLog new file mode 100644 index 000000000..53e3c2591 --- /dev/null +++ b/apps/fastload/ChangeLog @@ -0,0 +1,3 @@ +0.01: New App! +0.02: Allow redirection of loads to the launcher +0.03: Allow hiding the fastloading info screen diff --git a/apps/fastload/README.md b/apps/fastload/README.md new file mode 100644 index 000000000..a1feedcf8 --- /dev/null +++ b/apps/fastload/README.md @@ -0,0 +1,21 @@ +# Fastload Utils + +*EXPERIMENTAL* Use this with caution. When you find something misbehaving please check if the problem actually persists when removing this app. + +This allows fast loading of all apps with two conditions: +* Loaded app contains `Bangle.loadWidgets`. This is needed to prevent problems with apps not expecting widgets to be already loaded. +* Current app can be removed completely from RAM. + +## Settings + +* Allows to redirect all loads usually loading the clock to the launcher instead +* The "Fastloading..." screen can be switched off + +## Technical infos + +This is still experimental but it uses the same mechanism as `.bootcde` does. +It checks the app to be loaded for widget use and stores the result of that and a hash of the js in a cache. + +# Creator + +[halemmerich](https://github.com/halemmerich) diff --git a/apps/fastload/boot.js b/apps/fastload/boot.js new file mode 100644 index 000000000..c9271abbf --- /dev/null +++ b/apps/fastload/boot.js @@ -0,0 +1,66 @@ +{ +const SETTINGS = require("Storage").readJSON("fastload.json") || {}; + +let loadingScreen = function(){ + g.reset(); + + let x = g.getWidth()/2; + let y = g.getHeight()/2; + g.setColor(g.theme.bg); + g.fillRect(x-49, y-19, x+49, y+19); + g.setColor(g.theme.fg); + g.drawRect(x-50, y-20, x+50, y+20); + g.setFont("6x8"); + g.setFontAlign(0,0); + g.drawString("Fastloading...", x, y); + g.flip(true); +}; + +let cache = require("Storage").readJSON("fastload.cache") || {}; + +let checkApp = function(n){ + // no widgets, no problem + if (!global.WIDGETS) return true; + let app = require("Storage").read(n); + if (cache[n] && E.CRC32(app) == cache[n].crc) + return cache[n].fast + cache[n] = {}; + cache[n].fast = app.includes("Bangle.loadWidgets"); + cache[n].crc = E.CRC32(app); + require("Storage").writeJSON("fastload.cache", cache); + return cache[n].fast; +} + +global._load = load; + +let slowload = function(n){ + global._load(n); +} + +let fastload = function(n){ + if (!n || checkApp(n)){ + // Bangle.load can call load, to prevent recursion this must be the system load + global.load = slowload; + Bangle.load(n); + // if fastloading worked, we need to set load back to this method + global.load = fastload; + } + else + slowload(n); +}; +global.load = fastload; + +Bangle.load = (o => (name) => { + if (Bangle.uiRemove && !SETTINGS.hideLoading) loadingScreen(); + if (SETTINGS.autoloadLauncher && !name){ + let orig = Bangle.load; + Bangle.load = (n)=>{ + Bangle.load = orig; + fastload(n); + } + Bangle.showLauncher(); + Bangle.load = orig; + } else + o(name); +})(Bangle.load); +} diff --git a/apps/fastload/icon.png b/apps/fastload/icon.png new file mode 100644 index 000000000..7fe9afe6e Binary files /dev/null and b/apps/fastload/icon.png differ diff --git a/apps/fastload/metadata.json b/apps/fastload/metadata.json new file mode 100644 index 000000000..15adcb7e3 --- /dev/null +++ b/apps/fastload/metadata.json @@ -0,0 +1,16 @@ +{ "id": "fastload", + "name": "Fastload Utils", + "shortName" : "Fastload Utils", + "version": "0.03", + "icon": "icon.png", + "description": "Enable experimental fastloading for more apps", + "type":"bootloader", + "tags": "system", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"fastload.5.boot.js","url":"boot.js"}, + {"name":"fastload.settings.js","url":"settings.js"} + ], + "data": [{"name":"fastload.json"}] +} diff --git a/apps/fastload/settings.js b/apps/fastload/settings.js new file mode 100644 index 000000000..4904e057e --- /dev/null +++ b/apps/fastload/settings.js @@ -0,0 +1,38 @@ +(function(back) { + var FILE="fastload.json"; + var settings; + + function writeSettings(key, value) { + var s = require('Storage').readJSON(FILE, true) || {}; + s[key] = value; + require('Storage').writeJSON(FILE, s); + readSettings(); + } + + function readSettings(){ + settings = require('Storage').readJSON(FILE, true) || {}; + } + + readSettings(); + + function buildMainMenu(){ + var mainmenu = { + '': { 'title': 'Fastload', back: back }, + 'Force load to launcher': { + value: !!settings.autoloadLauncher, + onchange: v => { + writeSettings("autoloadLauncher",v); + } + }, + 'Hide "Fastloading..."': { + value: !!settings.hideLoading, + onchange: v => { + writeSettings("hideLoading",v); + } + } + }; + return mainmenu; + } + + E.showMenu(buildMainMenu()); +}) diff --git a/apps/fclock/ChangeLog b/apps/fclock/ChangeLog index 30e049f69..7e7307c59 100644 --- a/apps/fclock/ChangeLog +++ b/apps/fclock/ChangeLog @@ -1,2 +1,3 @@ 0.01: First published version of app 0.02: Move to Bangle.setUI to launcher support +0.03: Tell clock widgets to hide. diff --git a/apps/fclock/fclock.app.js b/apps/fclock/fclock.app.js index afa0c5e2d..838a5578d 100644 --- a/apps/fclock/fclock.app.js +++ b/apps/fclock/fclock.app.js @@ -173,6 +173,9 @@ const drawHR = function () { } }; +// Show launcher when button pressed +Bangle.setUI("clock"); + // clean app screen g.clear(); Bangle.loadWidgets(); @@ -198,6 +201,3 @@ Bangle.on('HRM', function (d) { // draw now drawClock(); - -// Show launcher when button pressed -Bangle.setUI("clock"); diff --git a/apps/fclock/metadata.json b/apps/fclock/metadata.json index da553e110..dffb197a2 100644 --- a/apps/fclock/metadata.json +++ b/apps/fclock/metadata.json @@ -2,7 +2,7 @@ "id": "fclock", "name": "fclock", "shortName": "F Clock", - "version": "0.02", + "version": "0.03", "description": "Simple design of a digital clock", "icon": "app.png", "type": "clock", diff --git a/apps/ffcniftya/ChangeLog b/apps/ffcniftya/ChangeLog index 420c553f5..6d2f50119 100644 --- a/apps/ffcniftya/ChangeLog +++ b/apps/ffcniftya/ChangeLog @@ -1,2 +1,6 @@ 0.01: New Clock Nifty A -0.02: Shows the current week number (ISO8601), can be disabled via settings "" +0.02: Shows the current week number (ISO8601), can be disabled via settings +0.03: Call setUI before loading widgets + Improve settings page +0.04: Use ClockFace library + diff --git a/apps/ffcniftya/README.md b/apps/ffcniftya/README.md index 86f1f5c2d..80005fd3c 100644 --- a/apps/ffcniftya/README.md +++ b/apps/ffcniftya/README.md @@ -1,13 +1,12 @@ # Nifty-A Clock -Colors are black/white - photos have non correct camera color "blue" +Colors are black/white - photos have non correct camera color "blue". -## This is the clock +This is the clock: ![](screenshot_nifty.png) -## The week number (ISO8601) can be turned of in settings -(default is **"On"**) +The week number (ISO8601) can be turned off in settings (default is `On`) ![](screenshot_settings_nifty.png) diff --git a/apps/ffcniftya/app.js b/apps/ffcniftya/app.js index 5da1ec48e..2c1a54f6e 100644 --- a/apps/ffcniftya/app.js +++ b/apps/ffcniftya/app.js @@ -1,111 +1,59 @@ -const locale = require("locale"); -const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; -const CFG = require('Storage').readJSON("ffcniftya.json", 1) || {showWeekNum: true}; - -/* Clock *********************************************/ -const scale = g.getWidth() / 176; - -const widget = 24; - -const viewport = { - width: g.getWidth(), - height: g.getHeight(), -} - -const center = { - x: viewport.width / 2, - y: Math.round(((viewport.height - widget) / 2) + widget), -} - -function ISO8601_week_no(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480 - var tdt = new Date(date.valueOf()); - var dayn = (date.getDay() + 6) % 7; - tdt.setDate(tdt.getDate() - dayn + 3); - var firstThursday = tdt.valueOf(); - tdt.setMonth(0, 1); - if (tdt.getDay() !== 4) { - tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7); - } - return 1 + Math.ceil((firstThursday - tdt) / 604800000); -} - -function d02(value) { - return ('0' + value).substr(-2); -} - -function draw() { - g.reset(); - g.clearRect(0, widget, viewport.width, viewport.height); - const now = new Date(); - - const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0)); - const minutes = d02(now.getMinutes()); - const day = d02(now.getDate()); - const month = d02(now.getMonth() + 1); - const year = now.getFullYear(now); - const weekNum = d02(ISO8601_week_no(now)); - const monthName = locale.month(now, 3); - const dayName = locale.dow(now, 3); - - const centerTimeScaleX = center.x + 32 * scale; - g.setFontAlign(1, 0).setFont("Vector", 90 * scale); - g.drawString(hour, centerTimeScaleX, center.y - 31 * scale); - g.drawString(minutes, centerTimeScaleX, center.y + 46 * scale); - - g.fillRect(center.x + 30 * scale, center.y - 72 * scale, center.x + 32 * scale, center.y + 74 * scale); - - const centerDatesScaleX = center.x + 40 * scale; - g.setFontAlign(-1, 0).setFont("Vector", 16 * scale); - g.drawString(year, centerDatesScaleX, center.y - 62 * scale); - g.drawString(month, centerDatesScaleX, center.y - 44 * scale); - g.drawString(day, centerDatesScaleX, center.y - 26 * scale); - if (CFG.showWeekNum) g.drawString(d02(ISO8601_week_no(now)), centerDatesScaleX, center.y + 15 * scale); - g.drawString(monthName, centerDatesScaleX, center.y + 48 * scale); - g.drawString(dayName, centerDatesScaleX, center.y + 66 * scale); -} - - -/* Minute Ticker *************************************/ - -let tickTimer; - -function clearTickTimer() { - if (tickTimer) { - clearTimeout(tickTimer); - tickTimer = undefined; +// copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480 +function ISO8601_week_no(date) { + var tdt = new Date(date.valueOf()); + var dayn = (date.getDay() + 6) % 7; + tdt.setDate(tdt.getDate() - dayn + 3); + var firstThursday = tdt.valueOf(); + tdt.setMonth(0, 1); + if (tdt.getDay() !== 4) { + tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7); } + return 1 + Math.ceil((firstThursday - tdt) / 604800000); } -function queueNextTick() { - clearTickTimer(); - tickTimer = setTimeout(tick, 60000 - (Date.now() % 60000)); - // tickTimer = setTimeout(tick, 3000); +function format(value) { + return ("0" + value).substr(-2); } -function tick() { - draw(); - queueNextTick(); -} +const ClockFace = require("ClockFace"); +const clock = new ClockFace({ + init: function () { + const appRect = Bangle.appRect; -/* Init **********************************************/ + this.viewport = { + width: appRect.w, + height: appRect.h + }; -// Clear the screen once, at startup -g.clear(); -// Start ticking -tick(); + this.center = { + x: this.viewport.width / 2, + y: Math.round((this.viewport.height / 2) + appRect.y) + }; -// Stop updates when LCD is off, restart when on -Bangle.on('lcdPower', (on) => { - if (on) { - tick(); // Start ticking - } else { - clearTickTimer(); // stop ticking - } + this.scale = g.getWidth() / this.viewport.width; + this.centerTimeScaleX = this.center.x + 32 * this.scale; + this.centerDatesScaleX = this.center.x + 40 * this.scale; + }, + draw: function (date) { + const hour = date.getHours() - (this.is12Hour && date.getHours() > 12 ? 12 : 0); + const month = date.getMonth() + 1; + const monthName = require("date_utils").month(month, 1); + const dayName = require("date_utils").dow(date.getDay(), 1); + + g.setFontAlign(1, 0).setFont("Vector", 90 * this.scale); + g.drawString(format(hour), this.centerTimeScaleX, this.center.y - 31 * this.scale); + g.drawString(format(date.getMinutes()), this.centerTimeScaleX, this.center.y + 46 * this.scale); + + g.fillRect(this.center.x + 30 * this.scale, this.center.y - 72 * this.scale, this.center.x + 32 * this.scale, this.center.y + 74 * this.scale); + + g.setFontAlign(-1, 0).setFont("Vector", 16 * this.scale); + g.drawString(date.getFullYear(date), this.centerDatesScaleX, this.center.y - 62 * this.scale); + g.drawString(format(month), this.centerDatesScaleX, this.center.y - 44 * this.scale); + g.drawString(format(date.getDate()), this.centerDatesScaleX, this.center.y - 26 * this.scale); + if (this.showWeekNum) g.drawString(format(ISO8601_week_no(date)), this.centerDatesScaleX, this.center.y + 15 * this.scale); + g.drawString(monthName, this.centerDatesScaleX, this.center.y + 48 * this.scale); + g.drawString(dayName, this.centerDatesScaleX, this.center.y + 66 * this.scale); + }, + settingsFile: "ffcniftya.json" }); - -// Load widgets -Bangle.loadWidgets(); -Bangle.drawWidgets(); - -// Show launcher when middle button pressed -Bangle.setUI("clock"); \ No newline at end of file +clock.start(); \ No newline at end of file diff --git a/apps/ffcniftya/metadata.json b/apps/ffcniftya/metadata.json index ce91cc225..015c56119 100644 --- a/apps/ffcniftya/metadata.json +++ b/apps/ffcniftya/metadata.json @@ -1,7 +1,7 @@ { "id": "ffcniftya", "name": "Nifty-A Clock", - "version": "0.02", + "version": "0.04", "description": "A nifty clock with time and date", "icon": "app.png", "screenshots": [{"url":"screenshot_nifty.png"}], diff --git a/apps/ffcniftya/settings.js b/apps/ffcniftya/settings.js index 46e4ef5aa..aec1d680a 100644 --- a/apps/ffcniftya/settings.js +++ b/apps/ffcniftya/settings.js @@ -1,23 +1,15 @@ -(function(back) { - var FILE = "ffcniftya.json"; - // Load settings - var cfg = require('Storage').readJSON(FILE, 1) || { showWeekNum: true }; +(function (back) { + const settings = Object.assign({ showWeekNum: true }, require("Storage").readJSON("ffcniftya.json", true)); - function writeSettings() { - require('Storage').writeJSON(FILE, cfg); - } - - // Show the menu E.showMenu({ - "" : { "title" : "Nifty-A Clock" }, - "< Back" : () => back(), - 'week number?': { - value: cfg.showWeekNum, - format: v => v?"On":"Off", + "": { "title": "Nifty-A Clock" }, + "< Back": () => back(), + /*LANG*/"Show Week Number": { + value: settings.showWeekNum, onchange: v => { - cfg.showWeekNum = v; - writeSettings(); + settings.showWeekNum = v; + require("Storage").writeJSON("ffcniftya.json", settings); } } }); -}) \ No newline at end of file +}) diff --git a/apps/ffcniftyb/ChangeLog b/apps/ffcniftyb/ChangeLog index dedd31452..83b11eb78 100644 --- a/apps/ffcniftyb/ChangeLog +++ b/apps/ffcniftyb/ChangeLog @@ -1,2 +1,6 @@ 0.01: New Clock Nifty B -0.02: Added configuration \ No newline at end of file +0.02: Added configuration +0.03: Call setUI before loading widgets + Fix bug with black being unselectable + Improve settings page +0.04: Use ClockFace library diff --git a/apps/ffcniftyb/README.md b/apps/ffcniftyb/README.md index e04243a0b..072f71cce 100644 --- a/apps/ffcniftyb/README.md +++ b/apps/ffcniftyb/README.md @@ -1,9 +1,6 @@ # Nifty Series B Clock - Display Time and Date -- Color Configuration - -## +- Colour Configuration ![](screenshot.png) - diff --git a/apps/ffcniftyb/app.js b/apps/ffcniftyb/app.js index 75d217ab4..540924fa5 100644 --- a/apps/ffcniftyb/app.js +++ b/apps/ffcniftyb/app.js @@ -1,118 +1,80 @@ -const locale = require("locale"); -const storage = require('Storage'); +var scale; +var screen; +var center; +var buf; +var img; -const is12Hour = (storage.readJSON("setting.json", 1) || {})["12hour"]; -const color = (storage.readJSON("ffcniftyb.json", 1) || {})["color"] || 63488 /* red */; - - -/* Clock *********************************************/ -const scale = g.getWidth() / 176; - -const screen = { - width: g.getWidth(), - height: g.getHeight() - 24, -}; - -const center = { - x: screen.width / 2, - y: screen.height / 2, -}; - -function d02(value) { - return ('0' + value).substr(-2); +function format(value) { + return ("0" + value).substr(-2); } function renderEllipse(g) { g.fillEllipse(center.x - 5 * scale, center.y - 70 * scale, center.x + 160 * scale, center.y + 90 * scale); } -function renderText(g) { - const now = new Date(); +function renderText(g, date) { + const hour = date.getHours() - (this.is12Hour && date.getHours() > 12 ? 12 : 0); + const month = date.getMonth() + 1; - const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0)); - const minutes = d02(now.getMinutes()); - const day = d02(now.getDate()); - const month = d02(now.getMonth() + 1); - const year = now.getFullYear(); - - const month2 = locale.month(now, 3); - const day2 = locale.dow(now, 3); + const monthName = require("date_utils").month(month, 1); + const dayName = require("date_utils").dow(date.getDay(), 1); g.setFontAlign(1, 0).setFont("Vector", 90 * scale); - g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale); - g.drawString(minutes, center.x + 32 * scale, center.y + 46 * scale); + g.drawString(format(hour), center.x + 32 * scale, center.y - 31 * scale); + g.drawString(format(date.getMinutes()), center.x + 32 * scale, center.y + 46 * scale); g.setFontAlign(1, 0).setFont("Vector", 16 * scale); - g.drawString(year, center.x + 80 * scale, center.y - 42 * scale); - g.drawString(month, center.x + 80 * scale, center.y - 26 * scale); - g.drawString(day, center.x + 80 * scale, center.y - 10 * scale); - g.drawString(month2, center.x + 80 * scale, center.y + 44 * scale); - g.drawString(day2, center.x + 80 * scale, center.y + 60 * scale); + g.drawString(date.getFullYear(), center.x + 80 * scale, center.y - 42 * scale); + g.drawString(format(month), center.x + 80 * scale, center.y - 26 * scale); + g.drawString(format(date.getDate()), center.x + 80 * scale, center.y - 10 * scale); + g.drawString(monthName, center.x + 80 * scale, center.y + 44 * scale); + g.drawString(dayName, center.x + 80 * scale, center.y + 60 * scale); } -const buf = Graphics.createArrayBuffer(screen.width, screen.height, 1, { - msb: true +const ClockFace = require("ClockFace"); +const clock = new ClockFace({ + init: function () { + const appRect = Bangle.appRect; + + screen = { + width: appRect.w, + height: appRect.h + }; + + center = { + x: screen.width / 2, + y: screen.height / 2 + }; + + buf = Graphics.createArrayBuffer(screen.width, screen.height, 1, { msb: true }); + + scale = g.getWidth() / screen.width; + + img = { + width: screen.width, + height: screen.height, + transparent: 0, + bpp: 1, + buffer: buf.buffer + }; + + // default to RED (see settings.js) + // don't use || to default because 0 is a valid color + this.color = this.color === undefined ? 63488 : this.color; + }, + draw: function (date) { + // render outside text with ellipse + buf.clear(); + renderText(buf.setColor(1), date); + renderEllipse(buf.setColor(0)); + g.setColor(this.color).drawImage(img, 0, 24); + + // render ellipse with inside text + buf.clear(); + renderEllipse(buf.setColor(1)); + renderText(buf.setColor(0), date); + g.setColor(this.color).drawImage(img, 0, 24); + }, + settingsFile: "ffcniftyb.json" }); - -function draw() { - - const img = { - width: screen.width, - height: screen.height, - transparent: 0, - bpp: 1, - buffer: buf.buffer - }; - - // cleat screen area - g.clearRect(0, 24, g.getWidth(), g.getHeight()); - - // render outside text with ellipse - buf.clear(); - renderText(buf.setColor(1)); - renderEllipse(buf.setColor(0)); - g.setColor(color).drawImage(img, 0, 24); - - // render ellipse with inside text - buf.clear(); - renderEllipse(buf.setColor(1)); - renderText(buf.setColor(0)); - g.setColor(color).drawImage(img, 0, 24); -} - - -/* Minute Ticker *************************************/ - -let ticker; - -function stopTick() { - if (ticker) { - clearTimeout(ticker); - ticker = undefined; - } -} - -function startTick(run) { - stopTick(); - run(); - ticker = setTimeout(() => startTick(run), 60000 - (Date.now() % 60000)); - // ticker = setTimeout(() => startTick(run), 3000); -} - -/* Init **********************************************/ - -g.clear(); -startTick(draw); - -Bangle.on('lcdPower', (on) => { - if (on) { - startTick(draw); - } else { - stopTick(); - } -}); - -Bangle.loadWidgets(); -Bangle.drawWidgets(); - -Bangle.setUI("clock"); +clock.start(); \ No newline at end of file diff --git a/apps/ffcniftyb/metadata.json b/apps/ffcniftyb/metadata.json index e4e099a51..019ae6eb3 100644 --- a/apps/ffcniftyb/metadata.json +++ b/apps/ffcniftyb/metadata.json @@ -1,13 +1,14 @@ { "id": "ffcniftyb", "name": "Nifty-B Clock", - "version": "0.02", - "description": "A nifty clock (series B) with time, date and color configuration", + "version": "0.04", + "description": "A nifty clock (series B) with time, date and colour configuration", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"ffcniftyb.app.js","url":"app.js"}, diff --git a/apps/ffcniftyb/settings.js b/apps/ffcniftyb/settings.js index 00abf80b5..da350edd8 100644 --- a/apps/ffcniftyb/settings.js +++ b/apps/ffcniftyb/settings.js @@ -1,49 +1,31 @@ (function (back) { - const storage = require('Storage'); - const SETTINGS_FILE = "ffcniftyb.json"; + const settings = Object.assign({ color: 63488 }, require("Storage").readJSON("ffcniftyb.json", true)); const colors = { - 65535: 'White', - 63488: 'Red', - 65504: 'Yellow', - 2047: 'Cyan', - 2016: 'Green', - 31: 'Blue', - 0: 'Black', + 65535: /*LANG*/"White", + 63488: /*LANG*/"Red", + 65504: /*LANG*/"Yellow", + 2047: /*LANG*/"Cyan", + 2016: /*LANG*/"Green", + 31: /*LANG*/"Blue", + 0: /*LANG*/"Black" } - function load(settings) { - return Object.assign(settings, storage.readJSON(SETTINGS_FILE, 1) || {}); - } + const menu = {}; + menu[""] = { title: "Nifty-B Clock" }; + menu["< Back"] = back; - function save(settings) { - storage.write(SETTINGS_FILE, settings) - } - - const settings = load({ - color: 63488 /* red */, + Object.keys(colors).forEach(color => { + var label = colors[color]; + menu[label] = { + value: settings.color == color, + onchange: () => { + settings.color = color; + require("Storage").write("ffcniftyb.json", settings); + setTimeout(load, 10); + } + }; }); - const saveColor = (color) => () => { - settings.color = color; - save(settings); - back(); - }; - - function showMenu(items, opt) { - items[''] = opt || {}; - items['< Back'] = back; - E.showMenu(items); - } - - showMenu( - Object.keys(colors).reduce((menu, color) => { - menu[colors[color]] = saveColor(color); - return menu; - }, {}), - { - title: 'Color', - selected: Object.keys(colors).indexOf(settings.color) - } - ); + E.showMenu(menu); }); diff --git a/apps/files/ChangeLog b/apps/files/ChangeLog index 1908f7e5c..4622e6f0f 100644 --- a/apps/files/ChangeLog +++ b/apps/files/ChangeLog @@ -3,4 +3,5 @@ 0.04: Add functionality to sort apps manually or alphabetically ascending/descending. 0.05: Tweaks to help with memory usage 0.06: Reduce memory usage -0.07: Allow negative numbers when manual-sorting \ No newline at end of file +0.07: Allow negative numbers when manual-sorting +0.08: Automatic translation of strings. diff --git a/apps/files/files.js b/apps/files/files.js index e7b42c101..2f7b5c9a1 100644 --- a/apps/files/files.js +++ b/apps/files/files.js @@ -1,24 +1,22 @@ const store = require('Storage'); -const boolFormat = (v) => v ? "On" : "Off"; - function showMainMenu() { const mainmenu = { '': { - 'title': 'App Manager', + 'title': /*LANG*/'App Manager', }, '< Back': ()=> {load();}, - 'Sort Apps': () => showSortAppsMenu(), - 'Manage Apps': ()=> showApps(), - 'Compact': () => { - E.showMessage('Compacting...'); + /*LANG*/'Sort Apps': () => showSortAppsMenu(), + /*LANG*/'Manage Apps': ()=> showApps(), + /*LANG*/'Compact': () => { + E.showMessage(/*LANG*/'Compacting...'); try { store.compact(); } catch (e) { } showMainMenu(); }, - 'Free': { + /*LANG*/'Free': { value: undefined, format: (v) => { return store.getFree(); @@ -67,13 +65,13 @@ function eraseData(info) { }); } function eraseApp(app, files,data) { - E.showMessage('Erasing\n' + app.name + '...'); + E.showMessage(/*LANG*/'Erasing\n' + app.name + '...'); var info = store.readJSON(app.id + ".info", 1)||{}; if (files) eraseFiles(info); if (data) eraseData(info); } function eraseOne(app, files,data){ - E.showPrompt('Erase\n'+app.name+'?').then((v) => { + E.showPrompt(/*LANG*/'Erase\n'+app.name+'?').then((v) => { if (v) { Bangle.buzz(100, 1); eraseApp(app, files, data); @@ -84,7 +82,7 @@ function eraseOne(app, files,data){ }); } function eraseAll(apps, files,data) { - E.showPrompt('Erase all?').then((v) => { + E.showPrompt(/*LANG*/'Erase all?').then((v) => { if (v) { Bangle.buzz(100, 1); apps.forEach(app => eraseApp(app, files, data)); @@ -101,11 +99,11 @@ function showAppMenu(app) { '< Back': () => showApps(), }; if (app.hasData) { - appmenu['Erase Completely'] = () => eraseOne(app, true, true); - appmenu['Erase App,Keep Data'] = () => eraseOne(app, true, false); - appmenu['Only Erase Data'] = () => eraseOne(app, false, true); + appmenu[/*LANG*/'Erase Completely'] = () => eraseOne(app, true, true); + appmenu[/*LANG*/'Erase App,Keep Data'] = () => eraseOne(app, true, false); + appmenu[/*LANG*/'Only Erase Data'] = () => eraseOne(app, false, true); } else { - appmenu['Erase'] = () => eraseOne(app, true, false); + appmenu[/*LANG*/'Erase'] = () => eraseOne(app, true, false); } E.showMenu(appmenu); } @@ -113,7 +111,7 @@ function showAppMenu(app) { function showApps() { const appsmenu = { '': { - 'title': 'Apps', + 'title': /*LANG*/'Apps', }, '< Back': () => showMainMenu(), }; @@ -130,17 +128,17 @@ function showApps() { menu[app.name] = () => showAppMenu(app); return menu; }, appsmenu); - appsmenu['Erase All'] = () => { + appsmenu[/*LANG*/'Erase All'] = () => { E.showMenu({ - '': {'title': 'Erase All'}, - 'Erase Everything': () => eraseAll(list, true, true), - 'Erase Apps,Keep Data': () => eraseAll(list, true, false), - 'Only Erase Data': () => eraseAll(list, false, true), + '': {'title': /*LANG*/'Erase All'}, + /*LANG*/'Erase Everything': () => eraseAll(list, true, true), + /*LANG*/'Erase Apps,Keep Data': () => eraseAll(list, true, false), + /*LANG*/'Only Erase Data': () => eraseAll(list, false, true), '< Back': () => showApps(), }); }; } else { - appsmenu['...No Apps...'] = { + appsmenu[/*LANG*/'...No Apps...'] = { value: undefined, format: ()=> '', onchange: ()=> {} @@ -152,16 +150,16 @@ function showApps() { function showSortAppsMenu() { const sorterMenu = { '': { - 'title': 'App Sorter', + 'title': /*LANG*/'App Sorter', }, '< Back': () => showMainMenu(), - 'Sort: manually': ()=> showSortAppsManually(), - 'Sort: alph. ASC': () => { - E.showMessage('Sorting:\nAlphabetically\nascending ...'); + /*LANG*/'Sort: manually': ()=> showSortAppsManually(), + /*LANG*/'Sort: alph. ASC': () => { + E.showMessage(/*LANG*/'Sorting:\nAlphabetically\nascending ...'); sortAlphabet(false); }, 'Sort: alph. DESC': () => { - E.showMessage('Sorting:\nAlphabetically\ndescending ...'); + E.showMessage(/*LANG*/'Sorting:\nAlphabetically\ndescending ...'); sortAlphabet(true); } }; @@ -171,7 +169,7 @@ function showSortAppsMenu() { function showSortAppsManually() { const appsSorterMenu = { '': { - 'title': 'Sort: manually', + 'title': /*LANG*/'Sort: manually', }, '< Back': () => showSortAppsMenu(), }; @@ -188,7 +186,7 @@ function showSortAppsManually() { return menu; }, appsSorterMenu); } else { - appsSorterMenu['...No Apps...'] = { + appsSorterMenu[/*LANG*/'...No Apps...'] = { value: undefined, format: ()=> '', onchange: ()=> {} diff --git a/apps/files/metadata.json b/apps/files/metadata.json index ac73a7717..a53f914e6 100644 --- a/apps/files/metadata.json +++ b/apps/files/metadata.json @@ -1,7 +1,7 @@ { "id": "files", "name": "App Manager", - "version": "0.07", + "version": "0.08", "description": "Show currently installed apps, free space, and allow their deletion from the watch", "icon": "files.png", "tags": "tool,system,files", diff --git a/apps/flappy/ChangeLog b/apps/flappy/ChangeLog index 349cb9d07..d660f85aa 100644 --- a/apps/flappy/ChangeLog +++ b/apps/flappy/ChangeLog @@ -2,3 +2,4 @@ 0.03: A few tweaks to improve rendering speed 0.04: Add "ram" keyword to allow 2v06 Espruino builds to cache function that needs to be fast 0.05: Don't use Bangle.setLCDMode, just use offscreen buffer (allows widgets) +0.06: Bangle.js 2 enhancements - remove offscreen buffer and render direct diff --git a/apps/flappy/app.js b/apps/flappy/app.js index e9ca31fa5..70553fe97 100644 --- a/apps/flappy/app.js +++ b/apps/flappy/app.js @@ -1,19 +1,20 @@ -b = Graphics.createArrayBuffer(120,120,8); -var gimg = { - width:120, - height:104, - bpp:8, - buffer:b.buffer - }; - +var Y; if (process.env.HWVERSION==2) { - b.flip = function() { - g.drawImage(gimg,28,50); - }; + // we have offscreen graphics, so just go direct + b = g; + Y = 24; // widgets } else { + b = Graphics.createArrayBuffer(120,120,8); + var gimg = { + width:120, + height:104, + bpp:8, + buffer:b.buffer + }; b.flip = function() { g.drawImage(gimg,0,24,{scale:2}); }; + Y = 0; // we offset for widgets anyway } var BIRDIMG = E.toArrayBuffer(atob("EQyI/v7+/v7+/gAAAAAAAP7+/v7+/v7+/gYG0tLS0gDXAP7+/v7+/v4A0tLS0tIA19fXAP7+/v4AAAAA0tLS0gDX1wDXAP7+ANfX19cA0tLSANfXANcA/v4A19fX19cA0tLSANfX1wD+/gDS19fX0gDS0tLSAAAAAAD+/gDS0tIA0tLS0gDAwMDAwAD+/gAAAM3Nzc0AwAAAAAAA/v7+/v4Azc3Nzc0AwMDAwAD+/v7+/v4AAM3Nzc0AAAAAAP7+/v7+/v7+AAAAAP7+/v7+/g==")) @@ -30,14 +31,14 @@ function newBarrier(x) { barriers.push({ x1 : x-7, x2 : x+7, - y : 20+Math.random()*38, + y : Y+20+Math.random()*38, gap : 12+Math.random()*15 }); } function gameStart() { running = true; - birdy = 48/2; + birdy = Y + 48/2; birdvy = 0; barriers = []; for (var i=38;ibbot)) gameStop(); }); diff --git a/apps/flappy/metadata.json b/apps/flappy/metadata.json index 910797066..cb50f0094 100644 --- a/apps/flappy/metadata.json +++ b/apps/flappy/metadata.json @@ -1,7 +1,7 @@ { "id": "flappy", "name": "Flappy Bird", - "version": "0.05", + "version": "0.06", "description": "A Flappy Bird game clone", "icon": "app.png", "screenshots": [{"url":"screenshot1_flappy.png"},{"url":"screenshot2_flappy.png"}], diff --git a/apps/flipper/ChangeLog b/apps/flipper/ChangeLog index 9db0e26c5..2e94a2286 100644 --- a/apps/flipper/ChangeLog +++ b/apps/flipper/ChangeLog @@ -1 +1,2 @@ 0.01: first release +0.02: updated dark theme bg2 color value diff --git a/apps/flipper/flipper.app.js b/apps/flipper/app.js similarity index 95% rename from apps/flipper/flipper.app.js rename to apps/flipper/app.js index 7171306b1..ad5aa383c 100644 --- a/apps/flipper/flipper.app.js +++ b/apps/flipper/app.js @@ -18,7 +18,7 @@ function flipTheme() { if (!g.theme.dark) { upd({ fg:cl("#fff"), bg:cl("#000"), - fg2:cl("#0ff"), bg2:cl("#000"), + fg2:cl("#fff"), bg2:cl("#004"), fgH:cl("#fff"), bgH:cl("#00f"), dark:true }); diff --git a/apps/flipper/flipper.png b/apps/flipper/app.png similarity index 100% rename from apps/flipper/flipper.png rename to apps/flipper/app.png diff --git a/apps/flipper/flipper.icon.js b/apps/flipper/icon.js similarity index 100% rename from apps/flipper/flipper.icon.js rename to apps/flipper/icon.js diff --git a/apps/flipper/metadata.json b/apps/flipper/metadata.json index aac4f1643..366145d95 100644 --- a/apps/flipper/metadata.json +++ b/apps/flipper/metadata.json @@ -2,17 +2,17 @@ { "id": "flipper", "name": "flipper", - "version": "0.01", + "version": "0.02", "description": "Switch between dark and light theme and vice versa, combine with pattern launcher and swipe to flip.", "readme":"README.md", - "screenshots": [{"url":"flipper.png"}], - "icon": "flipper.png", + "screenshots": [{"url":"app.png"}], + "icon": "app.png", "type": "app", - "tags": "game", + "tags": "tool", "supports": ["BANGLEJS2"], "allow_emulator": true, "storage": [ - {"name":"flipper.app.js","url":"flipper.app.js"}, - {"name":"flipper.img","url":"flipper.icon.js","evaluate":true} + {"name":"flipper.app.js","url":"app.js"}, + {"name":"flipper.img","url":"icon.js","evaluate":true} ] } diff --git a/apps/floralclk/metadata.json b/apps/floralclk/metadata.json index d4848b0d8..33ab6b8ae 100644 --- a/apps/floralclk/metadata.json +++ b/apps/floralclk/metadata.json @@ -8,6 +8,7 @@ "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"floralclk.app.js","url":"app.js"}, diff --git a/apps/football/ChangeLog b/apps/football/ChangeLog new file mode 100644 index 000000000..66b9882cc --- /dev/null +++ b/apps/football/ChangeLog @@ -0,0 +1,2 @@ +1.00: Initial implementation +1.01: Bug fixes and performance and visual improvements diff --git a/apps/football/README.md b/apps/football/README.md new file mode 100644 index 000000000..f751b927e --- /dev/null +++ b/apps/football/README.md @@ -0,0 +1,3 @@ +# Classic Football Chronometer Game + +Context: https://www.anaitgames.com/analisis/analisis-casio-football-14 diff --git a/apps/football/app-icon.js b/apps/football/app-icon.js new file mode 100644 index 000000000..7eec578c6 --- /dev/null +++ b/apps/football/app-icon.js @@ -0,0 +1 @@ +atob("MDABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAA/wAAAAAA/wAAAAAA/wAAAAAA/wAAAAAA/wAAAAAA/wAAAAAA/wAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAA//AAAAAA//AAAAAA//AAAAAA//AAAAAADw8AD/AADw8AD/AADw8AD/AADw8AD/AP/wAAD/AP/wAAD/AP/wAAD/AP/wAAD/AA8PAAAAAA8PAAAAAA8PAAAAAA8PAAAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") diff --git a/apps/football/app.js b/apps/football/app.js new file mode 100644 index 000000000..d12f07e2b --- /dev/null +++ b/apps/football/app.js @@ -0,0 +1,474 @@ +// globals. TODO: move into an object +const digit = []; +let part = 0; +let endInc = 0; +var endGame = false; +let goalFrame = 0; +var stopped = true; +let score0 = 0; +let score1 = 0; +let inc = 0; +let msinc = 0; +let seq0 = 0; +let seq1 = 0; +let goaler = -1; +const w = g.getWidth(); +let owner = -1; + +const dash = { + width: 75, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AH4A/AH4A/AH4A/AH4A/AB0D/4AB+AJEBAX/BAk/CQ8PCQ4kDCQoIDCQgkDCQgkECQgIE4ASHFxH8JRgSEEgYSEPJAkEAH4A/AH4A/AH4A/AH4A/ACg=')) +}; + +function loadDigits () { + digit[0] = { + width: 80, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AH4AGn//AAngBIMfBIvABIMPBIuABIMHBIoIBg0DBAn+gYSBgIJE/kHBIOABIn4h4JB4F/BIfwj4JB8BQEAoIJBBoJOEv4JBEIJOEIwMHGoIJDIIIJBJIJOEBIQOCJwYJDOIR9DBISFCSIYJCTISlDBIXwBIZoBBP4J/BP4J/BNX+gED//gBIc/BIMB//ABIcf/gDB/+ABIcP/AhCBAYuBFoU+BIkDFoUcBIkBFoUIBIkAFogA/AAZPJMZJ3JRZKfIWZLHJbZL5bBP4J/BP4J/BKPgBIc/BIfABIcfBIeA/4AB/EPBIcBBIX8AwIJB/0DBIQECBIIOCAAQYBBIIiCAAQsBBIPwGwIAC4F/BIPgJQIACAoIJBBoIJDDIIJBJwZQDBIJODKAcAgxODKAZxBJwgABPYROEKASFDAAiRCJwhQCTYYAkA')) + }; + + digit[1] = { + width: 80, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AH4A2wAIHgIJIgYJIg4JIh4JIj4JIn4JIv4JI/4JHgIJIgYJIg4JIh4JIj4JIn4J/BP4J/BP4Jqj//BA0Ah/+BI8H/gJHgf4BI8B+AJHgHgBJFABJAA/J55jIO5KLJT5KzJY5L5nBP4J/BP4J/AAcfBJEPBJEHBJEDBJEBBJEABJN/BJE/BJEfBJEPBJEHBJEDBJEBBJEABJIA/AAwA=')) + }; + + digit[2] = { + width: 80, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AH4AGj//AAnwBIMPBIvgBIMHBIvABIMDBIuABIMBBIsBGQQIE/0DBIV/BIf8g4JCn4JD/EPKA/wj4JCKAngn4JCKAnAv4JCKAmA/4JCKAgEBQYZOEBIgADFgIJHIAKlJBI5oBBP4J/BP4J/BOcfBJEP/wJHg/8Aof/AAP+gf4BAUBBIX/gPwBIUDBIeA8AiDBIfAoA2DBIYSDJQQACEwZeCAAQ6DgF/BJATJE5I7IghPFBIUOMYomDO4g6EwCLDJwgiDAAhyFTohKEToheEBP4J/BP4J/BOHwBJHgBJHAv////8BImABAP//wJEAIIACBIf+BImABIX8g4JD4AJC/EPBIZACgfwj4JDKgUD8E/BIZoCgZODKAkDJwZQEgcBBIhQCgROEKAhOEKAhOEKAhOEKAgAm')) + }; + + digit[3] = { + width: 80, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AH4AGj//AAnwBIMPBIvgBIMHBIvABIMDBIuABIMBBIsBGQQIE/0DBIV/BIf8g4JCn4JD/EPKA/wj4JCKAngn4JCKAnAv4JCKAmA/4JCKAgEBQYZOEBIgADFgIJHIAKlJBI5oBBI58BBP4J/BP4J/BL8/BJEf/wJHh/8BI8H/AFD/4AB/0D+AICgIJDgPgBIUDBIQ5B4AiDBIeAwA2DBIYSDJQIJDEwZeCAAQ6DOQQACJwgTJE5I7JJ5JjEgIUDO4kDFAgJC/kDIwipNj4JIn7HIbZL5TBP4J/BP4J/BJs/BJEfBJEPBIgjB//8g4JDgIIB//+gYJDAgIACBwIJCDAIACwAJDFgIAC4F/IAgAC8E/KggAC+EfIgoAB/EPBIQIDKAROFKAZOGKAROGKAQJI4BOGKAQJI+CfHAEAA==')) + }; + digit[4] = { + width: 80, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AH4AswEBBJOABAoHBgPABIsDBIPgBIsHBIPwBIsPBIP4BIsfBIP8BIs/BIP+BIt/BIP/BIv/BIRQEAwQCBKAkDBIZQEg4JDKAkPBIZQEj4JIn4J/BP4J/BP4JjgAJFj//AYN/8AJDh/+C4QJEg/8C4XAv////+gYjCh+ABAIABgPwC4Q9BAAWAEYUCgYJD4FAFgYJDIAoJDEwRUDAARoGAAROCCZYnJHZJPJMZAABO46hCRYwAFT4YAFWYYAFY4YAFbYYJ/BP4J/BP4Jnj4JIh4JIg4JIgYJIgIJIgAJJv4JIn4JIj4JIh4JIg4JIgYJIgIJIgAJJAH4AGA=')) + }; + + digit[5] = { + width: 80, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AGUP/4AE/gJBg4JF/AJBgYJF+AJBgIJF8AJBwAJF4AJB4F/BImABIPgn4JEIoXwj4ID/wJC/BQEJwUA/hQEJwUA/xQEJwUA/5QEJwQJBKAhOCBIJQEJwQJBKAiVDFggAEIAgJFKgYJFNAYJ/BP4J/BP4Jmv/8BI8//AJHj/wBI8P8E//4ABBIcH4F/BIWABIUDwAIC//ABIUBgIJD8AeDgYJDGwkHBIZKEh4JDLwkfBIZyECZInJHZJPFkChEMYdwUIh3DFAiLDgKvIgbDIJQKvIUIgJFUIZ8FBP4J/BP4J/BL8PBJEHBJEDBJEBBIl//4ABwAJEBAQHBv4JCDAIAC8E/BIQsBAAXwj4JCIAIAC/EPBIRUBAAX8g4JCNAIAC/0DBI//gIJCJwZQCBIQIEKANAJwpQCJwxQCJwxQCJwxQCBJYAwA=')) + }; + + digit[6] = { + width: 80, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AGU//4AE8AJBj4JF4AJBh4JFwAJBg4JFBAMGgYIE/wSCgIJE/gJCwAJE/AJC4F/BIfwBIXgKAhOCg/wKAhOCg/4KAhOD/hQEOwUH/xQDJwRiCKAZOCBIRQDJwQJCGwQAEBIJKCBIxeCBP4J/BP4J/BOED//gBI0B//ABI0A/+ABI9/CoIAB/gJDnwpBAAP+BIccHoIACBIcIh4JDFgkfBIZAEBIhUEv4JDNAgJE/ATNn4nIHZBPGKARjFgIUCO4sDFASLFg48COQsPKARyGUILHGn6hBBIJyGco4J/BP4J/BP4Jm8AJDn4JD4AJDj4JDwAJDh4JDgP/AAP8AwIJB/0DBIQECBIIOCAAQYBBIP4EQIACwAJC+A2BAAXAv4JB8BKBAAQFBBIINBBIYZBBIIhBAAYtBBIJODKAcAgxODKAZnBJwhQCOIYAEPoROEKASZDAAilCJwhQCTYYAuA==')) + }; + + digit[7] = { + width: 80, + height: 128, + bpp: 4, + buffer : require("heatshrink").decompress(atob("AH4A/AEtVADdQE5Nf/4AayAnJgoma/J4LKDR2KKDZODUMadChKhiJwefE5RQXJwbxLKCxOEE5hQVJwgnNKCZOFE5pQTJwonOKCJOGE5xQRD4Q8EE5xQPJw4nPgFZzIAMqCdFE6IARJwgnhJwonhJwonhe5In/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4nlr4mE/NQE78FE4n1Ez5QGE0JQEJ0RQETsBQFJ0gABrJOkAH4A/AH4A/ADNZqAmkgv/yAnkr///JQjJwIABypOkAAP5J0oABUMJODKAShgEwhQiE/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/AA+fE80JE8xQGE8JQFE8JQFE8RQEE8RQEE8ZQDE8ZQDE8hQCE8hQCE8pQBE8pQBE80JE80AE84A/AH4A/AH4A/AAQA==")) + }; + + digit[8] = { + width: 80, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AGUf/4AE+AJBh4JF8AJBg4JF4AJBgYJFwAIBgIJFgOAgeABAn+A4MD4F/BIf8g4JB8E/BIf4h4JB+BQEAoIJBBoJOEn4JBEIJOEv4JBGoJOEAIIHBKAgEBBIRQDDAQJCKAYsCBISFCSIYJCTISlDBIX4BIZoBBP4J/BP4J/BNkB//wBIcf/4DB//gBIcP/wDBv/ABIcH/ghCwH/AAP+gYtCj4pBAAUBFoUOHoIACwAtCgkHBIfAoA2DBIZAEJQIACKgheBAARoGBKInJHZBPJMZJ3JRZKfJWZDHJfM4J/BP4J/BP4JP+AJDj4JD8AJDh4JD4AJDg4JDwH/AAP+AwQCBgIJCAgQJBBwQACDAIJB/giBAAXAv4JB/A2BAAXgn4JB+BKBAAQFBBIINBBIYZBBIIhBBIYtBBIJODKAYJBJwhQCwECJwhQCwBxCAAh9CJwhQCTIYAEUoROEKASbDAFwA=')) + }; + + digit[9] = { + width: 80, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AGUP/4AE/gJBg4JF/AJBgYJF+AJBgIJF8AJBwAJF4FAgHAv4JEwHAgHgn4JEgIJB+EfBAf+gYJB/BQE/kHBIIDBJwkPBIIXBJwkfBIIrBJwk/BIRQEJYIJCKAgOBBIXgIwYiBBIR7CQ4YJCR4SbDBISjCV4YJC/wJDFYIJ/BP4J/BP4Jjv/8BIcP/+AgE//AJDg//C4XwBIcDEYUP8E//4ABgIjCg/Av4JCwAjCgeABAQ5BuAJBgMBBIfgkAsDBIY2EIAIACJQhUBAAReENAIACOQgTJE5I7JJ5KhBMYwABO44ABRY4AFT4YAFWYYAFY4YAFbYYJ/BP4J/BP4Jnh4JIg4JIgYJIgIJEv//AAOABIgICA4N/BIQYBAAXgn4JCFgIAC+EfBIRABAAX4h4JCKgIAC/kHBIRoBAAX+gYJCn4JD/8BBIRODKAQJCBAhQBoBOFKAROGKAROGKAROGKAQJLAGA=')) + }; +} + +// sprites + +const left0 = { + width: 8, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob('ADAwEDgUcCgEAA==') +}; + +const left1 = { + width: 8, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob('ADAwEDh0ECAoAA==') +}; + +const left2 = { + width: 8, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob('ADAwEBg4EBg0AA==') +}; + +const left4 = { + width: 8, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob('ABgYVDgQEChEAA==') +}; + +const right0 = { + width: 8, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob('AAwMCBwoDhQgAA==') +}; + +const right1 = { + width: 8, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob('AAwMCBwuCAQUAA==') +}; + +const right2 = { + width: 8, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob('AAwMCBgcCBgsAA==') +}; + +const right4 = left4; + +const ball0 = { + width: 8, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAwMAAAAA==') +}; + +const ball1 = { + width: 8, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAGBgAAA==') +}; +const gol01 = { + width: 8, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob('ABhkhIS0sIAAAA==') +}; + +const gol11 = { + width: 8, + height: 10, + bpp: 1, + buffer: require('heatshrink').decompress(atob('gEYk0hkMthsBBAI=')) + +}; + +loadDigits(); + + +function printNumber (n, x, y, options) { + if (n > 9 || n < -1) { + console.log(n); + return; // error + } + if (digit.length === 0) { + g.setColor(1, 1, 1); + if (options.scale == 0.2) { + g.setFont12x20(1); + } else { + g.setFont12x20(2.5); + } + g.drawString('' + n, x, y); + return; + } + const img = (n == -1) ? dash : digit[n]; + if (img) { + // g.setColor(0,0,0); + // g.fillRect(x,y,x+32*options.scale,64*options.scale); + g.setColor(1, 1, 1); + g.drawImage(img, x, y, options); + } +} + +g.setBgColor(0, 0, 0); +g.clear(); +g.setColor(1, 1, 1); +function onStop () { + if (goalFrame > 0) { + return; + } + stopped = !stopped; + if (stopped) { + // Bangle.beep(); + if (msinc == 0) { + // GOOL + if (owner == 0) { + score0++; + goaler = 0; + } else if (owner == 1) { + score1++; + goaler = 1; + } + goalFrame = 5; + } + let newOwner = 0; + if (msinc % 2) { + newOwner = 1; + } else { + newOwner = 0; + } + if (newOwner) { + seq0--; + seq1++; + } else { + seq0++; + seq1--; + } + if (seq0 < 0) seq0 = 0; + if (seq0 > 3) seq0 = 3; + if (seq1 < 0) seq1 = 0; + if (seq1 > 3) seq1 = 3; + owner = newOwner; + } + refresh(); + refresh_ms(); +} + +function onButtonPress() { + console.log('on.tap'); + setWatch(() => { + onButtonPress(); +}, BTN1); + Bangle.beep(); + if (endGame) { + score0 = 0; + score1 = 0; + seq0 = 0; + seq1 = 0; + part = 0; + inc = 0; + msinc = 0; + stopped = true; + endGame = false; + } else { + if (inc == 0) { + // autogame(); + stopped = false; + } else { + onStop(); + } + } +} + +setWatch(() => { + onButtonPress(); +}, BTN1); +/*Bangle.on('tap', function () { + onButtonPress(); +}); +*/ +g.setFont12x20(3); + +function refresh () { + g.clear(); + if (inc > 59) { + inc = 0; + part++; + } + if (part >= 2 && inc > 30) { + part = 2; + Bangle.buzz(); + endGame = true; + endInc = inc; + inc = 0; + } + if (inc > 44) { + inc = 0; + if (part < 2) { + part++; + } + if (part >= 2) { + if (score0 != score1) { + Bangle.buzz(); + endGame = true; + endInc = inc; + inc = 0; + } + } + // end of 1st or 2nd part of the game? + } + let two = (inc < 10) ? '0' + inc : '' + inc; + if (endGame) { + g.setColor(0, 0, 0); + g.fillRect(0, 64, w, h); + if (inc % 2) { + two = (endInc < 10) ? '0' + endInc : '' + endInc; + printNumber(-1, 2, 64 + 16, { scale: 0.4 }); + printNumber(part, 34, 64 + 16, { scale: 0.4 }); + printNumber(two[0], 74, 64 + 16, { scale: 0.4 }); + printNumber(two[1], 74 + 32, 64 + 16, { scale: 0.4 }); + } + } else { + // seconds + printNumber(0, 2, 64 + 16, { scale: 0.4 }); + printNumber(part, 34, 64 + 16, { scale: 0.4 }); + printNumber(two[0], 74, 64 + 16, { scale: 0.4 }); + printNumber(two[1], 74 + 32, 64 + 16, { scale: 0.4 }); + } + refresh_ms(); + refresh_score(); + refresh_pixels(); +} + +function refresh_pixels () { + let frame4 = inc % 2; + if (goalFrame > 0) { + frame4 = goalFrame % 2; + if (goaler == 0) { + g.drawImage(frame4 ? right4 : right0, 20, 10, { scale: 5 }); + g.setColor(1, 1, 1); + g.drawImage(gol11, w - 50, 10, { scale: 5 }); + } else if (goaler == 1) { + g.drawImage(frame4 ? left0 : left4, w - 50, 10, { scale: 5 }); + g.setColor(1, 1, 1); + g.drawImage(gol01, 30, 10, { scale: 5 }); + } + return; + } + if (endGame) { + if (score0 > score1) { + g.drawImage(frame4 ? right1 : right0, 5 + (seq0 * 10), 10, { scale: 5 }); + } else if (score0 < score1) { + g.drawImage(frame4 ? left0 : left1, w - 30 - (seq1 * 10), 10, { scale: 5 }); + } + return; + } + g.drawImage(frame4 ? right1 : right0, 5 + (seq0 * 10), 10, { scale: 5 }); + g.drawImage(frame4 ? left0 : left1, w - 30 - (seq1 * 10), 10, { scale: 5 }); + let bx = (owner == 0) ? w / 3 : w / 2; + bx += 2; + g.drawImage(frame4 ? ball0 : ball1, bx, 10, { scale: 5 }); + const liney = 60; + if (owner) { + g.drawLine(w-8, liney, 2*(w/3), liney); + } else { + g.drawLine(8, liney, w/3, liney); + } +} +let dots = 0; +function refresh_dots () { + if (endGame) { + dots = 0; + } else { + dots++; + } + if (dots % 2) { + g.setColor(1, 1, 1); + } else { + g.setColor(0, 0, 0); + } + const x = 67; + let y = 100; + g.fillRect(x, y, x + 4, y + 4); + y += 16; + g.fillRect(x, y, x + 4, y + 4); +} + +const h = g.getHeight(); + +function refresh_ms () { + if (endGame) { + if (inc % 2) { + printNumber(-1, 80 + 64 - 4, 64 + 16 + 8 + 16, { scale: 0.2 }); + printNumber(-1, 80 + 64 + 16 - 4, 64 + 32 + 8, { scale: 0.2 }); + } + return; + } + // nanoseconds + if (msinc > 59) { + msinc = 0; + } + g.setColor(0, 0, 0); + g.fillRect(80 + 64, h / 2, 80 + 64 + 32, g.getHeight()); + const two = (msinc < 10) ? '0' + msinc : '' + msinc; + printNumber(two[0], 80 + 64 - 4, 64 + 16 + 8 + 16, { scale: 0.2 }); + printNumber(two[1], 80 + 64 + 16 - 4, 64 + 32 + 8, { scale: 0.2 }); +} + +function refresh_score () { + g.setColor(0, 0, 0); + g.fillRect(0, h - 32, w, h); + let two = (score0 < 10) ? '0' + score0 : '' + score0; + printNumber(two[0], 64 - 16, 32 + 64 + 16 + 8 + 16, { scale: 0.2 }); + printNumber(two[1], 64, 32 + 64 + 32 + 8, { scale: 0.2 }); + two = (score1 < 10) ? '0' + score1 : '' + score1; + printNumber(two[0], 32 + 64, 32 + 64 + 16 + 8 + 16, { scale: 0.2 }); + printNumber(two[1], 32 + 64 + 16, 32 + 64 + 32 + 8, { scale: 0.2 }); +} +refresh(); + +setInterval(function () { + if (!stopped || endGame) { + inc++; + } + if (goalFrame > 0) { + goalFrame--; + } + refresh(); +}, 1000); + +setInterval(function () { + refresh_dots(); +}, 500); + +setInterval(function () { + if (!stopped) { + msinc++; + if (msinc > 59) { + msinc = 0; + } + } +}, 10); + +setInterval(function () { + if (!stopped) { + refresh_ms(); + } +}, 250); + +function autogame () { + if (endGame) { + return; + } + onStop(); + if (stopped) { + setTimeout(autogame, 500); + } else { + setTimeout(autogame, 315 + 10 * (Math.random() * 5)); + } +} + +Bangle.setOptions({ lockTimeout: 0, backlightTimeout: 0 }); +// autogame(); + diff --git a/apps/football/app.png b/apps/football/app.png new file mode 100644 index 000000000..80d7cea15 Binary files /dev/null and b/apps/football/app.png differ diff --git a/apps/football/media/ball0.png b/apps/football/media/ball0.png new file mode 100644 index 000000000..5b890c180 Binary files /dev/null and b/apps/football/media/ball0.png differ diff --git a/apps/football/media/ball1.png b/apps/football/media/ball1.png new file mode 100644 index 000000000..c72e56189 Binary files /dev/null and b/apps/football/media/ball1.png differ diff --git a/apps/football/media/dash.png b/apps/football/media/dash.png new file mode 100644 index 000000000..6a9b0c4ac Binary files /dev/null and b/apps/football/media/dash.png differ diff --git a/apps/football/media/digit0.png b/apps/football/media/digit0.png new file mode 100644 index 000000000..33856cc5e Binary files /dev/null and b/apps/football/media/digit0.png differ diff --git a/apps/football/media/digit1.png b/apps/football/media/digit1.png new file mode 100644 index 000000000..53b914ded Binary files /dev/null and b/apps/football/media/digit1.png differ diff --git a/apps/football/media/digit2.png b/apps/football/media/digit2.png new file mode 100644 index 000000000..7a7787b05 Binary files /dev/null and b/apps/football/media/digit2.png differ diff --git a/apps/football/media/digit3.png b/apps/football/media/digit3.png new file mode 100644 index 000000000..a197d5993 Binary files /dev/null and b/apps/football/media/digit3.png differ diff --git a/apps/football/media/digit4.png b/apps/football/media/digit4.png new file mode 100644 index 000000000..f2810a0b6 Binary files /dev/null and b/apps/football/media/digit4.png differ diff --git a/apps/football/media/digit5.png b/apps/football/media/digit5.png new file mode 100644 index 000000000..d8027c362 Binary files /dev/null and b/apps/football/media/digit5.png differ diff --git a/apps/football/media/digit6.png b/apps/football/media/digit6.png new file mode 100644 index 000000000..bd7980045 Binary files /dev/null and b/apps/football/media/digit6.png differ diff --git a/apps/football/media/digit7.png b/apps/football/media/digit7.png new file mode 100644 index 000000000..9ef0df11a Binary files /dev/null and b/apps/football/media/digit7.png differ diff --git a/apps/football/media/digit8.png b/apps/football/media/digit8.png new file mode 100644 index 000000000..6916a301a Binary files /dev/null and b/apps/football/media/digit8.png differ diff --git a/apps/football/media/digit9.png b/apps/football/media/digit9.png new file mode 100644 index 000000000..d8d327523 Binary files /dev/null and b/apps/football/media/digit9.png differ diff --git a/apps/football/media/digits.png b/apps/football/media/digits.png new file mode 100644 index 000000000..68ace56af Binary files /dev/null and b/apps/football/media/digits.png differ diff --git a/apps/football/media/digits128.png b/apps/football/media/digits128.png new file mode 100644 index 000000000..f363a7e8e Binary files /dev/null and b/apps/football/media/digits128.png differ diff --git a/apps/football/media/digits64.png b/apps/football/media/digits64.png new file mode 100644 index 000000000..445c14dfa Binary files /dev/null and b/apps/football/media/digits64.png differ diff --git a/apps/football/media/gol00.png b/apps/football/media/gol00.png new file mode 100644 index 000000000..3b16aa967 Binary files /dev/null and b/apps/football/media/gol00.png differ diff --git a/apps/football/media/gol01.png b/apps/football/media/gol01.png new file mode 100644 index 000000000..3b16aa967 Binary files /dev/null and b/apps/football/media/gol01.png differ diff --git a/apps/football/media/gol10.png b/apps/football/media/gol10.png new file mode 100644 index 000000000..178b6fe3d Binary files /dev/null and b/apps/football/media/gol10.png differ diff --git a/apps/football/media/gol11.png b/apps/football/media/gol11.png new file mode 100644 index 000000000..732fa815d Binary files /dev/null and b/apps/football/media/gol11.png differ diff --git a/apps/football/media/left0.png b/apps/football/media/left0.png new file mode 100644 index 000000000..20599cbb7 Binary files /dev/null and b/apps/football/media/left0.png differ diff --git a/apps/football/media/left1.png b/apps/football/media/left1.png new file mode 100644 index 000000000..b6ffc22f9 Binary files /dev/null and b/apps/football/media/left1.png differ diff --git a/apps/football/media/left2.png b/apps/football/media/left2.png new file mode 100644 index 000000000..11ff8885f Binary files /dev/null and b/apps/football/media/left2.png differ diff --git a/apps/football/media/left4.png b/apps/football/media/left4.png new file mode 100644 index 000000000..a7301a4f8 Binary files /dev/null and b/apps/football/media/left4.png differ diff --git a/apps/football/media/right0.png b/apps/football/media/right0.png new file mode 100644 index 000000000..ac418ad7b Binary files /dev/null and b/apps/football/media/right0.png differ diff --git a/apps/football/media/right1.png b/apps/football/media/right1.png new file mode 100644 index 000000000..31554cbc3 Binary files /dev/null and b/apps/football/media/right1.png differ diff --git a/apps/football/media/right2.png b/apps/football/media/right2.png new file mode 100644 index 000000000..8c8d0ece4 Binary files /dev/null and b/apps/football/media/right2.png differ diff --git a/apps/football/media/right4.png b/apps/football/media/right4.png new file mode 100644 index 000000000..83a78b52c Binary files /dev/null and b/apps/football/media/right4.png differ diff --git a/apps/football/metadata.json b/apps/football/metadata.json new file mode 100644 index 000000000..43e7ac1bf --- /dev/null +++ b/apps/football/metadata.json @@ -0,0 +1,31 @@ +{ + "id": "football", + "name": "football", + "shortName": "football", + "version": "1.01", + "type": "app", + "description": "Classic football game of the CASIO chronometer", + "icon": "app.png", + "allow_emulator": true, + "tags": "games", + "supports": [ + "BANGLEJS2" + ], + "readme": "README.md", + "storage": [ + { + "name": "football.app.js", + "url": "app.js" + }, + { + "name": "football.img", + "url": "app-icon.js", + "evaluate": true + } + ], + "screenshots": [ + { + "url": "screenshot.png" + } + ] +} diff --git a/apps/football/screenshot.png b/apps/football/screenshot.png new file mode 100644 index 000000000..5742fe9e1 Binary files /dev/null and b/apps/football/screenshot.png differ diff --git a/apps/ftclock/.gitignore b/apps/ftclock/.gitignore index b384cf1f2..304792757 100644 --- a/apps/ftclock/.gitignore +++ b/apps/ftclock/.gitignore @@ -1,4 +1,5 @@ -timezonedb.csv.zip +TimeZoneDB.csv.zip country.csv -zone.csv -timezone.csv +time_zone.csv +database.sql +readme.txt diff --git a/apps/ftclock/ChangeLog b/apps/ftclock/ChangeLog index c944dd9ac..c30dae69f 100644 --- a/apps/ftclock/ChangeLog +++ b/apps/ftclock/ChangeLog @@ -1,2 +1,4 @@ 0.01: first release 0.02: RAM efficient version of `fourTwentyTz.js` (as suggested by @gfwilliams). +0.03: `mkFourTwentyTz.js` now handles new timezonedb.com CSV format +0.04: Tell clock widgets to hide. diff --git a/apps/ftclock/app.js b/apps/ftclock/app.js index b12db10f1..4f2cef895 100644 --- a/apps/ftclock/app.js +++ b/apps/ftclock/app.js @@ -33,6 +33,8 @@ function draw() { // Clear the screen once, at startup g.clear(); +// Show launcher when middle button pressed +Bangle.setUI("clock"); // Load widgets Bangle.loadWidgets(); Bangle.drawWidgets(); @@ -47,5 +49,4 @@ Bangle.on('lcdPower',on=>{ drawTimeout = undefined; } }); -// Show launcher when middle button pressed -Bangle.setUI("clock"); + diff --git a/apps/ftclock/fourTwentyTz.js b/apps/ftclock/fourTwentyTz.js index 5fa6cdab7..7ef7418f6 100644 --- a/apps/ftclock/fourTwentyTz.js +++ b/apps/ftclock/fourTwentyTz.js @@ -1,33 +1,33 @@ // Generated by mkFourTwentyTz.js -// Wed Jan 12 2022 19:35:36 GMT+0200 (Israel Standard Time) -// Data source: https://timezonedb.com/files/timezonedb.csv.zip +// Sun Mar 27 2022 14:10:08 GMT+0300 (Israel Daylight Time) +// Data source: https://timezonedb.com/files/TimeZoneDB.csv.zip exports.offsets = [1380,1320,1260,1200,1140,1080,1020,960,900,840,780,720,660,600,540,480,420,360,300,240,180,120,60,0]; exports.timezones = function(offs) { switch (offs) { - case 1380: return ["Cape Verde, Cabo Verde","Scoresbysund, Greenland","Azores, Portugal"]; - case 1320: return ["Noronha, Brazil","South Georgia, South Georgia and the South Sandwich Islands"]; - case 1260: return ["Palmer, Antarctica","Rothera, Antarctica","Buenos Aires, Argentina","Cordoba, Argentina","Salta, Argentina","Jujuy, Argentina","Tucuman, Argentina","Catamarca, Argentina","La Rioja, Argentina","San Juan, Argentina","Mendoza, Argentina","San Luis, Argentina","Rio Gallegos, Argentina","Ushuaia, Argentina","Belem, Brazil","Fortaleza, Brazil","Recife, Brazil","Araguaina, Brazil","Maceio, Brazil","Bahia, Brazil","Sao Paulo, Brazil","Santarem, Brazil","Santiago, Chile","Punta Arenas, Chile","Stanley, Falkland Islands (Malvinas)","Cayenne, French Guiana","Nuuk, Greenland","Miquelon, Saint Pierre and Miquelon","Asuncion, Paraguay","Paramaribo, Suriname","Montevideo, Uruguay"]; - case 1200: return ["Antigua, Antigua and Barbuda","Anguilla, Anguilla","Aruba, Aruba","Barbados, Barbados","St Barthelemy, Saint Barthélemy","Bermuda, Bermuda","La Paz, Bolivia (Plurinational State of)","Kralendijk, Bonaire, Sint Eustatius and Saba","Campo Grande, Brazil","Cuiaba, Brazil","Porto Velho, Brazil","Boa Vista, Brazil","Manaus, Brazil","Halifax, Canada","Glace Bay, Canada","Moncton, Canada","Goose Bay, Canada","Blanc-Sablon, Canada","Curacao, Curaçao","Dominica, Dominica","Santo Domingo, Dominican Republic","Grenada, Grenada","Thule, Greenland","Guadeloupe, Guadeloupe","Guyana, Guyana","St Kitts, Saint Kitts and Nevis","St Lucia, Saint Lucia","Marigot, Saint Martin (French part)","Martinique, Martinique","Montserrat, Montserrat","Puerto Rico, Puerto Rico","Lower Princes, Sint Maarten (Dutch part)","Port of_Spain, Trinidad and Tobago","St Vincent, Saint Vincent and the Grenadines","Caracas, Venezuela (Bolivarian Republic of)","Tortola, Virgin Islands (British)","St Thomas, Virgin Islands (U.S.)"]; - case 1140: return ["Eirunepe, Brazil","Rio Branco, Brazil","Nassau, Bahamas","Toronto, Canada","Nipigon, Canada","Thunder Bay, Canada","Iqaluit, Canada","Pangnirtung, Canada","Atikokan, Canada","Easter, Chile","Bogota, Colombia","Havana, Cuba","Guayaquil, Ecuador","Port-au-Prince, Haiti","Jamaica, Jamaica","Cayman, Cayman Islands","Cancun, Mexico","Panama, Panama","Lima, Peru","Grand Turk, Turks and Caicos Islands","New York, United States of America","Detroit, United States of America","Louisville, Kentucky","Monticello, Kentucky","Indianapolis, Indiana","Vincennes, Indiana","Winamac, Indiana","Marengo, Indiana","Petersburg, Indiana","Vevay, Indiana"]; - case 1080: return ["Belize, Belize","Winnipeg, Canada","Rainy River, Canada","Resolute, Canada","Rankin Inlet, Canada","Regina, Canada","Swift Current, Canada","Costa Rica, Costa Rica","Galapagos, Ecuador","Guatemala, Guatemala","Tegucigalpa, Honduras","Mexico City, Mexico","Merida, Mexico","Monterrey, Mexico","Matamoros, Mexico","Bahia Banderas, Mexico","Managua, Nicaragua","El Salvador, El Salvador","Chicago, United States of America","Tell City, Indiana","Knox, Indiana","Menominee, United States of America","Center, North Dakota","New_Salem, North Dakota","Beulah, North Dakota"]; - case 1020: return ["Edmonton, Canada","Cambridge Bay, Canada","Yellowknife, Canada","Inuvik, Canada","Creston, Canada","Dawson Creek, Canada","Fort Nelson, Canada","Whitehorse, Canada","Dawson, Canada","Mazatlan, Mexico","Chihuahua, Mexico","Ojinaga, Mexico","Hermosillo, Mexico","Denver, United States of America","Boise, United States of America","Phoenix, United States of America"]; - case 960: return ["Vancouver, Canada","Tijuana, Mexico","Pitcairn, Pitcairn","Los Angeles, United States of America"]; - case 900: return ["Gambier, French Polynesia","Anchorage, United States of America","Juneau, United States of America","Sitka, United States of America","Metlakatla, United States of America","Yakutat, United States of America","Nome, United States of America"]; - case 840: return ["Rarotonga, Cook Islands","Kiritimati, Kiribati","Tahiti, French Polynesia","Adak, United States of America","Honolulu, United States of America"]; - case 780: return ["McMurdo, Antarctica","Pago Pago, American Samoa","Fiji, Fiji","Kanton, Kiribati","Niue, Niue","Auckland, New Zealand","Fakaofo, Tokelau","Tongatapu, Tonga","Midway, United States Minor Outlying Islands","Apia, Samoa"]; - case 720: return ["Tarawa, Kiribati","Majuro, Marshall Islands","Kwajalein, Marshall Islands","Norfolk, Norfolk Island","Nauru, Nauru","Kamchatka, Russian Federation","Anadyr, Russian Federation","Funafuti, Tuvalu","Wake, United States Minor Outlying Islands","Wallis, Wallis and Futuna"]; - case 660: return ["Casey, Antarctica","Lord Howe, Australia","Macquarie, Australia","Hobart, Australia","Melbourne, Australia","Sydney, Australia","Pohnpei, Micronesia (Federated States of)","Kosrae, Micronesia (Federated States of)","Noumea, New Caledonia","Bougainville, Papua New Guinea","Magadan, Russian Federation","Sakhalin, Russian Federation","Srednekolymsk, Russian Federation","Guadalcanal, Solomon Islands","Efate, Vanuatu"]; - case 600: return ["DumontDUrville, Antarctica","Brisbane, Australia","Lindeman, Australia","Chuuk, Micronesia (Federated States of)","Guam, Guam","Saipan, Northern Mariana Islands","Port Moresby, Papua New Guinea","Vladivostok, Russian Federation","Ust-Nera, Russian Federation"]; - case 540: return ["Jayapura, Indonesia","Tokyo, Japan","Pyongyang, Korea (Democratic People's Republic of)","Seoul, Korea, Republic of","Palau, Palau","Chita, Russian Federation","Yakutsk, Russian Federation","Khandyga, Russian Federation","Dili, Timor-Leste"]; + case 1380: return ["Cape Verde, Cape Verde"]; + case 1320: return ["Noronha, Brazil","Nuuk, Greenland","South Georgia, South Georgia and the South Sandwich Islands","Miquelon, Saint Pierre and Miquelon"]; + case 1260: return ["Palmer, Antarctica","Rothera, Antarctica","Buenos Aires, Argentina","Cordoba, Argentina","Salta, Argentina","Jujuy, Argentina","Tucuman, Argentina","Catamarca, Argentina","La Rioja, Argentina","San Juan, Argentina","Mendoza, Argentina","San Luis, Argentina","Rio Gallegos, Argentina","Ushuaia, Argentina","Bermuda, Bermuda","Belem, Brazil","Fortaleza, Brazil","Recife, Brazil","Araguaina, Brazil","Maceio, Brazil","Bahia, Brazil","Sao Paulo, Brazil","Santarem, Brazil","Halifax, Canada","Glace Bay, Canada","Moncton, Canada","Goose Bay, Canada","Santiago, Chile","Punta Arenas, Chile","Stanley, Falkland Islands (Malvinas)","Cayenne, French Guiana","Thule, Greenland","Paramaribo, Suriname","Montevideo, Uruguay"]; + case 1200: return ["Antigua, Antigua and Barbuda","Anguilla, Anguilla","Aruba, Aruba","Barbados, Barbados","St Barthelemy, Saint Barthélemy","La Paz, Bolivia, Plurinational State of","Kralendijk, Bonaire, Sint Eustatius and Saba","Campo Grande, Brazil","Cuiaba, Brazil","Porto Velho, Brazil","Boa Vista, Brazil","Manaus, Brazil","Nassau, Bahamas","Blanc-Sablon, Canada","Toronto, Canada","Nipigon, Canada","Thunder Bay, Canada","Iqaluit, Canada","Pangnirtung, Canada","Havana, Cuba","Curacao, Curaçao","Dominica, Dominica","Santo Domingo, Dominican Republic","Grenada, Grenada","Guadeloupe, Guadeloupe","Guyana, Guyana","Port-au-Prince, Haiti","St Kitts, Saint Kitts and Nevis","St Lucia, Saint Lucia","Marigot, Saint Martin (French part)","Martinique, Martinique","Montserrat, Montserrat","Puerto Rico, Puerto Rico","Asuncion, Paraguay","Lower Princes, Sint Maarten (Dutch part)","Grand Turk, Turks and Caicos Islands","Port of_Spain, Trinidad and Tobago","New York, United States","Detroit, United States","Louisville, Kentucky","Monticello, Kentucky","Indianapolis, Indiana","Vincennes, Indiana","Winamac, Indiana","Marengo, Indiana","Petersburg, Indiana","Vevay, Indiana","St Vincent, Saint Vincent and the Grenadines","Caracas, Venezuela, Bolivarian Republic of","Tortola, Virgin Islands, British","St Thomas, Virgin Islands, U.S."]; + case 1140: return ["Eirunepe, Brazil","Rio Branco, Brazil","Atikokan, Canada","Winnipeg, Canada","Rainy River, Canada","Resolute, Canada","Rankin Inlet, Canada","Easter, Chile","Bogota, Colombia","Guayaquil, Ecuador","Jamaica, Jamaica","Cayman, Cayman Islands","Cancun, Mexico","Matamoros, Mexico","Panama, Panama","Lima, Peru","Chicago, United States","Tell City, Indiana","Knox, Indiana","Menominee, United States","Center, North Dakota","New_Salem, North Dakota","Beulah, North Dakota"]; + case 1080: return ["Belize, Belize","Regina, Canada","Swift Current, Canada","Edmonton, Canada","Cambridge Bay, Canada","Yellowknife, Canada","Inuvik, Canada","Costa Rica, Costa Rica","Galapagos, Ecuador","Guatemala, Guatemala","Tegucigalpa, Honduras","Mexico City, Mexico","Merida, Mexico","Monterrey, Mexico","Ojinaga, Mexico","Bahia Banderas, Mexico","Managua, Nicaragua","El Salvador, El Salvador","Denver, United States","Boise, United States"]; + case 1020: return ["Creston, Canada","Dawson Creek, Canada","Fort Nelson, Canada","Whitehorse, Canada","Dawson, Canada","Vancouver, Canada","Mazatlan, Mexico","Chihuahua, Mexico","Hermosillo, Mexico","Tijuana, Mexico","Phoenix, United States","Los Angeles, United States"]; + case 960: return ["Pitcairn, Pitcairn","Anchorage, United States","Juneau, United States","Sitka, United States","Metlakatla, United States","Yakutat, United States","Nome, United States"]; + case 900: return ["Gambier, French Polynesia","Adak, United States"]; + case 840: return ["Rarotonga, Cook Islands","Kiritimati, Kiribati","Tahiti, French Polynesia","Honolulu, United States"]; + case 780: return ["McMurdo, Antarctica","Pago Pago, American Samoa","Kanton, Kiribati","Niue, Niue","Auckland, New Zealand","Fakaofo, Tokelau","Tongatapu, Tonga","Midway, United States Minor Outlying Islands","Apia, Samoa"]; + case 720: return ["Fiji, Fiji","Tarawa, Kiribati","Majuro, Marshall Islands","Kwajalein, Marshall Islands","Norfolk, Norfolk Island","Nauru, Nauru","Kamchatka, Russian Federation","Anadyr, Russian Federation","Funafuti, Tuvalu","Wake, United States Minor Outlying Islands","Wallis, Wallis and Futuna"]; + case 660: return ["Casey, Antarctica","Lord Howe, Australia","Macquarie, Australia","Hobart, Australia","Melbourne, Australia","Sydney, Australia","Pohnpei, Micronesia, Federated States of","Kosrae, Micronesia, Federated States of","Noumea, New Caledonia","Bougainville, Papua New Guinea","Magadan, Russian Federation","Sakhalin, Russian Federation","Srednekolymsk, Russian Federation","Guadalcanal, Solomon Islands","Efate, Vanuatu"]; + case 600: return ["DumontDUrville, Antarctica","Brisbane, Australia","Lindeman, Australia","Chuuk, Micronesia, Federated States of","Guam, Guam","Saipan, Northern Mariana Islands","Port Moresby, Papua New Guinea","Vladivostok, Russian Federation","Ust-Nera, Russian Federation"]; + case 540: return ["Jayapura, Indonesia","Tokyo, Japan","Pyongyang, Korea, Democratic People's Republic of","Seoul, Korea, Republic of","Palau, Palau","Chita, Russian Federation","Yakutsk, Russian Federation","Khandyga, Russian Federation","Dili, Timor-Leste"]; case 480: return ["Perth, Australia","Brunei, Brunei Darussalam","Shanghai, China","Hong Kong, Hong Kong","Makassar, Indonesia","Ulaanbaatar, Mongolia","Choibalsan, Mongolia","Macau, Macao","Kuala Lumpur, Malaysia","Kuching, Malaysia","Manila, Philippines","Irkutsk, Russian Federation","Singapore, Singapore","Taipei, Taiwan, Province of China"]; case 420: return ["Davis, Antarctica","Christmas, Christmas Island","Jakarta, Indonesia","Pontianak, Indonesia","Phnom Penh, Cambodia","Vientiane, Lao People's Democratic Republic","Hovd, Mongolia","Novosibirsk, Russian Federation","Barnaul, Russian Federation","Tomsk, Russian Federation","Novokuznetsk, Russian Federation","Krasnoyarsk, Russian Federation","Bangkok, Thailand","Ho Chi_Minh, Viet Nam"]; case 360: return ["Vostok, Antarctica","Dhaka, Bangladesh","Thimphu, Bhutan","Urumqi, China","Chagos, British Indian Ocean Territory","Bishkek, Kyrgyzstan","Almaty, Kazakhstan","Qostanay, Kazakhstan","Omsk, Russian Federation"]; case 300: return ["Mawson, Antarctica","Qyzylorda, Kazakhstan","Aqtobe, Kazakhstan","Aqtau, Kazakhstan","Atyrau, Kazakhstan","Oral, Kazakhstan","Maldives, Maldives","Karachi, Pakistan","Yekaterinburg, Russian Federation","Kerguelen, French Southern Territories","Dushanbe, Tajikistan","Ashgabat, Turkmenistan","Samarkand, Uzbekistan","Tashkent, Uzbekistan"]; case 240: return ["Dubai, United Arab Emirates","Yerevan, Armenia","Baku, Azerbaijan","Tbilisi, Georgia","Mauritius, Mauritius","Muscat, Oman","Reunion, Réunion","Astrakhan, Russian Federation","Saratov, Russian Federation","Ulyanovsk, Russian Federation","Samara, Russian Federation","Mahe, Seychelles"]; - case 180: return ["Syowa, Antarctica","Bahrain, Bahrain","Minsk, Belarus","Djibouti, Djibouti","Asmara, Eritrea","Addis Ababa, Ethiopia","Baghdad, Iraq","Nairobi, Kenya","Comoro, Comoros","Kuwait, Kuwait","Antananarivo, Madagascar","Qatar, Qatar","Moscow, Russian Federation","Simferopol, Ukraine","Kirov, Russian Federation","Volgograd, Russian Federation","Riyadh, Saudi Arabia","Mogadishu, Somalia","Istanbul, Turkey","Dar es_Salaam, Tanzania, United Republic of","Kampala, Uganda","Aden, Yemen","Mayotte, Mayotte"]; - case 120: return ["Mariehamn, Åland Islands","Sofia, Bulgaria","Bujumbura, Burundi","Gaborone, Botswana","Lubumbashi, Congo, Democratic Republic of the","Nicosia, Cyprus","Famagusta, Cyprus","Tallinn, Estonia","Cairo, Egypt","Helsinki, Finland","Athens, Greece","Jerusalem, Israel","Amman, Jordan","Beirut, Lebanon","Maseru, Lesotho","Vilnius, Lithuania","Riga, Latvia","Tripoli, Libya","Chisinau, Moldova, Republic of","Blantyre, Malawi","Maputo, Mozambique","Windhoek, Namibia","Gaza, Palestine, State of","Hebron, Palestine, State of","Bucharest, Romania","Kaliningrad, Russian Federation","Kigali, Rwanda","Khartoum, Sudan","Juba, South Sudan","Damascus, Syrian Arab Republic","Mbabane, Eswatini","Kiev, Ukraine","Uzhgorod, Ukraine","Zaporozhye, Ukraine","Johannesburg, South Africa","Lusaka, Zambia","Harare, Zimbabwe"]; - case 60: return ["Andorra, Andorra","Tirane, Albania","Luanda, Angola","Vienna, Austria","Sarajevo, Bosnia and Herzegovina","Brussels, Belgium","Porto-Novo, Benin","Kinshasa, Congo, Democratic Republic of the","Bangui, Central African Republic","Brazzaville, Congo","Zurich, Switzerland","Douala, Cameroon","Prague, Czechia","Berlin, Germany","Busingen, Germany","Copenhagen, Denmark","Algiers, Algeria","El Aaiun, Western Sahara","Madrid, Spain","Ceuta, Spain","Paris, France","Libreville, Gabon","Gibraltar, Gibraltar","Malabo, Equatorial Guinea","Zagreb, Croatia","Budapest, Hungary","Rome, Italy","Vaduz, Liechtenstein","Luxembourg, Luxembourg","Casablanca, Morocco","Monaco, Monaco","Podgorica, Montenegro","Skopje, North Macedonia","Malta, Malta","Niamey, Niger","Lagos, Nigeria","Amsterdam, Netherlands","Oslo, Norway","Warsaw, Poland","Belgrade, Serbia","Stockholm, Sweden","Ljubljana, Slovenia","Longyearbyen, Svalbard and Jan Mayen","Bratislava, Slovakia","San Marino, San Marino","Ndjamena, Chad","Tunis, Tunisia","Vatican, Holy See"]; - case 0: return ["Troll, Antarctica","Ouagadougou, Burkina Faso","Abidjan, Côte d'Ivoire","Canary, Spain","Faroe, Faroe Islands","London, United Kingdom of Great Britain and Northern Ireland","Guernsey, Guernsey","Accra, Ghana","Danmarkshavn, Greenland","Banjul, Gambia","Conakry, Guinea","Bissau, Guinea-Bissau","Dublin, Ireland","Isle of_Man, Isle of Man","Reykjavik, Iceland","Jersey, Jersey","Monrovia, Liberia","Bamako, Mali","Nouakchott, Mauritania","Lisbon, Portugal","Madeira, Portugal","St Helena, Saint Helena, Ascension and Tristan da Cunha","Freetown, Sierra Leone","Dakar, Senegal","Sao Tome, Sao Tome and Principe","Lome, Togo"]; + case 180: return ["Syowa, Antarctica","Mariehamn, Åland Islands","Sofia, Bulgaria","Bahrain, Bahrain","Minsk, Belarus","Nicosia, Cyprus","Famagusta, Cyprus","Djibouti, Djibouti","Tallinn, Estonia","Asmara, Eritrea","Addis Ababa, Ethiopia","Helsinki, Finland","Athens, Greece","Jerusalem, Israel","Baghdad, Iraq","Amman, Jordan","Nairobi, Kenya","Comoro, Comoros","Kuwait, Kuwait","Beirut, Lebanon","Vilnius, Lithuania","Riga, Latvia","Chisinau, Moldova, Republic of","Antananarivo, Madagascar","Gaza, Palestine, State of","Hebron, Palestine, State of","Qatar, Qatar","Bucharest, Romania","Moscow, Russian Federation","Simferopol, Ukraine","Kirov, Russian Federation","Volgograd, Russian Federation","Riyadh, Saudi Arabia","Mogadishu, Somalia","Damascus, Syrian Arab Republic","Istanbul, Turkey","Dar es_Salaam, Tanzania, United Republic of","Kiev, Ukraine","Uzhgorod, Ukraine","Zaporozhye, Ukraine","Kampala, Uganda","Aden, Yemen","Mayotte, Mayotte"]; + case 120: return ["Andorra, Andorra","Tirane, Albania","Troll, Antarctica","Vienna, Austria","Sarajevo, Bosnia and Herzegovina","Brussels, Belgium","Bujumbura, Burundi","Gaborone, Botswana","Lubumbashi, Congo, the Democratic Republic of the","Zurich, Switzerland","Prague, Czech Republic","Berlin, Germany","Busingen, Germany","Copenhagen, Denmark","Cairo, Egypt","Madrid, Spain","Ceuta, Spain","Paris, France","Gibraltar, Gibraltar","Zagreb, Croatia","Budapest, Hungary","Rome, Italy","Vaduz, Liechtenstein","Maseru, Lesotho","Luxembourg, Luxembourg","Tripoli, Libya","Monaco, Monaco","Podgorica, Montenegro","Skopje, Macedonia, the Former Yugoslav Republic of","Malta, Malta","Blantyre, Malawi","Maputo, Mozambique","Windhoek, Namibia","Amsterdam, Netherlands","Oslo, Norway","Warsaw, Poland","Belgrade, Serbia","Kaliningrad, Russian Federation","Kigali, Rwanda","Khartoum, Sudan","Stockholm, Sweden","Ljubljana, Slovenia","Longyearbyen, Svalbard and Jan Mayen","Bratislava, Slovakia","San Marino, San Marino","Juba, South Sudan","Mbabane, Swaziland","Vatican, Holy See (Vatican City State)","Johannesburg, South Africa","Lusaka, Zambia","Harare, Zimbabwe"]; + case 60: return ["Luanda, Angola","Porto-Novo, Benin","Kinshasa, Congo, the Democratic Republic of the","Bangui, Central African Republic","Brazzaville, Congo","Douala, Cameroon","Algiers, Algeria","Canary, Spain","Faroe, Faroe Islands","Libreville, Gabon","London, United Kingdom","Guernsey, Guernsey","Malabo, Equatorial Guinea","Dublin, Ireland","Isle of_Man, Isle of Man","Jersey, Jersey","Niamey, Niger","Lagos, Nigeria","Lisbon, Portugal","Madeira, Portugal","Ndjamena, Chad","Tunis, Tunisia"]; + case 0: return ["Ouagadougou, Burkina Faso","Abidjan, Côte d'Ivoire","El Aaiun, Western Sahara","Accra, Ghana","Danmarkshavn, Greenland","Scoresbysund, Greenland","Banjul, Gambia","Conakry, Guinea","Bissau, Guinea-Bissau","Reykjavik, Iceland","Monrovia, Liberia","Casablanca, Morocco","Bamako, Mali","Nouakchott, Mauritania","Azores, Portugal","St Helena, Saint Helena, Ascension and Tristan da Cunha","Freetown, Sierra Leone","Dakar, Senegal","Sao Tome, Sao Tome and Principe","Lome, Togo"]; default: return ["Houston, we have a bug."]; } }; diff --git a/apps/ftclock/metadata.json b/apps/ftclock/metadata.json index 3cbf5ce66..96a4f84b9 100644 --- a/apps/ftclock/metadata.json +++ b/apps/ftclock/metadata.json @@ -1,7 +1,7 @@ { "id": "ftclock", "name": "Four Twenty Clock", - "version": "0.02", + "version": "0.04", "description": "A clock that tells when and where it's going to be 4:20 next", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}, {"url":"screenshot1.png"}], diff --git a/apps/ftclock/mkFourTwentyTz.js b/apps/ftclock/mkFourTwentyTz.js index 4571c15f7..4e7829aa3 100644 --- a/apps/ftclock/mkFourTwentyTz.js +++ b/apps/ftclock/mkFourTwentyTz.js @@ -4,7 +4,7 @@ let csv = require('csv'); let countries = {}, zones = {}, offsdict = {}, - now = Date.now(); // we need this to find zone's current DST state + now = Date.now()/1000; // we need this to find zone's current DST state function handleWrite(err,bytes) { if (err) { @@ -19,10 +19,10 @@ fs.createReadStream(__dirname+'/country.csv') countries[r[0]] = r[1]; }) .on('end', () => { - fs.createReadStream(__dirname+'/zone.csv') + fs.createReadStream(__dirname+'/time_zone.csv') .pipe(csv.parse()) .on('data', (r) => { - let parts = r[2].replace('_',' ').split('/'); + let parts = r[0].replace('_',' ').split('/'); let city = parts[parts.length-1]; let country =''; if (parts.length>2) { // e.g. America/North_Dakota/New_Salem @@ -30,59 +30,51 @@ fs.createReadStream(__dirname+'/country.csv') } else { country = countries[r[1]]; // e.g. United States } - zones[parseInt(r[0])] = {"name": `${city}, ${country}`}; + zone = zones[r[0]] || { "name": `${city}, ${country}` }; + let starttime = parseInt(r[3] || "0"), // Bugger. They're feeding us blanks for UTC now + offs = parseInt(r[4]); + if (offs<0) { + offs += 60*60*24; + } + if (starttime { - fs.createReadStream(__dirname+'/timezone.csv') - .pipe(csv.parse()) - .on('data', (r) => { - code = parseInt(r[0]); - if (!(code in zones)) return; - starttime = parseInt(r[2] || "0"); // Bugger. They're feeding us blanks for UTC now - offs = parseInt(r[3]); - if (offs<0) { - offs += 60*60*24; - } - zone = zones[code]; - if (starttime { - for (z in zones) { - zone = zones[z]; - if (zone.offs%60) continue; // One a dem funky timezones. Ignore. - zonelist = offsdict[zone.offs] || []; - zonelist.push(zone.name); - offsdict[zone.offs] = zonelist; - } - offsets = []; - for (o in offsdict) { - offsets.unshift(parseInt(o)); - } - fs.open("fourTwentyTz.js","w", (err, fd) => { - if (err) { - console.log("Can't open output file"); - return; - } - fs.write(fd, "// Generated by mkFourTwentyTz.js\n", handleWrite); - fs.write(fd, `// ${Date()}\n`, handleWrite); - fs.write(fd, "// Data source: https://timezonedb.com/files/timezonedb.csv.zip\n", handleWrite); - fs.write(fd, "exports.offsets = ", handleWrite); - fs.write(fd, JSON.stringify(offsets), handleWrite); - fs.write(fd, ";\n", handleWrite); - fs.write(fd, "exports.timezones = function(offs) {\n", handleWrite); - fs.write(fd, " switch (offs) {\n", handleWrite); - for (i=0; i { + if (err) { + console.log("Can't open output file"); + return; + } + fs.write(fd, "// Generated by mkFourTwentyTz.js\n", handleWrite); + fs.write(fd, `// ${Date()}\n`, handleWrite); + fs.write(fd, "// Data source: https://timezonedb.com/files/TimeZoneDB.csv.zip\n", handleWrite); + fs.write(fd, "exports.offsets = ", handleWrite); + fs.write(fd, JSON.stringify(offsets), handleWrite); + fs.write(fd, ";\n", handleWrite); + fs.write(fd, "exports.timezones = function(offs) {\n", handleWrite); + fs.write(fd, " switch (offs) {\n", handleWrite); + for (i=0; i{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +Bangle.setUI('clock'); +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/fuzzyw/fuzzyw.icon.js b/apps/fuzzyw/fuzzyw.icon.js new file mode 100644 index 000000000..acc7e2fcf --- /dev/null +++ b/apps/fuzzyw/fuzzyw.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgP/ABX8oYFD+AFE8AFE8IXE8YFKwFCj08h4FBocenEHCIPDjk4CoIFBhlwAoeMuIFEuBSBAoOI+AFD4HxGoQFB+AFD4P4uYFC8P4gYFD/w7BAFEfApfEj+B/Ecg/Ah8A+EMg/Dw0YseHj/Dw/8sfHAoPH/lhDoIFBwFwj4FB40AvkPAoU8v4dCAoIdDw04FIMP4EOgFwh47Bj8EvEfw/DJwgFXABY")) diff --git a/apps/fuzzyw/fuzzyw.png b/apps/fuzzyw/fuzzyw.png new file mode 100644 index 000000000..afd0b0f76 Binary files /dev/null and b/apps/fuzzyw/fuzzyw.png differ diff --git a/apps/fuzzyw/metadata.json b/apps/fuzzyw/metadata.json new file mode 100644 index 000000000..5d3045edb --- /dev/null +++ b/apps/fuzzyw/metadata.json @@ -0,0 +1,18 @@ +{ + "id":"fuzzyw", + "name":"Fuzzy Text Clock", + "shortName": "Fuzzy Text", + "version": "0.02", + "description": "An imprecise clock for when you're not in a rush", + "readme": "README.md", + "icon":"fuzzyw.png", + "screenshots": [{"url":"fuzzyw-light.png"},{"url":"fuzzyw-dark.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS", "BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"fuzzyw.app.js","url":"fuzzyw.app.js"}, + {"name":"fuzzyw.img","url":"fuzzyw.icon.js","evaluate":true} + ] +} diff --git a/apps/fwupdate/ChangeLog b/apps/fwupdate/ChangeLog index 06f84a11a..ea0b48eb9 100644 --- a/apps/fwupdate/ChangeLog +++ b/apps/fwupdate/ChangeLog @@ -5,3 +5,4 @@ 0.03: Improve bootloader update safety. Now sets unsafeFlash:1 to allow flash with 2v11 and later Add CRC checks for common bootloaders that we know don't work 0.04: Include a precompiled bootloader for easy bootloader updates +0.05: Rename Bootloader->DFU and add explanation to avoid confusion with Bootloader app diff --git a/apps/fwupdate/custom.html b/apps/fwupdate/custom.html index b3cd7e12d..31eb4a256 100644 --- a/apps/fwupdate/custom.html +++ b/apps/fwupdate/custom.html @@ -3,7 +3,7 @@ -

This tool allows you to update the bootloader on Bangle.js 2 devices +

This tool allows you to update the firmware on Bangle.js 2 devices from within the App Loader.

@@ -12,27 +12,41 @@ see the Bangle.js 1 instructions

    -

    Your current firmware version is unknown and bootloader is unknown

    +

    Your current firmware version is unknown and DFU is unknown

+
+ +
+
+
+
@@ -44,8 +97,23 @@ - + + diff --git a/apps/waypoints/lib.js b/apps/waypoints/lib.js new file mode 100644 index 000000000..88b402a73 --- /dev/null +++ b/apps/waypoints/lib.js @@ -0,0 +1,7 @@ +exports.load = (num) => { + return require("Storage").readJSON(`waypoints${num?"."+num:""}.json`)||[{name:"NONE"}]; +}; + +exports.save = (waypoints,num) => { + require("Storage").writeJSON(`waypoints${num?"."+num:""}.json`, waypoints); +}; diff --git a/apps/waypoints/metadata.json b/apps/waypoints/metadata.json new file mode 100644 index 000000000..d7fa00f7e --- /dev/null +++ b/apps/waypoints/metadata.json @@ -0,0 +1,20 @@ +{ "id": "waypoints", + "name": "Waypoints", + "version":"0.01", + "description": "Provides 'waypoints.json' used by various navigation apps, as well as a way to edit it from the App Loader with maps or a list", + "icon": "app.png", + "tags": "tool,outdoors,gps", + "type": "waypoints", + "supports" : ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "interface": "interface.html", + "storage": [ + {"name":"waypoints","url":"lib.js"} + ], + "data": [ + {"name":"waypoints.json","url":"waypoints.json"}, + {"name":"waypoints.1.json"}, + {"name":"waypoints.2.json"}, + {"name":"waypoints.3.json"} + ] +} diff --git a/apps/waypoints/waypoints.json b/apps/waypoints/waypoints.json new file mode 100644 index 000000000..066f05c10 --- /dev/null +++ b/apps/waypoints/waypoints.json @@ -0,0 +1,12 @@ +[ + { + "name":"No10", + "lat":51.5032, + "lon":-0.1269 + }, + { + "name":"Stone", + "lat":51.1788, + "lon":-1.8260 + } +] diff --git a/apps/wclock/ChangeLog b/apps/wclock/ChangeLog index 9a2ebdd5f..e50ee6842 100644 --- a/apps/wclock/ChangeLog +++ b/apps/wclock/ChangeLog @@ -1,2 +1,3 @@ 0.02: Modified for use with new bootloader and firmware 0.03: setUI and support for different screens +0.04: Tell clock widgets to hide. diff --git a/apps/wclock/clock-word.js b/apps/wclock/clock-word.js index aff134273..7ddb7bc35 100644 --- a/apps/wclock/clock-word.js +++ b/apps/wclock/clock-word.js @@ -122,11 +122,11 @@ Bangle.on('lcdPower', function(on) { if (on) drawWordClock(); }); +// Show launcher when button pressed +Bangle.setUI("clock"); + g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); setInterval(drawWordClock, 1E4); drawWordClock(); - -// Show launcher when button pressed -Bangle.setUI("clock"); diff --git a/apps/wclock/metadata.json b/apps/wclock/metadata.json index f22b53dc1..820af7ac0 100644 --- a/apps/wclock/metadata.json +++ b/apps/wclock/metadata.json @@ -1,7 +1,7 @@ { "id": "wclock", "name": "Word Clock", - "version": "0.03", + "version": "0.04", "description": "Display Time as Text", "icon": "clock-word.png", "screenshots": [{"url":"screenshot_word.png"}], diff --git a/apps/weather/ChangeLog b/apps/weather/ChangeLog index 101da48e1..f1d001c81 100644 --- a/apps/weather/ChangeLog +++ b/apps/weather/ChangeLog @@ -12,3 +12,10 @@ 0.13: Tweak Bangle.js 2 light theme colors 0.14: Use weather condition code for icon selection 0.15: Fix widget icon +0.16: Don't mark app as clock +0.17: Added clkinfo for clocks. +0.18: Added hasRange to clkinfo. +0.19: Added weather condition to clkinfo. +0.20: Added weather condition with temperature to clkinfo. +0.21: Updated clkinfo icon. +0.22: Automatic translation of strings, some left untranslated. diff --git a/apps/weather/app.js b/apps/weather/app.js index efd9b0209..8988c5002 100644 --- a/apps/weather/app.js +++ b/apps/weather/app.js @@ -16,10 +16,10 @@ var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [ {type: "txt", font: "12%", valign: -1, id: "tempUnit", label: "°C"}, ]}, {filly: 1}, - {type: "txt", font: "6x8", pad: 2, halign: 1, label: "Humidity"}, + {type: "txt", font: "6x8", pad: 2, halign: 1, label: /*LANG*/"Humidity"}, {type: "txt", font: "9%", pad: 2, halign: 1, id: "hum", label: "000%"}, {filly: 1}, - {type: "txt", font: "6x8", pad: 2, halign: -1, label: "Wind"}, + {type: "txt", font: "6x8", pad: 2, halign: -1, label: /*LANG*/"Wind"}, {type: "h", halign: -1, c: [ {type: "txt", font: "9%", pad: 2, id: "wind", label: "00"}, {type: "txt", font: "6x8", pad: 2, valign: -1, id: "windUnit", label: "km/h"}, @@ -27,22 +27,22 @@ var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [ ]}, ]}, {filly: 1}, - {type: "txt", font: "9%", wrap: true, height: g.getHeight()*0.18, fillx: 1, id: "cond", label: "Weather condition"}, + {type: "txt", font: "9%", wrap: true, height: g.getHeight()*0.18, fillx: 1, id: "cond", label: /*LANG*/"Weather condition"}, {filly: 1}, {type: "h", c: [ {type: "txt", font: "6x8", pad: 4, id: "loc", label: "Toronto"}, {fillx: 1}, - {type: "txt", font: "6x8", pad: 4, id: "updateTime", label: "15 minutes ago"}, + {type: "txt", font: "6x8", pad: 4, id: "updateTime", label: /*LANG*/"15 minutes ago"}, ]}, {filly: 1}, ]}, {lazy: true}); function formatDuration(millis) { let pluralize = (n, w) => n + " " + w + (n == 1 ? "" : "s"); - if (millis < 60000) return "< 1 minute"; - if (millis < 3600000) return pluralize(Math.floor(millis/60000), "minute"); - if (millis < 86400000) return pluralize(Math.floor(millis/3600000), "hour"); - return pluralize(Math.floor(millis/86400000), "day"); + if (millis < 60000) return /*LANG*/"< 1 minute"; + if (millis < 3600000) return pluralize(Math.floor(millis/60000), /*LANG*/"minute"); + if (millis < 86400000) return pluralize(Math.floor(millis/3600000), /*LANG*/"hour"); + return pluralize(Math.floor(millis/86400000), /*LANG*/"day"); } function draw() { @@ -57,7 +57,7 @@ function draw() { layout.windUnit.label = wind[2] + " " + (current.wrose||'').toUpperCase(); layout.cond.label = current.txt.charAt(0).toUpperCase()+(current.txt||'').slice(1); layout.loc.label = current.loc; - layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`; + layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`; // How to autotranslate this and similar? layout.update(); layout.render(); } @@ -77,9 +77,9 @@ function update() { } else { layout.forgetLazyState(); if (NRF.getSecurityStatus().connected) { - E.showMessage("Weather\nunknown\n\nIs Gadgetbridge\nweather\nreporting set\nup on your\nphone?"); + E.showMessage(/*LANG*/"Weather\nunknown\n\nIs Gadgetbridge\nweather\nreporting set\nup on your\nphone?"); } else { - E.showMessage("Weather\nunknown\n\nGadgetbridge\nnot connected"); + E.showMessage(/*LANG*/"Weather\nunknown\n\nGadgetbridge\nnot connected"); NRF.on("connect", update); } } @@ -101,7 +101,11 @@ weather.on("update", update); update(); -// Show launcher when middle button pressed +// We want this app to behave like a clock: +// i.e. show launcher when middle button pressed Bangle.setUI("clock"); +// But the app is not actually a clock +// This matters for widgets that hide themselves for clocks, like widclk or widclose +delete Bangle.CLOCK; Bangle.drawWidgets(); diff --git a/apps/weather/clkinfo.js b/apps/weather/clkinfo.js new file mode 100644 index 000000000..3cdd31c59 --- /dev/null +++ b/apps/weather/clkinfo.js @@ -0,0 +1,75 @@ +(function() { + var weather = { + temp: "?", + hum: "?", + wind: "?", + txt: "?", + }; + + var weatherJson = require("Storage").readJSON('weather.json'); + if(weatherJson !== undefined && weatherJson.weather !== undefined){ + weather = weatherJson.weather; + weather.temp = require("locale").temp(weather.temp-273.15); + weather.hum = weather.hum + "%"; + weather.wind = require("locale").speed(weather.wind).match(/^(\D*\d*)(.*)$/); + weather.wind = Math.round(weather.wind[1]) + "kph"; + } + + function weatherIcon(code) { + var ovr = Graphics.createArrayBuffer(24,24,1,{msb:true}); + require("weather").drawIcon({code:code},12,12,12,ovr); + var img = ovr.asImage(); + img.transparent = 0; + //for (var i=0;i ({ text: weather.temp, img: weatherIcon(weather.code), + v: parseInt(weather.temp), min: -30, max: 55}), + show: function() { this.emit("redraw"); }, + hide: function () {} + }, + { + name: "condition", + get: () => ({ text: weather.txt, img: weatherIcon(weather.code), + v: weather.code}), + show: function() { this.emit("redraw"); }, + hide: function () {} + }, + { + name: "temperature", + hasRange : true, + get: () => ({ text: weather.temp, img: atob("GBiBAAA8AAB+AADnAADDAADDAADDAADDAADDAADbAADbAADbAADbAADbAADbAAHbgAGZgAM8wAN+wAN+wAM8wAGZgAHDgAD/AAA8AA=="), + v: parseInt(weather.temp), min: -30, max: 55}), + show: function() { this.emit("redraw"); }, + hide: function () {} + }, + { + name: "humidity", + hasRange : true, + get: () => ({ text: weather.hum, img: atob("GBiBAAAEAAAMAAAOAAAfAAAfAAA/gAA/gAI/gAY/AAcfAA+AQA+A4B/A4D/B8D/h+D/j+H/n/D/n/D/n/B/H/A+H/AAH/AAD+AAA8A=="), + v: parseInt(weather.hum), min: 0, max: 100}), + show: function() { this.emit("redraw"); }, + hide: function () {} + }, + { + name: "wind", + hasRange : true, + get: () => ({ text: weather.wind, img: atob("GBiBAAHgAAPwAAYYAAwYAAwMfAAY/gAZh3/xg//hgwAAAwAABg///g//+AAAAAAAAP//wH//4AAAMAAAMAAYMAAYMAAMcAAP4AADwA=="), + v: parseInt(weather.wind), min: 0, max: 118}), + show: function() { this.emit("redraw"); }, + hide: function () {} + }, + ] + }; + + return weatherItems; +}) diff --git a/apps/weather/lib.js b/apps/weather/lib.js index 1d48116e1..8c59fd3e3 100644 --- a/apps/weather/lib.js +++ b/apps/weather/lib.js @@ -62,12 +62,29 @@ scheduleExpiry(storage.readJSON('weather.json')||{}); * @param x Left * @param y Top * @param r Icon Size + * @param ovr Graphics instance (or undefined for g) */ -exports.drawIcon = function(cond, x, y, r) { +exports.drawIcon = function(cond, x, y, r, ovr) { var palette; - + var monochrome=1; + if(!ovr) { + ovr = g; + monochrome=0; + } + if(monochrome) { + palette = { + sun: '#FFF', + cloud: '#FFF', + bgCloud: '#FFF', + rain: '#FFF', + lightning: '#FFF', + snow: '#FFF', + mist: '#FFF', + background: '#000' + }; + } else if (B2) { - if (g.theme.dark) { + if (ovr.theme.dark) { palette = { sun: '#FF0', cloud: '#FFF', @@ -89,7 +106,7 @@ exports.drawIcon = function(cond, x, y, r) { }; } } else { - if (g.theme.dark) { + if (ovr.theme.dark) { palette = { sun: '#FE0', cloud: '#BBB', @@ -113,19 +130,19 @@ exports.drawIcon = function(cond, x, y, r) { } function drawSun(x, y, r) { - g.setColor(palette.sun); - g.fillCircle(x, y, r); + ovr.setColor(palette.sun); + ovr.fillCircle(x, y, r); } function drawCloud(x, y, r, c) { const u = r/12; if (c==null) c = palette.cloud; - g.setColor(c); - g.fillCircle(x-8*u, y+3*u, 4*u); - g.fillCircle(x-4*u, y-2*u, 5*u); - g.fillCircle(x+4*u, y+0*u, 4*u); - g.fillCircle(x+9*u, y+4*u, 3*u); - g.fillPoly([ + ovr.setColor(c); + ovr.fillCircle(x-8*u, y+3*u, 4*u); + ovr.fillCircle(x-4*u, y-2*u, 5*u); + ovr.fillCircle(x+4*u, y+0*u, 4*u); + ovr.fillCircle(x+9*u, y+4*u, 3*u); + ovr.fillPoly([ x-8*u, y+7*u, x-8*u, y+3*u, x-4*u, y-2*u, @@ -137,19 +154,23 @@ exports.drawIcon = function(cond, x, y, r) { function drawBrokenClouds(x, y, r) { drawCloud(x+1/8*r, y-1/8*r, 7/8*r, palette.bgCloud); + if(monochrome) + drawCloud(x-1/8*r, y+2/16*r, r, palette.background); drawCloud(x-1/8*r, y+1/8*r, 7/8*r); } function drawFewClouds(x, y, r) { drawSun(x+3/8*r, y-1/8*r, 5/8*r); + if(monochrome) + drawCloud(x-1/8*r, y+2/16*r, r, palette.background); drawCloud(x-1/8*r, y+1/8*r, 7/8*r); } function drawRainLines(x, y, r) { - g.setColor(palette.rain); + ovr.setColor(palette.rain); const y1 = y+1/2*r; const y2 = y+1*r; - const poly = g.fillPolyAA ? p => g.fillPolyAA(p) : p => g.fillPoly(p); + const poly = ovr.fillPolyAA ? p => ovr.fillPolyAA(p) : p => ovr.fillPoly(p); poly([ x-6/12*r, y1, x-8/12*r, y2, @@ -182,8 +203,8 @@ exports.drawIcon = function(cond, x, y, r) { function drawThunderstorm(x, y, r) { function drawLightning(x, y, r) { - g.setColor(palette.lightning); - g.fillPoly([ + ovr.setColor(palette.lightning); + ovr.fillPoly([ x-2/6*r, y-r, x-4/6*r, y+1/6*r, x-1/6*r, y+1/6*r, @@ -194,8 +215,9 @@ exports.drawIcon = function(cond, x, y, r) { ]); } - drawBrokenClouds(x, y-1/3*r, r); + if(monochrome) drawBrokenClouds(x, y-1/3*r, r); drawLightning(x-1/12*r, y+1/2*r, 1/2*r); + drawBrokenClouds(x, y-1/3*r, r); } function drawSnow(x, y, r) { @@ -210,7 +232,7 @@ exports.drawIcon = function(cond, x, y, r) { } } - g.setColor(palette.snow); + ovr.setColor(palette.snow); const w = 1/12*r; for(let i = 0; i<=6; ++i) { const points = [ @@ -220,7 +242,7 @@ exports.drawIcon = function(cond, x, y, r) { x+w, y+r, ]; rotatePoints(points, x, y, i/3*Math.PI); - g.fillPoly(points); + ovr.fillPoly(points); for(let j = -1; j<=1; j += 2) { const points = [ @@ -231,7 +253,7 @@ exports.drawIcon = function(cond, x, y, r) { ]; rotatePoints(points, x, y+7/12*r, j/3*Math.PI); rotatePoints(points, x, y, i/3*Math.PI); - g.fillPoly(points); + ovr.fillPoly(points); } } } @@ -245,18 +267,18 @@ exports.drawIcon = function(cond, x, y, r) { [-0.2, 0.3], ]; - g.setColor(palette.mist); + ovr.setColor(palette.mist); for(let i = 0; i<5; ++i) { - g.fillRect(x+layers[i][0]*r, y+(0.4*i-0.9)*r, x+layers[i][1]*r, + ovr.fillRect(x+layers[i][0]*r, y+(0.4*i-0.9)*r, x+layers[i][1]*r, y+(0.4*i-0.7)*r-1); - g.fillCircle(x+layers[i][0]*r, y+(0.4*i-0.8)*r-0.5, 0.1*r-0.5); - g.fillCircle(x+layers[i][1]*r, y+(0.4*i-0.8)*r-0.5, 0.1*r-0.5); + ovr.fillCircle(x+layers[i][0]*r, y+(0.4*i-0.8)*r-0.5, 0.1*r-0.5); + ovr.fillCircle(x+layers[i][1]*r, y+(0.4*i-0.8)*r-0.5, 0.1*r-0.5); } } function drawUnknown(x, y, r) { drawCloud(x, y, r, palette.bgCloud); - g.setColor(g.theme.fg).setFontAlign(0, 0).setFont('Vector', r*2).drawString("?", x+r/10, y+r/6); + ovr.setColor(ovr.theme.fg).setFontAlign(0, 0).setFont('Vector', r*2).drawString("?", x+r/10, y+r/6); } /* diff --git a/apps/weather/metadata.json b/apps/weather/metadata.json index 1d0b6b469..7fefb7685 100644 --- a/apps/weather/metadata.json +++ b/apps/weather/metadata.json @@ -1,11 +1,11 @@ { "id": "weather", "name": "Weather", - "version": "0.15", + "version": "0.22", "description": "Show Gadgetbridge weather report", "icon": "icon.png", "screenshots": [{"url":"screenshot.png"}], - "tags": "widget,outdoors", + "tags": "widget,outdoors,clkinfo", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "readme.md", "storage": [ @@ -13,7 +13,8 @@ {"name":"weather.wid.js","url":"widget.js"}, {"name":"weather","url":"lib.js"}, {"name":"weather.img","url":"icon.js","evaluate":true}, - {"name":"weather.settings.js","url":"settings.js"} + {"name":"weather.settings.js","url":"settings.js"}, + {"name":"weather.clkinfo.js","url":"clkinfo.js"} ], "data": [{"name":"weather.json"}] } diff --git a/apps/weather/readme.md b/apps/weather/readme.md index 6d0ea04a5..2187ef061 100644 --- a/apps/weather/readme.md +++ b/apps/weather/readme.md @@ -11,6 +11,37 @@ You can view the full report through the app: 1. Install [Gadgetbridge for Android](https://f-droid.org/packages/nodomain.freeyourgadget.gadgetbridge/) on your phone. 2. Set up [Gadgetbridge weather reporting](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Weather). +If using the `Bangle.js Gadgetbridge` app on your phone (as opposed to the standard F-Droid `Gadgetbridge`) you need to set the package name +to `com.espruino.gadgetbridge.banglejs` in the settings of the weather app (`settings -> gadgetbridge support -> package name`). + +## Android Weather Apps + +There are two weather apps for Android that can connect with Gadgetbridge + * Tiny Weather Forecast Germany + ** F-Droid - https://f-droid.org/en/packages/de.kaffeemitkoffein.tinyweatherforecastgermany/ + ** Source code - https://codeberg.org/Starfish/TinyWeatherForecastGermany + * QuickWeather + ** F-Droid - https://f-droid.org/en/packages/com.ominous.quickweather/ + ** Google Play - https://play.google.com/store/apps/details?id=com.ominous.quickweather + ** Source code - https://github.com/TylerWilliamson/QuickWeather + + ### Tiny Weather Forecast Germany + Even though Tiny Weather Forecast Germany is made for Germany, it can be used around the world. To do this: + +1. Tap on the three dots in the top right hand corner and go to settings +2. Go down to Location and tap on the checkbox labeled "Use location services". You may also want to check on the "Check Location checkbox". Alternatively, you may select the "manual" checkbox and choose your location. +3. Scroll down further to the "other" section and tap "Gadgetbridge support". Then tap on "Enable". You may also choose to tap on "Send current time". +4. If you're using the specific Gadgetbridge for Bangle.JS app, you'll want to tap on "Package name." In the dialog box that appears, you'll want to put in "com.espruino.gadgetbridge.banglejs" without the quotes. If you're using the original Gadgetbridge, leave this as the default. + + +### QuickWeather +QuickWeather requires an OpenWeatherMap API. You will need the "One Call By Call" plan, which is free if you're not making too many calls. Sign up or get more information at https://openweathermap.org/api + +1. When you first load QuickWeather, it will take you through the setup process. You will fill out all the required information as well as put your API key in. If you do not have the "One Call By Call", or commonly known as "One Call", API, you will need to sign up for that. QuickWeather will work automatically with both the main version of Gadgetbridge and Gadgetbridge for bangle.JS. + +### Weather Notification +* Note - at one time, the Weather Notification app also worked with Gadgetbridge. However, many users are reporting it's no longer seeing the OpenWeatherMap API key as valid. The app has not received any updates since August of 2020, and may be unmaintained. + ## Settings * Expiration timespan can be set after which the local weather data is considered as invalid diff --git a/apps/weatherClock/app.js b/apps/weatherClock/app.js index 1a7f53f05..91d0ab36f 100644 --- a/apps/weatherClock/app.js +++ b/apps/weatherClock/app.js @@ -71,7 +71,6 @@ function chooseIconByCode(code) { case 801: return partSunIcon; default: return cloudIcon; } - break; default: return cloudIcon; } } diff --git a/apps/widChargingStatus/ChangeLog b/apps/widChargingStatus/ChangeLog index 1033c0cd3..5a6db5cb7 100644 --- a/apps/widChargingStatus/ChangeLog +++ b/apps/widChargingStatus/ChangeLog @@ -1 +1,2 @@ 0.01: First release. +0.02: No functional changes, just moved codebase to Typescript. diff --git a/apps/widChargingStatus/metadata.json b/apps/widChargingStatus/metadata.json index f68ccf5b4..573c594e7 100644 --- a/apps/widChargingStatus/metadata.json +++ b/apps/widChargingStatus/metadata.json @@ -2,7 +2,7 @@ "name": "Charging Status", "shortName":"ChargingStatus", "icon": "widget.png", - "version":"0.01", + "version":"0.02", "type": "widget", "description": "A simple widget that shows a yellow lightning icon to indicate whenever the watch is charging. This way one can see the charging status at a glance, no matter which battery widget is being used.", "tags": "widget", diff --git a/apps/widChargingStatus/widget.js b/apps/widChargingStatus/widget.js index 90f9199fa..5d9ea3837 100644 --- a/apps/widChargingStatus/widget.js +++ b/apps/widChargingStatus/widget.js @@ -1,31 +1,33 @@ -(() => { - const icon = require("heatshrink").decompress(atob("ikggMAiEAgYIBmEAg4EB+EAh0AgPggEeCAIEBnwQBAgP+gEP//x///j//8f//k///H//4BYOP/4lBv4bDvwEB4EAvAEBwEAuA7DCAI7BgAQBhEAA")); - const iconWidth = 18; - - function draw() { - g.reset(); - if (Bangle.isCharging()) { - g.setColor("#FD0"); - g.drawImage(icon, this.x + 1, this.y + 1, { - scale: 0.6875 - }); - } - } - - WIDGETS.chargingStatus = { - area: 'tr', - width: Bangle.isCharging() ? iconWidth : 0, - draw: draw, - }; - - Bangle.on('charging', (charging) => { - if (charging) { - Bangle.buzz(); - WIDGETS.chargingStatus.width = iconWidth; - } else { - WIDGETS.chargingStatus.width = 0; - } - Bangle.drawWidgets(); // re-layout widgets - g.flip(); - }); -})(); \ No newline at end of file +"use strict"; +(() => { + const icon = require('heatshrink').decompress(atob('ikggMAiEAgYIBmEAg4EB+EAh0AgPggEeCAIEBnwQBAgP+gEP//x///j//8f//k///H//4BYOP/4lBv4bDvwEB4EAvAEBwEAuA7DCAI7BgAQBhEAA')); + const iconWidth = 18; + function draw() { + g.reset(); + if (Bangle.isCharging()) { + g.setColor('#FD0'); + g.drawImage(icon, this.x + 1, this.y + 1, { + scale: 0.6875, + }); + } + } + WIDGETS.chargingStatus = { + area: 'tr', + width: Bangle.isCharging() ? iconWidth : 0, + draw: draw, + }; + Bangle.on('charging', (charging) => { + const widget = WIDGETS.chargingStatus; + if (widget) { + if (charging) { + Bangle.buzz(); + widget.width = iconWidth; + } + else { + widget.width = 0; + } + Bangle.drawWidgets(); // re-layout widgets + g.flip(); + } + }); +})(); diff --git a/apps/widChargingStatus/widget.ts b/apps/widChargingStatus/widget.ts new file mode 100644 index 000000000..14b4df4a4 --- /dev/null +++ b/apps/widChargingStatus/widget.ts @@ -0,0 +1,38 @@ +(() => { + const icon = require('heatshrink').decompress( + atob( + 'ikggMAiEAgYIBmEAg4EB+EAh0AgPggEeCAIEBnwQBAgP+gEP//x///j//8f//k///H//4BYOP/4lBv4bDvwEB4EAvAEBwEAuA7DCAI7BgAQBhEAA' + ) + ); + const iconWidth = 18; + + function draw(this: { x: number; y: number }) { + g.reset(); + if (Bangle.isCharging()) { + g.setColor('#FD0'); + g.drawImage(icon, this.x + 1, this.y + 1, { + scale: 0.6875, + }); + } + } + + WIDGETS.chargingStatus = { + area: 'tr', + width: Bangle.isCharging() ? iconWidth : 0, + draw: draw, + }; + + Bangle.on('charging', (charging) => { + const widget = WIDGETS.chargingStatus; + if (widget) { + if (charging) { + Bangle.buzz(); + widget.width = iconWidth; + } else { + widget.width = 0; + } + Bangle.drawWidgets(); // re-layout widgets + g.flip(); + } + }); +})(); diff --git a/apps/wid_edit/ChangeLog b/apps/wid_edit/ChangeLog index 2fa857bd8..d8e165029 100644 --- a/apps/wid_edit/ChangeLog +++ b/apps/wid_edit/ChangeLog @@ -1 +1,4 @@ -0.01: new Widget Editor! \ No newline at end of file +0.01: new Widget Editor! +0.02: Wrap loadWidgets instead of replacing to keep original functionality intact + Change back entry to menu option + Allow changing widgets into all areas, including bottom widget bar diff --git a/apps/wid_edit/boot.js b/apps/wid_edit/boot.js index 872965c97..3cb545a34 100644 --- a/apps/wid_edit/boot.js +++ b/apps/wid_edit/boot.js @@ -1,24 +1,20 @@ -Bangle.loadWidgets = function() { - global.WIDGETS={}; - require("Storage").list(/\.wid\.js$/) - .forEach(w=>{ - try { eval(require("Storage").read(w)); } - catch (e) { print(w, e); } - }); - const s = require("Storage").readJSON("wid_edit.json", 1) || {}, - c = s.custom || {}; +Bangle.loadWidgets = (o => ()=>{ + o(); + const s = require("Storage").readJSON("wid_edit.json", 1) || {}; + const c = s.custom || {}; for (const w in c){ if (!(w in WIDGETS)) continue; // widget no longer exists // store defaults of customized values in _WIDGETS - global._WIDGETS=global._WIDGETS||{}; - _WIDGETS[w] = {}; - Object.keys(c[w]).forEach(k => _WIDGETS[w][k] = WIDGETS[w][k]); - Object.assign(WIDGETS[w], c[w]); + if (!global._WIDGETS) global._WIDGETS = {}; + if (!global._WIDGETS[w]) global._WIDGETS[w] = {}; + Object.keys(c[w]).forEach(k => global._WIDGETS[w][k] = global.WIDGETS[w][k]); + //overide values in widget with configured ones + Object.assign(global.WIDGETS[w], c[w]); } - const W = WIDGETS; - WIDGETS = {}; + const W = global.WIDGETS; + global.WIDGETS = {}; Object.keys(W) .sort() .sort((a, b) => (0|W[b].sortorder)-(0|W[a].sortorder)) - .forEach(k => WIDGETS[k] = W[k]); -} \ No newline at end of file + .forEach(k => global.WIDGETS[k] = W[k]); +})(Bangle.loadWidgets); diff --git a/apps/wid_edit/metadata.json b/apps/wid_edit/metadata.json index 66d0192f6..d963a53d0 100644 --- a/apps/wid_edit/metadata.json +++ b/apps/wid_edit/metadata.json @@ -1,6 +1,6 @@ { "id": "wid_edit", - "version": "0.01", + "version": "0.02", "name": "Widget Editor", "icon": "icon.png", "description": "Customize widget locations", diff --git a/apps/wid_edit/settings.js b/apps/wid_edit/settings.js index 0969ed533..1d34ae0ca 100644 --- a/apps/wid_edit/settings.js +++ b/apps/wid_edit/settings.js @@ -9,7 +9,7 @@ let cleanup = false; for (const id in settings.custom) { - if (!(id in WIDGETS)) { + if (!(id in global.WIDGETS)) { // widget which no longer exists cleanup = true; delete settings.custom[id]; @@ -24,12 +24,12 @@ * Sort & redraw all widgets */ function redrawWidgets() { - let W = WIDGETS; + let W = global.WIDGETS; global.WIDGETS = {}; Object.keys(W) .sort() .sort((a, b) => (0|W[b].sortorder)-(0|W[a].sortorder)) - .forEach(k => {WIDGETS[k] = W[k]}); + .forEach(k => {global.WIDGETS[k] = W[k];}); Bangle.drawWidgets(); } @@ -52,9 +52,9 @@ } function edit(id) { - let WIDGET = WIDGETS[id], + let WIDGET = global.WIDGETS[id], def = {area: WIDGET.area, sortorder: WIDGET.sortorder|0}; // default values - Object.assign(def, _WIDGETS[id]||{}); // defaults were saved in _WIDGETS + Object.assign(def, global._WIDGETS[id]||{}); // defaults were saved in _WIDGETS settings.custom = settings.custom||{}; let saved = settings.custom[id] || {}, @@ -102,7 +102,7 @@ settings.custom = settings.custom || {}; settings.custom[id] = saved; } else if (settings.custom) { - delete settings.custom[id] + delete settings.custom[id]; } if (!Object.keys(settings.custom).length) delete settings.custom; require("Storage").writeJSON("wid_edit.json", settings); @@ -112,8 +112,8 @@ let _W = {}; if (saved.area) _W.area = def.area; if ('sortorder' in saved) _W.sortorder = def.sortorder; - if (Object.keys(_W).length) _WIDGETS[id] = _W; - else delete _WIDGETS[id]; + if (Object.keys(_W).length) global._WIDGETS[id] = _W; + else delete global._WIDGETS[id]; // drawWidgets won't clear e.g. bottom bar if we just disabled the last bottom widget redrawWidgets(); @@ -122,17 +122,23 @@ m.draw(); } + const AREA_NAMES = [ "Top left", "Top right", "Bottom left", "Bottom right" ]; + const AREAS = [ "tl", "tr", "bl", "br" ]; + const menu = { - "": {"title": name(id)}, - /*LANG*/"< Back": () => { - redrawWidgets(); - mainMenu(); - }, - /*LANG*/"Side": { - value: (area === 'tl'), - format: tl => tl ? /*LANG*/"Left" : /*LANG*/"Right", - onchange: tl => { - area = tl ? "tl" : "tr"; + "": {"title": name(id), + back: () => { + redrawWidgets(); + mainMenu(); + } }, + /*LANG*/"Position": { + value: AREAS.indexOf(area), + format: v => AREA_NAMES[v], + min: 0, + max: AREAS.length - 1, + onchange: v => { + print("v", v); + area = AREAS[v]; save(); } }, @@ -149,7 +155,7 @@ save(); mainMenu(); // changing multiple values made the rest of the menu wrong, so take the easy out } - } + }; let m = E.showMenu(menu); } @@ -157,31 +163,32 @@ function mainMenu() { let menu = { - "": {"title": /*LANG*/"Widgets"}, + "": { + "title": /*LANG*/"Widgets", + back: ()=>{ + if (!Object.keys(global._WIDGETS).length) delete global._WIDGETS; // no defaults to remember + back(); + }}, }; - menu[/*LANG*/"< Back"] = ()=>{ - if (!Object.keys(_WIDGETS).length) delete _WIDGETS; // no defaults to remember - back(); - }; - Object.keys(WIDGETS).forEach(id=>{ + Object.keys(global.WIDGETS).forEach(id=>{ // mark customized widgets with asterisk - menu[name(id)+((id in _WIDGETS) ? " *" : "")] = () => edit(id); + menu[name(id)+((id in global._WIDGETS) ? " *" : "")] = () => edit(id); }); - if (Object.keys(_WIDGETS).length) { // only show reset if there is anything to reset + if (Object.keys(global._WIDGETS).length) { // only show reset if there is anything to reset menu[/*LANG*/"Reset All"] = () => { E.showPrompt(/*LANG*/"Reset all widgets?").then(confirm => { if (confirm) { delete settings.custom; require("Storage").writeJSON("wid_edit.json", settings); - for(let id in _WIDGETS) { - Object.assign(WIDGETS[id], _WIDGETS[id]) // restore defaults + for(let id in global._WIDGETS) { + Object.assign(global.WIDGETS[id], global._WIDGETS[id]); // restore defaults } global._WIDGETS = {}; redrawWidgets(); } mainMenu(); // reload with reset widgets - }) - } + }); + }; } E.showMenu(menu); diff --git a/apps/widadjust/ChangeLog b/apps/widadjust/ChangeLog new file mode 100644 index 000000000..9b2a8d3c8 --- /dev/null +++ b/apps/widadjust/ChangeLog @@ -0,0 +1,2 @@ +0.01: New widget +0.02: Use default Bangle formatter for booleans diff --git a/apps/widadjust/README.md b/apps/widadjust/README.md new file mode 100644 index 000000000..4f89af54b --- /dev/null +++ b/apps/widadjust/README.md @@ -0,0 +1,57 @@ +# Adjust Clock + +Adjusts clock continually in the background to counter clock drift. + +## Usage + +First you need to determine the clock drift of your watch in PPM (parts per million). + +For example if you measure that your watch clock is too fast by 5 seconds in 24 hours, +then PPM is `5 / (24*60*60) * 1000000 = 57.9`. + +Then set PPM in settings and this widget will continually adjust the clock by that amount. + +## Settings + +See **Basic logic** below for more details. + +- **PPM x 10** - change PPM in steps of 10 +- **PPM x 1** - change PPM in steps of 1 +- **PPM x 0.1** - change PPM in steps of 0.1 +- **Update Interval** - How often to update widget and clock error. +- **Threshold** - Threshold for adjusting clock. + When clock error exceeds this threshold, clock is adjusted with `setTime`. +- **Save State** - If `On` clock error state is saved to file when widget exits, if needed. + That is recommended and default setting. + If `Off` clock error state is forgotten and reset to 0 whenever widget is restarted, + for example when going to Launcher. This can cause significant inaccuracy especially + with large **Update Interval** or **Threshold**. +- **Debug Log** - If `On` some debug information is logged to file `widadjust.log`. + +## Display + +Widget shows clock error in milliseconds and PPM. + +## Basic logic + +- When widget starts, clock error state is loaded from file `widadjust.state`. +- While widget is running, widget display and clock error is updated + periodically (**Update Interval**) according to **PPM**. +- When clock error exceeds **Threshold** clock is adjusted with `setTime`. +- When widget exists, clock error state is saved to file `widadjust.state` if needed. + +## Services + +Other apps/widgets can use `WIDGETS.adjust.now()` to request current adjusted time. +To support also case where this widget isn't present, the following code can be used: + +``` +function adjustedNow() { + return WIDGETS.adjust ? WIDGETS.adjust.now() : Date.now(); +} +``` + +## Acknowledgment + +Uses [Clock Settings](https://icons8.com/icon/tQvI71EfIWy3/clock-settings) +icon by [Icons8](https://icons8.com). diff --git a/apps/widadjust/icon.png b/apps/widadjust/icon.png new file mode 100644 index 000000000..f97394618 Binary files /dev/null and b/apps/widadjust/icon.png differ diff --git a/apps/widadjust/metadata.json b/apps/widadjust/metadata.json new file mode 100644 index 000000000..cef91369f --- /dev/null +++ b/apps/widadjust/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "widadjust", + "name": "Adjust Clock", + "icon": "icon.png", + "version": "0.02", + "description": "Adjusts clock continually in the background to counter clock drift", + "type": "widget", + "tags": "widget", + "supports": [ "BANGLEJS", "BANGLEJS2" ], + "readme": "README.md", + "storage": [ + { "name": "widadjust.wid.js", "url": "widget.js" }, + { "name": "widadjust.settings.js", "url": "settings.js" } + ], + "data": [ + { "name": "widadjust.json" }, + { "name": "widadjust.state" } + ] +} diff --git a/apps/widadjust/settings.js b/apps/widadjust/settings.js new file mode 100644 index 000000000..6743c7fc5 --- /dev/null +++ b/apps/widadjust/settings.js @@ -0,0 +1,114 @@ +(function(back) { + const SETTINGS_FILE = 'widadjust.json'; + const STATE_FILE = 'widadjust.state'; + + const DEFAULT_ADJUST_THRESHOLD = 100; + let thresholdV = [ 10, 25, 50, 100, 250, 500, 1000 ]; + + const DEFAULT_UPDATE_INTERVAL = 60000; + let intervalV = [ 10000, 30000, 60000, 180000, 600000, 1800000, 3600000 ]; + let intervalN = [ "10 s", "30 s", "1 m", "3 m", "10 m", "30 m", "1 h" ]; + + let stateFileErased = false; + + let settings = Object.assign({ + advanced: false, + saveState: true, + debugLog: false, + ppm: 0, + adjustThreshold: DEFAULT_ADJUST_THRESHOLD, + updateInterval: DEFAULT_UPDATE_INTERVAL, + }, require('Storage').readJSON(SETTINGS_FILE, true) || {}); + + if (thresholdV.indexOf(settings.adjustThreshold) == -1) { + settings.adjustThreshold = DEFAULT_ADJUST_THRESHOLD; + } + + if (intervalV.indexOf(settings.updateInterval) == -1) { + settings.updateInterval = DEFAULT_UPDATE_INTERVAL; + } + + function onPpmChange(v) { + settings.ppm = v; + mainMenu['PPM x 10' ].value = v; + mainMenu['PPM x 1' ].value = v; + mainMenu['PPM x 0.1'].value = v; + } + + let mainMenu = { + '': { 'title' : 'Adjust Clock' }, + + '< Back': () => { + require('Storage').writeJSON(SETTINGS_FILE, settings); + back(); + }, + + /* + // NOT FULLY WORKING YET + 'Mode': { + value: settings.advanced, + format: v => v ? 'Advanced' : 'Basic', + onchange: () => { + settings.advanced = !settings.advanced; + } + }, + */ + + 'PPM x 10' : { + value: settings.ppm, + format: v => v.toFixed(1), + step: 10, + onchange : onPpmChange, + }, + + 'PPM x 1' : { + value: settings.ppm, + format: v => v.toFixed(1), + step: 1, + onchange : onPpmChange, + }, + + 'PPM x 0.1' : { + value: settings.ppm, + format: v => v.toFixed(1), + step: 0.1, + onchange : onPpmChange, + }, + + 'Update Interval': { + value: intervalV.indexOf(settings.updateInterval), + min: 0, + max: intervalV.length - 1, + format: v => intervalN[v], + onchange: v => settings.updateInterval = intervalV[v] , + }, + + 'Threshold': { + value: thresholdV.indexOf(settings.adjustThreshold), + min: 0, + max: thresholdV.length - 1, + format: v => thresholdV[v] + " ms", + onchange: v => { + settings.adjustThreshold = thresholdV[v]; + }, + }, + + 'Save State': { + value: settings.saveState, + onchange: (v) => { + settings.saveState = v; + if (!v && !stateFileErased) { + stateFileErased = true; + require("Storage").erase(STATE_FILE); + } + }, + }, + + 'Debug Log': { + value: settings.debugLog, + onchange: v => settings.debugLog = v, + }, + }; + + E.showMenu(mainMenu); +}) diff --git a/apps/widadjust/widget.js b/apps/widadjust/widget.js new file mode 100644 index 000000000..138833783 --- /dev/null +++ b/apps/widadjust/widget.js @@ -0,0 +1,244 @@ +(() => { + // ====================================================================== + // CONST + + const DEBUG_LOG_FILE = 'widadjust.log'; + const SETTINGS_FILE = 'widadjust.json'; + const STATE_FILE = 'widadjust.state'; + + const DEFAULT_ADJUST_THRESHOLD = 100; + const DEFAULT_UPDATE_INTERVAL = 60 * 1000; + const MIN_INTERVAL = 10 * 1000; + + const MAX_CLOCK_ERROR_FROM_SAVED_STATE = 2000; + + const SAVE_STATE_CLOCK_ERROR_DELTA_THRESHOLD = 1; + const SAVE_STATE_CLOCK_ERROR_DELTA_IN_PPM_THRESHOLD = 1; + const SAVE_STATE_PPM_DELTA_THRESHOLD = 1; + + // Widget width. + const WIDTH = 22; + + // ====================================================================== + // VARIABLES + + let settings; + let saved; + + let lastClockCheckTime = Date.now(); + let lastClockErrorUpdateTime; + + let clockError; + let currentUpdateInterval; + let lastPpm = null; + + let debugLogFile = null; + + // ====================================================================== + // FUNCTIONS + + function clockCheck() { + let now = Date.now(); + let elapsed = now - lastClockCheckTime; + lastClockCheckTime = now; + + let prevUpdateInterval = currentUpdateInterval; + currentUpdateInterval = settings.updateInterval; + setTimeout(clockCheck, lastClockCheckTime + currentUpdateInterval - Date.now()); + + // If elapsed time differs a lot from expected, + // some other app probably used setTime to change clock significantly. + // -> reset clock error since elapsed time can't be trusted + if (Math.abs(elapsed - prevUpdateInterval) > 10 * 1000) { + // RESET CLOCK ERROR + + clockError = 0; + lastClockErrorUpdateTime = now; + + debug( + 'Looks like some other app used setTime, so reset clockError. (elapsed = ' + + elapsed.toFixed(0) + ')' + ); + WIDGETS.adjust.draw(); + + } else if (!settings.advanced) { + // UPDATE CLOCK ERROR WITHOUT TEMPERATURE COMPENSATION + + updateClockError(settings.ppm); + } else { + // UPDATE CLOCK ERROR WITH TEMPERATURE COMPENSATION + + Bangle.getPressure().then(d => { + let temp = d.temperature; + updateClockError(settings.ppm0 + settings.ppm1 * temp + settings.ppm2 * temp * temp); + }).catch(e => { + WIDGETS.adjust.draw(); + }); + } + } + + function debug(line) { + console.log(line); + if (debugLogFile !== null) { + debugLogFile.write(line + '\n'); + } + } + + function draw() { + g.reset().setFont('6x8').setFontAlign(0, 0); + g.clearRect(this.x, this.y, this.x + WIDTH - 1, this.y + 23); + g.drawString(Math.round(clockError), this.x + WIDTH/2, this.y + 9); + + if (lastPpm !== null) { + g.setFont('4x6').setFontAlign(0, 1); + g.drawString(lastPpm.toFixed(1), this.x + WIDTH/2, this.y + 23); + } + } + + function loadSettings() { + settings = Object.assign({ + advanced: false, + saveState: true, + debugLog: false, + ppm: 0, + ppm0: 0, + ppm1: 0, + ppm2: 0, + adjustThreshold: DEFAULT_ADJUST_THRESHOLD, + updateInterval: DEFAULT_UPDATE_INTERVAL, + }, require('Storage').readJSON(SETTINGS_FILE, true) || {}); + + if (settings.debugLog) { + if (debugLogFile === null) { + debugLogFile = require('Storage').open(DEBUG_LOG_FILE, 'a'); + } + } else { + debugLogFile = null; + } + + settings.updateInterval = Math.max(settings.updateInterval, MIN_INTERVAL); + } + + function onQuit() { + let now = Date.now(); + // WIP + let ppm = (lastPpm !== null) ? lastPpm : settings.ppm; + let updatedClockError = clockError + (now - lastClockErrorUpdateTime) * ppm / 1000000; + let save = false; + + if (! settings.saveState) { + debug(new Date(now).toISOString() + ' QUIT'); + + } else if (saved === undefined) { + save = true; + debug(new Date(now).toISOString() + ' QUIT & SAVE STATE'); + + } else { + let elapsedSaved = now - saved.time; + let estimatedClockError = saved.clockError + elapsedSaved * saved.ppm / 1000000; + + let clockErrorDelta = updatedClockError - estimatedClockError; + let clockErrorDeltaInPpm = clockErrorDelta / elapsedSaved * 1000000; + let ppmDelta = ppm - saved.ppm; + + let debugA = new Date(now).toISOString() + ' QUIT'; + let debugB = + '\n> ' + updatedClockError.toFixed(2) + ' - ' + estimatedClockError.toFixed(2) + ' = ' + + clockErrorDelta.toFixed(2) + ' (' + + clockErrorDeltaInPpm.toFixed(1) + ' PPM) ; ' + + ppm.toFixed(1) + ' - ' + saved.ppm.toFixed(1) + ' = ' + ppmDelta.toFixed(1); + + if ((Math.abs(clockErrorDelta) >= SAVE_STATE_CLOCK_ERROR_DELTA_THRESHOLD + && Math.abs(clockErrorDeltaInPpm) >= SAVE_STATE_CLOCK_ERROR_DELTA_IN_PPM_THRESHOLD + ) || Math.abs(ppmDelta) >= SAVE_STATE_PPM_DELTA_THRESHOLD + ) + { + save = true; + debug(debugA + ' & SAVE STATE' + debugB); + } else { + debug(debugA + debugB); + } + } + + if (save) { + require('Storage').writeJSON(STATE_FILE, { + counter: (saved === undefined) ? 1 : saved.counter + 1, + time: Math.round(now), + clockError: Math.round(updatedClockError * 1000) / 1000, + ppm: Math.round(ppm * 1000) / 1000, + }); + } + } + + function updateClockError(ppm) { + let now = Date.now(); + let elapsed = now - lastClockErrorUpdateTime; + let drift = elapsed * ppm / 1000000; + clockError += drift; + lastClockErrorUpdateTime = now; + lastPpm = ppm; + + if (Math.abs(clockError) >= settings.adjustThreshold) { + let now = Date.now(); + // Shorter variables are faster to look up and this part is time sensitive. + let e = clockError / 1000; + setTime(getTime() - e); + debug( + new Date(now).toISOString() + ' -> ' + ((now / 1000 - e) % 60).toFixed(3) + + ' SET TIME (' + clockError.toFixed(2) + ')' + ); + clockError = 0; + } + + WIDGETS.adjust.draw(); + } + + // ====================================================================== + // MAIN + + loadSettings(); + + WIDGETS.adjust = { + area: 'tr', + draw: draw, + now: () => { + let now = Date.now(); + // WIP + let ppm = (lastPpm !== null) ? lastPpm : settings.ppm; + let updatedClockError = clockError + (now - lastClockErrorUpdateTime) * ppm / 1000000; + return now - updatedClockError; + }, + width: WIDTH, + }; + + if (settings.saveState) { + saved = require('Storage').readJSON(STATE_FILE, true); + } + + let now = Date.now(); + lastClockErrorUpdateTime = now; + if (saved === undefined) { + clockError = 0; + debug(new Date().toISOString() + ' START'); + } else { + clockError = saved.clockError + (now - saved.time) * saved.ppm / 1000000; + + if (Math.abs(clockError) <= MAX_CLOCK_ERROR_FROM_SAVED_STATE) { + debug( + new Date().toISOString() + ' START & LOAD STATE (' + + clockError.toFixed(2) + ')' + ); + } else { + debug( + new Date().toISOString() + ' START & IGNORE STATE (' + + clockError.toFixed(2) + ')' + ); + clockError = 0; + } + } + + clockCheck(); + + E.on('kill', onQuit); + +})() diff --git a/apps/widagps/ChangeLog b/apps/widagps/ChangeLog new file mode 100644 index 000000000..6728683c8 --- /dev/null +++ b/apps/widagps/ChangeLog @@ -0,0 +1 @@ +0.01: first version diff --git a/apps/widagps/README.md b/apps/widagps/README.md new file mode 100644 index 000000000..e5e9a8f8a --- /dev/null +++ b/apps/widagps/README.md @@ -0,0 +1,12 @@ +# A-GPS Data + +Load assisted GPS data directly to the watch using the new http requests on Android GadgetBridge. + +Make sure: +* your GadgetBridge version supports http requests +* turn on internet access in GadgetBridge settings + +The widget loads the data in the background every 12 hours. It retries every 10min if the http request fails. It is only visible during a request or on error. + +## Creator +[@pidajo](https://github.com/pidajo) diff --git a/apps/widagps/metadata.json b/apps/widagps/metadata.json new file mode 100644 index 000000000..ee1eb9f08 --- /dev/null +++ b/apps/widagps/metadata.json @@ -0,0 +1,14 @@ +{ "id": "widagps", + "name": "AGPS Widget (automatic download)", + "shortName":"AGPS Widget", + "icon": "widget.png", + "type": "widget", + "version":"0.01", + "description": "Once installed, this widget allows your Bangle.js 2 to load AGPS data in the background **via Gadgetbridge on an Android phone** so it is always up to date. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.", + "readme": "README.md", + "tags": "widget,agps,http", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"widagps.wid.js","url":"widget.js"} + ] +} diff --git a/apps/widagps/widget.js b/apps/widagps/widget.js new file mode 100644 index 000000000..57ed801e1 --- /dev/null +++ b/apps/widagps/widget.js @@ -0,0 +1,165 @@ +if (!global.WIDGETS) { + WIDGETS = {}; + Bangle.loadWidgets(); + var isTest = true; +} + +(function(){ + var warnTime = 24*60*60000; //warn missing data + var nextTime = 12*60*60000; //time between requests + var retryTime = 10*60000; //time between retries + + const JSON_FILE = "agpsdata.json"; + var isRequesting = false; + var lastAGPS = 0; + var nextGet = null; + + const WIDGET_ID = "widagps"; + WIDGETS[WIDGET_ID]={ + area:"tl", + width:24, + draw:function() { + var w = 0; + var x = this.x, y = this.y; + g.reset(); + if (isRequesting) { + g.setColor("#00f"); + w = 24; + } + else { + if (Date.now() - lastAGPS > warnTime) { + g.setColor("#f00"); + w = 24; + } + } + if (w) { +g.drawImage(atob("FBQBAAAABgAAYAAPAACwABOAATgAI8ACPABD4AQ+AEPgD8EAg/AQP4AD+CD/wjD8WAHmAAY="), x + 1, y + 1); + } + if (WIDGETS[WIDGET_ID].width != w) { + WIDGETS[WIDGET_ID].width = w; + Bangle.drawWidgets(); + } + } + }; + + var _GB = global.GB; + + global.GB = function(msg) { + //console.log(msg); + if (msg.t == "http") { + applyAGPS(msg.resp); + } + if (_GB) { + _GB(msg); + } + } + + function nextAGPS(when) { + if (nextGet) { + clearTimeout(nextGet); + nextGet = null; + } + console.log("Next AGPS request:", new Date(Date.now() + when)); + nextGet = setTimeout(() => { + getAGPS(); + }, when); + } + + function applyAGPS(data) { + isRequesting = false; + var success = false; + if (data) { + success = setAGPS(data); + } + if (success) { + lastAGPS = Date.now(); + nextAGPS(nextTime); + require("Storage").writeJSON(JSON_FILE, {lastAGPS: lastAGPS}); + } + else { + console.log("Failed to apply AGPS data"); + nextAGPS(retryTime); + } + Bangle.drawWidgets(); + } + + function setAGPS(data) { + var js = jsFromBase64(data); + Bangle.setGPSPower(true, "agpsdata"); + try { + eval(js); + Bangle.setGPSPower(false, "agpsdata"); + return true; + } + catch(e) { + console.log("Error:", e); + } + Bangle.setGPSPower(false, "agpsdata"); + return false; + } + + function jsFromBase64(b64) { + var bin = atob(b64); + var chunkSize = 128; + var js = "";//"Bangle.setGPSPower(1);\n"; // turn GPS on + var gnss_select="1"; + js += `Serial1.println("${CASIC_CHECKSUM("$PCAS04,"+gnss_select)}")\n`; // set GNSS mode + // What about: + // NAV-TIMEUTC (0x01 0x10) + // NAV-PV (0x01 0x03) + // or AGPS.zip uses AID-INI (0x0B 0x01) + + for (var i=0;i { + GB({t:"http", resp:testData}); + }, 5000); + } + //check for request timeout + setTimeout(() => { + if (isRequesting) { + applyAGPS(); + } + }, 10000); + Bangle.drawWidgets(); + } + + var data = require("Storage").readJSON(JSON_FILE); + if (data && data.lastAGPS) { + //lastAGPS = data.lastAGPS; + } + + nextAGPS(Math.max(0, nextTime - (Date.now() - lastAGPS))); + NRF.on('connect', () => { + if (Date.now() - lastAGPS > warnTime) { + nextAGPS(0); + } + }); + + var testData = "QUdOU1MgZGF0YSBmcm9tIENBU0lDLgpEYXRhTGVuZ3RoOiAyNTk4LgpMaW1pdGF0aW9uOiAzLzEwMDAuCrrOSAAIB7YdxSr+Sg2h8NYlBux1jiUgQbrXgJk/KJvFZVv8pP//uy3i/PH6rv9EMQH6SwBfAOxepgDsXgAAlCULALv/AAtCAAAAAQMAALQ7kly6zkgACAdBzVam9HANoXGycgoqGmnG5X9h3mKrWicvBKhXAp7//+00U/9j/jP/SDHM/Vn/JADrXqYA614AAF6d6v8DAADaEAAAAAIDAADKmrVTus5IAAgHTUirJrjvDKHJDjACcmXJJ+8Lv6rw5LQnl4OChUCt//9XKG3/+u6ZE84ZQuwDAMf/7F6mAOxeAADa0Pb/mP8ABDUAAAADAwAA4pBeVLrOSAAIB5291DTIzAyhJGfzAJ7pCIcIUQMgcfEoJ2TIjrHkqv//YjDTCKj/wRPdGIz/8/9NAOxepgDsXgAAl9/6/yQAAPbhAAAABAMAAIJ7sXC6zkgACAeKKDOgmwEOocKrFwP8Jmgqq4lOQ1VtLSfEi8yDVqn//5MskP6E7WQRPxzv6vv/0//sXqYA7F4AAM4w/f/0/wDoJwAAAAUDAABcUW5Hus5IAAgHu+Va48nxDaFaw0UBkVc73Y3FB+HFmDgoUWIPW+Ck//+iLcf9X/t5/ikzgPrT/wgA7F6mAOxeAAADVgsAiQAACB0AAAAGAwAAvsu9zbrOSAAIB0OVp/7+JQ2hFANPCF+F6KOOMwe9/nC5JsX0CtthqP//WzhzADr/dgqbIRj/nwDj/+xepgDsXgAAP4oKAPv/AOg4AAAABwMAAM4qVwS6zkgACAey9J9b+zwOofLIzwObDg0HLdEmMOA3PydWaUQvr6b//0wxwf/7EJ8K/yLREhkANADsXqYA7F4AALug/f/y/wALLAAAAAgDAACs6Ue+us5IAAgHm7NJLLhaDKEumRQBAFtVTExfuke3AeEmNg9cr2Cq//92MTIJm/63FCkXPv4gABgA616mAOteAACQQfX/HgAAAzUAAAAJAwAAfmebX7rOSAAIB9fgVnnchw2hNlTrAyWnJ5rnBMWFV9GyJzPfZYV8rf//Oyh2AA3xmhLdGtXu/f/Z/+xepgDsXgAA8gXx/37/AAU/AAAACgMAAPbBtfm6zkgACAc+F7Ct97wMoVEVPQCJJG1yeakXMm80PSet+BBd+aH//5AzPf2J+uX97jG7+fj/DgDsXqYA7F4AAGqE//8WAADufQIAAAsDAADELmhius5IAAgHW3g6rwRJDqFpPmYEPf8lNRy9lKO/IX8nJPRjCLOt//90Lir7QQcEGFYUTAguAA4A7F6mAOxeAADrZfj/zP8A5SsAAAAMAwAA/vB8ZbrOSAAIB1mVSsfiXw2hUX8RA60JSyUgLkEgC910JykTq7Usqv//8S9rBw//HhIpGyT/+v8fAOxepgDsXgAAitgKAD4AAOcpAAAADQMAAPoqnZW6zkgACAcCLzz8Q1ANocBvBQFuUWqAxVSgoRjR0SaS5AkH3qr//7cx3vlcB2AYPROjCOr/vf/sXqYA7F4AAA5Y/P/7/wDvGwMAAA4DAABMXoD/us5IAAgH+RiyseCLDKGnOjkHATiXLOud6AnbGuclr2roqg2l///FNxcHi/0hFLoW4fyj/10A7F6mAOxeAAANRf7/GgAA6TQAAAAPAwAAOjJsarrOSAAIB1/+xFM3Xg2hVDKBBg6Tsx25u5hYROV9J/QyJQmLrf//zC1e+7QGxRfmFOAHhf+s/+xepgDsXgAAaE/v/+j/AOonAAAAEAMAAAb9ka66zkgACAc74z4AUZEMofLN6wbbUEjD/w54MWPC3SfOFa4yQ6f//4wtrwH5EOwKOCRTFLz/UP/sXqYA7F4AAOaAFAApAADoOQAAABEDAAC+xoUHus5IAAgH1yXSbYfZDaFOrzwBIM5keona3uYD8JYnC6ekWw+l//8JMC/9ivsaAGEwM/ssAN//7F6mAOxeAAAymwQAof8A7l8AAAASAwAA9kus4rrOSAAIB/9znedCsA2h5VDBBLdHG1Uw8P6P93bRJw5UgTR9qf//LS1BAj0TAwkqJioWBABHAOxepgDsXgAAD54FACwAAN6xAAAAEwMAAEboQta6zkgACAdVJvSzetsMoRclcgKU4/OAvUl5A73wdCYbpBB/P6X//08yQ/4N7voNgx5160gAFwDsXqYA7F4AADa+EADk/wDuLwAAABQDAADyTPBuus5IAAgHhpZXHJnuDaGfsmkMbSLS2QeTnDZBLR8ntwGPV8mj//+SM6n8rftj/EUyZfw7AaX/7F6mAOxeAAAvSQUAAAAA6j4AAAAVAwAAVC23P7rOSAAIB8FSSGuHmw2hXoTeBoU+tLS9TdsrYGopJ+h3h7O9p///mjEIB3X/GhN5GXP/c//V/+xepgDsXgAASREJADgAAO4rAAAAFgMAAMqlmN26zkgACAeGsPJwF8ENoU2cJwHhxiN62PG7uVyKfydPWVyEG6z//8spSgCQ8NkRnxsl7sr/EQDsXqYA7F4AAI0S///u/wDudQEAABcDAABUYe3ous5IAAgHtHJyvWVdDaFr1mwGwPVWIZcaxOQXwAwmeHqW1+Sh//+PPnAAQgAOCxwhof8sAGwA7F6mAOxeAAAhMQcAtf8ABjsAAAAYAwAAsOXsgbrOSAAIB4nxObhoSQ2hOjBcBf2n5ijflOaX/Iz8JpPiNAUTrP//ajEL++oEchfzE9AFSgDT/+tepgDrXgAAohMLACkAAAweAAAAGQMAAFrje3e6zkgACAexAdJ/MyYOoeXvjwPazb0PcXcXfIVaNCZ2mjMDkqn//z02W/qPBMcW7BM7BcX/6//sXqYA7F4AABv4BgAVAAAPIgAAABoDAACqA6wGus5IAAgHxYz/b8dNDaH6/WQF3AILHLKDgTJ+VponRocZMKSm//8bLycAMRANDG8i/RHR/2wA7F6mAOxeAAArEwcAHAAABEgAAQAbAwAA0hkH57rOSAAIB5HXkJcOjA2h8B4kAebzvl2gmkU1K1TzJ86wODP6qP//0CzCAM4OmQrkI1UR7f/n/+xepgDsXgAAcs/u/9//AOplAAAAHQMAAGqvKTa6zkgACAekogIGh+8NoYBTBQMDtQeTiKQ4u0KYHiYkF4HbzaT//7A80v9N/gQLmSC4/icA6v/sXqYA7F4AAB2V7v/2/wAIGQAAAB4DAACQRQ0Tus5IAAgHUsOCUJ71DaHkPFgFdJcpEDguP6ldR+UmgbrM2+6m//9vOPT/S/7vC1sh6/49AJH/7F6mAOxeAAAQCfr/8/8A4wwAAAAfAwAA7IYNqLrOSAAIByZDZIfa4AyhxlEVA9QtFKN+R+1NPIIIJ8xm1q8Qqv//djDzB9H+pBNQGGj+rv++/+xepgDsXgAA3f/6/67/AAFUAAAAIAMAAJSG0BW6zhQACAWVGZOmAAAAAPr///8SEpCmiQcDAD4zLlK6zhAACAZIDf33DwP+/jYK//gDAAAAoBoC9g=="; + +})() +if (global.isTest) { + Bangle.drawWidgets(); +} + diff --git a/apps/widagps/widget.png b/apps/widagps/widget.png new file mode 100644 index 000000000..ee16c114a Binary files /dev/null and b/apps/widagps/widget.png differ diff --git a/apps/widalarm/ChangeLog b/apps/widalarm/ChangeLog new file mode 100644 index 000000000..63568a9bd --- /dev/null +++ b/apps/widalarm/ChangeLog @@ -0,0 +1 @@ +0.01: Moved out of 'alarm' app diff --git a/apps/widalarm/app.png b/apps/widalarm/app.png new file mode 100644 index 000000000..a859dd2ef Binary files /dev/null and b/apps/widalarm/app.png differ diff --git a/apps/widalarm/metadata.json b/apps/widalarm/metadata.json new file mode 100644 index 000000000..b91457138 --- /dev/null +++ b/apps/widalarm/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "widalarm", + "name": "Alarms Widget", + "version": "0.01", + "description": "Displays an alarm icon in the widgets bar if any alarm is active", + "icon": "app.png", + "type": "widget", + "tags": "tool,alarm,widget", + "supports": [ "BANGLEJS", "BANGLEJS2" ], + "provides_widgets" : ["alarm"], + "default" : true, + "storage": [ + { "name": "widalarm.wid.js", "url": "widget.js" } + ] +} diff --git a/apps/alarm/widget.js b/apps/widalarm/widget.js similarity index 56% rename from apps/alarm/widget.js rename to apps/widalarm/widget.js index e8bb79fc7..964176fc7 100644 --- a/apps/alarm/widget.js +++ b/apps/widalarm/widget.js @@ -1,7 +1,8 @@ WIDGETS["alarm"]={area:"tl",width:0,draw:function() { if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y); },reload:function() { - WIDGETS["alarm"].width = (require('Storage').readJSON('alarm.json',1)||[]).some(alarm=>alarm.on) ? 24 : 0; + // don't include library here as we're trying to use as little RAM as possible + WIDGETS["alarm"].width = (require('Storage').readJSON('sched.json',1)||[]).some(alarm=>alarm.on&&(alarm.hidden!==true)) ? 24 : 0; } }; WIDGETS["alarm"].reload(); diff --git a/apps/widalarmeta/metadata.json b/apps/widalarmeta/metadata.json new file mode 100644 index 000000000..ef9f55ba8 --- /dev/null +++ b/apps/widalarmeta/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "widalarmeta", + "name": "Alarm & Timer ETA", + "shortName": "Alarm ETA", + "version": "0.01", + "description": "A widget that displays the time to the next Alarm or Timer in hours and minutes, maximum 24h", + "icon": "widget.png", + "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS","BANGLEJS2"], + "provides_widgets" : ["alarm"], + "screenshots" : [ { "url":"screenshot.png" } ], + "storage": [ + {"name":"widalarmeta.wid.js","url":"widget.js"} + ] +} diff --git a/apps/widalarmeta/screenshot.png b/apps/widalarmeta/screenshot.png new file mode 100644 index 000000000..41a109557 Binary files /dev/null and b/apps/widalarmeta/screenshot.png differ diff --git a/apps/widalarmeta/widget.js b/apps/widalarmeta/widget.js new file mode 100644 index 000000000..0cddf953a --- /dev/null +++ b/apps/widalarmeta/widget.js @@ -0,0 +1,35 @@ +(() => { + const alarms = require("Storage").readJSON("sched.json",1) || []; + + function draw() { + const times = alarms.map(alarm => require("sched").getTimeToAlarm(alarm)).filter(a => a !== undefined); + const next = Math.min.apply(null, times); + if (next > 0 && next < 86400000) { + const hours = Math.floor((next % 86400000) / 3600000).toString(); + const minutes = Math.floor(((next % 86400000) % 3600000) / 60000).toString(); + + g.reset(); // reset the graphics context to defaults (color/font/etc) + g.setFontAlign(0,0); // center fonts + g.clearRect(this.x, this.y, this.x+this.width-1, this.y+23); + + var text = hours.padStart(2, '0') + ":" + minutes.padStart(2, '0'); + g.setFont("6x8:1x2"); + g.drawString(text, this.x+this.width/2, this.y+12); + if (this.width === 0) { + this.width = 6*5+2; + Bangle.drawWidgets(); // width changed, re-layout + } + } + } + + setInterval(function() { + WIDGETS["widalarmeta"].draw(WIDGETS["widalarmeta"]); + }, 30000); // update every half minute + + // add your widget + WIDGETS["widalarmeta"]={ + area:"tl", + width: 0, // hide by default = assume no timer + draw:draw + }; +})(); diff --git a/apps/widalarmeta/widget.png b/apps/widalarmeta/widget.png new file mode 100644 index 000000000..cfd942ea0 Binary files /dev/null and b/apps/widalarmeta/widget.png differ diff --git a/apps/widalt/ChangeLog b/apps/widalt/ChangeLog new file mode 100644 index 000000000..ec66c5568 --- /dev/null +++ b/apps/widalt/ChangeLog @@ -0,0 +1 @@ +0.01: Initial version diff --git a/apps/widalt/README.md b/apps/widalt/README.md new file mode 100644 index 000000000..08ee4f83d --- /dev/null +++ b/apps/widalt/README.md @@ -0,0 +1,2 @@ +# Altimeter widget +Displays barometric altitude in the top right corner. diff --git a/apps/widalt/metadata.json b/apps/widalt/metadata.json new file mode 100644 index 000000000..309c5bf2c --- /dev/null +++ b/apps/widalt/metadata.json @@ -0,0 +1,18 @@ + +{ + "id": "widalt", + "name": "Altimeter widget", + "version": "0.01", + "description": "Displays barometric altitude", + "readme": "README.md", + "icon": "widalt.png", + "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS2"], + "allow_emulator":false, + "storage": [ + {"name":"widalt.wid.js","url":"widalt.wid.js"}, + {"name":"widalt.settings.js","url":"widalt.settings.js"} + ], + "data": [{"name":"widalt.json"}] + } diff --git a/apps/widalt/widalt.png b/apps/widalt/widalt.png new file mode 100644 index 000000000..43e5465bc Binary files /dev/null and b/apps/widalt/widalt.png differ diff --git a/apps/widalt/widalt.settings.js b/apps/widalt/widalt.settings.js new file mode 100644 index 000000000..57993474e --- /dev/null +++ b/apps/widalt/widalt.settings.js @@ -0,0 +1,27 @@ +(function(back) { + var settings = Object.assign({ + interval: 5000, + }, require('Storage').readJSON("widalt.json", true) || {}); + o=Bangle.getOptions(); + Bangle.getPressure().then((p)=>{ + E.showMenu({ + "" : { "title" : "Altimeter Widget" }, + "< Back" : () => back(), + 'QNH': { + value: Math.floor(o.seaLevelPressure), + min: 100, max: 10000, + format: v=>(v+"hPa\nAlt: "+(44330 * (1.0 - Math.pow(p.pressure/v, 0.1903))).toFixed(0)+"m"), + onchange: v => {Bangle.setOptions({seaLevelPressure:v});} + }, + 'update Interval': { + value: settings.interval/1000, + min: 1, max: 60, + format: v=>(v+"s"), + onchange: v => { + settings.interval=v*1000; + require('Storage').writeJSON("widalt.json", settings); + } + } + }); + }); +}) diff --git a/apps/widalt/widalt.wid.js b/apps/widalt/widalt.wid.js new file mode 100644 index 000000000..dbd1a763e --- /dev/null +++ b/apps/widalt/widalt.wid.js @@ -0,0 +1,38 @@ +(()=>{ + var alt=""; + var lastAlt=0; + var settings = Object.assign({ + interval: 5000, + }, require('Storage').readJSON("widalt.json", true) || {}); + Bangle.setBarometerPower(true,"widalt"); + Bangle.on("pressure", (p)=>{ + if (Math.floor(p.altitude)!=lastAlt) { + lastAlt=Math.floor(p.altitude); + alt=p.altitude.toFixed(0); + var w = WIDGETS["widalt"].width; + WIDGETS["widalt"].width = 1 + (alt.length)*12+16; + if (w!=WIDGETS["widalt"].width) Bangle.drawWidgets(); + else WIDGETS["widalt"].draw(); + } + Bangle.setBarometerPower(false,"widalt") + setTimeout(()=>{Bangle.setBarometerPower(true,"widalt");},settings.interval); + }); + + function draw() { + if (!Bangle.isLCDOn()) return; + g.reset(); + g.setColor(g.theme.bg); + g.fillRect(this.x, this.y, this.x + this.width, this.y + 23); + g.setColor(g.theme.fg); + g.drawImage(atob("EBCBAAAAAAAIAAwgFXAX0BCYIIggTD/EYPZADkACf/4AAAAA"), this.x, this.y+4); +g.setFontCustom(atob("AAAAABwAAOAAAgAAHAADwAD4AB8AB8AA+AAeAADAAAAOAAP+AH/8B4DwMAGBgAwMAGBgAwOAOA//gD/4AD4AAAAAAAABgAAcAwDAGAwAwP/+B//wAAGAAAwAAGAAAAAAAAIAwHgOA4DwMA+BgOwMDmBg4wOeGA/gwDwGAAAAAAAAAGAHA8A4DwMAGBhAwMMGBjgwOcOA+/gDj4AAAAABgAAcAAHgADsAA5gAOMAHBgBwMAP/+B//wABgAAMAAAAAAAgD4OB/AwOYGBjAwMYGBjBwMe8Bh/AIHwAAAAAAAAAfAAP8AHxwB8GAdgwPMGBxgwMOOAB/gAH4AAAAAAABgAAMAABgAwMAeBgPgMHwBj4AN8AB+AAPAABAAAAAAAMfAH38B/xwMcGBhgwMMGBjgwP+OA+/gDj4AAAAAAAAOAAH4AA/gQMMGBgzwME8BhvAOPgA/4AD8AAEAAAAAAGAwA4OAHBwAAA="), 46, atob("BAgMDAwMDAwMDAwMBQ=="), 21+(1<<8)+(1<<16)); + g.setFontAlign(-1, 0); + g.drawString(alt, this.x+16, this.y + 12); + } + WIDGETS["widalt"] = { + area: "tr", + width: 6, + draw: draw + }; + +})(); diff --git a/apps/widanclk/ChangeLog b/apps/widanclk/ChangeLog new file mode 100644 index 000000000..337288ad2 --- /dev/null +++ b/apps/widanclk/ChangeLog @@ -0,0 +1,2 @@ +0.01: New app +0.02: Clear between redraws diff --git a/apps/widanclk/metadata.json b/apps/widanclk/metadata.json new file mode 100644 index 000000000..cd9347601 --- /dev/null +++ b/apps/widanclk/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "widanclk", + "name": "Analog clock widget", + "version": "0.02", + "description": "A simple analog clock widget that appears when not showing a fullscreen clock", + "icon": "widget.png", + "type": "widget", + "tags": "widget,clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"widanclk.wid.js","url":"widget.js"} + ] +} diff --git a/apps/widanclk/widget.js b/apps/widanclk/widget.js new file mode 100644 index 000000000..c58f56459 --- /dev/null +++ b/apps/widanclk/widget.js @@ -0,0 +1,25 @@ +/* Simple analog clock that appears in the widget bar if no other clock +is running. We update once per minute, but don't bother stopping +if the */ +WIDGETS["wdanclk"]={area:"tl",width:Bangle.CLOCK?0:24,draw:function() { + if (!Bangle.CLOCK == !this.width) { // if we're the wrong size for if we have a clock or not... + this.width = Bangle.CLOCK?0:24; + return setTimeout(Bangle.drawWidgets,1); // widget changed size - redraw + } + if (!this.width) return; // if size not right, return + g.reset(); + let d = new Date(); + let x=this.x+12, y=this.y+12, + ah = (d.getHours()+d.getMinutes()/60)*Math.PI/6, + am = d.getMinutes()*Math.PI/30; + g.clearRect(this.x, this.y, this.x+this.width-1, this.y+23). + drawCircle(x, y, 11). + drawLine(x,y, x+Math.sin(ah)*7, y-Math.cos(ah)*7). + drawLine(x,y, x+Math.sin(am)*9, y-Math.cos(am)*9); + // queue draw in one minute + if (this.drawTimeout) clearTimeout(this.drawTimeout); + this.drawTimeout = setTimeout(()=>{ + this.drawTimeout = undefined; + this.draw(); + }, 60000 - (Date.now() % 60000)); +}}; diff --git a/apps/widanclk/widget.png b/apps/widanclk/widget.png new file mode 100644 index 000000000..9e4b37c39 Binary files /dev/null and b/apps/widanclk/widget.png differ diff --git a/apps/widbaroalarm/ChangeLog b/apps/widbaroalarm/ChangeLog new file mode 100644 index 000000000..2dfe8336d --- /dev/null +++ b/apps/widbaroalarm/ChangeLog @@ -0,0 +1,9 @@ +0.01: Initial version +0.02: Do not warn multiple times for the same exceed +0.03: Fix crash +0.04: Use Prompt with dismiss and pause + Improve barometer value median calculation +0.05: Fix warning calculation + Show difference of last measurement to pressure average of the the last three hours in the widget + Only use valid pressure values +0.06: Fix exception diff --git a/apps/widbaroalarm/README.md b/apps/widbaroalarm/README.md new file mode 100644 index 000000000..478b48a71 --- /dev/null +++ b/apps/widbaroalarm/README.md @@ -0,0 +1,27 @@ +# Barometer alarm widget + +Get a notification when the pressure reaches defined thresholds. + + +## Settings +* Interval: check interval of sensor data in minutes. 0 to disable automatic check. +* Low alarm: Toggle low alarm + * Low threshold: Warn when pressure drops below this value +* High alarm: Toggle high alarm + * High threshold: Warn when pressure exceeds above this value +* Drop alarm: Warn when pressure drops more than this value in the recent 3 hours (having at least 30 min of data) + 0 to disable this alarm. +* Raise alarm: Warn when pressure raises more than this value in the recent 3 hours (having at least 30 min of data) + 0 to disable this alarm. +* Show widget: Enable/disable widget visibility +* Buzz on alarm: Enable/disable buzzer on alarm +* Dismiss delay: Delay added before the next alert if the alert is dismissed. From 5 to 60 min +* Pause delay: Same as Dismiss delay but longer (useful for meetings and such). From 30 to 240 min + +## Widget +The widget shows two rows: +1. pressure value of last measurement +2. difference of last measurement to pressure average of the the last three hours + +## Creator +Marco ([myxor](https://github.com/myxor)) diff --git a/apps/widbaroalarm/default.json b/apps/widbaroalarm/default.json new file mode 100644 index 000000000..696c70819 --- /dev/null +++ b/apps/widbaroalarm/default.json @@ -0,0 +1,13 @@ +{ + "buzz": true, + "lowalarm": false, + "min": 950, + "highalarm": false, + "max": 1030, + "drop3halarm": 2, + "raise3halarm": 0, + "show": true, + "interval": 15, + "dismissDelayMin": 15, + "pauseDelayMin": 60 +} diff --git a/apps/widbaroalarm/metadata.json b/apps/widbaroalarm/metadata.json new file mode 100644 index 000000000..ba6c47b37 --- /dev/null +++ b/apps/widbaroalarm/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "widbaroalarm", + "name": "Barometer Alarm Widget", + "shortName": "Barometer Alarm", + "version": "0.06", + "description": "A widget that can alarm on when the pressure reaches defined thresholds.", + "icon": "widget.png", + "type": "widget", + "tags": "tool,barometer", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "screenshots": [{"url":"screenshot.png"}], + "storage": [ + {"name":"widbaroalarm.wid.js","url":"widget.js"}, + {"name":"widbaroalarm.settings.js","url":"settings.js"}, + {"name":"widbaroalarm.default.json","url":"default.json"} + ], + "data": [{"name":"widbaroalarm.json"}, {"name":"widbaroalarm.log"}] +} diff --git a/apps/widbaroalarm/screenshot.png b/apps/widbaroalarm/screenshot.png new file mode 100644 index 000000000..fcb6ad67e Binary files /dev/null and b/apps/widbaroalarm/screenshot.png differ diff --git a/apps/widbaroalarm/settings.js b/apps/widbaroalarm/settings.js new file mode 100644 index 000000000..ee8ce82c2 --- /dev/null +++ b/apps/widbaroalarm/settings.js @@ -0,0 +1,115 @@ +(function(back) { + const SETTINGS_FILE = "widbaroalarm.json"; + const storage = require('Storage'); + let settings = Object.assign( + storage.readJSON("widbaroalarm.default.json", true) || {}, + storage.readJSON(SETTINGS_FILE, true) || {} + ); + + function save(key, value) { + settings[key] = value; + storage.write(SETTINGS_FILE, settings); + } + + function showMainMenu() { + let menu ={ + '': { 'title': 'Barometer alarm widget' }, + /*LANG*/'< Back': back, + "Interval": { + value: settings.interval, + min: 0, + max: 120, + step: 1, + format: x => { + return x != 0 ? x + ' min' : 'off'; + }, + onchange: x => save("interval", x) + }, + "Low alarm": { + value: settings.lowalarm, + format: x => { + return x ? 'Yes' : 'No'; + }, + onchange: x => save("lowalarm", x), + }, + "Low threshold": { + value: settings.min, + min: 600, + max: 1000, + step: 5, + onchange: x => save("min", x), + }, + "High alarm": { + value: settings.highalarm, + format: x => { + return x ? 'Yes' : 'No'; + }, + onchange: x => save("highalarm", x), + }, + "High threshold": { + value: settings.max, + min: 700, + max: 1100, + step: 5, + onchange: x => save("max", x), + }, + "Drop alarm": { + value: settings.drop3halarm, + min: 0, + max: 10, + step: 1, + format: x => { + return x != 0 ? x + ' hPa/3h' : 'off'; + }, + onchange: x => save("drop3halarm", x) + }, + "Raise alarm": { + value: settings.raise3halarm, + min: 0, + max: 10, + step: 1, + format: x => { + return x != 0 ? x + ' hPa/3h' : 'off'; + }, + onchange: x => save("raise3halarm", x) + }, + "Show widget": { + value: settings.show, + format: x => { + return x ? 'Yes' : 'No'; + }, + onchange: x => save('show', x) + }, + "Buzz on alarm": { + value: settings.buzz, + format: x => { + return x ? 'Yes' : 'No'; + }, + onchange: x => save('buzz', x) + }, + 'Dismiss delay': { + value: settings.dismissDelayMin, + min: 5, max: 60, + onchange: v => { + save('dismissDelayMin', v) + }, + format: x => { + return x + " min"; + } + }, + 'Pause delay': { + value: settings.pauseDelayMin, + min: 30, max: 240, + onchange: v => { + save('pauseDelayMin', v) + }, + format: x => { + return x + " min"; + } + }, + }; + E.showMenu(menu); + } + + showMainMenu(); +}); diff --git a/apps/widbaroalarm/widget.js b/apps/widbaroalarm/widget.js new file mode 100644 index 000000000..d877c4384 --- /dev/null +++ b/apps/widbaroalarm/widget.js @@ -0,0 +1,304 @@ +(function() { +let medianPressure; +let threeHourAvrPressure; +let currentPressures = []; +let stop = false; // semaphore + +const LOG_FILE = "widbaroalarm.log.json"; +const SETTINGS_FILE = "widbaroalarm.json"; +const storage = require('Storage'); + +let settings; + +function loadSettings() { + settings = + Object.assign(storage.readJSON("widbaroalarm.default.json", true) || {}, + storage.readJSON(SETTINGS_FILE, true) || {}); +} + +loadSettings(); + +function setting(key) { return settings[key]; } + +function saveSetting(key, value) { + settings[key] = value; + storage.write(SETTINGS_FILE, settings); +} + +const interval = setting("interval"); + +let history3 = + storage.readJSON(LOG_FILE, true) || []; // history of recent 3 hours + +function showAlarm(body, key, type) { + if (body == undefined) + return; + stop = true; + + E.showPrompt(body, { + title : "Pressure " + (type != undefined ? type : "alarm"), + buttons : {"Ok" : 1, "Dismiss" : 2, "Pause" : 3} + }).then(function(v) { + const tsNow = Math.round(Date.now() / 1000); // seconds + + if (v == 1) { + saveSetting(key, tsNow); + } + if (v == 2) { + // save timestamp of the future so that we do not warn again for the same + // event until then + saveSetting(key, tsNow + 60 * setting('dismissDelayMin')); + } + if (v == 3) { + // save timestamp of the future so that we do not warn again for the same + // event until then + saveSetting(key, tsNow + 60 * setting('pauseDelayMin')); + } + stop = false; + load(); + }); + + if (setting("buzz") && !(storage.readJSON('setting.json', 1) || {}).quiet) { + Bangle.buzz(); + } + + setTimeout(function() { + stop = false; + load(); + }, 20000); +} + +/* + * returns true if an alarm should be triggered + */ +function doWeNeedToAlarm(key) { + const tsNow = Math.round(Date.now() / 1000); // seconds + return setting(key) == undefined || setting(key) == 0 || tsNow > setting(key); +} + +function isValidPressureValue(pressure) { + if (pressure == undefined || pressure <= 0) + return false; + return pressure > 800 && pressure < 1200; // very rough values +} + +function handlePressureValue(pressure) { + if (pressure == undefined || pressure <= 0) + return; + + const ts = Math.round(Date.now() / 1000); // seconds + const d = {"ts" : ts, "p" : pressure}; + + history3.push(d); + + // delete oldest entries until we have max 50 + while (history3.length > 50) { + history3.shift(); + } + + // delete entries older than 3h + for (let i = 0; i < history3.length; i++) { + if (history3[i]["ts"] < ts - (3 * 60 * 60)) { + history3.shift(); + } else { + break; + } + } + + // write data to storage + storage.writeJSON(LOG_FILE, history3); + + calculcate3hAveragePressure(); + + if (setting("lowalarm") || setting("highalarm") || setting("drop3halarm") || + setting("raise3halarm")) { + checkForAlarms(pressure, ts); + } +} + +function checkForAlarms(pressure, ts) { + let alreadyWarned = false; + + if (setting("lowalarm")) { + // Is below the alarm threshold? + if (pressure <= setting("min")) { + if (!doWeNeedToAlarm("lowWarnTs")) { + showAlarm("Pressure low: " + Math.round(pressure) + " hPa", "lowWarnTs", + "low"); + alreadyWarned = true; + } + } else { + saveSetting("lowWarnTs", 0); + } + } + + if (setting("highalarm")) { + // Is above the alarm threshold? + if (pressure >= setting("max")) { + if (doWeNeedToAlarm("highWarnTs")) { + showAlarm("Pressure high: " + Math.round(pressure) + " hPa", + "highWarnTs", "high"); + alreadyWarned = true; + } + } else { + saveSetting("highWarnTs", 0); + } + } + + if (history3.length > 0 && !alreadyWarned) { + // 3h change detection + const drop3halarm = setting("drop3halarm"); + const raise3halarm = setting("raise3halarm"); + if (drop3halarm > 0 || raise3halarm > 0) { + // we need at least 30 minutes of data for reliable detection + const diffDateAge = Math.abs(history3[0]["ts"] - ts); + if (diffDateAge > 30 * 60) { + // Get oldest entry: + const oldestPressure = history3[0]["p"]; + if (oldestPressure != undefined && oldestPressure > 0) { + const diffPressure = Math.abs(oldestPressure - pressure); + + // drop alarm + if (drop3halarm > 0 && oldestPressure > pressure) { + if (diffPressure >= drop3halarm) { + if (doWeNeedToAlarm("dropWarnTs")) { + showAlarm((Math.round(diffPressure * 10) / 10) + + " hPa/3h from " + Math.round(oldestPressure) + + " to " + Math.round(pressure) + " hPa", + "dropWarnTs", "drop"); + } + } else { + if (ts > setting("dropWarnTs")) + saveSetting("dropWarnTs", 0); + } + } else { + if (ts > setting("dropWarnTs")) + saveSetting("dropWarnTs", 0); + } + + // raise alarm + if (raise3halarm > 0 && oldestPressure < pressure) { + if (diffPressure >= raise3halarm) { + if (doWeNeedToAlarm("raiseWarnTs")) { + showAlarm((Math.round(diffPressure * 10) / 10) + + " hPa/3h from " + Math.round(oldestPressure) + + " to " + Math.round(pressure) + " hPa", + "raiseWarnTs", "raise"); + } + } else { + if (ts > setting("raiseWarnTs")) + saveSetting("raiseWarnTs", 0); + } + } else { + if (ts > setting("raiseWarnTs")) + saveSetting("raiseWarnTs", 0); + } + } + } + } + } +} + +function calculcate3hAveragePressure() { + if (history3 != undefined && history3.length > 0) { + let sum = 0; + for (let i = 0; i < history3.length; i++) { + sum += history3[i]["p"]; + } + threeHourAvrPressure = sum / history3.length; + } else { + threeHourAvrPressure = undefined; + } +} + +/* + turn on barometer power + take multiple measurements + sort the results + take the middle one (median) + turn off barometer power +*/ +function getPressureValue() { + if (stop) + return; + const MEDIANLENGTH = 20; + Bangle.setBarometerPower(true, "widbaroalarm"); + Bangle.on('pressure', function(e) { + while (currentPressures.length > MEDIANLENGTH) + currentPressures.pop(); + + const pressure = e.pressure; + if (isValidPressureValue(pressure)) { + currentPressures.unshift(pressure); + median = currentPressures.slice().sort(); + + if (median.length > 10) { + var mid = median.length >> 1; + medianPressure = Math.round(E.sum(median.slice(mid - 4, mid + 5)) / 9); + if (medianPressure > 0) { + turnOff(); + draw(); + handlePressureValue(medianPressure); + } + } + } + }); + + setTimeout(function() { turnOff(); }, 30000); +} + +function turnOff() { + if (Bangle.isBarometerOn()) + Bangle.setBarometerPower(false, "widbaroalarm"); +} + +function draw() { + if (global.WIDGETS != undefined && typeof global.WIDGETS === "object") { + global.WIDGETS["baroalarm"] = { + width : setting("show") ? 24 : 0, + area : "tr", + draw : draw + }; + } + g.reset(); + + if (this.x == undefined || this.y != 0) + return; // widget not yet there + + g.clearRect(this.x, this.y, this.x + this.width - 1, this.y + 23); + + if (setting("show")) { + g.setFont("6x8", 1).setFontAlign(1, 0); + if (medianPressure == undefined) { + // trigger a new check + getPressureValue(); + + // lets load last value from log (if available) + if (history3.length > 0) { + medianPressure = history3[history3.length - 1]["p"]; + g.drawString(Math.round(medianPressure), this.x + 24, this.y + 6); + } else { + g.drawString("...", this.x + 24, this.y + 6); + } + } else { + g.drawString(Math.round(medianPressure), this.x + 24, this.y + 6); + } + + if (threeHourAvrPressure == undefined) { + calculcate3hAveragePressure(); + } + if (threeHourAvrPressure != undefined) { + if (medianPressure != undefined) { + const diff = Math.round(medianPressure - threeHourAvrPressure); + g.drawString((diff > 0 ? "+" : "") + diff, this.x + 24, + this.y + 6 + 10); + } + } + } +} + +if (interval > 0) { + setInterval(getPressureValue, interval * 60000); +} +getPressureValue(); +})(); diff --git a/apps/widbaroalarm/widget.png b/apps/widbaroalarm/widget.png new file mode 100644 index 000000000..5be292143 Binary files /dev/null and b/apps/widbaroalarm/widget.png differ diff --git a/apps/widbaroalarm/widget24.png b/apps/widbaroalarm/widget24.png new file mode 100644 index 000000000..2f0d5e4ce Binary files /dev/null and b/apps/widbaroalarm/widget24.png differ diff --git a/apps/widbars/ChangeLog b/apps/widbars/ChangeLog index 61e28e6e4..0065c1b27 100644 --- a/apps/widbars/ChangeLog +++ b/apps/widbars/ChangeLog @@ -1,3 +1,4 @@ 0.01: New Widget! 0.02: Battery bar turns yellow on charge Memory status bar does not trigger garbage collect +0.03: Set battery color on load diff --git a/apps/widbars/metadata.json b/apps/widbars/metadata.json index a9981305c..06965e77e 100644 --- a/apps/widbars/metadata.json +++ b/apps/widbars/metadata.json @@ -1,7 +1,7 @@ { "id": "widbars", "name": "Bars Widget", - "version": "0.02", + "version": "0.03", "description": "Display several measurements as vertical bars.", "icon": "icon.png", "screenshots": [{"url":"screenshot.png"}], diff --git a/apps/widbars/widget.js b/apps/widbars/widget.js index cceeb0897..0a4bba76c 100644 --- a/apps/widbars/widget.js +++ b/apps/widbars/widget.js @@ -42,7 +42,7 @@ if (top) g .clearRect(x,y, x+w-1,y+top-1); // erase above bar if (f) g.setColor(col).fillRect(x,y+top, x+w-1,y+h-1); // even for f=0.001 this is still 1 pixel high } - let batColor='#0f0'; + let batColor=Bangle.isCharging()?'#ff0':'#0f0'; function draw() { g.reset(); const x = this.x, y = this.y, diff --git a/apps/widbat/metadata.json b/apps/widbat/metadata.json index 0f040396f..993310eb2 100644 --- a/apps/widbat/metadata.json +++ b/apps/widbat/metadata.json @@ -6,6 +6,8 @@ "icon": "widget.png", "type": "widget", "tags": "widget,battery", + "provides_widgets" : ["battery"], + "default" : true, "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widbat.wid.js","url":"widget.js"} diff --git a/apps/widbata/metadata.json b/apps/widbata/metadata.json index 26968a7d7..ddc901e80 100644 --- a/apps/widbata/metadata.json +++ b/apps/widbata/metadata.json @@ -10,6 +10,7 @@ "readme": "README.md", "description": "Shows the current battery level status in the top right using the clocks colour theme", "tags": "widget,battery", + "provides_widgets" : ["battery"], "storage": [ {"name":"widbata.wid.js","url":"widbata.wid.js"} ] diff --git a/apps/widbatpc/metadata.json b/apps/widbatpc/metadata.json index 7da4e3e0c..953f8d345 100644 --- a/apps/widbatpc/metadata.json +++ b/apps/widbatpc/metadata.json @@ -7,6 +7,7 @@ "icon": "widget.png", "type": "widget", "tags": "widget,battery", + "provides_widgets" : ["battery"], "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "screenshots": [{"url":"widbatpc.full.jpg"},{"url":"widbatpc.part.jpg"}], diff --git a/apps/widbatv/metadata.json b/apps/widbatv/metadata.json index 37cf6197b..d4ef28315 100644 --- a/apps/widbatv/metadata.json +++ b/apps/widbatv/metadata.json @@ -6,6 +6,7 @@ "icon": "widget.png", "type": "widget", "tags": "widget,battery", + "provides_widgets" : ["battery"], "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widbatv.wid.js","url":"widget.js"} diff --git a/apps/widbatwarn/ChangeLog b/apps/widbatwarn/ChangeLog index 5420b9706..c0e2387db 100644 --- a/apps/widbatwarn/ChangeLog +++ b/apps/widbatwarn/ChangeLog @@ -1,2 +1,3 @@ 0.01: New Battery Warning! 0.02: Respect Quiet Mode +0.03: Use default Bangle formatter for booleans diff --git a/apps/widbatwarn/metadata.json b/apps/widbatwarn/metadata.json index 959eeca08..26143ad4a 100644 --- a/apps/widbatwarn/metadata.json +++ b/apps/widbatwarn/metadata.json @@ -2,7 +2,7 @@ "id": "widbatwarn", "name": "Battery Warning", "shortName": "Battery Warning", - "version": "0.02", + "version": "0.03", "description": "Show a warning when the battery runs low.", "icon": "widget.png", "screenshots": [{"url":"screenshot.png"}], diff --git a/apps/widbatwarn/settings.js b/apps/widbatwarn/settings.js index 8d15c8458..c3464a82b 100644 --- a/apps/widbatwarn/settings.js +++ b/apps/widbatwarn/settings.js @@ -2,9 +2,8 @@ * @param {function} back Use back() to return to settings menu */ (function(back) { - const SETTINGS_FILE = "widbatwarn.json", - storage = require("Storage"), - translate = require("locale").translate; + const SETTINGS_FILE = "widbatwarn.json"; + const storage = require("Storage"); // initialize with default settings... let s = { @@ -39,7 +38,6 @@ }, "Buzz": { value: s.buzz, - format: b => translate(b?"Yes":"No"), onchange: save("buzz"), }, }; diff --git a/apps/widbt/metadata.json b/apps/widbt/metadata.json index e2d5082a5..1623db7a1 100644 --- a/apps/widbt/metadata.json +++ b/apps/widbt/metadata.json @@ -6,6 +6,8 @@ "icon": "widget.png", "type": "widget", "tags": "widget,bluetooth", + "provides_widgets" : ["bluetooth"], + "default" : true, "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widbt.wid.js","url":"widget.js"} diff --git a/apps/widbt_notify/ChangeLog b/apps/widbt_notify/ChangeLog new file mode 100644 index 000000000..d48cdca63 --- /dev/null +++ b/apps/widbt_notify/ChangeLog @@ -0,0 +1,16 @@ +0.02: Tweaks for variable size widget system +0.03: Ensure redrawing works with variable size widget system +0.04: Fix automatic update of Bluetooth connection status +0.05: Make Bluetooth widget thinner, and when on a bright theme use light grey for disabled color +0.06: Tweaking colors for dark/light themes and low bpp screens +0.07: Memory usage improvements +0.08: Disable LCD on, on bluetooth status change +0.09: Vibrate on connection loss +0.10: Bug fix +0.11: Avoid too many notifications. Change disconnected colour to red. +0.12: Prevent repeated execution of `draw()` from the current app. +0.13: Added "connection restored" notification. Fixed restoring of the watchface. +0.14: Added configuration option +0.15: Added option to hide widget when connected +0.16: Simplify code, add option to disable displaying a message +0.17: Minor display fix \ No newline at end of file diff --git a/apps/widbt_notify/metadata.json b/apps/widbt_notify/metadata.json new file mode 100644 index 000000000..5e3f15af2 --- /dev/null +++ b/apps/widbt_notify/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "widbt_notify", + "name": "Bluetooth Widget with Notification", + "version": "0.17", + "description": "Show the current Bluetooth connection status with some optional features: show message, buzz on connect/loss, hide always/if connected.", + "icon": "widget.png", + "type": "widget", + "tags": "widget,bluetooth", + "provides_widgets" : ["bluetooth"], + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"widbt_notify.wid.js","url":"widget.js"}, + {"name":"widbt_notify.settings.js","url":"settings.js"} + ], + "data": [ + {"name":"widbt_notify.json"} + ] +} diff --git a/apps/widbt_notify/settings.js b/apps/widbt_notify/settings.js new file mode 100644 index 000000000..5c67fed7b --- /dev/null +++ b/apps/widbt_notify/settings.js @@ -0,0 +1,57 @@ +(function(back) { + + var filename = "widbt_notify.json"; + + // set Storage and load settings + var storage = require("Storage"); + var settings = Object.assign({ + showWidget: true, + buzzOnConnect: true, + buzzOnLoss: true, + hideConnected: true, + showMessage: true, + nextBuzz: 30000 + }, storage.readJSON(filename, true) || {}); + + // setup boolean menu entries + function boolEntry(key) { + return { + value: settings[key], + onchange: v => { + // change the value of key + settings[key] = v; + // write to storage + storage.writeJSON(filename, settings); + } + }; + } + + // setup menu + var menu = { + "": { + "title": "Bluetooth Widget WN" + }, + "< Back": () => back(), + "Show Widget": boolEntry("showWidget"), + "Buzz on connect": boolEntry("buzzOnConnect"), + "Buzz on loss": boolEntry("buzzOnLoss"), + "Hide connected": boolEntry("hideConnected"), + "Show Message": boolEntry("showMessage"), + "Next Buzz": { + value: settings.nextBuzz, + step: 1000, + min: 1000, + max: 120000, + wrap: true, + format: v => (v / 1000) + "s", + onchange: v => { + settings.nextBuzz = v; + storage.writeJSON(filename, settings); + } + } + }; + + // draw main menu + E.showMenu(menu); + +}) \ No newline at end of file diff --git a/apps/widbt_notify/widget.js b/apps/widbt_notify/widget.js new file mode 100644 index 000000000..4730db5b5 --- /dev/null +++ b/apps/widbt_notify/widget.js @@ -0,0 +1,90 @@ +(function() { + // load settings + var settings = Object.assign({ + showWidget: true, + buzzOnConnect: true, + buzzOnLoss: true, + hideConnected: true, + showMessage: true, + nextBuzz: 30000 + }, require("Storage").readJSON("widbt_notify.json", true) || {}); + + // setup widget with to hide if connected and option set + var widWidth = settings.hideConnected && NRF.getSecurityStatus().connected ? 0 : 15; + + // write widget with loaded settings + WIDGETS.bluetooth_notify = Object.assign(settings, { + + // set area and width + area: "tr", + width: widWidth, + + // setup warning status + warningEnabled: 1, + + draw: function() { + if (this.showWidget) { + g.reset(); + if (NRF.getSecurityStatus().connected) { + if (!this.hideConnected) { + g.setColor((g.getBPP() > 8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f")); + g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="), 2 + this.x, 2 + this.y); + } + } else { + // g.setColor(g.theme.dark ? "#666" : "#999"); + g.setColor("#f00"); // red is easier to distinguish from blue + g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="), 2 + this.x, 2 + this.y); + } + } + }, + + redrawCurrentApp: function() { + if (typeof(draw) == 'function') { + g.reset().clear(); + draw(); + Bangle.loadWidgets(); + Bangle.drawWidgets(); + } else { + load(); // fallback. This might reset some variables + } + }, + + onNRF: function(connect) { + // setup widget with and reload widgets to show/hide if hideConnected is enabled + if (this.hideConnected) { + this.width = connect ? 0 : 15; // ensures correct redraw + Bangle.drawWidgets(); + } else { + // redraw widget + this.draw(); + } + + if (this.warningEnabled) { + if (this.showMessage) { + E.showMessage( /*LANG*/ 'Connection\n' + (connect ? /*LANG*/ 'restored.' : /*LANG*/ 'lost.'), 'Bluetooth'); + setTimeout(() => { + WIDGETS.bluetooth_notify.redrawCurrentApp(); + }, 3000); // clear message - this will reload the widget, resetting 'warningEnabled'. + } + + this.warningEnabled = 0; + setTimeout('WIDGETS.bluetooth_notify.warningEnabled = 1;', this.nextBuzz); // don't buzz for the next X seconds. + + var quiet = (require('Storage').readJSON('setting.json', 1) || {}).quiet; + if (!quiet && (connect ? this.buzzOnConnect : this.buzzOnLoss)) { + Bangle.buzz(700, 1); // buzz on connection resume or loss + } + } + } + + }); + + // clear variables + settings = undefined; + widWidth = undefined; + + // setup bluetooth connection events + NRF.on('connect', (addr) => WIDGETS.bluetooth_notify.onNRF(addr)); + NRF.on('disconnect', () => WIDGETS.bluetooth_notify.onNRF()); + +})() \ No newline at end of file diff --git a/apps/widbt_notify/widget.png b/apps/widbt_notify/widget.png new file mode 100644 index 000000000..1a884a62c Binary files /dev/null and b/apps/widbt_notify/widget.png differ diff --git a/apps/widbthide/metadata.json b/apps/widbthide/metadata.json new file mode 100644 index 000000000..e3ac5cd54 --- /dev/null +++ b/apps/widbthide/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "widbthide", + "name": "Bluetooth Widget (hides when no connection)", + "version": "0.01", + "description": "Shows Bluetooth icon (when connected) in the top right of the clock", + "icon": "widget.png", + "type": "widget", + "tags": "widget,bluetooth", + "provides_widgets" : ["bluetooth"], + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"widbthide.wid.js","url":"widget.js"} + ] +} diff --git a/apps/widbthide/widget.js b/apps/widbthide/widget.js new file mode 100644 index 000000000..620811b36 --- /dev/null +++ b/apps/widbthide/widget.js @@ -0,0 +1,13 @@ +WIDGETS["bluetooth"]={area:"tr",draw:function() { + if (WIDGETS.bluetooth.width==0) + return; + g.reset(); + g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f")); + g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),2+this.x,2+this.y); +},changed:function() { + WIDGETS.bluetooth.width = NRF.getSecurityStatus().connected?15:0; + Bangle.drawWidgets(); +},width:NRF.getSecurityStatus().connected?15:0 +}; +NRF.on('connect',WIDGETS.bluetooth.changed); +NRF.on('disconnect',WIDGETS.bluetooth.changed); diff --git a/apps/widbthide/widget.png b/apps/widbthide/widget.png new file mode 100644 index 000000000..1a884a62c Binary files /dev/null and b/apps/widbthide/widget.png differ diff --git a/apps/widcal/ChangeLog b/apps/widcal/ChangeLog index a4bc24d1a..07b8f7424 100644 --- a/apps/widcal/ChangeLog +++ b/apps/widcal/ChangeLog @@ -1 +1,2 @@ -0.01: First version \ No newline at end of file +0.01: First version +0.02: Fix memory leak \ No newline at end of file diff --git a/apps/widcal/metadata.json b/apps/widcal/metadata.json index 74ab6d488..fc7d6dd1d 100644 --- a/apps/widcal/metadata.json +++ b/apps/widcal/metadata.json @@ -1,7 +1,7 @@ { "id": "widcal", "name": "Calendar Widget", - "version": "0.01", + "version": "0.02", "description": "Widget with the current date", "icon": "widget.png", "type": "widget", diff --git a/apps/widcal/widget.js b/apps/widcal/widget.js index 4214d280a..d4a4676a7 100644 --- a/apps/widcal/widget.js +++ b/apps/widcal/widget.js @@ -24,7 +24,8 @@ ]); } // redraw when date changes - setTimeout(()=>WIDGETS["cal"].draw(), (86401 - Math.floor(date/1000) % 86400)*1000); + if (WIDGETS["cal"].to) clearTimeout(WIDGETS["cal"].to); + WIDGETS["cal"].to = setTimeout(()=>WIDGETS["cal"].draw(), (86401 - Math.floor(date/1000) % 86400)*1000); } }; })(); diff --git a/apps/widclk/ChangeLog b/apps/widclk/ChangeLog index c74857ab4..1a89e2780 100644 --- a/apps/widclk/ChangeLog +++ b/apps/widclk/ChangeLog @@ -3,3 +3,4 @@ 0.04: Fix regression stopping correct widget updates 0.05: Don't show clock widget if already showing clock app 0.06: Use 7 segment font, update *on* the minute, use less memory +0.07: allow turning on/off when quick-switching apps diff --git a/apps/widclk/metadata.json b/apps/widclk/metadata.json index 6996f4080..e4d7d76d1 100644 --- a/apps/widclk/metadata.json +++ b/apps/widclk/metadata.json @@ -1,8 +1,8 @@ { "id": "widclk", "name": "Digital clock widget", - "version": "0.06", - "description": "A simple digital clock widget", + "version": "0.07", + "description": "A simple digital clock widget that appears when not showing a fullscreen clock", "icon": "widget.png", "type": "widget", "tags": "widget,clock", diff --git a/apps/widclk/widget.js b/apps/widclk/widget.js index 9e035ca9a..7c281f761 100644 --- a/apps/widclk/widget.js +++ b/apps/widclk/widget.js @@ -1,10 +1,13 @@ /* Simple clock that appears in the widget bar if no other clock is running. We update once per minute, but don't bother stopping if the */ - -// don't show widget if we know we have a clock app running -if (!Bangle.CLOCK) WIDGETS["wdclk"]={area:"tl",width:52/* g.stringWidth("00:00") */,draw:function() { - g.reset().setFontCustom(atob("AAAAAAAAAAIAAAQCAQAAAd0BgMBdwAAAAAAAdwAB0RiMRcAAAERiMRdwAcAQCAQdwAcERiMRBwAd0RiMRBwAAEAgEAdwAd0RiMRdwAcERiMRdwAFAAd0QiEQdwAdwRCIRBwAd0BgMBAAABwRCIRdwAd0RiMRAAAd0QiEQAAAAAAAAAA="), 32, atob("BgAAAAAAAAAAAAAAAAYCAAYGBgYGBgYGBgYCAAAAAAAABgYGBgYG"), 512+9); +WIDGETS["wdclk"]={area:"tl",width:Bangle.CLOCK?0:52/* g.stringWidth("00:00") */,draw:function() { + if (!Bangle.CLOCK == !this.width) { // if we're the wrong size for if we have a clock or not... + this.width = Bangle.CLOCK?0:52; + return setTimeout(Bangle.drawWidgets,1); // widget changed size - redraw + } + if (!this.width) return; // if size not right, return +g.reset().setFontCustom(atob("AAAAAAAAAAIAAAQCAQAAAd0BgMBdwAAAAAAAdwAB0RiMRcAAAERiMRdwAcAQCAQdwAcERiMRBwAd0RiMRBwAAEAgEAdwAd0RiMRdwAcERiMRdwAFAAd0QiEQdwAdwRCIRBwAd0BgMBAAABwRCIRdwAd0RiMRAAAd0QiEQAAAAAAAAAA="), 32, atob("BgAAAAAAAAAAAAAAAAYCAAYGBgYGBgYGBgYCAAAAAAAABgYGBgYG"), 512+9); var time = require("locale").time(new Date(),1); g.drawString(time, this.x, this.y+3, true); // 5 * 6*2 = 60 // queue draw in one minute diff --git a/apps/widclkbttm/ChangeLog b/apps/widclkbttm/ChangeLog index 326169af5..9dc8f8d2c 100644 --- a/apps/widclkbttm/ChangeLog +++ b/apps/widclkbttm/ChangeLog @@ -1,4 +1,4 @@ 0.01: Fork of widclk v0.04 github.com/espruino/BangleApps/tree/master/apps/widclk 0.02: Modification for bottom widget area and text color 0.03: based in widclk v0.05 compatible at same time, bottom area and color - +0.04: refactored to use less memory, and allow turning on/off when quick-switching apps diff --git a/apps/widclkbttm/metadata.json b/apps/widclkbttm/metadata.json index 9e92f7c46..7c5fe4b63 100644 --- a/apps/widclkbttm/metadata.json +++ b/apps/widclkbttm/metadata.json @@ -2,8 +2,8 @@ "id": "widclkbttm", "name": "Digital clock (Bottom) widget", "shortName": "Digital clock Bottom Widget", - "version": "0.03", - "description": "Displays time in the bottom area.", + "version": "0.04", + "description": "Displays time in the bottom of the screen (may not be compatible with some apps)", "icon": "widclkbttm.png", "type": "widget", "tags": "widget", diff --git a/apps/widclkbttm/widclkbttm.wid.js b/apps/widclkbttm/widclkbttm.wid.js index c27906786..c5e85318c 100644 --- a/apps/widclkbttm/widclkbttm.wid.js +++ b/apps/widclkbttm/widclkbttm.wid.js @@ -1,31 +1,16 @@ -(function() { - // don't show widget if we know we have a clock app running - if (Bangle.CLOCK) return; - - let intervalRef = null; - var width = 5 * 6*2; - var text_color=0x07FF;//cyan - - function draw() { - g.reset().setFont("6x8", 2).setFontAlign(-1, 0).setColor(text_color); - var time = require("locale").time(new Date(),1); - g.drawString(time, this.x, this.y+11, true); // 5 * 6*2 = 60 +WIDGETS["wdclkbttm"]={area:"br",width:Bangle.CLOCK?0:60,draw:function() { + if (!Bangle.CLOCK == !this.width) { // if we're the wrong size for if we have a clock or not... + this.width = Bangle.CLOCK?0:60; + return setTimeout(Bangle.drawWidgets,1); // widget changed size - redraw } - function clearTimers(){ - if(intervalRef) { - clearInterval(intervalRef); - intervalRef = null; - } - } - function startTimers(){ - intervalRef = setInterval(()=>WIDGETS["wdclkbttm"].draw(), 60*1000); - WIDGETS["wdclkbttm"].draw(); - } - Bangle.on('lcdPower', (on) => { - clearTimers(); - if (on) startTimers(); - }); - - WIDGETS["wdclkbttm"]={area:"br",width:width,draw:draw}; - if (Bangle.isLCDOn) intervalRef = setInterval(()=>WIDGETS["wdclkbttm"].draw(), 60*1000); -})() + if (!this.width) return; // if size not right, return + g.reset().setFont("6x8", 2).setFontAlign(-1, 0).setColor("#0ff"); // cyan + var time = require("locale").time(new Date(),1); + g.drawString(time, this.x, this.y+11, true); // 5 * 6*2 = 60 + // queue draw in one minute + if (this.drawTimeout) clearTimeout(this.drawTimeout); + this.drawTimeout = setTimeout(()=>{ + this.drawTimeout = undefined; + this.draw(); + }, 60000 - (Date.now() % 60000)); +}}; diff --git a/apps/widclose/ChangeLog b/apps/widclose/ChangeLog new file mode 100644 index 000000000..1e0c86fc6 --- /dev/null +++ b/apps/widclose/ChangeLog @@ -0,0 +1,2 @@ +0.01: New widget! +0.02: allow turning on/off when quick-switching apps diff --git a/apps/widclose/README.md b/apps/widclose/README.md new file mode 100644 index 000000000..55c8de483 --- /dev/null +++ b/apps/widclose/README.md @@ -0,0 +1,7 @@ +# Close Button + +Adds a ![X](preview.png) button to close the current app and go back to the clock. +(Widget is not visible on the clock screen) + +![Light theme screenshot](screenshot_light.png) +![Dark theme screenshot](screenshot_dark.png) \ No newline at end of file diff --git a/apps/widclose/icon.png b/apps/widclose/icon.png new file mode 100644 index 000000000..1d95ba0ce Binary files /dev/null and b/apps/widclose/icon.png differ diff --git a/apps/widclose/metadata.json b/apps/widclose/metadata.json new file mode 100644 index 000000000..19009fd82 --- /dev/null +++ b/apps/widclose/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "widclose", + "name": "Close Button", + "version": "0.02", + "description": "A button to close the current app", + "readme": "README.md", + "icon": "icon.png", + "type": "widget", + "tags": "widget,tools", + "supports": ["BANGLEJS2"], + "screenshots": [{"url":"screenshot_light.png"},{"url":"screenshot_dark.png"}], + "storage": [ + {"name":"widclose.wid.js","url":"widget.js"} + ] +} diff --git a/apps/widclose/preview.png b/apps/widclose/preview.png new file mode 100644 index 000000000..d90a3b4c5 Binary files /dev/null and b/apps/widclose/preview.png differ diff --git a/apps/widclose/screenshot_dark.png b/apps/widclose/screenshot_dark.png new file mode 100644 index 000000000..58067a3b9 Binary files /dev/null and b/apps/widclose/screenshot_dark.png differ diff --git a/apps/widclose/screenshot_light.png b/apps/widclose/screenshot_light.png new file mode 100644 index 000000000..32817ea8d Binary files /dev/null and b/apps/widclose/screenshot_light.png differ diff --git a/apps/widclose/widget.js b/apps/widclose/widget.js new file mode 100644 index 000000000..aeba1de00 --- /dev/null +++ b/apps/widclose/widget.js @@ -0,0 +1,17 @@ +WIDGETS.close = { + area: "tr", width: Bangle.CLOCK?0:24, sortorder: 10, // we want the right-most spot please + draw: function() { + if (!Bangle.CLOCK == !this.width) { // if we're the wrong size for if we have a clock or not... + this.width = Bangle.CLOCK?0:24; + return setTimeout(Bangle.drawWidgets,1); // widget changed size - redraw + } + if (this.width) g.reset().setColor("#f00").drawImage(atob( // red to match setUI back button + // b/w version of preview.png, 24x24 + "GBgBABgAAf+AB//gD//wH//4P//8P//8fn5+fjx+fxj+f4H+/8P//8P/f4H+fxj+fjx+fn5+P//8P//8H//4D//wB//gAf+AABgA" + ), this.x, this.y); + }, touch: function(_, c) { // if touched + const w = WIDGETS.close; // if in range, go back to the clock + if (w && c.x>=w.x && c.x=w.y && c.y<=w.y+24) load(); + } +}; +Bangle.on("touch", WIDGETS.close.touch); diff --git a/apps/widcloselaunch/ChangeLog b/apps/widcloselaunch/ChangeLog new file mode 100644 index 000000000..1e0c86fc6 --- /dev/null +++ b/apps/widcloselaunch/ChangeLog @@ -0,0 +1,2 @@ +0.01: New widget! +0.02: allow turning on/off when quick-switching apps diff --git a/apps/widcloselaunch/README.md b/apps/widcloselaunch/README.md new file mode 100644 index 000000000..1eb384ce1 --- /dev/null +++ b/apps/widcloselaunch/README.md @@ -0,0 +1,9 @@ +# Close Button Launcher + +Adds a ![X](preview.png) button to close the current app and go back to the launcher. +(Widget is not visible on the clock screen) + +Copied from widclose by @rigrig and slightly modified. + +![Light theme screenshot](screenshot_light.png) +![Dark theme screenshot](screenshot_dark.png) diff --git a/apps/widcloselaunch/icon.png b/apps/widcloselaunch/icon.png new file mode 100644 index 000000000..1d95ba0ce Binary files /dev/null and b/apps/widcloselaunch/icon.png differ diff --git a/apps/widcloselaunch/metadata.json b/apps/widcloselaunch/metadata.json new file mode 100644 index 000000000..b5c83e37e --- /dev/null +++ b/apps/widcloselaunch/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "widcloselaunch", + "name": "Close Button to launcher", + "version": "0.02", + "description": "A button to close the current app and go to launcher", + "readme": "README.md", + "icon": "icon.png", + "type": "widget", + "tags": "widget,tools", + "supports": ["BANGLEJS2"], + "screenshots": [{"url":"screenshot_light.png"},{"url":"screenshot_dark.png"}], + "storage": [ + {"name":"widcloselaunch.wid.js","url":"widget.js"} + ] +} diff --git a/apps/widcloselaunch/preview.png b/apps/widcloselaunch/preview.png new file mode 100644 index 000000000..d90a3b4c5 Binary files /dev/null and b/apps/widcloselaunch/preview.png differ diff --git a/apps/widcloselaunch/screenshot_dark.png b/apps/widcloselaunch/screenshot_dark.png new file mode 100644 index 000000000..58067a3b9 Binary files /dev/null and b/apps/widcloselaunch/screenshot_dark.png differ diff --git a/apps/widcloselaunch/screenshot_light.png b/apps/widcloselaunch/screenshot_light.png new file mode 100644 index 000000000..32817ea8d Binary files /dev/null and b/apps/widcloselaunch/screenshot_light.png differ diff --git a/apps/widcloselaunch/widget.js b/apps/widcloselaunch/widget.js new file mode 100644 index 000000000..2c9147d16 --- /dev/null +++ b/apps/widcloselaunch/widget.js @@ -0,0 +1,17 @@ +WIDGETS.close = { + area: "tr", width: Bangle.CLOCK?0:24, sortorder: 10, // we want the right-most spot please + draw: function() { + if (!Bangle.CLOCK == !this.width) { // if we're the wrong size for if we have a clock or not... + this.width = Bangle.CLOCK?0:24; + return setTimeout(Bangle.drawWidgets,1); // widget changed size - redraw + } + if (this.width) g.reset().setColor("#f00").drawImage(atob( // red to match setUI back button + // b/w version of preview.png, 24x24 + "GBgBABgAAf+AB//gD//wH//4P//8P//8fn5+fjx+fxj+f4H+/8P//8P/f4H+fxj+fjx+fn5+P//8P//8H//4D//wB//gAf+AABgA" + ), this.x, this.y); + }, touch: function(_, c) { + const w = WIDGETS.close; + if (w && c.x>=w.x && c.x=w.y && c.y<=w.y+24) Bangle.showLauncher(); + } +}; +Bangle.on("touch", WIDGETS.close.touch); diff --git a/apps/widcw/ChangeLog b/apps/widcw/ChangeLog new file mode 100644 index 000000000..07b8f7424 --- /dev/null +++ b/apps/widcw/ChangeLog @@ -0,0 +1,2 @@ +0.01: First version +0.02: Fix memory leak \ No newline at end of file diff --git a/apps/widcw/logo.svg b/apps/widcw/logo.svg new file mode 100644 index 000000000..e093414d5 --- /dev/null +++ b/apps/widcw/logo.svg @@ -0,0 +1,62 @@ + + + + + + + + CW + + diff --git a/apps/widcw/metadata.json b/apps/widcw/metadata.json new file mode 100644 index 000000000..467ab1729 --- /dev/null +++ b/apps/widcw/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "widcw", + "name": "Calendar Week Widget", + "version": "0.02", + "description": "Widget which shows the current calendar week", + "icon": "widget.png", + "type": "widget", + "tags": "widget,calendar", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"widcw.wid.js","url":"widget.js"} + ] +} diff --git a/apps/widcw/widget.js b/apps/widcw/widget.js new file mode 100644 index 000000000..e33ad0aad --- /dev/null +++ b/apps/widcw/widget.js @@ -0,0 +1,48 @@ +(function() { + var width = 22; // width of the widget + + function draw() { + const x = this.x, y = this.y, x2 = x+21, y2 = y+23; + + var date = new Date(); + + // Calculate calendar week (https://stackoverflow.com/a/6117889) + getCW= function(date){ + var d=new Date(date.getFullYear(), date.getMonth(), date.getDate()); + var dayNum = d.getDay() || 7; + d.setDate(d.getDate() + 4 - dayNum); + var yearStart = new Date(d.getFullYear(),0,1); + return Math.ceil((((d - yearStart) / 86400000) + 1)/7); + }; + + g.reset().setFontAlign(0, 0) // center all text + // header + .setBgColor("#f00").setColor("#fff") + .clearRect(x, y, x2, y+8).setFont("4x6").drawString("CW", (x+x2)/2+1, y+5) + // date + .setBgColor("#fff").setColor("#000") + .clearRect(x, y+9, x2, y2).setFont("Vector:16").drawString(getCW(date), (x+x2)/2+2, y+17); + + if (!g.theme.dark) { + // black border around date for light themes + g.setColor("#000").drawPoly([ + x, y+9, + x, y2, + x2, y2, + x2, y+9 + ]); + } + + // redraw when date changes + if (WIDGETS["widcw"].to) clearTimeout(WIDGETS["widcw"].to); + WIDGETS["widcw"].to = setTimeout(()=>WIDGETS["widcw"].draw(), (86401 - Math.floor(date/1000) % 86400)*1000); + } + + // add your widget + WIDGETS["widcw"]={ + area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right) + width: width, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout + draw:draw // called to draw the widget + }; + +})(); diff --git a/apps/widcw/widget.png b/apps/widcw/widget.png new file mode 100644 index 000000000..c73d40c5a Binary files /dev/null and b/apps/widcw/widget.png differ diff --git a/apps/widcw/widget.svg b/apps/widcw/widget.svg new file mode 100644 index 000000000..d3e567286 --- /dev/null +++ b/apps/widcw/widget.svg @@ -0,0 +1,55 @@ + + + + + + + + CW + + diff --git a/apps/widday/ChangeLog b/apps/widday/ChangeLog new file mode 100644 index 000000000..7b83706bf --- /dev/null +++ b/apps/widday/ChangeLog @@ -0,0 +1 @@ +0.01: First release diff --git a/apps/widday/README.md b/apps/widday/README.md new file mode 100644 index 000000000..61f48aa4c --- /dev/null +++ b/apps/widday/README.md @@ -0,0 +1,9 @@ +# Day Widget + +Just shows the day of the current date, to save space in the widget area. The month and year should be known because they don't change that often. Just the number in maximum size for readability. + +![](screenshot.png) + +## Creator +[@pidajo](https://github.com/pidajo) + diff --git a/apps/widday/app-icon.js b/apps/widday/app-icon.js new file mode 100644 index 000000000..aa17aedc5 --- /dev/null +++ b/apps/widday/app-icon.js @@ -0,0 +1 @@ +atob("MDCEAiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIiIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIiIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIiIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIiIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIiIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIiIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIiIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIiIBEREREREREREREREREREREREREREQIiIf///////////////////////////xIiIf///////////////////////////xIiIf///////////////////////////xIiIf///////////////////////////xIiIf///////////////////////////xIiIf///////////////////////////xIiIf///////////////////////////xIiIf///////zMz////8zMzMzMzP////xIiIf///////czM////PMzMzMzMzP///xIiIf/////zzMzM////PMzMzMzMzP///xIiIf////PczMzM////PMzMzMzMzP///xIiIf////3MzMzM////8zMzMz3MzP///xIiIf////3MM8zM//////////zMw////xIiIf////0/88zM/////////zzMz////xIiIf//////88zM/////////8zM3////xIiIf//////88zM////////88zM/////xIiIf//////88zM/////////MzN/////xIiIf//////88zM////////PMzD/////xIiIf//////88zM////////3Mzf/////xIiIf//////88zM////////zMw//////xIiIf//////88zM///////9zMz//////xIiIf//////88zM///////8zMP//////xIiIf//////88zM//////88zM///////xIiIf//////88zM///////MzN///////xIiIf////8zM8zM//////PMzD///////xIiIf////3MzMzMzM3///3Mzf///////xIiIf////3MzMzMzM3//zzMw////////xIiIf////PMzMzMzM3//9zMz////////xIiIf///////////////////////////xIiIf///////////////////////////xIiIf///////////////////////////xIiIf///////////////////////////xIiIf///////////////////////////xIiIf///////////////////////////xIiIf///////////////////////////xIiIf///////////////////////////xIiIBEREREREREREREREREREREREREREQIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIg==") diff --git a/apps/widday/metadata.json b/apps/widday/metadata.json new file mode 100644 index 000000000..82de23035 --- /dev/null +++ b/apps/widday/metadata.json @@ -0,0 +1,15 @@ +{ "id": "widday", + "name": "Day Widget", + "shortName":"My Timer", + "icon": "widget.png", + "type": "widget", + "version":"0.01", + "description": "Just the day of the current date as widget", + "readme": "README.md", + "tags": "widget,say,date", + "supports": ["BANGLEJS", "BANGLEJS2"], + "screenshots" : [ { "url":"screenshot.png" } ], + "storage": [ + {"name":"widday.wid.js","url":"widget.js"} + ] +} diff --git a/apps/widday/screenshot.png b/apps/widday/screenshot.png new file mode 100644 index 000000000..9a7a9f8cf Binary files /dev/null and b/apps/widday/screenshot.png differ diff --git a/apps/widday/widget.js b/apps/widday/widget.js new file mode 100644 index 000000000..cdea76a29 --- /dev/null +++ b/apps/widday/widget.js @@ -0,0 +1,27 @@ +(() => { + var width = 32; // width of the widget + + function draw() { + var date = new Date(); + g.reset(); // reset the graphics context to defaults (color/font/etc) + g.setFontAlign(0,1); // center fonts + //g.drawRect(this.x, this.y, this.x+width-1, this.y+23); // check the bounds! + + var text = date.getDate(); + g.setFont("Vector", 24); + g.drawString(text, this.x+width/2+1, this.y + 28); + //g.setColor(0, 0, 1); + //g.drawRect(this.x, this.y, this.x+width-2, this.y+1); + } + + setInterval(function() { + WIDGETS["widday"].draw(WIDGETS["widdateday"]); + }, 10*60000); // update every 10 minutes + + // add your widget + WIDGETS["widday"]={ + area:"bl", // tl (top left), tr (top right), bl (bottom left), br (bottom right) + width: width, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout + draw:draw // called to draw the widget + }; +})() diff --git a/apps/widday/widget.png b/apps/widday/widget.png new file mode 100644 index 000000000..462097871 Binary files /dev/null and b/apps/widday/widget.png differ diff --git a/apps/widdst/ChangeLog b/apps/widdst/ChangeLog new file mode 100644 index 000000000..e350137ee --- /dev/null +++ b/apps/widdst/ChangeLog @@ -0,0 +1,2 @@ +0.01: Initial version +0.02: Checks for correct firmware; E.setDST(...) moved to boot.js diff --git a/apps/widdst/README.md b/apps/widdst/README.md new file mode 100644 index 000000000..ca41c2bed --- /dev/null +++ b/apps/widdst/README.md @@ -0,0 +1,34 @@ +# Daylight savings time widget + +This widget will set the daylight saving rules on your watch. Note you may need a firmware update before this can work. + +## Settings + +You need to set your timezone, and your daylight savings rules, in `Settings -> Apps -> Daylight Savings`. The settings are + +**Enabled** enables or disables the widget + +**Icon** enables or disables the showing of a small "DST" icon when daylight savings is in effect + +**Base TZ** is your "base" timezone - i.e. the timezone you keep when daylight savings is not in effect. It is positive east. + +**Change** is the size of the daylight savings change, which is +1 hour almost everywhere. + +**DST Start, DST End** set the rules for the start and end of daylight savings. + +## Setting the daylight savings rules + +The page for setting **DST Start** and **DST End** ask you to describe the rules for daylight savings changes. For instance, in the UK, the rules are + +**DST Start** `The [last] [Sun] of [Mar] minus [0 days] at [01:00]` + +**DST End** `The [last] [Sun] of [Oct] minus [0 days] at [02:00]` + +In most of the US, the rules are + +**DST Start** `The [2nd] [Sun] of [Mar] minus [0 days] at [02:00]` + +**DST End** `The [1st] [Sun] of [Nov] minus [0 days] at [02:00]` + + + diff --git a/apps/widdst/boot.js b/apps/widdst/boot.js new file mode 100644 index 000000000..b0a844532 --- /dev/null +++ b/apps/widdst/boot.js @@ -0,0 +1,15 @@ +(() => { + + if (E.setDST) { + var dstSettings = require('Storage').readJSON('widdst.json',1)||{}; + if (dstSettings.has_dst) { + E.setDST(60*dstSettings.dst_size, 60*dstSettings.tz, dstSettings.dst_start.dow_number, dstSettings.dst_start.dow, + dstSettings.dst_start.month, dstSettings.dst_start.day_offset, 60*dstSettings.dst_start.at, + dstSettings.dst_end.dow_number, dstSettings.dst_end.dow, dstSettings.dst_end.month, dstSettings.dst_end.day_offset, + 60*dstSettings.dst_end.at); + } else { + E.setDST(0,0,0,0,0,0,0,0,0,0,0,0); + } + } + +})() diff --git a/apps/widdst/icon.png b/apps/widdst/icon.png new file mode 100644 index 000000000..aa7142959 Binary files /dev/null and b/apps/widdst/icon.png differ diff --git a/apps/widdst/metadata.json b/apps/widdst/metadata.json new file mode 100644 index 000000000..144c02998 --- /dev/null +++ b/apps/widdst/metadata.json @@ -0,0 +1,16 @@ +{ "id": "widdst", + "name": "Daylight Saving", + "version":"0.02", + "description": "Widget to set daylight saving rules. Requires Espruino 2v14.49 or later - see the instructions below for more information.", + "icon": "icon.png", + "type": "widget", + "tags": "widget,tool", + "supports" : ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"widdst.wid.js","url":"widget.js"}, + {"name":"widdst.boot.js","url":"boot.js"}, + {"name":"widdst.settings.js","url":"settings.js"} + ], + "data": [{"name":"widdst.json"}] +} diff --git a/apps/widdst/settings.js b/apps/widdst/settings.js new file mode 100644 index 000000000..9a7e579b7 --- /dev/null +++ b/apps/widdst/settings.js @@ -0,0 +1,184 @@ +(function(back) { + + var dows = require("date_utils").dows(0,1); + var months = require("date_utils").months(1); + + var settings = Object.assign({ + has_dst: false, + show_icon: true, + tz: 0, + dst_size: 1, + dst_start: { + dow_number: 4, // "1st", "2nd", "3rd", "4th", "last" + dow: 0, // "Sun", "Mon", ... + month: 2, + day_offset: 0, + at: 1 + }, + dst_end: { + dow_number: 4, + dow: 0, + month: 9, + day_offset: 0, + at: 2 + } + }, require("Storage").readJSON("widdst.json", true) || {}); + + var dst_start_end = { + is_start: true, + day_offset: 0, + dow_number: 0, + dow: 0, + month: 0, + at: 0 + }; + + function writeSettings() { + require('Storage').writeJSON("widdst.json", settings); + } + + function writeSubMenuSettings() { + if (dst_start_end.is_start) { + settings.dst_start.day_offset = dst_start_end.day_offset; + settings.dst_start.dow_number = dst_start_end.dow_number; + settings.dst_start.dow = dst_start_end.dow; + settings.dst_start.month = dst_start_end.month; + settings.dst_start.at = dst_start_end.at; + } else { + settings.dst_end.day_offset = dst_start_end.day_offset; + settings.dst_end.dow_number = dst_start_end.dow_number; + settings.dst_end.dow = dst_start_end.dow; + settings.dst_end.month = dst_start_end.month; + settings.dst_end.at = dst_start_end.at; + } + writeSettings(); + } + + function hoursToString(h) { + return (h|0) + ':' + (((6*h)%6)|0) + (((60*h)%10)|0); + } + + function getDSTStartEndMenu(start) { + dst_start_end.is_start = start; + if (start) { + dst_start_end.day_offset = settings.dst_start.day_offset; + dst_start_end.dow_number = settings.dst_start.dow_number; + dst_start_end.dow = settings.dst_start.dow; + dst_start_end.month = settings.dst_start.month; + dst_start_end.at = settings.dst_start.at; + } else { + dst_start_end.day_offset = settings.dst_end.day_offset; + dst_start_end.dow_number = settings.dst_end.dow_number; + dst_start_end.dow = settings.dst_end.dow; + dst_start_end.month = settings.dst_end.month; + dst_start_end.at = settings.dst_end.at; + } + return { + "": { + "Title": start ? /*LANG*/"DST Start" : /*LANG*/"DST End" + }, + "< Back": () => E.showMenu(dstMenu), + /*LANG*/"The" : { + value: dst_start_end.dow_number, + format: v => [/*LANG*/"1st",/*LANG*/"2nd",/*LANG*/"3rd",/*LANG*/"4th",/*LANG*/"last"][v], + min: 0, + max: 4, + onchange: v => { + dst_start_end.dow_number = v; + writeSubMenuSettings(); + } + }, + " -" : { + value: dst_start_end.dow, + format: v => dows[v], + min:0, + max:6, + onchange: v => { + dst_start_end.dow = v; + writeSubMenuSettings(); + } + }, + /*LANG*/"of": { + value: dst_start_end.month, + format: v => months[v], + min: 0, + max: 11, + onchange: v => { + dst_start_end.month = v; + writeSubMenuSettings(); + } + }, + /*LANG*/"minus" : { + value: dst_start_end.day_offset, + format: v => v + ((v == 1) ? /*LANG*/" day" : /*LANG*/" days"), + min: 0, + max: 7, + onchange: v => { + dst_start_end.day_offset = v; + writeSubMenuSettings(); + } + }, + /*LANG*/"at": { + value: dst_start_end.at, + format: v => hoursToString(v), + min: 0, + max: 23, + // step: 0.05, // every 3 minutes - FOR DEVELOPMENT PURPOSES + onchange: v => { + dst_start_end.at = v; + writeSubMenuSettings(); + } + } + } + } + + var dstMenu = { + "": { + "Title": /*LANG*/"Daylight Saving" + }, + "< Back": () => back(), + /*LANG*/"Enabled": { + value: settings.has_dst, + format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", + onchange: v => { + settings.has_dst = v; + writeSettings(); + } + }, + /*LANG*/"Icon": { + value: settings.show_icon, + format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", + onchange: v => { + settings.show_icon = v; + writeSettings(); + } + }, + /*LANG*/"Base TZ": { + value: settings.tz, + format: v => (v >= 0 ? '+' + hoursToString(v) : '-' + hoursToString(-v)), + onchange: v => { + settings.tz = v; + writeSettings(); + }, + min: -13, + max: 13, + step: 0.25 + }, + /*LANG*/"Change": { + value: settings.dst_size, + format: v => (v >= 0 ? '+' + hoursToString(v): '-' + hoursToString(-v)), + min: -1, + max: 1, + step: 0.5, + onchange: v=> { + settings.dst_size = v; + writeSettings(); + } + }, + /*LANG*/"DST Start": () => E.showMenu(getDSTStartEndMenu(true)), + /*LANG*/"DST End": () => E.showMenu(getDSTStartEndMenu(false)) + }; + + E.showMenu(dstMenu); + +}); diff --git a/apps/widdst/widget.js b/apps/widdst/widget.js new file mode 100644 index 000000000..f690cc68f --- /dev/null +++ b/apps/widdst/widget.js @@ -0,0 +1,55 @@ +(() => { + + // our setTimeout() return value for the function that periodically check the status of DST + var check_timeout = undefined; + + // Called by draw() when we are not in DST or when we are not displaying the icon + function clear() { + if (this.width) { + this.width = 0; + Bangle.drawWidgets(); + } + } + + // draw, or erase, our little "dst" icon in the widgets area + function draw() { + var dstSettings = require('Storage').readJSON('widdst.json',1)||{}; + if ((dstSettings.has_dst) && (dstSettings.show_icon)) { + var now = new Date(); + if (now.getIsDST()) { + if (this.width) { + g.drawImage( + { + width : 24, height : 24, bpp : 1, palette: new Uint16Array([g.theme.bg, g.theme.fg]), + buffer : atob("AAAAAAAAPAAAIgAAIQAAIQAAIQAAIQAAIngAPIQAAIAAAPAAAAwAAAQAAIX8AHggAAAgAAAgAAAgAAAgAAAgAAAgAAAAAAAA") + }, this.x, this.y + ); + } else { + this.width = 24; + Bangle.drawWidgets(); + } + } else { + clear(); + } + if (check_timeout) clearTimeout(check_timeout); + check_timeout = setTimeout( function() { + check_timeout = undefined; + draw(); + }, 3600000 - (now.getTime() % 3600000)); // Check every hour. + } else { + clear(); + } + } + + // Register ourselves + if (E.setDST) { + WIDGETS["widdst"] = { + area: "tl", + width: 0, + draw: draw + }; + } else { + E.showAlert("Firmware update needed to support Daylight Saving Time"); + } + +})() diff --git a/apps/widgps/ChangeLog b/apps/widgps/ChangeLog index 3d47b8a0c..b530843e7 100644 --- a/apps/widgps/ChangeLog +++ b/apps/widgps/ChangeLog @@ -2,3 +2,8 @@ 0.02: Don't break if running on 2v08 firmware (just don't display anything) 0.03: Fix positioning 0.04: Show GPS fix status +0.05: Don't poll for GPS status, override setGPSPower handler (fix #1456) +0.06: Periodically update so the always on display does show current GPS fix +0.07: Alternative marker icon (configurable via settings) +0.08: Add ability to hide the icon when GPS is off, for a cleaner appearance. +0.09: Do not take widget space if icon is hidden diff --git a/apps/widgps/README.md b/apps/widgps/README.md index 37e088bcf..98f0ba6f7 100644 --- a/apps/widgps/README.md +++ b/apps/widgps/README.md @@ -6,12 +6,22 @@ The GPS can quickly run the battery down if it is on all the time so it is useful to know if it has been switched on or not. - Uses Bangle.isGPSOn() -- Shows in grey when the GPS is off -- Shows in amber when the GPS is on but has no fix -- Shows in green when the GPS is on and has a fix +There are two icons which can be used to visualize the GPS/GNSS status: +1. A cross colored depending on the GPS/GNSS status + - Shows in grey when the GPS is off + - Shows in amber when the GPS is on but has no fix + - Shows in green when the GPS is on and has a fix +2. Different place markers depending on GPS/GNSS status + +You can also choose to hide the icon when the GPS is off in the settings. + Written by: [Hugh Barney](https://github.com/hughbarney) For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/) Extended by Marco ([myxor](https://github.com/myxor)) + +Extended by khromov ([khromov](https://github.com/khromov)) + +Place marker icons from [icons8.com](https://icons8.com/icon/set/maps/material-outlined). diff --git a/apps/widgps/default.json b/apps/widgps/default.json new file mode 100644 index 000000000..28482ddb0 --- /dev/null +++ b/apps/widgps/default.json @@ -0,0 +1 @@ +{"crossIcon": true, "hideWhenGpsOff": false} \ No newline at end of file diff --git a/apps/widgps/metadata.json b/apps/widgps/metadata.json index 2f59aa82a..cfd35f5bb 100644 --- a/apps/widgps/metadata.json +++ b/apps/widgps/metadata.json @@ -1,14 +1,19 @@ { "id": "widgps", "name": "GPS Widget", - "version": "0.04", - "description": "Tiny widget to show the power and fix status of the GPS", + "version": "0.09", + "description": "Tiny widget to show the power and fix status of the GPS/GNSS", "icon": "widget.png", "type": "widget", "tags": "widget,gps", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ - {"name":"widgps.wid.js","url":"widget.js"} + {"name":"widgps.wid.js","url":"widget.js"}, + {"name":"widgps.default.json","url":"default.json"}, + {"name":"widgps.settings.js","url":"settings.js"} + ], + "data": [ + {"name":"widgps.json"} ] } diff --git a/apps/widgps/settings.js b/apps/widgps/settings.js new file mode 100644 index 000000000..7a1c186c9 --- /dev/null +++ b/apps/widgps/settings.js @@ -0,0 +1,32 @@ +(function(back) { +function writeSettings(key, value) { + var s = require('Storage').readJSON(FILE, true) || {}; + s[key] = value; + require('Storage').writeJSON(FILE, s); + readSettings(); +} + +function readSettings() { + settings = Object.assign( + require('Storage').readJSON("widgps.default.json", true) || {}, + require('Storage').readJSON(FILE, true) || {}); +} + +var FILE = "widgps.json"; +var settings; +readSettings(); + +var mainmenu = { + '' : {'title' : 'GPS widget'}, + '< Back' : back, + "Cross icon" : { + value : settings.crossIcon , + onchange : v => { writeSettings("crossIcon", v); } + }, + "Hide icon when GPS off" : { + value : settings.hideWhenGpsOff , + onchange : v => { writeSettings("hideWhenGpsOff", v); } + }, +}; +E.showMenu(mainmenu); +}); \ No newline at end of file diff --git a/apps/widgps/widget.js b/apps/widgps/widget.js index 25df2178a..73351eaa6 100644 --- a/apps/widgps/widget.js +++ b/apps/widgps/widget.js @@ -1,33 +1,85 @@ -(function(){ - if (!Bangle.isGPSOn) return; // old firmware +(function() { - function draw() { +let settings = Object.assign( + require('Storage').readJSON("widgps.default.json", true) || {}, + require('Storage').readJSON("widgps.json", true) || {} +); + +var interval; + +// override setGPSPower so we know if GPS is on or off +var oldSetGPSPower = Bangle.setGPSPower; +Bangle.setGPSPower = function(on, id) { + var isGPSon = oldSetGPSPower(on, id); + WIDGETS.gps.width = !isGPSon && settings.hideWhenGpsOff ? 0 : 24; + Bangle.drawWidgets(); + return isGPSon; +}; + +WIDGETS.gps = { + area : "tr", + width : !Bangle.isGPSOn() && settings.hideWhenGpsOff ? 0 : 24, + draw : function() { g.reset(); - if (Bangle.isGPSOn()) { - const gpsObject = Bangle.getGPSFix(); - if (gpsObject && gpsObject.fix > 0) { - g.setColor("#0F0"); // on and has fix = green + + // check if we need to update the widget periodically + if (Bangle.isGPSOn() && interval === undefined) { + interval = setInterval( + function() { WIDGETS.gps.draw(WIDGETS.gps); }, + 10 * 1000); // update every 10 seconds to show gps fix/no fix + } else if (!Bangle.isGPSOn() && interval !== undefined) { + clearInterval(interval); + interval = undefined; + } + if (settings.crossIcon) { + if (Bangle.isGPSOn()) { + const gpsObject = Bangle.getGPSFix(); + if (gpsObject && gpsObject.fix > 0) { + g.setColor("#0F0"); // on and has fix = green + } else { + g.setColor("#FD0"); // on but no fix = amber + } + + g.drawImage( + atob( + "GBiBAAAAAAAAAAAAAA//8B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+A//8AAAAAAAAAAAAA=="), + this.x, 2 + this.y); + } else { - g.setColor("#FD0"); // on but no fix = amber + if(!settings.hideWhenGpsOff) { + g.setColor("#888"); // off = grey + + g.drawImage( + atob( + "GBiBAAAAAAAAAAAAAA//8B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+A//8AAAAAAAAAAAAA=="), + this.x, 2 + this.y); + } + } + + } else { // marker icons + if (Bangle.isGPSOn()) { + const gpsObject = Bangle.getGPSFix(); + if (gpsObject && gpsObject.fix > 0) { + // on and has fix + g.drawImage( + atob("GBiBAAAAAAAAAAB+AAD/AAHDgAMAwAcAwAY8YAY8YAY8YAY8YAMAwAMAwAOBwAGBgAHDgADDAABmAAB+AAA8AAAYAAAAAAAAAAAAAA=="), + this.x, 2 + this.y); + + } else { + // GNSS on but no fix + g.drawImage( + atob("GBiBAAAAAAAAAAh+AA3/gA+B4A8AcA+AMAA8GAB+GADnDADDDADDDDDDADBmADB+ABg8ABgYAAwB8A4A8AeB8AH/sAB+EAAAAAAAAA=="), + this.x, 2 + this.y); + } + } else { + // GNSS off + if(!settings.hideWhenGpsOff) { + g.drawImage( + atob("GBiBAAAAAAAAAAB+ABj/ABxDgA4AwAcAwAeMYAfEYAbgYAZwYAM4wAMcQAOOAAGHAAHDgADDwABm4AB+cAA8OAAYGAAAAAAAAAAAAA=="), + this.x, 2 + this.y); + } } - } else { - g.setColor("#888"); // off = grey } - g.drawImage(atob("GBiBAAAAAAAAAAAAAA//8B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+A//8AAAAAAAAAAAAA=="), this.x, 2+this.y); } - - var timerInterval; - Bangle.on('lcdPower', function(on) { - if (on) { - WIDGETS.gps.draw(); - if (!timerInterval) timerInterval = setInterval(()=>WIDGETS.gps.draw(), 2000); - } else { - if (timerInterval) { - clearInterval(timerInterval); - timerInterval = undefined; - } - } - }); - - WIDGETS.gps={area:"tr",width:24,draw:draw}; +}; })(); diff --git a/apps/widhrm/ChangeLog b/apps/widhrm/ChangeLog index 93e2eaf66..1b5d6da57 100644 --- a/apps/widhrm/ChangeLog +++ b/apps/widhrm/ChangeLog @@ -3,3 +3,4 @@ 0.03: Ensure redrawing works with variable size widget system 0.04: tag HRM power requests to allow this ot work alongside other widgets/apps (fix #799) 0.05: Use new 'lock' event, not LCD (so it works on Bangle.js 2) +0.06: Allow configuration of minimum confidence for HRM values diff --git a/apps/widhrm/metadata.json b/apps/widhrm/metadata.json index e566142d2..51a7f3392 100644 --- a/apps/widhrm/metadata.json +++ b/apps/widhrm/metadata.json @@ -1,13 +1,15 @@ { "id": "widhrm", "name": "Simple Heart Rate widget", - "version": "0.05", + "version": "0.06", "description": "When the screen is on, the widget turns on the heart rate monitor and displays the current heart rate (or last known in grey). For this to work well you'll need at least a 15 second LCD Timeout.", "icon": "widget.png", "type": "widget", "tags": "health,widget", "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ - {"name":"widhrm.wid.js","url":"widget.js"} - ] + {"name":"widhrm.wid.js","url":"widget.js"}, + {"name":"widhrm.settings.js","url":"settings.js"} + ], + "data": [{"name":"widhrm.json"}] } diff --git a/apps/widhrm/settings.js b/apps/widhrm/settings.js new file mode 100644 index 000000000..0b8c989ac --- /dev/null +++ b/apps/widhrm/settings.js @@ -0,0 +1,36 @@ +/** + * @param {function} back Use back() to return to settings menu + */ +(function(back) { + const SETTINGS_FILE = "widhrm.json"; + const storage = require("Storage"); + + let s = { + confidence: 0 + }; + const saved = storage.readJSON(SETTINGS_FILE, 1) || {}; + for(const key in saved) { + s[key] = saved[key]; + } + + function save(key, value) { + s[key] = value; + storage.write(SETTINGS_FILE, s); + } + + const menu = { + "": {"title": "Simple Heart Rate widget"}, + "< Back": back, + /*LANG*/'min. confidence': { + value: s.confidence, + min: 0, + max : 100, + step: 5, + format: x => { + return x + "%"; + }, + onchange: x => save('confidence', x), + }, + }; + E.showMenu(menu); +}); diff --git a/apps/widhrm/widget.js b/apps/widhrm/widget.js index 7ffe1aa6d..891f284a7 100644 --- a/apps/widhrm/widget.js +++ b/apps/widhrm/widget.js @@ -1,5 +1,18 @@ (() => { if (!Bangle.isLocked) return; // old firmware + + const SETTINGS_FILE = 'widhrm.json'; + let settings; + function loadSettings() { + settings = require('Storage').readJSON(SETTINGS_FILE, 1) || {}; + const DEFAULTS = { + 'confidence': 0 + }; + Object.keys(DEFAULTS).forEach(k=>{ + if (settings[k]===undefined) settings[k]=DEFAULTS[k]; + }); + } + var currentBPM; var lastBPM; var isHRMOn = false; @@ -39,7 +52,7 @@ bpm = lastBPM; isCurrent = false; } - if (bpm===undefined) + if (bpm===undefined || (settings && bpm { + function getAlertText() { + const medicalinfo = require("medicalinfo").load(); + const alertText = ((Array.isArray(medicalinfo.medicalAlert)) && (medicalinfo.medicalAlert[0])) ? medicalinfo.medicalAlert[0] : ""; + return (g.wrapString(alertText, g.getWidth()).length === 1) ? alertText : "MEDICAL ALERT"; + } + + // Top right star of life logo + WIDGETS["widmedatr"] = { + area: "tr", + width: 24, + draw: function () { + g.reset(); + g.setColor("#f00"); + g.drawImage(atob("FhYBAAAAA/AAD8AAPwAc/OD/P8P8/x/z/n+/+P5/wP58A/nwP5/x/v/n/P+P8/w/z/Bz84APwAA/AAD8AAAAAA=="), this.x + 1, this.y + 1); + } + }; + + // Bottom medical alert text + WIDGETS["widmedabl"] = { + area: "bl", + width: Bangle.CLOCK ? Bangle.appRect.w : 0, + draw: function () { + // Only show the widget on clocks + if (!Bangle.CLOCK) return; + + g.reset(); + g.setBgColor(g.theme.dark ? "#fff" : "#f00"); + g.setColor(g.theme.dark ? "#f00" : "#fff"); + g.setFont("Vector", 16); + g.setFontAlign(0, 0); + g.clearRect(this.x, this.y, this.x + this.width - 1, this.y + 23); + + const alertText = getAlertText(); + g.drawString(alertText, this.width / 2, this.y + (23 / 2)); + } + }; + + Bangle.on("touch", (_, c) => { + const bl = WIDGETS.widmedabl; + const tr = WIDGETS.widmedatr; + if ((bl && c.x >= bl.x && c.x < bl.x + bl.width && c.y >= bl.y && c.y <= bl.y + 24) + || (tr && c.x >= tr.x && c.x < tr.x + tr.width && c.y >= tr.y && c.y <= tr.y + 24)) { + load("medicalinfo.app.js"); + } + }); +})(); diff --git a/apps/widmeda/widget.png b/apps/widmeda/widget.png new file mode 100644 index 000000000..249bf15bf Binary files /dev/null and b/apps/widmeda/widget.png differ diff --git a/apps/widmessages/ChangeLog b/apps/widmessages/ChangeLog new file mode 100644 index 000000000..598068920 --- /dev/null +++ b/apps/widmessages/ChangeLog @@ -0,0 +1,3 @@ +0.01: Moved messages widget into standalone widget app +0.02: Fix 'srcs' being defined in global scope + Remove library stub diff --git a/apps/widmessages/README.md b/apps/widmessages/README.md new file mode 100644 index 000000000..398cb4fa8 --- /dev/null +++ b/apps/widmessages/README.md @@ -0,0 +1,30 @@ +# Messages widget + +The default widget to show icons for new messages +It is installed automatically if you install `Android Integration` or `iOS Integration`. + +![screenshot](screenshot.gif) + +## Settings +You can change settings by going to the global `Settings` app, then `App Settings` +and `Messages`: + +* `Flash icon` Toggle flashing of the widget icons. + +* `Widget messages` Not used by this widget. + +## Requests + +Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=widmessages%widget + +## Creator + +Gordon Williams + +## Contributors + +[Jeroen Peters](https://github.com/jeroenpeters1986) + +## Attributions + +Icons used in this app are from https://icons8.com diff --git a/apps/widmessages/app.png b/apps/widmessages/app.png new file mode 100644 index 000000000..c9177692e Binary files /dev/null and b/apps/widmessages/app.png differ diff --git a/apps/widmessages/metadata.json b/apps/widmessages/metadata.json new file mode 100644 index 000000000..a8a23df19 --- /dev/null +++ b/apps/widmessages/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "widmessages", + "name": "Message Widget", + "version": "0.02", + "description": "Widget showing new messages", + "icon": "app.png", + "type": "widget", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "screenshots": [{"url": "screenshot.gif"}], + "dependencies" : { "messageicons":"module" }, + "provides_widgets" : ["message"], + "default" : true, + "readme": "README.md", + "storage": [ + {"name":"widmessages.wid.js","url":"widget.js"} + ] +} diff --git a/apps/widmessages/screenshot.gif b/apps/widmessages/screenshot.gif new file mode 100644 index 000000000..e5cc669bd Binary files /dev/null and b/apps/widmessages/screenshot.gif differ diff --git a/apps/widmessages/widget.js b/apps/widmessages/widget.js new file mode 100644 index 000000000..316957e29 --- /dev/null +++ b/apps/widmessages/widget.js @@ -0,0 +1,77 @@ +(() => { + if ((require("Storage").readJSON("messages.settings.json", true) || {}).maxMessages===0) return; + + function filterMessages(msgs) { + return msgs.filter(msg => msg.new && msg.id != "music") + .map(m => m.src) // we only need this for icon/color + .filter((msg, i, arr) => arr.findIndex(nmsg => msg.src == nmsg.src) == i); + } + + // NOTE when adding a custom "essages" widget: + // the name still needs to be "messages": the library calls WIDGETS["messages'].hide()/show() + // see e.g. widmsggrid + WIDGETS["messages"] = { + area: "tl", width: 0, srcs: [], draw: function(recall) { + // If we had a setTimeout queued from the last time we were called, remove it + if (WIDGETS["messages"].i) { + clearTimeout(WIDGETS["messages"].i); + delete WIDGETS["messages"].i; + } + Bangle.removeListener("touch", this.touch); + if (!this.width) return; + let settings = Object.assign({flash: true, maxMessages: 3}, require("Storage").readJSON("messages.settings.json", true) || {}); + if (recall!==true || settings.flash) { + const msgsShown = E.clip(this.srcs.length, 0, settings.maxMessages); + srcs = Object.keys(this.srcs); + g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+23); + for(let i = 0; isettings.maxMessages ? atob("EASBAGGG88/zz2GG") : require("messageicons").getImage(src), + this.x+12+i*24, this.y+12, {rotate: 0/*force centering*/}); + } + } + WIDGETS["messages"].i = setTimeout(() => WIDGETS["messages"].draw(true), 1000); + if (process.env.HWVERSION>1) Bangle.on("touch", this.touch); + }, onMsg: function(type, msg) { + if (this.hidden) return; + if (type==="music") return; + if (msg.id && !msg.new && msg.t!=="remove") return; + this.srcs = filterMessages(require("messages").getMessages(msg)); + const settings = Object.assign({maxMessages:3},require('Storage').readJSON("messages.settings.json", true) || {}); + this.width = 24 * E.clip(this.srcs.length, 0, settings.maxMessages); + if (type!=="init") Bangle.drawWidgets(); // "init" is not a real message type: see below + }, touch: function(b, c) { + var w = WIDGETS["messages"]; + if (!w || !w.width || c.xw.x+w.width || c.yw.y+24) return; + require("messages").openGUI(); + }, + // hide() and show() are required by the "message" library! + hide() { + this.hidden=true; + if (this.width) { + // hide widget + this.width = 0; + Bangle.drawWidgets(); + } + }, show() { + delete this.hidden + this.onMsg("show", {}); // reload messages+redraw + } + }; + + Bangle.on("message", WIDGETS["messages"].onMsg); + WIDGETS["messages"].onMsg("init", {}); // abuse type="init" to prevent Bangle.drawWidgets(); +})(); diff --git a/apps/widminbat/ChangeLog b/apps/widminbat/ChangeLog new file mode 100644 index 000000000..8ff6e2cb6 --- /dev/null +++ b/apps/widminbat/ChangeLog @@ -0,0 +1 @@ +0.01: Initial Version: Display at under 30% battery diff --git a/apps/widminbat/metadata.json b/apps/widminbat/metadata.json new file mode 100644 index 000000000..1c7c10b15 --- /dev/null +++ b/apps/widminbat/metadata.json @@ -0,0 +1,13 @@ +{ "id": "widminbat", + "name": "Minimal Battery", + "shortName":"MinBat", + "version":"0.01", + "description": "A minimal version of the battery widget that only appears if the battery is running low (below 30%)", + "icon": "widget.png", + "type": "widget", + "tags": "widget,battery,minimal", + "supports" : ["BANGLEJS2", "BANGLEJS"], + "storage": [ + {"name":"widminbat.wid.js","url":"widget.js"} + ] +} diff --git a/apps/widminbat/widget.js b/apps/widminbat/widget.js new file mode 100644 index 000000000..9c088bf06 --- /dev/null +++ b/apps/widminbat/widget.js @@ -0,0 +1,29 @@ +(()=>{ + function getWidth() { + return E.getBattery() <= 30 ? 40 : 0; + } + WIDGETS.minbat={area:"tr",width:getWidth(),draw:function() { + if(this.width < 40) return; + var s = 39; + var bat = E.getBattery(); + var x = this.x, y = this.y; + g.reset(); + g.clearRect(x,y,x+s,y+24); + g.setColor(g.theme.fg).fillRect(x,y+2,x+s-4,y+21).clearRect(x+2,y+4,x+s-6,y+19).fillRect(x+s-3,y+10,x+s,y+14); + var barWidth = bat*(s-12)/100; + var color = bat < 15 ? "#f00" : "#f80"; + g.setColor(color).fillRect(x+4,y+6,x+4+barWidth,y+17); + },update: function() { + var newWidth = getWidth(); + if(newWidth != this.width) { + this.width = newWidth; + Bangle.drawWidgets();//relayout + }else{ + this.draw(); + } + }}; + setInterval(()=>{ + var widget = WIDGETS.minbat; + if(widget) {widget.update();} + }, 10*60*1000); +})(); diff --git a/apps/widminbat/widget.png b/apps/widminbat/widget.png new file mode 100644 index 000000000..b04bc4ef9 Binary files /dev/null and b/apps/widminbat/widget.png differ diff --git a/apps/widminbt/ChangeLog b/apps/widminbt/ChangeLog new file mode 100644 index 000000000..28f11c1c7 --- /dev/null +++ b/apps/widminbt/ChangeLog @@ -0,0 +1 @@ +0.01: Initial Release diff --git a/apps/widminbt/metadata.json b/apps/widminbt/metadata.json new file mode 100644 index 000000000..a78f9e0a4 --- /dev/null +++ b/apps/widminbt/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "widminbt", + "name": "Minimal Bluetooth Widget", + "version": "0.01", + "description": "Appears whenever bluetooth is disconnected", + "icon": "widget.png", + "type": "widget", + "tags": "widget,bluetooth,minimal", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"widminbt.wid.js","url":"widget.js"} + ] +} diff --git a/apps/widminbt/widget.js b/apps/widminbt/widget.js new file mode 100644 index 000000000..87439f8c4 --- /dev/null +++ b/apps/widminbt/widget.js @@ -0,0 +1,15 @@ +(()=> { + WIDGETS.minbt={area:"tr",width:NRF.getSecurityStatus().connected?0:15,draw:function() { + if(this.width<15)return; + g.reset(); + g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f")); + g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),2+this.x,2+this.y); + g.setColor("#f00"); + g.drawImage(atob("CxSBAMA8DYG4YwxzBmD4DwHAGAeA8DcGYY4wzB2B4DA="), 2+this.x, 2+this.y); + },changed:function(){ + WIDGETS.minbt.width=NRF.getSecurityStatus().connected?0:15; + Bangle.drawWidgets(); + }}; + NRF.on('connect',WIDGETS.minbt.changed); + NRF.on('disconnect',WIDGETS.minbt.changed); +})(); diff --git a/apps/widminbt/widget.png b/apps/widminbt/widget.png new file mode 100644 index 000000000..661d1a64c Binary files /dev/null and b/apps/widminbt/widget.png differ diff --git a/apps/widmnth/ChangeLog b/apps/widmnth/ChangeLog new file mode 100644 index 000000000..370f41e8a --- /dev/null +++ b/apps/widmnth/ChangeLog @@ -0,0 +1 @@ +0.01: Simple new widget! diff --git a/apps/widmnth/README.md b/apps/widmnth/README.md new file mode 100644 index 000000000..ef912c739 --- /dev/null +++ b/apps/widmnth/README.md @@ -0,0 +1,22 @@ +# Widget Name + +The days left in month widget is simple and just prints the number of days left in the month in the top left corner. +The idea is to encourage people to keep track of time and keep goals they may have for the month. + +## Usage + +Hopefully you just have to Install it and it'll work. Customizing the location would just be changing tl to tr. + +## Features + +* Shows days left in month +* Only updates at midnight. + + +## Requests + +Complaints,compliments,problems,suggestions,annoyances,bugs, and all other feedback can be filed at [this repo](https://github.com/N-Onorato/BangleApps) + +## Creator + +Nick diff --git a/apps/widmnth/metadata.json b/apps/widmnth/metadata.json new file mode 100644 index 000000000..25f3a8126 --- /dev/null +++ b/apps/widmnth/metadata.json @@ -0,0 +1,14 @@ +{ "id": "widmnth", + "name": "Days left in month widget", + "shortName":"Month Countdown", + "version":"0.01", + "description": "A simple widget that displays the number of days left in the month.", + "icon": "widget.png", + "type": "widget", + "tags": "widget,date,time,countdown,month", + "supports" : ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"widmnth.wid.js","url":"widget.js"} + ] +} diff --git a/apps/widmnth/widget.js b/apps/widmnth/widget.js new file mode 100644 index 000000000..c4eca155a --- /dev/null +++ b/apps/widmnth/widget.js @@ -0,0 +1,42 @@ + +(() => { + var days_left; + var clearCode; + + function getDaysLeft(day) { + let year = day.getMonth() == 11 ? day.getFullYear() + 1 : day.getFullYear(); // rollover if december. + next_month = new Date(year, (day.getMonth() + 1) % 12, 1, 0, 0, 0); + let days_left = Math.floor((next_month - day) / 86400000); // ms left in month divided by ms in a day + return days_left; + } + + function getTimeTilMidnight(now) { + let midnight = new Date(now.getTime()); + midnight.setHours(23); + midnight.setMinutes(59); + midnight.setSeconds(59); + midnight.setMilliseconds(999); + return (midnight - now) + 1; + } + + function update() { + let now = new Date(); + days_left = getDaysLeft(now); + let ms_til_midnight = getTimeTilMidnight(now); + clearCode = setTimeout(update, ms_til_midnight); + } + + function draw() { + g.reset(); + g.setFont("4x6", 3); + if(!clearCode) update(); // On first run calculate days left and setup interval to update state. + g.drawString(days_left < 10 ? "0" + days_left : days_left.toString(), this.x + 2, this.y + 4); + } + + // add your widget + WIDGETS.widmonthcountdown={ + area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right) + width: 24, + draw:draw + }; +})(); \ No newline at end of file diff --git a/apps/widmnth/widget.png b/apps/widmnth/widget.png new file mode 100644 index 000000000..4a042ec05 Binary files /dev/null and b/apps/widmnth/widget.png differ diff --git a/apps/widmp/ChangeLog b/apps/widmp/ChangeLog index 02658296a..a51ac080a 100644 --- a/apps/widmp/ChangeLog +++ b/apps/widmp/ChangeLog @@ -2,3 +2,7 @@ 0.02: Fix position and overdraw bugs 0.03: Better memory usage, theme support 0.04: Replace the 8 phases by a more exact drawing, see forum.espruino.com/conversations/371985 +0.05: Fixed the algorithm for calculating the moon's phase +0.06: Darkmode, custom colours, and fix a bug with acting on mylocation changes +0.07: Use default Bangle formatter for booleans +0.08: Better formula for the moon's phase diff --git a/apps/widmp/metadata.json b/apps/widmp/metadata.json index 94f05a426..654b5a383 100644 --- a/apps/widmp/metadata.json +++ b/apps/widmp/metadata.json @@ -1,13 +1,15 @@ { "id": "widmp", - "name": "Moon Phase Widget", - "version": "0.04", - "description": "Display the current moon phase in blueish for both hemispheres. In the southern hemisphere the 'My Location' app is needed.", + "name": "Moon Phase", + "version": "0.08", + "description": "Display the current moon phase in blueish (in light mode) or white (in dark mode) for both hemispheres. In the southern hemisphere the 'My Location' app is needed.", "icon": "widget.png", "type": "widget", "tags": "widget,tools", "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ - {"name":"widmp.wid.js","url":"widget.js"} - ] + {"name":"widmp.wid.js","url":"widget.js"}, + {"name":"widmp.settings.js","url":"settings.js"} + ], + "data": [{"name":"widmp.json"}] } diff --git a/apps/widmp/settings.js b/apps/widmp/settings.js new file mode 100644 index 000000000..a389f7918 --- /dev/null +++ b/apps/widmp/settings.js @@ -0,0 +1,72 @@ +(function(back) { + + var settings = Object.assign({ + default_colour: true, + red: 0, + green: 0, + blue: 0, + }, require('Storage').readJSON("widmp.json", true) || {}); + + function writeSettings() { + require('Storage').writeJSON("widmp.json", settings); + if (WIDGETS["widmp"]) WIDGETS["widmp"].draw(); + } + + function writeSettingsCustom() { + settings.default_colour = false; + mainmenu["Default"].value = false; + writeSettings(); + } + + var mainmenu = { + "": { + "title": "Moon colour" + }, + "< Back": () => back(), + "Default": { + value: (settings.default_colour !== undefined ? settings.default_colour : true), + onchange: v => { + settings.default_colour = v; + writeSettings(); + } + }, + "Custom...": () => E.showMenu(custommenu) + }; + + var custommenu = { + "": { + "title": "Custom colour..." + }, + "< Back": () => E.showMenu(mainmenu), + "red": { + value: 0|settings.red, + min: 0, + max: 4, + onchange: v => { + settings.red = v; + writeSettingsCustom(); + } + }, + "green": { + value: 0|settings.green, + min: 0, + max: 4, + onchange: v => { + settings.green = v; + writeSettingsCustom(); + } + }, + "blue": { + value: 0|settings.blue, + min: 0, + max: 4, + onchange: v => { + settings.blue = v; + writeSettingsCustom(); + } + } + }; + + E.showMenu(mainmenu); + +}); diff --git a/apps/widmp/widget.js b/apps/widmp/widget.js index 6da572aab..e5aa7fef2 100644 --- a/apps/widmp/widget.js +++ b/apps/widmp/widget.js @@ -1,25 +1,26 @@ -WIDGETS["widmoon"] = { area: "tr", width: 24, draw: function() { - const CenterX = this.x + 12, CenterY = this.y + 12, Radius = 11; - var southernHemisphere = false; // when in southern hemisphere, use the "My Location" App +(() => { - const simulate = false; // simulate one month in one minute - const updateR = 1000; // update every x ms in simulation + var lastCalculated = 0; // When we last calculated the phase + var phase = 0; // The last phase we calculated + var southernHemisphere = false; // when in southern hemisphere -- use the "My Location" App - function moonPhase() { - const d = Date(); - var month = d.getMonth(), year = d.getFullYear(), day = d.getDate(); - if (simulate) day = d.getSeconds() / 2 +1; - if (month < 3) {year--; month += 12;} - mproz = ((365.25 * year + 30.6 * ++month + day - 694039.09) / 29.5305882); - mproz = mproz - (mproz | 0); // strip integral digits, result is between 0 and <1 - if (simulate) console.log(mproz + " " + day); - return (mproz); + // https://github.com/deirdreobyrne/LunarPhase + function moonPhase(sec) { + d = (4.847408287988257 + sec/406074.7465115577) % (2.0*Math.PI); + m = (6.245333801867877 + sec/5022682.784840698) % (2.0*Math.PI); + l = (4.456038755040014 + sec/378902.2499653011) % (2.0*Math.PI); + t = d+1.089809730923715e-01 * Math.sin(l)-3.614132757006379e-02 * Math.sin(m)+2.228248661252023e-02 * Math.sin(d+d-l)+1.353592753655652e-02 * Math.sin(d+d)+4.238560208195022e-03 * Math.sin(l+l)+1.961408105275610e-03 * Math.sin(d); + k = (1.0 - Math.cos(t))/2.0; + if ((t >= Math.PI) && (t < 2.0*Math.PI)) { + k = -k; + } + return (k); // Goes 0 -> 1 for waxing, and from -1 -> 0 for waning } function loadLocation() { // "mylocation.json" is created by the "My Location" app location = require("Storage").readJSON("mylocation.json",1)||{"lat":50.1236,"lon":8.6553,"location":"Frankfurt"}; - if (location.lat < 0) southernHemisphere = true; + southernHemisphere = (location.lat < 0); } // code source: github.com/rozek/banglejs-2-activities/blob/main/README.md#drawmoonphase @@ -38,26 +39,60 @@ WIDGETS["widmoon"] = { area: "tr", width: 24, draw: function() { g.drawLine(CenterX-leftFactor*y,CenterY+x, CenterX+rightFactor*y,CenterY+x); } } + + function setMoonColour(g) { + var settings = Object.assign({ + default_colour: true, + red: 0, + green: 0, + blue: 0, + }, require('Storage').readJSON("widmp.json", true) || {}); + if (settings.default_colour) { + if (g.theme.dark) { + g.setColor(0xffff); // white + } else { + // rrrrrggggggbbbbb + // 0000010000011111 + g.setColor(0x41f); // blue-ish + } + } else { + g.setColor(settings.red/4, settings.green/4, settings.blue/4); + } + } - function updateWidget() { + + function draw() { + const CenterX = this.x + 12, CenterY = this.y + 12, Radius = 11; + + loadLocation(); g.reset().setColor(g.theme.bg); g.fillRect(CenterX - Radius, CenterY - Radius, CenterX + Radius, CenterY + Radius); - g.setColor(0x41f); - mproz = moonPhase(); // mproz = 0..<1 + millis = (new Date()).getTime(); + if ((millis - lastCalculated) >= 7000000) { // if it's more than 7,000 sec since last calculation, re-calculate! + phase = moonPhase(millis/1000); + lastCalculated = millis; + } - leftFactor = mproz * 4 - 1; - rightFactor = (1 - mproz) * 4 - 1; - if (mproz >= 0.5) leftFactor = 1; else rightFactor = 1; + if (phase < 0) { // waning - phase goes from -1 to 0 + leftFactor = 1; + rightFactor = -1 - 2*phase; + } else { // waxing - phase goes from 0 to 1 + rightFactor = 1; + leftFactor = -1 + 2*phase; + } if (true == southernHemisphere) { var tmp=leftFactor; leftFactor=rightFactor; rightFactor=tmp; } + setMoonColour(g); drawMoonPhase(CenterX,CenterY, Radius, leftFactor,rightFactor); - - if (simulate) setTimeout(updateWidget, updateR); } - loadLocation(); - updateWidget(); -} }; + WIDGETS["widmp"] = { + area: "tr", + width: 24, + draw: draw + }; + +})(); diff --git a/apps/widmsggrid/ChangeLog b/apps/widmsggrid/ChangeLog new file mode 100644 index 000000000..9be40817a --- /dev/null +++ b/apps/widmsggrid/ChangeLog @@ -0,0 +1,4 @@ +0.01: New widget! +0.02: Adjust to message icons moving to messageicons lib +0.03: Use new message library +0.04: Remove library stub \ No newline at end of file diff --git a/apps/widmsggrid/README.md b/apps/widmsggrid/README.md new file mode 100644 index 000000000..36aad20e2 --- /dev/null +++ b/apps/widmsggrid/README.md @@ -0,0 +1,23 @@ +# Messages Grid Widget + +Widget that displays multiple notification icons in a grid. +The widget has a fixed size: if there are multiple notifications it uses smaller +icons. +It shows a single icon per application, so if you have two SMS messages, the +grid only has one SMS icon. +If there are multiple messages waiting, the total number is shown in the +bottom-right corner. + +Example: one SMS, one Signal, and two WhatsApp messages: +![screenshot](screenshot.png) + +## Installation +There can only be one messages widget, so you should uninstall the default "Message Widget". + +## Settings +You can change settings by going to the global `Settings` app, then `App Settings` +and `Messages`: + +* `Flash icon` Toggle flashing of the widget icons. + +* `Widget messages` Not used by this widget. \ No newline at end of file diff --git a/apps/widmsggrid/metadata.json b/apps/widmsggrid/metadata.json new file mode 100644 index 000000000..17d3573ad --- /dev/null +++ b/apps/widmsggrid/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "widmsggrid", + "name": "Messages Grid Widget", + "version": "0.04", + "description": "Widget that displays notification icons in a grid", + "icon": "widget.png", + "type": "widget", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "dependencies" : { "messages":"module" }, + "provides_widgets" : ["message"], + "readme": "README.md", + "storage": [ + {"name":"widmsggrid.wid.js","url":"widget.js"} + ], + "screenshots": [{"url":"screenshot.png"}] +} diff --git a/apps/widmsggrid/screenshot.png b/apps/widmsggrid/screenshot.png new file mode 100644 index 000000000..b1cdb2a5a Binary files /dev/null and b/apps/widmsggrid/screenshot.png differ diff --git a/apps/widmsggrid/widget.js b/apps/widmsggrid/widget.js new file mode 100644 index 000000000..6a5b175ac --- /dev/null +++ b/apps/widmsggrid/widget.js @@ -0,0 +1,102 @@ +(function () { + if (global.MESSAGES) return; // don't load widget while in the app + let settings = require('Storage').readJSON("messages.settings.json", true) || {}; + const s = { + flash: (settings.flash === undefined) ? true : !!settings.flash, + showRead: !!settings.showRead, + }; + delete settings; + // widget name needs to be "messages": the library calls WIDGETS["messages'].hide()/show() + WIDGETS["messages"] = { + area: "tl", width: 0, + flash: s.flash, + showRead: s.showRead, + init: function() { + // runs on first draw + delete w.init; // don't run again + Bangle.on("touch", w.touch); + Bangle.on("message", w.listener); + w.listener(); // update status now + }, + draw: function () { + if (w.init) w.init(); + // If we had a setTimeout queued from the last time we were called, remove it + if (w.t) { + clearTimeout(w.t); + delete w.t; + } + if (!w.width || this.hidden) return; + const b = w.flash && w.status === "new" && ((Date.now() / 1000) & 1), // Blink(= inverse colors) on this second? + // show multiple icons in a grid, by scaling them down + cols = Math.ceil(Math.sqrt(w.srcs.length - 0.1)); // cols===rows, -0.1 to work around rounding error + g.reset().clearRect(w.x, w.y, w.x + w.width - 1, w.y + 24) + .setClipRect(w.x, w.y, w.x + w.width - 1, w.y + 24); // guard against oversized icons + let r = 0, c = 0; // row, column + const offset = pos => Math.floor(pos / cols * 24); // pixel offset for position in row/column + let icons = require("messageicons"); + let defaultCol = icons.getColor("alert", {settings:settings}); + w.srcs.forEach(src => { + const appColor = icons.getColor(src, {settings:settings,default:defaultCol}); + let colors = [g.theme.bg, appColor]; + if (b) { + if (colors[1] == g.theme.fg) colors = colors.reverse(); + else colors[1] = g.theme.fg; + } + g.setColor(colors[1]).setBgColor(colors[0]); + g.drawImage(icons.getImage(src), w.x+offset(c), w.y+offset(r), { scale: 1 / cols }); + if (++c >= cols) { + c = 0; + r++; + } + }); + if (w.total > 1) { + // show total number of messages in bottom-right corner + g.reset(); + if (w.total < 10) g.fillCircle(w.x + w.width - 5, w.y + 20, 4); // single digits get a round background, double digits fill their rectangle + g.setColor(g.theme.bg).setBgColor(g.theme.fg) + .setFont('6x8').setFontAlign(1, 1) + .drawString(w.total, w.x + w.width - 1, w.y + 24, w.total > 9); + } + if (w.flash && w.status === "new") w.t = setTimeout(w.draw, 1000); // schedule redraw while blinking + }, + // show() and hide() are required by the "message" library! + show: function (m) { + delete w.hidden; + w.width = 24; + w.srcs = require("messages").getMessages(m) + .filter(m => !['call', 'map', 'music'].includes(m.id)) + .filter(m => m.new || w.showRead) + .map(m => m.src); + w.total = w.srcs.length; + w.srcs = w.srcs.filter((src, i, uniq) => uniq.indexOf(src) === i); // keep unique entries only + Bangle.drawWidgets(); + Bangle.setLCDPower(1); // turns screen on + }, hide: function () { + w.hidden = true; + w.width = 0; + w.srcs = []; + w.total = 0; + Bangle.drawWidgets(); + }, touch: function (b, c) { + if (!w || !w.width) return; // widget not shown + if (process.env.HWVERSION < 2) { + // Bangle.js 1: open app when on clock we touch the side with widget + if (!Bangle.CLOCK) return; + const m = Bangle.appRect / 2; + if ((w.x < m && b !== 1) || (w.x > m && b !== 2)) return; + } + // Bangle.js 2: open app when touching the widget + else if (c.x < w.x || c.x > w.x + w.width || c.y < w.y || c.y > w.y + 24) return; + require("messages").openGUI(); + }, listener: function (t,m) { + if (this.hidden) return; + w.status = require("messages").status(m); + if (w.status === "new" || (w.status === "old" && w.showRead)) w.show(m); + else w.hide(); + delete w.hidden; // always set by w.hide(), but we checked it wasn't there before + } + }; + delete s; + const w = WIDGETS["messages"]; + Bangle.on("message", w.listener); +})(); diff --git a/apps/widmsggrid/widget.png b/apps/widmsggrid/widget.png new file mode 100644 index 000000000..ce6e7b7ac Binary files /dev/null and b/apps/widmsggrid/widget.png differ diff --git a/apps/widram/ChangeLog b/apps/widram/ChangeLog index e7b406081..7b00c8a48 100644 --- a/apps/widram/ChangeLog +++ b/apps/widram/ChangeLog @@ -1,2 +1,3 @@ 0.01: New Widget! 0.02: Now also visible on Bangle.js 2 +0.03: Remove global declaration of BANGLEJS2 var (fix #2123) diff --git a/apps/widram/metadata.json b/apps/widram/metadata.json index 19ae6d311..ebf23742b 100644 --- a/apps/widram/metadata.json +++ b/apps/widram/metadata.json @@ -2,7 +2,7 @@ "id": "widram", "name": "RAM Widget", "shortName": "RAM Widget", - "version": "0.02", + "version": "0.03", "description": "Display your Bangle's RAM usage percentage in a widget", "icon": "widget.png", "type": "widget", diff --git a/apps/widram/widget.js b/apps/widram/widget.js index 210c85357..07b7c0a5f 100644 --- a/apps/widram/widget.js +++ b/apps/widram/widget.js @@ -1,6 +1,6 @@ (() => { function draw() { - BANGLEJS2 = process.env.HWVERSION==2; + const BANGLEJS2 = process.env.HWVERSION==2; g.reset(); var m = process.memory(); var percent = Math.round(m.usage*100/m.total); diff --git a/apps/widscrlock/ChangeLog b/apps/widscrlock/ChangeLog new file mode 100644 index 000000000..84e926362 --- /dev/null +++ b/apps/widscrlock/ChangeLog @@ -0,0 +1,3 @@ +0.01: 25/Jun/2022 Added Screenlock widget to depository. +0.02: 26/Jun/2022 Tidied up graphics. Fixed versioning style... +0.03: 28/Jun/2022 Tidied up code. diff --git a/apps/widscrlock/README.md b/apps/widscrlock/README.md new file mode 100644 index 000000000..043bfbf40 --- /dev/null +++ b/apps/widscrlock/README.md @@ -0,0 +1,3 @@ +A Bangle 2 widget to lock the screen. + +Tap on the widget's lock icon to lock and darken the touch-screen as happens with the LCD timeout. diff --git a/apps/widscrlock/metadata.json b/apps/widscrlock/metadata.json new file mode 100644 index 000000000..5110d76c1 --- /dev/null +++ b/apps/widscrlock/metadata.json @@ -0,0 +1,14 @@ +{ "id": "widscrlock", + "name": "Screenlock Widget", + "shortName":"Screenlock", + "version":"0.03", + "description": "Lock a Bangle 2 screen by tapping a widget.", + "icon": "widget.png", + "type": "widget", + "tags": "widget, screenlock", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"widscrlock.wid.js","url":"widget.js"} + ] +} diff --git a/apps/widscrlock/widget.js b/apps/widscrlock/widget.js new file mode 100644 index 000000000..cbb71e4cc --- /dev/null +++ b/apps/widscrlock/widget.js @@ -0,0 +1,32 @@ +// Screenlock Widget + +(() => { + function draw() { + // Draw icon. + g.reset(); + g.drawImage(atob("GBiDAkkkkiSSSUkkkkkkiSSSSSUkkkkiSSf/ySSUkkkSSf///ySSkkiST/ySf+SQUkiST+SST+SAUkSSfySSSfwACkSSfySSSewACiSSfySSSWwAASSSfySSSGwAASSSfySSQGwAASST///+222AASST///2222AASST//+A222AASST//wAG22AASST/+AAA22AASST/2wAG22AAUST+22A222ACkiT222A222ACkiSG22A22wAUkkQA22222ACkkkiAG222wAUkkkkSAAAAASkkkkkkSQACSkkkg=="),scrlock.x,scrlock.y); + } + + // add widget. + WIDGETS.widscrlock={ + area:"tr", + width: 24, + draw:draw // Draw widget. + }; + + var scrlock = WIDGETS.widscrlock; + + function restoreTimeout(){ + // Restore LCDTimeout settings. + Bangle.setLCDTimeout(options.lockTimeout / 1000); + } + + var options = []; + Bangle.on('touch', function(button, xy) { + if(xy.x>=scrlock.x && xy.x<=scrlock.x+23 && xy.y>=scrlock.y && xy.y<=scrlock.y+23) { + options = Bangle.getOptions(); // Store current Timeout settings. + Bangle.setLCDTimeout(0.1); // Lock screen. + setTimeout(restoreTimeout, 1000); + } + }); +})(); diff --git a/apps/widscrlock/widget.png b/apps/widscrlock/widget.png new file mode 100644 index 000000000..76c490785 Binary files /dev/null and b/apps/widscrlock/widget.png differ diff --git a/apps/widshipbell/metadata.json b/apps/widshipbell/metadata.json new file mode 100644 index 000000000..c130b04ee --- /dev/null +++ b/apps/widshipbell/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "widshipbell", + "name": "Ship's bell Widget", + "shortName": "Ship's bell", + "version": "0.01", + "description": "A widget that buzzes according to a nautical bell, one strike at 04:30, two strikes at 05:00, up to eight strikes at 08:00 and so on.", + "icon": "widget.png", + "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"widshipbell.wid.js","url":"widget.js"}, + {"name":"widshipbell.settings.js","url":"settings.js"} + ], + "data": [{"name":"widshipbell.json"}] +} diff --git a/apps/widshipbell/settings.js b/apps/widshipbell/settings.js new file mode 100644 index 000000000..bb47e9b20 --- /dev/null +++ b/apps/widshipbell/settings.js @@ -0,0 +1,27 @@ +(function(back) { + var FILE = "widshipbell.json"; + // Load settings + var settings = Object.assign({ + strength: 1, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + // Show the menu + E.showMenu({ + "" : { "title" : "Ship's bell" }, + "< Back" : () => back(), + 'Strength': { + value: settings.strength, + min: 0, max: 2, + format: v => ["Off", "Weak", "Strong"][v], + onchange: v => { + settings.strength = v; + writeSettings(); + } + }, + }); +}) + diff --git a/apps/widshipbell/widget.js b/apps/widshipbell/widget.js new file mode 100644 index 000000000..e37edb6fd --- /dev/null +++ b/apps/widshipbell/widget.js @@ -0,0 +1,52 @@ +(() => { + const strength = Object.assign({ + strength: 1, + }, require('Storage').readJSON("widshipbell.json", true) || {}).strength; + + function replaceAll(target, search, replacement) { + return target.split(search).join(replacement); + } + + function check() { + const now = new Date(); + const currentMinute = now.getMinutes(); + const currentSecond = now.getSeconds(); + const etaMinute = 30-(currentMinute % 30); + + if (etaMinute === 30 && currentSecond === 0) { + const strikeHour = now.getHours() % 4; + // buzz now + let pattern=''; + if (strikeHour === 0 && currentMinute == 0) { + pattern = '.. .. .. ..'; + } else if (strikeHour === 0 && currentMinute === 30) { + pattern = '.'; + } else if (strikeHour === 1 && currentMinute === 0) { + pattern = '..'; + } else if (strikeHour === 1 && currentMinute === 30) { + pattern = '.. .'; + } else if (strikeHour === 2 && currentMinute === 0) { + pattern = '.. ..'; + } else if (strikeHour === 2 && currentMinute === 30) { + pattern = '.. .. .'; + } else if (strikeHour === 3 && currentMinute === 0) { + pattern = '.. .. ..'; + } else if (strikeHour === 3 && currentMinute === 30) { + pattern = '.. .. .. .'; + } + pattern = replaceAll(pattern, ' ', ' '); // 4x pause + pattern = replaceAll(pattern, '.', '. '); // pause between bells + if (strength === 2) { // strong selected + pattern = replaceAll(pattern, '.', ':'); + } + require("buzz").pattern(pattern); + } + + const etaSecond = etaMinute*60-currentSecond; + setTimeout(check, etaSecond*1000); + } + + if (strength !== 0) { + check(); + } +})(); diff --git a/apps/widshipbell/widget.png b/apps/widshipbell/widget.png new file mode 100644 index 000000000..891679f7d Binary files /dev/null and b/apps/widshipbell/widget.png differ diff --git a/apps/widsleepstatus/ChangeLog b/apps/widsleepstatus/ChangeLog new file mode 100644 index 000000000..bb17be181 --- /dev/null +++ b/apps/widsleepstatus/ChangeLog @@ -0,0 +1,4 @@ +0.01: First version +0.02: Load settings only once + Better icons + Read sleep status on every draw diff --git a/apps/widsleepstatus/metadata.json b/apps/widsleepstatus/metadata.json new file mode 100644 index 000000000..bd0e5d537 --- /dev/null +++ b/apps/widsleepstatus/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "widsleepstatus", + "name": "Sleep Status Widget", + "version": "0.02", + "description": "Shows current status of sleep from sleeplog app.", + "icon": "widget.png", + "type": "widget", + "tags": "widget,sleep", + "supports": ["BANGLEJS","BANGLEJS2"], + "dependencies" : { "sleeplog":"app" }, + "storage": [ + {"name":"widsleepstatus.wid.js","url":"widget.js"}, + {"name":"widsleepstatus.settings.js","url":"settings.js"} + ], + "data": [{"name":"widsleepstatus.json"}] +} diff --git a/apps/widsleepstatus/settings.js b/apps/widsleepstatus/settings.js new file mode 100644 index 000000000..da402e08e --- /dev/null +++ b/apps/widsleepstatus/settings.js @@ -0,0 +1,32 @@ +/** + * @param {function} back Use back() to return to settings menu + */ +(function(back) { + const SETTINGS_FILE = "widsleepstatus.json"; + const storage = require("Storage"); + + let s = { + hidewhenawake: true + }; + const saved = storage.readJSON(SETTINGS_FILE, 1) || {}; + for(const key in saved) { + s[key] = saved[key]; + } + + function save(key) { + return function(value) { + s[key] = value; + storage.write(SETTINGS_FILE, s); + }; + } + + const menu = { + "": {"title": "Sleep Status Widget"}, + "< Back": back, + "Hide when awake": { + value: s.hidewhenawake, + onchange: save("hidewhenawake"), + }, + }; + E.showMenu(menu); +}); diff --git a/apps/widsleepstatus/widget.js b/apps/widsleepstatus/widget.js new file mode 100644 index 000000000..82a058993 --- /dev/null +++ b/apps/widsleepstatus/widget.js @@ -0,0 +1,49 @@ +(function() { + if (!sleeplog) return; + const SETTINGS_FILE = 'widsleepstatus.json'; + let settings; + + function loadSettings() { + settings = require('Storage').readJSON(SETTINGS_FILE, 1) || {}; + const DEFAULTS = { + 'hidewhenawake': true + }; + Object.keys(DEFAULTS).forEach(k => { + if (settings[k] === undefined) settings[k] = DEFAULTS[k]; + }); + } + loadSettings(); + + WIDGETS.sleepstatus = { + area: "tr", + width: 0, + draw: function(w) { + let status = sleeplog.status || 0; + if (w.width != (status >= 2 ? 24 : 0)){ + w.width = status >= 2 ? 24 : 0; + return Bangle.drawWidgets(); + } + g.reset(); + switch (status) { + case 0: + case 1: + break; + case 2: // awake + if (settings && !settings["hidewhenawake"]) g.drawImage(atob("GBiBAAAAAAAAAAAMAAA+AAAjAAEjMAGyYAGeYAzAwB5/gB4/AB4jAB4jAB4jAB4jAB//+Bv/+Bg2GB+2+B+2eB42eAAAAAAAAAAAAA=="), w.x, w.y); + break; + case 3: // light sleep + g.drawImage(atob("GBiBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAGAAAGAAAGAAAGcf/Ge//GWwBGewBmcwBn///mAABmAABmAABgAAAAAAAAAAAA=="), w.x, w.y); + break; + case 4: // deep sleep + g.drawImage(atob("GBiBAAAAAAAAAAAB4APw4APxwADh8AHAAAOAAGPwAGAAAGAAAGAAAGcf/Ge//GWwBGewBmcwBn///mAABmAABmAABgAAAAAAAAAAAA=="), w.x, w.y); + break; + } + } + }; + + setInterval(()=>{ + WIDGETS.sleepstatus.draw(WIDGETS.sleepstatus); + }, 60000); + + Bangle.drawWidgets(); +})() diff --git a/apps/widsleepstatus/widget.png b/apps/widsleepstatus/widget.png new file mode 100644 index 000000000..bb7f11f67 Binary files /dev/null and b/apps/widsleepstatus/widget.png differ diff --git a/apps/widslimbat/metadata.json b/apps/widslimbat/metadata.json new file mode 100644 index 000000000..a83046e90 --- /dev/null +++ b/apps/widslimbat/metadata.json @@ -0,0 +1,13 @@ +{ "id": "widslimbat", + "name": "Slim battery widget with cells", + "shortName":"Slim battery with cells", + "version":"0.01", + "description": "A small (13px wide) battery widget with cells", + "icon": "widget.png", + "type": "widget", + "tags": "widget", + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"widslimbat.wid.js","url":"widget.js"} + ] +} diff --git a/apps/widslimbat/widget.js b/apps/widslimbat/widget.js new file mode 100644 index 000000000..4a8bb3b5d --- /dev/null +++ b/apps/widslimbat/widget.js @@ -0,0 +1,55 @@ +(() => { + const intervalLow = 60000; // update time when not charging + const intervalHigh = 2000; // update time when charging + const outline = atob("CRSBAD4AP/AYDAYDAYDAYDAYDAYDAYDAYD/w"); + + let COLORS = { + 'black': g.theme.dark ? "#fff" : "#000", + 'charging': "#0f0", + 'low': "#f00", + }; + + function draw() { + var i; + var oCol = COLORS.low; + var cCol = COLORS.low; + var nCells = 0; + + const bat = E.getBattery(); + if (bat>5) { + oCol = COLORS.black; + nCells = 1 + Math.floor((bat-6)/19); + } + if (nCells>1) + cCol = COLORS.black; + if (Bangle.isCharging()) + oCol = COLORS.charging; + g.reset(); + g.setColor(oCol).drawImage(outline,this.x+2,this.y+2); + for (i=0;iWIDGETS["widslimbat"].draw(),intervalLow); + + WIDGETS["widslimbat"]={ + area:"tr", + width:13, + draw:draw + }; +})(); diff --git a/apps/widslimbat/widget.png b/apps/widslimbat/widget.png new file mode 100644 index 000000000..a9c7d416d Binary files /dev/null and b/apps/widslimbat/widget.png differ diff --git a/apps/widstep/ChangeLog b/apps/widstep/ChangeLog new file mode 100644 index 000000000..55cda0f21 --- /dev/null +++ b/apps/widstep/ChangeLog @@ -0,0 +1 @@ +0.01: New widget diff --git a/apps/widstep/README.md b/apps/widstep/README.md new file mode 100644 index 000000000..c41b025cd --- /dev/null +++ b/apps/widstep/README.md @@ -0,0 +1,9 @@ +# Step counter widget +This is my step counter. There are many like it, but this one is mine. + +A pedometer widget designed to be as narrow as possible, but still easy to read, by sacrificing accuracy and only showing to the nearest 100 steps (0.1k). +Shows a subtle fill colour in the background for progress to the goal. The goal is picked up from the health tracker settings. + + +![](widstep-light.png) +![](widstep-dark.png) diff --git a/apps/widstep/icons8-winter-boots-48.png b/apps/widstep/icons8-winter-boots-48.png new file mode 100644 index 000000000..7dceceef0 Binary files /dev/null and b/apps/widstep/icons8-winter-boots-48.png differ diff --git a/apps/widstep/metadata.json b/apps/widstep/metadata.json new file mode 100644 index 000000000..ea108e0f1 --- /dev/null +++ b/apps/widstep/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "widstep", + "name": "Step counter widget", + "version": "0.01", + "description": "Step counter widget, narrow but clearly readable", + "readme": "README.md", + "icon": "icons8-winter-boots-48.png", + "screenshots": [{"url":"widstep-light.png"},{"url":"widstep-dark.png"}], + "type": "widget", + "tags": "widget,health", + "supports": ["BANGLEJS","BANGLEJS2"], + "dependencies" : {"health":"app"}, + "allow_emulator":false, + "storage": [ + {"name":"widstep.wid.js","url":"widstep.wid.js"} + ] + } diff --git a/apps/widstep/widstep-dark.png b/apps/widstep/widstep-dark.png new file mode 100644 index 000000000..c8e1a8065 Binary files /dev/null and b/apps/widstep/widstep-dark.png differ diff --git a/apps/widstep/widstep-light.png b/apps/widstep/widstep-light.png new file mode 100644 index 000000000..9cce1e7c2 Binary files /dev/null and b/apps/widstep/widstep-light.png differ diff --git a/apps/widstep/widstep.wid.js b/apps/widstep/widstep.wid.js new file mode 100644 index 000000000..6ad971af7 --- /dev/null +++ b/apps/widstep/widstep.wid.js @@ -0,0 +1,23 @@ +let wsSettingsGoal = (require('Storage').readJSON("health.json", 1) || {}).stepGoal || 10000; + +Bangle.on('step', function(s) { WIDGETS["widstep"].draw(); }); +Bangle.on('lcdPower', function(on) { + if (on) WIDGETS["widstep"].draw(); +}); +WIDGETS["widstep"]={area:"tl", sortorder:-1, width:28, + draw:function() { + if (!Bangle.isLCDOn()) return; // dont redraw if LCD is off + var steps = Bangle.getHealthStatus("day").steps; + g.reset(); + g.setColor(g.theme.bg); + g.fillRect(this.x, this.y, this.x + this.width, this.y + 23); + g.setColor(g.theme.dark ? '#00f' : '#0ff'); + var progress = this.width * Math.min(steps/wsSettingsGoal, 1); + g.fillRect(this.x+1, this.y+1, this.x + progress -1, this.y + 23); + g.setColor(g.theme.fg); + g.setFontAlign(0, -1); + var steps_k = (steps/1000).toFixed(1) + 'k'; + g.setFont('6x15').drawString(steps_k, this.x+this.width/2, this.y + 10); + g.setFont('4x6').drawString('steps', this.x+this.width/2, this.y + 2); + } +}; diff --git a/apps/widviztime/changelog b/apps/widviztime/ChangeLog similarity index 100% rename from apps/widviztime/changelog rename to apps/widviztime/ChangeLog diff --git a/apps/wpmoto/ChangeLog b/apps/wpmoto/ChangeLog new file mode 100644 index 000000000..63f4bf24c --- /dev/null +++ b/apps/wpmoto/ChangeLog @@ -0,0 +1,4 @@ +... +0.02: First update with ChangeLog Added +0.03: Move waypoints.json (and editor) to 'waypoints' app +0.04: Added adjustment for Bangle.js magnetometer heading fix diff --git a/apps/wpmoto/app.js b/apps/wpmoto/app.js index 7deacb6ca..f08cb8279 100644 --- a/apps/wpmoto/app.js +++ b/apps/wpmoto/app.js @@ -1,6 +1,6 @@ var loc = require("locale"); -var waypoints = require("Storage").readJSON("waypoints.json") || []; +var waypoints = require("waypoints").load(); var wp = waypoints[0]; if (wp == undefined) wp = {name:"NONE"}; var wp_bearing = 0; @@ -134,7 +134,7 @@ function read_heading() { Bangle.setCompassPower(1); var d = 0; var m = Bangle.getCompass(); - if (!isNaN(m.heading)) d = -m.heading; + if (!isNaN(m.heading)) d = m.heading; heading = d; } @@ -196,7 +196,7 @@ function addCurrentWaypoint() { } function saveWaypoints() { - require("Storage").writeJSON("waypoints.json", waypoints); + require("waypoints").save(waypoints); } function deleteWaypoint(w) { diff --git a/apps/wpmoto/metadata.json b/apps/wpmoto/metadata.json index f562b23ba..32c41d757 100644 --- a/apps/wpmoto/metadata.json +++ b/apps/wpmoto/metadata.json @@ -2,17 +2,16 @@ "id": "wpmoto", "name": "Waypointer Moto", "shortName": "Waypointer Moto", - "version": "0.02", + "version": "0.04", "description": "Waypoint-based motorcycle navigation aid", "icon": "wpmoto.png", "tags": "tool,outdoors,gps", "supports": ["BANGLEJS","BANGLEJS2"], "screenshots": [{"url":"screenshot.png"},{"url":"screenshot-menu.png"},{"url":"screenshot-delete.png"}], "readme": "README.md", - "interface": "wpmoto.html", + "dependencies" : { "waypoints":"type" }, "storage": [ {"name":"wpmoto.app.js","url":"app.js"}, {"name":"wpmoto.img","url":"icon.js","evaluate":true} - ], - "data": [{"name":"waypoints.json","url":"waypoints.json"}] + ] } diff --git a/apps/wpmoto/waypoints.json b/apps/wpmoto/waypoints.json deleted file mode 100644 index 8a4ab83b8..000000000 --- a/apps/wpmoto/waypoints.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - { - "name":"NONE" - }, -] diff --git a/apps/wristlight/Icon.png b/apps/wristlight/Icon.png new file mode 100644 index 000000000..0bbcd3e36 Binary files /dev/null and b/apps/wristlight/Icon.png differ diff --git a/apps/wristlight/README.md b/apps/wristlight/README.md new file mode 100644 index 000000000..e69d61b76 --- /dev/null +++ b/apps/wristlight/README.md @@ -0,0 +1,9 @@ +# Wrist Light + +A flash light on your wrist with different colors + +![](screenshot.png) +![](screenshot_red.png) + +## Creator +[@pidajo](https://github.com/pidajo) diff --git a/apps/wristlight/app.js b/apps/wristlight/app.js new file mode 100644 index 000000000..24bc1eab2 --- /dev/null +++ b/apps/wristlight/app.js @@ -0,0 +1,52 @@ +function draw(color) { + if (color == undefined) { + color = -1; + } + g.clear(); + g.setColor(color); + g.fillRect(0, 0, g.getWidth(), g.getHeight()); +} + +function draw2Pattern() { + colors = ["ff0000", "8080ff", "00ff00", + "ffffff"]; + drawPattern(2, colors); +} + +function draw3Pattern() { + colors = ["ff0000", "00ff00", "0000ff", + "ff00ff", "ffffff", "00ffff", + "ffff00", "ff8000", "ff0080"]; + drawPattern(3, colors); +} + +function drawPattern(size, colors) { + g.clear(); + var w = g.getWidth() / size; + var h = g.getHeight() / size; + for (var i = 0; i < size; i++) { + for (var j = 0; j < size; j++) { + var color = colors[i*size + j]; + g.setColor("#" + color); + g.fillRect(j * w, i * h, j * w + w, i * h + h); + } + } + Bangle.on("touch", function(btn, xy) { + var x = parseInt((xy.x) / w); + var y = parseInt((xy.y) / h); + draw("#" + colors[y * size + x]); + }); +} + +// Clear the screen once, at startup +// draw immediately at first +draw3Pattern(); + +/* +require("Storage").write("wristlight.info",{ + "id":"wristlight", + "name":"Wrist Light", + "src":"wristlight.app.js", + "icon":"wristlight.img" +}); +*/ diff --git a/apps/wristlight/metadata.json b/apps/wristlight/metadata.json new file mode 100644 index 000000000..af1d700df --- /dev/null +++ b/apps/wristlight/metadata.json @@ -0,0 +1,16 @@ +{ "id": "wristlight", + "name": "Wrist Light", + "shortName":"Wrist Light", + "icon": "wristlight48.png", + "version":"0.01", + "description": "A flash light with different colors on your wrist", + "tags": "flash,light", + "allow_emulator":true, + "supports": ["BANGLEJS", "BANGLEJS2"], + "readme":"README.md", + "screenshots" : [ { "url":"screenshot.png" }, { "url":"screenshot_red.png" } ], + "storage": [ + {"name":"wristlight.app.js","url":"app.js"}, + {"name":"wristlight.img","url":"wristlight-icon.js","evaluate":true} + ] +} diff --git a/apps/wristlight/screenshot.png b/apps/wristlight/screenshot.png new file mode 100644 index 000000000..31850d1ca Binary files /dev/null and b/apps/wristlight/screenshot.png differ diff --git a/apps/wristlight/screenshot_red.png b/apps/wristlight/screenshot_red.png new file mode 100644 index 000000000..06ac7a190 Binary files /dev/null and b/apps/wristlight/screenshot_red.png differ diff --git a/apps/wristlight/wristlight-icon.js b/apps/wristlight/wristlight-icon.js new file mode 100644 index 000000000..689372499 --- /dev/null +++ b/apps/wristlight/wristlight-icon.js @@ -0,0 +1 @@ +atob("MDCEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMzMzMzMAN3d3d3d3QDu7u7u7gAAAAAADMzMzMzMDd3d3d3d3dDu7u7u7uAAAAAAzMzMzMzMDd3d3d3d3dDu7u7u7u4AAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu7u7u7uwAAAA//8AAAAHd3d3d3cAAAALu7u7u7u7AAD/////AAB3d3d3d3dwAAALu7u7u7u7AA//////8AB3d3d3d3dwAAALu7u7u7u7AP///////wB3d3d3d3dwAAALu7u7u7u7AP///////wB3d3d3d3dwAAALu7u7u7u7D/////////B3d3d3d3dwAAALu7u7u7u7D/////////B3d3d3d3dwAAALu7u7u7u7D/////////B3d3d3d3dwAAALu7u7u7u7A////////zB3d3d3d3dwAAALu7u7u7u7AP///////wB3d3d3d3dwAAALu7u7u7u7AP///////wB3d3d3d3dwAAALu7u7u7u7AA//////8AB3d3d3d3dwAAALu7u7u7u7AAD/////AAB3d3d3d3dwAAAMu7u7u7u8AAACMzMgAAB3d3d3d3dwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALu7u7u7u7AIiIiIiIiACIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAAAu7u7u7u7CYiIiIiIiJCIiIiIiIgAAAAAq7u7u7u7CYiIiIiIiJCIiIiIiIAAAAAAALu7u7u7CoiIiIiIiKCIiIiIiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") diff --git a/apps/wristlight/wristlight48.png b/apps/wristlight/wristlight48.png new file mode 100644 index 000000000..8f0ff59f7 Binary files /dev/null and b/apps/wristlight/wristlight48.png differ diff --git a/backup.js b/backup.js new file mode 100644 index 000000000..06d98a366 --- /dev/null +++ b/backup.js @@ -0,0 +1,125 @@ +/* Code to handle Backup/Restore functionality */ + +const BACKUP_STORAGEFILE_DIR = "storage-files"; + +function bangleDownload() { + var zip = new JSZip(); + Progress.show({title:"Scanning...",sticky:true}); + var normalFiles, storageFiles; + console.log("Listing normal files..."); + Comms.reset() + .then(() => Comms.showMessage("Backing up...")) + .then(() => Comms.listFiles({sf:false})) + .then(f => { + normalFiles = f; + console.log(" - "+f.join(",")); + console.log("Listing StorageFiles..."); + return Comms.listFiles({sf:true}); + }).then(f => { + storageFiles = f; + console.log(" - "+f.join(",")); + var fileCount = normalFiles.length + storageFiles.length; + var promise = Promise.resolve(); + // Normal files + normalFiles.forEach((filename,n) => { + if (filename==".firmware") { + console.log("Ignoring .firmware file"); + return; + } + promise = promise.then(() => { + Progress.hide({sticky: true}); + var percent = n/fileCount; + Progress.show({title:`Download ${filename}`,sticky:true,min:percent,max:percent+(1/fileCount),percent:0}); + return Comms.readFile(filename).then(data => zip.file(filename,data)); + }); + }); + // Storage files + if (storageFiles.length) { + var zipStorageFiles = zip.folder(BACKUP_STORAGEFILE_DIR); + storageFiles.forEach((filename,n) => { + promise = promise.then(() => { + Progress.hide({sticky: true}); + var percent = (normalFiles.length+n)/fileCount; + Progress.show({title:`Download ${filename}`,sticky:true,min:percent,max:percent+(1/fileCount),percent:0}); + return Comms.readStorageFile(filename).then(data => zipStorageFiles.file(filename,data)); + }); + }); + } + return promise; + }).then(() => { + return Comms.showMessage(Const.MESSAGE_RELOAD); + }).then(() => { + return zip.generateAsync({type:"binarystring"}); + }).then(content => { + Progress.hide({ sticky: true }); + showToast('Backup complete!', 'success'); + Espruino.Core.Utils.fileSaveDialog(content, "Banglejs backup.zip"); + }).catch(err => { + Progress.hide({ sticky: true }); + showToast('Backup failed, ' + err, 'error'); + }); +} + +function bangleUpload() { + Espruino.Core.Utils.fileOpenDialog({ + id:"backup", + type:"arraybuffer", + mimeType:".zip,application/zip"}, function(data) { + if (data===undefined) return; + var promise = Promise.resolve(); + var zip = new JSZip(); + var cmds = ""; + zip.loadAsync(data).then(function(zip) { + return showPrompt("Restore from ZIP","Are you sure? This will remove all existing apps"); + }).then(()=>{ + Progress.show({title:`Reading ZIP`}); + zip.forEach(function (path, file){ + console.log("path"); + promise = promise + .then(() => file.async("string")) + .then(data => { + console.log("decoded", path); + if (data.length==0) { // https://github.com/espruino/BangleApps/issues/1593 + console.log("Can't restore files of length 0, ignoring "+path); + } else if (path.startsWith(BACKUP_STORAGEFILE_DIR)) { + path = path.substr(BACKUP_STORAGEFILE_DIR.length+1); + cmds += AppInfo.getStorageFileUploadCommands(path, data)+"\n"; + } else if (!path.includes("/")) { + cmds += AppInfo.getFileUploadCommands(path, data)+"\n"; + } else console.log("Ignoring "+path); + }); + }); + return promise; + }) + .then(() => { + Progress.hide({sticky:true}); + Progress.show({title:`Erasing...`}); + return Comms.removeAllApps(); }) + .then(() => { + Progress.hide({sticky:true}); + Progress.show({title:`Restoring...`, sticky:true}); + return Comms.showMessage(`Restoring...`); }) + .then(() => Comms.write("\x10"+Comms.getProgressCmd()+"\n")) + .then(() => Comms.uploadCommandList(cmds, 0, cmds.length)) + .then(() => getInstalledApps(true)) + .then(() => Comms.showMessage(Const.MESSAGE_RELOAD)) + .then(() => { + Progress.hide({sticky:true}); + showToast('Restore complete!', 'success'); + }) + .catch(err => { + Progress.hide({sticky:true}); + showToast('Restore failed, ' + err, 'error'); + }); + return promise; + }); +} + +window.addEventListener('load', (event) => { + document.getElementById("downloadallapps").addEventListener("click",event=>{ + bangleDownload(); + }); + document.getElementById("uploadallapps").addEventListener("click",event=>{ + bangleUpload(); + }); +}); diff --git a/bin/apploader.js b/bin/apploader.js index 427a0ef99..26c4c1f09 100755 --- a/bin/apploader.js +++ b/bin/apploader.js @@ -1,4 +1,4 @@ -#!/usr/bin/nodejs +#!/usr/bin/env node /* Simple Command-line app loader for Node.js =============================================== @@ -8,35 +8,44 @@ as a normal dependency) because we want `sanitycheck.js` to be able to run *quickly* in travis for every commit, and we don't want NPM pulling in (and compiling native modules) for Noble. + */ var SETTINGS = { pretokenise : true }; var APPSDIR = __dirname+"/../apps/"; -var Utils = require("../core/js/utils.js"); -var AppInfo = require("../core/js/appinfo.js"); var noble; -try { - noble = require('@abandonware/noble'); -} catch (e) {} -if (!noble) try { - noble = require('noble'); -} catch (e) { } +["@abandonware/noble", "noble"].forEach(module => { + if (!noble) try { + noble = require(module); + } catch(e) { + if (e.code !== 'MODULE_NOT_FOUND') { + throw e; + } + } +}); if (!noble) { console.log("You need to:") console.log(" npm install @abandonware/noble") console.log("or:") console.log(" npm install noble") + process.exit(1); } - -var apps = []; - function ERROR(msg) { console.error(msg); process.exit(1); } +//eval(require("fs").readFileSync(__dirname+"../core/js/utils.js")); +var AppInfo = require("../core/js/appinfo.js"); +global.Const = { + /* Are we only putting a single app on a device? If so + apps should all be saved as .bootcde and we write info + about the current app into app.info */ + SINGLE_APP_ONLY : false, +}; +var deviceId = "BANGLEJS2"; var apps = []; var dirs = require("fs").readdirSync(APPSDIR, {withFileTypes: true}); dirs.forEach(dir => { @@ -54,6 +63,10 @@ dirs.forEach(dir => { var args = process.argv; +var bangleParam = args.findIndex(arg => /-b\d/.test(arg)); +if (bangleParam!==-1) { + deviceId = "BANGLEJS"+args.splice(bangleParam, 1)[0][2]; +} if (args.length==3 && args[2]=="list") cmdListApps(); else if (args.length==3 && args[2]=="devices") cmdListDevices(); else if (args.length==4 && args[2]=="install") cmdInstallApp(args[3]); @@ -68,7 +81,10 @@ apploader.js list - list available apps apploader.js devices - list available device addresses -apploader.js install appname [de:vi:ce:ad:dr:es] +apploader.js install [-b1] appname [de:vi:ce:ad:dr:es] + +NOTE: By default this App Loader expects the device it uploads to +(deviceId) to be BANGLEJS2, pass '-b1' for it to work with Bangle.js 1 `); process.exit(0); } @@ -104,7 +120,10 @@ function cmdInstallApp(appId, deviceAddress) { fileGetter:function(url) { console.log(__dirname+"/"+url); return Promise.resolve(require("fs").readFileSync(__dirname+"/../"+url).toString("binary")); - }, settings : SETTINGS}).then(files => { + }, + settings : SETTINGS, + device : { id : deviceId } + }).then(files => { //console.log(files); var command = files.map(f=>f.cmd).join("\n")+"\n"; bangleSend(command, deviceAddress).then(() => process.exit(0)); diff --git a/bin/create_app_supports_field.js b/bin/create_app_supports_field.js index d6aada357..24d6694f2 100644 --- a/bin/create_app_supports_field.js +++ b/bin/create_app_supports_field.js @@ -1,4 +1,4 @@ -#!/usr/bin/nodejs +#!/usr/bin/env nodejs /* Quick hack to add proper 'supports' field to apps.json */ diff --git a/bin/create_apps_json.sh b/bin/create_apps_json.sh index 665079fc6..c9f310e57 100755 --- a/bin/create_apps_json.sh +++ b/bin/create_apps_json.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # ================================================================ # apps.json used to contain the metadata for every app. Now the # metadata is stored in each apps's directory - app/yourapp/metadata.js @@ -23,7 +23,7 @@ echo "[" > "$outfile" first=1 for app in apps/*/; do echo "Processing $app..."; - if [[ "$app" =~ ^apps/_example.* ]]; then + if [[ "$app" =~ ^apps/_example.* ]] || [ ! -e "$app/"metadata.json ]; then echo "Ignoring $app" else if [ $first -eq 1 ]; then diff --git a/bin/firmwaremaker.js b/bin/firmwaremaker.js index 1c2d9cb77..1dc5ec073 100755 --- a/bin/firmwaremaker.js +++ b/bin/firmwaremaker.js @@ -1,4 +1,4 @@ -#!/usr/bin/nodejs +#!/usr/bin/env nodejs /* Mashes together a bunch of different apps to make a single firmware JS file which can be uploaded. diff --git a/bin/firmwaremaker_c.js b/bin/firmwaremaker_c.js index 7940e551d..08fc6fe83 100755 --- a/bin/firmwaremaker_c.js +++ b/bin/firmwaremaker_c.js @@ -1,4 +1,4 @@ -#!/usr/bin/nodejs +#!/usr/bin/env node /* Mashes together a bunch of different apps into a big binary blob. We then store this *inside* the Bangle.js firmware and can use it @@ -23,13 +23,13 @@ if (DEVICE=="BANGLEJS") { var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs1_storage_default.c'); var APPS = [ // IDs of apps to install "boot","launch","mclock","setting", - "about","alarm","widbat","widbt","welcome" + "about","alarm","sched","widbat","widbt","welcome" ]; } else if (DEVICE=="BANGLEJS2") { var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c'); var APPS = [ // IDs of apps to install "boot","launch","antonclk","setting", - "about","alarm","health","widlock","widbat","widbt","widid","welcome" + "about","alarm","sched","health","widlock","widbat","widbt","widid","welcome" ]; } else { console.log("USAGE:"); @@ -114,7 +114,7 @@ function evaluateFile(file) { var hsStart = 'require("heatshrink").decompress(atob("'; var hsEnd = '"))'; if (file.content.startsWith(hsStart) && file.content.endsWith(hsEnd)) { - var heatshrink = require(ROOTDIR+"/core/lib/heatshrink.js"); + var heatshrink = require(ROOTDIR+"/webtools/heatshrink.js"); var b64 = file.content.slice(hsStart.length, -hsEnd.length); var decompressed = heatshrink.decompress(atob(b64)); file.content = ""; @@ -172,7 +172,7 @@ Promise.all(APPS.map(appid => { // Generated by BangleApps/bin/build_bangles_c.js const int jsfStorageInitialContentLength = ${storageContent.length}; -const char jsfStorageInitialContents[] = { +const unsigned char jsfStorageInitialContents[] = { `; for (var i=0;i { + translations.GLOBAL[translationItem.str] = translationItem.str; + resolve() + })) } } }); diff --git a/bin/pre-publish.sh b/bin/pre-publish.sh index ee73968d7..710160ded 100755 --- a/bin/pre-publish.sh +++ b/bin/pre-publish.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash cd `dirname $0`/.. nodejs bin/sanitycheck.js || exit 1 diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js old mode 100755 new mode 100644 index 363e86922..6537f4389 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -1,9 +1,9 @@ -#!/usr/bin/nodejs +#!/usr/bin/env node /* Checks for any obvious problems in apps.json */ var fs = require("fs"); -var heatshrink = require("../core/lib/heatshrink"); +var heatshrink = require("../webtools/heatshrink"); var acorn; try { acorn = require("acorn"); @@ -17,13 +17,25 @@ try { } var BASEDIR = __dirname+"/../"; -var APPSDIR = BASEDIR+"apps/"; -function ERROR(s) { - console.error("ERROR: "+s); - process.exit(1); +var APPSDIR_RELATIVE = "apps/"; +var APPSDIR = BASEDIR + APPSDIR_RELATIVE; +var warningCount = 0; +var errorCount = 0; +function ERROR(msg, opt) { + // file=app.js,line=1,col=5,endColumn=7 + opt = opt||{}; + console.log(`::error${Object.keys(opt).length?" ":""}${Object.keys(opt).map(k=>k+"="+opt[k]).join(",")}::${msg}`); + errorCount++; } -function WARN(s) { - console.log("Warning: "+s); +function WARN(msg, opt) { + // file=app.js,line=1,col=5,endColumn=7 + opt = opt||{}; + if (KNOWN_WARNINGS.includes(msg)) { + console.log(`Known warning : ${msg}`); + } else { + console.log(`::warning${Object.keys(opt).length?" ":""}${Object.keys(opt).map(k=>k+"="+opt[k]).join(",")}::${msg}`); + } + warningCount++; } var apps = []; @@ -43,16 +55,20 @@ dirs.forEach(dir => { } catch (e) { console.log(e); var m = e.toString().match(/in JSON at position (\d+)/); + var messageInfo = { + file : "apps/"+dir.name+"/metadata.json", + }; if (m) { var char = parseInt(m[1]); + messageInfo.line = appsFile.substr(0,char).split("\n").length; console.log("==============================================="); - console.log("LINE "+appsFile.substr(0,char).split("\n").length); + console.log("LINE "+messageInfo.line); console.log("==============================================="); console.log(appsFile.substr(char-10, 20)); console.log("==============================================="); } console.log(m); - ERROR(dir.name+"/metadata.json not valid JSON"); + ERROR(messageInfo.file+" not valid JSON", messageInfo); } }); @@ -60,14 +76,25 @@ const APP_KEYS = [ 'id', 'name', 'shortName', 'version', 'icon', 'screenshots', 'description', 'tags', 'type', 'sortorder', 'readme', 'custom', 'customConnect', 'interface', 'storage', 'data', 'supports', 'allow_emulator', - 'dependencies' + 'dependencies', 'provides_modules', 'provides_widgets', "default" ]; -const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite', 'supports']; +const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite', 'supports', 'noOverwrite']; const DATA_KEYS = ['name', 'wildcard', 'storageFile', 'url', 'content', 'evaluate']; const SUPPORTS_DEVICES = ["BANGLEJS","BANGLEJS2"]; // device IDs allowed for 'supports' +const METADATA_TYPES = ["app","clock","widget","bootloader","RAM","launch","scheduler","notify","locale","settings","waypoints","textinput","module","clkinfo"]; // values allowed for "type" field const FORBIDDEN_FILE_NAME_CHARS = /[,;]/; // used as separators in appid.info const VALID_DUPLICATES = [ '.tfmodel', '.tfnames' ]; const GRANDFATHERED_ICONS = ["s7clk", "snek", "astral", "alpinenav", "slomoclock", "arrow", "pebble", "rebble"]; +const INTERNAL_FILES_IN_APP_TYPE = { // list of app types and files they SHOULD provide... + 'textinput' : ['textinput'], + 'waypoints' : ['waypoints'], + // notify? +}; +/* These are warnings we know about but don't want in our output */ +var KNOWN_WARNINGS = [ +"App gpsrec data file wildcard .gpsrc? does not include app ID", +"App owmweather data file weather.json is also listed as data file for app weather", +]; function globToRegex(pattern) { const ESCAPE = '.*+-?^${}()|[]\\'; @@ -86,82 +113,98 @@ let allFiles = []; let existingApps = []; apps.forEach((app,appIdx) => { if (!app.id) ERROR(`App ${appIdx} has no id`); - if (existingApps.includes(app.id)) ERROR(`Duplicate app '${app.id}'`); + var appDirRelative = APPSDIR_RELATIVE+app.id+"/"; + var appDir = APPSDIR+app.id+"/"; + var metadataFile = appDirRelative+"metadata.json"; + if (existingApps.includes(app.id)) ERROR(`Duplicate app '${app.id}'`, {file:metadataFile}); existingApps.push(app.id); //console.log(`Checking ${app.id}...`); - var appDir = APPSDIR+app.id+"/"; + if (!fs.existsSync(APPSDIR+app.id)) ERROR(`App ${app.id} has no directory`); - if (!app.name) ERROR(`App ${app.id} has no name`); + if (!app.name) ERROR(`App ${app.id} has no name`, {file:metadataFile}); var isApp = !app.type || app.type=="app"; - if (app.name.length>20 && !app.shortName && isApp) ERROR(`App ${app.id} has a long name, but no shortName`); - if (!Array.isArray(app.supports)) ERROR(`App ${app.id} has no 'supports' field or it's not an array`); + if (app.name.length>20 && !app.shortName && isApp) ERROR(`App ${app.id} has a long name, but no shortName`, {file:metadataFile}); + if (app.type && !METADATA_TYPES.includes(app.type)) + ERROR(`App ${app.id} 'type' is one one of `+METADATA_TYPES, {file:metadataFile}); + if (!Array.isArray(app.supports)) ERROR(`App ${app.id} has no 'supports' field or it's not an array`, {file:metadataFile}); else { app.supports.forEach(dev => { if (!SUPPORTS_DEVICES.includes(dev)) - ERROR(`App ${app.id} has unknown device in 'supports' field - ${dev}`); + ERROR(`App ${app.id} has unknown device in 'supports' field - ${dev}`, {file:metadataFile}); }); } - if (!app.version) WARN(`App ${app.id} has no version`); + if (!app.version) ERROR(`App ${app.id} has no version`, {file:metadataFile}); else { if (!fs.existsSync(appDir+"ChangeLog")) { - if (app.version != "0.01") - WARN(`App ${app.id} has no ChangeLog`); + var invalidChangeLog = fs.readdirSync(appDir).find(f => f.toLowerCase().startsWith("changelog") && f!="ChangeLog"); + if (invalidChangeLog) + ERROR(`App ${app.id} has wrongly named ChangeLog (${invalidChangeLog})`, {file:appDirRelative+invalidChangeLog}); + else if (app.version != "0.01") + WARN(`App ${app.id} has no ChangeLog`, {file:metadataFile}); } else { var changeLog = fs.readFileSync(appDir+"ChangeLog").toString(); var versions = changeLog.match(/\d+\.\d+:/g); - if (!versions) ERROR(`No versions found in ${app.id} ChangeLog (${appDir}ChangeLog)`); + if (!versions) ERROR(`No versions found in ${app.id} ChangeLog (${appDir}ChangeLog)`, {file:metadataFile}); var lastChangeLog = versions.pop().slice(0,-1); if (lastChangeLog != app.version) - WARN(`App ${app.id} app version (${app.version}) and ChangeLog (${lastChangeLog}) don't agree`); + ERROR(`App ${app.id} app version (${app.version}) and ChangeLog (${lastChangeLog}) don't agree`, {file:appDirRelative+"ChangeLog", line:changeLog.split("\n").length-1}); } } - if (!app.description) ERROR(`App ${app.id} has no description`); - if (!app.icon) ERROR(`App ${app.id} has no icon`); - if (!fs.existsSync(appDir+app.icon)) ERROR(`App ${app.id} icon doesn't exist`); + if (!app.description) ERROR(`App ${app.id} has no description`, {file:metadataFile}); + if (!app.icon) ERROR(`App ${app.id} has no icon`, {file:metadataFile}); + if (!fs.existsSync(appDir+app.icon)) ERROR(`App ${app.id} icon doesn't exist`, {file:metadataFile}); if (app.screenshots) { - if (!Array.isArray(app.screenshots)) ERROR(`App ${app.id} screenshots is not an array`); + if (!Array.isArray(app.screenshots)) ERROR(`App ${app.id} screenshots is not an array`, {file:metadataFile}); app.screenshots.forEach(screenshot => { if (!fs.existsSync(appDir+screenshot.url)) - ERROR(`App ${app.id} screenshot file ${screenshot.url} not found`); + ERROR(`App ${app.id} screenshot file ${screenshot.url} not found`, {file:metadataFile}); }); } - if (app.readme && !fs.existsSync(appDir+app.readme)) ERROR(`App ${app.id} README file doesn't exist`); - if (app.custom && !fs.existsSync(appDir+app.custom)) ERROR(`App ${app.id} custom HTML doesn't exist`); - if (app.customConnect && !app.custom) ERROR(`App ${app.id} has customConnect but no customn HTML`); - if (app.interface && !fs.existsSync(appDir+app.interface)) ERROR(`App ${app.id} interface HTML doesn't exist`); + if (app.readme && !fs.existsSync(appDir+app.readme)) ERROR(`App ${app.id} README file doesn't exist`, {file:metadataFile}); + if (app.custom && !fs.existsSync(appDir+app.custom)) ERROR(`App ${app.id} custom HTML doesn't exist`, {file:metadataFile}); + if (app.customConnect && !app.custom) ERROR(`App ${app.id} has customConnect but no customn HTML`, {file:metadataFile}); + if (app.interface && !fs.existsSync(appDir+app.interface)) ERROR(`App ${app.id} interface HTML doesn't exist`, {file:metadataFile}); if (app.dependencies) { if (("object"==typeof app.dependencies) && !Array.isArray(app.dependencies)) { Object.keys(app.dependencies).forEach(dependency => { - if (!["type","app"].includes(app.dependencies[dependency])) - ERROR(`App ${app.id} 'dependencies' must all be tagged 'type' or 'app' right now`); + if (!["type","app","module","widget"].includes(app.dependencies[dependency])) + ERROR(`App ${app.id} 'dependencies' must all be tagged 'type/app/module/widget' right now`, {file:metadataFile}); + if (app.dependencies[dependency]=="type" && !METADATA_TYPES.includes(dependency)) + ERROR(`App ${app.id} 'type' dependency must be one of `+METADATA_TYPES, {file:metadataFile}); }); } else - ERROR(`App ${app.id} 'dependencies' must be an object`); + ERROR(`App ${app.id} 'dependencies' must be an object`, {file:metadataFile}); } + var fileNames = []; app.storage.forEach((file) => { - if (!file.name) ERROR(`App ${app.id} has a file with no name`); - if (isGlob(file.name)) ERROR(`App ${app.id} storage file ${file.name} contains wildcards`); + if (!file.name) ERROR(`App ${app.id} has a file with no name`, {file:metadataFile}); + if (isGlob(file.name)) ERROR(`App ${app.id} storage file ${file.name} contains wildcards`, {file:metadataFile}); let char = file.name.match(FORBIDDEN_FILE_NAME_CHARS) - if (char) ERROR(`App ${app.id} storage file ${file.name} contains invalid character "${char[0]}"`) + if (char) ERROR(`App ${app.id} storage file ${file.name} contains invalid character "${char[0]}"`, {file:metadataFile}) if (fileNames.includes(file.name) && !file.supports) // assume that there aren't duplicates if 'supports' is set - ERROR(`App ${app.id} file ${file.name} is a duplicate`); + ERROR(`App ${app.id} file ${file.name} is a duplicate`, {file:metadataFile}); if (file.supports && !Array.isArray(file.supports)) - ERROR(`App ${app.id} file ${file.name} supports field must be an array`); + ERROR(`App ${app.id} file ${file.name} supports field must be an array`, {file:metadataFile}); if (file.supports) file.supports.forEach(dev => { if (!SUPPORTS_DEVICES.includes(dev)) - ERROR(`App ${app.id} file ${file.name} has unknown device in 'supports' field - ${dev}`); + ERROR(`App ${app.id} file ${file.name} has unknown device in 'supports' field - ${dev}`, {file:metadataFile}); }); fileNames.push(file.name); - allFiles.push({app: app.id, file: file.name}); - if (file.url) if (!fs.existsSync(appDir+file.url)) ERROR(`App ${app.id} file ${file.url} doesn't exist`); - if (!file.url && !file.content && !app.custom) ERROR(`App ${app.id} file ${file.name} has no contents`); + var fileInternal = false; + if (app.type && INTERNAL_FILES_IN_APP_TYPE[app.type]) { + if (INTERNAL_FILES_IN_APP_TYPE[app.type].includes(file.name)) + fileInternal = true; + } + allFiles.push({app: app.id, file: file.name, internal:fileInternal}); + if (file.url) if (!fs.existsSync(appDir+file.url)) ERROR(`App ${app.id} file ${file.url} doesn't exist`, {file:metadataFile}); + if (!file.url && !file.content && !app.custom) ERROR(`App ${app.id} file ${file.name} has no contents`, {file:metadataFile}); var fileContents = ""; if (file.content) fileContents = file.content; if (file.url) fileContents = fs.readFileSync(appDir+file.url).toString(); - if (file.supports && !Array.isArray(file.supports)) ERROR(`App ${app.id} file ${file.name} supports field is not an array`); + if (file.supports && !Array.isArray(file.supports)) ERROR(`App ${app.id} file ${file.name} supports field is not an array`, {file:metadataFile}); if (file.evaluate) { try { acorn.parse("("+fileContents+")"); @@ -173,7 +216,7 @@ apps.forEach((app,appIdx) => { console.log("====================================================="); console.log(fileContents); console.log("====================================================="); - ERROR(`App ${app.id}'s ${file.name} has evaluate:true but is not valid JS expression`); + ERROR(`App ${app.id}'s ${file.name} has evaluate:true but is not valid JS expression`, {file:appDirRelative+file.url}); } } if (file.name.endsWith(".js")) { @@ -188,58 +231,66 @@ apps.forEach((app,appIdx) => { console.log("====================================================="); console.log(fileContents); console.log("====================================================="); - ERROR(`App ${app.id}'s ${file.name} is a JS file but isn't valid JS`); + ERROR(`App ${app.id}'s ${file.name} is a JS file but isn't valid JS`, {file:appDirRelative+file.url}); + } + // clock app checks + if (app.type=="clock") { + var a = fileContents.indexOf("Bangle.loadWidgets()"); + var b = fileContents.indexOf("Bangle.setUI("); + if (a>=0 && b>=0 && a 48 || icon[0] < 24 || icon[1] > 48 || icon[1] < 24) { - if (GRANDFATHERED_ICONS.includes(app.id)) WARN(`JS icon ${file.name} should be 48x48px (or slightly under) but is instead ${icon[0]}x${icon[1]}px`); - else ERROR(`JS icon ${file.name} should be 48x48px (or slightly under) but is instead ${icon[0]}x${icon[1]}px`); + if (GRANDFATHERED_ICONS.includes(app.id)) WARN(`JS icon ${file.name} should be 48x48px (or slightly under) but is instead ${icon[0]}x${icon[1]}px`, {file:appDirRelative+file.url}); + else ERROR(`JS icon ${file.name} should be 48x48px (or slightly under) but is instead ${icon[0]}x${icon[1]}px`, {file:appDirRelative+file.url}); } } } }); let dataNames = []; (app.data||[]).forEach((data)=>{ - if (!data.name && !data.wildcard) ERROR(`App ${app.id} has a data file with no name`); + if (!data.name && !data.wildcard) ERROR(`App ${app.id} has a data file with no name`, {file:metadataFile}); if (dataNames.includes(data.name||data.wildcard)) - ERROR(`App ${app.id} data file ${data.name||data.wildcard} is a duplicate`); + ERROR(`App ${app.id} data file ${data.name||data.wildcard} is a duplicate`, {file:metadataFile}); dataNames.push(data.name||data.wildcard) allFiles.push({app: app.id, data: (data.name||data.wildcard)}); if ('name' in data && 'wildcard' in data) - ERROR(`App ${app.id} data file ${data.name} has both name and wildcard`); + ERROR(`App ${app.id} data file ${data.name} has both name and wildcard`, {file:metadataFile}); if (isGlob(data.name)) - ERROR(`App ${app.id} data file name ${data.name} contains wildcards`); + ERROR(`App ${app.id} data file name ${data.name} contains wildcards`, {file:metadataFile}); if (data.wildcard) { if (!isGlob(data.wildcard)) - ERROR(`App ${app.id} data file wildcard ${data.wildcard} does not actually contains wildcard`); + ERROR(`App ${app.id} data file wildcard ${data.wildcard} does not actually contains wildcard`, {file:metadataFile}); if (data.wildcard.replace(/\?|\*/g,'') === '') - ERROR(`App ${app.id} data file wildcard ${data.wildcard} does not contain regular characters`); + ERROR(`App ${app.id} data file wildcard ${data.wildcard} does not contain regular characters`, {file:metadataFile}); else if (data.wildcard.replace(/\?|\*/g,'').length < 3) - WARN(`App ${app.id} data file wildcard ${data.wildcard} is very broad`); + WARN(`App ${app.id} data file wildcard ${data.wildcard} is very broad`, {file:metadataFile}); else if (!data.wildcard.includes(app.id)) - WARN(`App ${app.id} data file wildcard ${data.wildcard} does not include app ID`); + WARN(`App ${app.id} data file wildcard ${data.wildcard} does not include app ID`, {file:metadataFile}); } let char = (data.name||data.wildcard).match(FORBIDDEN_FILE_NAME_CHARS) - if (char) ERROR(`App ${app.id} data file ${data.name||data.wildcard} contains invalid character "${char[0]}"`) + if (char) ERROR(`App ${app.id} data file ${data.name||data.wildcard} contains invalid character "${char[0]}"`, {file:metadataFile}) if ('storageFile' in data && typeof data.storageFile !== 'boolean') - ERROR(`App ${app.id} data file ${data.name||data.wildcard} has non-boolean value for "storageFile"`); + ERROR(`App ${app.id} data file ${data.name||data.wildcard} has non-boolean value for "storageFile"`, {file:metadataFile}); for (const key in data) { if (!DATA_KEYS.includes(key)) - ERROR(`App ${app.id} data file ${data.name||data.wildcard} has unknown property "${key}"`); + ERROR(`App ${app.id} data file ${data.name||data.wildcard} has unknown property "${key}"`, {file:metadataFile}); } }); // prefer "appid.json" over "appid.settings.json" (TODO: change to ERROR once all apps comply?) @@ -249,32 +300,50 @@ apps.forEach((app,appIdx) => { WARN(`App ${app.id} uses data file ${app.id+'.settings.json'}`)*/ // settings files should be listed under data, not storage (TODO: change to ERROR once all apps comply?) if (fileNames.includes(app.id+".settings.json")) - WARN(`App ${app.id} uses storage file ${app.id+'.settings.json'} instead of data file`) + WARN(`App ${app.id} uses storage file ${app.id+'.settings.json'} instead of data file`, {file:metadataFile}) if (fileNames.includes(app.id+".json")) - WARN(`App ${app.id} uses storage file ${app.id+'.json'} instead of data file`) + WARN(`App ${app.id} uses storage file ${app.id+'.json'} instead of data file`, {file:metadataFile}) // warn if storage file matches data file of same app dataNames.forEach(dataName=>{ const glob = globToRegex(dataName) fileNames.forEach(fileName=>{ if (glob.test(fileName)) { - if (isGlob(dataName)) WARN(`App ${app.id} storage file ${fileName} matches data wildcard ${dataName}`) - else WARN(`App ${app.id} storage file ${fileName} is also listed in data`) + if (isGlob(dataName)) WARN(`App ${app.id} storage file ${fileName} matches data wildcard ${dataName}`, {file:metadataFile}) + else WARN(`App ${app.id} storage file ${fileName} is also listed in data`, {file:metadataFile}) } }) }) //console.log(fileNames); - if (isApp && !fileNames.includes(app.id+".app.js")) ERROR(`App ${app.id} has no entrypoint`); - if (isApp && !fileNames.includes(app.id+".img")) ERROR(`App ${app.id} has no JS icon`); - if (app.type=="widget" && !fileNames.includes(app.id+".wid.js")) ERROR(`Widget ${app.id} has no entrypoint`); + if (isApp && !fileNames.includes(app.id+".app.js")) ERROR(`App ${app.id} has no entrypoint`, {file:metadataFile}); + if (isApp && !fileNames.includes(app.id+".img")) ERROR(`App ${app.id} has no JS icon`, {file:metadataFile}); + if (app.type=="widget" && !fileNames.includes(app.id+".wid.js")) ERROR(`Widget ${app.id} has no entrypoint`, {file:metadataFile}); for (const key in app) { - if (!APP_KEYS.includes(key)) ERROR(`App ${app.id} has unknown key ${key}`); + if (!APP_KEYS.includes(key)) ERROR(`App ${app.id} has unknown key ${key}`, {file:metadataFile}); + } + if (app.type && INTERNAL_FILES_IN_APP_TYPE[app.type]) { + INTERNAL_FILES_IN_APP_TYPE[app.type].forEach(fileName => { + if (!fileNames.includes(fileName)) + ERROR(`App ${app.id} should include file named ${fileName} but it doesn't`, {file:metadataFile}); + }); + } + if (app.type=="module" && !app.provides_modules) { + ERROR(`App ${app.id} has type:module but it doesn't have a provides_modules field`, {file:metadataFile}); + } + if (app.provides_modules) { + app.provides_modules.forEach(modulename => { + if (!app.storage.find(s=>s.name==modulename)) + ERROR(`App ${app.id} has provides_modules ${modulename} but it doesn't provide that filename`, {file:metadataFile}); + }); } }); + + // Do not allow files from different apps to collide let fileA + while(fileA=allFiles.pop()) { if (VALID_DUPLICATES.includes(fileA.file)) - return; + break; const nameA = (fileA.file||fileA.data), globA = globToRegex(nameA), typeA = fileA.file?'storage':'data' @@ -284,9 +353,16 @@ while(fileA=allFiles.pop()) { typeB = fileB.file?'storage':'data' if (globA.test(nameB)||globB.test(nameA)) { if (isGlob(nameA)||isGlob(nameB)) - ERROR(`App ${fileB.app} ${typeB} file ${nameB} matches app ${fileA.app} ${typeB} file ${nameA}`) - else if (fileA.app != fileB.app) - WARN(`App ${fileB.app} ${typeB} file ${nameB} is also listed as ${typeA} file for app ${fileA.app}`) + ERROR(`App ${fileB.app} ${typeB} file ${nameB} matches app ${fileA.app} ${typeB} file ${nameA}`); + else if (fileA.app != fileB.app && (!fileA.internal) && (!fileB.internal)) + WARN(`App ${fileB.app} ${typeB} file ${nameB} is also listed as ${typeA} file for app ${fileA.app}`); } }) } + +console.log("=================================="); +console.log(`${errorCount} errors, ${warningCount} warnings`); +console.log("=================================="); +if (errorCount) { + process.exit(1); +} diff --git a/bin/thumbnailer.js b/bin/thumbnailer.js index b6862741a..0895098e9 100755 --- a/bin/thumbnailer.js +++ b/bin/thumbnailer.js @@ -1,4 +1,4 @@ -#!/usr/bin/node +#!/usr/bin/env node /* var EMULATOR = "banglejs2"; @@ -37,7 +37,8 @@ var SETTINGS = { var Const = { }; module = undefined; -eval(require("fs").readFileSync(__dirname + "/../core/lib/espruinotools.js").toString()); +var Espruino = require(__dirname + "/../core/lib/espruinotools.js"); +//eval(require("fs").readFileSync(__dirname + "/../core/lib/espruinotools.js").toString()); eval(require("fs").readFileSync(__dirname + "/../core/js/utils.js").toString()); eval(require("fs").readFileSync(__dirname + "/../core/js/appinfo.js").toString()); var apps = JSON.parse(require("fs").readFileSync(__dirname+"/../apps.json")); @@ -78,7 +79,7 @@ function getThumbnail(appId, imageFn) { settings : SETTINGS, device : { id : DEVICEID } }).then(files => { - console.log("AppInfo returned");//, files); + console.log(`AppInfo returned for ${appId}`);//, files); flashMemory.set(factoryFlashMemory); jsTransmitString("reset()\n"); console.log("Uploading..."); @@ -88,6 +89,7 @@ function getThumbnail(appId, imageFn) { appLog = ""; jsTransmitString(command); console.log("Done."); + jsTransmitString("Bangle.setLCDMode();clearInterval();clearTimeout();\n"); jsStopIdle(); var rgba = new Uint8Array(GFX_WIDTH*GFX_HEIGHT*4); @@ -96,7 +98,7 @@ function getThumbnail(appId, imageFn) { var firstPixel = rgba32[0]; var blankImage = rgba32.every(col=>col==firstPixel) - if (appLog.indexOf("Uncaught")>=0) + if (appLog.replace("Uncaught Storage Updated!", "").indexOf("Uncaught")>=0) erroredApps.push( { id : app.id, log : appLog } ); if (!blankImage) { diff --git a/core b/core index a7a80a13f..2a89ea64f 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit a7a80a13fa187a4ff5f89669992babca2d95812c +Subproject commit 2a89ea64f7874b9264572f68836fe8ecd0a6b191 diff --git a/css/main.css b/css/main.css index f4850babe..ceb9fcd17 100644 --- a/css/main.css +++ b/css/main.css @@ -19,7 +19,7 @@ } .tile.column.col-6.col-sm-12.col-xs-12.app-tile { - border: solid 1px #fafafa; + border: solid 1px #dadee4; margin: 0; min-height: 150px; padding-top: 0.5rem; @@ -30,7 +30,7 @@ } a.mr-2{ - display: flex; + display: flex; align-items: center; } @@ -102,12 +102,23 @@ a.btn.btn-link.dropdown-toggle { content: url("data:image/svg+xml,%3Csvg fill='rgb(87, 85, 217)' xmlns='http://www.w3.org/2000/svg' viewBox='4 4 40 40' width='1em' height='1em'%3E%3Cpath d='M 8.5 5 C 6.0324991 5 4 7.0324991 4 9.5 L 4 30.5 C 4 32.967501 6.0324991 35 8.5 35 L 17 35 L 17 40 L 13.5 40 A 1.50015 1.50015 0 1 0 13.5 43 L 18.253906 43 A 1.50015 1.50015 0 0 0 18.740234 43 L 29.253906 43 A 1.50015 1.50015 0 0 0 29.740234 43 L 34.5 43 A 1.50015 1.50015 0 1 0 34.5 40 L 31 40 L 31 35 L 39.5 35 C 41.967501 35 44 32.967501 44 30.5 L 44 9.5 C 44 7.0324991 41.967501 5 39.5 5 L 8.5 5 z M 8.5 8 L 39.5 8 C 40.346499 8 41 8.6535009 41 9.5 L 41 30.5 C 41 31.346499 40.346499 32 39.5 32 L 29.746094 32 A 1.50015 1.50015 0 0 0 29.259766 32 L 18.746094 32 A 1.50015 1.50015 0 0 0 18.259766 32 L 8.5 32 C 7.6535009 32 7 31.346499 7 30.5 L 7 9.5 C 7 8.6535009 7.6535009 8 8.5 8 z M 17.5 12 C 16.136406 12 15 13.136406 15 14.5 L 15 25.5 C 15 26.863594 16.136406 28 17.5 28 L 30.5 28 C 31.863594 28 33 26.863594 33 25.5 L 33 14.5 C 33 13.136406 31.863594 12 30.5 12 L 17.5 12 z M 18 18 L 30 18 L 30 25 L 18 25 L 18 18 z M 20 35 L 28 35 L 28 40 L 20 40 L 20 35 z'/%3E%3C/svg%3E"); } .icon.icon-favourite { text-indent: 0px; } /*override spectre*/ +.icon.icon-favourite-active { text-indent: 0px; } /*override spectre*/ .icon.icon-favourite::before { - content: "\02661"; /* 0x2661 = empty heart; 0x2606 = empty star */ + content: url("data:image/svg+xml,%3Csvg fill='rgb(255, 0, 0)' xmlns='http://www.w3.org/2000/svg' viewBox='0 -3 50 47' width='1.5em' height='1.5em'%3E%3Cpath d='M 16.375 9 C 10.117188 9 5 14.054688 5 20.28125 C 5 33.050781 19.488281 39.738281 24.375 43.78125 L 25 44.3125 L 25.625 43.78125 C 30.511719 39.738281 45 33.050781 45 20.28125 C 45 14.054688 39.882813 9 33.625 9 C 30.148438 9 27.085938 10.613281 25 13.0625 C 22.914063 10.613281 19.851563 9 16.375 9 Z M 16.375 11 C 19.640625 11 22.480469 12.652344 24.15625 15.15625 L 25 16.40625 L 25.84375 15.15625 C 27.519531 12.652344 30.359375 11 33.625 11 C 38.808594 11 43 15.144531 43 20.28125 C 43 31.179688 30.738281 37.289063 25 41.78125 C 19.261719 37.289063 7 31.179688 7 20.28125 C 7 15.144531 11.1875 11 16.375 11 Z'/%3E%3C/svg%3E"); } .icon.icon-favourite-active::before { - content: "\02665"; /* 0x2665 = solid heart; 0x2605 = solid star */ + content: url("data:image/svg+xml,%3Csvg fill='rgb(255, 0, 0)' xmlns='http://www.w3.org/2000/svg' viewBox='0 -3 50 47' width='1.5em' height='1.5em'%3E%3Cpath d='M 25 44.296875 L 24.363281 43.769531 C 23.363281 42.941406 22.019531 42.027344 20.46875 40.96875 C 14.308594 36.765625 5 30.414063 5 20.285156 C 5 14.0625 10.097656 9 16.363281 9 C 19.714844 9 22.851563 10.457031 25 12.957031 C 27.148438 10.457031 30.289063 9 33.636719 9 C 39.902344 9 45 14.0625 45 20.285156 C 45 30.414063 35.691406 36.765625 29.53125 40.96875 C 27.976563 42.027344 26.636719 42.941406 25.636719 43.769531 Z'/%3E%3C/svg%3E"); } +.icon.icon-favourite span { + font-size: 50%; + color : #F66; + position:relative; + top:-0.7em; +} +.icon.icon-favourite-active span { + color : white; +} + .icon.icon-interface {text-indent: 0px;} /*override spectre*/ .icon.icon-interface::before { position: absolute; left: 50%; top: 70%; diff --git a/defaultapps_banglejs2.json b/defaultapps_banglejs2.json index 04bd44504..f96f81f60 100644 --- a/defaultapps_banglejs2.json +++ b/defaultapps_banglejs2.json @@ -1 +1 @@ -["boot","launch","antonclk","health","setting","about","widbat","widbt","widlock","widid"] +["boot","launch","antonclk","health","setting","about","alarm","widbat","widbt","widlock","widid"] diff --git a/gadgetbridge.js b/gadgetbridge.js new file mode 100644 index 000000000..679fffc60 --- /dev/null +++ b/gadgetbridge.js @@ -0,0 +1,162 @@ +/* Detects if we're running under Gadgetbridge in a WebView, and if +so it overwrites the 'Puck' library with a special one that calls back +into Gadgetbridge to handle watch communications */ + +/*// test code +Android = { + bangleTx : function(data) { + console.log("TX : "+JSON.stringify(data)); + } +};*/ + +if (typeof Android!=="undefined") { + console.log("Running under Gadgetbridge, overwrite Puck library"); + + var isBusy = false; + var queue = []; + var connection = { + cb : function(data) {}, + write : function(data, writecb) { + Android.bangleTx(data); + Puck.writeProgress(data.length, data.length); + if (writecb) setTimeout(writecb,10); + }, + close : function() {}, + received : "", + hadData : false + } + + function bangleRx(data) { +// document.getElementById("status").innerText = "RX:"+data; + connection.received += data; + connection.hadData = true; + if (connection.cb) connection.cb(data); + } + + function log(level, s) { + if (Puck.log) Puck.log(level, s); + } + + function handleQueue() { + if (!queue.length) return; + var q = queue.shift(); + log(3,"Executing "+JSON.stringify(q)+" from queue"); + if (q.type == "write") Puck.write(q.data, q.callback, q.callbackNewline); + else log(1,"Unknown queue item "+JSON.stringify(q)); + } + + /* convenience function... Write data, call the callback with data: + callbackNewline = false => if no new data received for ~0.2 sec + callbackNewline = true => after a newline */ + function write(data, callback, callbackNewline) { + let result; + /// If there wasn't a callback function, then promisify + if (typeof callback !== 'function') { + callbackNewline = callback; + + result = new Promise((resolve, reject) => callback = (value, err) => { + if (err) reject(err); + else resolve(value); + }); + } + + if (isBusy) { + log(3, "Busy - adding Puck.write to queue"); + queue.push({type:"write", data:data, callback:callback, callbackNewline:callbackNewline}); + return result; + } + + var cbTimeout; + function onWritten() { + if (callbackNewline) { + connection.cb = function(d) { + var newLineIdx = connection.received.indexOf("\n"); + if (newLineIdx>=0) { + var l = connection.received.substr(0,newLineIdx); + connection.received = connection.received.substr(newLineIdx+1); + connection.cb = undefined; + if (cbTimeout) clearTimeout(cbTimeout); + cbTimeout = undefined; + if (callback) + callback(l); + isBusy = false; + handleQueue(); + } + }; + } + // wait for any received data if we have a callback... + var maxTime = 300; // 30 sec - Max time we wait in total, even if getting data + var dataWaitTime = callbackNewline ? 100/*10 sec if waiting for newline*/ : 3/*300ms*/; + var maxDataTime = dataWaitTime; // max time we wait after having received data + cbTimeout = setTimeout(function timeout() { + cbTimeout = undefined; + if (maxTime) maxTime--; + if (maxDataTime) maxDataTime--; + if (connection.hadData) maxDataTime=dataWaitTime; + if (maxDataTime && maxTime) { + cbTimeout = setTimeout(timeout, 100); + } else { + connection.cb = undefined; + if (callback) + callback(connection.received); + isBusy = false; + handleQueue(); + connection.received = ""; + } + connection.hadData = false; + }, 100); + } + + if (!connection.txInProgress) connection.received = ""; + isBusy = true; + connection.write(data, onWritten); + return result + } + + // ---------------------------------------------------------- + + Puck = { + /// Are we writing debug information? 0 is no, 1 is some, 2 is more, 3 is all. + debug : Puck.debug, + /// Should we use flow control? Default is true + flowControl : true, + /// Used internally to write log information - you can replace this with your own function + log : function(level, s) { if (level <= this.debug) console.log(" "+s)}, + /// Called with the current send progress or undefined when done - you can replace this with your own function + writeProgress : Puck.writeProgress, + connect : function(callback) { + setTimeout(callback, 10); + }, + write : write, + eval : function(expr, cb) { + const response = write('\x10Bluetooth.println(JSON.stringify(' + expr + '))\n', true) + .then(function (d) { + try { + return JSON.parse(d); + } catch (e) { + log(1, "Unable to decode " + JSON.stringify(d) + ", got " + e.toString()); + return Promise.reject(d); + } + }); + if (cb) { + return void response.then(cb, (err) => cb(null, err)); + } else { + return response; + } + }, + isConnected : function() { return true; }, + getConnection : function() { return connection; }, + close : function() { + if (connection) + connection.close(); + }, + }; + // no need for header + document.getElementsByTagName("header")[0].style="display:none"; + // force connection attempt automatically + setTimeout(function() { + getInstalledApps(true).catch(err => { + showToast("Device connection failed, "+err,"error"); + }); + }, 100); +} diff --git a/index.html b/index.html index 6c9a21bf8..cb00d87ab 100644 --- a/index.html +++ b/index.html @@ -75,18 +75,23 @@
+ + +
@@ -128,19 +133,35 @@

Utilities

- + + -

+

+

+

Settings

+ + +
+ Send favourite and installed apps to banglejs.com
+ For 'Sort by Installed/Favourited' functionality (see privacy policy) +
`; showPrompt("Which Bangle.js?",html,{},false); + var usageStats = document.getElementById("usage_stats"); + usageStats.addEventListener("change",event=>{ + console.log("Send Usage Stats "+(event.target.checked?"on":"off")); + SETTINGS.sendUsageStats = event.target.checked; + saveSettings(); + }); htmlToArray(document.querySelectorAll(".devicechooser")).forEach(button => { button.addEventListener("click",event => { - let rememberDevice = document.getElementById("remember_device").checked; - + let rememberDevice = !!document.getElementById("remember_device").checked; let button = event.currentTarget; let deviceId = button.getAttribute("deviceid"); hidePrompt(); @@ -155,6 +203,33 @@ window.addEventListener('load', (event) => { }); }); + // Button to install all default apps in one go + document.getElementById("reinstallall").addEventListener("click",event=>{ + var promise = showPrompt("Reinstall","Really re-install all apps?").then(() => { + Comms.reset().then(_ => + getInstalledApps() + ).then(installedapps => { + console.log(installedapps); + var promise = Promise.resolve(); + installedapps.forEach(app => { + if (app.custom) + return console.log(`Ignoring ${app.id} as customised`); + var oldApp = app; + app = appJSON.find(a => a.id==oldApp.id); + if (!app) + return console.log(`Ignoring ${oldApp.id} as not found`); + promise = promise.then(() => updateApp(app, {noReset:true, noFinish:true})); + }); + return promise; + }).then( _ => + Comms.showUploadFinished() + ).catch(err=>{ + Progress.hide({sticky:true}); + showToast("App re-install failed, "+err,"error"); + }); + }); + }); + // Button to install all default apps in one go document.getElementById("installdefault").addEventListener("click",event=>{ getInstalledApps().then(() => { @@ -171,6 +246,26 @@ window.addEventListener('load', (event) => { }); }); + // BLE Compatibility + var selectBLECompat = document.getElementById("settings-ble-compat"); + Puck.increaseMTU = !SETTINGS.bleCompat; + selectBLECompat.checked = !!SETTINGS.bleCompat; + selectBLECompat.addEventListener("change",event=>{ + console.log("BLE compatibility mode "+(event.target.checked?"on":"off")); + SETTINGS.bleCompat = event.target.checked; + Puck.increaseMTU = !SETTINGS.bleCompat; + saveSettings(); + }); + + // Sending usage stats + var selectUsageStats = document.getElementById("settings-usage-stats"); + selectUsageStats.checked = !!SETTINGS.sendUsageStats; + selectUsageStats.addEventListener("change",event=>{ + console.log("Send Usage Stats "+(event.target.checked?"on":"off")); + SETTINGS.sendUsageStats = event.target.checked; + saveSettings(); + }); + // Load language list httpGet("lang/index.json").then(languagesJSON=>{ var languages; diff --git a/modules/.eslintrc.json b/modules/.eslintrc.json new file mode 100644 index 000000000..d656c2555 --- /dev/null +++ b/modules/.eslintrc.json @@ -0,0 +1,163 @@ +{ + "env": { + // TODO: "espruino": false + // TODO: "banglejs": false + }, + "extends": "eslint:recommended", + "globals": { + // Methods and Fields at https://banglejs.com/reference + "Array": "readonly", + "ArrayBuffer": "readonly", + "ArrayBufferView": "readonly", + "Bangle": "readonly", + "BluetoothDevice": "readonly", + "BluetoothRemoteGATTCharacteristic": "readonly", + "BluetoothRemoteGATTServer": "readonly", + "BluetoothRemoteGATTService": "readonly", + "Boolean": "readonly", + "console": "readonly", + "DataView": "readonly", + "Date": "readonly", + "E": "readonly", + "Error": "readonly", + "Flash": "readonly", + "Float32Array": "readonly", + "Float64Array": "readonly", + "fs": "readonly", + "Function": "readonly", + "Graphics": "readonly", + "heatshrink": "readonly", + "I2C": "readonly", + "Int16Array": "readonly", + "Int32Array": "readonly", + "Int8Array": "readonly", + "InternalError": "readonly", + "JSON": "readonly", + "Math": "readonly", + "Modules": "readonly", + "NRF": "readonly", + "Number": "readonly", + "Object": "readonly", + "OneWire": "readonly", + "Pin": "readonly", + "process": "readonly", + "Promise": "readonly", + "ReferenceError": "readonly", + "RegExp": "readonly", + "Serial": "readonly", + "SPI": "readonly", + "Storage": "readonly", + "StorageFile": "readonly", + "String": "readonly", + "SyntaxError": "readonly", + "tensorflow": "readonly", + "TFMicroInterpreter": "readonly", + "TypeError": "readonly", + "Uint16Array": "readonly", + "Uint24Array": "readonly", + "Uint32Array": "readonly", + "Uint8Array": "readonly", + "Uint8ClampedArray": "readonly", + "Waveform": "readonly", + // Methods and Fields at https://banglejs.com/reference + "analogRead": "readonly", + "analogWrite": "readonly", + "arguments": "readonly", + "atob": "readonly", + "Bluetooth": "readonly", + "BTN": "readonly", + "BTN1": "readonly", + "BTN2": "readonly", + "BTN3": "readonly", + "BTN4": "readonly", + "BTN5": "readonly", + "btoa": "readonly", + "changeInterval": "readonly", + "clearInterval": "readonly", + "clearTimeout": "readonly", + "clearWatch": "readonly", + "decodeURIComponent": "readonly", + "digitalPulse": "readonly", + "digitalRead": "readonly", + "digitalWrite": "readonly", + "dump": "readonly", + "echo": "readonly", + "edit": "readonly", + "encodeURIComponent": "readonly", + "eval": "readonly", + "getPinMode": "readonly", + "getSerial": "readonly", + "getTime": "readonly", + "global": "readonly", + "HIGH": "readonly", + "I2C1": "readonly", + "Infinity": "readonly", + "isFinite": "readonly", + "isNaN": "readonly", + "LED": "readonly", + "LED1": "readonly", + "LED2": "readonly", + "load": "readonly", + "LoopbackA": "readonly", + "LoopbackB": "readonly", + "LOW": "readonly", + "NaN": "readonly", + "parseFloat": "readonly", + "parseInt": "readonly", + "peek16": "readonly", + "peek32": "readonly", + "peek8": "readonly", + "pinMode": "readonly", + "poke16": "readonly", + "poke32": "readonly", + "poke8": "readonly", + "print": "readonly", + "require": "readonly", + "reset": "readonly", + "save": "readonly", + "Serial1": "readonly", + "setBusyIndicator": "readonly", + "setInterval": "readonly", + "setSleepIndicator": "readonly", + "setTime": "readonly", + "setTimeout": "readonly", + "setWatch": "readonly", + "shiftOut": "readonly", + "SPI1": "readonly", + "Terminal": "readonly", + "trace": "readonly", + "VIBRATE": "readonly", + // Aliases and not defined at https://banglejs.com/reference + "g": "readonly", + "WIDGETS": "readonly" + }, + "parserOptions": { + "ecmaVersion": 11 + }, + "rules": { + "indent": [ + "off", + 2, + { + "SwitchCase": 1 + } + ], + "no-case-declarations": "off", + "no-constant-condition": "off", + "no-delete-var": "off", + "no-empty": "off", + "no-global-assign": "off", + "no-inner-declarations": "off", + "no-octal": "off", + "no-prototype-builtins": "off", + "no-redeclare": "off", + "no-unreachable": "warn", + "no-cond-assign": "warn", + "no-useless-catch": "warn", + // TODO: "no-undef": "warn", + "no-undef": "off", + "no-unused-vars": "off", + "no-useless-escape": "off", + "no-control-regex" : "off" + } +} diff --git a/modules/ClockFace.js b/modules/ClockFace.js new file mode 100644 index 000000000..b1b007be9 --- /dev/null +++ b/modules/ClockFace.js @@ -0,0 +1,147 @@ +/* +Most of the boilerplate needed to run a clock. +See ClockFace.md for documentation +*/ +function ClockFace(options) { + if ("function"=== typeof options) options = {draw: options}; // simple usage + // some validation, in the hopes of at least catching typos/basic mistakes + Object.keys(options).forEach(k => { + if (![ + "precision", + "init", "draw", "update", + "pause", "resume", "remove", + "up", "down", "upDown", + "settingsFile", + ].includes(k)) throw `Invalid ClockFace option: ${k}`; + }); + if (!options.draw && !options.update) throw "ClockFace needs at least one of draw() or update() functions"; + this.draw = options.draw || (t=> { + options.update.apply(this, [t, {d: true, h: true, m: true, s: true}]); + }); + this.update = options.update || (t => { + g.clearRect(Bangle.appRect); + options.draw.apply(this, [t, {d: true, h: true, m: true, s: true}]); + }); + if (options.precision===1000||options.precision===60000) throw "ClockFace precision is in seconds, not ms"; + this.precision = (options.precision || 60); + if (options.init) this.init = options.init; + if (options.pause) this._pause = options.pause; + if (options.resume) this._resume = options.resume; + if (options.remove) this._remove = options.remove; + if ((options.up || options.down) && options.upDown) throw "ClockFace up/down and upDown cannot be used together"; + if (options.up || options.down) this._upDown = (dir) => { + if (dir<0 && options.up) options.up.apply(this); + if (dir>0 && options.down) options.down.apply(this); + }; + if (options.upDown) this._upDown = options.upDown; + + if (options.settingsFile) { + const settings = (require("Storage").readJSON(options.settingsFile, true) || {}); + Object.keys(settings).forEach(k => { + this[k] = settings[k]; + }); + } + // these default to true + ["showDate", "loadWidgets"].forEach(k => { + if (this[k]===undefined) this[k] = true; + }); + let s = require("Storage").readJSON("setting.json",1)||{}; + if ((global.__FILE__===undefined || global.__FILE__===s.clock) + && s.clockHasWidgets!==this.loadWidgets) { + // save whether we can Fast Load + s.clockHasWidgets = this.loadWidgets; + require("Storage").writeJSON("setting.json", s); + } + // use global 24/12-hour setting if not set by clock-settings + if (!('is12Hour' in this)) this.is12Hour = !!(s["12hour"]); +} + +ClockFace.prototype.tick = function() { + "ram" + const time = new Date(); + const now = { + d: `${time.getFullYear()}-${time.getMonth()}-${time.getDate()}`, + h: time.getHours(), + m: time.getMinutes(), + s: time.getSeconds(), + }; + if (!this._last) { + g.clear(true); + if (global.WIDGETS) Bangle.drawWidgets(); + g.reset(); + this.draw.apply(this, [time, {d: true, h: true, m: true, s: true}]); + } else { + let c = {d: false, h: false, m: false, s: false}; // changed + if (now.d!==this._last.d) c.d = c.h = c.m = c.s = true; + else if (now.h!==this._last.h) c.h = c.m = c.s = true; + else if (now.m!==this._last.m) c.m = c.s = true; + else if (now.s!==this._last.s) c.s = true; + g.reset(); + this.update.apply(this, [time, c]); + } + this._last = now; + if (this.paused) return; // called redraw() while still paused + // figure out timeout: if e.g. precision=60s, update at the start of a new minute + const interval = this.precision*1000; + this._timeout = setTimeout(() => this.tick(), interval-(Date.now()%interval)); +}; + +ClockFace.prototype.start = function() { + /* Some widgets want to know if we're in a clock or not (like chrono, widget clock, etc). Normally + .CLOCK is set by Bangle.setUI('clock') but we want to load widgets so we can check appRect and *then* + call setUI. see #1864 */ + Bangle.CLOCK = 1; + if (this.loadWidgets) Bangle.loadWidgets(); + if (this.init) this.init.apply(this); + const uiRemove = this._remove ? () => this.remove() : undefined; + if (this._upDown) { + Bangle.setUI({ + mode: "clockupdown", + remove: uiRemove, + }, d => this._upDown.apply(this, [d])); + } else { + Bangle.setUI({ + mode: "clock", + remove: uiRemove, + }); + } + delete this._last; + this.paused = false; + this.tick(); + + this._onLcd = on => { + if (on) this.resume(); + else this.pause(); + }; + Bangle.on("lcdPower", this._onLcd); +}; + +ClockFace.prototype.pause = function() { + if (!this._timeout) return; // already paused + clearTimeout(this._timeout); + delete this._timeout; + this.paused = true; // apps might want to check this + if (this._pause) this._pause.apply(this); +}; +ClockFace.prototype.resume = function() { + if (this._timeout) return; // not paused + delete this._last; + this.paused = false; + if (this._resume) this._resume.apply(this); + this.tick(); +}; +ClockFace.prototype.remove = function() { + if (this._timeout) clearTimeout(this._timeout); + Bangle.removeListener("lcdPower", this._onLcd); + if (this._remove) this._remove.apply(this); +}; + +/** + * Force a complete redraw + */ +ClockFace.prototype.redraw = function() { + delete this._last; + this.tick(); +}; + +exports = ClockFace; diff --git a/modules/ClockFace.md b/modules/ClockFace.md new file mode 100644 index 000000000..f123d38c0 --- /dev/null +++ b/modules/ClockFace.md @@ -0,0 +1,220 @@ +ClockFace +========= + +This module handles most of the tasks needed to set up a clock, so you can +concentrate on drawing the time. + +Example +------- +Tthe [tutorial clock](https://www.espruino.com/Bangle.js+Clock) converted to use +this module: + +```js + +// Load fonts +require("Font7x11Numeric7Seg").add(Graphics); +// position on screen +const X = 160, Y = 140; + +var ClockFace = require("ClockFace"); +var clock = new ClockFace({ + precision: 1, // update every second + draw: function(d) { + // work out how to display the current time + var h = d.getHours(), m = d.getMinutes(); + var time = (" "+h).substr(-2)+":"+("0"+m).substr(-2); + // draw the current time (4x size 7 segment) + g.setFont("7x11Numeric7Seg", 4); + g.setFontAlign(1, 1); // align right bottom + g.drawString(time, X, Y, true /*clear background*/); + // draw the seconds (2x size 7 segment) + g.setFont("7x11Numeric7Seg", 2); + g.drawString(("0"+d.getSeconds()).substr(-2), X+30, Y, true /*clear background*/); + // draw the date, in a normal font + g.setFont("6x8"); + g.setFontAlign(0, 1); // align center bottom + // pad the date - this clears the background if the date were to change length + var dateStr = " "+require("locale").date(d)+" "; + g.drawString(dateStr, g.getWidth()/2, Y+15, true /*clear background*/); + } +}); +clock.start(); + +``` + + + +Complete Usage +-------------- + +```js + +var ClockFace = require("ClockFace"); +var clock = new ClockFace({ + precision: 1, // optional, defaults to 60: how often to call update(), in seconds + init: function() { // optional + // called only once before starting the clock, but after setting up the + // screen/widgets, so you can use Bangle.appRect + }, + draw: function(time, changed) { // at least draw or update is required + // (re)draw entire clockface, time is a Date object + // `changed` is the same format as for update() below, but always all true + // You can use `this.is12Hour` to test if the 'Time Format' setting is set to "12h" or "24h" + }, + // The difference between draw() and update() is that the screen is cleared + // before draw() is called, so it needs to always redraw the entire clock + update: function(time, changed) { // at least draw or update is required + // redraw date/time, time is a Date object + // if you want, you can only redraw the changed parts: + if (changed.d) // redraw date (changed.h/m/s will also all be true) + if (changed.h) // redraw hours + if (changed.m) // redraw minutes + if (changed.s) // redraw seconds + }, + pause: function() { // optional, called when the screen turns off + // for example: turn off GPS/compass if the watch used it + }, + resume: function() { // optional, called when the screen turns on + // for example: turn GPS/compass back on + }, + remove: function() { // optional, used for Fast Loading + // for example: remove listeners + // Fast Loading will not be used unless this function is present, + // if there is nothing to clean up, you can just leave it empty. + }, + up: function() { // optional, up handler + }, + down: function() { // optional, down handler + }, + upDown: function(dir) { // optional, combined up/down handler + if (dir === -1) // Up + else // (dir === 1): Down + }, + settingsFile: 'appid.settings.json', // optional, values from file will be applied to `this` + }); +clock.start(); + +``` + + +Simple Usage +------------ +Basic clocks can pass just a function to redraw the entire screen every minute: + +```js + +var ClockFace = require("ClockFace"); +var clock = new ClockFace(function(time) { + // draw the current time at the center of the screen + g.setFont("Vector:50").setFontAlign(0, 0) + .drawString( + require("locale").time(time, true), + Bangle.appRect.w/2, Bangle.appRect.h/2 + ); +}); +clock.start(); + +``` + + +SettingsFile +------------ +If you use the `settingsFile` option, values from that file are loaded and set +directly on the clock. + +For example: + +```json +// example.settings.json: +{ + "showDate": false, + "foo": 123 +} +``` +```js + var ClockFace = require("ClockFace"); + var clock = new ClockFace({ + draw: function(){/*...*/}, + settingsFile: "example.settings.json", + }); + // now + clock.showDate === false; + clock.foo === 123; + clock.loadWidgets === true; // default when not in settings file + clock.is12Hour === ??; // not in settings file: uses global setting + clock.start(); + +``` + +Properties +---------- +The following properties are automatically set on the clock: +* `is12Hour`: `true` if the "Time Format" setting is set to "12h", `false` for "24h". +* `paused`: `true` while the clock is paused. (You don't need to check this inside your `draw()` code) +* `showDate`: `true` (if not overridden through the settings file.) +* `loadWidgets`: `true` (if not overridden through the settings file.) + If set to `false` before calling `start()`, the clock won't call `Bangle.loadWidgets();` for you. + Best is to add a setting for this, but if you never want to load widgets, you could do this: + ```js + var ClockFace = require("ClockFace"); + var clock = new ClockFace({draw: function(){/*...*/}}); + clock.loadWidgets = false; // prevent loading of widgets + clock.start(); + ``` + +Inside the `draw()`/`update()` function you can access these using `this`: + +```js + +var ClockFace = require("ClockFace"); +var clock = new ClockFace({ + draw: function (time) { + if (this.is12Hour) // draw 12h time + else // use 24h format + } +}); +clock.start(); + +Bangle.on('step', function(steps) { + if (clock.paused === false) // draw step count +}); + +``` + + +ClockFace_menu +============== +If your clock comes with a settings menu, you can use this library to easily add +some common options: + +```js + +let settings = require("Storage").readJSON(".settings.json", true)||{}; +function save(key, value) { + settings[key] = value; + require("Storage").writeJSON(".settings.json", settings); +} + +let menu = { + "": {"title": /*LANG*/" Settings"}, +}; +require("ClockFace_menu").addItems(menu, save, { + showDate: settings.showDate, + loadWidgets: settings.loadWidgets, +}); +E.showMenu(menu); + +``` + +Or even simpler, if you just want to use a basic settings file: +```js +let menu = { + "": {"title": /*LANG*/" Settings"}, + /*LANG*/"< Back": back, +}; +require("ClockFace_menu").addSettingsFile(menu, ".settings.json", [ + "showDate", "loadWidgets", "powerSave", +]); +E.showMenu(menu); + +``` \ No newline at end of file diff --git a/modules/ClockFace_menu.js b/modules/ClockFace_menu.js new file mode 100644 index 000000000..a1dd76fee --- /dev/null +++ b/modules/ClockFace_menu.js @@ -0,0 +1,52 @@ +/** + * Add setting items to a menu + * + * @param {object} menu Menu to add items to + * @param {function} callback Callback when value changes + * @param {object} items Menu items to add, with their current value + */ +exports.addItems = function(menu, callback, items) { + Object.keys(items).forEach(key => { + let value = items[key]; + const label = { + showDate:/*LANG*/"Show date", + loadWidgets:/*LANG*/"Load widgets", + powerSave:/*LANG*/"Power saving", + }[key]; + switch(key) { + // boolean options which default to true + case "showDate": + case "loadWidgets": + if (value===undefined) value = true; + // fall through + case "powerSave": + // same for all boolean options: + menu[label] = { + value: !!value, + onchange: v => callback(key, v), + }; + } + }); +}; + +/** + * Create a basic settings menu for app, reading/writing to settings file + * + * @param {object} menu Menu to add settings to + * @param {string} settingsFile File to read/write settings to/from + * @param {string[]} items List of settings to add + */ +exports.addSettingsFile = function(menu, settingsFile, items) { + let s = require("Storage").readJSON(settingsFile, true) || {}; + + function save(key, value) { + s[key] = value; + require("Storage").writeJSON(settingsFile, s); + } + + let toAdd = {}; + items.forEach(function(key) { + toAdd[key] = s[key]; + }); + exports.addItems(menu, save, toAdd); +}; \ No newline at end of file diff --git a/modules/Layout.js b/modules/Layout.js index 134cc8103..f8e27b66b 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -1,82 +1,11 @@ -/* Copyright (c) 2022 Bangle.js contibutors. See the file LICENSE for copying permission. */ -/* +/* Copyright (c) 2022 Bangle.js contributors. See the file LICENSE for copying permission. */ -Take a look at README.md for hints on developing with this library. +// See Layout.md for documentation -Usage: +/* Minify to 'Layout.min.js' by: -``` -var Layout = require("Layout"); -var layout = new Layout( layoutObject, options ) -layout.render(optionalObject); -``` - -For example: - -``` -var Layout = require("Layout"); -var layout = new Layout( { - type:"v", c: [ - {type:"txt", font:"20%", label:"12:00" }, - {type:"txt", font:"6x8", label:"The Date" } - ] -}); -g.clear(); -layout.render(); -``` - - -layoutObject has: - -* A `type` field of: - * `undefined` - blank, can be used for padding - * `"txt"` - a text label, with value `label` and `r` for text rotation. 'font' is required - * `"btn"` - a button, with value `label` and callback `cb` - optional `src` specifies an image (like img) in which case label is ignored - * `"img"` - an image where `src` is an image, or a function which is called to return an image to draw. - optional `scale` specifies if image should be scaled up or not - * `"custom"` - a custom block where `render(layoutObj)` is called to render - * `"h"` - Horizontal layout, `c` is an array of more `layoutObject` - * `"v"` - Vertical layout, `c` is an array of more `layoutObject` -* A `id` field. If specified the object is added with this name to the - returned `layout` object, so can be referenced as `layout.foo` -* A `font` field, eg `6x8` or `30%` to use a percentage of screen height -* A `wrap` field to enable line wrapping. Requires some combination of `width`/`height` - and `fillx`/`filly` to be set. Not compatible with text rotation. -* A `col` field, eg `#f00` for red -* A `bgCol` field for background color (will automatically fill on render) -* A `halign` field to set horizontal alignment WITHIN a `v` container. `-1`=left, `1`=right, `0`=center -* A `valign` field to set vertical alignment WITHIN a `h` container. `-1`=top, `1`=bottom, `0`=center -* A `pad` integer field to set pixels padding -* A `fillx` int to choose if the object should fill available space in x. 0=no, 1=yes, 2=2x more space -* A `filly` int to choose if the object should fill available space in y. 0=no, 1=yes, 2=2x more space -* `width` and `height` fields to optionally specify minimum size - -options is an object containing: - -* `lazy` - a boolean specifying whether to enable automatic lazy rendering -* `btns` - array of objects containing: - * `label` - the text on the button - * `cb` - a callback function - * `cbl` - a callback function for long presses - -If automatic lazy rendering is enabled, calls to `layout.render()` will attempt to automatically -determine what objects have changed or moved, clear their previous locations, and re-render just those objects. - -Once `layout.update()` is called, the following fields are added -to each object: - -* `x` and `y` for the top left position -* `w` and `h` for the width and height -* `_w` and `_h` for the **minimum** width and height - - -Other functions: - -* `layout.update()` - update positions of everything if contents have changed -* `layout.debug(obj)` - draw outlines for objects on screen -* `layout.clear(obj)` - clear the given object (you can also just specify `bgCol` to clear before each render) -* `layout.forgetLazyState()` - if lazy rendering is enabled, makes the next call to `render()` perform a full re-render + * checking out: https://github.com/espruino/EspruinoDocs + * run: ../EspruinoDocs/bin/minify.js modules/Layout.js modules/Layout.min.js */ @@ -84,17 +13,17 @@ Other functions: function Layout(layout, options) { this._l = this.l = layout; // Do we have >1 physical buttons? - this.physBtns = (process.env.HWVERSION==2) ? 1 : 3; - options = options || {}; - this.lazy = options.lazy || false; - var btnList; - Bangle.setUI(); // remove all existing input handlers + this.options = options || {}; + this.lazy = this.options.lazy || false; + this.physBtns = 1; + let btnList; if (process.env.HWVERSION!=2) { + this.physBtns = 3; // no touchscreen, find any buttons in 'layout' btnList = []; - function btnRecurser(l) { + function btnRecurser(l) {"ram"; if (l.type=="btn") btnList.push(l); if (l.c) l.c.forEach(btnRecurser); } @@ -104,46 +33,19 @@ function Layout(layout, options) { this.physBtns = 0; this.buttons = btnList; this.selectedButton = -1; - Bangle.setUI("updown", dir=>{ - var s = this.selectedButton, l=this.buttons.length; - if (dir===undefined && this.buttons[s]) - return this.buttons[s].cb(); - if (this.buttons[s]) { - delete this.buttons[s].selected; - this.render(this.buttons[s]); - } - s = (s+l+dir) % l; - if (this.buttons[s]) { - this.buttons[s].selected = 1; - this.render(this.buttons[s]); - } - this.selectedButton = s; - }); } } - if (options.btns) { - var buttons = options.btns; - this.b = buttons; + if (this.options.btns) { + var buttons = this.options.btns; if (this.physBtns >= buttons.length) { - // Handler for button watch events - function pressHandler(btn,e) { - if (e.time-e.lastTime > 0.75 && this.b[btn].cbl) - this.b[btn].cbl(e); - else - if (this.b[btn].cb) this.b[btn].cb(e); - } // enough physical buttons + this.b = buttons; let btnHeight = Math.floor(Bangle.appRect.h / this.physBtns); - if (Bangle.btnWatch) Bangle.btnWatch.forEach(clearWatch); - Bangle.btnWatch = []; if (this.physBtns > 2 && buttons.length==1) buttons.unshift({label:""}); // pad so if we have a button in the middle while (this.physBtns > buttons.length) buttons.push({label:""}); - if (buttons[0]) Bangle.btnWatch.push(setWatch(pressHandler.bind(this,0), BTN1, {repeat:true,edge:-1})); - if (buttons[1]) Bangle.btnWatch.push(setWatch(pressHandler.bind(this,1), BTN2, {repeat:true,edge:-1})); - if (buttons[2]) Bangle.btnWatch.push(setWatch(pressHandler.bind(this,2), BTN3, {repeat:true,edge:-1})); this._l.width = g.getWidth()-8; // text width this._l = {type:"h", filly:1, c: [ this._l, @@ -160,9 +62,62 @@ function Layout(layout, options) { if (btnList) btnList.push.apply(btnList, this._l.c[1].c); } } - if (process.env.HWVERSION==2) { + // Link in all buttons/touchscreen/etc + this.setUI(); + // recurse over layout doing some fixing up if needed + var ll = this; + function recurser(l) {"ram"; + // add IDs + if (l.id) ll[l.id] = l; + // fix type up + if (!l.type) l.type=""; + if (l.c) l.c.forEach(recurser); + } + recurser(this._l); + this.updateNeeded = true; +} - // Handler for touch events +Layout.prototype.setUI = function() { + Bangle.setUI(); // remove all existing input handlers + + let uiSet; + if (this.buttons) { + // multiple buttons so we'll jus use back/next/select + Bangle.setUI({mode:"updown", back:this.options.back, remove:this.options.remove}, dir=>{ + var s = this.selectedButton, l=this.buttons.length; + if (dir===undefined && this.buttons[s]) + return this.buttons[s].cb(); + if (this.buttons[s]) { + delete this.buttons[s].selected; + this.render(this.buttons[s]); + } + s = (s+l+dir) % l; + if (this.buttons[s]) { + this.buttons[s].selected = 1; + this.render(this.buttons[s]); + } + this.selectedButton = s; + }); + uiSet = true; + } + if ((this.options.back || this.options.remove) && !uiSet) Bangle.setUI({mode: "custom", back: this.options.back, remove: this.options.remove}); + // physical buttons -> actual applications + if (this.b) { + // Handler for button watch events + function pressHandler(btn,e) { + if (e.time-e.lastTime > 0.75 && this.b[btn].cbl) + this.b[btn].cbl(e); + else + if (this.b[btn].cb) this.b[btn].cb(e); + } + if (Bangle.btnWatches) Bangle.btnWatches.forEach(clearWatch); + Bangle.btnWatches = []; + if (this.b[0]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,0), BTN1, {repeat:true,edge:-1})); + if (this.b[1]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,1), BTN2, {repeat:true,edge:-1})); + if (this.b[2]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,2), BTN3, {repeat:true,edge:-1})); + } + // Handle touch events on new Bangle.js + if (process.env.HWVERSION==2) { function touchHandler(l,e) { if (l.cb && e.x>=l.x && e.y>=l.y && e.x<=l.x+l.w && e.y<=l.y+l.h) { if (e.type==2 && l.cbl) l.cbl(e); else if (l.cb) l.cb(e); @@ -172,38 +127,8 @@ function Layout(layout, options) { Bangle.touchHandler = (_,e)=>touchHandler(this._l,e); Bangle.on('touch',Bangle.touchHandler); } - - // recurse over layout doing some fixing up if needed - var ll = this; - function recurser(l) { - // add IDs - if (l.id) ll[l.id] = l; - // fix type up - if (!l.type) l.type=""; - // FIXME ':'/fsz not needed in new firmwares - Font:12 is handled internally - // fix fonts for pre-2v11 firmware - if (l.font && l.font.includes(":")) { - var f = l.font.split(":"); - l.font = f[0]; - l.fsz = f[1]; - } - if (l.c) l.c.forEach(recurser); - } - recurser(this._l); - this.updateNeeded = true; } -Layout.prototype.remove = function (l) { - if (Bangle.btnWatch) { - Bangle.btnWatch.forEach(clearWatch); - delete Bangle.btnWatch; - } - if (Bangle.touchHandler) { - Bangle.removeListener("touch",Bangle.touchHandler); - delete Bangle.touchHandler; - } -}; - function prepareLazyRender(l, rectsToClear, drawList, rects, parentBg) { var bgCol = l.bgCol == null ? parentBg : g.toColor(l.bgCol); if (bgCol != parentBg || l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") { @@ -230,26 +155,25 @@ Layout.prototype.render = function (l) { if (!l) l = this._l; if (this.updateNeeded) this.update(); - function render(l) {"ram" - g.reset(); - if (l.col) g.setColor(l.col); - if (l.bgCol!==undefined) g.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1); + var gfx=g; // define locally, because this is faster + function render(l) {"ram"; + gfx.reset(); + if (l.col!==undefined) gfx.setColor(l.col); + if (l.bgCol!==undefined) gfx.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1); cb[l.type](l); } var cb = { "":function(){}, - "txt":function(l){ + "txt":function(l){"ram"; if (l.wrap) { - g.setFont(l.font,l.fsz).setFontAlign(0,-1); - var lines = g.wrapString(l.label, l.w); - var y = l.y+((l.h-g.getFontHeight()*lines.length)>>1); - // TODO: on 2v11 we can just render in a single drawString call - lines.forEach((line, i) => g.drawString(line, l.x+(l.w>>1), y+g.getFontHeight()*i)); + var lines = gfx.setFont(l.font).setFontAlign(0,-1).wrapString(l.label, l.w); + var y = l.y+((l.h-gfx.getFontHeight()*lines.length)>>1); + gfx.drawString(lines.join("\n"), l.x+(l.w>>1), y); } else { - g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1)); + gfx.setFont(l.font).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1)); } - }, "btn":function(l){ + }, "btn":function(l){"ram"; var x = l.x+(0|l.pad), y = l.y+(0|l.pad), w = l.w-(l.pad<<1), h = l.h-(l.pad<<1); var poly = [ @@ -262,17 +186,26 @@ Layout.prototype.render = function (l) { x+4,y+h-1, x,y+h-5, x,y+4 - ], bg = l.selected?g.theme.bgH:g.theme.bg2; - g.setColor(bg).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg2).drawPoly(poly); - if (l.col) g.setColor(l.col); - if (l.src) g.setBgColor(bg).drawImage("function"==typeof l.src?l.src():l.src, l.x + 10 + (0|l.pad), l.y + 8 + (0|l.pad)); - else g.setFont("6x8",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); - }, "img":function(l){ - g.drawImage("function"==typeof l.src?l.src():l.src, l.x + (0|l.pad), l.y + (0|l.pad), l.scale?{scale:l.scale}:undefined); - }, "custom":function(l){ - l.render(l); - },"h":function(l) { l.c.forEach(render); }, - "v":function(l) { l.c.forEach(render); } + ], bg = l.selected?gfx.theme.bgH:gfx.theme.bg2; + gfx.setColor(bg).fillPoly(poly).setColor(l.selected ? gfx.theme.fgH : gfx.theme.fg2).drawPoly(poly); + if (l.col!==undefined) gfx.setColor(l.col); + if (l.src) gfx.setBgColor(bg).drawImage( + "function"==typeof l.src?l.src():l.src, + l.x + l.w/2, + l.y + l.h/2, + {scale: l.scale||undefined, rotate: Math.PI*0.5*(l.r||0)} + ); + else gfx.setFont(l.font||"6x8:2").setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); + }, "img":function(l){"ram"; + gfx.drawImage( + "function"==typeof l.src?l.src():l.src, + l.x + l.w/2, + l.y + l.h/2, + {scale: l.scale||undefined, rotate: Math.PI*0.5*(l.r||0)} + ); + }, "custom":function(l){"ram"; l.render(l); + }, "h":function(l) { "ram"; l.c.forEach(render); + }, "v":function(l) { "ram"; l.c.forEach(render); } }; if (this.lazy) { @@ -285,7 +218,7 @@ Layout.prototype.render = function (l) { prepareLazyRender(l, rectsToClear, drawList, this.rects, null); for (var h in rectsToClear) delete this.rects[h]; var clearList = Object.keys(rectsToClear).map(k=>rectsToClear[k]).reverse(); // Rects are cleared in reverse order so that the original bg color is restored - for (var r of clearList) g.setBgColor(r.bg).clearRect.apply(g, r); + for (var r of clearList) gfx.setBgColor(r.bg).clearRect.apply(g, r); drawList.forEach(render); } else { // non-lazy render(l); @@ -298,9 +231,8 @@ Layout.prototype.forgetLazyState = function () { Layout.prototype.layout = function (l) { // l = current layout element - // exw,exh = extra width/height available - switch (l.type) { - case "h": { + var cb = { + "h" : function(l) {"ram"; var acc_w = l.x + (0|l.pad); var accfillx = 0; var fillx = l.c && l.c.reduce((a,l)=>a+(0|l.fillx),0); @@ -314,11 +246,10 @@ Layout.prototype.layout = function (l) { c.w = 0|(x - c.x); c.h = 0|(c.filly ? l.h - (l.pad<<1) : c._h); c.y = 0|(l.y + (0|l.pad) + ((1+(0|c.valign))*(l.h-(l.pad<<1)-c.h)>>1)); - if (c.c) this.layout(c); + if (c.c) cb[c.type](c); }); - break; - } - case "v": { + }, + "v" : function(l) {"ram"; var acc_h = l.y + (0|l.pad); var accfilly = 0; var filly = l.c && l.c.reduce((a,l)=>a+(0|l.filly),0); @@ -332,11 +263,11 @@ Layout.prototype.layout = function (l) { c.h = 0|(y - c.y); c.w = 0|(c.fillx ? l.w - (l.pad<<1) : c._w); c.x = 0|(l.x + (0|l.pad) + ((1+(0|c.halign))*(l.w-(l.pad<<1)-c.w)>>1)); - if (c.c) this.layout(c); + if (c.c) cb[c.type](c); }); - break; } - } + }; + cb[l.type](l); }; Layout.prototype.debug = function(l,c) { if (!l) l = this._l; @@ -349,48 +280,51 @@ Layout.prototype.debug = function(l,c) { }; Layout.prototype.update = function() { delete this.updateNeeded; + var gfx=g; // define locally, because this is faster // update sizes - function updateMin(l) {"ram" + function updateMin(l) {"ram"; cb[l.type](l); if (l.r&1) { // rotation var t = l._w;l._w=l._h;l._h=t; } - l._w = 0|Math.max(l._w + (l.pad<<1), 0|l.width); - l._h = 0|Math.max(l._h + (l.pad<<1), 0|l.height); + l._w = Math.max(l._w + (l.pad<<1), 0|l.width); + l._h = Math.max(l._h + (l.pad<<1), 0|l.height); } var cb = { - "txt" : function(l) { + "txt" : function(l) {"ram"; if (l.font.endsWith("%")) - l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100); + l.font = "Vector"+Math.round(gfx.getHeight()*l.font.slice(0,-1)/100); if (l.wrap) { l._h = l._w = 0; } else { - var m = g.setFont(l.font,l.fsz).stringMetrics(l.label); + var m = g.setFont(l.font).stringMetrics(l.label); l._w = m.width; l._h = m.height; } - }, "btn": function(l) { - var m = l.src?g.imageMetrics("function"==typeof l.src?l.src():l.src):g.setFont("6x8",2).stringMetrics(l.label); + }, "btn": function(l) {"ram"; + if (l.font && l.font.endsWith("%")) + l.font = "Vector"+Math.round(gfx.getHeight()*l.font.slice(0,-1)/100); + var m = l.src?gfx.imageMetrics("function"==typeof l.src?l.src():l.src):gfx.setFont(l.font||"6x8:2").stringMetrics(l.label); l._h = 16 + m.height; l._w = 20 + m.width; - }, "img": function(l) { - var m = g.imageMetrics("function"==typeof l.src?l.src():l.src), s=l.scale||1; // get width and height out of image + }, "img": function(l) {"ram"; + var m = gfx.imageMetrics("function"==typeof l.src?l.src():l.src), s=l.scale||1; // get width and height out of image l._w = m.width*s; l._h = m.height*s; - }, "": function(l) { + }, "": function(l) {"ram"; // size should already be set up in width/height l._w = 0; l._h = 0; - }, "custom": function(l) { + }, "custom": function(l) {"ram"; // size should already be set up in width/height l._w = 0; l._h = 0; - }, "h": function(l) { + }, "h": function(l) {"ram"; l.c.forEach(updateMin); l._h = l.c.reduce((a,b)=>Math.max(a,b._h),0); l._w = l.c.reduce((a,b)=>a+b._w,0); if (l.fillx == null && l.c.some(c=>c.fillx)) l.fillx = 1; if (l.filly == null && l.c.some(c=>c.filly)) l.filly = 1; - }, "v": function(l) { + }, "v": function(l) {"ram"; l.c.forEach(updateMin); l._h = l.c.reduce((a,b)=>a+b._h,0); l._w = l.c.reduce((a,b)=>Math.max(a,b._w),0); @@ -401,6 +335,7 @@ Layout.prototype.update = function() { var l = this._l; updateMin(l); + delete cb; if (l.fillx || l.filly) { // fill all l.w = Bangle.appRect.w; l.h = Bangle.appRect.h; diff --git a/modules/Layout.md b/modules/Layout.md new file mode 100644 index 000000000..67db21858 --- /dev/null +++ b/modules/Layout.md @@ -0,0 +1,82 @@ +Bangle.js Layout Library +======================== + +> Take a look at README.md for hints on developing with this library. + +Usage +----- + +```JS +var Layout = require("Layout"); +var layout = new Layout(layoutObject, options) + +layout.render(optionalObject); +``` + +For example: + +```JS +var Layout = require("Layout"); +var layout = new Layout({ + type:"v", + c: [ + { type: "txt", font: "20%", label: "12:00" }, + { type: "txt", font: "6x8", label: "The Date" } + ] +}); + +g.clear(); + +layout.render(); +``` + +`layoutObject` has: + +- A `type` field of: + - `undefined` - blank, can be used for padding + - `"txt"` - a text label, with value `label`. `font` is required + - `"btn"` - a button, with value `label` and callback `cb`. Optional `src` specifies an image (like img) in which case label is ignored. Default font is `6x8`, scale 2. This can be overridden with the `font` or `scale` fields. + - `"img"` - an image where `src` is an image, or a function which is called to return an image to draw + - `"custom"` - a custom block where `render(layoutObj)` is called to render + - `"h"` - Horizontal layout, `c` is an array of more `layoutObject` + - `"v"` - Vertical layout, `c` is an array of more `layoutObject` +- A `id` field. If specified the object is added with this name to the returned `layout` object, so can be referenced as `layout.foo` +- A `font` field, eg `6x8` or `30%` to use a percentage of screen height. Set scale with :, e.g. `6x8:2`. +- A `scale` field, eg `2` to set scale of an image +- A `r` field to set rotation of text or images (0: 0°, 1: 90°, 2: 180°, 3: 270°). +- A `wrap` field to enable line wrapping. Requires some combination of `width`/`height` and `fillx`/`filly` to be set. Not compatible with text rotation. +- A `col` field, eg `#f00` for red +- A `bgCol` field for background color (will automatically fill on render) +- A `halign` field to set horizontal alignment WITHIN a `v` container. `-1`=left, `1`=right, `0`=center +- A `valign` field to set vertical alignment WITHIN a `h` container. `-1`=top, `1`=bottom, `0`=center +- A `pad` integer field to set pixels padding +- A `fillx` int to choose if the object should fill available space in x. 0=no, 1=yes, 2=2x more space +- A `filly` int to choose if the object should fill available space in y. 0=no, 1=yes, 2=2x more space +- `width` and `height` fields to optionally specify minimum size options is an object containing: +- `lazy` - a boolean specifying whether to enable automatic lazy rendering +- `btns` - array of objects containing: + - `label` - the text on the button + - `cb` - a callback function + - `cbl` - a callback function for long presses +- `back` - a callback function, passed as `back` into Bangle.setUI (which usually adds an icon in the top left) +- `remove` - a cleanup function, passed as `remove` into Bangle.setUI (allows to cleanly remove the app from memory) + +If automatic lazy rendering is enabled, calls to `layout.render()` will attempt to automatically determine what objects have changed or moved, clear their previous locations, and re-render just those objects. + +Once `layout.update()` is called, the following fields are added to each object: + +- `x` and `y` for the top left position +- `w` and `h` for the width and height +- `_w` and `_h` for the **minimum** width and height + +Other functions: + +- `layout.update()` - update positions of everything if contents have changed +- `layout.debug(obj)` - draw outlines for objects on screen +- `layout.clear(obj)` - clear the given object (you can also just specify `bgCol` to clear before each render) +- `layout.forgetLazyState()` - if lazy rendering is enabled, makes the next call to `render()` perform a full re-render + +Links +----- + +- [Official tutorial](https://www.espruino.com/Bangle.js+Layout) diff --git a/modules/Layout.min.js b/modules/Layout.min.js new file mode 100644 index 000000000..19e60f7a0 --- /dev/null +++ b/modules/Layout.min.js @@ -0,0 +1,14 @@ +function p(d,h){function b(e){"ram";e.id&&(a[e.id]=e);e.type||(e.type="");e.c&&e.c.forEach(b)}this._l=this.l=d;this.options=h||{};this.lazy=this.options.lazy||!1;this.physBtns=1;let f;if(2!=process.env.HWVERSION){this.physBtns=3;f=[];function e(l){"ram";"btn"==l.type&&f.push(l);l.c&&l.c.forEach(e)}e(d);f.length&&(this.physBtns=0,this.buttons=f,this.selectedButton=-1)}if(this.options.btns)if(d=this.options.btns,this.physBtns>=d.length){this.b=d;let e=Math.floor(Bangle.appRect.h/this.physBtns); +for(2d.length;)d.push({label:""});this._l.width=g.getWidth()-8;this._l={type:"h",filly:1,c:[this._l,{type:"v",pad:1,filly:1,c:d.map(l=>(l.type="txt",l.font="6x8",l.height=e,l.r=1,l))}]}}else this._l.width=g.getWidth()-32,this._l={type:"h",c:[this._l,{type:"v",c:d.map(e=>(e.type="btn",e.filly=1,e.width=32,e.r=1,e))}]},f&&f.push.apply(f,this._l.c[1].c);this.setUI();var a=this;b(this._l);this.updateNeeded=!0}function t(d,h,b,f,a){var e= +null==d.bgCol?a:g.toColor(d.bgCol);if(e!=a||"txt"==d.type||"btn"==d.type||"img"==d.type||"custom"==d.type){var l=d.c;delete d.c;var k="H"+E.CRC32(E.toJS(d));l&&(d.c=l);delete h[k]||((f[k]=[d.x,d.y,d.x+d.w-1,d.y+d.h-1]).bg=null==a?g.theme.bg:a,b&&(b.push(d),b=null))}if(d.c)for(var c of d.c)t(c,h,b,f,e)}p.prototype.setUI=function(){Bangle.setUI();let d;this.buttons&&(Bangle.setUI({mode:"updown",back:this.options.back,remove:this.options.remove},h=>{var b=this.selectedButton,f=this.buttons.length;if(void 0=== +h&&this.buttons[b])return this.buttons[b].cb();this.buttons[b]&&(delete this.buttons[b].selected,this.render(this.buttons[b]));b=(b+f+h)%f;this.buttons[b]&&(this.buttons[b].selected=1,this.render(this.buttons[b]));this.selectedButton=b}),d=!0);!this.options.back&&!this.options.remove||d||Bangle.setUI({mode:"custom",back:this.options.back,remove:this.options.remove});if(this.b){function h(b,f){.75=b.x&&f.y>=b.y&&f.x<=b.x+b.w&&f.y<=b.y+b.h&&(2==f.type&&b.cbl?b.cbl(f):b.cb&&b.cb(f));b.c&&b.c.forEach(a=>h(a,f))}Bangle.touchHandler=(b,f)=>h(this._l,f);Bangle.on("touch", +Bangle.touchHandler)}};p.prototype.render=function(d){function h(c){"ram";b.reset();void 0!==c.col&&b.setColor(c.col);void 0!==c.bgCol&&b.setBgColor(c.bgCol).clearRect(c.x,c.y,c.x+c.w-1,c.y+c.h-1);f[c.type](c)}d||(d=this._l);this.updateNeeded&&this.update();var b=g,f={"":function(){},txt:function(c){"ram";if(c.wrap){var m=b.setFont(c.font).setFontAlign(0,-1).wrapString(c.label,c.w),n=c.y+(c.h-b.getFontHeight()*m.length>>1);b.drawString(m.join("\n"),c.x+(c.w>>1),n)}else b.setFont(c.font).setFontAlign(0, +0,c.r).drawString(c.label,c.x+(c.w>>1),c.y+(c.h>>1))},btn:function(c){"ram";var m=c.x+(0|c.pad),n=c.y+(0|c.pad),q=c.w-(c.pad<<1),r=c.h-(c.pad<<1);m=[m,n+4,m+4,n,m+q-5,n,m+q-1,n+4,m+q-1,n+r-5,m+q-5,n+r-1,m+4,n+r-1,m,n+r-5,m,n+4];n=c.selected?b.theme.bgH:b.theme.bg2;b.setColor(n).fillPoly(m).setColor(c.selected?b.theme.fgH:b.theme.fg2).drawPoly(m);void 0!==c.col&&b.setColor(c.col);c.src?b.setBgColor(n).drawImage("function"==typeof c.src?c.src():c.src,c.x+c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5* +Math.PI*(c.r||0)}):b.setFont(c.font||"6x8:2").setFontAlign(0,0,c.r).drawString(c.label,c.x+c.w/2,c.y+c.h/2)},img:function(c){"ram";b.drawImage("function"==typeof c.src?c.src():c.src,c.x+c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5*Math.PI*(c.r||0)})},custom:function(c){"ram";c.render(c)},h:function(c){"ram";c.c.forEach(h)},v:function(c){"ram";c.c.forEach(h)}};if(this.lazy){this.rects||(this.rects={});var a=this.rects.clone(),e=[];t(d,a,e,this.rects,null);for(var l in a)delete this.rects[l];d= +Object.keys(a).map(c=>a[c]).reverse();for(var k of d)b.setBgColor(k.bg).clearRect.apply(g,k);e.forEach(h)}else h(d)};p.prototype.forgetLazyState=function(){this.rects={}};p.prototype.layout=function(d){var h={h:function(b){"ram";var f=b.x+(0|b.pad),a=0,e=b.c&&b.c.reduce((k,c)=>k+(0|c.fillx),0);e||(f+=b.w-b._w>>1,e=1);var l=f;b.c.forEach(k=>{k.x=0|l;f+=k._w;a+=0|k.fillx;l=f+Math.floor(a*(b.w-b._w)/e);k.w=0|l-k.x;k.h=0|(k.filly?b.h-(b.pad<<1):k._h);k.y=0|b.y+(0|b.pad)+((1+(0|k.valign))*(b.h-(b.pad<< +1)-k.h)>>1);if(k.c)h[k.type](k)})},v:function(b){"ram";var f=b.y+(0|b.pad),a=0,e=b.c&&b.c.reduce((k,c)=>k+(0|c.filly),0);e||(f+=b.h-b._h>>1,e=1);var l=f;b.c.forEach(k=>{k.y=0|l;f+=k._h;a+=0|k.filly;l=f+Math.floor(a*(b.h-b._h)/e);k.h=0|l-k.y;k.w=0|(k.fillx?b.w-(b.pad<<1):k._w);k.x=0|b.x+(0|b.pad)+((1+(0|k.halign))*(b.w-(b.pad<<1)-k.w)>>1);if(k.c)h[k.type](k)})}};h[d.type](d)};p.prototype.debug=function(d,h){d||(d=this._l);h=h||1;g.setColor(h&1,h&2,h&4).drawRect(d.x+h-1,d.y+h-1,d.x+d.w-h,d.y+d.h-h); +d.pad&&g.drawRect(d.x+d.pad-1,d.y+d.pad-1,d.x+d.w-d.pad,d.y+d.h-d.pad);h++;d.c&&d.c.forEach(b=>this.debug(b,h))};p.prototype.update=function(){function d(a){"ram";b[a.type](a);if(a.r&1){var e=a._w;a._w=a._h;a._h=e}a._w=Math.max(a._w+(a.pad<<1),0|a.width);a._h=Math.max(a._h+(a.pad<<1),0|a.height)}delete this.updateNeeded;var h=g,b={txt:function(a){"ram";a.font.endsWith("%")&&(a.font="Vector"+Math.round(h.getHeight()*a.font.slice(0,-1)/100));if(a.wrap)a._h=a._w=0;else{var e=g.setFont(a.font).stringMetrics(a.label); +a._w=e.width;a._h=e.height}},btn:function(a){"ram";a.font&&a.font.endsWith("%")&&(a.font="Vector"+Math.round(h.getHeight()*a.font.slice(0,-1)/100));var e=a.src?h.imageMetrics("function"==typeof a.src?a.src():a.src):h.setFont(a.font||"6x8:2").stringMetrics(a.label);a._h=16+e.height;a._w=20+e.width},img:function(a){"ram";var e=h.imageMetrics("function"==typeof a.src?a.src():a.src),l=a.scale||1;a._w=e.width*l;a._h=e.height*l},"":function(a){"ram";a._w=0;a._h=0},custom:function(a){"ram";a._w=0;a._h=0}, +h:function(a){"ram";a.c.forEach(d);a._h=a.c.reduce((e,l)=>Math.max(e,l._h),0);a._w=a.c.reduce((e,l)=>e+l._w,0);null==a.fillx&&a.c.some(e=>e.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(e=>e.filly)&&(a.filly=1)},v:function(a){"ram";a.c.forEach(d);a._h=a.c.reduce((e,l)=>e+l._h,0);a._w=a.c.reduce((e,l)=>Math.max(e,l._w),0);null==a.fillx&&a.c.some(e=>e.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(e=>e.filly)&&(a.filly=1)}},f=this._l;d(f);delete b;f.fillx||f.filly?(f.w=Bangle.appRect.w,f.h=Bangle.appRect.h, +f.x=Bangle.appRect.x,f.y=Bangle.appRect.y):(f.w=f._w,f.h=f._h,f.x=Bangle.appRect.w-f.w>>1,f.y=Bangle.appRect.y+(Bangle.appRect.h-f.h>>1));this.layout(f)};p.prototype.clear=function(d){d||(d=this._l);g.reset();void 0!==d.bgCol&&g.setBgColor(d.bgCol);g.clearRect(d.x,d.y,d.x+d.w-1,d.y+d.h-1)};exports=p \ No newline at end of file diff --git a/modules/README.md b/modules/README.md index 62ce90a97..fcb403bd5 100644 --- a/modules/README.md +++ b/modules/README.md @@ -13,10 +13,29 @@ Development When apps that use these modules are uploaded via the app loader, the module is automatically included in the app's source. However -when developing via the IDE the module won't get pulled in by default. +when developing via the IDE the module won't get pulled in by default +so you may see the error "Module not found" in the IDE when sending code to the Bangle. To fix this you have three options: + +### Change the Web IDE search path to include Bangle.js modules + +This is nice and easy (and the results are the same as if the app was +uploaded via the app loader), however you cannot then make/test changes +to the module. + +* In the IDE, Click the `Settings` icon in the top right +* Click `Communications` and scroll down to `Module URL` +* Now change the module URL from the default of `https://www.espruino.com/modules` +to `https://banglejs.com/apps/modules|https://www.espruino.com/modules` + +The next time you upload your app, the module will automatically be included. + +**Note:** You can optionally use `https://raw.githubusercontent.com/espruino/BangleApps/master/modules|https://www.espruino.com/modules` +as the module URL to pull in modules direct from the development app loader (which could be slightly newer than the ones on https://banglejs.com/apps) + + ### Host your own App Loader and upload from that This is reasonably easy to set up, but it's more difficult to make changes and upload: @@ -26,6 +45,7 @@ This is reasonably easy to set up, but it's more difficult to make changes and u * Refresh and upload your app from the app loader (you can have the IDE connected at the same time so you can see any error messages) + ### Upload the module to the Bangle's internal storage This allows you to develop both the app and module very quickly, but the app is @@ -40,15 +60,4 @@ or the method below: You can now upload the app direct from the IDE. You can even leave a second Web IDE window open (one for the app, one for the module) to allow you to change the module. -### Change the Web IDE search path to include Bangle.js modules -This is nice and easy (and the results are the same as if the app was -uploaded via the app loader), however you cannot then make/test changes -to the module. - -* In the IDE, Click the `Settings` icon in the top right -* Click `Communications` and scroll down to `Module URL` -* Now change the module URL from the default of `https://www.espruino.com/modules` -to `https://banglejs.com/apps/modules|https://www.espruino.com/modules` - -The next time you upload your app, the module will automatically be included. diff --git a/modules/buzz.js b/modules/buzz.js new file mode 100644 index 000000000..aed0e2e7b --- /dev/null +++ b/modules/buzz.js @@ -0,0 +1,33 @@ +/** + * Buzz the passed `pattern` out on the internal vibration motor. + * + * A pattern is a sequence of `.`, `,`, `-`, `:`, `;` and `=` where + * - `.` is one short and weak vibration + * - `,` is one medium and weak vibration + * - `-` is one long and weak vibration + * - `:` is one short and strong vibration + * - `;` is one medium and strong vibration + * - `=` is one long and strong vibration + * + * You can use the `buzz_menu` module to display a menu where some common patterns can be chosen. + * + * @param {string} pattern A string like `.-.`, `..=`, `:.:`, `..`, etc. + * @returns a Promise + */ +exports.pattern = pattern => new Promise(resolve => { + function doBuzz() { + if (pattern == "") resolve(); + var c = pattern[0]; + pattern = pattern.substr(1); + const BUZZ_WEAK = 0.25, BUZZ_STRONG = 1; + const SHORT_MS = 100, MEDIUM_MS = 200, LONG_MS = 500; + if (c == ".") Bangle.buzz(SHORT_MS, BUZZ_WEAK).then(() => setTimeout(doBuzz, 100)); + else if (c == ",") Bangle.buzz(MEDIUM_MS, BUZZ_WEAK).then(() => setTimeout(doBuzz, 100)); + else if (c == "-") Bangle.buzz(LONG_MS, BUZZ_WEAK).then(() => setTimeout(doBuzz, 100)); + else if (c == ":") Bangle.buzz(SHORT_MS, BUZZ_STRONG).then(() => setTimeout(doBuzz, 100)); + else if (c == ";") Bangle.buzz(MEDIUM_MS, BUZZ_STRONG).then(() => setTimeout(doBuzz, 100)); + else if (c == "=") Bangle.buzz(LONG_MS, BUZZ_STRONG).then(() => setTimeout(doBuzz, 100)); + else setTimeout(doBuzz, 100); + } + doBuzz(); +}); diff --git a/modules/buzz_menu.js b/modules/buzz_menu.js new file mode 100644 index 000000000..7ca155a2c --- /dev/null +++ b/modules/buzz_menu.js @@ -0,0 +1,19 @@ +/** + * Display a menu to select from various common vibration patterns for use with buzz.js. + * + * @param {string} value The pre-selected pattern + * @param {*} callback A function called with the user selected pattern + */ +exports.pattern = function (value, callback) { + var patterns = ["", ".", ":", "..", "::", ",", ";", ",,", ";;", "-", "=", "--", "==", "...", ":::", "---", ";;;", "==="]; + return { + value: Math.max(0, patterns.indexOf(value)), + min: 0, + max: patterns.length - 1, + format: v => patterns[v] || /*LANG*/"Off", + onchange: v => { + require("buzz").pattern(patterns[v]); + callback(patterns[v]); + } + }; +} diff --git a/modules/clock_info.js b/modules/clock_info.js new file mode 100644 index 000000000..50968311e --- /dev/null +++ b/modules/clock_info.js @@ -0,0 +1,309 @@ +/* Module that allows for loading of clock 'info' displays +that can be scrolled through on the clock face. + +`load()` returns an array of menu objects, where each object contains a list of menu items: +* `name` : text to display and identify menu object (e.g. weather) +* `img` : a 24x24px image +* `dynamic` : if `true`, items are not constant but are sorted (e.g. calendar events sorted by date) +* `items` : menu items such as temperature, humidity, wind etc. + +Note that each item is an object with: + +* `item.name` : friendly name to identify an item (e.g. temperature) +* `item.hasRange` : if `true`, `.get` returns `v/min/max` values (for progress bar/guage) +* `item.get` : function that returns an object: + +{ + 'text' // the text to display for this item + 'short' : (optional) a shorter text to display for this item (at most 6 characters) + 'img' // optional: a 24x24px image to display for this item + 'v' // (if hasRange==true) a numerical value + 'min','max' // (if hasRange==true) a minimum and maximum numerical value (if this were to be displayed as a guage) +} + +* `item.show` : called when item should be shown. Enables updates. Call BEFORE 'get' +* `item.hide` : called when item should be hidden. Disables updates. +* `.on('redraw', ...)` : event that is called when 'get' should be called again (only after 'item.show') +* `item.run` : (optional) called if the info screen is tapped - can perform some action. Return true if the caller should feedback the user. + +See the bottom of this file for example usage... + +example.clkinfo.js : + +(function() { + return { + name: "Bangle", + img: atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==") }), + items: [ + { name : "Item1", + get : () => ({ text : "TextOfItem1", v : 10, min : 0, max : 100, + img : atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==") + }), + show : () => {}, + hide : () => {} + // run : () => {} optional (called when tapped) + } + ] + }; +}) // must not have a semi-colon! + +*/ + +let storage = require("Storage"); +let stepGoal = undefined; +// Load step goal from health app and pedometer widget +let d = storage.readJSON("health.json", true) || {}; +stepGoal = d != undefined && d.settings != undefined ? d.settings.stepGoal : undefined; +if (stepGoal == undefined) { + d = storage.readJSON("wpedom.json", true) || {}; + stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000; +} + +exports.load = function() { + // info used for drawing... + var hrm = "--"; + var alt = "--"; + // callbacks (needed for easy removal of listeners) + function batteryUpdateHandler() { bangleItems[0].emit("redraw"); } + function stepUpdateHandler() { bangleItems[1].emit("redraw"); } + function hrmUpdateHandler() { bangleItems[2].emit("redraw"); } + function altUpdateHandler() { + Bangle.getPressure().then(data=>{ + if (!data) return; + alt = Math.round(data.altitude) + "m"; + bangleItems[3].emit("redraw"); + }); + } + // actual menu + var menu = [{ + name: "Bangle", + img: atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA=="), + items: [ + { name : "Battery", + hasRange : true, + get : () => { let v = E.getBattery(); return { + text : v + "%", v : v, min:0, max:100, + img : atob(Bangle.isCharging() ? "GBiBAAABgAADwAAHwAAPgACfAAHOAAPkBgHwDwP4Hwf8Pg/+fB//OD//kD//wD//4D//8D//4B//QB/+AD/8AH/4APnwAHAAACAAAA==" : "GBiBAAAAAAAAAAAAAAAAAAAAAD//+P///IAAAr//Ar//Ar//A7//A7//A7//A7//Ar//AoAAAv///D//+AAAAAAAAAAAAAAAAAAAAA==") + }}, + show : function() { this.interval = setInterval(()=>this.emit('redraw'), 60000); Bangle.on("charging", batteryUpdateHandler); batteryUpdateHandler(); }, + hide : function() { clearInterval(this.interval); delete this.interval; Bangle.removeListener("charging", batteryUpdateHandler); }, + }, + { name : "Steps", + hasRange : true, + get : () => { let v = Bangle.getHealthStatus("day").steps; return { + text : v, v : v, min : 0, max : stepGoal, + img : atob("GBiBAAcAAA+AAA/AAA/AAB/AAB/gAA/g4A/h8A/j8A/D8A/D+AfH+AAH8AHn8APj8APj8AHj4AHg4AADAAAHwAAHwAAHgAAHgAADAA==") + }}, + show : function() { Bangle.on("step", stepUpdateHandler); stepUpdateHandler(); }, + hide : function() { Bangle.removeListener("step", stepUpdateHandler); }, + }, + { name : "HRM", + hasRange : true, + get : () => { let v = Math.round(Bangle.getHealthStatus("last").bpm); return { + text : v + " bpm", v : v, min : 40, max : 200, + img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAADAAADAAAHAAAHjAAHjgAPngH9n/n82/gA+AAA8AAA8AAAcAAAYAAAYAAAAAAAAAAAAAAAAAAAAAA==") + }}, + show : function() { Bangle.setHRMPower(1,"clkinfo"); Bangle.on("HRM", hrmUpdateHandler); hrm = Math.round(Bangle.getHealthStatus("last").bpm); hrmUpdateHandler(); }, + hide : function() { Bangle.setHRMPower(0,"clkinfo"); Bangle.removeListener("HRM", hrmUpdateHandler); hrm = "--"; }, + } + ], + }]; + var bangleItems = menu[0].items; + + if (Bangle.getPressure){ // Altimeter may not exist + bangleItems.push({ name : "Altitude", + get : () => ({ + text : alt, v : alt, + img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAAACAAAGAAAPAAEZgAOwwAPwQAZgYAwAMBgAGBAACDAADGAABv///////wAAAAAAAAAAAAAAAAAAAA==") + }), + show : function() { this.interval = setInterval(altUpdateHandler, 60000); alt = "--"; altUpdateHandler(); }, + hide : function() { clearInterval(this.interval); delete this.interval; }, + }); + } + + // In case there exists already a menu object b with the same name as the next + // object a, we append the items. Otherwise we add the new object a to the list. + require("Storage").list(/clkinfo.js$/).forEach(fn => { + try{ + var a = eval(require("Storage").read(fn))(); + var b = menu.find(x => x.name === a.name) + if(b) b.items = b.items.concat(a.items); + else menu = menu.concat(a); + } catch(e){ + console.log("Could not load clock info "+E.toJS(fn)) + } + }); + + // return it all! + return menu; +}; + +/** Adds an interactive menu that could be used on a clock face by swiping. +Simply supply the menu data (from .load) and a function to draw the clock info. + +For example: + +let clockInfoItems = require("clock_info").load(); +let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { + x : 20, y: 20, w: 80, h:80, // dimensions of area used for clock_info + draw : (itm, info, options) => { + g.reset().clearRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1); + if (options.focus) g.drawRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1); // show if focused + var midx = options.x+options.w/2; + if (info.img) g.drawImage(info.img, midx-12,options.y+4); + g.setFont("6x8:2").setFontAlign(0,1).drawString(info.text, midx,options.y+44); + } +}); +// then when clock 'unloads': +clockInfoMenu.remove(); +delete clockInfoMenu; + +Then if you need to unload the clock info so it no longer +uses memory or responds to swipes, you can call clockInfoMenu.remove() +and delete clockInfoMenu + +clockInfoMenu is the 'options' parameter, with the following added: + +* `index` : int - which instance number are we? Starts at 0 +* `menuA` : int - index in 'menu' of showing clockInfo item +* `menuB` : int - index in 'menu[menuA].items' of showing clockInfo item +* `remove` : function - remove this clockInfo item +* `redraw` : function - force a redraw +* `focus` : function - bool to show if menu is focused or not + +You can have more than one clock_info at once as well, sfor instance: + +let clockInfoDraw = (itm, info, options) => { + g.reset().setBgColor(options.bg).setColor(options.fg).clearRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1); + if (options.focus) g.drawRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1) + var midx = options.x+options.w/2; + if (info.img) g.drawImage(info.img, midx-12,options.y); + g.setFont("6x15").setFontAlign(0,1).drawString(info.text, midx,options.y+41); +}; +let clockInfoItems = require("clock_info").load(); +let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { x:126, y:24, w:50, h:40, draw : clockInfoDraw, bg : g.theme.bg, fg : g.theme.fg }); +let clockInfoMenu2 = require("clock_info").addInteractive(clockInfoItems, { x:0, y:120, w:50, h:40, draw : clockInfoDraw, bg : bgColor, fg : g.theme.bg}); + +*/ +exports.addInteractive = function(menu, options) { + if (!menu.length || !menu[0].items.length) return; // no infos - can't load a clock_info + if ("function" == typeof options) options = {draw:options}; // backwards compatibility + options.index = 0|exports.loadCount; + exports.loadCount = options.index+1; + options.focus = options.index==0 && options.x===undefined; // focus if we're the first one loaded and no position has been defined + const appName = "default:"+options.index; + + { // load the currently showing clock_infos + let settings = require("Storage").readJSON("clock_info.json",1)||{}; + if (settings[appName]) { + let a = settings[appName].a|0; + let b = settings[appName].b|0; + if (menu[a] && menu[a].items[b]) { // all ok + options.menuA = a; + options.menuB = b; + } + } + } + if (options.menuA===undefined) options.menuA = 0; + if (options.menuB===undefined) options.menuB = Math.min(exports.loadCount, menu[options.menuA].items.length)-1; + function drawItem(itm) { + options.draw(itm, itm.get(), options); + } + function menuShowItem(itm) { + options.redrawHandler = ()=>drawItem(itm); + itm.on('redraw', options.redrawHandler); + itm.uses = (0|itm.uses)+1; + if (itm.uses==1) itm.show(); + itm.emit("redraw"); + } + function menuHideItem(itm) { + itm.removeListener('redraw',options.redrawHandler); + delete options.redrawHandler; + itm.uses--; + if (!itm.uses) + itm.hide(); + } + // handling for swipe between menu items + function swipeHandler(lr,ud){ + if (!options.focus) return; // ignore if we're not focussed + var oldMenuItem; + if (ud) { + if (menu[options.menuA].items.length==1) return; // 1 item - can't move + oldMenuItem = menu[options.menuA].items[options.menuB]; + options.menuB += ud; + if (options.menuB<0) options.menuB = menu[options.menuA].items.length-1; + if (options.menuB>=menu[options.menuA].items.length) options.menuB = 0; + } else if (lr) { + if (menu.length==1) return; // 1 item - can't move + oldMenuItem = menu[options.menuA].items[options.menuB]; + options.menuA += lr; + if (options.menuA<0) options.menuA = menu.length-1; + if (options.menuA>=menu.length) options.menuA = 0; + options.menuB = 0; + } + if (oldMenuItem) { + menuHideItem(oldMenuItem); + oldMenuItem.removeAllListeners("draw"); + menuShowItem(menu[options.menuA].items[options.menuB]); + } + // save the currently showing clock_info + let settings = require("Storage").readJSON("clock_info.json",1)||{}; + settings[appName] = {a:options.menuA,b:options.menuB}; + require("Storage").writeJSON("clock_info.json",settings); + } + Bangle.on("swipe",swipeHandler); + var touchHandler; + if (options.x!==undefined && options.y!==undefined && options.w && options.h) { + touchHandler = function(_,e) { + if (e.x(options.x+options.w) || e.y>(options.y+options.h)) { + if (options.focus) { + options.focus=false; + options.redraw(); + } + return; // outside area + } + if (!options.focus) { + options.focus=true; // if not focussed, set focus + options.redraw(); + } else if (menu[options.menuA].items[options.menuB].run) + menu[options.menuA].items[options.menuB].run(); // allow tap on an item to run it (eg home assistant) + else options.focus=true; + }; + Bangle.on("touch",touchHandler); + } + // draw the first item + menuShowItem(menu[options.menuA].items[options.menuB]); + // return an object with info that can be used to remove the info + options.remove = function() { + Bangle.removeListener("swipe",swipeHandler); + if (touchHandler) Bangle.removeListener("touch",touchHandler); + menuHideItem(menu[options.menuA].items[options.menuB]); + exports.loadCount--; + }; + options.redraw = function() { + drawItem(menu[options.menuA].items[options.menuB]); + }; + return options; +}; + +// Code for testing (plots all elements from first list) +/* +g.clear(); +var menu = exports.load(); // or require("clock_info").load() +var items = menu[0].items; +items.forEach((itm,i) => { + var y = i*24; + console.log("Starting", itm.name); + function draw() { + var info = itm.get(); + g.reset().setFont("6x8:2").setFontAlign(-1,0); + g.clearRect(0,y,g.getWidth(),y+23); + g.drawImage(info.img, 0,y); + g.drawString(info.text, 48,y+12); + } + itm.on('redraw', draw); // ensures we redraw when we need to + itm.show(); + draw(); +}); +*/ diff --git a/modules/date_utils.js b/modules/date_utils.js new file mode 100644 index 000000000..7239d4f1f --- /dev/null +++ b/modules/date_utils.js @@ -0,0 +1,65 @@ +// module "date_utils" +// +// Utility functions that use the "locale" module so can produce +// date-related text in the currently selected language. +// +// Some functions have a "firstDayOfWeek" parameter. +// Most used values are: +// - 0/undefined --> Sunday +// - 1 --> Monday +// but you can start the week from any day if you need it. +// +// Some functions have an "abbreviated" parameter. +// It supports the following 3 values: +// - 0/undefined --> get the full value, without abbreviation (eg.: "Monday", "January", etc.) +// - 1 --> get the short value (eg.: "Mon", "Jan", etc.) +// - 2 --> get only the first char (eg.: "M", "J", etc.) +// + +/** + * @param {int} i The index of the day of the week (0 = Sunday) + * @param {int} abbreviated + * @returns The localized name of the i-th day of the week + */ +exports.dow = (i, abbreviated) => { + var dow = require("locale").dow(new Date(((i || 0) + 3.5) * 86400000), abbreviated).slice(0, (abbreviated == 2) ? 1 : 100); + return abbreviated == 2 ? dow.toUpperCase() : dow; +} + +/** + * @param {int} firstDayOfWeek 0/undefined -> Sunday, + * 1 -> Monday + * @param {int} abbreviated + * @returns All 7 days of the week (localized) as an array + */ +exports.dows = (firstDayOfWeek, abbreviated) => { + var dows = []; + var locale = require("locale"); + for (var i = 0; i < 7; i++) { + dows.push(exports.dow(i + (firstDayOfWeek || 0), abbreviated)) + } + return abbreviated == 2 ? dows.map(dow => dow.toUpperCase()) : dows; +}; + +/** + * @param {int} i The index of the month (1 = January) + * @param {int} abbreviated + * @returns The localized name of the i-th month + */ +exports.month = (i, abbreviated) => { + var month = require("locale").month(new Date((i - 0.5) * 2628000000), abbreviated).slice(0, (abbreviated == 2) ? 1 : 100); + return abbreviated == 2 ? month.toUpperCase() : month; +} + +/** + * @param {int} abbreviated + * @returns All 12 months (localized) as an array + */ +exports.months = (abbreviated) => { + var months = []; + var locale = require("locale"); + for (var i = 1; i <= 12; i++) { + months.push(locale.month(new Date((i - 0.5) * 2628000000), abbreviated).slice(0, (abbreviated == 2) ? 1 : 100)); + } + return abbreviated == 2 ? months.map(month => month.toUpperCase()) : months; +}; diff --git a/modules/exstats.js b/modules/exstats.js index 8890ae9db..461ae727f 100644 --- a/modules/exstats.js +++ b/modules/exstats.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2022 Bangle.js contibutors. See the file LICENSE for copying permission. */ +/* Copyright (c) 2022 Bangle.js contributors. See the file LICENSE for copying permission. */ /* Exercise Stats module Take a look at README.md for hints on developing with this library. @@ -15,6 +15,7 @@ print(ExStats.getList()); {name: "Distance", id:"dist"}, {name: "Steps", id:"step"}, {name: "Heart (BPM)", id:"bpm"}, + {name: "Max BPM", id:"maxbpm"}, {name: "Pace (avr)", id:"pacea"}, {name: "Pace (current)", id:"pacec"}, {name: "Cadence", id:"caden"}, @@ -48,6 +49,15 @@ var menu = { ... }; ExStats.appendMenuItems(menu, settings, saveSettingsFunction); E.showMenu(menu); +// Additionally, if your app makes use of the stat notifications, you can display additional menu +// settings for configuring when to notify (note the added line in the example below)W + +var menu = { ... }; +ExStats.appendMenuItems(menu, settings, saveSettingsFunction); +ExStats.appendNotifyMenuItems(menu, settings, saveSettingsFunction); +E.showMenu(menu); + + */ var state = { active : false, // are we working or not? @@ -63,15 +73,32 @@ var state = { // cadence // steps per minute adjusted if <1 minute // BPM // beats per minute // BPMage // how many seconds was BPM set? + // maxBPM // The highest BPM reached while active + // Notifies: 0 for disabled, otherwise how often to notify in meters, seconds, or steps + notify: { + dist: { + increment: 0, + next: 0, + }, + steps: { + increment: 0, + next: 0, + }, + time: { + increment: 0, + next: 0, + }, + }, }; // list of active stats (indexed by ID) var stats = {}; // distance between 2 lat and lons, in meters, Mean Earth Radius = 6371km // https://www.movable-type.co.uk/scripts/latlong.html +// (Equirectangular approximation) function calcDistance(a,b) { function radians(a) { return a*Math.PI/180; } - var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2)); + var x = radians(b.lon-a.lon) * Math.cos(radians((a.lat+b.lat)/2)); var y = radians(b.lat-a.lat); return Math.sqrt(x*x + y*y) * 6371000; } @@ -101,56 +128,98 @@ function formatPace(speed, paceLength) { Bangle.on("GPS", function(fix) { if (!fix.fix) return; // only process actual fixes - - if (!state.active) return; state.lastGPS = state.thisGPS; state.thisGPS = fix; + if (stats["altg"]) stats["altg"].emit("changed",stats["altg"]); + if (stats["speed"]) stats["speed"].emit("changed",stats["speed"]); + if (!state.active) return; if (state.lastGPS.fix) state.distance += calcDistance(state.lastGPS, fix); if (stats["dist"]) stats["dist"].emit("changed",stats["dist"]); var duration = Date.now() - state.startTime; // in ms state.avrSpeed = state.distance * 1000 / duration; // meters/sec - state.curSpeed = state.curSpeed*0.8 + fix.speed*0.2/3.6; // meters/sec + if (!isNaN(fix.speed)) state.curSpeed = state.curSpeed*0.8 + fix.speed*0.2/3.6; // meters/sec if (stats["pacea"]) stats["pacea"].emit("changed",stats["pacea"]); if (stats["pacec"]) stats["pacec"].emit("changed",stats["pacec"]); - if (stats["speed"]) stats["speed"].emit("changed",stats["speed"]); + if (state.notify.dist.increment > 0 && state.notify.dist.next <= state.distance) { + stats["dist"].emit("notify",stats["dist"]); + state.notify.dist.next = state.notify.dist.next + state.notify.dist.increment; + } }); Bangle.on("step", function(steps) { if (!state.active) return; if (stats["step"]) stats["step"].emit("changed",stats["step"]); + state.stepHistory[0] += steps-state.lastStepCount; state.lastStepCount = steps; + if (state.notify.step.increment > 0 && state.notify.step.next <= steps) { + stats["step"].emit("notify",stats["step"]); + state.notify.step.next = state.notify.step.next + state.notify.step.increment; + } }); Bangle.on("HRM", function(h) { if (h.confidence>=60) { state.BPM = h.bpm; state.BPMage = 0; - stats["bpm"].emit("changed",stats["bpm"]); + if (state.maxBPM < h.bpm) { + state.maxBPM = h.bpm; + if (stats["maxbpm"]) stats["maxbpm"].emit("changed",stats["maxbpm"]); + } + if (stats["bpm"]) stats["bpm"].emit("changed",stats["bpm"]); + } +}); +if (Bangle.setBarometerPower) Bangle.on("pressure", function(e) { + if (state.alt === undefined) + state.alt = e.altitude; + else + state.alt = state.alt*0.9 + e.altitude*0.1; + var i = Math.round(state.alt); + if (i!==state.alti) { + state.alti = i; + if (stats["altb"]) stats["altb"].emit("changed",stats["altb"]); } }); /** Get list of available statistic types */ exports.getList = function() { - return [ + var l = [ {name: "Time", id:"time"}, {name: "Distance", id:"dist"}, {name: "Steps", id:"step"}, {name: "Heart (BPM)", id:"bpm"}, - {name: "Pace (avr)", id:"pacea"}, - {name: "Pace (current)", id:"pacec"}, + {name: "Max BPM", id:"maxbpm"}, + {name: "Pace (avg)", id:"pacea"}, + {name: "Pace (curr)", id:"pacec"}, {name: "Speed", id:"speed"}, {name: "Cadence", id:"caden"}, + {name: "Altitude (GPS)", id:"altg"} ]; + if (Bangle.setBarometerPower) l.push({name: "Altitude (baro)", id:"altb"}); + return l; }; -/** Instatiate the given list of statistic IDs (see comments at top) +/** Instantiate the given list of statistic IDs (see comments at top) options = { paceLength : meters to measure pace over + notify: { + dist: { + increment: 0 to not notify on distance milestones, otherwise the number of meters to notify after, repeating + }, + step: { + increment: 0 to not notify on step milestones, otherwise the number of steps to notify after, repeating + }, + time: { + increment: 0 to not notify on time milestones, otherwise the number of milliseconds to notify after, repeating + } + } } */ exports.getStats = function(statIDs, options) { options = options||{}; options.paceLength = options.paceLength||1000; - var needGPS,needHRM; + options.notify.dist.increment = (options.notify && options.notify.dist && options.notify.dist.increment)||0; + options.notify.step.increment = (options.notify && options.notify.step && options.notify.step.increment)||0; + options.notify.time.increment = (options.notify && options.notify.time && options.notify.time.increment)||0; + var needGPS,needHRM,needBaro; // ====================== if (statIDs.includes("time")) { stats["time"]={ @@ -158,13 +227,13 @@ exports.getStats = function(statIDs, options) { getValue : function() { return Date.now()-state.startTime; }, getString : function() { return formatTime(this.getValue()) }, }; - }; + } if (statIDs.includes("dist")) { needGPS = true; stats["dist"]={ title : "Dist", getValue : function() { return state.distance; }, - getString : function() { return require("locale").distance(state.distance); }, + getString : function() { return require("locale").distance(state.distance,2); }, }; } if (statIDs.includes("step")) { @@ -182,6 +251,14 @@ exports.getStats = function(statIDs, options) { getString : function() { return state.BPM||"--" }, }; } + if (statIDs.includes("maxbpm")) { + needHRM = true; + stats["maxbpm"]={ + title : "Max BPM", + getValue : function() { return state.maxBPM; }, + getString : function() { return state.maxBPM||"--" }, + }; + } if (statIDs.includes("pacea")) { needGPS = true; stats["pacea"]={ @@ -203,25 +280,42 @@ exports.getStats = function(statIDs, options) { stats["speed"]={ title : "Speed", getValue : function() { return state.curSpeed*3.6; }, // in kph - getString : function() { return require("locale").speed(state.curSpeed*3.6); }, + getString : function() { return require("locale").speed(state.curSpeed*3.6,2); }, }; } if (statIDs.includes("caden")) { - needGPS = true; stats["caden"]={ title : "Cadence", getValue : function() { return state.stepsPerMin; }, getString : function() { return state.stepsPerMin; }, }; } + if (statIDs.includes("altg")) { + needGPS = true; + stats["altg"]={ + title : "Altitude", + getValue : function() { return state.thisGPS.alt; }, + getString : function() { return (state.thisGPS.alt===undefined)?"-":Math.round(state.thisGPS.alt)+"m"; }, + }; + } + if (statIDs.includes("altb")) { + needBaro = true; + stats["altb"]={ + title : "Altitude", + getValue : function() { return state.alt; }, + getString : function() { return (state.alt===undefined)?"-":state.alti+"m"; }, + }; + } // ====================== for (var i in stats) stats[i].id=i; // set up ID field if (needGPS) Bangle.setGPSPower(true,"exs"); if (needHRM) Bangle.setHRMPower(true,"exs"); + if (needBaro) Bangle.setBarometerPower(true,"exs"); setInterval(function() { // run once a second.... if (!state.active) return; // called once a second - var duration = Date.now() - state.startTime; // in ms + var now = Date.now(); + var duration = now - state.startTime; // in ms // set cadence -> steps over last minute state.stepsPerMin = Math.round(60000 * E.sum(state.stepHistory) / Math.min(duration,60000)); if (stats["caden"]) stats["caden"].emit("changed",stats["caden"]); @@ -235,6 +329,10 @@ exports.getStats = function(statIDs, options) { state.BPM = 0; if (stats["bpm"]) stats["bpm"].emit("changed",stats["bpm"]); } + if (state.notify.time.increment > 0 && state.notify.time.next <= now) { + stats["time"].emit("notify",stats["time"]); + state.notify.time.next = state.notify.time.next + state.notify.time.increment; + } }, 1000); function reset() { state.startTime = Date.now(); @@ -247,6 +345,19 @@ exports.getStats = function(statIDs, options) { state.curSpeed = 0; state.BPM = 0; state.BPMage = 0; + state.maxBPM = 0; + state.alt = undefined; // barometer altitude (meters) + state.alti = 0; // integer ver of state.alt (to avoid repeated 'changed' notifications) + state.notify = options.notify; + if (options.notify.dist.increment > 0) { + state.notify.dist.next = state.distance + options.notify.dist.increment; + } + if (options.notify.step.increment > 0) { + state.notify.step.next = state.startSteps + options.notify.step.increment; + } + if (options.notify.time.increment > 0) { + state.notify.time.next = state.startTime + options.notify.time.increment; + } } reset(); return { @@ -262,15 +373,50 @@ exports.getStats = function(statIDs, options) { }; exports.appendMenuItems = function(menu, settings, saveSettings) { - var paceNames = ["1000m","1 mile","1/2 Mthn", "Marathon",]; - var paceAmts = [1000,1609,21098,42195]; + var paceNames = ["1000m", "1 mile", "1/2 Mthn", "Marathon",]; + var paceAmts = [1000, 1609, 21098, 42195]; menu['Pace'] = { - min :0, max: paceNames.length-1, - value: Math.max(paceAmts.indexOf(settings.paceLength),0), + min: 0, max: paceNames.length - 1, + value: Math.max(paceAmts.indexOf(settings.paceLength), 0), format: v => paceNames[v], onchange: v => { settings.paceLength = paceAmts[v]; saveSettings(); }, }; +} +exports.appendNotifyMenuItems = function(menu, settings, saveSettings) { + var distNames = ['Off', "1000m","1 mile","1/2 Mthn", "Marathon",]; + var distAmts = [0, 1000, 1609, 21098, 42195]; + menu['Ntfy Dist'] = { + min: 0, max: distNames.length-1, + value: Math.max(distAmts.indexOf(settings.notify.dist.increment),0), + format: v => distNames[v], + onchange: v => { + settings.notify.dist.increment = distAmts[v]; + saveSettings(); + }, + }; + var stepNames = ['Off', '100', '500', '1000', '5000', '10000']; + var stepAmts = [0, 100, 500, 1000, 5000, 10000]; + menu['Ntfy Steps'] = { + min: 0, max: stepNames.length-1, + value: Math.max(stepAmts.indexOf(settings.notify.step.increment),0), + format: v => stepNames[v], + onchange: v => { + settings.notify.step.increment = stepAmts[v]; + saveSettings(); + }, + }; + var timeNames = ['Off', '30s', '1min', '2min', '5min', '10min', '30min', '1hr']; + var timeAmts = [0, 30000, 60000, 120000, 300000, 600000, 1800000, 3600000]; + menu['Ntfy Time'] = { + min: 0, max: timeNames.length-1, + value: Math.max(timeAmts.indexOf(settings.notify.time.increment),0), + format: v => timeNames[v], + onchange: v => { + settings.notify.time.increment = timeAmts[v]; + saveSettings(); + }, + }; }; diff --git a/modules/suncalc.js b/modules/suncalc.js new file mode 100644 index 000000000..0c22c6cb2 --- /dev/null +++ b/modules/suncalc.js @@ -0,0 +1,341 @@ +/* + (c) 2011-2015, Vladimir Agafonkin + SunCalc is a JavaScript library for calculating sun/moon position and light phases. + https://github.com/mourner/suncalc + +Copyright (c) 2014, Vladimir Agafonkin +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +(function () { 'use strict'; + +// shortcuts for easier to read formulas + +var PI = Math.PI, + sin = Math.sin, + cos = Math.cos, + tan = Math.tan, + asin = Math.asin, + atan = Math.atan2, + acos = Math.acos, + rad = PI / 180; + +// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas + + +// date/time constants and conversions + +var dayMs = 1000 * 60 * 60 * 24, + J1970 = 2440588, + J2000 = 2451545; + +function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; } +function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); } +function toDays(date) { return toJulian(date) - J2000; } + + +// general calculations for position + +var e = rad * 23.4397; // obliquity of the Earth + +function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); } +function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); } + +function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); } +function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); } + +function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; } + +function astroRefraction(h) { + if (h < 0) // the following formula works for positive altitudes only. + h = 0; // if h = -0.08901179 a div/0 would occur. + + // formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. + // 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad: + return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179)); +} + +// general sun calculations + +function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); } + +function eclipticLongitude(M) { + + var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center + P = rad * 102.9372; // perihelion of the Earth + + return M + C + P + PI; +} + +function sunCoords(d) { + + var M = solarMeanAnomaly(d), + L = eclipticLongitude(M); + + return { + dec: declination(L, 0), + ra: rightAscension(L, 0) + }; +} + + +var SunCalc = {}; + + +// calculates sun position for a given date and latitude/longitude + +SunCalc.getPosition = function (date, lat, lng) { + + var lw = rad * -lng, + phi = rad * lat, + d = toDays(date), + + c = sunCoords(d), + H = siderealTime(d, lw) - c.ra; + + return { + azimuth: azimuth(H, phi, c.dec), + altitude: altitude(H, phi, c.dec) + }; +}; + + +// sun times configuration (angle, morning name, evening name) + +var times = SunCalc.times = [ + [-0.833, 'sunrise', 'sunset' ], + [ -0.3, 'sunriseEnd', 'sunsetStart' ], + [ -6, 'dawn', 'dusk' ], + [ -12, 'nauticalDawn', 'nauticalDusk'], + [ -18, 'nightEnd', 'night' ], + [ 6, 'goldenHourEnd', 'goldenHour' ] +]; + +// adds a custom time to the times config + +SunCalc.addTime = function (angle, riseName, setName) { + times.push([angle, riseName, setName]); +}; + + +// calculations for sun times + +var J0 = 0.0009; + +function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); } + +function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; } +function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); } + +function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); } +function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; } + +// returns set time for the given sun altitude +function getSetJ(h, lw, phi, dec, n, M, L) { + + var w = hourAngle(h, phi, dec), + a = approxTransit(w, lw, n); + return solarTransitJ(a, M, L); +} + + +// calculates sun times for a given date, latitude/longitude, and, optionally, +// the observer height (in meters) relative to the horizon + +SunCalc.getTimes = function (date, lat, lng, height) { + + height = height || 0; + + var lw = rad * -lng, + phi = rad * lat, + + dh = observerAngle(height), + + d = toDays(date), + n = julianCycle(d, lw), + ds = approxTransit(0, lw, n), + + M = solarMeanAnomaly(ds), + L = eclipticLongitude(M), + dec = declination(L, 0), + + Jnoon = solarTransitJ(ds, M, L), + + i, len, time, h0, Jset, Jrise; + + + var result = { + solarNoon: fromJulian(Jnoon), + nadir: fromJulian(Jnoon - 0.5) + }; + + for (i = 0, len = times.length; i < len; i += 1) { + time = times[i]; + h0 = (time[0] + dh) * rad; + + Jset = getSetJ(h0, lw, phi, dec, n, M, L); + Jrise = Jnoon - (Jset - Jnoon); + + result[time[1]] = fromJulian(Jrise); + result[time[2]] = fromJulian(Jset); + } + + return result; +}; + + +// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas + +function moonCoords(d) { // geocentric ecliptic coordinates of the moon + + var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude + M = rad * (134.963 + 13.064993 * d), // mean anomaly + F = rad * (93.272 + 13.229350 * d), // mean distance + + l = L + rad * 6.289 * sin(M), // longitude + b = rad * 5.128 * sin(F), // latitude + dt = 385001 - 20905 * cos(M); // distance to the moon in km + + return { + ra: rightAscension(l, b), + dec: declination(l, b), + dist: dt + }; +} + +SunCalc.getMoonPosition = function (date, lat, lng) { + + var lw = rad * -lng, + phi = rad * lat, + d = toDays(date), + + c = moonCoords(d), + H = siderealTime(d, lw) - c.ra, + h = altitude(H, phi, c.dec), + // formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. + pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H)); + + h = h + astroRefraction(h); // altitude correction for refraction + + return { + azimuth: azimuth(H, phi, c.dec), + altitude: h, + distance: c.dist, + parallacticAngle: pa + }; +}; + + +// calculations for illumination parameters of the moon, +// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and +// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. + +SunCalc.getMoonIllumination = function (date) { + + var d = toDays(date || new Date()), + s = sunCoords(d), + m = moonCoords(d), + + sdist = 149598000, // distance from Earth to Sun in km + + phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)), + inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi)), + angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) - + cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra)); + + return { + fraction: (1 + cos(inc)) / 2, + phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI, + angle: angle + }; +}; + + +function hoursLater(date, h) { + return new Date(date.valueOf() + h * dayMs / 24); +} + +// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article + +SunCalc.getMoonTimes = function (date, lat, lng, inUTC) { + var t = new Date(date); + if (inUTC) t.setUTCHours(0, 0, 0, 0); + else t.setHours(0, 0, 0, 0); + + var hc = 0.133 * rad, + h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc, + h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx; + + // go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set) + for (var i = 1; i <= 24; i += 2) { + h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc; + h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc; + + a = (h0 + h2) / 2 - h1; + b = (h2 - h0) / 2; + xe = -b / (2 * a); + ye = (a * xe + b) * xe + h1; + d = b * b - 4 * a * h1; + roots = 0; + + if (d >= 0) { + dx = Math.sqrt(d) / (Math.abs(a) * 2); + x1 = xe - dx; + x2 = xe + dx; + if (Math.abs(x1) <= 1) roots++; + if (Math.abs(x2) <= 1) roots++; + if (x1 < -1) x1 = x2; + } + + if (roots === 1) { + if (h0 < 0) rise = i + x1; + else set = i + x1; + + } else if (roots === 2) { + rise = i + (ye < 0 ? x2 : x1); + set = i + (ye < 0 ? x1 : x2); + } + + if (rise && set) break; + + h0 = h2; + } + + var result = {}; + + if (rise) result.rise = hoursLater(t, rise); + if (set) result.set = hoursLater(t, set); + + if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true; + + return result; +}; + + +// export as Node module / AMD module / browser variable +if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc; +else if (typeof define === 'function' && define.amd) define(SunCalc); +else window.SunCalc = SunCalc; + +}()); diff --git a/modules/time_utils.js b/modules/time_utils.js new file mode 100644 index 000000000..6a3ed6faf --- /dev/null +++ b/modules/time_utils.js @@ -0,0 +1,84 @@ +// module "time_utils" +// +// Utility functions useful to work with time and durations. +// +// This module functions usually receive or return a {d, h, m, s} object +// or a number of milliseconds representing a time or a duration. +// +// The {h, m, s} object encapsulates a "time" (eg: 12:30:00). +// Note: a "time" needs the day set to 0/undefined, otherwise it is a "duration". +// +// The {d, h, m, s} object encapsulates a "duration" in number of days, hours, +// minutes and seconds (eg. 3d 20h 35m 20s). +// Note that if a field is undefined then its value is zero. +// + +const ONE_SECOND = 1000; +const ONE_MINUTE = 60 * ONE_SECOND; +const ONE_HOUR = 60 * ONE_MINUTE; +const ONE_DAY = 24 * ONE_HOUR; + +/** + * @param {object} time {d, h, m, s} + * @returns the milliseconds contained in the passed time object + */ +exports.encodeTime = (time) => { + time = safeTime(time); + return time.d * ONE_DAY + time.h * ONE_HOUR + time.m * ONE_MINUTE + time.s * ONE_SECOND; +} + +// internal use, set to zero all the undefined fields +function safeTime(time) { + return { d: time.d || 0, h: time.h || 0, m: time.m || 0, s: time.s || 0 }; +} + +/** + * @param {int} millis the number of milliseconds + * @returns a time object {d, h, m, s} built from the milliseconds + */ +exports.decodeTime = (millis) => { + if (typeof millis !== "number") throw "Only a number can be decoded"; + var d = Math.floor(millis / ONE_DAY); + millis -= d * ONE_DAY; + var h = Math.floor(millis / ONE_HOUR); + millis -= h * ONE_HOUR; + var m = Math.floor(millis / ONE_MINUTE); + millis -= m * ONE_MINUTE; + var s = Math.floor(millis / ONE_SECOND); + return { d: d, h: h, m: m, s: s }; +} + +/** + * @param {object|int} value {h, m} object or milliseconds + * @returns an human-readable time string like "10:25" + * @throws an exception if d != 0 or h > 23 or m > 59 + */ +exports.formatTime = (value) => { + var time = safeTime(typeof value === "object" ? value : exports.decodeTime(value)); + if (time.d != 0) throw "days not supported here"; + if (time.h < 0 || time.h > 23) throw "Invalid value: must be 0 <= h <= 23"; + if (time.m < 0 || time.m > 59) throw "Invalid value: must be 0 <= m <= 59"; + return time.h + ":" + ("0" + time.m).substr(-2); +} + +/** + * @param {object|int} value {d, h, m, s} object or milliseconds + * @param {boolean} compact `true` to remove all whitespaces between the values + * @returns an human-readable duration string like "3d 1h 10m 45s" (or "3d1h10m45s" if `compact` is `true`) + */ +exports.formatDuration = (value, compact) => { + compact = compact || false; + var duration = ""; + var time = safeTime(typeof value === "object" ? value : exports.decodeTime(value)); + if (time.d > 0) duration += time.d + "d "; + if (time.h > 0) duration += time.h + "h "; + if (time.m > 0) duration += time.m + "m "; + if (time.s > 0) duration += time.s + "s" + duration = duration.trim() + return compact ? duration.replace(" ", "") : duration; +} + +exports.getCurrentTimeMillis = () => { + var time = new Date(); + return (time.getHours() * 3600 + time.getMinutes() * 60 + time.getSeconds()) * 1000; +} diff --git a/modules/wear_detect.js b/modules/wear_detect.js new file mode 100644 index 000000000..9581657cf --- /dev/null +++ b/modules/wear_detect.js @@ -0,0 +1,18 @@ +/** Returns a promise that resolves with whether the Bangle +is worn or not. + +Usage: + +require("wear_detect").isWorn().then(worn => { + console.log(worn ? "is worn" : "not worn"); +}); +*/ +exports.isWorn = function() { + return new Promise(resolve => { + if (Bangle.isCharging()) + return resolve(false); + if (Bangle.getHealthStatus().movement > 124) + return resolve(true); + return resolve(false); + }); +}; diff --git a/modules/widget_utils.js b/modules/widget_utils.js new file mode 100644 index 000000000..33fd303f9 --- /dev/null +++ b/modules/widget_utils.js @@ -0,0 +1,142 @@ +/// hide any visible widgets +exports.hide = function() { + exports.cleanup(); + if (!global.WIDGETS) return; + g.reset(); // reset colors + for (var w of global.WIDGETS) { + if (w._draw) return; // already hidden + w._draw = w.draw; + w.draw = () => {}; + w._area = w.area; + w.area = ""; + if (w.x!=undefined) g.clearRect(w.x,w.y,w.x+w.width-1,w.y+23); + } +}; + +/// Show any hidden widgets +exports.show = function() { + exports.cleanup(); + if (!global.WIDGETS) return; + for (var w of global.WIDGETS) { + if (!w._draw) return; // not hidden + w.draw = w._draw; + w.area = w._area; + delete w._draw; + delete w._area; + w.draw(w); + } +}; + +/// Remove any intervals/handlers/etc that we might have added. Does NOT re-show widgets that were hidden +exports.cleanup = function() { + delete exports.autohide; + delete Bangle.appRect; + if (exports.swipeHandler) { + Bangle.removeListener("swipe", exports.swipeHandler); + delete exports.swipeHandler; + } + if (exports.animInterval) { + clearInterval(exports.animInterval); + delete exports.animInterval; + } + if (exports.hideTimeout) { + clearTimeout(exports.hideTimeout); + delete exports.hideTimeout; + } + if (exports.origDraw) { + Bangle.drawWidgets = exports.origDraw; + delete exports.origDraw; + } +} + +/** Put widgets offscreen, and allow them to be swiped +back onscreen with a downwards swipe. Use .show to undo. +First parameter controls automatic hiding time, 0 equals not hiding at all. +Default value is 2000ms until hiding. +Bangle.js 2 only at the moment. */ +exports.swipeOn = function(autohide) { + exports.cleanup(); + if (!global.WIDGETS) return; + exports.autohide=autohide===undefined?2000:autohide; + /* TODO: maybe when widgets are offscreen we don't even + store them in an offscreen buffer? */ + + // force app rect to be fullscreen + Bangle.appRect = { x: 0, y: 0, w: g.getWidth(), h: g.getHeight(), x2: g.getWidth()-1, y2: g.getHeight()-1 }; + // setup offscreen graphics for widgets + let og = Graphics.createArrayBuffer(g.getWidth(),24,16,{msb:true}); + og.theme = g.theme; + og._reset = og.reset; + og.reset = function() { + return this._reset().setColor(g.theme.fg).setBgColor(g.theme.bg); + }; + og.reset().clearRect(0,0,og.getWidth(),og.getHeight()); + let _g = g; + let offset = -24; // where on the screen are we? -24=hidden, 0=full visible + + function queueDraw() { + Bangle.appRect.y = offset+24; + Bangle.appRect.h = 1 + Bangle.appRect.y2 - Bangle.appRect.y; + if (offset>-24) Bangle.setLCDOverlay(og, 0, offset); + else Bangle.setLCDOverlay(); + } + + for (var w of global.WIDGETS) { + if (w._draw) return; // already hidden + w._draw = w.draw; + w.draw = function() { + g=og; + this._draw(this); + g=_g; + if (offset>-24) queueDraw(); + }; + w._area = w.area; + if (w.area.startsWith("b")) + w.area = "t"+w.area.substr(1); + } + + exports.origDraw = Bangle.drawWidgets; + Bangle.drawWidgets = ()=>{ + g=og; + exports.origDraw(); + g=_g; + }; + + function anim(dir, callback) { + if (exports.animInterval) clearInterval(exports.interval); + exports.animInterval = setInterval(function() { + offset += dir; + let stop = false; + if (dir>0 && offset>=0) { // fully down + stop = true; + offset = 0; + } else if (dir<0 && offset<-23) { // fully up + stop = true; + offset = -24; + } + if (stop) { + clearInterval(exports.animInterval); + delete exports.animInterval; + if (callback) callback(); + } + queueDraw(); + }, 50); + } + // On swipe down, animate to show widgets + exports.swipeHandler = function(lr,ud) { + if (exports.hideTimeout) { + clearTimeout(exports.hideTimeout); + delete exports.hideTimeout; + } + let cb; + if (exports.autohide > 0) cb = function() { + exports.hideTimeout = setTimeout(function() { + anim(-4); + }, exports.autohide); + } + if (ud>0 && offset<0) anim(4, cb); + if (ud<0 && offset>-24) anim(-4); + + }; + Bangle.on("swipe", exports.swipeHandler); +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..e981abdb8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1595 @@ +{ + "name": "BangleApps", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@eslint/eslintrc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", + "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.3.2", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@humanwhocodes/config-array": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", + "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "array-includes": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + } + }, + "array.prototype.flat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", + "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "es-abstract": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", + "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "regexp.prototype.flags": "^1.4.3", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + } + }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.17.0.tgz", + "integrity": "sha512-gq0m0BTJfci60Fz4nczYxNAlED+sMcihltndR8t9t1evnU/azx53x3t2UHXC/uRjcbvRw/XctpaNygSTcQD+Iw==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.9.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.2", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-module-utils": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", + "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "find-up": "^2.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "requires": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", + "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "dev": true, + "requires": { + "acorn": "^8.7.1", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "dependencies": { + "acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "dev": true + } + } + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", + "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true + }, + "is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "nodemon": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", + "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", + "dev": true, + "requires": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-watch": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/npm-watch/-/npm-watch-0.11.0.tgz", + "integrity": "sha512-wAOd0moNX2kSA2FNvt8+7ORwYaJpQ1ZoWjUYdb1bBCxq4nkWuU0IiJa9VpVxrj5Ks+FGXQd62OC/Bjk0aSr+dg==", + "dev": true, + "requires": { + "nodemon": "^2.0.7", + "through2": "^4.0.2" + } + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "object.entries": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", + "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "requires": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "simple-update-notifier": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", + "integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==", + "dev": true, + "requires": { + "semver": "~7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, + "string.prototype.trimend": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "requires": { + "readable-stream": "3" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "requires": { + "nopt": "~1.0.10" + } + }, + "tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 32c96e3ea..08f3a19ce 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,17 @@ "description": "Bangle.js App Loader (and Apps)", "author": "Gordon Williams (http://espruino.com)", "version": "0.0.1", + "license": "MIT", + "repository": "https://github.com/espruino/BangleApps", "devDependencies": { - "eslint": "7.1.0" + "eslint": "^8.14.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-plugin-import": "^2.26.0", + "npm-watch": "^0.11.0" }, "scripts": { "lint-apps": "eslint ./apps --ext .js", - "test": "node bin/sanitycheck.js && eslint ./apps --ext .js", + "test": "node bin/sanitycheck.js && eslint ./apps --ext .js && eslint ./modules --ext .js", "update-local-apps": "./bin/create_apps_json.sh apps.local.json", "local": "npm-watch & npx http-server -a localhost -c-1", "start": "npx http-server -c-1" @@ -18,8 +23,5 @@ }, "dependencies": { "acorn": "^7.2.0" - }, - "devDpendencies": { - "npm-watch": "^0.11.0" } } diff --git a/tests/Layout/bin/runalltests.sh b/tests/Layout/bin/runalltests.sh index 3a7aac50b..2d2985a4f 100755 --- a/tests/Layout/bin/runalltests.sh +++ b/tests/Layout/bin/runalltests.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash cd `dirname $0`/.. ls tests/*.js | xargs -I{} bin/runtest.sh {} diff --git a/tests/Layout/bin/runtest.sh b/tests/Layout/bin/runtest.sh index c85b3fe6c..f6c566d18 100755 --- a/tests/Layout/bin/runtest.sh +++ b/tests/Layout/bin/runtest.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Requires Linux x64 (for ./espruino) # Also imagemagick for display @@ -18,24 +18,35 @@ SRCJS=$1 SRCBMP=$SRCDIR/`basename $SRCJS .js`.bmp echo "TEST $SRCJS ($SRCBMP)" -cat ../../modules/Layout.js > $TESTJS -echo 'Bangle = { setUI : function(){} };BTN1=0;process.env = process.env;process.env.HWVERSION=2;' >> $TESTJS -echo 'g = Graphics.createArrayBuffer(176,176,4);' >> $TESTJS -cat $SRCJS >> $TESTJS || exit 1 -echo 'layout.render()' >> $TESTJS -#echo 'layout.debug()' >> $TESTJS -echo 'require("fs").writeFileSync("'$TESTBMP'",g.asBMP())' >> $TESTJS +run_test () { + LAYOUTFILE=$1 + echo 'exports = {};' > $TESTJS + cat $LAYOUTFILE >> $TESTJS + echo ';' >> $TESTJS + echo 'Layout = exports;' >> $TESTJS + echo 'Bangle = { setUI : function(){}, appRect:{x:0,y:0,w:176,h:176,x2:175,y2:175} };BTN1=0;process.env = process.env;process.env.HWVERSION=2;' >> $TESTJS + echo 'g = Graphics.createArrayBuffer(176,176,4);' >> $TESTJS + cat $SRCJS >> $TESTJS || exit 1 + echo 'layout.render()' >> $TESTJS + #echo 'layout.debug()' >> $TESTJS + echo 'require("fs").writeFileSync("'$TESTBMP'",g.asBMP())' >> $TESTJS + echo ============================================= + echo TESTING $LAYOUTFILE $SRCJS + bin/espruino $TESTJS || exit 1 + if ! cmp $TESTBMP $SRCBMP >/dev/null 2>&1 + then + echo ============================================= + echo $LAYOUTFILE + echo $TESTBMP $SRCBMP differ + echo ============================================== + convert "+append" $TESTBMP $SRCBMP testresult.bmp + display testresult.bmp + exit 1 + else + echo Files are the same + exit 0 + fi +} -bin/espruino $TESTJS || exit 1 -if ! cmp $TESTBMP $SRCBMP >/dev/null 2>&1 -then - echo ============================================= - echo $TESTBMP $SRCBMP differ - echo ============================================== - convert "+append" $TESTBMP $SRCBMP testresult.bmp - display testresult.bmp - exit 1 -else - echo Files are the same - exit 0 -fi +run_test ../../modules/Layout.js +run_test ../../modules/Layout.min.js diff --git a/tests/Layout/tests/buttons_1_bangle1.js b/tests/Layout/tests/buttons_1_bangle1.js index fb6fb29fa..481f09df3 100644 --- a/tests/Layout/tests/buttons_1_bangle1.js +++ b/tests/Layout/tests/buttons_1_bangle1.js @@ -1,6 +1,7 @@ var BTN2 = 1, BTN3=2; process.env = process.env;process.env.HWVERSION=1; g = Graphics.createArrayBuffer(240,240,4); +Bangle.appRect = {x:0,y:0,w:240,h:240,x2:239,y2:239}; var layout = new Layout({ type: "v", c: [ {type:"txt", font:"6x8", label:"A test"}, diff --git a/tests/Layout/tests/buttons_3_bangle1.js b/tests/Layout/tests/buttons_3_bangle1.js index c8346f449..2d5fbea9d 100644 --- a/tests/Layout/tests/buttons_3_bangle1.js +++ b/tests/Layout/tests/buttons_3_bangle1.js @@ -1,6 +1,7 @@ var BTN2 = 1, BTN3=2; process.env = process.env;process.env.HWVERSION=1; g = Graphics.createArrayBuffer(240,240,4); +Bangle.appRect = {x:0,y:0,w:240,h:240,x2:239,y2:239}; var layout = new Layout({ type: "v", c: [ {type:"txt", font:"6x8", label:"A test"}, diff --git a/tests/Layout/tests/buttons_osd_bangle1.js b/tests/Layout/tests/buttons_osd_bangle1.js index 108cb62b0..55656ef33 100644 --- a/tests/Layout/tests/buttons_osd_bangle1.js +++ b/tests/Layout/tests/buttons_osd_bangle1.js @@ -1,6 +1,7 @@ var BTN2 = 1, BTN3=2; process.env = process.env;process.env.HWVERSION=1; g = Graphics.createArrayBuffer(240,240,4); +Bangle.appRect = {x:0,y:0,w:240,h:240,x2:239,y2:239}; /* When displaying OSD buttons on Bangle.js 1 we should turn the side buttons into 'soft' buttons and then use the physical diff --git a/tests/Layout/tests/padding.bmp b/tests/Layout/tests/padding.bmp index 84ae4dc1b..506bb014e 100644 Binary files a/tests/Layout/tests/padding.bmp and b/tests/Layout/tests/padding.bmp differ diff --git a/tests/Layout/tests/padding_with_fill.bmp b/tests/Layout/tests/padding_with_fill.bmp index 9f82ed09f..92eccace2 100644 Binary files a/tests/Layout/tests/padding_with_fill.bmp and b/tests/Layout/tests/padding_with_fill.bmp differ diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..8da08b8e2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "module": "commonjs", + "noImplicitAny": true, + "target": "es6", + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noLib": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "strict": true, + "typeRoots": ["./typescript/types"] + }, + "include": ["./**/*"], + "exclude": [ + "**/gpconv.d.ts", + "**/gpconv_bg.wasm.d.ts" + ] +} diff --git a/typescript/.gitignore b/typescript/.gitignore new file mode 100644 index 000000000..630f61ee5 --- /dev/null +++ b/typescript/.gitignore @@ -0,0 +1,2 @@ +./node_modules +!package-lock.json diff --git a/typescript/README.md b/typescript/README.md new file mode 100644 index 000000000..7c1e21abd --- /dev/null +++ b/typescript/README.md @@ -0,0 +1,26 @@ +# Bangle.ts + +A generic project setup for compiling apps from Typescript to +Bangle.js-ready, readable JavaScript. + +The types are now automatically generated by a script (see +[here](https://github.com/espruino/Espruino/blob/master/TYPESCRIPT.md). + +## Compilation + +Install [npm](https://www.npmjs.com/get-npm) and node.js if you haven't already. We recommend using a version manager like nvm, which is also referenced in the linked documentation. +Make sure you are using node version 16 by running `nvm use 16` and npm version ^8 by running `npm -v`. If the latter version is incorrect, run `npm i -g npm@^8`. + +After having installed npm for your platform, open a terminal, and navigate into the `/typescript` folder. Then run: + +``` +npm ci +``` + +to install the project's build tools, and: + +``` +npm run build +``` + +To build all Typescript apps and widgets. The last command will generate the `app.js` files containing the transpiled code for the Bangle.js. diff --git a/typescript/package-lock.json b/typescript/package-lock.json new file mode 100644 index 000000000..15653acc2 --- /dev/null +++ b/typescript/package-lock.json @@ -0,0 +1,165 @@ +{ + "name": "Bangle.ts", + "version": "0.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "Bangle.ts", + "version": "0.0.1", + "dependencies": { + "node-fetch": "^3.2.10" + }, + "devDependencies": { + "typescript": "4.5.2" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", + "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", + "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/typescript": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", + "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "engines": { + "node": ">= 8" + } + } + }, + "dependencies": { + "data-uri-to-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", + "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==" + }, + "fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + } + }, + "formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "requires": { + "fetch-blob": "^3.1.2" + } + }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" + }, + "node-fetch": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", + "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", + "requires": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + } + }, + "typescript": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", + "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", + "dev": true + }, + "web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==" + } + } +} diff --git a/typescript/package.json b/typescript/package.json new file mode 100644 index 000000000..9533d77a7 --- /dev/null +++ b/typescript/package.json @@ -0,0 +1,15 @@ +{ + "name": "Bangle.ts", + "description": "Bangle.js Typescript Project Setup and Types", + "author": "Sebastian Di Luzio (https://diluz.io) and qucchia (https://github.com/qucchia)", + "version": "0.0.1", + "devDependencies": { + "typescript": "4.5.2" + }, + "scripts": { + "build": "tsc" + }, + "dependencies": { + "node-fetch": "^3.2.10" + } +} diff --git a/typescript/types/main.d.ts b/typescript/types/main.d.ts new file mode 100644 index 000000000..89921972c --- /dev/null +++ b/typescript/types/main.d.ts @@ -0,0 +1,13012 @@ +// NOTE: This file has been automatically generated. + +/// + +// TYPES + +/** + * Menu item that holds a boolean value. + */ +type MenuBooleanItem = { + value: boolean; + format?: (value: boolean) => string; + onchange?: (value: boolean) => void; +}; + +/** + * Menu item that holds a numerical value. + */ +type MenuNumberItem = { + value: number; + format?: (value: number) => string; + onchange?: (value: number) => void; + step?: number; + min?: number; + max?: number; + wrap?: boolean; +}; + +/** + * Options passed to a menu. + */ +type MenuOptions = { + title?: string; + back?: () => void; + selected?: number; + fontHeight?: number; + x?: number; + y?: number; + x2?: number; + y2?: number; + cB?: number; + cF?: number; + cHB?: number; + cHF?: number; + predraw?: (g: Graphics) => void; + preflip?: (g: Graphics, less: boolean, more: boolean) => void; +}; + +/** + * Object containing data about a menu to pass to `E.showMenu`. + */ +type Menu = { + ""?: MenuOptions; + [key: string]: + | MenuOptions + | (() => void) + | MenuBooleanItem + | MenuNumberItem + | { value: string; onchange?: () => void } + | undefined; +}; + +/** + * Menu instance. + */ +type MenuInstance = { + draw: () => void; + move: (n: number) => void; + select: () => void; +}; + +type ImageObject = { + width: number; + height: number; + bpp?: number; + buffer: ArrayBuffer | string; + transparent?: number; + palette?: Uint16Array; +}; + +type Image = string | ImageObject | ArrayBuffer | Graphics; + +type ColorResolvable = number | `#${string}`; + +type FontName = + | "4x4" + | "4x4Numeric" + | "4x5" + | "4x5Numeric" + | "4x8Numeric" + | "5x7Numeric7Seg" + | "5x9Numeric7Seg" + | "6x8" + | "6x12" + | "7x11Numeric7Seg" + | "8x12" + | "8x16" + | "Dennis8" + | "Cherry6x10" + | "Copasectic40x58Numeric" + | "Dylex7x13" + | "HaxorNarrow7x17" + | "Sinclair" + | "Teletext10x18Mode7" + | "Teletext5x9Ascii" + | "Teletext5x9Mode7" + | "Vector"; + +type FontNameWithScaleFactor = + | FontName + | `${FontName}:${number}` + | `${FontName}:${number}x${number}`; + +type Theme = { + fg: number; + bg: number; + fg2: number; + bg2: number; + fgH: number; + bgH: number; + dark: boolean; +}; + +type NRFFilters = { + services?: string[]; + name?: string; + namePrefix?: string; + id?: string; + serviceData?: object; + manufacturerData?: object; +}; + +declare const BTN1: Pin; +declare const BTN2: Pin; +declare const BTN3: Pin; +declare const BTN4: Pin; +declare const BTN5: Pin; + +declare const g: Graphics; + +type WidgetArea = "tl" | "tr" | "bl" | "br"; +type Widget = { + area: WidgetArea; + width: number; + draw: (this: { x: number; y: number }) => void; +}; +declare const WIDGETS: { [key: string]: Widget }; + +type AccelData = { + x: number; + y: number; + z: number; + diff: number; + mag: number; +}; + +type HealthStatus = { + movement: number; + steps: number; + bpm: number; + bpmConfidence: number; +}; + +type CompassData = { + x: number; + y: number; + z: number; + dx: number; + dy: number; + dz: number; + heading: number; +}; + +type GPSFix = { + lat: number; + lon: number; + alt: number; + speed: number; + course: number; + time: Date; + satellites: number; + fix: number; + hdop: number +}; + +type PressureData = { + temperature: number; + pressure: number; + altitude: number; +} + +type TapAxis = -2 | -1 | 0 | 1 | 2; + +type SwipeCallback = (directionLR: -1 | 0 | 1, directionUD?: -1 | 0 | 1) => void; + +type TouchCallback = (button: number, xy?: { x: number, y: number }) => void; + +type DragCallback = (event: { + x: number; + y: number; + dx: number; + dy: number; + b: 1 | 0; +}) => void; + +type LCDMode = + | "direct" + | "doublebuffered" + | "120x120" + | "80x80" + +type BangleOptions = { + wakeOnBTN1: boolean; + wakeOnBTN2: boolean; + wakeOnBTN3: boolean; + wakeOnFaceUp: boolean; + wakeOnTouch: boolean; + wakeOnTwist: boolean; + twistThreshold: number; + twistMaxY: number; + twistTimeout: number; + gestureStartThresh: number; + gestureEndThresh: number; + gestureInactiveCount: number; + gestureMinLength: number; + powerSave: boolean; + lockTimeout: number; + lcdPowerTimeout: number; + backlightTimeout: number; +}; + +interface ArrayLike { + readonly length: number; + readonly [n: number]: T; +} + +type PinMode = + | "analog" + | "input" + | "input_pullup" + | "input_pulldown" + | "output" + | "opendrain" + | "af_output" + | "af_opendrain"; + +type ErrorFlag = + | "FIFO_FULL" + | "BUFFER_FULL" + | "CALLBACK" + | "LOW_MEMORY" + | "MEMORY" + | "UART_OVERFLOW"; + +type Flag = + | "deepSleep" + | "pretokenise" + | "unsafeFlash" + | "unsyncFiles"; + +type Uint8ArrayResolvable = + | number + | string + | Uint8ArrayResolvable[] + | ArrayBuffer + | ArrayBufferView + | { data: Uint8ArrayResolvable, count: number } + | { callback: () => Uint8ArrayResolvable } + +type VariableSizeInformation = { + name: string; + size: number; + more?: VariableSizeInformation; +}; + + +// CLASSES + +/** + * Class containing [micro:bit's](https://www.espruino.com/MicroBit) utility + * functions. + * @url http://www.espruino.com/Reference#Microbit + */ +declare class Microbit { + /** + * The micro:bit's speaker pin + * @returns {Pin} + * @url http://www.espruino.com/Reference#l_Microbit_SPEAKER + */ + static SPEAKER: Pin; + + /** + * The micro:bit's microphone pin + * `MIC_ENABLE` should be set to 1 before using this + * @returns {Pin} + * @url http://www.espruino.com/Reference#l_Microbit_MIC + */ + static MIC: Pin; + + /** + * The micro:bit's microphone enable pin + * @returns {Pin} + * @url http://www.espruino.com/Reference#l_Microbit_MIC_ENABLE + */ + static MIC_ENABLE: Pin; + + /** + * Called when the Micro:bit is moved in a deliberate fashion, and includes data on + * the detected gesture. + * @param {string} event - The event to listen to. + * @param {(gesture: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `gesture` An Int8Array containing the accelerations (X,Y,Z) from the last gesture detected by the accelerometer + * @url http://www.espruino.com/Reference#l_Microbit_gesture + */ + static on(event: "gesture", callback: (gesture: any) => void): void; + + /** + * @returns {any} An Object `{x,y,z}` of magnetometer readings as integers + * @url http://www.espruino.com/Reference#l_Microbit_mag + */ + static mag(): any; + + /** + * @returns {any} An Object `{x,y,z}` of acceleration readings in G + * @url http://www.espruino.com/Reference#l_Microbit_accel + */ + static accel(): any; + + /** + * **Note:** This function is only available on the [BBC micro:bit](/MicroBit) + * board + * Write the given value to the accelerometer + * + * @param {number} addr - Accelerometer address + * @param {number} data - Data to write + * @url http://www.espruino.com/Reference#l_Microbit_accelWr + */ + static accelWr(addr: number, data: number): void; + + /** + * Turn on the accelerometer, and create `Microbit.accel` and `Microbit.gesture` + * events. + * **Note:** The accelerometer is currently always enabled - this code just + * responds to interrupts and reads + * @url http://www.espruino.com/Reference#l_Microbit_accelOn + */ + static accelOn(): void; + + /** + * Turn off events from the accelerometer (started with `Microbit.accelOn`) + * @url http://www.espruino.com/Reference#l_Microbit_accelOff + */ + static accelOff(): void; + + /** + * Play a waveform on the Micro:bit's speaker + * + * @param {any} waveform - An array of data to play (unsigned 8 bit) + * @param {any} samplesPerSecond - The number of samples per second for playback default is 4000 + * @param {any} callback - A function to call when playback is finished + * @url http://www.espruino.com/Reference#l_Microbit_play + */ + static play(waveform: any, samplesPerSecond: any, callback: any): void; + + /** + * Records sound from the micro:bit's onboard microphone and returns the result + * + * @param {any} samplesPerSecond - The number of samples per second for recording - 4000 is recommended + * @param {any} callback - A function to call with the result of recording (unsigned 8 bit ArrayBuffer) + * @param {any} [samples] - [optional] How many samples to record (6000 default) + * @url http://www.espruino.com/Reference#l_Microbit_record + */ + static record(samplesPerSecond: any, callback: any, samples?: any): void; + + +} + +interface MathConstructor { + /** + * @returns {number} The value of E - 2.718281828459045 + * @url http://www.espruino.com/Reference#l_Math_E + */ + E: number; + + /** + * @returns {number} The value of PI - 3.141592653589793 + * @url http://www.espruino.com/Reference#l_Math_PI + */ + PI: number; + + /** + * @returns {number} The natural logarithm of 2 - 0.6931471805599453 + * @url http://www.espruino.com/Reference#l_Math_LN2 + */ + LN2: number; + + /** + * @returns {number} The natural logarithm of 10 - 2.302585092994046 + * @url http://www.espruino.com/Reference#l_Math_LN10 + */ + LN10: number; + + /** + * @returns {number} The base 2 logarithm of e - 1.4426950408889634 + * @url http://www.espruino.com/Reference#l_Math_LOG2E + */ + LOG2E: number; + + /** + * @returns {number} The base 10 logarithm of e - 0.4342944819032518 + * @url http://www.espruino.com/Reference#l_Math_LOG10E + */ + LOG10E: number; + + /** + * @returns {number} The square root of 2 - 1.4142135623730951 + * @url http://www.espruino.com/Reference#l_Math_SQRT2 + */ + SQRT2: number; + + /** + * @returns {number} The square root of 1/2 - 0.7071067811865476 + * @url http://www.espruino.com/Reference#l_Math_SQRT1_2 + */ + SQRT1_2: number; + + /** + * + * @param {number} x - A floating point value + * @returns {number} The absolute value of x (eg, ```Math.abs(2)==2```, but also ```Math.abs(-2)==2```) + * @url http://www.espruino.com/Reference#l_Math_abs + */ + abs(x: number): number; + + /** + * + * @param {number} x - The value to get the arc cosine of + * @returns {number} The arc cosine of x, between 0 and PI + * @url http://www.espruino.com/Reference#l_Math_acos + */ + acos(x: number): number; + + /** + * + * @param {number} x - The value to get the arc sine of + * @returns {number} The arc sine of x, between -PI/2 and PI/2 + * @url http://www.espruino.com/Reference#l_Math_asin + */ + asin(x: number): number; + + /** + * + * @param {number} x - The value to get the arc tangent of + * @returns {number} The arc tangent of x, between -PI/2 and PI/2 + * @url http://www.espruino.com/Reference#l_Math_atan + */ + atan(x: number): number; + + /** + * + * @param {number} y - The Y-part of the angle to get the arc tangent of + * @param {number} x - The X-part of the angle to get the arc tangent of + * @returns {number} The arctangent of Y/X, between -PI and PI + * @url http://www.espruino.com/Reference#l_Math_atan2 + */ + atan2(y: number, x: number): number; + + /** + * + * @param {number} theta - The angle to get the cosine of + * @returns {number} The cosine of theta + * @url http://www.espruino.com/Reference#l_Math_cos + */ + cos(theta: number): number; + + /** + * + * @param {number} x - The value to raise to the power + * @param {number} y - The power x should be raised to + * @returns {number} x raised to the power y (x^y) + * @url http://www.espruino.com/Reference#l_Math_pow + */ + pow(x: number, y: number): number; + + /** + * @returns {number} A random number between 0 and 1 + * @url http://www.espruino.com/Reference#l_Math_random + */ + random(): number; + + /** + * + * @param {number} x - The value to round + * @returns {any} x, rounded to the nearest integer + * @url http://www.espruino.com/Reference#l_Math_round + */ + round(x: number): any; + + /** + * + * @param {number} theta - The angle to get the sine of + * @returns {number} The sine of theta + * @url http://www.espruino.com/Reference#l_Math_sin + */ + sin(theta: number): number; + + /** + * + * @param {number} theta - The angle to get the tangent of + * @returns {number} The tangent of theta + * @url http://www.espruino.com/Reference#l_Math_tan + */ + tan(theta: number): number; + + /** + * + * @param {number} x - The value to take the square root of + * @returns {number} The square root of x + * @url http://www.espruino.com/Reference#l_Math_sqrt + */ + sqrt(x: number): number; + + /** + * + * @param {number} x - The value to round up + * @returns {number} x, rounded upwards to the nearest integer + * @url http://www.espruino.com/Reference#l_Math_ceil + */ + ceil(x: number): number; + + /** + * + * @param {number} x - The value to round down + * @returns {number} x, rounded downwards to the nearest integer + * @url http://www.espruino.com/Reference#l_Math_floor + */ + floor(x: number): number; + + /** + * + * @param {number} x - The value raise E to the power of + * @returns {number} E^x + * @url http://www.espruino.com/Reference#l_Math_exp + */ + exp(x: number): number; + + /** + * + * @param {number} x - The value to take the logarithm (base E) root of + * @returns {number} The log (base E) of x + * @url http://www.espruino.com/Reference#l_Math_log + */ + log(x: number): number; + + /** + * DEPRECATED - Please use `E.clip()` instead. Clip a number to be between min and + * max (inclusive) + * + * @param {number} x - A floating point value to clip + * @param {number} min - The smallest the value should be + * @param {number} max - The largest the value should be + * @returns {number} The value of x, clipped so as not to be below min or above max. + * @url http://www.espruino.com/Reference#l_Math_clip + */ + clip(x: number, min: number, max: number): number; + + /** + * DEPRECATED - This is not part of standard JavaScript libraries + * Wrap a number around if it is less than 0 or greater than or equal to max. For + * instance you might do: ```Math.wrap(angleInDegrees, 360)``` + * + * @param {number} x - A floating point value to wrap + * @param {number} max - The largest the value should be + * @returns {number} The value of x, wrapped so as not to be below min or above max. + * @url http://www.espruino.com/Reference#l_Math_wrap + */ + wrap(x: number, max: number): number; + + /** + * Find the minimum of a series of numbers + * + * @param {any} args - Floating point values to clip + * @returns {number} The minimum of the supplied values + * @url http://www.espruino.com/Reference#l_Math_min + */ + min(...args: any[]): number; + + /** + * Find the maximum of a series of numbers + * + * @param {any} args - Floating point values to clip + * @returns {number} The maximum of the supplied values + * @url http://www.espruino.com/Reference#l_Math_max + */ + max(...args: any[]): number; +} + +interface Math { + +} + +/** + * This is a standard JavaScript class that contains useful Maths routines + * @url http://www.espruino.com/Reference#Math + */ +declare const Math: MathConstructor + +/** + * Class containing an instance of TFMicroInterpreter + * @url http://www.espruino.com/Reference#TFMicroInterpreter + */ +declare class TFMicroInterpreter { + + + /** + * @returns {any} An arraybuffer referencing the input data + * @url http://www.espruino.com/Reference#l_TFMicroInterpreter_getInput + */ + getInput(): ArrayBufferView; + + /** + * @returns {any} An arraybuffer referencing the output data + * @url http://www.espruino.com/Reference#l_TFMicroInterpreter_getOutput + */ + getOutput(): ArrayBufferView; + + /** + * @url http://www.espruino.com/Reference#l_TFMicroInterpreter_invoke + */ + invoke(): void; +} + +/** + * Class containing utility functions for accessing IO on the hexagonal badge + * @url http://www.espruino.com/Reference#Badge + */ +declare class Badge { + /** + * Capacitive sense - the higher the capacitance, the higher the number returned. + * Supply a corner number between 1 and 6, and an integer value will be returned + * that is proportional to the capacitance + * + * @param {number} corner - The corner to use + * @returns {number} Capacitive sense counter + * @url http://www.espruino.com/Reference#l_Badge_capSense + */ + static capSense(corner: number): number; + + /** + * Return an approximate battery percentage remaining based on a normal CR2032 + * battery (2.8 - 2.2v) + * @returns {number} A percentage between 0 and 100 + * @url http://www.espruino.com/Reference#l_Badge_getBatteryPercentage + */ + static getBatteryPercentage(): number; + + /** + * Set the LCD's contrast + * + * @param {number} c - Contrast between 0 and 1 + * @url http://www.espruino.com/Reference#l_Badge_setContrast + */ + static setContrast(c: number): void; + + +} + +/** + * Class containing [Puck.js's](http://www.puck-js.com) utility functions. + * @url http://www.espruino.com/Reference#Puck + */ +declare class Puck { + /** + * Turn on the magnetometer, take a single reading, and then turn it off again. + * An object of the form `{x,y,z}` is returned containing magnetometer readings. + * Due to residual magnetism in the Puck and magnetometer itself, with no magnetic + * field the Puck will not return `{x:0,y:0,z:0}`. + * Instead, it's up to you to figure out what the 'zero value' is for your Puck in + * your location and to then subtract that from the value returned. If you're not + * trying to measure the Earth's magnetic field then it's a good idea to just take + * a reading at startup and use that. + * With the aerial at the top of the board, the `y` reading is vertical, `x` is + * horizontal, and `z` is through the board. + * Readings are in increments of 0.1 micro Tesla (uT). The Earth's magnetic field + * varies from around 25-60 uT, so the reading will vary by 250 to 600 depending on + * location. + * @returns {any} An Object `{x,y,z}` of magnetometer readings as integers + * @url http://www.espruino.com/Reference#l_Puck_mag + */ + static mag(): any; + + /** + * Turn on the magnetometer, take a single temperature reading from the MAG3110 + * chip, and then turn it off again. + * (If the magnetometer is already on, this just returns the last reading obtained) + * `E.getTemperature()` uses the microcontroller's temperature sensor, but this + * uses the magnetometer's. + * The reading obtained is an integer (so no decimal places), but the sensitivity + * is factory trimmed. to 1°C, however the temperature offset isn't - so + * absolute readings may still need calibrating. + * @returns {number} Temperature in degrees C + * @url http://www.espruino.com/Reference#l_Puck_magTemp + */ + static magTemp(): number; + + /** + * Called after `Puck.magOn()` every time magnetometer data is sampled. There is + * one argument which is an object of the form `{x,y,z}` containing magnetometer + * readings as integers (for more information see `Puck.mag()`). + * Check out [the Puck.js page on the + * magnetometer](http://www.espruino.com/Puck.js#on-board-peripherals) for more + * information. + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_Puck_mag + */ + static on(event: "mag", callback: () => void): void; + + /** + * Only on Puck.js v2.0 + * Called after `Puck.accelOn()` every time accelerometer data is sampled. There is + * one argument which is an object of the form `{acc:{x,y,z}, gyro:{x,y,z}}` + * containing the data. + * The data is as it comes off the accelerometer and is not scaled to 1g. For more + * information see `Puck.accel()` or [the Puck.js page on the + * magnetometer](http://www.espruino.com/Puck.js#on-board-peripherals). + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_Puck_accel + */ + static on(event: "accel", callback: () => void): void; + + /** + * Turn the magnetometer on and start periodic sampling. Samples will then cause a + * 'mag' event on 'Puck': + * ``` + * Puck.magOn(); + * Puck.on('mag', function(xyz) { + * console.log(xyz); + * // {x:..., y:..., z:...} + * }); + * // Turn events off with Puck.magOff(); + * ``` + * This call will be ignored if the sampling is already on. + * If given an argument, the sample rate is set (if not, it's at 0.63 Hz). The + * sample rate must be one of the following (resulting in the given power + * consumption): + * * 80 Hz - 900uA + * * 40 Hz - 550uA + * * 20 Hz - 275uA + * * 10 Hz - 137uA + * * 5 Hz - 69uA + * * 2.5 Hz - 34uA + * * 1.25 Hz - 17uA + * * 0.63 Hz - 8uA + * * 0.31 Hz - 8uA + * * 0.16 Hz - 8uA + * * 0.08 Hz - 8uA + * When the battery level drops too low while sampling is turned on, the + * magnetometer may stop sampling without warning, even while other Puck functions + * continue uninterrupted. + * Check out [the Puck.js page on the + * magnetometer](http://www.espruino.com/Puck.js#on-board-peripherals) for more + * information. + * + * @param {number} samplerate - The sample rate in Hz, or undefined + * @url http://www.espruino.com/Reference#l_Puck_magOn + */ + static magOn(samplerate: number): void; + + /** + * Turn the magnetometer off + * @url http://www.espruino.com/Reference#l_Puck_magOff + */ + static magOff(): void; + + /** + * Writes a register on the LIS3MDL / MAX3110 Magnetometer. Can be used for + * configuring advanced functions. + * Check out [the Puck.js page on the + * magnetometer](http://www.espruino.com/Puck.js#on-board-peripherals) for more + * information and links to modules that use this function. + * + * @param {number} reg + * @param {number} data + * @url http://www.espruino.com/Reference#l_Puck_magWr + */ + static magWr(reg: number, data: number): void; + + /** + * Reads a register from the LIS3MDL / MAX3110 Magnetometer. Can be used for + * configuring advanced functions. + * Check out [the Puck.js page on the + * magnetometer](http://www.espruino.com/Puck.js#on-board-peripherals) for more + * information and links to modules that use this function. + * + * @param {number} reg + * @returns {number} + * @url http://www.espruino.com/Reference#l_Puck_magRd + */ + static magRd(reg: number): number; + + /** + * On Puck.js v2.0 this will use the on-board PCT2075TP temperature sensor, but on + * Puck.js the less accurate on-chip Temperature sensor is used. + * @returns {number} Temperature in degrees C + * @url http://www.espruino.com/Reference#l_Puck_getTemperature + */ + static getTemperature(): number; + + /** + * Accepted values are: + * * 1.6 Hz (no Gyro) - 40uA (2v05 and later firmware) + * * 12.5 Hz (with Gyro)- 350uA + * * 26 Hz (with Gyro) - 450 uA + * * 52 Hz (with Gyro) - 600 uA + * * 104 Hz (with Gyro) - 900 uA + * * 208 Hz (with Gyro) - 1500 uA + * * 416 Hz (with Gyro) (not recommended) + * * 833 Hz (with Gyro) (not recommended) + * * 1660 Hz (with Gyro) (not recommended) + * Once `Puck.accelOn()` is called, the `Puck.accel` event will be called each time + * data is received. `Puck.accelOff()` can be called to turn the accelerometer off. + * For instance to light the red LED whenever Puck.js is face up: + * ``` + * Puck.on('accel', function(a) { + * digitalWrite(LED1, a.acc.z > 0); + * }); + * Puck.accelOn(); + * ``` + * Check out [the Puck.js page on the + * accelerometer](http://www.espruino.com/Puck.js#on-board-peripherals) for more + * information. + * + * @param {number} samplerate - The sample rate in Hz, or undefined + * @url http://www.espruino.com/Reference#l_Puck_accelOn + */ + static accelOn(samplerate: number): void; + + /** + * Turn the accelerometer off after it has been turned on by `Puck.accelOn()`. + * Check out [the Puck.js page on the + * accelerometer](http://www.espruino.com/Puck.js#on-board-peripherals) for more + * information. + * @url http://www.espruino.com/Reference#l_Puck_accelOff + */ + static accelOff(): void; + + /** + * Turn on the accelerometer, take a single reading, and then turn it off again. + * The values reported are the raw values from the chip. In normal configuration: + * * accelerometer: full-scale (32768) is 4g, so you need to divide by 8192 to get + * correctly scaled values + * * gyro: full-scale (32768) is 245 dps, so you need to divide by 134 to get + * correctly scaled values + * If taking more than one reading, we'd suggest you use `Puck.accelOn()` and the + * `Puck.accel` event. + * @returns {any} An Object `{acc:{x,y,z}, gyro:{x,y,z}}` of accelerometer/gyro readings + * @url http://www.espruino.com/Reference#l_Puck_accel + */ + static accel(): any; + + /** + * Writes a register on the LSM6DS3TR-C Accelerometer. Can be used for configuring + * advanced functions. + * Check out [the Puck.js page on the + * accelerometer](http://www.espruino.com/Puck.js#on-board-peripherals) for more + * information and links to modules that use this function. + * + * @param {number} reg + * @param {number} data + * @url http://www.espruino.com/Reference#l_Puck_accelWr + */ + static accelWr(reg: number, data: number): void; + + /** + * Reads a register from the LSM6DS3TR-C Accelerometer. Can be used for configuring + * advanced functions. + * Check out [the Puck.js page on the + * accelerometer](http://www.espruino.com/Puck.js#on-board-peripherals) for more + * information and links to modules that use this function. + * + * @param {number} reg + * @returns {number} + * @url http://www.espruino.com/Reference#l_Puck_accelRd + */ + static accelRd(reg: number): number; + + /** + * Transmit the given set of IR pulses - data should be an array of pulse times in + * milliseconds (as `[on, off, on, off, on, etc]`). + * For example `Puck.IR(pulseTimes)` - see http://www.espruino.com/Puck.js+Infrared + * for a full example. + * You can also attach an external LED to Puck.js, in which case you can just + * execute `Puck.IR(pulseTimes, led_cathode, led_anode)` + * It is also possible to just supply a single pin for IR transmission with + * `Puck.IR(pulseTimes, led_anode)` (on 2v05 and above). + * + * @param {any} data - An array of pulse lengths, in milliseconds + * @param {Pin} cathode - (optional) pin to use for IR LED cathode - if not defined, the built-in IR LED is used + * @param {Pin} anode - (optional) pin to use for IR LED anode - if not defined, the built-in IR LED is used + * @url http://www.espruino.com/Reference#l_Puck_IR + */ + static IR(data: any, cathode: Pin, anode: Pin): void; + + /** + * Capacitive sense - the higher the capacitance, the higher the number returned. + * If called without arguments, a value depending on the capacitance of what is + * attached to pin D11 will be returned. If you attach a length of wire to D11, + * you'll be able to see a higher value returned when your hand is near the wire + * than when it is away. + * You can also supply pins to use yourself, however if you do this then the TX pin + * must be connected to RX pin and sense plate via a roughly 1MOhm resistor. + * When not supplying pins, Puck.js uses an internal resistor between D12(tx) and + * D11(rx). + * + * @param {Pin} tx + * @param {Pin} rx + * @returns {number} Capacitive sense counter + * @url http://www.espruino.com/Reference#l_Puck_capSense + */ + static capSense(tx: Pin, rx: Pin): number; + + /** + * Return a light value based on the light the red LED is seeing. + * **Note:** If called more than 5 times per second, the received light value may + * not be accurate. + * @returns {number} A light value from 0 to 1 + * @url http://www.espruino.com/Reference#l_Puck_light + */ + static light(): number; + + /** + * DEPRECATED - Please use `E.getBattery()` instead. + * Return an approximate battery percentage remaining based on a normal CR2032 + * battery (2.8 - 2.2v). + * @returns {number} A percentage between 0 and 100 + * @url http://www.espruino.com/Reference#l_Puck_getBatteryPercentage + */ + static getBatteryPercentage(): number; + + /** + * Run a self-test, and return true for a pass. This checks for shorts between + * pins, so your Puck shouldn't have anything connected to it. + * **Note:** This self-test auto starts if you hold the button on your Puck down + * while inserting the battery, leave it pressed for 3 seconds (while the green LED + * is lit) and release it soon after all LEDs turn on. 5 red blinks is a fail, 5 + * green is a pass. + * If the self test fails, it'll set the Puck.js Bluetooth advertising name to + * `Puck.js !ERR` where ERR is a 3 letter error code. + * @returns {boolean} True if the self-test passed + * @url http://www.espruino.com/Reference#l_Puck_selfTest + */ + static selfTest(): boolean; + + +} + +/** + * This is the File object - it allows you to stream data to and from files (As + * opposed to the `require('fs').readFile(..)` style functions that read an entire + * file). + * To create a File object, you must type ```var fd = + * E.openFile('filepath','mode')``` - see [E.openFile](#l_E_openFile) for more + * information. + * **Note:** If you want to remove an SD card after you have started using it, you + * *must* call `E.unmountSD()` or you may cause damage to the card. + * @url http://www.espruino.com/Reference#File + */ +declare class File { + + + /** + * Close an open file. + * @url http://www.espruino.com/Reference#l_File_close + */ + close(): void; + + /** + * Write data to a file. + * **Note:** By default this function flushes all changes to the SD card, which + * makes it slow (but also safe!). You can use `E.setFlags({unsyncFiles:1})` to + * disable this behaviour and really speed up writes - but then you must be sure to + * close all files you are writing before power is lost or you will cause damage to + * your SD card's filesystem. + * + * @param {any} buffer - A string containing the bytes to write + * @returns {number} the number of bytes written + * @url http://www.espruino.com/Reference#l_File_write + */ + write(buffer: any): number; + + /** + * Read data in a file in byte size chunks + * + * @param {number} length - is an integer specifying the number of bytes to read. + * @returns {any} A string containing the characters that were read + * @url http://www.espruino.com/Reference#l_File_read + */ + read(length: number): any; + + /** + * Skip the specified number of bytes forward in the file + * + * @param {number} nBytes - is a positive integer specifying the number of bytes to skip forwards. + * @url http://www.espruino.com/Reference#l_File_skip + */ + skip(nBytes: number): void; + + /** + * Seek to a certain position in the file + * + * @param {number} nBytes - is an integer specifying the number of bytes to skip forwards. + * @url http://www.espruino.com/Reference#l_File_seek + */ + seek(nBytes: number): void; + + /** + * Pipe this file to a stream (an object with a 'write' method) + * + * @param {any} destination - The destination file/stream that will receive content from the source. + * @param {any} options + * An optional object `{ chunkSize : int=32, end : bool=true, complete : function }` + * chunkSize : The amount of data to pipe from source to destination at a time + * complete : a function to call when the pipe activity is complete + * end : call the 'end' function on the destination when the source is finished + * @url http://www.espruino.com/Reference#l_File_pipe + */ + pipe(destination: any, options: any): void; +} + +/** + * Class containing utility functions for the Seeed WIO LTE board + * @url http://www.espruino.com/Reference#WioLTE + */ +declare class WioLTE { + /** + * Set the WIO's LED + * + * @param {number} red - 0-255, red LED intensity + * @param {number} green - 0-255, green LED intensity + * @param {number} blue - 0-255, blue LED intensity + * @url http://www.espruino.com/Reference#l_WioLTE_LED + */ + static LED(red: number, green: number, blue: number): void; + + /** + * Set the power of Grove connectors, except for `D38` and `D39` which are always + * on. + * + * @param {boolean} onoff - Whether to turn the Grove connectors power on or off (D38/D39 are always powered) + * @url http://www.espruino.com/Reference#l_WioLTE_setGrovePower + */ + static setGrovePower(onoff: boolean): void; + + /** + * Turn power to the WIO's LED on or off. + * Turning the LED on won't immediately display a color - that must be done with + * `WioLTE.LED(r,g,b)` + * + * @param {boolean} onoff - true = on, false = off + * @url http://www.espruino.com/Reference#l_WioLTE_setLEDPower + */ + static setLEDPower(onoff: boolean): void; + + /** + * @returns {any} + * @url http://www.espruino.com/Reference#l_WioLTE_D38 + */ + static D38: any; + + /** + * @returns {any} + * @url http://www.espruino.com/Reference#l_WioLTE_D20 + */ + static D20: any; + + /** + * @returns {any} + * @url http://www.espruino.com/Reference#l_WioLTE_A6 + */ + static A6: any; + + /** + * @returns {any} + * @url http://www.espruino.com/Reference#l_WioLTE_I2C + */ + static I2C: any; + + /** + * @returns {any} + * @url http://www.espruino.com/Reference#l_WioLTE_UART + */ + static UART: any; + + /** + * @returns {any} + * @url http://www.espruino.com/Reference#l_WioLTE_A4 + */ + static A4: any; + + +} + +/** + * Class containing utility functions for + * [Pixl.js](http://www.espruino.com/Pixl.js) + * @url http://www.espruino.com/Reference#Pixl + */ +declare class Pixl { + /** + * DEPRECATED - Please use `E.getBattery()` instead. + * Return an approximate battery percentage remaining based on a normal CR2032 + * battery (2.8 - 2.2v) + * @returns {number} A percentage between 0 and 100 + * @url http://www.espruino.com/Reference#l_Pixl_getBatteryPercentage + */ + static getBatteryPercentage(): number; + + /** + * Set the LCD's contrast + * + * @param {number} c - Contrast between 0 and 1 + * @url http://www.espruino.com/Reference#l_Pixl_setContrast + */ + static setContrast(c: number): void; + + /** + * This function can be used to turn Pixl.js's LCD off or on. + * * With the LCD off, Pixl.js draws around 0.1mA + * * With the LCD on, Pixl.js draws around 0.25mA + * + * @param {boolean} isOn - True if the LCD should be on, false if not + * @url http://www.espruino.com/Reference#l_Pixl_setLCDPower + */ + static setLCDPower(isOn: boolean): void; + + /** + * Writes a command directly to the ST7567 LCD controller + * + * @param {number} c + * @url http://www.espruino.com/Reference#l_Pixl_lcdw + */ + static lcdw(c: number): void; + + /** + * Display a menu on Pixl.js's screen, and set up the buttons to navigate through + * it. + * DEPRECATED: Use `E.showMenu` + * + * @param {any} menu - An object containing name->function mappings to to be used in a menu + * @returns {any} A menu object with `draw`, `move` and `select` functions + * @url http://www.espruino.com/Reference#l_Pixl_menu + */ + static menu(menu: Menu): MenuInstance; + + +} + +/** + * This class exists in order to interface Espruino with fast-moving trigger + * wheels. Trigger wheels are physical discs with evenly spaced teeth cut into + * them, and often with one or two teeth next to each other missing. A sensor sends + * a signal whenever a tooth passed by, and this allows a device to measure not + * only RPM, but absolute position. + * This class is currently in testing - it is NOT AVAILABLE on normal boards. + * @url http://www.espruino.com/Reference#Trig + */ +declare class Trig { + /** + * Get the position of the trigger wheel at the given time (from getTime) + * + * @param {number} time - The time at which to find the position + * @returns {number} The position of the trigger wheel in degrees - as a floating point number + * @url http://www.espruino.com/Reference#l_Trig_getPosAtTime + */ + static getPosAtTime(time: number): number; + + /** + * Initialise the trigger class + * + * @param {Pin} pin - The pin to use for triggering + * @param {any} options - Additional options as an object. defaults are: ```{teethTotal:60,teethMissing:2,minRPM:30,keyPosition:0}``` + * @url http://www.espruino.com/Reference#l_Trig_setup + */ + static setup(pin: Pin, options: any): void; + + /** + * Set a trigger for a certain point in the cycle + * + * @param {number} num - The trigger number (0..7) + * @param {number} pos - The position (in degrees) to fire the trigger at + * @param {any} pins - An array of pins to pulse (max 4) + * @param {number} pulseLength - The time (in msec) to pulse for + * @url http://www.espruino.com/Reference#l_Trig_setTrigger + */ + static setTrigger(num: number, pos: number, pins: any, pulseLength: number): void; + + /** + * Disable a trigger + * + * @param {number} num - The trigger number (0..7) + * @url http://www.espruino.com/Reference#l_Trig_killTrigger + */ + static killTrigger(num: number): void; + + /** + * Get the current state of a trigger + * + * @param {number} num - The trigger number (0..7) + * @returns {any} A structure containing all information about the trigger + * @url http://www.espruino.com/Reference#l_Trig_getTrigger + */ + static getTrigger(num: number): any; + + /** + * Get the RPM of the trigger wheel + * @returns {number} The current RPM of the trigger wheel + * @url http://www.espruino.com/Reference#l_Trig_getRPM + */ + static getRPM(): number; + + /** + * Get the current error flags from the trigger wheel - and zero them + * @returns {number} The error flags + * @url http://www.espruino.com/Reference#l_Trig_getErrors + */ + static getErrors(): number; + + /** + * Get the current error flags from the trigger wheel - and zero them + * @returns {any} An array of error strings + * @url http://www.espruino.com/Reference#l_Trig_getErrorArray + */ + static getErrorArray(): any; + + +} + +/** + * Class containing AES encryption/decryption + * **Note:** This library is currently only included in builds for boards where + * there is space. For other boards there is `crypto.js` which implements SHA1 in + * JS. + * @url http://www.espruino.com/Reference#AES + */ +declare class AES { + /** + * + * @param {any} passphrase - Message to encrypt + * @param {any} key - Key to encrypt message - must be an ArrayBuffer of 128, 192, or 256 BITS + * @param {any} options - An optional object, may specify `{ iv : new Uint8Array(16), mode : 'CBC|CFB|CTR|OFB|ECB' }` + * @returns {any} Returns an ArrayBuffer + * @url http://www.espruino.com/Reference#l_AES_encrypt + */ + static encrypt(passphrase: any, key: any, options: any): ArrayBuffer; + + /** + * + * @param {any} passphrase - Message to decrypt + * @param {any} key - Key to encrypt message - must be an ArrayBuffer of 128, 192, or 256 BITS + * @param {any} options - An optional object, may specify `{ iv : new Uint8Array(16), mode : 'CBC|CFB|CTR|OFB|ECB' }` + * @returns {any} Returns an ArrayBuffer + * @url http://www.espruino.com/Reference#l_AES_decrypt + */ + static decrypt(passphrase: any, key: any, options: any): ArrayBuffer; + + +} + +/** + * This class provides Graphics operations that can be applied to a surface. + * Use Graphics.createXXX to create a graphics object that renders in the way you + * want. See [the Graphics page](https://www.espruino.com/Graphics) for more + * information. + * **Note:** On boards that contain an LCD, there is a built-in 'LCD' object of + * type Graphics. For instance to draw a line you'd type: + * ```LCD.drawLine(0,0,100,100)``` + * @url http://www.espruino.com/Reference#Graphics + */ +declare class Graphics { + /** + * On devices like Pixl.js or HYSTM boards that contain a built-in display this + * will return an instance of the graphics class that can be used to access that + * display. + * Internally, this is stored as a member called `gfx` inside the 'hiddenRoot'. + * @returns {any} An instance of `Graphics` or undefined + * @url http://www.espruino.com/Reference#l_Graphics_getInstance + */ + static getInstance(): Graphics | undefined + + /** + * Create a Graphics object that renders to an Array Buffer. This will have a field + * called 'buffer' that can get used to get at the buffer itself + * + * @param {number} width - Pixels wide + * @param {number} height - Pixels high + * @param {number} bpp - Number of bits per pixel + * @param {any} options + * An object of other options. `{ zigzag : true/false(default), vertical_byte : true/false(default), msb : true/false(default), color_order: 'rgb'(default),'bgr',etc }` + * `zigzag` = whether to alternate the direction of scanlines for rows + * `vertical_byte` = whether to align bits in a byte vertically or not + * `msb` = when bits<8, store pixels most significant bit first, when bits>8, store most significant byte first + * `interleavex` = Pixels 0,2,4,etc are from the top half of the image, 1,3,5,etc from the bottom half. Used for P3 LED panels. + * `color_order` = re-orders the colour values that are supplied via setColor + * @returns {any} The new Graphics object + * @url http://www.espruino.com/Reference#l_Graphics_createArrayBuffer + */ + static createArrayBuffer(width: number, height: number, bpp: number, options?: { zigzag?: boolean, vertical_byte?: boolean, msb?: boolean, color_order?: "rgb" | "rbg" | "brg" | "bgr" | "grb" | "gbr" }): Graphics; + + /** + * Create a Graphics object that renders by calling a JavaScript callback function + * to draw pixels + * + * @param {number} width - Pixels wide + * @param {number} height - Pixels high + * @param {number} bpp - Number of bits per pixel + * @param {any} callback - A function of the form ```function(x,y,col)``` that is called whenever a pixel needs to be drawn, or an object with: ```{setPixel:function(x,y,col),fillRect:function(x1,y1,x2,y2,col)}```. All arguments are already bounds checked. + * @returns {any} The new Graphics object + * @url http://www.espruino.com/Reference#l_Graphics_createCallback + */ + static createCallback(width: number, height: number, bpp: number, callback: ((x: number, y: number, col: number) => void) | { setPixel: (x: number, y: number, col: number) => void; fillRect: (x1: number, y1: number, x2: number, y2: number, col: number) => void }): Graphics; + + /** + * Create a Graphics object that renders to SDL window (Linux-based devices only) + * + * @param {number} width - Pixels wide + * @param {number} height - Pixels high + * @param {number} bpp - Bits per pixel (8,16,24 or 32 supported) + * @returns {any} The new Graphics object + * @url http://www.espruino.com/Reference#l_Graphics_createSDL + */ + static createSDL(width: number, height: number, bpp: number): Graphics; + + /** + * Create a simple Black and White image for use with `Graphics.drawImage`. + * Use as follows: + * ``` + * var img = Graphics.createImage(` + * XXXXXXXXX + * X X + * X X X + * X X X + * X X + * XXXXXXXXX + * `); + * g.drawImage(img, x,y); + * ``` + * If the characters at the beginning and end of the string are newlines, they will + * be ignored. Spaces are treated as `0`, and any other character is a `1` + * + * @param {any} str - A String containing a newline-separated image - space is 0, anything else is 1 + * @returns {any} An Image object that can be used with `Graphics.drawImage` + * @url http://www.espruino.com/Reference#l_Graphics_createImage + */ + static createImage(str: string): ImageObject; + + /** + * Set the current font + * + * @param {number} scale - (optional) If >1 the font will be scaled up by that amount + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_setFont6x15 + */ + setFont6x15(scale: number): Graphics; + + /** + * Set the current font + * + * @param {number} scale - (optional) If >1 the font will be scaled up by that amount + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_setFont12x20 + */ + setFont12x20(scale: number): Graphics; + + /** + * On instances of graphics that drive a display with an offscreen buffer, calling + * this function will copy the contents of the offscreen buffer to the screen. + * Call this when you have drawn something to Graphics and you want it shown on the + * screen. + * If a display does not have an offscreen buffer, it may not have a `g.flip()` + * method. + * On Bangle.js 1, there are different graphics modes chosen with + * `Bangle.setLCDMode()`. The default mode is unbuffered and in this mode + * `g.flip()` does not affect the screen contents. + * On some devices, this command will attempt to only update the areas of the + * screen that have changed in order to increase speed. If you have accessed the + * `Graphics.buffer` directly then you may need to use `Graphics.flip(true)` to + * force a full update of the screen. + * + * @param {boolean} [all] - [optional] (only on some devices) If `true` then copy all pixels, not just those that have changed. + * @url http://www.espruino.com/Reference#l_Graphics_flip + */ + flip(all?: boolean): void; + + /** + * On Graphics instances with an offscreen buffer, this is an `ArrayBuffer` that + * provides access to the underlying pixel data. + * ``` + * g=Graphics.createArrayBuffer(8,8,8) + * g.drawLine(0,0,7,7) + * print(new Uint8Array(g.buffer)) + * new Uint8Array([ + * 255, 0, 0, 0, 0, 0, 0, 0, + * 0, 255, 0, 0, 0, 0, 0, 0, + * 0, 0, 255, 0, 0, 0, 0, 0, + * 0, 0, 0, 255, 0, 0, 0, 0, + * 0, 0, 0, 0, 255, 0, 0, 0, + * 0, 0, 0, 0, 0, 255, 0, 0, + * 0, 0, 0, 0, 0, 0, 255, 0, + * 0, 0, 0, 0, 0, 0, 0, 255]) + * ``` + * @returns {any} An ArrayBuffer (or not defined on Graphics instances not created with `Graphics.createArrayBuffer`) + * @url http://www.espruino.com/Reference#l_Graphics_buffer + */ + buffer: IsBuffer extends true ? ArrayBuffer : undefined + + /** + * The width of this Graphics instance + * @returns {number} The width of this Graphics instance + * @url http://www.espruino.com/Reference#l_Graphics_getWidth + */ + getWidth(): number; + + /** + * The height of this Graphics instance + * @returns {number} The height of this Graphics instance + * @url http://www.espruino.com/Reference#l_Graphics_getHeight + */ + getHeight(): number; + + /** + * The number of bits per pixel of this Graphics instance + * **Note:** Bangle.js 2 behaves a little differently here. The display is 3 bit, + * so `getBPP` returns 3 and `asBMP`/`asImage`/etc return 3 bit images. However in + * order to allow dithering, the colors returned by `Graphics.getColor` and + * `Graphics.theme` are actually 16 bits. + * @returns {number} The bits per pixel of this Graphics instance + * @url http://www.espruino.com/Reference#l_Graphics_getBPP + */ + getBPP(): number; + + /** + * Reset the state of Graphics to the defaults (e.g. Color, Font, etc) that would + * have been used when Graphics was initialised. + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_reset + */ + reset(): Graphics; + + /** + * Clear the LCD with the Background Color + * + * @param {boolean} [reset] - [optional] If `true`, resets the state of Graphics to the default (eg. Color, Font, etc) as if calling `Graphics.reset` + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_clear + */ + clear(reset?: boolean): Graphics; + + /** + * Fill a rectangular area in the Foreground Color + * On devices with enough memory, you can specify `{x,y,x2,y2,r}` as the first + * argument, which allows you to draw a rounded rectangle. + * + * @param {any} x1 - The left X coordinate OR an object containing `{x,y,x2,y2}` or `{x,y,w,h}` + * @param {number} y1 - The top Y coordinate + * @param {number} x2 - The right X coordinate + * @param {number} y2 - The bottom Y coordinate + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_fillRect + */ + fillRect(x1: number, y1: number, x2: number, y2: number): Graphics; + fillRect(rect: { x: number, y: number, x2: number, y2: number } | { x: number, y: number, w: number, h: number }): Graphics; + + /** + * Fill a rectangular area in the Background Color + * On devices with enough memory, you can specify `{x,y,x2,y2,r}` as the first + * argument, which allows you to draw a rounded rectangle. + * + * @param {any} x1 - The left X coordinate OR an object containing `{x,y,x2,y2}` or `{x,y,w,h}` + * @param {number} y1 - The top Y coordinate + * @param {number} x2 - The right X coordinate + * @param {number} y2 - The bottom Y coordinate + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_clearRect + */ + clearRect(x1: number, y1: number, x2: number, y2: number): Graphics; + clearRect(rect: { x: number, y: number, x2: number, y2: number } | { x: number, y: number, w: number, h: number }): Graphics; + + /** + * Draw an unfilled rectangle 1px wide in the Foreground Color + * + * @param {any} x1 - The left X coordinate OR an object containing `{x,y,x2,y2}` or `{x,y,w,h}` + * @param {number} y1 - The top Y coordinate + * @param {number} x2 - The right X coordinate + * @param {number} y2 - The bottom Y coordinate + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_drawRect + */ + drawRect(x1: number, y1: number, x2: number, y2: number): Graphics; + drawRect(rect: { x: number, y: number, x2: number, y2: number } | { x: number, y: number, w: number, h: number }): Graphics; + + /** + * Draw a filled circle in the Foreground Color + * + * @param {number} x - The X axis + * @param {number} y - The Y axis + * @param {number} rad - The circle radius + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_fillCircle + */ + fillCircle(x: number, y: number, rad: number): Graphics; + + /** + * Draw an unfilled circle 1px wide in the Foreground Color + * + * @param {number} x - The X axis + * @param {number} y - The Y axis + * @param {number} rad - The circle radius + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_drawCircle + */ + drawCircle(x: number, y: number, rad: number): Graphics; + + /** + * Draw a circle, centred at (x,y) with radius r in the current foreground color + * + * @param {number} x - Centre x-coordinate + * @param {number} y - Centre y-coordinate + * @param {number} r - Radius + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_drawCircleAA + */ + drawCircleAA(x: number, y: number, r: number): Graphics; + + /** + * Draw a filled ellipse in the Foreground Color + * + * @param {number} x1 - The left X coordinate + * @param {number} y1 - The top Y coordinate + * @param {number} x2 - The right X coordinate + * @param {number} y2 - The bottom Y coordinate + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_fillEllipse + */ + fillEllipse(x1: number, y1: number, x2: number, y2: number): Graphics; + + /** + * Draw an ellipse in the Foreground Color + * + * @param {number} x1 - The left X coordinate + * @param {number} y1 - The top Y coordinate + * @param {number} x2 - The right X coordinate + * @param {number} y2 - The bottom Y coordinate + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_drawEllipse + */ + drawEllipse(x1: number, y1: number, x2: number, y2: number): Graphics; + + /** + * Get a pixel's color + * + * @param {number} x - The left + * @param {number} y - The top + * @returns {number} The color + * @url http://www.espruino.com/Reference#l_Graphics_getPixel + */ + getPixel(x: number, y: number): number; + + /** + * Set a pixel's color + * + * @param {number} x - The left + * @param {number} y - The top + * @param {any} col - The color (if `undefined`, the foreground color is useD) + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_setPixel + */ + setPixel(x: number, y: number, col?: ColorResolvable): Graphics; + + /** + * Work out the color value to be used in the current bit depth based on the arguments. + * This is used internally by setColor and setBgColor + * ``` + * // 1 bit + * g.toColor(1,1,1) => 1 + * // 16 bit + * g.toColor(1,0,0) => 0xF800 + * ``` + * + * @param {any} r - Red (between 0 and 1) **OR** an integer representing the color in the current bit depth and color order **OR** a hexidecimal color string of the form `'#rrggbb' or `'#rgb'` + * @param {any} g - Green (between 0 and 1) + * @param {any} b - Blue (between 0 and 1) + * @returns {number} The color index represented by the arguments + * @url http://www.espruino.com/Reference#l_Graphics_toColor + */ + toColor(r: number, g: number, b: number): number; + toColor(col: ColorResolvable): number; + + /** + * Blend between two colors, and return the result. + * ``` + * // dark yellow - halfway between red and green + * var col = g.blendColor("#f00","#0f0", 0.5); + * // Get a color 25% brighter than the theme's background colour + * var col = g.blendColor(g.theme.fg,g.theme.bg, 0.75); + * // then... + * g.setColor(col).fillRect(10,10,100,100); + * ``` + * + * @param {any} col_a - Color to blend from (either a single integer color value, or a string) + * @param {any} col_b - Color to blend to (either a single integer color value, or a string) + * @param {any} amt - The amount to blend. 0=col_a, 1=col_b, 0.5=halfway between (and so on) + * @returns {number} The color index represented by the blended colors + * @url http://www.espruino.com/Reference#l_Graphics_blendColor + */ + blendColor(col_a: ColorResolvable, col_b: ColorResolvable, amt: number): number; + + /** + * Set the color to use for subsequent drawing operations. + * If just `r` is specified as an integer, the numeric value will be written directly into a pixel. eg. On a 24 bit `Graphics` instance you set bright blue with either `g.setColor(0,0,1)` or `g.setColor(0x0000FF)`. + * A good shortcut to ensure you get white on all platforms is to use `g.setColor(-1)` + * The mapping is as follows: + * * 32 bit: `r,g,b` => `0xFFrrggbb` + * * 24 bit: `r,g,b` => `0xrrggbb` + * * 16 bit: `r,g,b` => `0brrrrrggggggbbbbb` (RGB565) + * * Other bpp: `r,g,b` => white if `r+g+b > 50%`, otherwise black (use `r` on its own as an integer) + * If you specified `color_order` when creating the `Graphics` instance, `r`,`g` and `b` will be swapped as you specified. + * **Note:** On devices with low flash memory, `r` **must** be an integer representing the color in the current bit depth. It cannot + * be a floating point value, and `g` and `b` are ignored. + * + * @param {any} r - Red (between 0 and 1) **OR** an integer representing the color in the current bit depth and color order **OR** a hexidecimal color string of the form `'#012345'` + * @param {any} [g] - [optional] Green (between 0 and 1) + * @param {any} [b] - [optional] Blue (between 0 and 1) + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_setColor + */ + setColor(r: number, g: number, b: number): number; + setColor(col: ColorResolvable): number; + + /** + * Set the background color to use for subsequent drawing operations. + * See `Graphics.setColor` for more information on the mapping of `r`, `g`, and `b` to pixel values. + * **Note:** On devices with low flash memory, `r` **must** be an integer representing the color in the current bit depth. It cannot + * be a floating point value, and `g` and `b` are ignored. + * + * @param {any} r - Red (between 0 and 1) **OR** an integer representing the color in the current bit depth and color order **OR** a hexidecimal color string of the form `'#012345'` + * @param {any} g - Green (between 0 and 1) + * @param {any} b - Blue (between 0 and 1) + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_setBgColor + */ + setBgColor(r: number, g: number, b: number): number; + setBgColor(col: ColorResolvable): number; + + /** + * Get the color to use for subsequent drawing operations + * @returns {number} The integer value of the colour + * @url http://www.espruino.com/Reference#l_Graphics_getColor + */ + getColor(): number; + + /** + * Get the background color to use for subsequent drawing operations + * @returns {number} The integer value of the colour + * @url http://www.espruino.com/Reference#l_Graphics_getBgColor + */ + getBgColor(): number; + + /** + * This sets the 'clip rect' that subsequent drawing operations are clipped to sit + * between. + * These values are inclusive - e.g. `g.setClipRect(1,0,5,0)` will ensure that only + * pixel rows 1,2,3,4,5 are touched on column 0. + * **Note:** For maximum flexibility on Bangle.js 1, the values here are not range + * checked. For normal use, X and Y should be between 0 and + * `getWidth()-1`/`getHeight()-1`. + * **Note:** The x/y values here are rotated, so that if `Graphics.setRotation` is + * used they correspond to the coordinates given to the draw functions, *not to the + * physical device pixels*. + * + * @param {number} x1 - Top left X coordinate + * @param {number} y1 - Top left Y coordinate + * @param {number} x2 - Bottom right X coordinate + * @param {number} y2 - Bottom right Y coordinate + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_setClipRect + */ + setClipRect(x1: number, y1: number, x2: number, y2: number): Graphics; + + /** + * Make subsequent calls to `drawString` use the built-in 4x6 pixel bitmapped Font + * It is recommended that you use `Graphics.setFont("4x6")` for more flexibility. + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_setFontBitmap + */ + setFontBitmap(): Graphics; + + /** + * Make subsequent calls to `drawString` use a Vector Font of the given height. + * It is recommended that you use `Graphics.setFont("Vector", size)` for more + * flexibility. + * + * @param {number} size - The height of the font, as an integer + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_setFontVector + */ + setFontVector(size: number): Graphics; + + /** + * Make subsequent calls to `drawString` use a Custom Font of the given height. See + * the [Fonts page](http://www.espruino.com/Fonts) for more information about + * custom fonts and how to create them. + * For examples of use, see the [font + * modules](https://www.espruino.com/Fonts#font-modules). + * **Note:** while you can specify the character code of the first character with + * `firstChar`, the newline character 13 will always be treated as a newline and + * not rendered. + * + * @param {any} bitmap - A column-first, MSB-first, 1bpp bitmap containing the font bitmap + * @param {number} firstChar - The first character in the font - usually 32 (space) + * @param {any} width - The width of each character in the font. Either an integer, or a string where each character represents the width + * @param {number} height - The height as an integer (max 255). Bits 8-15 represent the scale factor (eg. `2<<8` is twice the size). Bits 16-23 represent the BPP (0,1=1 bpp, 2=2 bpp, 4=4 bpp) + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_setFontCustom + */ + setFontCustom(bitmap: ArrayBuffer, firstChar: number, width: number | string, height: number): Graphics; + + /** + * Set the alignment for subsequent calls to `drawString` + * + * @param {number} x - X alignment. -1=left (default), 0=center, 1=right + * @param {number} y - Y alignment. -1=top (default), 0=center, 1=bottom + * @param {number} rotation - Rotation of the text. 0=normal, 1=90 degrees clockwise, 2=180, 3=270 + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_setFontAlign + */ + setFontAlign(x: -1 | 0 | 1, y?: -1 | 0 | 1, rotation?: 0 | 1 | 2 | 3): Graphics; + + /** + * Set the font by name. Various forms are available: + * * `g.setFont("4x6")` - standard 4x6 bitmap font + * * `g.setFont("Vector:12")` - vector font 12px high + * * `g.setFont("4x6:2")` - 4x6 bitmap font, doubled in size + * * `g.setFont("6x8:2x3")` - 6x8 bitmap font, doubled in width, tripled in height + * You can also use these forms, but they are not recommended: + * * `g.setFont("Vector12")` - vector font 12px high + * * `g.setFont("4x6",2)` - 4x6 bitmap font, doubled in size + * `g.getFont()` will return the current font as a String. + * For a list of available font names, you can use `g.getFonts()`. + * + * @param {any} name - The name of the font to use (if undefined, the standard 4x6 font will be used) + * @param {number} size - The size of the font (or undefined) + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_setFont + */ + setFont(name: FontNameWithScaleFactor): Graphics; + setFont(name: FontName, size: number): Graphics; + + /** + * Get the font by name - can be saved and used with `Graphics.setFont`. + * Normally this might return something like `"4x6"`, but if a scale factor is + * specified, a colon and then the size is reported, like "4x6:2" + * **Note:** For custom fonts, `Custom` is currently reported instead of the font + * name. + * @returns {any} Get the name of the current font + * @url http://www.espruino.com/Reference#l_Graphics_getFont + */ + getFont(): FontNameWithScaleFactor | "Custom" + + /** + * Return an array of all fonts currently in the Graphics library. + * **Note:** Vector fonts are specified as `Vector#` where `#` is the font height. + * As there are effectively infinite fonts, just `Vector` is included in the list. + * @returns {any} And array of font names + * @url http://www.espruino.com/Reference#l_Graphics_getFonts + */ + getFonts(): FontName[]; + + /** + * Return the height in pixels of the current font + * @returns {number} The height in pixels of the current font + * @url http://www.espruino.com/Reference#l_Graphics_getFontHeight + */ + getFontHeight(): number; + + /** + * Return the size in pixels of a string of text in the current font + * + * @param {any} str - The string + * @returns {number} The length of the string in pixels + * @url http://www.espruino.com/Reference#l_Graphics_stringWidth + */ + stringWidth(str: string): number; + + /** + * Return the width and height in pixels of a string of text in the current font + * + * @param {any} str - The string + * @returns {any} An object containing `{width,height}` of the string + * @url http://www.espruino.com/Reference#l_Graphics_stringMetrics + */ + stringMetrics(str: string): { width: number, height: number }; + + /** + * Wrap a string to the given pixel width using the current font, and return the + * lines as an array. + * To render within the screen's width you can do: + * ``` + * g.drawString(g.wrapString(text, g.getWidth()).join("\n")), + * ``` + * + * @param {any} str - The string + * @param {number} maxWidth - The width in pixels + * @returns {any} An array of lines that are all less than `maxWidth` + * @url http://www.espruino.com/Reference#l_Graphics_wrapString + */ + wrapString(str: string, maxWidth: number): string[]; + + /** + * Draw a string of text in the current font. + * ``` + * g.drawString("Hello World", 10, 10); + * ``` + * Images may also be embedded inside strings (e.g. to render Emoji or characters + * not in the current font). To do this, just add `0` then the image string ([about + * Images](http://www.espruino.com/Graphics#images-bitmaps)) For example: + * ``` + * g.drawString("Hi \0\7\5\1\x82 D\x17\xC0"); + * // draws: + * // # # # # # + * // # # # + * // ### ## # + * // # # # # # + * // # # ### ##### + * ``` + * + * @param {any} str - The string + * @param {number} x - The X position of the leftmost pixel + * @param {number} y - The Y position of the topmost pixel + * @param {boolean} solid - For bitmap fonts, should empty pixels be filled with the background color? + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_drawString + */ + drawString(str: string, x: number, y: number, solid?: boolean): Graphics; + + /** + * Draw a line between x1,y1 and x2,y2 in the current foreground color + * + * @param {number} x1 - The left + * @param {number} y1 - The top + * @param {number} x2 - The right + * @param {number} y2 - The bottom + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_drawLine + */ + drawLine(x1: number, y1: number, x2: number, y2: number): Graphics; + + /** + * Draw a line between x1,y1 and x2,y2 in the current foreground color + * + * @param {number} x1 - The left + * @param {number} y1 - The top + * @param {number} x2 - The right + * @param {number} y2 - The bottom + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_drawLineAA + */ + drawLineAA(x1: number, y1: number, x2: number, y2: number): Graphics; + + /** + * Draw a line from the last position of `lineTo` or `moveTo` to this position + * + * @param {number} x - X value + * @param {number} y - Y value + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_lineTo + */ + lineTo(x: number, y: number): Graphics; + + /** + * Move the cursor to a position - see lineTo + * + * @param {number} x - X value + * @param {number} y - Y value + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_moveTo + */ + moveTo(x: number, y: number): Graphics; + + /** + * Draw a polyline (lines between each of the points in `poly`) in the current + * foreground color + * **Note:** there is a limit of 64 points (128 XY elements) for polygons + * + * @param {any} poly - An array of vertices, of the form ```[x1,y1,x2,y2,x3,y3,etc]``` + * @param {boolean} closed - Draw another line between the last element of the array and the first + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_drawPoly + */ + drawPoly(poly: number[], closed?: boolean): Graphics; + + /** + * Draw an **antialiased** polyline (lines between each of the points in `poly`) in + * the current foreground color + * **Note:** there is a limit of 64 points (128 XY elements) for polygons + * + * @param {any} poly - An array of vertices, of the form ```[x1,y1,x2,y2,x3,y3,etc]``` + * @param {boolean} closed - Draw another line between the last element of the array and the first + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_drawPolyAA + */ + drawPolyAA(poly: number[], closed?: boolean): Graphics; + + /** + * Draw a filled polygon in the current foreground color. + * ``` + * g.fillPoly([ + * 16, 0, + * 31, 31, + * 26, 31, + * 16, 12, + * 6, 28, + * 0, 27 ]); + * ``` + * This fills from the top left hand side of the polygon (low X, low Y) *down to + * but not including* the bottom right. When placed together polygons will align + * perfectly without overdraw - but this will not fill the same pixels as + * `drawPoly` (drawing a line around the edge of the polygon). + * **Note:** there is a limit of 64 points (128 XY elements) for polygons + * + * @param {any} poly - An array of vertices, of the form ```[x1,y1,x2,y2,x3,y3,etc]``` + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_fillPoly + */ + fillPoly(poly: number[]): Graphics; + + /** + * Draw a filled polygon in the current foreground color. + * ``` + * g.fillPolyAA([ + * 16, 0, + * 31, 31, + * 26, 31, + * 16, 12, + * 6, 28, + * 0, 27 ]); + * ``` + * This fills from the top left hand side of the polygon (low X, low Y) *down to + * but not including* the bottom right. When placed together polygons will align + * perfectly without overdraw - but this will not fill the same pixels as + * `drawPoly` (drawing a line around the edge of the polygon). + * **Note:** there is a limit of 64 points (128 XY elements) for polygons + * + * @param {any} poly - An array of vertices, of the form ```[x1,y1,x2,y2,x3,y3,etc]``` + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_fillPolyAA + */ + fillPolyAA(poly: number[]): Graphics; + + /** + * Set the current rotation of the graphics device. + * + * @param {number} rotation - The clockwise rotation. 0 for no rotation, 1 for 90 degrees, 2 for 180, 3 for 270 + * @param {boolean} reflect - Whether to reflect the image + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_setRotation + */ + setRotation(rotation: 0 | 1 | 2 | 3, reflect?: boolean): Graphics; + + /** + * Return the width and height in pixels of an image (either Graphics, Image + * Object, Image String or ArrayBuffer). Returns `undefined` if image couldn't be + * decoded. + * `frames` is also included is the image contains more information than you'd + * expect for a single bitmap. In this case the bitmap might be an animation with + * multiple frames + * + * @param {any} str - The string + * @returns {any} An object containing `{width,height,bpp,transparent}` for the image + * @url http://www.espruino.com/Reference#l_Graphics_imageMetrics + */ + imageMetrics(img: Image): { width: number, height: number, bpp: number, transparent: number, frames?: ArrayBuffer[] } | undefined; + + /** + * Image can be: + * * An object with the following fields `{ width : int, height : int, bpp : + * optional int, buffer : ArrayBuffer/String, transparent: optional int, + * palette : optional Uint16Array(2/4/16) }`. bpp = bits per pixel (default is + * 1), transparent (if defined) is the colour that will be treated as + * transparent, and palette is a color palette that each pixel will be looked up + * in first + * * A String where the the first few bytes are: + * `width,height,bpp,[transparent,]image_bytes...`. If a transparent colour is + * specified the top bit of `bpp` should be set. + * * An ArrayBuffer Graphics object (if `bpp<8`, `msb:true` must be set) - this is + * disabled on devices without much flash memory available + * Draw an image at the specified position. + * * If the image is 1 bit, the graphics foreground/background colours will be + * used. + * * If `img.palette` is a Uint16Array or 2/4/16 elements, color data will be + * looked from the supplied palette + * * On Bangle.js, 2 bit images blend from background(0) to foreground(1) colours + * * On Bangle.js, 4 bit images use the Apple Mac 16 color palette + * * On Bangle.js, 8 bit images use the Web Safe 216 color palette + * * Otherwise color data will be copied as-is. Bitmaps are rendered MSB-first + * If `options` is supplied, `drawImage` will allow images to be rendered at any + * scale or angle. If `options.rotate` is set it will center images at `x,y`. + * `options` must be an object of the form: + * ``` + * { + * rotate : float, // the amount to rotate the image in radians (default 0) + * scale : float, // the amount to scale the image up (default 1) + * frame : int // if specified and the image has frames of data + * // after the initial frame, draw one of those frames from the image + * } + * ``` + * For example: + * ``` + * // In the top left of the screen + * g.drawImage(img,0,0); + * // In the top left of the screen, twice as big + * g.drawImage(img,0,0,{scale:2}); + * // In the center of the screen, twice as big, 45 degrees + * g.drawImage(img, g.getWidth()/2, g.getHeight()/2, + * {scale:2, rotate:Math.PI/4}); + * ``` + * + * @param {any} image - An image to draw, either a String or an Object (see below) + * @param {number} x - The X offset to draw the image + * @param {number} y - The Y offset to draw the image + * @param {any} options - options for scaling,rotation,etc (see below) + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_drawImage + */ + drawImage(image: Image, x: number, y: number, options?: { rotate?: number, scale?: number, frame?: number }): Graphics; + + /** + * Draws multiple images *at once* - which avoids flicker on unbuffered systems + * like Bangle.js. Maximum layer count right now is 4. + * ``` + * layers = [ { + * {x : int, // x start position + * y : int, // y start position + * image : string/object, + * scale : float, // scale factor, default 1 + * rotate : float, // angle in radians + * center : bool // center on x,y? default is top left + * repeat : should this image be repeated (tiled?) + * nobounds : bool // if true, the bounds of the image are not used to work out the default area to draw + * } + * ] + * options = { // the area to render. Defaults to rendering just enough to cover what's requested + * x,y, + * width,height + * } + * ``` + * + * @param {any} layers - An array of objects {x,y,image,scale,rotate,center} (up to 3) + * @param {any} options - options for rendering - see below + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_drawImages + */ + drawImages(layers: { x: number, y: number, image: Image, scale?: number, rotate?: number, center?: boolean, repeat?: boolean, nobounds?: boolean }[], options?: { x: number, y: number, width: number, height: number }): Graphics; + + /** + * Return this Graphics object as an Image that can be used with + * `Graphics.drawImage`. Check out [the Graphics reference + * page](http://www.espruino.com/Graphics#images-bitmaps) for more information on + * images. + * Will return undefined if data can't be allocated for the image. + * The image data itself will be referenced rather than copied if: + * * An image `object` was requested (not `string`) + * * The Graphics instance was created with `Graphics.createArrayBuffer` + * * Is 8 bpp *OR* the `{msb:true}` option was given + * * No other format options (zigzag/etc) were given + * Otherwise data will be copied, which takes up more space and may be quite slow. + * + * @param {any} type - The type of image to return. Either `object`/undefined to return an image object, or `string` to return an image string + * @returns {any} An Image that can be used with `Graphics.drawImage` + * @url http://www.espruino.com/Reference#l_Graphics_asImage + */ + asImage(type?: "object"): ImageObject; + asImage(type: "string"): string; + + /** + * Return the area of the Graphics canvas that has been modified, and optionally + * clear the modified area to 0. + * For instance if `g.setPixel(10,20)` was called, this would return `{x1:10, + * y1:20, x2:10, y2:20}` + * + * @param {boolean} reset - Whether to reset the modified area or not + * @returns {any} An object {x1,y1,x2,y2} containing the modified area, or undefined if not modified + * @url http://www.espruino.com/Reference#l_Graphics_getModified + */ + getModified(reset?: boolean): { x1: number, y1: number, x2: number, y2: number }; + + /** + * Scroll the contents of this graphics in a certain direction. The remaining area + * is filled with the background color. + * Note: This uses repeated pixel reads and writes, so will not work on platforms + * that don't support pixel reads. + * + * @param {number} x - X direction. >0 = to right + * @param {number} y - Y direction. >0 = down + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_scroll + */ + scroll(x: number, y: number): Graphics; + + /** + * Blit one area of the screen (x1,y1 w,h) to another (x2,y2 w,h) + * ``` + * g.blit({ + * x1:0, y1:0, + * w:32, h:32, + * x2:100, y2:100, + * setModified : true // should we set the modified area? + * }); + * ``` + * Note: This uses repeated pixel reads and writes, so will not work on platforms + * that don't support pixel reads. + * + * @param {any} options - options - see below + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_blit + */ + blit(options: { x1: number, y1: number, x2: number, y2: number, w: number, h: number, setModified?: boolean }): Graphics; + + /** + * Create a Windows BMP file from this Graphics instance, and return it as a + * String. + * @returns {any} A String representing the Graphics as a Windows BMP file (or 'undefined' if not possible) + * @url http://www.espruino.com/Reference#l_Graphics_asBMP + */ + asBMP(): string; + + /** + * Create a URL of the form `data:image/bmp;base64,...` that can be pasted into the + * browser. + * The Espruino Web IDE can detect this data on the console and render the image + * inline automatically. + * @returns {any} A String representing the Graphics as a URL (or 'undefined' if not possible) + * @url http://www.espruino.com/Reference#l_Graphics_asURL + */ + asURL(): string; + + /** + * Output this image as a bitmap URL of the form `data:image/bmp;base64,...`. The + * Espruino Web IDE will detect this on the console and will render the image + * inline automatically. + * This is identical to `console.log(g.asURL())` - it is just a convenient function + * for easy debugging and producing screenshots of what is currently in the + * Graphics instance. + * **Note:** This may not work on some bit depths of Graphics instances. It will + * also not work for the main Graphics instance of Bangle.js 1 as the graphics on + * Bangle.js 1 are stored in write-only memory. + * @url http://www.espruino.com/Reference#l_Graphics_dump + */ + dump(): void; + + /** + * Calculate the square area under a Bezier curve. + * x0,y0: start point x1,y1: control point y2,y2: end point + * Max 10 points without start point. + * + * @param {any} arr - An array of three vertices, six enties in form of ```[x0,y0,x1,y1,x2,y2]``` + * @param {any} options - number of points to calulate + * @returns {any} Array with calculated points + * @url http://www.espruino.com/Reference#l_Graphics_quadraticBezier + */ + quadraticBezier(arr: [number, number, number, number, number, number], options?: number): number[]; + + /** + * Transformation can be: + * * An object of the form + * ``` + * { + * x: float, // x offset (default 0) + * y: float, // y offset (default 0) + * scale: float, // scale factor (default 1) + * rotate: float, // angle in radians (default 0) + * } + * ``` + * * A six-element array of the form `[a,b,c,d,e,f]`, which represents the 2D transformation matrix + * ``` + * a c e + * b d f + * 0 0 1 + * ``` + * Apply a transformation to an array of vertices. + * + * @param {any} verts - An array of vertices, of the form ```[x1,y1,x2,y2,x3,y3,etc]``` + * @param {any} transformation - The transformation to apply, either an Object or an Array (see below) + * @returns {any} Array of transformed vertices + * @url http://www.espruino.com/Reference#l_Graphics_transformVertices + */ + transformVertices(arr: number[], transformation: { x?: number, y?: number, scale?: number, rotate?: number } | [number, number, number, number, number, number]): number[]; + + /** + * Returns an object of the form: + * ``` + * { + * fg : 0xFFFF, // foreground colour + * bg : 0, // background colour + * fg2 : 0xFFFF, // accented foreground colour + * bg2 : 0x0007, // accented background colour + * fgH : 0xFFFF, // highlighted foreground colour + * bgH : 0x02F7, // highlighted background colour + * dark : true, // Is background dark (e.g. foreground should be a light colour) + * } + * ``` + * These values can then be passed to `g.setColor`/`g.setBgColor` for example + * `g.setColor(g.theme.fg2)`. When the Graphics instance is reset, the background + * color is automatically set to `g.theme.bg` and foreground is set to + * `g.theme.fg`. + * On Bangle.js these values can be changed by writing updated values to `theme` in + * `settings.js` and reloading the app - or they can be changed temporarily by + * calling `Graphics.setTheme` + * @returns {any} An object containing the current 'theme' (see below) + * @url http://www.espruino.com/Reference#l_Graphics_theme + */ + theme: Theme; + + /** + * Set the global colour scheme. On Bangle.js, this is reloaded from + * `settings.json` for each new app loaded. + * See `Graphics.theme` for the fields that can be provided. For instance you can + * change the background to red using: + * ``` + * g.setTheme({bg:"#f00"}); + * ``` + * + * @param {any} theme - An object of the form returned by `Graphics.theme` + * @returns {any} The instance of Graphics this was called on, to allow call chaining + * @url http://www.espruino.com/Reference#l_Graphics_setTheme + */ + setTheme(theme: { [key in keyof Theme]?: Theme[key] extends number ? ColorResolvable : Theme[key] }): Graphics; +} + +/** + * This class helps to convert URLs into Objects of information ready for + * http.request/get + * @url http://www.espruino.com/Reference#url + */ +declare class url { + /** + * A utility function to split a URL into parts + * This is useful in web servers for instance when handling a request. + * For instance `url.parse("/a?b=c&d=e",true)` returns + * `{"method":"GET","host":"","path":"/a?b=c&d=e","pathname":"/a","search":"?b=c&d=e","port":80,"query":{"b":"c","d":"e"}}` + * + * @param {any} urlStr - A URL to be parsed + * @param {boolean} parseQuery - Whether to parse the query string into an object not (default = false) + * @returns {any} An object containing options for ```http.request``` or ```http.get```. Contains `method`, `host`, `path`, `pathname`, `search`, `port` and `query` + * @url http://www.espruino.com/Reference#l_url_parse + */ + static parse(urlStr: any, parseQuery: boolean): any; + + +} + +/** + * The socket server created by `require('net').createServer` + * @url http://www.espruino.com/Reference#Server + */ +declare class Server { + + + /** + * Start listening for new connections on the given port + * + * @param {number} port - The port to listen on + * @returns {any} The HTTP server instance that 'listen' was called on + * @url http://www.espruino.com/Reference#l_Server_listen + */ + listen(port: number): any; + + /** + * Stop listening for new connections + * @url http://www.espruino.com/Reference#l_Server_close + */ + close(): void; +} + +/** + * An actual socket connection - allowing transmit/receive of TCP data + * @url http://www.espruino.com/Reference#Socket + */ +declare class Socket { + /** + * The 'data' event is called when data is received. If a handler is defined with + * `X.on('data', function(data) { ... })` then it will be called, otherwise data + * will be stored in an internal buffer, where it can be retrieved with `X.read()` + * @param {string} event - The event to listen to. + * @param {(data: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `data` A string containing one or more characters of received data + * @url http://www.espruino.com/Reference#l_Socket_data + */ + static on(event: "data", callback: (data: any) => void): void; + + /** + * Called when the connection closes. + * @param {string} event - The event to listen to. + * @param {(had_error: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `had_error` A boolean indicating whether the connection had an error (use an error event handler to get error details). + * @url http://www.espruino.com/Reference#l_Socket_close + */ + static on(event: "close", callback: (had_error: any) => void): void; + + /** + * There was an error on this socket and it is closing (or wasn't opened in the + * first place). If a "connected" event was issued on this socket then the error + * event is always followed by a close event. The error codes are: + * * -1: socket closed (this is not really an error and will not cause an error + * callback) + * * -2: out of memory (typically while allocating a buffer to hold data) + * * -3: timeout + * * -4: no route + * * -5: busy + * * -6: not found (DNS resolution) + * * -7: max sockets (... exceeded) + * * -8: unsent data (some data could not be sent) + * * -9: connection reset (or refused) + * * -10: unknown error + * * -11: no connection + * * -12: bad argument + * * -13: SSL handshake failed + * * -14: invalid SSL data + * @param {string} event - The event to listen to. + * @param {(details: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `details` An error object with an error code (a negative integer) and a message. + * @url http://www.espruino.com/Reference#l_Socket_error + */ + static on(event: "error", callback: (details: any) => void): void; + + /** + * An event that is fired when the buffer is empty and it can accept more data to + * send. + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_Socket_drain + */ + static on(event: "drain", callback: () => void): void; + + /** + * Return how many bytes are available to read. If there is already a listener for + * data, this will always return 0. + * @returns {number} How many bytes are available + * @url http://www.espruino.com/Reference#l_Socket_available + */ + available(): number; + + /** + * Return a string containing characters that have been received + * + * @param {number} chars - The number of characters to read, or undefined/0 for all available + * @returns {any} A string containing the required bytes. + * @url http://www.espruino.com/Reference#l_Socket_read + */ + read(chars: number): any; + + /** + * Pipe this to a stream (an object with a 'write' method) + * + * @param {any} destination - The destination file/stream that will receive content from the source. + * @param {any} options + * An optional object `{ chunkSize : int=32, end : bool=true, complete : function }` + * chunkSize : The amount of data to pipe from source to destination at a time + * complete : a function to call when the pipe activity is complete + * end : call the 'end' function on the destination when the source is finished + * @url http://www.espruino.com/Reference#l_Socket_pipe + */ + pipe(destination: any, options: any): void; + + /** + * This function writes the `data` argument as a string. Data that is passed in + * (including arrays) will be converted to a string with the normal JavaScript + * `toString` method. + * If you wish to send binary data then you need to convert that data directly to a + * String. This can be done with `String.fromCharCode`, however it's often easier + * and faster to use the Espruino-specific `E.toString`, which will read its + * arguments as an array of bytes and convert that to a String: + * ``` + * socket.write(E.toString([0,1,2,3,4,5])); + * ``` + * If you need to send something other than bytes, you can use 'Typed Arrays', or + * even `DataView`: + * ``` + * var d = new DataView(new ArrayBuffer(8)); // 8 byte array buffer + * d.setFloat32(0, 765.3532564); // write float at bytes 0-3 + * d.setInt8(4, 42); // write int8 at byte 4 + * socket.write(E.toString(d.buffer)) + * ``` + * + * @param {any} data - A string containing data to send + * @returns {boolean} For node.js compatibility, returns the boolean false. When the send buffer is empty, a `drain` event will be sent + * @url http://www.espruino.com/Reference#l_Socket_write + */ + write(data: any): boolean; + + /** + * Close this socket - optional data to append as an argument. + * See `Socket.write` for more information about the data argument + * + * @param {any} data - A string containing data to send + * @url http://www.espruino.com/Reference#l_Socket_end + */ + end(data: any): void; +} + +/** + * An actual socket connection - allowing transmit/receive of TCP data + * @url http://www.espruino.com/Reference#dgramSocket + */ +declare class dgramSocket { + /** + * The 'message' event is called when a datagram message is received. If a handler + * is defined with `X.on('message', function(msg) { ... })` then it will be called` + * @param {string} event - The event to listen to. + * @param {(msg: any, rinfo: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `msg` A string containing the received message + * * `rinfo` Sender address,port containing information + * @url http://www.espruino.com/Reference#l_dgramSocket_message + */ + static on(event: "message", callback: (msg: any, rinfo: any) => void): void; + + /** + * Called when the connection closes. + * @param {string} event - The event to listen to. + * @param {(had_error: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `had_error` A boolean indicating whether the connection had an error (use an error event handler to get error details). + * @url http://www.espruino.com/Reference#l_dgramSocket_close + */ + static on(event: "close", callback: (had_error: any) => void): void; + + /** + * + * @param {any} buffer - A string containing message to send + * @param {any} offset - Offset in the passed string where the message starts [optional] + * @param {any} length - Number of bytes in the message [optional] + * @param {any} args - Destination port number, Destination IP address string + * @url http://www.espruino.com/Reference#l_dgramSocket_send + */ + send(buffer: any, offset: any, length: any, ...args: any[]): void; + + /** + * + * @param {number} port - The port to bind at + * @param {any} callback - A function(res) that will be called when the socket is bound. You can then call `res.on('message', function(message, info) { ... })` and `res.on('close', function() { ... })` to deal with the response. + * @returns {any} The dgramSocket instance that 'bind' was called on + * @url http://www.espruino.com/Reference#l_dgramSocket_bind + */ + bind(port: number, callback: any): any; + + /** + * Close the socket + * @url http://www.espruino.com/Reference#l_dgramSocket_close + */ + close(): void; + + /** + * + * @param {any} group - A string containing the group ip to join + * @param {any} ip - A string containing the ip to join with + * @url http://www.espruino.com/Reference#l_dgramSocket_addMembership + */ + addMembership(group: any, ip: any): void; +} + +/** + * An instantiation of a WiFi network adaptor + * @url http://www.espruino.com/Reference#WLAN + */ +declare class WLAN { + + + /** + * Connect to a wireless network + * + * @param {any} ap - Access point name + * @param {any} key - WPA2 key (or undefined for unsecured connection) + * @param {any} callback - Function to call back with connection status. It has one argument which is one of 'connect'/'disconnect'/'dhcp' + * @returns {boolean} True if connection succeeded, false if it didn't. + * @url http://www.espruino.com/Reference#l_WLAN_connect + */ + connect(ap: any, key: any, callback: any): boolean; + + /** + * Completely uninitialise and power down the CC3000. After this you'll have to use + * ```require("CC3000").connect()``` again. + * @url http://www.espruino.com/Reference#l_WLAN_disconnect + */ + disconnect(): void; + + /** + * Completely uninitialise and power down the CC3000, then reconnect to the old + * access point. + * @url http://www.espruino.com/Reference#l_WLAN_reconnect + */ + reconnect(): void; + + /** + * Get the current IP address + * @returns {any} + * @url http://www.espruino.com/Reference#l_WLAN_getIP + */ + getIP(): any; + + /** + * Set the current IP address for get an IP from DHCP (if no options object is + * specified). + * **Note:** Changes are written to non-volatile memory, but will only take effect + * after calling `wlan.reconnect()` + * + * @param {any} options - Object containing IP address options `{ ip : '1,2,3,4', subnet, gateway, dns }`, or do not supply an object in otder to force DHCP. + * @returns {boolean} True on success + * @url http://www.espruino.com/Reference#l_WLAN_setIP + */ + setIP(options: any): boolean; +} + +/** + * Class containing utility functions for the + * [ESP8266](http://www.espruino.com/EspruinoESP8266) + * @url http://www.espruino.com/Reference#ESP8266 + */ +declare class ESP8266 { + /** + * **DEPRECATED** - please use `Wifi.ping` instead. + * Perform a network ping request. The parameter can be either a String or a + * numeric IP address. + * + * @param {any} ipAddr - A string representation of an IP address. + * @param {any} pingCallback - Optional callback function. + * @url http://www.espruino.com/Reference#l_ESP8266_ping + */ + static ping(ipAddr: any, pingCallback: any): void; + + /** + * Perform a hardware reset/reboot of the esp8266. + * @url http://www.espruino.com/Reference#l_ESP8266_reboot + */ + static reboot(): void; + + /** + * At boot time the esp8266's firmware captures the cause of the reset/reboot. This + * function returns this information in an object with the following fields: + * * `reason`: "power on", "wdt reset", "exception", "soft wdt", "restart", "deep + * sleep", or "reset pin" + * * `exccause`: exception cause + * * `epc1`, `epc2`, `epc3`: instruction pointers + * * `excvaddr`: address being accessed + * * `depc`: (?) + * @returns {any} An object with the reset cause information + * @url http://www.espruino.com/Reference#l_ESP8266_getResetInfo + */ + static getResetInfo(): any; + + /** + * Enable or disable the logging of debug information. A value of `true` enables + * debug logging while a value of `false` disables debug logging. Debug output is + * sent to UART1 (gpio2). + * + * @param {boolean} enable - Enable or disable the debug logging. + * @url http://www.espruino.com/Reference#l_ESP8266_logDebug + */ + static logDebug(enable: boolean): void; + + /** + * Set the debug logging mode. It can be disabled (which frees ~1.2KB of heap), + * enabled in-memory only, or in-memory and output to a UART. + * + * @param {number} mode - Debug log mode: 0=off, 1=in-memory only, 2=in-mem and uart0, 3=in-mem and uart1. + * @url http://www.espruino.com/Reference#l_ESP8266_setLog + */ + static setLog(mode: number): void; + + /** + * Prints the contents of the debug log to the console. + * @url http://www.espruino.com/Reference#l_ESP8266_printLog + */ + static printLog(): void; + + /** + * Returns one line from the log or up to 128 characters. + * @url http://www.espruino.com/Reference#l_ESP8266_readLog + */ + static readLog(): void; + + /** + * Dumps info about all sockets to the log. This is for troubleshooting the socket + * implementation. + * @url http://www.espruino.com/Reference#l_ESP8266_dumpSocketInfo + */ + static dumpSocketInfo(): void; + + /** + * **Note:** This is deprecated. Use `E.setClock(80/160)` **Note:** Set the + * operating frequency of the ESP8266 processor. The default is 160Mhz. + * **Warning**: changing the cpu frequency affects the timing of some I/O + * operations, notably of software SPI and I2C, so things may be a bit slower at + * 80Mhz. + * + * @param {any} freq - Desired frequency - either 80 or 160. + * @url http://www.espruino.com/Reference#l_ESP8266_setCPUFreq + */ + static setCPUFreq(freq: any): void; + + /** + * Returns an object that contains details about the state of the ESP8266 with the + * following fields: + * * `sdkVersion` - Version of the SDK. + * * `cpuFrequency` - CPU operating frequency in Mhz. + * * `freeHeap` - Amount of free heap in bytes. + * * `maxCon` - Maximum number of concurrent connections. + * * `flashMap` - Configured flash size&map: '512KB:256/256' .. '4MB:512/512' + * * `flashKB` - Configured flash size in KB as integer + * * `flashChip` - Type of flash chip as string with manufacturer & chip, ex: '0xEF + * 0x4016` + * @returns {any} The state of the ESP8266 + * @url http://www.espruino.com/Reference#l_ESP8266_getState + */ + static getState(): any; + + /** + * **Note:** This is deprecated. Use `require("Flash").getFree()` + * @returns {any} Array of objects with `addr` and `length` properties describing the free flash areas available + * @url http://www.espruino.com/Reference#l_ESP8266_getFreeFlash + */ + static getFreeFlash(): any; + + /** + * + * @param {any} arrayOfData - Array of data to CRC + * @returns {any} 32-bit CRC + * @url http://www.espruino.com/Reference#l_ESP8266_crc32 + */ + static crc32(arrayOfData: any): any; + + /** + * **This function is deprecated.** Please use `require("neopixel").write(pin, + * data)` instead + * + * @param {Pin} pin - Pin for output signal. + * @param {any} arrayOfData - Array of LED data. + * @url http://www.espruino.com/Reference#l_ESP8266_neopixelWrite + */ + static neopixelWrite(pin: Pin, arrayOfData: any): void; + + /** + * Put the ESP8266 into 'deep sleep' for the given number of microseconds, reducing + * power consumption drastically. + * meaning of option values: + * 0 - the 108th Byte of init parameter decides whether RF calibration will be + * performed or not. + * 1 - run RF calibration after waking up. Power consumption is high. + * 2 - no RF calibration after waking up. Power consumption is low. + * 4 - no RF after waking up. Power consumption is the lowest. + * **Note:** unlike normal Espruino boards' 'deep sleep' mode, ESP8266 deep sleep + * actually turns off the processor. After the given number of microseconds have + * elapsed, the ESP8266 will restart as if power had been turned off and then back + * on. *All contents of RAM will be lost*. Connect GPIO 16 to RST to enable wakeup. + * **Special:** 0 microseconds cause sleep forever until external wakeup RST pull + * down occurs. + * + * @param {any} micros - Number of microseconds to sleep. + * @param {any} option - posible values are 0, 1, 2 or 4 + * @url http://www.espruino.com/Reference#l_ESP8266_deepSleep + */ + static deepSleep(micros: any, option: any): void; + + +} + +/** + * An instantiation of an Ethernet network adaptor + * @url http://www.espruino.com/Reference#Ethernet + */ +declare class Ethernet { + + + /** + * Get the current IP address, subnet, gateway and mac address. + * + * @param {any} options - An optional `callback(err, ipinfo)` function to be called back with the IP information. + * @returns {any} + * @url http://www.espruino.com/Reference#l_Ethernet_getIP + */ + getIP(options: any): any; + + /** + * Set the current IP address or get an IP from DHCP (if no options object is + * specified) + * If 'mac' is specified as an option, it must be a string of the form + * `"00:01:02:03:04:05"` The default mac is 00:08:DC:01:02:03. + * + * @param {any} options - Object containing IP address options `{ ip : '1.2.3.4', subnet : '...', gateway: '...', dns:'...', mac:':::::' }`, or do not supply an object in order to force DHCP. + * @param {any} callback - An optional `callback(err)` function to invoke when ip is set. `err==null` on success, or a string on failure. + * @returns {boolean} True on success + * @url http://www.espruino.com/Reference#l_Ethernet_setIP + */ + setIP(options: any, callback: any): boolean; + + /** + * Set hostname allow to set the hosname used during the dhcp request. min 8 and + * max 12 char, best set before calling `eth.setIP()` Default is WIZnet010203, + * 010203 is the default nic as part of the mac. Best to set the hosname before + * calling setIP(). + * + * @param {any} hostname - hostname as string + * @param {any} callback - An optional `callback(err)` function to be called back with null or error text. + * @returns {boolean} True on success + * @url http://www.espruino.com/Reference#l_Ethernet_setHostname + */ + setHostname(hostname: any, callback: any): boolean; + + /** + * Returns the hostname + * + * @param {any} callback - An optional `callback(err,hostname)` function to be called back with the status information. + * @returns {any} + * @url http://www.espruino.com/Reference#l_Ethernet_getHostname + */ + getHostname(callback: any): any; + + /** + * Get the current status of the ethernet device + * + * @param {any} options - An optional `callback(err, status)` function to be called back with the status information. + * @returns {any} + * @url http://www.espruino.com/Reference#l_Ethernet_getStatus + */ + getStatus(options: any): any; +} + +/** + * The HTTP server created by `require('http').createServer` + * @url http://www.espruino.com/Reference#httpSrv + */ +declare class httpSrv { + + + /** + * Start listening for new HTTP connections on the given port + * + * @param {number} port - The port to listen on + * @returns {any} The HTTP server instance that 'listen' was called on + * @url http://www.espruino.com/Reference#l_httpSrv_listen + */ + listen(port: number): any; + + /** + * Stop listening for new HTTP connections + * @url http://www.espruino.com/Reference#l_httpSrv_close + */ + close(): void; +} + +/** + * The HTTP server request + * @url http://www.espruino.com/Reference#httpSRq + */ +declare class httpSRq { + /** + * The 'data' event is called when data is received. If a handler is defined with + * `X.on('data', function(data) { ... })` then it will be called, otherwise data + * will be stored in an internal buffer, where it can be retrieved with `X.read()` + * @param {string} event - The event to listen to. + * @param {(data: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `data` A string containing one or more characters of received data + * @url http://www.espruino.com/Reference#l_httpSRq_data + */ + static on(event: "data", callback: (data: any) => void): void; + + /** + * Called when the connection closes. + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_httpSRq_close + */ + static on(event: "close", callback: () => void): void; + + /** + * The headers to sent to the server with this HTTP request. + * @returns {any} An object mapping header name to value + * @url http://www.espruino.com/Reference#l_httpSRq_headers + */ + headers: any; + + /** + * The HTTP method used with this request. Often `"GET"`. + * @returns {any} A string + * @url http://www.espruino.com/Reference#l_httpSRq_method + */ + method: any; + + /** + * The URL requested in this HTTP request, for instance: + * * `"/"` - the main page + * * `"/favicon.ico"` - the web page's icon + * @returns {any} A string representing the URL + * @url http://www.espruino.com/Reference#l_httpSRq_url + */ + url: any; + + /** + * Return how many bytes are available to read. If there is already a listener for + * data, this will always return 0. + * @returns {number} How many bytes are available + * @url http://www.espruino.com/Reference#l_httpSRq_available + */ + available(): number; + + /** + * Return a string containing characters that have been received + * + * @param {number} chars - The number of characters to read, or undefined/0 for all available + * @returns {any} A string containing the required bytes. + * @url http://www.espruino.com/Reference#l_httpSRq_read + */ + read(chars: number): any; + + /** + * Pipe this to a stream (an object with a 'write' method) + * + * @param {any} destination - The destination file/stream that will receive content from the source. + * @param {any} options + * An optional object `{ chunkSize : int=32, end : bool=true, complete : function }` + * chunkSize : The amount of data to pipe from source to destination at a time + * complete : a function to call when the pipe activity is complete + * end : call the 'end' function on the destination when the source is finished + * @url http://www.espruino.com/Reference#l_httpSRq_pipe + */ + pipe(destination: any, options: any): void; +} + +/** + * The HTTP server response + * @url http://www.espruino.com/Reference#httpSRs + */ +declare class httpSRs { + /** + * An event that is fired when the buffer is empty and it can accept more data to + * send. + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_httpSRs_drain + */ + static on(event: "drain", callback: () => void): void; + + /** + * Called when the connection closes. + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_httpSRs_close + */ + static on(event: "close", callback: () => void): void; + + /** + * The headers to send back along with the HTTP response. + * The default contents are: + * ``` + * { + * "Connection": "close" + * } + * ``` + * @returns {any} An object mapping header name to value + * @url http://www.espruino.com/Reference#l_httpSRs_headers + */ + headers: any; + + /** + * This function writes the `data` argument as a string. Data that is passed in + * (including arrays) will be converted to a string with the normal JavaScript + * `toString` method. For more information about sending binary data see + * `Socket.write` + * + * @param {any} data - A string containing data to send + * @returns {boolean} For node.js compatibility, returns the boolean false. When the send buffer is empty, a `drain` event will be sent + * @url http://www.espruino.com/Reference#l_httpSRs_write + */ + write(data: any): boolean; + + /** + * See `Socket.write` for more information about the data argument + * + * @param {any} data - A string containing data to send + * @url http://www.espruino.com/Reference#l_httpSRs_end + */ + end(data: any): void; + + /** + * Send the given status code and headers. If not explicitly called this will be + * done automatically the first time data is written to the response. + * This cannot be called twice, or after data has already been sent in the + * response. + * + * @param {number} statusCode - The HTTP status code + * @param {any} headers - An object containing the headers + * @url http://www.espruino.com/Reference#l_httpSRs_writeHead + */ + writeHead(statusCode: number, headers: any): void; + + /** + * Set a value to send in the header of this HTTP response. This updates the + * `httpSRs.headers` property. + * Any headers supplied to `writeHead` will overwrite any headers with the same + * name. + * + * @param {any} name - The name of the header as a String + * @param {any} value - The value of the header as a String + * @url http://www.espruino.com/Reference#l_httpSRs_setHeader + */ + setHeader(name: any, value: any): void; +} + +/** + * The HTTP client request, returned by `http.request()` and `http.get()`. + * @url http://www.espruino.com/Reference#httpCRq + */ +declare class httpCRq { + /** + * An event that is fired when the buffer is empty and it can accept more data to + * send. + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_httpCRq_drain + */ + static on(event: "drain", callback: () => void): void; + + /** + * An event that is fired if there is an error making the request and the response + * callback has not been invoked. In this case the error event concludes the + * request attempt. The error event function receives an error object as parameter + * with a `code` field and a `message` field. + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_httpCRq_error + */ + static on(event: "error", callback: () => void): void; + + /** + * This function writes the `data` argument as a string. Data that is passed in + * (including arrays) will be converted to a string with the normal JavaScript + * `toString` method. For more information about sending binary data see + * `Socket.write` + * + * @param {any} data - A string containing data to send + * @returns {boolean} For node.js compatibility, returns the boolean false. When the send buffer is empty, a `drain` event will be sent + * @url http://www.espruino.com/Reference#l_httpCRq_write + */ + write(data: any): boolean; + + /** + * Finish this HTTP request - optional data to append as an argument + * See `Socket.write` for more information about the data argument + * + * @param {any} data - A string containing data to send + * @url http://www.espruino.com/Reference#l_httpCRq_end + */ + end(data: any): void; +} + +/** + * The HTTP client response, passed to the callback of `http.request()` an + * `http.get()`. + * @url http://www.espruino.com/Reference#httpCRs + */ +declare class httpCRs { + /** + * The 'data' event is called when data is received. If a handler is defined with + * `X.on('data', function(data) { ... })` then it will be called, otherwise data + * will be stored in an internal buffer, where it can be retrieved with `X.read()` + * @param {string} event - The event to listen to. + * @param {(data: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `data` A string containing one or more characters of received data + * @url http://www.espruino.com/Reference#l_httpCRs_data + */ + static on(event: "data", callback: (data: any) => void): void; + + /** + * Called when the connection closes with one `hadError` boolean parameter, which + * indicates whether an error occurred. + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_httpCRs_close + */ + static on(event: "close", callback: () => void): void; + + /** + * An event that is fired if there is an error receiving the response. The error + * event function receives an error object as parameter with a `code` field and a + * `message` field. After the error event the close even will also be triggered to + * conclude the HTTP request/response. + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_httpCRs_error + */ + static on(event: "error", callback: () => void): void; + + /** + * The headers received along with the HTTP response + * @returns {any} An object mapping header name to value + * @url http://www.espruino.com/Reference#l_httpCRs_headers + */ + headers: any; + + /** + * The HTTP response's status code - usually `"200"` if all went well + * @returns {any} The status code as a String + * @url http://www.espruino.com/Reference#l_httpCRs_statusCode + */ + statusCode: any; + + /** + * The HTTP response's status message - Usually `"OK"` if all went well + * @returns {any} An String Status Message + * @url http://www.espruino.com/Reference#l_httpCRs_statusMessage + */ + statusMessage: any; + + /** + * The HTTP version reported back by the server - usually `"1.1"` + * @returns {any} Th + * @url http://www.espruino.com/Reference#l_httpCRs_httpVersion + */ + httpVersion: any; + + /** + * Return how many bytes are available to read. If there is a 'data' event handler, + * this will always return 0. + * @returns {number} How many bytes are available + * @url http://www.espruino.com/Reference#l_httpCRs_available + */ + available(): number; + + /** + * Return a string containing characters that have been received + * + * @param {number} chars - The number of characters to read, or undefined/0 for all available + * @returns {any} A string containing the required bytes. + * @url http://www.espruino.com/Reference#l_httpCRs_read + */ + read(chars: number): any; + + /** + * Pipe this to a stream (an object with a 'write' method) + * + * @param {any} destination - The destination file/stream that will receive content from the source. + * @param {any} options + * An optional object `{ chunkSize : int=32, end : bool=true, complete : function }` + * chunkSize : The amount of data to pipe from source to destination at a time + * complete : a function to call when the pipe activity is complete + * end : call the 'end' function on the destination when the source is finished + * @url http://www.espruino.com/Reference#l_httpCRs_pipe + */ + pipe(destination: any, options: any): void; +} + +/** + * This class provides functionality to recognise gestures drawn on a touchscreen. + * It is only built into Bangle.js 2. + * Usage: + * ``` + * var strokes = { + * stroke1 : Unistroke.new(new Uint8Array([x1, y1, x2, y2, x3, y3, ...])), + * stroke2 : Unistroke.new(new Uint8Array([x1, y1, x2, y2, x3, y3, ...])), + * stroke3 : Unistroke.new(new Uint8Array([x1, y1, x2, y2, x3, y3, ...])) + * }; + * var r = Unistroke.recognise(strokes,new Uint8Array([x1, y1, x2, y2, x3, y3, ...])) + * print(r); // stroke1/stroke2/stroke3 + * ``` + * @url http://www.espruino.com/Reference#Unistroke + */ +declare class Unistroke { + /** + * Create a new Unistroke based on XY coordinates + * + * @param {any} xy - An array of interleaved XY coordinates + * @returns {any} A string of data representing this unistroke + * @url http://www.espruino.com/Reference#l_Unistroke_new + */ + static new(xy: any): any; + + /** + * Recognise based on an object of named strokes, and a list of XY coordinates + * + * @param {any} strokes - An object of named strokes : `{arrow:..., circle:...}` + * @param {any} xy - An array of interleaved XY coordinates + * @returns {any} The key name of the matched stroke + * @url http://www.espruino.com/Reference#l_Unistroke_recognise + */ + static recognise(strokes: any, xy: any): any; + + +} + +/** + * The NRF class is for controlling functionality of the Nordic nRF51/nRF52 chips. + * Most functionality is related to Bluetooth Low Energy, however there are also + * some functions related to NFC that apply to NRF52-based devices. + * @url http://www.espruino.com/Reference#NRF + */ +declare class NRF { + /** + * @returns {any} An object + * @url http://www.espruino.com/Reference#l_NRF_getSecurityStatus + */ + static getSecurityStatus(): any; + + /** + * @returns {any} An object + * @url http://www.espruino.com/Reference#l_NRF_getAddress + */ + static getAddress(): any; + + /** + * + * @param {any} data - The service (and characteristics) to advertise + * @param {any} options - Optional object containing options + * @url http://www.espruino.com/Reference#l_NRF_setServices + */ + static setServices(data: any, options: any): void; + + /** + * + * @param {any} data - The data to advertise as an object - see below for more info + * @param {any} options - An optional object of options + * @url http://www.espruino.com/Reference#l_NRF_setAdvertising + */ + static setAdvertising(data: any, options: any): void; + + /** + * Called when a host device connects to Espruino. The first argument contains the + * address. + * @param {string} event - The event to listen to. + * @param {(addr: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `addr` The address of the device that has connected + * @url http://www.espruino.com/Reference#l_NRF_connect + */ + static on(event: "connect", callback: (addr: any) => void): void; + + /** + * Called when a host device disconnects from Espruino. + * The most common reason is: + * * 19 - `REMOTE_USER_TERMINATED_CONNECTION` + * * 22 - `LOCAL_HOST_TERMINATED_CONNECTION` + * @param {string} event - The event to listen to. + * @param {(reason: number) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `reason` The reason code reported back by the BLE stack - see Nordic's [`ble_hci.h` file](https://github.com/espruino/Espruino/blob/master/targetlibs/nrf5x_12/components/softdevice/s132/headers/ble_hci.h#L71) for more information + * @url http://www.espruino.com/Reference#l_NRF_disconnect + */ + static on(event: "disconnect", callback: (reason: number) => void): void; + + /** + * Contains updates on the security of the current Bluetooth link. + * See Nordic's `ble_gap_evt_auth_status_t` structure for more information. + * @param {string} event - The event to listen to. + * @param {(status: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `status` An object containing `{auth_status,bonded,lv4,kdist_own,kdist_peer} + * @url http://www.espruino.com/Reference#l_NRF_security + */ + static on(event: "security", callback: (status: any) => void): void; + + /** + * Called with a single byte value when Espruino is set up as a HID device and the + * computer it is connected to sends a HID report back to Espruino. This is usually + * used for handling indications such as the Caps Lock LED. + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_NRF_HID + */ + static on(event: "HID", callback: () => void): void; + + /** + * Called with discovered services when discovery is finished + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_NRF_servicesDiscover + */ + static on(event: "servicesDiscover", callback: () => void): void; + + /** + * Called with discovered characteristics when discovery is finished + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_NRF_characteristicsDiscover + */ + static on(event: "characteristicsDiscover", callback: () => void): void; + + /** + * Called when an NFC field is detected + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_NRF_NFCon + */ + static on(event: "NFCon", callback: () => void): void; + + /** + * Called when an NFC field is no longer detected + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_NRF_NFCoff + */ + static on(event: "NFCoff", callback: () => void): void; + + /** + * When NFC is started with `NRF.nfcStart`, this is fired when NFC data is + * received. It doesn't get called if NFC is started with `NRF.nfcURL` or + * `NRF.nfcRaw` + * @param {string} event - The event to listen to. + * @param {(arr: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `arr` An ArrayBuffer containign the received data + * @url http://www.espruino.com/Reference#l_NRF_NFCrx + */ + static on(event: "NFCrx", callback: (arr: any) => void): void; + + /** + * If a device is connected to Espruino, disconnect from it. + * @url http://www.espruino.com/Reference#l_NRF_disconnect + */ + static disconnect(): void; + + /** + * Disable Bluetooth advertising and disconnect from any device that connected to + * Puck.js as a peripheral (this won't affect any devices that Puck.js initiated + * connections to). + * This makes Puck.js undiscoverable, so it can't be connected to. + * Use `NRF.wake()` to wake up and make Puck.js connectable again. + * @url http://www.espruino.com/Reference#l_NRF_sleep + */ + static sleep(): void; + + /** + * Enable Bluetooth advertising (this is enabled by default), which allows other + * devices to discover and connect to Puck.js. + * Use `NRF.sleep()` to disable advertising. + * @url http://www.espruino.com/Reference#l_NRF_wake + */ + static wake(): void; + + /** + * Restart the Bluetooth softdevice (if there is currently a BLE connection, it + * will queue a restart to be done when the connection closes). + * You shouldn't need to call this function in normal usage. However, Nordic's BLE + * softdevice has some settings that cannot be reset. For example there are only a + * certain number of unique UUIDs. Once these are all used the only option is to + * restart the softdevice to clear them all out. + * + * @param {any} callback - An optional function to be called while the softdevice is uninitialised. Use with caution - accessing console/bluetooth will almost certainly result in a crash. + * @url http://www.espruino.com/Reference#l_NRF_restart + */ + static restart(callback: any): void; + + /** + * Get this device's default Bluetooth MAC address. + * For Puck.js, the last 5 characters of this (eg. `ee:ff`) are used in the + * device's advertised Bluetooth name. + * @returns {any} MAC address - a string of the form 'aa:bb:cc:dd:ee:ff' + * @url http://www.espruino.com/Reference#l_NRF_getAddress + */ + static getAddress(): any; + + /** + * Set this device's default Bluetooth MAC address: + * ``` + * NRF.setAddress("ff:ee:dd:cc:bb:aa random"); + * ``` + * Addresses take the form: + * * `"ff:ee:dd:cc:bb:aa"` or `"ff:ee:dd:cc:bb:aa public"` for a public address + * * `"ff:ee:dd:cc:bb:aa random"` for a random static address (the default for + * Espruino) + * This may throw a `INVALID_BLE_ADDR` error if the upper two bits of the address + * don't match the address type. + * To change the address, Espruino must restart the softdevice. It will only do so + * when it is disconnected from other devices. + * + * @param {any} addr - The address to use (as a string) + * @url http://www.espruino.com/Reference#l_NRF_setAddress + */ + static setAddress(addr: any): void; + + /** + * Get the battery level in volts (the voltage that the NRF chip is running off + * of). + * This is the battery level of the device itself - it has nothing to with any + * device that might be connected. + * @returns {number} Battery level in volts + * @url http://www.espruino.com/Reference#l_NRF_getBattery + */ + static getBattery(): number; + + /** + * Change the data that Espruino advertises. + * Data can be of the form `{ UUID : data_as_byte_array }`. The UUID should be a + * [Bluetooth Service + * ID](https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx). + * For example to return battery level at 95%, do: + * ``` + * NRF.setAdvertising({ + * 0x180F : [95] // Service data 0x180F = 95 + * }); + * ``` + * Or you could report the current temperature: + * ``` + * setInterval(function() { + * NRF.setAdvertising({ + * 0x1809 : [Math.round(E.getTemperature())] + * }); + * }, 30000); + * ``` + * If you specify a value for the object key, Service Data is advertised. However + * if you specify `undefined`, the Service UUID is advertised: + * ``` + * NRF.setAdvertising({ + * 0x180D : undefined // Advertise service UUID 0x180D (HRM) + * }); + * ``` + * Service UUIDs can also be supplied in the second argument of `NRF.setServices`, + * but those go in the scan response packet. + * You can also supply the raw advertising data in an array. For example to + * advertise as an Eddystone beacon: + * ``` + * NRF.setAdvertising([0x03, // Length of Service List + * 0x03, // Param: Service List + * 0xAA, 0xFE, // Eddystone ID + * 0x13, // Length of Service Data + * 0x16, // Service Data + * 0xAA, 0xFE, // Eddystone ID + * 0x10, // Frame type: URL + * 0xF8, // Power + * 0x03, // https:// + * 'g','o','o','.','g','l','/','B','3','J','0','O','c'], + * {interval:100}); + * ``` + * (However for Eddystone we'd advise that you use the [Espruino Eddystone + * library](/Puck.js+Eddystone)) + * **Note:** When specifying data as an array, certain advertising options such as + * `discoverable` and `showName` won't have any effect. + * **Note:** The size of Bluetooth LE advertising packets is limited to 31 bytes. + * If you want to advertise more data, consider using an array for `data` (See + * below), or `NRF.setScanResponse`. + * You can even specify an array of arrays or objects, in which case each + * advertising packet will be used in turn - for instance to make your device + * advertise battery level and its name as well as both Eddystone and iBeacon : + * ``` + * NRF.setAdvertising([ + * {0x180F : [Puck.getBatteryPercentage()]}, // normal advertising, with battery % + * require("ble_ibeacon").get(...), // iBeacon + * require("ble_eddystone").get(...), // eddystone + * ], {interval:300}); + * ``` + * `options` is an object, which can contain: + * ``` + * { + * name: "Hello" // The name of the device + * showName: true/false // include full name, or nothing + * discoverable: true/false // general discoverable, or limited - default is limited + * connectable: true/false // whether device is connectable - default is true + * scannable : true/false // whether device can be scanned for scan response packets - default is true + * interval: 600 // Advertising interval in msec, between 20 and 10000 (default is 375ms) + * manufacturer: 0x0590 // IF sending manufacturer data, this is the manufacturer ID + * manufacturerData: [...] // IF sending manufacturer data, this is an array of data + * phy: "1mbps/2mbps/coded" // (NRF52840 only) use the long-range coded phy for transmission (1mbps default) + * } + * ``` + * Setting `connectable` and `scannable` to false gives the lowest power + * consumption as the BLE radio doesn't have to listen after sending advertising. + * **NOTE:** Non-`connectable` advertising can't have an advertising interval less + * than 100ms according to the BLE spec. + * So for instance to set the name of Puck.js without advertising any other data + * you can just use the command: + * ``` + * NRF.setAdvertising({},{name:"Hello"}); + * ``` + * You can also specify 'manufacturer data', which is another form of advertising + * data. We've registered the Manufacturer ID 0x0590 (as Pur3 Ltd) for use with + * *Official Espruino devices* - use it to advertise whatever data you'd like, but + * we'd recommend using JSON. + * For example by not advertising a device name you can send up to 24 bytes of JSON + * on Espruino's manufacturer ID: + * ``` + * var data = {a:1,b:2}; + * NRF.setAdvertising({},{ + * showName:false, + * manufacturer:0x0590, + * manufacturerData:JSON.stringify(data) + * }); + * ``` + * If you're using [EspruinoHub](https://github.com/espruino/EspruinoHub) then it + * will automatically decode this into the folling MQTT topics: + * * `/ble/advertise/ma:c_:_a:dd:re:ss/espruino` -> `{"a":10,"b":15}` + * * `/ble/advertise/ma:c_:_a:dd:re:ss/a` -> `1` + * * `/ble/advertise/ma:c_:_a:dd:re:ss/b` -> `2` + * Note that **you only have 24 characters available for JSON**, so try to use the + * shortest field names possible and avoid floating point values that can be very + * long when converted to a String. + * + * @param {any} data - The service data to advertise as an object - see below for more info + * @param {any} options - An optional object of options + * @url http://www.espruino.com/Reference#l_NRF_setAdvertising + */ + static setAdvertising(data: any, options: any): void; + + /** + * This is just like `NRF.setAdvertising`, except instead of advertising the data, + * it returns the packet that would be advertised as an array. + * + * @param {any} data - The data to advertise as an object + * @param {any} options - An optional object of options + * @returns {any} An array containing the advertising data + * @url http://www.espruino.com/Reference#l_NRF_getAdvertisingData + */ + static getAdvertisingData(data: any, options: any): any; + + /** + * The raw scan response data should be supplied as an array. For example to return + * "Sample" for the device name: + * ``` + * NRF.setScanResponse([0x07, // Length of Data + * 0x09, // Param: Complete Local Name + * 'S', 'a', 'm', 'p', 'l', 'e']); + * ``` + * **Note:** `NRF.setServices(..., {advertise:[ ... ]})` writes advertised services + * into the scan response - so you can't use both `advertise` and `NRF.setServices` + * or one will overwrite the other. + * + * @param {any} data - The data to for the scan response + * @url http://www.espruino.com/Reference#l_NRF_setScanResponse + */ + static setScanResponse(data: any): void; + + /** + * Change the services and characteristics Espruino advertises. + * If you want to **change** the value of a characteristic, you need to use + * `NRF.updateServices()` instead + * To expose some information on Characteristic `ABCD` on service `BCDE` you could + * do: + * ``` + * NRF.setServices({ + * 0xBCDE : { + * 0xABCD : { + * value : "Hello", + * readable : true + * } + * } + * }); + * ``` + * Or to allow the 3 LEDs to be controlled by writing numbers 0 to 7 to a + * characteristic, you can do the following. `evt.data` is an ArrayBuffer. + * ``` + * NRF.setServices({ + * 0xBCDE : { + * 0xABCD : { + * writable : true, + * onWrite : function(evt) { + * digitalWrite([LED3,LED2,LED1], evt.data[0]); + * } + * } + * } + * }); + * ``` + * You can supply many different options: + * ``` + * NRF.setServices({ + * 0xBCDE : { + * 0xABCD : { + * value : "Hello", // optional + * maxLen : 5, // optional (otherwise is length of initial value) + * broadcast : false, // optional, default is false + * readable : true, // optional, default is false + * writable : true, // optional, default is false + * notify : true, // optional, default is false + * indicate : true, // optional, default is false + * description: "My Characteristic", // optional, default is null, + * security: { // optional - see NRF.setSecurity + * read: { // optional + * encrypted: false, // optional, default is false + * mitm: false, // optional, default is false + * lesc: false, // optional, default is false + * signed: false // optional, default is false + * }, + * write: { // optional + * encrypted: true, // optional, default is false + * mitm: false, // optional, default is false + * lesc: false, // optional, default is false + * signed: false // optional, default is false + * } + * }, + * onWrite : function(evt) { // optional + * console.log("Got ", evt.data); // an ArrayBuffer + * }, + * onWriteDesc : function(evt) { // optional - called when the 'cccd' descriptor is written + * // for example this is called when notifications are requested by the client: + * console.log("Notifications enabled = ", evt.data[0]&1); + * } + * } + * // more characteristics allowed + * } + * // more services allowed + * }); + * ``` + * **Note:** UUIDs can be integers between `0` and `0xFFFF`, strings of the form + * `"ABCD"`, or strings of the form `"ABCDABCD-ABCD-ABCD-ABCD-ABCDABCDABCD"` + * `options` can be of the form: + * ``` + * NRF.setServices(undefined, { + * hid : new Uint8Array(...), // optional, default is undefined. Enable BLE HID support + * uart : true, // optional, default is true. Enable BLE UART support + * advertise: [ '180D' ] // optional, list of service UUIDs to advertise + * ancs : true, // optional, Bangle.js-only, enable Apple ANCS support for notifications + * ams : true // optional, Bangle.js-only, enable Apple AMS support for media control + * }); + * ``` + * To enable BLE HID, you must set `hid` to an array which is the BLE report + * descriptor. The easiest way to do this is to use the `ble_hid_controls` or + * `ble_hid_keyboard` modules. + * **Note:** Just creating a service doesn't mean that the service will be + * advertised. It will only be available after a device connects. To advertise, + * specify the UUIDs you wish to advertise in the `advertise` field of the second + * `options` argument. For example this will create and advertise a heart rate + * service: + * ``` + * NRF.setServices({ + * 0x180D: { // heart_rate + * 0x2A37: { // heart_rate_measurement + * notify: true, + * value : [0x06, heartrate], + * } + * } + * }, { advertise: [ '180D' ] }); + * ``` + * You may specify 128 bit UUIDs to advertise, however you may get a `DATA_SIZE` + * exception because there is insufficient space in the Bluetooth LE advertising + * packet for the 128 bit UART UUID as well as the UUID you specified. In this case + * you can add `uart:false` after the `advertise` element to disable the UART, + * however you then be unable to connect to Puck.js's console via Bluetooth. + * If you absolutely require two or more 128 bit UUIDs then you will have to + * specify your own raw advertising data packets with `NRF.setAdvertising` + * **Note:** The services on Espruino can only be modified when there is no device + * connected to it as it requires a restart of the Bluetooth stack. **iOS devices + * will 'cache' the list of services** so apps like NRF Connect may incorrectly + * display the old services even after you have modified them. To fix this, disable + * and re-enable Bluetooth on your iOS device, or use an Android device to run NRF + * Connect. + * **Note:** Not all combinations of security configuration values are valid, the + * valid combinations are: encrypted, encrypted + mitm, lesc, signed, signed + + * mitm. See `NRF.setSecurity` for more information. + * + * @param {any} data - The service (and characteristics) to advertise + * @param {any} options - Optional object containing options + * @url http://www.espruino.com/Reference#l_NRF_setServices + */ + static setServices(data: any, options: any): void; + + /** + * Update values for the services and characteristics Espruino advertises. Only + * services and characteristics previously declared using `NRF.setServices` are + * affected. + * To update the '0xABCD' characteristic in the '0xBCDE' service: + * ``` + * NRF.updateServices({ + * 0xBCDE : { + * 0xABCD : { + * value : "World" + * } + * } + * }); + * ``` + * You can also use 128 bit UUIDs, for example + * `"b7920001-3c1b-4b40-869f-3c0db9be80c6"`. + * To define a service and characteristic and then notify connected clients of a + * change to it when a button is pressed: + * ``` + * NRF.setServices({ + * 0xBCDE : { + * 0xABCD : { + * value : "Hello", + * maxLen : 20, + * notify: true + * } + * } + * }); + * setWatch(function() { + * NRF.updateServices({ + * 0xBCDE : { + * 0xABCD : { + * value : "World!", + * notify: true + * } + * } + * }); + * }, BTN, { repeat:true, edge:"rising", debounce: 50 }); + * ``` + * This only works if the characteristic was created with `notify: true` using + * `NRF.setServices`, otherwise the characteristic will be updated but no + * notification will be sent. + * Also note that `maxLen` was specified. If it wasn't then the maximum length of + * the characteristic would have been 5 - the length of `"Hello"`. + * To indicate (i.e. notify with ACK) connected clients of a change to the '0xABCD' + * characteristic in the '0xBCDE' service: + * ``` + * NRF.updateServices({ + * 0xBCDE : { + * 0xABCD : { + * value : "World", + * indicate: true + * } + * } + * }); + * ``` + * This only works if the characteristic was created with `indicate: true` using + * `NRF.setServices`, otherwise the characteristic will be updated but no + * notification will be sent. + * **Note:** See `NRF.setServices` for more information + * + * @param {any} data - The service (and characteristics) to update + * @url http://www.espruino.com/Reference#l_NRF_updateServices + */ + static updateServices(data: any): void; + + /** + * Start/stop listening for BLE advertising packets within range. Returns a + * `BluetoothDevice` for each advertsing packet. **By default this is not an active + * scan, so Scan Response advertising data is not included (see below)** + * ``` + * // Start scanning + * packets=10; + * NRF.setScan(function(d) { + * packets--; + * if (packets<=0) + * NRF.setScan(); // stop scanning + * else + * console.log(d); // print packet info + * }); + * ``` + * Each `BluetoothDevice` will look a bit like: + * ``` + * BluetoothDevice { + * "id": "aa:bb:cc:dd:ee:ff", // address + * "rssi": -89, // signal strength + * "services": [ "128bit-uuid", ... ], // zero or more service UUIDs + * "data": new Uint8Array([ ... ]).buffer, // ArrayBuffer of returned data + * "serviceData" : { "0123" : [ 1 ] }, // if service data is in 'data', it's extracted here + * "manufacturer" : 0x1234, // if manufacturer data is in 'data', the 16 bit manufacturer ID is extracted here + * "manufacturerData" : new Uint8Array([...]).buffer, // if manufacturer data is in 'data', the data is extracted here as an ArrayBuffer + * "name": "DeviceName" // the advertised device name + * } + * ``` + * You can also supply a set of filters (as described in `NRF.requestDevice`) as a + * second argument, which will allow you to filter the devices you get a callback + * for. This helps to cut down on the time spent processing JavaScript code in + * areas with a lot of Bluetooth advertisements. For example to find only devices + * with the manufacturer data `0x0590` (Espruino's ID) you could do: + * ``` + * NRF.setScan(function(d) { + * console.log(d.manufacturerData); + * }, { filters: [{ manufacturerData:{0x0590:{}} }] }); + * ``` + * You can also specify `active:true` in the second argument to perform active + * scanning (this requests scan response packets) from any devices it finds. + * **Note:** Using a filter in `setScan` filters each advertising packet + * individually. As a result, if you filter based on a service UUID and a device + * advertises with multiple packets (or a scan response when `active:true`) only + * the packets matching the filter are returned. To aggregate multiple packets you + * can use `NRF.findDevices`. + * **Note:** BLE advertising packets can arrive quickly - faster than you'll be + * able to print them to the console. It's best only to print a few, or to use a + * function like `NRF.findDevices(..)` which will collate a list of available + * devices. + * **Note:** Using setScan turns the radio's receive mode on constantly. This can + * draw a *lot* of power (12mA or so), so you should use it sparingly or you can + * run your battery down quickly. + * + * @param {any} callback - The callback to call with received advertising packets, or undefined to stop + * @param {any} options - An optional object `{filters: ...}` (as would be passed to `NRF.requestDevice`) to filter devices by + * @url http://www.espruino.com/Reference#l_NRF_setScan + */ + static setScan(callback: any, options: any): void; + + /** + * This function can be used to quickly filter through Bluetooth devices. + * For instance if you wish to scan for multiple different types of device at the + * same time then you could use `NRF.findDevices` with all the filters you're + * interested in. When scanning is finished you can then use `NRF.filterDevices` to + * pick out just the devices of interest. + * ``` + * // the two types of device we're interested in + * var filter1 = [{serviceData:{"fe95":{}}}]; + * var filter2 = [{namePrefix:"Pixl.js"}]; + * // the following filter will return both types of device + * var allFilters = filter1.concat(filter2); + * // now scan for both types of device, and filter them out afterwards + * NRF.findDevices(function(devices) { + * var devices1 = NRF.filterDevices(devices, filter1); + * var devices2 = NRF.filterDevices(devices, filter2); + * // ... + * }, {filters : allFilters}); + * ``` + * + * @param {any} devices - An array of `BluetoothDevice` objects, from `NRF.findDevices` or similar + * @param {any} filters - A list of filters (as would be passed to `NRF.requestDevice`) to filter devices by + * @returns {any} An array of `BluetoothDevice` objects that match the given filters + * @url http://www.espruino.com/Reference#l_NRF_filterDevices + */ + static filterDevices(devices: any, filters: any): any; + + /** + * Utility function to return a list of BLE devices detected in range. Behind the + * scenes, this uses `NRF.setScan(...)` and collates the results. + * ``` + * NRF.findDevices(function(devices) { + * console.log(devices); + * }, 1000); + * ``` + * prints something like: + * ``` + * [ + * BluetoothDevice { + * "id" : "e7:e0:57:ad:36:a2 random", + * "rssi": -45, + * "services": [ "4567" ], + * "serviceData" : { "0123" : [ 1 ] }, + * "manufacturer" : 1424, + * "manufacturerData" : new Uint8Array([ ... ]).buffer, + * "data": new ArrayBuffer([ ... ]).buffer, + * "name": "Puck.js 36a2" + * }, + * BluetoothDevice { + * "id": "c0:52:3f:50:42:c9 random", + * "rssi": -65, + * "data": new ArrayBuffer([ ... ]), + * "name": "Puck.js 8f57" + * } + * ] + * ``` + * For more information on the structure returned, see `NRF.setScan`. + * If you want to scan only for specific devices you can replace the timeout with + * an object of the form `{filters: ..., timeout : ..., active: bool}` using the + * filters described in `NRF.requestDevice`. For example to search for devices with + * Espruino's `manufacturerData`: + * ``` + * NRF.findDevices(function(devices) { + * ... + * }, {timeout : 2000, filters : [{ manufacturerData:{0x0590:{}} }] }); + * ``` + * You could then use + * [`BluetoothDevice.gatt.connect(...)`](/Reference#l_BluetoothRemoteGATTServer_connect) + * on the device returned to make a connection. + * You can also use [`NRF.connect(...)`](/Reference#l_NRF_connect) on just the `id` + * string returned, which may be useful if you always want to connect to a specific + * device. + * **Note:** Using findDevices turns the radio's receive mode on for 2000ms (or + * however long you specify). This can draw a *lot* of power (12mA or so), so you + * should use it sparingly or you can run your battery down quickly. + * **Note:** The 'data' field contains the data of *the last packet received*. + * There may have been more packets. To get data for each packet individually use + * `NRF.setScan` instead. + * + * @param {any} callback - The callback to call with received advertising packets (as `BluetoothDevice`), or undefined to stop + * @param {any} [options] - [optional] A time in milliseconds to scan for (defaults to 2000), Or an optional object `{filters: ..., timeout : ..., active: bool}` (as would be passed to `NRF.requestDevice`) to filter devices by + * @url http://www.espruino.com/Reference#l_NRF_findDevices + */ + static findDevices(callback: (devices: BluetoothDevice[]) => void, options?: number | { filters?: NRFFilters, timeout?: number, active?: boolean }): void; + + /** + * Start/stop listening for RSSI values on the currently active connection (where + * This device is a peripheral and is being connected to by a 'central' device) + * ``` + * // Start scanning + * NRF.setRSSIHandler(function(rssi) { + * console.log(rssi); // prints -85 (or similar) + * }); + * // Stop Scanning + * NRF.setRSSIHandler(); + * ``` + * RSSI is the 'Received Signal Strength Indication' in dBm + * + * @param {any} callback - The callback to call with the RSSI value, or undefined to stop + * @url http://www.espruino.com/Reference#l_NRF_setRSSIHandler + */ + static setRSSIHandler(callback: any): void; + + /** + * Set the BLE radio transmit power. The default TX power is 0 dBm, and + * + * @param {number} power - Transmit power. Accepted values are -40(nRF52 only), -30(nRF51 only), -20, -16, -12, -8, -4, 0, and 4 dBm. On nRF52840 (eg Bangle.js 2) 5/6/7/8 dBm are available too. Others will give an error code. + * @url http://www.espruino.com/Reference#l_NRF_setTxPower + */ + static setTxPower(power: number): void; + + /** + * **THIS IS DEPRECATED** - please use `NRF.setConnectionInterval` for peripheral + * and `NRF.connect(addr, options)`/`BluetoothRemoteGATTServer.connect(options)` + * for central connections. + * This sets the connection parameters - these affect the transfer speed and power + * usage when the device is connected. + * * When not low power, the connection interval is between 7.5 and 20ms + * * When low power, the connection interval is between 500 and 1000ms + * When low power connection is enabled, transfers of data over Bluetooth will be + * very slow, however power usage while connected will be drastically decreased. + * This will only take effect after the connection is disconnected and + * re-established. + * + * @param {boolean} lowPower - Whether the connection is low power or not + * @url http://www.espruino.com/Reference#l_NRF_setLowPowerConnection + */ + static setLowPowerConnection(lowPower: boolean): void; + + /** + * Enables NFC and starts advertising the given URL. For example: + * ``` + * NRF.nfcURL("http://espruino.com"); + * ``` + * + * @param {any} url - The URL string to expose on NFC, or `undefined` to disable NFC + * @url http://www.espruino.com/Reference#l_NRF_nfcURL + */ + static nfcURL(url: any): void; + + /** + * Enables NFC and with an out of band 16 byte pairing key. + * For example the following will enable out of band pairing on BLE such that the + * device will pair when you tap the phone against it: + * ``` + * var bleKey = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00]; + * NRF.on('security',s=>print("security",JSON.stringify(s))); + * NRF.nfcPair(bleKey); + * NRF.setSecurity({oob:bleKey, mitm:true}); + * ``` + * + * @param {any} key - 16 byte out of band key + * @url http://www.espruino.com/Reference#l_NRF_nfcPair + */ + static nfcPair(key: any): void; + + /** + * Enables NFC with a record that will launch the given android app. + * For example: + * ``` + * NRF.nfcAndroidApp("no.nordicsemi.android.nrftoolbox") + * ``` + * + * @param {any} app - The unique identifier of the given Android App + * @url http://www.espruino.com/Reference#l_NRF_nfcAndroidApp + */ + static nfcAndroidApp(app: any): void; + + /** + * Enables NFC and starts advertising with Raw data. For example: + * ``` + * NRF.nfcRaw(new Uint8Array([193, 1, 0, 0, 0, 13, 85, 3, 101, 115, 112, 114, 117, 105, 110, 111, 46, 99, 111, 109])); + * // same as NRF.nfcURL("http://espruino.com"); + * ``` + * + * @param {any} payload - The NFC NDEF message to deliver to the reader + * @url http://www.espruino.com/Reference#l_NRF_nfcRaw + */ + static nfcRaw(payload: any): void; + + /** + * **Advanced NFC Functionality.** If you just want to advertise a URL, use + * `NRF.nfcURL` instead. + * Enables NFC and starts advertising. `NFCrx` events will be fired when data is + * received. + * ``` + * NRF.nfcStart(); + * ``` + * + * @param {any} payload - Optional 7 byte UID + * @returns {any} Internal tag memory (first 10 bytes of tag data) + * @url http://www.espruino.com/Reference#l_NRF_nfcStart + */ + static nfcStart(payload: any): any; + + /** + * **Advanced NFC Functionality.** If you just want to advertise a URL, use + * `NRF.nfcURL` instead. + * Disables NFC. + * ``` + * NRF.nfcStop(); + * ``` + * + * @url http://www.espruino.com/Reference#l_NRF_nfcStop + */ + static nfcStop(): void; + + /** + * **Advanced NFC Functionality.** If you just want to advertise a URL, use + * `NRF.nfcURL` instead. + * Acknowledges the last frame and optionally transmits a response. If payload is + * an array, then a array.length byte nfc frame is sent. If payload is a int, then + * a 4bit ACK/NACK is sent. **Note:** ```nfcSend``` should always be called after + * an ```NFCrx``` event. + * ``` + * NRF.nfcSend(new Uint8Array([0x01, 0x02, ...])); + * // or + * NRF.nfcSend(0x0A); + * // or + * NRF.nfcSend(); + * ``` + * + * @param {any} payload - Optional tx data + * @url http://www.espruino.com/Reference#l_NRF_nfcSend + */ + static nfcSend(payload: any): void; + + /** + * Send a USB HID report. HID must first be enabled with `NRF.setServices({}, {hid: + * hid_report})` + * + * @param {any} data - Input report data as an array + * @param {any} callback - A callback function to be called when the data is sent + * @url http://www.espruino.com/Reference#l_NRF_sendHIDReport + */ + static sendHIDReport(data: any, callback: any): void; + + /** + * Check if Apple Notification Center Service (ANCS) is currently active on the BLE + * connection + * + * @returns {boolean} True if Apple Notification Center Service (ANCS) has been initialised and is active + * @url http://www.espruino.com/Reference#l_NRF_ancsIsActive + */ + static ancsIsActive(): boolean; + + /** + * Send an ANCS action for a specific Notification UID. Corresponds to + * posaction/negaction in the 'ANCS' event that was received + * + * @param {number} uid - The UID of the notification to respond to + * @param {boolean} positive - `true` for positive action, `false` for negative + * @url http://www.espruino.com/Reference#l_NRF_ancsAction + */ + static ancsAction(uid: number, positive: boolean): void; + + /** + * Get ANCS info for a notification, eg: + * + * @param {number} uid - The UID of the notification to get information for + * @returns {any} A `Promise` that is resolved (or rejected) when the connection is complete + * @url http://www.espruino.com/Reference#l_NRF_ancsGetNotificationInfo + */ + static ancsGetNotificationInfo(uid: number): Promise; + + /** + * Get ANCS info for an app (add id is available via `ancsGetNotificationInfo`) + * Promise returns: + * ``` + * { + * "uid" : int, + * "appId" : string, + * "title" : string, + * "subtitle" : string, + * "message" : string, + * "messageSize" : string, + * "date" : string, + * "posAction" : string, + * "negAction" : string, + * "name" : string, + * } + * ``` + * + * @param {any} id - The app ID to get information for + * @returns {any} A `Promise` that is resolved (or rejected) when the connection is complete + * @url http://www.espruino.com/Reference#l_NRF_ancsGetAppInfo + */ + static ancsGetAppInfo(id: any): Promise; + + /** + * Check if Apple Media Service (AMS) is currently active on the BLE connection + * + * @returns {boolean} True if Apple Media Service (AMS) has been initialised and is active + * @url http://www.espruino.com/Reference#l_NRF_amsIsActive + */ + static amsIsActive(): boolean; + + /** + * Get Apple Media Service (AMS) info for the current media player. "playbackinfo" + * returns a concatenation of three comma-separated values: + * - PlaybackState: a string that represents the integer value of the playback + * state: + * - PlaybackStatePaused = 0 + * - PlaybackStatePlaying = 1 + * - PlaybackStateRewinding = 2 + * - PlaybackStateFastForwarding = 3 + * - PlaybackRate: a string that represents the floating point value of the + * playback rate. + * - ElapsedTime: a string that represents the floating point value of the elapsed + * time of the current track, in seconds + * + * @param {any} id - Either 'name', 'playbackinfo' or 'volume' + * @returns {any} A `Promise` that is resolved (or rejected) when the connection is complete + * @url http://www.espruino.com/Reference#l_NRF_amsGetPlayerInfo + */ + static amsGetPlayerInfo(id: any): Promise; + + /** + * Get Apple Media Service (AMS) info for the currently-playing track + * + * @param {any} id - Either 'artist', 'album', 'title' or 'duration' + * @returns {any} A `Promise` that is resolved (or rejected) when the connection is complete + * @url http://www.espruino.com/Reference#l_NRF_amsGetTrackInfo + */ + static amsGetTrackInfo(id: any): Promise; + + /** + * Send an AMS command to an Apple Media Service device to control music playback + * Command is one of play, pause, playpause, next, prev, volup, voldown, repeat, + * shuffle, skipforward, skipback, like, dislike, bookmark + * + * @param {any} id - For example, 'play', 'pause', 'volup' or 'voldown' + * @url http://www.espruino.com/Reference#l_NRF_amsCommand + */ + static amsCommand(id: any): void; + + /** + * Search for available devices matching the given filters. Since we have no UI + * here, Espruino will pick the FIRST device it finds, or it'll call `catch`. + * `options` can have the following fields: + * * `filters` - a list of filters that a device must match before it is returned + * (see below) + * * `timeout` - the maximum time to scan for in milliseconds (scanning stops when + * a match is found. eg. `NRF.requestDevice({ timeout:2000, filters: [ ... ] })` + * * `active` - whether to perform active scanning (requesting 'scan response' + * packets from any devices that are found). eg. `NRF.requestDevice({ active:true, + * filters: [ ... ] })` + * * `phy` - (NRF52840 only) use the long-range coded phy (`"1mbps"` default, can + * be `"1mbps/2mbps/both/coded"`) + * * `extended` - (NRF52840 only) support receiving extended-length advertising + * packets (default=true if phy isn't `"1mbps"`) + * **NOTE:** `timeout` and `active` are not part of the Web Bluetooth standard. + * The following filter types are implemented: + * * `services` - list of services as strings (all of which must match). 128 bit + * services must be in the form '01230123-0123-0123-0123-012301230123' + * * `name` - exact device name + * * `namePrefix` - starting characters of device name + * * `id` - exact device address (`id:"e9:53:86:09:89:99 random"`) (this is + * Espruino-specific, and is not part of the Web Bluetooth spec) + * * `serviceData` - an object containing service characteristics which must all + * match (`serviceData:{"1809":{}}`). Matching of actual service data is not + * supported yet. + * * `manufacturerData` - an object containing manufacturer UUIDs which must all + * match (`manufacturerData:{0x0590:{}}`). Matching of actual manufacturer data + * is not supported yet. + * ``` + * NRF.requestDevice({ filters: [{ namePrefix: 'Puck.js' }] }).then(function(device) { ... }); + * // or + * NRF.requestDevice({ filters: [{ services: ['1823'] }] }).then(function(device) { ... }); + * // or + * NRF.requestDevice({ filters: [{ manufacturerData:{0x0590:{}} }] }).then(function(device) { ... }); + * ``` + * As a full example, to send data to another Puck.js to turn an LED on: + * ``` + * var gatt; + * NRF.requestDevice({ filters: [{ namePrefix: 'Puck.js' }] }).then(function(device) { + * return device.gatt.connect(); + * }).then(function(g) { + * gatt = g; + * return gatt.getPrimaryService("6e400001-b5a3-f393-e0a9-e50e24dcca9e"); + * }).then(function(service) { + * return service.getCharacteristic("6e400002-b5a3-f393-e0a9-e50e24dcca9e"); + * }).then(function(characteristic) { + * return characteristic.writeValue("LED1.set()\n"); + * }).then(function() { + * gatt.disconnect(); + * console.log("Done!"); + * }); + * ``` + * Or slightly more concisely, using ES6 arrow functions: + * ``` + * var gatt; + * NRF.requestDevice({ filters: [{ namePrefix: 'Puck.js' }]}).then( + * device => device.gatt.connect()).then( + * g => (gatt=g).getPrimaryService("6e400001-b5a3-f393-e0a9-e50e24dcca9e")).then( + * service => service.getCharacteristic("6e400002-b5a3-f393-e0a9-e50e24dcca9e")).then( + * characteristic => characteristic.writeValue("LED1.reset()\n")).then( + * () => { gatt.disconnect(); console.log("Done!"); } ); + * ``` + * Note that you have to keep track of the `gatt` variable so that you can + * disconnect the Bluetooth connection when you're done. + * **Note:** Using a filter in `NRF.requestDevice` filters each advertising packet + * individually. As soon as a matching advertisement is received, + * `NRF.requestDevice` resolves the promise and stops scanning. This means that if + * you filter based on a service UUID and a device advertises with multiple packets + * (or a scan response when `active:true`) only the packet matching the filter is + * returned - you may not get the device's name is that was in a separate packet. + * To aggregate multiple packets you can use `NRF.findDevices`. + * + * @param {any} options - Options used to filter the device to use + * @returns {any} A `Promise` that is resolved (or rejected) when the connection is complete + * @url http://www.espruino.com/Reference#l_NRF_requestDevice + */ + static requestDevice(options?: { filters?: NRFFilters, timeout?: number, active?: boolean, phy?: string, extended?: boolean }): Promise; + + /** + * Connect to a BLE device by MAC address. Returns a promise, the argument of which + * is the `BluetoothRemoteGATTServer` connection. + * ``` + * NRF.connect("aa:bb:cc:dd:ee").then(function(server) { + * // ... + * }); + * ``` + * This has the same effect as calling `BluetoothDevice.gatt.connect` on a + * `BluetoothDevice` requested using `NRF.requestDevice`. It just allows you to + * specify the address directly (without having to scan). + * You can use it as follows - this would connect to another Puck device and turn + * its LED on: + * ``` + * var gatt; + * NRF.connect("aa:bb:cc:dd:ee random").then(function(g) { + * gatt = g; + * return gatt.getPrimaryService("6e400001-b5a3-f393-e0a9-e50e24dcca9e"); + * }).then(function(service) { + * return service.getCharacteristic("6e400002-b5a3-f393-e0a9-e50e24dcca9e"); + * }).then(function(characteristic) { + * return characteristic.writeValue("LED1.set()\n"); + * }).then(function() { + * gatt.disconnect(); + * console.log("Done!"); + * }); + * ``` + * **Note:** Espruino Bluetooth devices use a type of BLE address known as 'random + * static', which is different to a 'public' address. To connect to an Espruino + * device you'll need to use an address string of the form `"aa:bb:cc:dd:ee + * random"` rather than just `"aa:bb:cc:dd:ee"`. If you scan for devices with + * `NRF.findDevices`/`NRF.setScan` then addresses are already reported in the + * correct format. + * + * @param {any} mac - The MAC address to connect to + * @param {any} options - (Espruino-specific) An object of connection options (see `BluetoothRemoteGATTServer.connect` for full details) + * @returns {any} A `Promise` that is resolved (or rejected) when the connection is complete + * @url http://www.espruino.com/Reference#l_NRF_connect + */ + static connect(mac: any, options: any): Promise; + + /** + * If set to true, whenever a device bonds it will be added to the whitelist. + * When set to false, the whitelist is cleared and newly bonded devices will not be + * added to the whitelist. + * **Note:** This is remembered between `reset()`s but isn't remembered after + * power-on (you'll have to add it to `onInit()`. + * + * @param {boolean} whitelisting - Are we using a whitelist? (default false) + * @url http://www.espruino.com/Reference#l_NRF_setWhitelist + */ + static setWhitelist(whitelisting: boolean): void; + + /** + * When connected, Bluetooth LE devices communicate at a set interval. Lowering the + * interval (eg. more packets/second) means a lower delay when sending data, higher + * bandwidth, but also more power consumption. + * By default, when connected as a peripheral Espruino automatically adjusts the + * connection interval. When connected it's as fast as possible (7.5ms) but when + * idle for over a minute it drops to 200ms. On continued activity (>1 BLE + * operation) the interval is raised to 7.5ms again. + * The options for `interval` are: + * * `undefined` / `"auto"` : (default) automatically adjust connection interval + * * `100` : set min and max connection interval to the same number (between 7.5ms + * and 4000ms) + * * `{minInterval:20, maxInterval:100}` : set min and max connection interval as a + * range + * This configuration is not remembered during a `save()` - you will have to re-set + * it via `onInit`. + * **Note:** If connecting to another device (as Central), you can use an extra + * argument to `NRF.connect` or `BluetoothRemoteGATTServer.connect` to specify a + * connection interval. + * **Note:** This overwrites any changes imposed by the deprecated + * `NRF.setLowPowerConnection` + * + * @param {any} interval - The connection interval to use (see below) + * @url http://www.espruino.com/Reference#l_NRF_setConnectionInterval + */ + static setConnectionInterval(interval: any): void; + + /** + * Sets the security options used when connecting/pairing. This applies to both + * central *and* peripheral mode. + * ``` + * NRF.setSecurity({ + * display : bool // default false, can this device display a passkey + * // - sent via the `BluetoothDevice.passkey` event + * keyboard : bool // default false, can this device enter a passkey + * // - request sent via the `BluetoothDevice.passkeyRequest` event + * bond : bool // default true, Perform bonding + * mitm : bool // default false, Man In The Middle protection + * lesc : bool // default false, LE Secure Connections + * passkey : // default "", or a 6 digit passkey to use + * oob : [0..15] // if specified, Out Of Band pairing is enabled and + * // the 16 byte pairing code supplied here is used + * encryptUart : bool // default false (unless oob or passkey specified) + * // This sets the BLE UART service such that it + * // is encrypted and can only be used from a bonded connection + * }); + * ``` + * **NOTE:** Some combinations of arguments will cause an error. For example + * supplying a passkey without `display:1` is not allowed. If `display:1` is set + * you do not require a physical display, the user just needs to know the passkey + * you supplied. + * For instance, to require pairing and to specify a passkey, use: + * ``` + * NRF.setSecurity({passkey:"123456", mitm:1, display:1}); + * ``` + * However, while most devices will request a passkey for pairing at this point it + * is still possible for a device to connect without requiring one (eg. using the + * 'NRF Connect' app). + * To force a passkey you need to protect each characteristic you define with + * `NRF.setSecurity`. For instance the following code will *require* that the + * passkey `123456` is entered before the characteristic + * `9d020002-bf5f-1d1a-b52a-fe52091d5b12` can be read. + * ``` + * NRF.setSecurity({passkey:"123456", mitm:1, display:1}); + * NRF.setServices({ + * "9d020001-bf5f-1d1a-b52a-fe52091d5b12" : { + * "9d020002-bf5f-1d1a-b52a-fe52091d5b12" : { + * // readable always + * value : "Not Secret" + * }, + * "9d020003-bf5f-1d1a-b52a-fe52091d5b12" : { + * // readable only once bonded + * value : "Secret", + * readable : true, + * security: { + * read: { + * mitm: true, + * encrypted: true + * } + * } + * }, + * "9d020004-bf5f-1d1a-b52a-fe52091d5b12" : { + * // readable always + * // writable only once bonded + * value : "Readable", + * readable : true, + * writable : true, + * onWrite : function(evt) { + * console.log("Wrote ", evt.data); + * }, + * security: { + * write: { + * mitm: true, + * encrypted: true + * } + * } + * } + * } + * }); + * ``` + * **Note:** If `passkey` or `oob` is specified, the Nordic UART service (if + * enabled) will automatically be set to require encryption, but otherwise it is + * open. + * + * @param {any} options - An object containing security-related options (see below) + * @url http://www.espruino.com/Reference#l_NRF_setSecurity + */ + static setSecurity(options: any): void; + + /** + * Return an object with information about the security state of the current + * peripheral connection: + * ``` + * { + * connected // The connection is active (not disconnected). + * encrypted // Communication on this link is encrypted. + * mitm_protected // The encrypted communication is also protected against man-in-the-middle attacks. + * bonded // The peer is bonded with us + * connected_addr // If connected=true, the MAC address of the currently connected device + * } + * ``` + * If there is no active connection, `{connected:false}` will be returned. + * See `NRF.setSecurity` for information about negotiating a secure connection. + * @returns {any} An object + * @url http://www.espruino.com/Reference#l_NRF_getSecurityStatus + */ + static getSecurityStatus(): any; + + /** + * + * @param {boolean} forceRepair - True if we should force repairing even if there is already valid pairing info + * @returns {any} A promise + * @url http://www.espruino.com/Reference#l_NRF_startBonding + */ + static startBonding(forceRepair: boolean): any; + + +} + +/** + * @url http://www.espruino.com/Reference#Bluetooth + */ +declare class Bluetooth { + /** + * @url http://www.espruino.com/Reference#l_Bluetooth_setConsole + */ + static setConsole(): void; + + +} + +/** + * A Web Bluetooth-style device - you can request one using + * `NRF.requestDevice(address)` + * For example: + * ``` + * var gatt; + * NRF.requestDevice({ filters: [{ name: 'Puck.js abcd' }] }).then(function(device) { + * console.log("found device"); + * return device.gatt.connect(); + * }).then(function(g) { + * gatt = g; + * console.log("connected"); + * return gatt.startBonding(); + * }).then(function() { + * console.log("bonded", gatt.getSecurityStatus()); + * gatt.disconnect(); + * }).catch(function(e) { + * console.log("ERROR",e); + * }); + * ``` + * @url http://www.espruino.com/Reference#BluetoothDevice + */ +declare class BluetoothDevice { + /** + * Called when the device gets disconnected. + * To connect and then print `Disconnected` when the device is disconnected, just + * do the following: + * ``` + * var gatt; + * NRF.connect("aa:bb:cc:dd:ee:ff").then(function(gatt) { + * gatt.device.on('gattserverdisconnected', function(reason) { + * console.log("Disconnected ",reason); + * }); + * }); + * ``` + * Or: + * ``` + * var gatt; + * NRF.requestDevice(...).then(function(device) { + * device.on('gattserverdisconnected', function(reason) { + * console.log("Disconnected ",reason); + * }); + * }); + * ``` + * @param {string} event - The event to listen to. + * @param {(reason: number) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `reason` The reason code reported back by the BLE stack - see Nordic's `ble_hci.h` file for more information + * @url http://www.espruino.com/Reference#l_BluetoothDevice_gattserverdisconnected + */ + static on(event: "gattserverdisconnected", callback: (reason: number) => void): void; + + /** + * Called when the device pairs and sends a passkey that Espruino should display. + * For this to be used, you'll have to specify that there's a display using + * `NRF.setSecurity` + * **This is not part of the Web Bluetooth Specification.** It has been added + * specifically for Espruino. + * @param {string} event - The event to listen to. + * @param {(passkey: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `passkey` A 6 character numeric String to be displayed + * @url http://www.espruino.com/Reference#l_BluetoothDevice_passkey + */ + static on(event: "passkey", callback: (passkey: any) => void): void; + + /** + * Called when the device pairs, displays a passkey, and wants Espruino to tell it + * what the passkey was. + * Respond with `BluetoothDevice.sendPasskey()` with a 6 character string + * containing only `0..9`. + * For this to be used, you'll have to specify that there's a keyboard using + * `NRF.setSecurity` + * **This is not part of the Web Bluetooth Specification.** It has been added + * specifically for Espruino. + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_BluetoothDevice_passkeyRequest + */ + static on(event: "passkeyRequest", callback: () => void): void; + + /** + * @returns {any} A `BluetoothRemoteGATTServer` for this device + * @url http://www.espruino.com/Reference#l_BluetoothDevice_gatt + */ + gatt: any; + + /** + * @returns {boolean} The last received RSSI (signal strength) for this device + * @url http://www.espruino.com/Reference#l_BluetoothDevice_rssi + */ + rssi: boolean; + + /** + * To be used as a response when the event `BluetoothDevice.sendPasskey` has been + * received. + * **This is not part of the Web Bluetooth Specification.** It has been added + * specifically for Espruino. + * + * @param {any} passkey - A 6 character numeric String to be returned to the device + * @url http://www.espruino.com/Reference#l_BluetoothDevice_sendPasskey + */ + sendPasskey(passkey: any): void; +} + +/** + * Web Bluetooth-style GATT server - get this using `NRF.connect(address)` or + * `NRF.requestDevice(options)` and `response.gatt.connect` + * https://webbluetoothcg.github.io/web-bluetooth/#bluetoothremotegattserver + * @url http://www.espruino.com/Reference#BluetoothRemoteGATTServer + */ +declare class BluetoothRemoteGATTServer { + + + /** + * Connect to a BLE device - returns a promise, the argument of which is the + * `BluetoothRemoteGATTServer` connection. + * See [`NRF.requestDevice`](/Reference#l_NRF_requestDevice) for usage examples. + * `options` is an optional object containing: + * ``` + * { + * minInterval // min connection interval in milliseconds, 7.5 ms to 4 s + * maxInterval // max connection interval in milliseconds, 7.5 ms to 4 s + * } + * ``` + * By default the interval is 20-200ms (or 500-1000ms if + * `NRF.setLowPowerConnection(true)` was called. During connection Espruino + * negotiates with the other device to find a common interval that can be used. + * For instance calling: + * ``` + * NRF.requestDevice({ filters: [{ namePrefix: 'Pixl.js' }] }).then(function(device) { + * return device.gatt.connect({minInterval:7.5, maxInterval:7.5}); + * }).then(function(g) { + * ``` + * will force the connection to use the fastest connection interval possible (as + * long as the device at the other end supports it). + * **Note:** The Web Bluetooth spec states that if a device hasn't advertised its + * name, when connected to a device the central (in this case Espruino) should + * automatically retrieve the name from the corresponding characteristic (`0x2a00` + * on service `0x1800`). Espruino does not automatically do this. + * + * @param {any} options - (Espruino-specific) An object of connection options (see below) + * @returns {any} A `Promise` that is resolved (or rejected) when the connection is complete + * @url http://www.espruino.com/Reference#l_BluetoothRemoteGATTServer_connect + */ + connect(options: any): Promise; + + /** + * @returns {boolean} Whether the device is connected or not + * @url http://www.espruino.com/Reference#l_BluetoothRemoteGATTServer_connected + */ + connected: boolean; + + /** + * @returns {number} The handle to this device (if it is currently connected) - the handle is an internal value used by the Bluetooth Stack + * @url http://www.espruino.com/Reference#l_BluetoothRemoteGATTServer_handle + */ + handle: number; + + /** + * Disconnect from a previously connected BLE device connected with + * `BluetoothRemoteGATTServer.connect` - this does not disconnect from something + * that has connected to the Espruino. + * **Note:** While `.disconnect` is standard Web Bluetooth, in the spec it returns + * undefined not a `Promise` for implementation reasons. In Espruino we return a + * `Promise` to make it easier to detect when Espruino is free to connect to + * something else. + * @returns {any} A `Promise` that is resolved (or rejected) when the disconnection is complete (non-standard) + * @url http://www.espruino.com/Reference#l_BluetoothRemoteGATTServer_disconnect + */ + disconnect(): Promise; + + /** + * Start negotiating bonding (secure communications) with the connected device, and + * return a Promise that is completed on success or failure. + * ``` + * var gatt; + * NRF.requestDevice({ filters: [{ name: 'Puck.js abcd' }] }).then(function(device) { + * console.log("found device"); + * return device.gatt.connect(); + * }).then(function(g) { + * gatt = g; + * console.log("connected"); + * return gatt.startBonding(); + * }).then(function() { + * console.log("bonded", gatt.getSecurityStatus()); + * gatt.disconnect(); + * }).catch(function(e) { + * console.log("ERROR",e); + * }); + * ``` + * **This is not part of the Web Bluetooth Specification.** It has been added + * specifically for Espruino. + * + * @param {boolean} forceRePair - If the device is already bonded, re-pair it + * @returns {any} A `Promise` that is resolved (or rejected) when the bonding is complete + * @url http://www.espruino.com/Reference#l_BluetoothRemoteGATTServer_startBonding + */ + startBonding(forceRePair: boolean): Promise; + + /** + * Return an object with information about the security state of the current + * connection: + * ``` + * { + * connected // The connection is active (not disconnected). + * encrypted // Communication on this link is encrypted. + * mitm_protected // The encrypted communication is also protected against man-in-the-middle attacks. + * bonded // The peer is bonded with us + * } + * ``` + * See `BluetoothRemoteGATTServer.startBonding` for information about negotiating a + * secure connection. + * **This is not part of the Web Bluetooth Specification.** It has been added + * specifically for Puck.js. + * @returns {any} An object + * @url http://www.espruino.com/Reference#l_BluetoothRemoteGATTServer_getSecurityStatus + */ + getSecurityStatus(): any; + + /** + * See `NRF.connect` for usage examples. + * + * @param {any} service - The service UUID + * @returns {any} A `Promise` that is resolved (or rejected) when the primary service is found (the argument contains a `BluetoothRemoteGATTService`) + * @url http://www.espruino.com/Reference#l_BluetoothRemoteGATTServer_getPrimaryService + */ + getPrimaryService(service: any): Promise; + + /** + * @returns {any} A `Promise` that is resolved (or rejected) when the primary services are found (the argument contains an array of `BluetoothRemoteGATTService`) + * @url http://www.espruino.com/Reference#l_BluetoothRemoteGATTServer_getPrimaryServices + */ + getPrimaryServices(): Promise; + + /** + * Start/stop listening for RSSI values on the active GATT connection + * ``` + * // Start listening for RSSI value updates + * gattServer.setRSSIHandler(function(rssi) { + * console.log(rssi); // prints -85 (or similar) + * }); + * // Stop listening + * gattServer.setRSSIHandler(); + * ``` + * RSSI is the 'Received Signal Strength Indication' in dBm + * + * @param {any} callback - The callback to call with the RSSI value, or undefined to stop + * @url http://www.espruino.com/Reference#l_BluetoothRemoteGATTServer_setRSSIHandler + */ + setRSSIHandler(callback: any): void; +} + +/** + * Web Bluetooth-style GATT service - get this using + * `BluetoothRemoteGATTServer.getPrimaryService(s)` + * https://webbluetoothcg.github.io/web-bluetooth/#bluetoothremotegattservice + * @url http://www.espruino.com/Reference#BluetoothRemoteGATTService + */ +declare class BluetoothRemoteGATTService { + + + /** + * @returns {any} The `BluetoothDevice` this Service came from + * @url http://www.espruino.com/Reference#l_BluetoothRemoteGATTService_device + */ + device: any; + + /** + * See `NRF.connect` for usage examples. + * + * @param {any} characteristic - The characteristic UUID + * @returns {any} A `Promise` that is resolved (or rejected) when the characteristic is found (the argument contains a `BluetoothRemoteGATTCharacteristic`) + * @url http://www.espruino.com/Reference#l_BluetoothRemoteGATTService_getCharacteristic + */ + getCharacteristic(characteristic: any): Promise; + + /** + * @returns {any} A `Promise` that is resolved (or rejected) when the characteristic is found (the argument contains an array of `BluetoothRemoteGATTCharacteristic`) + * @url http://www.espruino.com/Reference#l_BluetoothRemoteGATTService_getCharacteristics + */ + getCharacteristics(): Promise; +} + +/** + * Web Bluetooth-style GATT characteristic - get this using + * `BluetoothRemoteGATTService.getCharacteristic(s)` + * https://webbluetoothcg.github.io/web-bluetooth/#bluetoothremotegattcharacteristic + * @url http://www.espruino.com/Reference#BluetoothRemoteGATTCharacteristic + */ +declare class BluetoothRemoteGATTCharacteristic { + /** + * Called when a characteristic's value changes, *after* + * `BluetoothRemoteGATTCharacteristic.startNotifications` has been called. + * ``` + * ... + * return service.getCharacteristic("characteristic_uuid"); + * }).then(function(c) { + * c.on('characteristicvaluechanged', function(event) { + * console.log("-> "+event.target.value); + * }); + * return c.startNotifications(); + * }).then(... + * ``` + * The first argument is of the form `{target : + * BluetoothRemoteGATTCharacteristic}`, and + * `BluetoothRemoteGATTCharacteristic.value` will then contain the new value (as a + * DataView). + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_BluetoothRemoteGATTCharacteristic_characteristicvaluechanged + */ + static on(event: "characteristicvaluechanged", callback: () => void): void; + + /** + * @returns {any} The `BluetoothRemoteGATTService` this Service came from + * @url http://www.espruino.com/Reference#l_BluetoothRemoteGATTCharacteristic_service + */ + service: any; + + /** + * Write a characteristic's value + * ``` + * var device; + * NRF.connect(device_address).then(function(d) { + * device = d; + * return d.getPrimaryService("service_uuid"); + * }).then(function(s) { + * console.log("Service ",s); + * return s.getCharacteristic("characteristic_uuid"); + * }).then(function(c) { + * return c.writeValue("Hello"); + * }).then(function(d) { + * device.disconnect(); + * }).catch(function() { + * console.log("Something's broken."); + * }); + * ``` + * + * @param {any} data - The data to write + * @returns {any} A `Promise` that is resolved (or rejected) when the characteristic is written + * @url http://www.espruino.com/Reference#l_BluetoothRemoteGATTCharacteristic_writeValue + */ + writeValue(data: any): Promise; + + /** + * Read a characteristic's value, return a promise containing a `DataView` + * ``` + * var device; + * NRF.connect(device_address).then(function(d) { + * device = d; + * return d.getPrimaryService("service_uuid"); + * }).then(function(s) { + * console.log("Service ",s); + * return s.getCharacteristic("characteristic_uuid"); + * }).then(function(c) { + * return c.readValue(); + * }).then(function(d) { + * console.log("Got:", JSON.stringify(d.buffer)); + * device.disconnect(); + * }).catch(function() { + * console.log("Something's broken."); + * }); + * ``` + * @returns {any} A `Promise` that is resolved (or rejected) with a `DataView` when the characteristic is read + * @url http://www.espruino.com/Reference#l_BluetoothRemoteGATTCharacteristic_readValue + */ + readValue(): Promise; + + /** + * Starts notifications - whenever this characteristic's value changes, a + * `characteristicvaluechanged` event is fired and `characteristic.value` will then + * contain the new value as a `DataView`. + * ``` + * var device; + * NRF.connect(device_address).then(function(d) { + * device = d; + * return d.getPrimaryService("service_uuid"); + * }).then(function(s) { + * console.log("Service ",s); + * return s.getCharacteristic("characteristic_uuid"); + * }).then(function(c) { + * c.on('characteristicvaluechanged', function(event) { + * console.log("-> ",event.target.value); // this is a DataView + * }); + * return c.startNotifications(); + * }).then(function(d) { + * console.log("Waiting for notifications"); + * }).catch(function() { + * console.log("Something's broken."); + * }); + * ``` + * For example, to listen to the output of another Puck.js's Nordic Serial port + * service, you can use: + * ``` + * var gatt; + * NRF.connect("pu:ck:js:ad:dr:es random").then(function(g) { + * gatt = g; + * return gatt.getPrimaryService("6e400001-b5a3-f393-e0a9-e50e24dcca9e"); + * }).then(function(service) { + * return service.getCharacteristic("6e400003-b5a3-f393-e0a9-e50e24dcca9e"); + * }).then(function(characteristic) { + * characteristic.on('characteristicvaluechanged', function(event) { + * console.log("RX: "+JSON.stringify(event.target.value.buffer)); + * }); + * return characteristic.startNotifications(); + * }).then(function() { + * console.log("Done!"); + * }); + * ``` + * @returns {any} A `Promise` that is resolved (or rejected) with data when notifications have been added + * @url http://www.espruino.com/Reference#l_BluetoothRemoteGATTCharacteristic_startNotifications + */ + startNotifications(): Promise; + + /** + * Stop notifications (that were requested with + * `BluetoothRemoteGATTCharacteristic.startNotifications`) + * @returns {any} A `Promise` that is resolved (or rejected) with data when notifications have been removed + * @url http://www.espruino.com/Reference#l_BluetoothRemoteGATTCharacteristic_stopNotifications + */ + stopNotifications(): Promise; +} + +/** + * Class containing utility functions for the [Bangle.js Smart + * Watch](http://www.espruino.com/Bangle.js) + * @url http://www.espruino.com/Reference#Bangle + */ +declare class Bangle { + /** + * Accelerometer data available with `{x,y,z,diff,mag}` object as a parameter. + * * `x` is X axis (left-right) in `g` + * * `y` is Y axis (up-down) in `g` + * * `z` is Z axis (in-out) in `g` + * * `diff` is difference between this and the last reading in `g` + * * `mag` is the magnitude of the acceleration in `g` + * You can also retrieve the most recent reading with `Bangle.getAccel()`. + * @param {string} event - The event to listen to. + * @param {(xyz: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `xyz` + * @url http://www.espruino.com/Reference#l_Bangle_accel + */ + static on(event: "accel", callback: (xyz: AccelData) => void): void; + + /** + * Called whenever a step is detected by Bangle.js's pedometer. + * @param {string} event - The event to listen to. + * @param {(up: number) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `up` The number of steps since Bangle.js was last reset + * @url http://www.espruino.com/Reference#l_Bangle_step + */ + static on(event: "step", callback: (up: number) => void): void; + + /** + * See `Bangle.getHealthStatus()` for more information. This is used for health + * tracking to allow Bangle.js to record historical exercise data. + * @param {string} event - The event to listen to. + * @param {(info: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `info` An object containing the last 10 minutes health data + * @url http://www.espruino.com/Reference#l_Bangle_health + */ + static on(event: "health", callback: (info: HealthStatus) => void): void; + + /** + * Has the watch been moved so that it is face-up, or not face up? + * @param {string} event - The event to listen to. + * @param {(up: boolean) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `up` `true` if face-up + * @url http://www.espruino.com/Reference#l_Bangle_faceUp + */ + static on(event: "faceUp", callback: (up: boolean) => void): void; + + /** + * This event happens when the watch has been twisted around it's axis - for + * instance as if it was rotated so someone could look at the time. + * To tweak when this happens, see the `twist*` options in `Bangle.setOptions()` + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_Bangle_twist + */ + static on(event: "twist", callback: () => void): void; + + /** + * Is the battery charging or not? + * @param {string} event - The event to listen to. + * @param {(charging: boolean) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `charging` `true` if charging + * @url http://www.espruino.com/Reference#l_Bangle_charging + */ + static on(event: "charging", callback: (charging: boolean) => void): void; + + /** + * Magnetometer/Compass data available with `{x,y,z,dx,dy,dz,heading}` object as a + * parameter + * * `x/y/z` raw x,y,z magnetometer readings + * * `dx/dy/dz` readings based on calibration since magnetometer turned on + * * `heading` in degrees based on calibrated readings (will be NaN if magnetometer + * hasn't been rotated around 360 degrees) + * To get this event you must turn the compass on with `Bangle.setCompassPower(1)`. + * You can also retrieve the most recent reading with `Bangle.getCompass()`. + * @param {string} event - The event to listen to. + * @param {(xyz: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `xyz` + * @url http://www.espruino.com/Reference#l_Bangle_mag + */ + static on(event: "mag", callback: (xyz: CompassData) => void): void; + + /** + * Raw NMEA GPS / u-blox data messages received as a string + * To get this event you must turn the GPS on with `Bangle.setGPSPower(1)`. + * @param {string} event - The event to listen to. + * @param {(nmea: any, dataLoss: boolean) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `nmea` A string containing the raw NMEA data from the GPS + * * `dataLoss` This is set to true if some lines of GPS data have previously been lost (eg because system was too busy to queue up a GPS-raw event) + * @url http://www.espruino.com/Reference#l_Bangle_GPS-raw + */ + static on(event: "GPS-raw", callback: (nmea: string, dataLoss: boolean) => void): void; + + /** + * GPS data, as an object. Contains: + * ``` + * { "lat": number, // Latitude in degrees + * "lon": number, // Longitude in degrees + * "alt": number, // altitude in M + * "speed": number, // Speed in kph + * "course": number, // Course in degrees + * "time": Date, // Current Time (or undefined if not known) + * "satellites": 7, // Number of satellites + * "fix": 1 // NMEA Fix state - 0 is no fix + * "hdop": number, // Horizontal Dilution of Precision + * } + * ``` + * If a value such as `lat` is not known because there is no fix, it'll be `NaN`. + * `hdop` is a value from the GPS receiver that gives a rough idea of accuracy of + * lat/lon based on the geometry of the satellites in range. Multiply by 5 to get a + * value in meters. This is just a ballpark estimation and should not be considered + * remotely accurate. + * To get this event you must turn the GPS on with `Bangle.setGPSPower(1)`. + * @param {string} event - The event to listen to. + * @param {(fix: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `fix` An object with fix info (see below) + * @url http://www.espruino.com/Reference#l_Bangle_GPS + */ + static on(event: "GPS", callback: (fix: GPSFix) => void): void; + + /** + * Heat rate data, as an object. Contains: + * ``` + * { "bpm": number, // Beats per minute + * "confidence": number, // 0-100 percentage confidence in the heart rate + * "raw": Uint8Array, // raw samples from heart rate monitor + * } + * ``` + * To get this event you must turn the heart rate monitor on with + * `Bangle.setHRMPower(1)`. + * @param {string} event - The event to listen to. + * @param {(hrm: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `hrm` An object with heart rate info (see below) + * @url http://www.espruino.com/Reference#l_Bangle_HRM + */ + static on(event: "HRM", callback: (hrm: { bpm: number, confidence: number, raw: Uint8Array }) => void): void; + + /** + * Called when heart rate sensor data is available - see `Bangle.setHRMPower(1)`. + * `hrm` is of the form: + * ``` + * { "raw": -1, // raw value from sensor + * "filt": -1, // bandpass-filtered raw value from sensor + * "bpm": 88.9, // last BPM value measured + * "confidence": 0 // confidence in the BPM value + * } + * ``` + * @param {string} event - The event to listen to. + * @param {(hrm: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `hrm` A object containing instant readings from the heart rate sensor + * @url http://www.espruino.com/Reference#l_Bangle_HRM-raw + */ + static on(event: "HRM-raw", callback: (hrm: { raw: number, filt: number, bpm: number, confidence: number }) => void): void; + + /** + * When `Bangle.setBarometerPower(true)` is called, this event is fired containing + * barometer readings. + * Same format as `Bangle.getPressure()` + * @param {string} event - The event to listen to. + * @param {(e: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `e` An object containing `{temperature,pressure,altitude}` + * @url http://www.espruino.com/Reference#l_Bangle_pressure + */ + static on(event: "pressure", callback: (e: PressureData) => void): void; + + /** + * Has the screen been turned on or off? Can be used to stop tasks that are no + * longer useful if nothing is displayed. Also see `Bangle.isLCDOn()` + * @param {string} event - The event to listen to. + * @param {(on: boolean) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `on` `true` if screen is on + * @url http://www.espruino.com/Reference#l_Bangle_lcdPower + */ + static on(event: "lcdPower", callback: (on: boolean) => void): void; + + /** + * Has the screen been locked? Also see `Bangle.isLocked()` + * @param {string} event - The event to listen to. + * @param {(on: boolean) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `on` `true` if screen is locked, `false` if it is unlocked and touchscreen/buttons will work + * @url http://www.espruino.com/Reference#l_Bangle_lock + */ + static on(event: "lock", callback: (on: boolean) => void): void; + + /** + * If the watch is tapped, this event contains information on the way it was + * tapped. + * `dir` reports the side of the watch that was tapped (not the direction it was + * tapped in). + * ``` + * { + * dir : "left/right/top/bottom/front/back", + * double : true/false // was this a double-tap? + * x : -2 .. 2, // the axis of the tap + * y : -2 .. 2, // the axis of the tap + * z : -2 .. 2 // the axis of the tap + * ``` + * @param {string} event - The event to listen to. + * @param {(data: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `data` `{dir, double, x, y, z}` + * @url http://www.espruino.com/Reference#l_Bangle_tap + */ + static on(event: "tap", callback: (data: { dir: "left" | "right" | "top" | "bottom" | "front" | "back", double: boolean, x: TapAxis, y: TapAxis, z: TapAxis }) => void): void; + + /** + * Emitted when a 'gesture' (fast movement) is detected + * @param {string} event - The event to listen to. + * @param {(xyz: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `xyz` An Int8Array of XYZXYZXYZ data + * @url http://www.espruino.com/Reference#l_Bangle_gesture + */ + static on(event: "gesture", callback: (xyz: Int8Array) => void): void; + + /** + * Emitted when a 'gesture' (fast movement) is detected, and a Tensorflow model is + * in storage in the `".tfmodel"` file. + * If a `".tfnames"` file is specified as a comma-separated list of names, it will + * be used to decode `gesture` from a number into a string. + * @param {string} event - The event to listen to. + * @param {(gesture: any, weights: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `gesture` The name of the gesture (if '.tfnames' exists, or the index. 'undefined' if not matching + * * `weights` An array of floating point values output by the model + * @url http://www.espruino.com/Reference#l_Bangle_aiGesture + */ + static on(event: "aiGesture", callback: (gesture: string | undefined, weights: number[]) => void): void; + + /** + * Emitted when a swipe on the touchscreen is detected (a movement from + * left->right, right->left, down->up or up->down) + * Bangle.js 1 is only capable of detecting left/right swipes as it only contains a + * 2 zone touchscreen. + * @param {string} event - The event to listen to. + * @param {(directionLR: number, directionUD: number) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `directionLR` `-1` for left, `1` for right, `0` for up/down + * * `directionUD` `-1` for up, `1` for down, `0` for left/right (Bangle.js 2 only) + * @url http://www.espruino.com/Reference#l_Bangle_swipe + */ + static on(event: "swipe", callback: SwipeCallback): void; + + /** + * Emitted when the touchscreen is pressed + * @param {string} event - The event to listen to. + * @param {(button: number, xy: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `button` `1` for left, `2` for right + * * `xy` Object of form `{x,y}` containing touch coordinates (if the device supports full touch). Clipped to 0..175 (LCD pixel coordinates) on firmware 2v13 and later. + * @url http://www.espruino.com/Reference#l_Bangle_touch + */ + static on(event: "touch", callback: TouchCallback): void; + + /** + * Emitted when the touchscreen is dragged or released + * The touchscreen extends past the edge of the screen and while `x` and `y` + * coordinates are arranged such that they align with the LCD's pixels, if your + * finger goes towards the edge of the screen, `x` and `y` could end up larger than + * 175 (the screen's maximum pixel coordinates) or smaller than 0. Coordinates from + * the `touch` event are clipped. + * @param {string} event - The event to listen to. + * @param {(event: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `event` Object of form `{x,y,dx,dy,b}` containing touch coordinates, difference in touch coordinates, and an integer `b` containing number of touch points (currently 1 or 0) + * @url http://www.espruino.com/Reference#l_Bangle_drag + */ + static on(event: "drag", callback: DragCallback): void; + + /** + * Emitted when the touchscreen is dragged for a large enough distance to count as + * a gesture. + * If Bangle.strokes is defined and populated with data from `Unistroke.new`, the + * `event` argument will also contain a `stroke` field containing the most closely + * matching stroke name. + * For example: + * ``` + * Bangle.strokes = { + * up : Unistroke.new(new Uint8Array([57, 151, ... 158, 137])), + * alpha : Unistroke.new(new Uint8Array([161, 55, ... 159, 161])), + * }; + * Bangle.on('stroke',o=>{ + * print(o.stroke); + * g.clear(1).drawPoly(o.xy); + * }); + * // Might print something like + * { + * "xy": new Uint8Array([149, 50, ... 107, 136]), + * "stroke": "alpha" + * } + * ``` + * @param {string} event - The event to listen to. + * @param {(event: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `event` Object of form `{xy:Uint8Array([x1,y1,x2,y2...])}` containing touch coordinates + * @url http://www.espruino.com/Reference#l_Bangle_stroke + */ + static on(event: "stroke", callback: (event: { xy: Uint8Array, stroke?: string }) => void): void; + + /** + * Emitted at midnight (at the point the `day` health info is reset to 0). + * Can be used for housekeeping tasks that don't want to be run during the day. + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_Bangle_midnight + */ + static on(event: "midnight", callback: () => void): void; + + /** + * This function can be used to turn Bangle.js's LCD off or on. + * This function resets the Bangle's 'activity timer' (like pressing a button or + * the screen would) so after a time period of inactivity set by + * `Bangle.setLCDTimeout` the screen will turn off. + * If you want to keep the screen on permanently (until apps are changed) you can + * do: + * ``` + * Bangle.setLCDTimeout(0); // turn off the timeout + * Bangle.setLCDPower(1); // keep screen on + * ``` + * **When on full, the LCD draws roughly 40mA.** You can adjust When brightness + * using `Bangle.setLCDBrightness`. + * + * @param {boolean} isOn - True if the LCD should be on, false if not + * @url http://www.espruino.com/Reference#l_Bangle_setLCDPower + */ + static setLCDPower(isOn: boolean): void; + + /** + * This function can be used to adjust the brightness of Bangle.js's display, and + * hence prolong its battery life. + * Due to hardware design constraints, software PWM has to be used which means that + * the display may flicker slightly when Bluetooth is active and the display is not + * at full power. + * **Power consumption** + * * 0 = 7mA + * * 0.1 = 12mA + * * 0.2 = 18mA + * * 0.5 = 28mA + * * 0.9 = 40mA (switching overhead) + * * 1 = 40mA + * + * @param {number} brightness - The brightness of Bangle.js's display - from 0(off) to 1(on full) + * @url http://www.espruino.com/Reference#l_Bangle_setLCDBrightness + */ + static setLCDBrightness(brightness: number): void; + + /** + * This function can be used to change the way graphics is handled on Bangle.js. + * Available options for `Bangle.setLCDMode` are: + * * `Bangle.setLCDMode()` or `Bangle.setLCDMode("direct")` (the default) - The + * drawable area is 240x240 16 bit. Unbuffered, so draw calls take effect + * immediately. Terminal and vertical scrolling work (horizontal scrolling + * doesn't). + * * `Bangle.setLCDMode("doublebuffered")` - The drawable area is 240x160 16 bit, + * terminal and scrolling will not work. `g.flip()` must be called for draw + * operations to take effect. + * * `Bangle.setLCDMode("120x120")` - The drawable area is 120x120 8 bit, + * `g.getPixel`, terminal, and full scrolling work. Uses an offscreen buffer + * stored on Bangle.js, `g.flip()` must be called for draw operations to take + * effect. + * * `Bangle.setLCDMode("80x80")` - The drawable area is 80x80 8 bit, `g.getPixel`, + * terminal, and full scrolling work. Uses an offscreen buffer stored on + * Bangle.js, `g.flip()` must be called for draw operations to take effect. + * You can also call `Bangle.setLCDMode()` to return to normal, unbuffered + * `"direct"` mode. + * + * @param {any} mode - The LCD mode (See below) + * @url http://www.espruino.com/Reference#l_Bangle_setLCDMode + */ + static setLCDMode(mode?: LCDMode): void; + + /** + * The current LCD mode. + * See `Bangle.setLCDMode` for examples. + * @returns {any} The LCD mode as a String + * @url http://www.espruino.com/Reference#l_Bangle_getLCDMode + */ + static getLCDMode(): LCDMode; + + /** + * This can be used to move the displayed memory area up or down temporarily. It's + * used for displaying notifications while keeping the main display contents + * intact. + * + * @param {number} y - The amount of pixels to shift the LCD up or down + * @url http://www.espruino.com/Reference#l_Bangle_setLCDOffset + */ + static setLCDOffset(y: number): void; + + /** + * This function can be used to turn Bangle.js's LCD power saving on or off. + * With power saving off, the display will remain in the state you set it with + * `Bangle.setLCDPower`. + * With power saving on, the display will turn on if a button is pressed, the watch + * is turned face up, or the screen is updated (see `Bangle.setOptions` for + * configuration). It'll turn off automatically after the given timeout. + * **Note:** This function also sets the Backlight and Lock timeout (the time at + * which the touchscreen/buttons start being ignored). To set both separately, use + * `Bangle.setOptions` + * + * @param {number} isOn - The timeout of the display in seconds, or `0`/`undefined` to turn power saving off. Default is 10 seconds. + * @url http://www.espruino.com/Reference#l_Bangle_setLCDTimeout + */ + static setLCDTimeout(isOn: number): void; + + /** + * Set how often the watch should poll for new acceleration/gyro data and kick the + * Watchdog timer. It isn't recommended that you make this interval much larger + * than 1000ms, but values up to 4000ms are allowed. + * Calling this will set `Bangle.setOptions({powerSave: false})` - disabling the + * dynamic adjustment of poll interval to save battery power when Bangle.js is + * stationary. + * + * @param {number} interval - Polling interval in milliseconds (Default is 80ms - 12.5Hz to match accelerometer) + * @url http://www.espruino.com/Reference#l_Bangle_setPollInterval + */ + static setPollInterval(interval: number): void; + + /** + * Set internal options used for gestures, etc... + * * `wakeOnBTN1` should the LCD turn on when BTN1 is pressed? default = `true` + * * `wakeOnBTN2` (Bangle.js 1) should the LCD turn on when BTN2 is pressed? + * default = `true` + * * `wakeOnBTN3` (Bangle.js 1) should the LCD turn on when BTN3 is pressed? + * default = `true` + * * `wakeOnFaceUp` should the LCD turn on when the watch is turned face up? + * default = `false` + * * `wakeOnTouch` should the LCD turn on when the touchscreen is pressed? default + * = `false` + * * `wakeOnTwist` should the LCD turn on when the watch is twisted? default = + * `true` + * * `twistThreshold` How much acceleration to register a twist of the watch strap? + * Can be negative for opposite direction. default = `800` + * * `twistMaxY` Maximum acceleration in Y to trigger a twist (low Y means watch is + * facing the right way up). default = `-800` + * * `twistTimeout` How little time (in ms) must a twist take from low->high + * acceleration? default = `1000` + * * `gestureStartThresh` how big a difference before we consider a gesture + * started? default = `sqr(800)` + * * `gestureEndThresh` how small a difference before we consider a gesture ended? + * default = `sqr(2000)` + * * `gestureInactiveCount` how many samples do we keep after a gesture has ended? + * default = `4` + * * `gestureMinLength` how many samples must a gesture have before we notify about + * it? default = `10` + * * `powerSave` after a minute of not being moved, Bangle.js will change the + * accelerometer poll interval down to 800ms (10x accelerometer samples). On + * movement it'll be raised to the default 80ms. If `Bangle.setPollInterval` is + * used this is disabled, and for it to work the poll interval must be either + * 80ms or 800ms. default = `true`. Setting `powerSave:false` will disable this + * automatic power saving, but will **not** change the poll interval from its + * current value. If you desire a specific interval (e.g. the default 80ms) you + * must set it manually with `Bangle.setPollInterval(80)` after setting + * `powerSave:false`. + * * `lockTimeout` how many milliseconds before the screen locks + * * `lcdPowerTimeout` how many milliseconds before the screen turns off + * * `backlightTimeout` how many milliseconds before the screen's backlight turns + * off + * * `hrmPollInterval` set the requested poll interval (in milliseconds) for the + * heart rate monitor. On Bangle.js 2 only 10,20,40,80,160,200 ms are supported, + * and polling rate may not be exact. The algorithm's filtering is tuned for + * 20-40ms poll intervals, so higher/lower intervals may effect the reliability + * of the BPM reading. + * * `seaLevelPressure` (Bangle.js 2) Normally 1013.25 millibars - this is used for + * calculating altitude with the pressure sensor + * Where accelerations are used they are in internal units, where `8192 = 1g` + * + * @param {any} options + * @url http://www.espruino.com/Reference#l_Bangle_setOptions + */ + static setOptions(options: { [key in keyof BangleOptions]?: BangleOptions[key] }): void; + + /** + * Return the current state of options as set by `Bangle.setOptions` + * @returns {any} The current state of all options + * @url http://www.espruino.com/Reference#l_Bangle_getOptions + */ + static getOptions(): BangleOptions; + + /** + * Also see the `Bangle.lcdPower` event + * @returns {boolean} Is the display on or not? + * @url http://www.espruino.com/Reference#l_Bangle_isLCDOn + */ + static isLCDOn(): boolean; + + /** + * This function can be used to lock or unlock Bangle.js (e.g. whether buttons and + * touchscreen work or not) + * + * @param {boolean} isLocked - `true` if the Bangle is locked (no user input allowed) + * @url http://www.espruino.com/Reference#l_Bangle_setLocked + */ + static setLocked(isLocked: boolean): void; + + /** + * Also see the `Bangle.lock` event + * @returns {boolean} Is the screen locked or not? + * @url http://www.espruino.com/Reference#l_Bangle_isLocked + */ + static isLocked(): boolean; + + /** + * @returns {boolean} Is the battery charging or not? + * @url http://www.espruino.com/Reference#l_Bangle_isCharging + */ + static isCharging(): boolean; + + /** + * Writes a command directly to the ST7735 LCD controller + * + * @param {number} cmd + * @param {any} data + * @url http://www.espruino.com/Reference#l_Bangle_lcdWr + */ + static lcdWr(cmd: number, data: any): void; + + /** + * Set the power to the Heart rate monitor + * When on, data is output via the `HRM` event on `Bangle`: + * ``` + * Bangle.setHRMPower(true, "myapp"); + * Bangle.on('HRM',print); + * ``` + * *When on, the Heart rate monitor draws roughly 5mA* + * + * @param {boolean} isOn - True if the heart rate monitor should be on, false if not + * @param {any} appID - A string with the app's name in, used to ensure one app can't turn off something another app is using + * @returns {boolean} Is HRM on? + * @url http://www.espruino.com/Reference#l_Bangle_setHRMPower + */ + static setHRMPower(isOn: boolean, appID: string): boolean; + + /** + * Is the Heart rate monitor powered? + * Set power with `Bangle.setHRMPower(...);` + * @returns {boolean} Is HRM on? + * @url http://www.espruino.com/Reference#l_Bangle_isHRMOn + */ + static isHRMOn(): boolean; + + /** + * Set the power to the GPS. + * When on, data is output via the `GPS` event on `Bangle`: + * ``` + * Bangle.setGPSPower(true, "myapp"); + * Bangle.on('GPS',print); + * ``` + * *When on, the GPS draws roughly 20mA* + * + * @param {boolean} isOn - True if the GPS should be on, false if not + * @param {any} appID - A string with the app's name in, used to ensure one app can't turn off something another app is using + * @returns {boolean} Is the GPS on? + * @url http://www.espruino.com/Reference#l_Bangle_setGPSPower + */ + static setGPSPower(isOn: boolean, appID: string): boolean; + + /** + * Is the GPS powered? + * Set power with `Bangle.setGPSPower(...);` + * @returns {boolean} Is the GPS on? + * @url http://www.espruino.com/Reference#l_Bangle_isGPSOn + */ + static isGPSOn(): boolean; + + /** + * Get the last available GPS fix info (or `undefined` if GPS is off). + * The fix info received is the same as you'd get from the `Bangle.GPS` event. + * @returns {any} A GPS fix object with `{lat,lon,...}` + * @url http://www.espruino.com/Reference#l_Bangle_getGPSFix + */ + static getGPSFix(): GPSFix; + + /** + * Set the power to the Compass + * When on, data is output via the `mag` event on `Bangle`: + * ``` + * Bangle.setCompassPower(true, "myapp"); + * Bangle.on('mag',print); + * ``` + * *When on, the compass draws roughly 2mA* + * + * @param {boolean} isOn - True if the Compass should be on, false if not + * @param {any} appID - A string with the app's name in, used to ensure one app can't turn off something another app is using + * @returns {boolean} Is the Compass on? + * @url http://www.espruino.com/Reference#l_Bangle_setCompassPower + */ + static setCompassPower(isOn: boolean, appID: string): boolean; + + /** + * Is the compass powered? + * Set power with `Bangle.setCompassPower(...);` + * @returns {boolean} Is the Compass on? + * @url http://www.espruino.com/Reference#l_Bangle_isCompassOn + */ + static isCompassOn(): boolean; + + /** + * Resets the compass minimum/maximum values. Can be used if the compass isn't + * providing a reliable heading any more. + * + * @url http://www.espruino.com/Reference#l_Bangle_resetCompass + */ + static resetCompass(): void; + + /** + * Set the power to the barometer IC. Once enabled, `Bangle.pressure` events are + * fired each time a new barometer reading is available. + * When on, the barometer draws roughly 50uA + * + * @param {boolean} isOn - True if the barometer IC should be on, false if not + * @param {any} appID - A string with the app's name in, used to ensure one app can't turn off something another app is using + * @returns {boolean} Is the Barometer on? + * @url http://www.espruino.com/Reference#l_Bangle_setBarometerPower + */ + static setBarometerPower(isOn: boolean, appID: string): boolean; + + /** + * Is the Barometer powered? + * Set power with `Bangle.setBarometerPower(...);` + * @returns {boolean} Is the Barometer on? + * @url http://www.espruino.com/Reference#l_Bangle_isBarometerOn + */ + static isBarometerOn(): boolean; + + /** + * Returns the current amount of steps recorded by the step counter + * @returns {number} The number of steps recorded by the step counter + * @url http://www.espruino.com/Reference#l_Bangle_getStepCount + */ + static getStepCount(): number; + + /** + * Sets the current value of the step counter + * + * @param {number} count - The value with which to reload the step counter + * @url http://www.espruino.com/Reference#l_Bangle_setStepCount + */ + static setStepCount(count: number): void; + + /** + * Get the most recent Magnetometer/Compass reading. Data is in the same format as + * the `Bangle.on('mag',` event. + * Returns an `{x,y,z,dx,dy,dz,heading}` object + * * `x/y/z` raw x,y,z magnetometer readings + * * `dx/dy/dz` readings based on calibration since magnetometer turned on + * * `heading` in degrees based on calibrated readings (will be NaN if magnetometer + * hasn't been rotated around 360 degrees) + * To get this event you must turn the compass on with `Bangle.setCompassPower(1)`. + * @returns {any} An object containing magnetometer readings (as below) + * @url http://www.espruino.com/Reference#l_Bangle_getCompass + */ + static getCompass(): CompassData; + + /** + * Get the most recent accelerometer reading. Data is in the same format as the + * `Bangle.on('accel',` event. + * * `x` is X axis (left-right) in `g` + * * `y` is Y axis (up-down) in `g` + * * `z` is Z axis (in-out) in `g` + * * `diff` is difference between this and the last reading in `g` (calculated by + * comparing vectors, not magnitudes) + * * `td` is the elapsed + * * `mag` is the magnitude of the acceleration in `g` + * @returns {any} An object containing accelerometer readings (as below) + * @url http://www.espruino.com/Reference#l_Bangle_getAccel + */ + static getAccel(): AccelData & { td: number }; + + /** + * `range` is one of: + * * `undefined` or `'current'` - health data so far in the last 10 minutes is + * returned, + * * `'last'` - health data during the last 10 minutes + * * `'day'` - the health data so far for the day + * `getHealthStatus` returns an object containing: + * * `movement` is the 32 bit sum of all `acc.diff` readings since power on (and + * rolls over). It is the difference in accelerometer values as `g*8192` + * * `steps` is the number of steps during this period + * * `bpm` the best BPM reading from HRM sensor during this period + * * `bpmConfidence` best BPM confidence (0-100%) during this period + * + * @param {any} range - What time period to return data for, see below: + * @returns {any} Returns an object containing various health info + * @url http://www.espruino.com/Reference#l_Bangle_getHealthStatus + */ + static getHealthStatus(range?: "current" | "last" | "day"): HealthStatus; + + /** + * Reads debug info + * @returns {any} + * @url http://www.espruino.com/Reference#l_Bangle_dbg + */ + static dbg(): any; + + /** + * Writes a register on the accelerometer + * + * @param {number} reg + * @param {number} data + * @url http://www.espruino.com/Reference#l_Bangle_accelWr + */ + static accelWr(reg: number, data: number): void; + + /** + * Reads a register from the accelerometer + * **Note:** On Espruino 2v06 and before this function only returns a number (`cnt` + * is ignored). + * + * @param {number} reg + * @param {number} cnt - If specified, returns an array of the given length (max 128). If not (or 0) it returns a number + * @returns {any} + * @url http://www.espruino.com/Reference#l_Bangle_accelRd + */ + static accelRd(reg: number, cnt?: 0): number; + static accelRd(reg: number, cnt: number): number[]; + + /** + * Writes a register on the barometer IC + * + * @param {number} reg + * @param {number} data + * @url http://www.espruino.com/Reference#l_Bangle_barometerWr + */ + static barometerWr(reg: number, data: number): void; + + /** + * Reads a register from the barometer IC + * + * @param {number} reg + * @param {number} cnt - If specified, returns an array of the given length (max 128). If not (or 0) it returns a number + * @returns {any} + * @url http://www.espruino.com/Reference#l_Bangle_barometerRd + */ + static barometerRd(reg: number, cnt?: 0): number; + static barometerRd(reg: number, cnt: number): number[]; + + /** + * Writes a register on the Magnetometer/Compass + * + * @param {number} reg + * @param {number} data + * @url http://www.espruino.com/Reference#l_Bangle_compassWr + */ + static compassWr(reg: number, data: number): void; + + /** + * Read a register on the Magnetometer/Compass + * + * @param {number} reg + * @param {number} cnt - If specified, returns an array of the given length (max 128). If not (or 0) it returns a number + * @returns {any} + * @url http://www.espruino.com/Reference#l_Bangle_compassRd + */ + static compassRd(reg: number, cnt?: 0): number; + static compassRd(reg: number, cnt: number): number[]; + + /** + * Writes a register on the Heart rate monitor + * + * @param {number} reg + * @param {number} data + * @url http://www.espruino.com/Reference#l_Bangle_hrmWr + */ + static hrmWr(reg: number, data: number): void; + + /** + * Read a register on the Heart rate monitor + * + * @param {number} reg + * @param {number} cnt - If specified, returns an array of the given length (max 128). If not (or 0) it returns a number + * @returns {any} + * @url http://www.espruino.com/Reference#l_Bangle_hrmRd + */ + static hrmRd(reg: number, cnt?: 0): number; + static hrmRd(reg: number, cnt: number): number[]; + + /** + * Changes a pin state on the IO expander + * + * @param {number} mask + * @param {number} isOn + * @url http://www.espruino.com/Reference#l_Bangle_ioWr + */ + static ioWr(mask: number, isOn: number): void; + + /** + * Read temperature, pressure and altitude data. A promise is returned which will + * be resolved with `{temperature, pressure, altitude}`. + * If the Barometer has been turned on with `Bangle.setBarometerPower` then this + * will return almost immediately with the reading. If the Barometer is off, + * conversions take between 500-750ms. + * Altitude assumes a sea-level pressure of 1013.25 hPa + * ``` + * Bangle.getPressure().then(d=>{ + * console.log(d); + * // {temperature, pressure, altitude} + * }); + * ``` + * @returns {any} A promise that will be resolved with `{temperature, pressure, altitude}` + * @url http://www.espruino.com/Reference#l_Bangle_getPressure + */ + static getPressure(): PressureData; + + /** + * Perform a Spherical [Web Mercator + * projection](https://en.wikipedia.org/wiki/Web_Mercator_projection) of latitude + * and longitude into `x` and `y` coordinates, which are roughly equivalent to + * meters from `{lat:0,lon:0}`. + * This is the formula used for most online mapping and is a good way to compare + * GPS coordinates to work out the distance between them. + * + * @param {any} latlong - `{lat:..., lon:...}` + * @returns {any} {x:..., y:...} + * @url http://www.espruino.com/Reference#l_Bangle_project + */ + static project(latlong: { lat: number, lon: number }): { x: number, y: number }; + + /** + * Use the piezo speaker to Beep for a certain time period and frequency + * + * @param {number} [time] - [optional] Time in ms (default 200) + * @param {number} [freq] - [optional] Frequency in hz (default 4000) + * @returns {any} A promise, completed when beep is finished + * @url http://www.espruino.com/Reference#l_Bangle_beep + */ + static beep(time?: number, freq?: number): Promise; + + /** + * Use the vibration motor to buzz for a certain time period + * + * @param {number} [time] - [optional] Time in ms (default 200) + * @param {number} [strength] - [optional] Power of vibration from 0 to 1 (Default 1) + * @returns {any} A promise, completed when vibration is finished + * @url http://www.espruino.com/Reference#l_Bangle_buzz + */ + static buzz(time?: number, strength?: number): Promise; + + /** + * Turn Bangle.js off. It can only be woken by pressing BTN1. + * @url http://www.espruino.com/Reference#l_Bangle_off + */ + static off(): void; + + /** + * Turn Bangle.js (mostly) off, but keep the CPU in sleep mode until BTN1 is + * pressed to preserve the RTC (current time). + * @url http://www.espruino.com/Reference#l_Bangle_softOff + */ + static softOff(): void; + + /** + * * On platforms with an LCD of >=8bpp this is 222 x 104 x 2 bits + * * Otherwise it's 119 x 56 x 1 bits + * @returns {any} An image to be used with `g.drawImage` (as a String) + * @url http://www.espruino.com/Reference#l_Bangle_getLogo + */ + static getLogo(): string; + + /** + * Load all widgets from flash Storage. Call this once at the beginning of your + * application if you want any on-screen widgets to be loaded. + * They will be loaded into a global `WIDGETS` array, and can be rendered with + * `Bangle.drawWidgets`. + * @url http://www.espruino.com/Reference#l_Bangle_loadWidgets + */ + static loadWidgets(): void; + + /** + * Draw any onscreen widgets that were loaded with `Bangle.loadWidgets()`. + * Widgets should redraw themselves when something changes - you'll only need to + * call drawWidgets if you decide to clear the entire screen with `g.clear()`. + * @url http://www.espruino.com/Reference#l_Bangle_drawWidgets + */ + static drawWidgets(): void; + + /** + * @url http://www.espruino.com/Reference#l_Bangle_drawWidgets + */ + static drawWidgets(): void; + + /** + * Load the Bangle.js app launcher, which will allow the user to select an + * application to launch. + * @url http://www.espruino.com/Reference#l_Bangle_showLauncher + */ + static showLauncher(): void; + + /** + * This puts Bangle.js into the specified UI input mode, and calls the callback + * provided when there is user input. + * Currently supported interface types are: + * * 'updown' - UI input with upwards motion `cb(-1)`, downwards motion `cb(1)`, + * and select `cb()` + * * Bangle.js 1 uses BTN1/3 for up/down and BTN2 for select + * * Bangle.js 2 uses touchscreen swipe up/down and tap + * * 'leftright' - UI input with left motion `cb(-1)`, right motion `cb(1)`, and + * select `cb()` + * * Bangle.js 1 uses BTN1/3 for left/right and BTN2 for select + * * Bangle.js 2 uses touchscreen swipe left/right and tap/BTN1 for select + * * 'clock' - called for clocks. Sets `Bangle.CLOCK=1` and allows a button to + * start the launcher + * * Bangle.js 1 BTN2 starts the launcher + * * Bangle.js 2 BTN1 starts the launcher + * * 'clockupdown' - called for clocks. Sets `Bangle.CLOCK=1`, allows a button to + * start the launcher, but also provides up/down functionality + * * Bangle.js 1 BTN2 starts the launcher, BTN1/BTN3 call `cb(-1)` and `cb(1)` + * * Bangle.js 2 BTN1 starts the launcher, touchscreen tap in top/bottom right + * hand side calls `cb(-1)` and `cb(1)` + * * `{mode:"custom", ...}` allows you to specify custom handlers for different + * interactions. See below. + * * `undefined` removes all user interaction code + * While you could use setWatch/etc manually, the benefit here is that you don't + * end up with multiple `setWatch` instances, and the actual input method (touch, + * or buttons) is implemented dependent on the watch (Bangle.js 1 or 2) + * **Note:** You can override this function in boot code to change the interaction + * mode with the watch. For instance you could make all clocks start the launcher + * with a swipe by using: + * ``` + * (function() { + * var sui = Bangle.setUI; + * Bangle.setUI = function(mode, cb) { + * if (mode!="clock") return sui(mode,cb); + * sui(); // clear + * Bangle.CLOCK=1; + * Bangle.swipeHandler = Bangle.showLauncher; + * Bangle.on("swipe", Bangle.swipeHandler); + * }; + * })(); + * ``` + * The first argument can also be an object, in which case more options can be + * specified: + * ``` + * Bangle.setUI({ + * mode : "custom", + * back : function() {}, // optional - add a 'back' icon in top-left widget area and call this function when it is pressed + * touch : function(n,e) {}, // optional - handler for 'touch' events + * swipe : function(dir) {}, // optional - handler for 'swipe' events + * drag : function(e) {}, // optional - handler for 'drag' events (Bangle.js 2 only) + * btn : function(n) {}, // optional - handler for 'button' events (n==1 on Bangle.js 2, n==1/2/3 depending on button for Bangle.js 1) + * clock : 0 // optional - if set the behavior of 'clock' mode is added (does not override btn if defined) + * }); + * ``` + * + * @param {any} type - The type of UI input: 'updown', 'leftright', 'clock', 'clockupdown' or undefined to cancel. Can also be an object (see below) + * @param {any} callback - A function with one argument which is the direction + * @url http://www.espruino.com/Reference#l_Bangle_setUI + */ + static setUI(type?: "updown" | "leftright" | "clock" | "clockupdown" | { mode: "custom"; back?: () => void; touch?: TouchCallback; swipe?: SwipeCallback; drag?: DragCallback; btn?: (n: number) => void, clock?: boolean }, callback?: (direction?: -1 | 1) => void): void; + + /** + * @url http://www.espruino.com/Reference#l_Bangle_setUI + */ + static setUI(): void; + + /** + * Erase all storage and reload it with the default contents. + * This is only available on Bangle.js 2.0. On Bangle.js 1.0 you need to use + * `Install Default Apps` under the `More...` tab of http://banglejs.com/apps + * @url http://www.espruino.com/Reference#l_Bangle_factoryReset + */ + static factoryReset(): void; + + /** + * Returns the rectangle on the screen that is currently reserved for the app. + * @returns {any} An object of the form `{x,y,w,h,x2,y2}` + * @url http://www.espruino.com/Reference#l_Bangle_appRect + */ + static appRect: { x: number, y: number, w: number, h: number, x2: number, y2: number }; + + static CLOCK: boolean; + static strokes: undefined | { [key: string]: Unistroke }; +} + +interface DateConstructor { + /** + * Get the number of milliseconds elapsed since 1970 (or on embedded platforms, + * since startup) + * @returns {number} + * @url http://www.espruino.com/Reference#l_Date_now + */ + now(): number; + + /** + * Parse a date string and return milliseconds since 1970. Data can be either + * '2011-10-20T14:48:00', '2011-10-20' or 'Mon, 25 Dec 1995 13:30:00 +0430' + * + * @param {any} str - A String + * @returns {number} The number of milliseconds since 1970 + * @url http://www.espruino.com/Reference#l_Date_parse + */ + parse(str: string): number; + + /** + * Creates a date object + * @constructor + * + * @param {any} args - Either nothing (current time), one numeric argument (milliseconds since 1970), a date string (see `Date.parse`), or [year, month, day, hour, minute, second, millisecond] + * @returns {any} A Date object + * @url http://www.espruino.com/Reference#l_Date_Date + */ + new(): Date; + new(value: number | string): Date; + new(year: number, month: number, date?: number, hours?: number, minutes?: number, seconds?: number, ms?: number): Date; +} + +interface Date { + /** + * This returns the time-zone offset from UTC, in minutes. + * @returns {number} The difference, in minutes, between UTC and local time + * @url http://www.espruino.com/Reference#l_Date_getTimezoneOffset + */ + getTimezoneOffset(): number; + + /** + * This returns a boolean indicating whether daylight savings time is in effect. + * @returns {number} true if daylight savings time is in effect + * @url http://www.espruino.com/Reference#l_Date_getIsDST + */ + getIsDST(): boolean + + /** + * Return the number of milliseconds since 1970 + * @returns {number} + * @url http://www.espruino.com/Reference#l_Date_getTime + */ + getTime(): number; + + /** + * Return the number of milliseconds since 1970 + * @returns {number} + * @url http://www.espruino.com/Reference#l_Date_valueOf + */ + valueOf(): number; + + /** + * Set the time/date of this Date class + * + * @param {number} timeValue - the number of milliseconds since 1970 + * @returns {number} the number of milliseconds since 1970 + * @url http://www.espruino.com/Reference#l_Date_setTime + */ + setTime(timeValue: number): number; + + /** + * 0..23 + * @returns {number} + * @url http://www.espruino.com/Reference#l_Date_getHours + */ + getHours(): number; + + /** + * 0..59 + * @returns {number} + * @url http://www.espruino.com/Reference#l_Date_getMinutes + */ + getMinutes(): number; + + /** + * 0..59 + * @returns {number} + * @url http://www.espruino.com/Reference#l_Date_getSeconds + */ + getSeconds(): number; + + /** + * 0..999 + * @returns {number} + * @url http://www.espruino.com/Reference#l_Date_getMilliseconds + */ + getMilliseconds(): number; + + /** + * Day of the week (0=sunday, 1=monday, etc) + * @returns {number} + * @url http://www.espruino.com/Reference#l_Date_getDay + */ + getDay(): number; + + /** + * Day of the month 1..31 + * @returns {number} + * @url http://www.espruino.com/Reference#l_Date_getDate + */ + getDate(): number; + + /** + * Month of the year 0..11 + * @returns {number} + * @url http://www.espruino.com/Reference#l_Date_getMonth + */ + getMonth(): number; + + /** + * The year, eg. 2014 + * @returns {number} + * @url http://www.espruino.com/Reference#l_Date_getFullYear + */ + getFullYear(): number; + + /** + * 0..23 + * + * @param {number} hoursValue - number of hours, 0..23 + * @param {any} minutesValue - number of minutes, 0..59 + * @param {any} secondsValue - optional - number of seconds, 0..59 + * @param {any} millisecondsValue - optional - number of milliseconds, 0..999 + * @returns {number} The number of milliseconds since 1970 + * @url http://www.espruino.com/Reference#l_Date_setHours + */ + setHours(hoursValue: number, minutesValue?: number, secondsValue?: number, millisecondsValue?: number): number; + + /** + * 0..59 + * + * @param {number} minutesValue - number of minutes, 0..59 + * @param {any} secondsValue - optional - number of seconds, 0..59 + * @param {any} millisecondsValue - optional - number of milliseconds, 0..999 + * @returns {number} The number of milliseconds since 1970 + * @url http://www.espruino.com/Reference#l_Date_setMinutes + */ + setMinutes(minutesValue: number, secondsValue?: number, millisecondsValue?: number): number; + + /** + * 0..59 + * + * @param {number} secondsValue - number of seconds, 0..59 + * @param {any} millisecondsValue - optional - number of milliseconds, 0..999 + * @returns {number} The number of milliseconds since 1970 + * @url http://www.espruino.com/Reference#l_Date_setSeconds + */ + setSeconds(secondsValue: number, millisecondsValue?: number): number; + + /** + * + * @param {number} millisecondsValue - number of milliseconds, 0..999 + * @returns {number} The number of milliseconds since 1970 + * @url http://www.espruino.com/Reference#l_Date_setMilliseconds + */ + setMilliseconds(millisecondsValue: number): number; + + /** + * Day of the month 1..31 + * + * @param {number} dayValue - the day of the month, between 0 and 31 + * @returns {number} The number of milliseconds since 1970 + * @url http://www.espruino.com/Reference#l_Date_setDate + */ + setDate(dayValue: number): number; + + /** + * Month of the year 0..11 + * + * @param {number} yearValue - The month, between 0 and 11 + * @param {any} dayValue - optional - the day, between 0 and 31 + * @returns {number} The number of milliseconds since 1970 + * @url http://www.espruino.com/Reference#l_Date_setMonth + */ + setMonth(yearValue: number, dayValue?: number): number; + + /** + * + * @param {number} yearValue - The full year - eg. 1989 + * @param {any} monthValue - optional - the month, between 0 and 11 + * @param {any} dayValue - optional - the day, between 0 and 31 + * @returns {number} The number of milliseconds since 1970 + * @url http://www.espruino.com/Reference#l_Date_setFullYear + */ + setFullYear(yearValue: number, monthValue?: number, dayValue?: number): number; + + /** + * Converts to a String, eg: `Fri Jun 20 2014 14:52:20 GMT+0000` + * **Note:** This uses whatever timezone was set with `E.setTimeZone()` or + * `E.setDST()` + * @returns {any} A String + * @url http://www.espruino.com/Reference#l_Date_toString + */ + toString(): string; + + /** + * Converts to a String, eg: `Fri, 20 Jun 2014 14:52:20 GMT` + * **Note:** This always assumes a timezone of GMT + * @returns {any} A String + * @url http://www.espruino.com/Reference#l_Date_toUTCString + */ + toUTCString(): string; + + /** + * Converts to a ISO 8601 String, eg: `2014-06-20T14:52:20.123Z` + * **Note:** This always assumes a timezone of GMT + * @returns {any} A String + * @url http://www.espruino.com/Reference#l_Date_toISOString + */ + toISOString(): string; + + /** + * Calls `Date.toISOString` to output this date to JSON + * @returns {any} A String + * @url http://www.espruino.com/Reference#l_Date_toJSON + */ + toJSON(): string; + + /** + * Converts to a ISO 8601 String (with timezone information), eg: + * `2014-06-20T14:52:20.123-0500` + * @returns {any} A String + * @url http://www.espruino.com/Reference#l_Date_toLocalISOString + */ + toLocalISOString(): string; +} + +/** + * The built-in class for handling Dates. + * **Note:** By default the time zone is GMT+0, however you can change the timezone + * using the `E.setTimeZone(...)` function. + * For example `E.setTimeZone(1)` will be GMT+0100 + * *However* if you have daylight savings time set with `E.setDST(...)` then the + * timezone set by `E.setTimeZone(...)` will be _ignored_. + * @url http://www.espruino.com/Reference#Date + */ +declare const Date: DateConstructor + +/** + * This class provides a software-defined OneWire master. It is designed to be + * similar to Arduino's OneWire library. + * @url http://www.espruino.com/Reference#OneWire + */ +declare class OneWire { + /** + * Create a software OneWire implementation on the given pin + * @constructor + * + * @param {Pin} pin - The pin to implement OneWire on + * @returns {any} A OneWire object + * @url http://www.espruino.com/Reference#l_OneWire_OneWire + */ + static new(pin: Pin): any; + + /** + * Perform a reset cycle + * @returns {boolean} True is a device was present (it held the bus low) + * @url http://www.espruino.com/Reference#l_OneWire_reset + */ + reset(): boolean; + + /** + * Select a ROM - always performs a reset first + * + * @param {any} rom - The device to select (get this using `OneWire.search()`) + * @url http://www.espruino.com/Reference#l_OneWire_select + */ + select(rom: any): void; + + /** + * Skip a ROM + * @url http://www.espruino.com/Reference#l_OneWire_skip + */ + skip(): void; + + /** + * Write one or more bytes + * + * @param {any} data - A byte (or array of bytes) to write + * @param {boolean} power - Whether to leave power on after write (default is false) + * @url http://www.espruino.com/Reference#l_OneWire_write + */ + write(data: any, power: boolean): void; + + /** + * Read a byte + * + * @param {any} count - (optional) The amount of bytes to read + * @returns {any} The byte that was read, or a Uint8Array if count was specified and >=0 + * @url http://www.espruino.com/Reference#l_OneWire_read + */ + read(count: any): any; + + /** + * Search for devices + * + * @param {number} command - (Optional) command byte. If not specified (or zero), this defaults to 0xF0. This can could be set to 0xEC to perform a DS18B20 'Alarm Search Command' + * @returns {any} An array of devices that were found + * @url http://www.espruino.com/Reference#l_OneWire_search + */ + search(command: number): any; +} + +interface NumberConstructor { + /** + * @returns {number} Not a Number + * @url http://www.espruino.com/Reference#l_Number_NaN + */ + NaN: number; + + /** + * @returns {number} Maximum representable value + * @url http://www.espruino.com/Reference#l_Number_MAX_VALUE + */ + MAX_VALUE: number; + + /** + * @returns {number} Smallest representable value + * @url http://www.espruino.com/Reference#l_Number_MIN_VALUE + */ + MIN_VALUE: number; + + /** + * @returns {number} Negative Infinity (-1/0) + * @url http://www.espruino.com/Reference#l_Number_NEGATIVE_INFINITY + */ + NEGATIVE_INFINITY: number; + + /** + * @returns {number} Positive Infinity (1/0) + * @url http://www.espruino.com/Reference#l_Number_POSITIVE_INFINITY + */ + POSITIVE_INFINITY: number; + + /** + * Creates a number + * @constructor + * + * @param {any} value - A single value to be converted to a number + * @returns {any} A Number object + * @url http://www.espruino.com/Reference#l_Number_Number + */ + new(...value: any[]): any; +} + +interface Number { + /** + * Format the number as a fixed point number + * + * @param {number} decimalPlaces - A number between 0 and 20 specifying the number of decimal digits after the decimal point + * @returns {any} A string + * @url http://www.espruino.com/Reference#l_Number_toFixed + */ + toFixed(decimalPlaces: number): any; +} + +/** + * This is the built-in JavaScript class for numbers. + * @url http://www.espruino.com/Reference#Number + */ +declare const Number: NumberConstructor + +interface ArrayBufferConstructor { + /** + * Create an Array Buffer object + * @constructor + * + * @param {number} byteLength - The length in Bytes + * @returns {any} An ArrayBuffer object + * @url http://www.espruino.com/Reference#l_ArrayBuffer_ArrayBuffer + */ + new(byteLength: number): ArrayBuffer; +} + +interface ArrayBuffer { + /** + * The length, in bytes, of the `ArrayBuffer` + * @returns {number} The Length in bytes + * @url http://www.espruino.com/Reference#l_ArrayBuffer_byteLength + */ + byteLength: number; +} + +/** + * This is the built-in JavaScript class for array buffers. + * If you want to access arrays of differing types of data you may also find + * `DataView` useful. + * @url http://www.espruino.com/Reference#ArrayBuffer + */ +declare const ArrayBuffer: ArrayBufferConstructor + +/** + * This is the built-in JavaScript class that is the prototype for: + * * [Uint8Array](/Reference#Uint8Array) + * * [UintClamped8Array](/Reference#UintClamped8Array) + * * [Int8Array](/Reference#Int8Array) + * * [Uint16Array](/Reference#Uint16Array) + * * [Int16Array](/Reference#Int16Array) + * * [Uint24Array](/Reference#Uint24Array) (Espruino-specific - not standard JS) + * * [Uint32Array](/Reference#Uint32Array) + * * [Int32Array](/Reference#Int32Array) + * * [Float32Array](/Reference#Float32Array) + * * [Float64Array](/Reference#Float64Array) + * If you want to access arrays of differing types of data you may also find + * `DataView` useful. + * @url http://www.espruino.com/Reference#ArrayBufferView + */ +declare class ArrayBufferView { + + + /** + * The buffer this view references + * @returns {any} An ArrayBuffer object + * @url http://www.espruino.com/Reference#l_ArrayBufferView_buffer + */ + readonly buffer: T; + + /** + * The length, in bytes, of the `ArrayBufferView` + * @returns {number} The Length + * @url http://www.espruino.com/Reference#l_ArrayBufferView_byteLength + */ + readonly byteLength: number; + + /** + * The offset, in bytes, to the first byte of the view within the backing + * `ArrayBuffer` + * @returns {number} The byte Offset + * @url http://www.espruino.com/Reference#l_ArrayBufferView_byteOffset + */ + readonly byteOffset: number; + + /** + * Copy the contents of `array` into this one, mapping `this[x+offset]=array[x];` + * + * @param {any} arr - Floating point index to access + * @param {number} offset - The offset in this array at which to write the values (optional) + * @url http://www.espruino.com/Reference#l_ArrayBufferView_set + */ + set(arr: ArrayLike, offset: number): void + + /** + * Return an array which is made from the following: ```A.map(function) = + * [function(A[0]), function(A[1]), ...]``` + * **Note:** This returns an `ArrayBuffer` of the same type it was called on. To + * get an `Array`, use `Array.map`, e.g. `[].map.call(myArray, x=>x+1)` + * + * @param {any} function - Function used to map one item to another + * @param {any} thisArg - if specified, the function is called with 'this' set to thisArg (optional) + * @returns {any} An array containing the results + * @url http://www.espruino.com/Reference#l_ArrayBufferView_map + */ + map(callbackfn: (value: number, index: number, array: T) => number, thisArg?: any): T; + + /** + * Returns a smaller part of this array which references the same data (it doesn't + * copy it). + * + * @param {number} begin - Element to begin at, inclusive. If negative, this is from the end of the array. The entire array is included if this isn't specified + * @param {any} end - Element to end at, exclusive. If negative, it is relative to the end of the array. If not specified the whole array is included + * @returns {any} An `ArrayBufferView` of the same type as this one, referencing the same data + * @url http://www.espruino.com/Reference#l_ArrayBufferView_subarray + */ + subarray(begin?: number, end?: number): T; + + /** + * Return the index of the value in the array, or `-1` + * + * @param {any} value - The value to check for + * @param {number} startIndex - (optional) the index to search from, or 0 if not specified + * @returns {any} the index of the value in the array, or -1 + * @url http://www.espruino.com/Reference#l_ArrayBufferView_indexOf + */ + indexOf(value: number, startIndex?: number): number; + + /** + * Return `true` if the array includes the value, `false` otherwise + * + * @param {any} value - The value to check for + * @param {number} startIndex - (optional) the index to search from, or 0 if not specified + * @returns {boolean} `true` if the array includes the value, `false` otherwise + * @url http://www.espruino.com/Reference#l_ArrayBufferView_includes + */ + includes(value: number, startIndex?: number): boolean; + + /** + * Join all elements of this array together into one string, using 'separator' + * between them. e.g. ```[1,2,3].join(' ')=='1 2 3'``` + * + * @param {any} separator - The separator + * @returns {any} A String representing the Joined array + * @url http://www.espruino.com/Reference#l_ArrayBufferView_join + */ + join(separator?: string): string; + + /** + * Do an in-place quicksort of the array + * + * @param {any} var - A function to use to compare array elements (or undefined) + * @returns {any} This array object + * @url http://www.espruino.com/Reference#l_ArrayBufferView_sort + */ + sort(compareFn?: (a: number, b: number) => number): this; + + /** + * Executes a provided function once per array element. + * + * @param {any} function - Function to be executed + * @param {any} thisArg - if specified, the function is called with 'this' set to thisArg (optional) + * @url http://www.espruino.com/Reference#l_ArrayBufferView_forEach + */ + forEach(callbackfn: (value: number, index: number, array: T) => void, thisArg?: any): void; + + /** + * Execute `previousValue=initialValue` and then `previousValue = + * callback(previousValue, currentValue, index, array)` for each element in the + * array, and finally return previousValue. + * + * @param {any} callback - Function used to reduce the array + * @param {any} initialValue - if specified, the initial value to pass to the function + * @returns {any} The value returned by the last function called + * @url http://www.espruino.com/Reference#l_ArrayBufferView_reduce + */ + reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: T) => number, initialValue?: number): number; + + /** + * Fill this array with the given value, for every index `>= start` and `< end` + * + * @param {any} value - The value to fill the array with + * @param {number} start - Optional. The index to start from (or 0). If start is negative, it is treated as length+start where length is the length of the array + * @param {any} end - Optional. The index to end at (or the array length). If end is negative, it is treated as length+end. + * @returns {any} This array + * @url http://www.espruino.com/Reference#l_ArrayBufferView_fill + */ + fill(value: number, start?: number, end?: number): T; + + /** + * Return an array which contains only those elements for which the callback + * function returns 'true' + * + * @param {any} function - Function to be executed + * @param {any} thisArg - if specified, the function is called with 'this' set to thisArg (optional) + * @returns {any} An array containing the results + * @url http://www.espruino.com/Reference#l_ArrayBufferView_filter + */ + filter(predicate: (value: number, index: number, array: T) => any, thisArg?: any): T; + + /** + * Return the array element where `function` returns `true`, or `undefined` if it + * doesn't returns `true` for any element. + * + * @param {any} function - Function to be executed + * @returns {any} The array element where `function` returns `true`, or `undefined` + * @url http://www.espruino.com/Reference#l_ArrayBufferView_find + */ + find(predicate: (value: number, index: number, obj: T) => boolean, thisArg?: any): number | undefined; + + /** + * Return the array element's index where `function` returns `true`, or `-1` if it + * doesn't returns `true` for any element. + * + * @param {any} function - Function to be executed + * @returns {any} The array element's index where `function` returns `true`, or `-1` + * @url http://www.espruino.com/Reference#l_ArrayBufferView_findIndex + */ + findIndex(predicate: (value: number, index: number, obj: T) => boolean, thisArg?: any): number; + + /** + * Reverse the contents of this `ArrayBufferView` in-place + * @returns {any} This array + * @url http://www.espruino.com/Reference#l_ArrayBufferView_reverse + */ + reverse(): T + + /** + * Return a copy of a portion of this array (in a new array). + * **Note:** This currently returns a normal `Array`, not an `ArrayBuffer` + * + * @param {number} start - Start index + * @param {any} end - End index (optional) + * @returns {any} A new array + * @url http://www.espruino.com/Reference#l_ArrayBufferView_slice + */ + slice(start?: number, end?: number): number[]; + + [index: number]: number +} + +interface Uint8ArrayConstructor { + /** + * Create a typed array based on the given input. Either an existing Array Buffer, + * an Integer as a Length, or a simple array. If an `ArrayBufferView` (e.g. + * `Uint8Array` rather than `ArrayBuffer`) is given, it will be completely copied + * rather than referenced. + * @constructor + * + * @param {any} arr - The array or typed array to base this off, or an integer which is the array length + * @param {number} byteOffset - The byte offset in the ArrayBuffer (ONLY IF the first argument was an ArrayBuffer) + * @param {number} length - The length (ONLY IF the first argument was an ArrayBuffer) + * @returns {any} A typed array + * @url http://www.espruino.com/Reference#l_Uint8Array_Uint8Array + */ + new(length: number): Uint8Array; + new(array: ArrayLike): Uint8Array; + new(buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint8Array; +} + +type Uint8Array = ArrayBufferView; + +declare const Uint8Array: Uint8ArrayConstructor + +interface Uint8ClampedArrayConstructor { + /** + * Create a typed array based on the given input. Either an existing Array Buffer, + * an Integer as a Length, or a simple array. If an `ArrayBufferView` (e.g. + * `Uint8Array` rather than `ArrayBuffer`) is given, it will be completely copied + * rather than referenced. + * Clamped arrays clamp their values to the allowed range, rather than 'wrapping'. + * e.g. after `a[0]=12345;`, `a[0]==255`. + * @constructor + * + * @param {any} arr - The array or typed array to base this off, or an integer which is the array length + * @param {number} byteOffset - The byte offset in the ArrayBuffer (ONLY IF the first argument was an ArrayBuffer) + * @param {number} length - The length (ONLY IF the first argument was an ArrayBuffer) + * @returns {any} A typed array + * @url http://www.espruino.com/Reference#l_Uint8ClampedArray_Uint8ClampedArray + */ + new(length: number): Uint8ClampedArray; + new(array: ArrayLike): Uint8ClampedArray; + new(buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint8ClampedArray; +} + +type Uint8ClampedArray = ArrayBufferView; + +declare const Uint8ClampedArray: Uint8ClampedArrayConstructor + +interface Int8ArrayConstructor { + /** + * Create a typed array based on the given input. Either an existing Array Buffer, + * an Integer as a Length, or a simple array. If an `ArrayBufferView` (e.g. + * `Uint8Array` rather than `ArrayBuffer`) is given, it will be completely copied + * rather than referenced. + * @constructor + * + * @param {any} arr - The array or typed array to base this off, or an integer which is the array length + * @param {number} byteOffset - The byte offset in the ArrayBuffer (ONLY IF the first argument was an ArrayBuffer) + * @param {number} length - The length (ONLY IF the first argument was an ArrayBuffer) + * @returns {any} A typed array + * @url http://www.espruino.com/Reference#l_Int8Array_Int8Array + */ + new(length: number): Int8Array; + new(array: ArrayLike): Int8Array; + new(buffer: ArrayBuffer, byteOffset?: number, length?: number): Int8Array; +} + +type Int8Array = ArrayBufferView; + +declare const Int8Array: Int8ArrayConstructor + +interface Uint16ArrayConstructor { + /** + * Create a typed array based on the given input. Either an existing Array Buffer, + * an Integer as a Length, or a simple array. If an `ArrayBufferView` (e.g. + * `Uint8Array` rather than `ArrayBuffer`) is given, it will be completely copied + * rather than referenced. + * @constructor + * + * @param {any} arr - The array or typed array to base this off, or an integer which is the array length + * @param {number} byteOffset - The byte offset in the ArrayBuffer (ONLY IF the first argument was an ArrayBuffer) + * @param {number} length - The length (ONLY IF the first argument was an ArrayBuffer) + * @returns {any} A typed array + * @url http://www.espruino.com/Reference#l_Uint16Array_Uint16Array + */ + new(length: number): Uint16Array; + new(array: ArrayLike): Uint16Array; + new(buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint16Array; +} + +type Uint16Array = ArrayBufferView; + +declare const Uint16Array: Uint16ArrayConstructor + +interface Int16ArrayConstructor { + /** + * Create a typed array based on the given input. Either an existing Array Buffer, + * an Integer as a Length, or a simple array. If an `ArrayBufferView` (e.g. + * `Uint8Array` rather than `ArrayBuffer`) is given, it will be completely copied + * rather than referenced. + * @constructor + * + * @param {any} arr - The array or typed array to base this off, or an integer which is the array length + * @param {number} byteOffset - The byte offset in the ArrayBuffer (ONLY IF the first argument was an ArrayBuffer) + * @param {number} length - The length (ONLY IF the first argument was an ArrayBuffer) + * @returns {any} A typed array + * @url http://www.espruino.com/Reference#l_Int16Array_Int16Array + */ + new(length: number): Int16Array; + new(array: ArrayLike): Int16Array; + new(buffer: ArrayBuffer, byteOffset?: number, length?: number): Int16Array; +} + +type Int16Array = ArrayBufferView; + +declare const Int16Array: Int16ArrayConstructor + +/** + * This is the built-in JavaScript class for a typed array of 24 bit unsigned + * integers. + * Instantiate this in order to efficiently store arrays of data (Espruino's normal + * arrays store data in a map, which is inefficient for non-sparse arrays). + * Arrays of this type include all the methods from + * [ArrayBufferView](/Reference#ArrayBufferView) + * @url http://www.espruino.com/Reference#Uint24Array + */ +declare class Uint24Array { + /** + * Create a typed array based on the given input. Either an existing Array Buffer, + * an Integer as a Length, or a simple array. If an `ArrayBufferView` (e.g. + * `Uint8Array` rather than `ArrayBuffer`) is given, it will be completely copied + * rather than referenced. + * @constructor + * + * @param {any} arr - The array or typed array to base this off, or an integer which is the array length + * @param {number} byteOffset - The byte offset in the ArrayBuffer (ONLY IF the first argument was an ArrayBuffer) + * @param {number} length - The length (ONLY IF the first argument was an ArrayBuffer) + * @returns {any} A typed array + * @url http://www.espruino.com/Reference#l_Uint24Array_Uint24Array + */ + static new(length: number): Uint24Array; + static new(array: ArrayLike): Uint24Array; + static new(buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint24Array; + + +} + +interface Uint32ArrayConstructor { + /** + * Create a typed array based on the given input. Either an existing Array Buffer, + * an Integer as a Length, or a simple array. If an `ArrayBufferView` (e.g. + * `Uint8Array` rather than `ArrayBuffer`) is given, it will be completely copied + * rather than referenced. + * @constructor + * + * @param {any} arr - The array or typed array to base this off, or an integer which is the array length + * @param {number} byteOffset - The byte offset in the ArrayBuffer (ONLY IF the first argument was an ArrayBuffer) + * @param {number} length - The length (ONLY IF the first argument was an ArrayBuffer) + * @returns {any} A typed array + * @url http://www.espruino.com/Reference#l_Uint32Array_Uint32Array + */ + new(length: number): Uint32Array; + new(array: ArrayLike): Uint32Array; + new(buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint32Array; +} + +type Uint32Array = ArrayBufferView; + +declare const Uint32Array: Uint32ArrayConstructor + +interface Int32ArrayConstructor { + /** + * Create a typed array based on the given input. Either an existing Array Buffer, + * an Integer as a Length, or a simple array. If an `ArrayBufferView` (e.g. + * `Uint8Array` rather than `ArrayBuffer`) is given, it will be completely copied + * rather than referenced. + * @constructor + * + * @param {any} arr - The array or typed array to base this off, or an integer which is the array length + * @param {number} byteOffset - The byte offset in the ArrayBuffer (ONLY IF the first argument was an ArrayBuffer) + * @param {number} length - The length (ONLY IF the first argument was an ArrayBuffer) + * @returns {any} A typed array + * @url http://www.espruino.com/Reference#l_Int32Array_Int32Array + */ + new(length: number): Int32Array; + new(array: ArrayLike): Int32Array; + new(buffer: ArrayBuffer, byteOffset?: number, length?: number): Int32Array; +} + +type Int32Array = ArrayBufferView; + +declare const Int32Array: Int32ArrayConstructor + +interface Float32ArrayConstructor { + /** + * Create a typed array based on the given input. Either an existing Array Buffer, + * an Integer as a Length, or a simple array. If an `ArrayBufferView` (e.g. + * `Uint8Array` rather than `ArrayBuffer`) is given, it will be completely copied + * rather than referenced. + * @constructor + * + * @param {any} arr - The array or typed array to base this off, or an integer which is the array length + * @param {number} byteOffset - The byte offset in the ArrayBuffer (ONLY IF the first argument was an ArrayBuffer) + * @param {number} length - The length (ONLY IF the first argument was an ArrayBuffer) + * @returns {any} A typed array + * @url http://www.espruino.com/Reference#l_Float32Array_Float32Array + */ + new(length: number): Float32Array; + new(array: ArrayLike): Float32Array; + new(buffer: ArrayBuffer, byteOffset?: number, length?: number): Float32Array; +} + +type Float32Array = ArrayBufferView; + +declare const Float32Array: Float32ArrayConstructor + +interface Float64ArrayConstructor { + /** + * Create a typed array based on the given input. Either an existing Array Buffer, + * an Integer as a Length, or a simple array. If an `ArrayBufferView` (e.g. + * `Uint8Array` rather than `ArrayBuffer`) is given, it will be completely copied + * rather than referenced. + * @constructor + * + * @param {any} arr - The array or typed array to base this off, or an integer which is the array length + * @param {number} byteOffset - The byte offset in the ArrayBuffer (ONLY IF the first argument was an ArrayBuffer) + * @param {number} length - The length (ONLY IF the first argument was an ArrayBuffer) + * @returns {any} A typed array + * @url http://www.espruino.com/Reference#l_Float64Array_Float64Array + */ + new(length: number): Float64Array; + new(array: ArrayLike): Float64Array; + new(buffer: ArrayBuffer, byteOffset?: number, length?: number): Float64Array; +} + +type Float64Array = ArrayBufferView; + +declare const Float64Array: Float64ArrayConstructor + +interface PromiseConstructor { + /** + * Return a new promise that is resolved when all promises in the supplied array + * are resolved. + * + * @param {any} promises - An array of promises + * @returns {any} A new Promise + * @url http://www.espruino.com/Reference#l_Promise_all + */ + all(promises: Promise[]): Promise; + + /** + * Return a new promise that is already resolved (at idle it'll call `.then`) + * + * @param {any} promises - Data to pass to the `.then` handler + * @returns {any} A new Promise + * @url http://www.espruino.com/Reference#l_Promise_resolve + */ + resolve(promises: T): Promise; + + /** + * Return a new promise that is already rejected (at idle it'll call `.catch`) + * + * @param {any} promises - Data to pass to the `.catch` handler + * @returns {any} A new Promise + * @url http://www.espruino.com/Reference#l_Promise_reject + */ + reject(promises: any): any; + + /** + * Create a new Promise. The executor function is executed immediately (before the + * constructor even returns) and + * @constructor + * + * @param {any} executor - A function of the form `function (resolve, reject)` + * @returns {any} A Promise + * @url http://www.espruino.com/Reference#l_Promise_Promise + */ + new(executor: (resolve: (value: T) => void, reject: (reason?: any) => void) => void): Promise; +} + +interface Promise { + /** + * + * @param {any} onFulfilled - A callback that is called when this promise is resolved + * @param {any} [onRejected] - [optional] A callback that is called when this promise is rejected (or nothing) + * @returns {any} The original Promise + * @url http://www.espruino.com/Reference#l_Promise_then + */ + then(onfulfilled?: ((value: T) => TResult1 | Promise) | undefined | null, onrejected?: ((reason: any) => TResult2 | Promise) | undefined | null): Promise; + + /** + * + * @param {any} onRejected - A callback that is called when this promise is rejected + * @returns {any} The original Promise + * @url http://www.espruino.com/Reference#l_Promise_catch + */ + catch(onRejected: any): any; +} + +/** + * This is the built-in class for ES6 Promises + * @url http://www.espruino.com/Reference#Promise + */ +declare const Promise: PromiseConstructor + +/** + * This class allows use of the built-in SPI ports. Currently it is SPI master + * only. + * @url http://www.espruino.com/Reference#SPI + */ +declare class SPI { + /** + * Try and find an SPI hardware device that will work on this pin (eg. `SPI1`) + * May return undefined if no device can be found. + * + * @param {Pin} pin - A pin to search with + * @returns {any} An object of type `SPI`, or `undefined` if one couldn't be found. + * @url http://www.espruino.com/Reference#l_SPI_find + */ + static find(pin: Pin): any; + + /** + * Create a software SPI port. This has limited functionality (no baud rate), but + * it can work on any pins. + * Use `SPI.setup` to configure this port. + * @constructor + * @returns {any} A SPI object + * @url http://www.espruino.com/Reference#l_SPI_SPI + */ + static new(): any; + + /** + * Set up this SPI port as an SPI Master. + * Options can contain the following (defaults are shown where relevant): + * ``` + * { + * sck:pin, + * miso:pin, + * mosi:pin, + * baud:integer=100000, // ignored on software SPI + * mode:integer=0, // between 0 and 3 + * order:string='msb' // can be 'msb' or 'lsb' + * bits:8 // only available for software SPI + * } + * ``` + * If `sck`,`miso` and `mosi` are left out, they will automatically be chosen. + * However if one or more is specified then the unspecified pins will not be set + * up. + * You can find out which pins to use by looking at [your board's reference + * page](#boards) and searching for pins with the `SPI` marker. Some boards such as + * those based on `nRF52` chips can have SPI on any pins, so don't have specific + * markings. + * The SPI `mode` is between 0 and 3 - see + * http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus#Clock_polarity_and_phase + * On STM32F1-based parts, you cannot mix AF and non-AF pins (SPI pins are usually + * grouped on the chip - and you can't mix pins from two groups). Espruino will not + * warn you about this. + * + * @param {any} options - An Object containing extra information on initialising the SPI port + * @url http://www.espruino.com/Reference#l_SPI_setup + */ + setup(options: any): void; + + /** + * Send data down SPI, and return the result. Sending an integer will return an + * integer, a String will return a String, and anything else will return a + * Uint8Array. + * Sending multiple bytes in one call to send is preferable as they can then be + * transmitted end to end. Using multiple calls to send() will result in + * significantly slower transmission speeds. + * For maximum speeds, please pass either Strings or Typed Arrays as arguments. + * Note that you can even pass arrays of arrays, like `[1,[2,3,4],5]` + * + * @param {any} data - The data to send - either an Integer, Array, String, or Object of the form `{data: ..., count:#}` + * @param {Pin} nss_pin - An nSS pin - this will be lowered before SPI output and raised afterwards (optional). There will be a small delay between when this is lowered and when sending starts, and also between sending finishing and it being raised. + * @returns {any} The data that was returned + * @url http://www.espruino.com/Reference#l_SPI_send + */ + send(data: any, nss_pin: Pin): any; + + /** + * Write a character or array of characters to SPI - without reading the result + * back. + * For maximum speeds, please pass either Strings or Typed Arrays as arguments. + * + * @param {any} data + * One or more items to write. May be ints, strings, arrays, or special objects (see `E.toUint8Array` for more info). + * If the last argument is a pin, it is taken to be the NSS pin + * @url http://www.espruino.com/Reference#l_SPI_write + */ + write(...data: any[]): void; + + /** + * Send data down SPI, using 4 bits for each 'real' bit (MSB first). This can be + * useful for faking one-wire style protocols + * Sending multiple bytes in one call to send is preferable as they can then be + * transmitted end to end. Using multiple calls to send() will result in + * significantly slower transmission speeds. + * + * @param {any} data - The data to send - either an integer, array, or string + * @param {number} bit0 - The 4 bits to send for a 0 (MSB first) + * @param {number} bit1 - The 4 bits to send for a 1 (MSB first) + * @param {Pin} nss_pin - An nSS pin - this will be lowered before SPI output and raised afterwards (optional). There will be a small delay between when this is lowered and when sending starts, and also between sending finishing and it being raised. + * @url http://www.espruino.com/Reference#l_SPI_send4bit + */ + send4bit(data: any, bit0: number, bit1: number, nss_pin: Pin): void; + + /** + * Send data down SPI, using 8 bits for each 'real' bit (MSB first). This can be + * useful for faking one-wire style protocols + * Sending multiple bytes in one call to send is preferable as they can then be + * transmitted end to end. Using multiple calls to send() will result in + * significantly slower transmission speeds. + * + * @param {any} data - The data to send - either an integer, array, or string + * @param {number} bit0 - The 8 bits to send for a 0 (MSB first) + * @param {number} bit1 - The 8 bits to send for a 1 (MSB first) + * @param {Pin} nss_pin - An nSS pin - this will be lowered before SPI output and raised afterwards (optional). There will be a small delay between when this is lowered and when sending starts, and also between sending finishing and it being raised + * @url http://www.espruino.com/Reference#l_SPI_send8bit + */ + send8bit(data: any, bit0: number, bit1: number, nss_pin: Pin): void; +} + +/** + * This class allows use of the built-in I2C ports. Currently it allows I2C Master + * mode only. + * All addresses are in 7 bit format. If you have an 8 bit address then you need to + * shift it one bit to the right. + * @url http://www.espruino.com/Reference#I2C + */ +declare class I2C { + /** + * Try and find an I2C hardware device that will work on this pin (eg. `I2C1`) + * May return undefined if no device can be found. + * + * @param {Pin} pin - A pin to search with + * @returns {any} An object of type `I2C`, or `undefined` if one couldn't be found. + * @url http://www.espruino.com/Reference#l_I2C_find + */ + static find(pin: Pin): any; + + /** + * Create a software I2C port. This has limited functionality (no baud rate), but + * it can work on any pins. + * Use `I2C.setup` to configure this port. + * @constructor + * @returns {any} An I2C object + * @url http://www.espruino.com/Reference#l_I2C_I2C + */ + static new(): any; + + /** + * Set up this I2C port + * If not specified in options, the default pins are used (usually the lowest + * numbered pins on the lowest port that supports this peripheral) + * + * @param {any} options + * An optional structure containing extra information on initialising the I2C port + * ```{scl:pin, sda:pin, bitrate:100000}``` + * You can find out which pins to use by looking at [your board's reference page](#boards) and searching for pins with the `I2C` marker. Note that 400kHz is the maximum bitrate for most parts. + * @url http://www.espruino.com/Reference#l_I2C_setup + */ + setup(options: any): void; + + /** + * Transmit to the slave device with the given address. This is like Arduino's + * beginTransmission, write, and endTransmission rolled up into one. + * + * @param {any} address - The 7 bit address of the device to transmit to, or an object of the form `{address:12, stop:false}` to send this data without a STOP signal. + * @param {any} data - One or more items to write. May be ints, strings, arrays, or special objects (see `E.toUint8Array` for more info). + * @url http://www.espruino.com/Reference#l_I2C_writeTo + */ + writeTo(address: any, ...data: any[]): void; + + /** + * Request bytes from the given slave device, and return them as a Uint8Array + * (packed array of bytes). This is like using Arduino Wire's requestFrom, + * available and read functions. Sends a STOP + * + * @param {any} address - The 7 bit address of the device to request bytes from, or an object of the form `{address:12, stop:false}` to send this data without a STOP signal. + * @param {number} quantity - The number of bytes to request + * @returns {any} The data that was returned - as a Uint8Array + * @url http://www.espruino.com/Reference#l_I2C_readFrom + */ + readFrom(address: any, quantity: number): Uint8Array; +} + +/** + * This class handles waveforms. In Espruino, a Waveform is a set of data that you + * want to input or output. + * @url http://www.espruino.com/Reference#Waveform + */ +declare class Waveform { + /** + * Create a waveform class. This allows high speed input and output of waveforms. + * It has an internal variable called `buffer` (as well as `buffer2` when + * double-buffered - see `options` below) which contains the data to input/output. + * When double-buffered, a 'buffer' event will be emitted each time a buffer is + * finished with (the argument is that buffer). When the recording stops, a + * 'finish' event will be emitted (with the first argument as the buffer). + * @constructor + * + * @param {number} samples - The number of samples + * @param {any} options - Optional options struct `{doubleBuffer:bool, bits : 8/16}` where: `doubleBuffer` is whether to allocate two buffers or not (default false), and bits is the amount of bits to use (default 8). + * @returns {any} An Waveform object + * @url http://www.espruino.com/Reference#l_Waveform_Waveform + */ + static new(samples: number, options: any): any; + + /** + * Will start outputting the waveform on the given pin - the pin must have + * previously been initialised with analogWrite. If not repeating, it'll emit a + * `finish` event when it is done. + * + * @param {Pin} output - The pin to output on + * @param {number} freq - The frequency to output each sample at + * @param {any} options - Optional options struct `{time:float,repeat:bool}` where: `time` is the that the waveform with start output at, e.g. `getTime()+1` (otherwise it is immediate), `repeat` is a boolean specifying whether to repeat the give sample + * @url http://www.espruino.com/Reference#l_Waveform_startOutput + */ + startOutput(output: Pin, freq: number, options: any): void; + + /** + * Will start inputting the waveform on the given pin that supports analog. If not + * repeating, it'll emit a `finish` event when it is done. + * + * @param {Pin} output - The pin to output on + * @param {number} freq - The frequency to output each sample at + * @param {any} options - Optional options struct `{time:float,repeat:bool}` where: `time` is the that the waveform with start output at, e.g. `getTime()+1` (otherwise it is immediate), `repeat` is a boolean specifying whether to repeat the give sample + * @url http://www.espruino.com/Reference#l_Waveform_startInput + */ + startInput(output: Pin, freq: number, options: any): void; + + /** + * Stop a waveform that is currently outputting + * @url http://www.espruino.com/Reference#l_Waveform_stop + */ + stop(): void; +} + +/** + * This is the built-in class for Pins, such as D0,D1,LED1, or BTN + * You can call the methods on Pin, or you can use Wiring-style functions such as + * digitalWrite + * @url http://www.espruino.com/Reference#Pin + */ +declare class Pin { + /** + * Creates a pin from the given argument (or returns undefined if no argument) + * @constructor + * + * @param {any} value - A value to be converted to a pin. Can be a number, pin, or String. + * @returns {any} A Pin object + * @url http://www.espruino.com/Reference#l_Pin_Pin + */ + static new(value: any): any; + + /** + * Returns the input state of the pin as a boolean. + * **Note:** if you didn't call `pinMode` beforehand then this function will also + * reset the pin's state to `"input"` + * @returns {boolean} Whether pin is a logical 1 or 0 + * @url http://www.espruino.com/Reference#l_Pin_read + */ + read(): boolean; + + /** + * Sets the output state of the pin to a 1 + * **Note:** if you didn't call `pinMode` beforehand then this function will also + * reset the pin's state to `"output"` + * @url http://www.espruino.com/Reference#l_Pin_set + */ + set(): void; + + /** + * Sets the output state of the pin to a 0 + * **Note:** if you didn't call `pinMode` beforehand then this function will also + * reset the pin's state to `"output"` + * @url http://www.espruino.com/Reference#l_Pin_reset + */ + reset(): void; + + /** + * Sets the output state of the pin to the parameter given + * **Note:** if you didn't call `pinMode` beforehand then this function will also + * reset the pin's state to `"output"` + * + * @param {boolean} value - Whether to set output high (true/1) or low (false/0) + * @url http://www.espruino.com/Reference#l_Pin_write + */ + write(value: boolean): void; + + /** + * Sets the output state of the pin to the parameter given at the specified time. + * **Note:** this **doesn't** change the mode of the pin to an output. To do that, + * you need to use `pin.write(0)` or `pinMode(pin, 'output')` first. + * + * @param {boolean} value - Whether to set output high (true/1) or low (false/0) + * @param {number} time - Time at which to write + * @url http://www.espruino.com/Reference#l_Pin_writeAtTime + */ + writeAtTime(value: boolean, time: number): void; + + /** + * Return the current mode of the given pin. See `pinMode` for more information. + * @returns {any} The pin mode, as a string + * @url http://www.espruino.com/Reference#l_Pin_getMode + */ + getMode(): any; + + /** + * Set the mode of the given pin. See [`pinMode`](#l__global_pinMode) for more + * information on pin modes. + * + * @param {any} mode - The mode - a string that is either 'analog', 'input', 'input_pullup', 'input_pulldown', 'output', 'opendrain', 'af_output' or 'af_opendrain'. Do not include this argument if you want to revert to automatic pin mode setting. + * @url http://www.espruino.com/Reference#l_Pin_mode + */ + mode(mode: any): void; + + /** + * Toggles the state of the pin from off to on, or from on to off. + * **Note:** This method doesn't currently work on the ESP8266 port of Espruino. + * **Note:** if you didn't call `pinMode` beforehand then this function will also + * reset the pin's state to `"output"` + * @returns {boolean} True if the pin is high after calling the function + * @url http://www.espruino.com/Reference#l_Pin_toggle + */ + toggle(): boolean; + + /** + * Get information about this pin and its capabilities. Of the form: + * ``` + * { + * "port" : "A", // the Pin's port on the chip + * "num" : 12, // the Pin's number + * "in_addr" : 0x..., // (if available) the address of the pin's input address in bit-banded memory (can be used with peek) + * "out_addr" : 0x..., // (if available) the address of the pin's output address in bit-banded memory (can be used with poke) + * "analog" : { ADCs : [1], channel : 12 }, // If analog input is available + * "functions" : { + * "TIM1":{type:"CH1, af:0}, + * "I2C3":{type:"SCL", af:1} + * } + * } + * ``` + * Will return undefined if pin is not valid. + * @returns {any} An object containing information about this pins + * @url http://www.espruino.com/Reference#l_Pin_getInfo + */ + getInfo(): any; +} + +interface DataViewConstructor { + /** + * Create a `DataView` object that can be used to access the data in an + * `ArrayBuffer`. + * ``` + * var b = new ArrayBuffer(8) + * var v = new DataView(b) + * v.setUint16(0,"0x1234") + * v.setUint8(3,"0x56") + * console.log("0x"+v.getUint32(0).toString(16)) + * // prints 0x12340056 + * ``` + * @constructor + * + * @param {any} buffer - The `ArrayBuffer` to base this on + * @param {number} byteOffset - (optional) The offset of this view in bytes + * @param {number} byteLength - (optional) The length in bytes + * @returns {any} A `DataView` object + * @url http://www.espruino.com/Reference#l_DataView_DataView + */ + new(buffer: ArrayBuffer, byteOffset?: number, byteLength?: number): DataView; +} + +interface DataView { + /** + * + * @param {number} byteOffset - The offset in bytes to read from + * @param {boolean} littleEndian - (optional) Whether to read in little endian - if false or undefined data is read as big endian + * @returns {any} the index of the value in the array, or -1 + * @url http://www.espruino.com/Reference#l_DataView_getFloat32 + */ + getFloat32(byteOffset: number, littleEndian?: boolean): number; + + /** + * + * @param {number} byteOffset - The offset in bytes to read from + * @param {boolean} littleEndian - (optional) Whether to read in little endian - if false or undefined data is read as big endian + * @returns {any} the index of the value in the array, or -1 + * @url http://www.espruino.com/Reference#l_DataView_getFloat64 + */ + getFloat64(byteOffset: number, littleEndian?: boolean): number; + + /** + * + * @param {number} byteOffset - The offset in bytes to read from + * @param {boolean} littleEndian - (optional) Whether to read in little endian - if false or undefined data is read as big endian + * @returns {any} the index of the value in the array, or -1 + * @url http://www.espruino.com/Reference#l_DataView_getInt8 + */ + getInt8(byteOffset: number, littleEndian?: boolean): number; + + /** + * + * @param {number} byteOffset - The offset in bytes to read from + * @param {boolean} littleEndian - (optional) Whether to read in little endian - if false or undefined data is read as big endian + * @returns {any} the index of the value in the array, or -1 + * @url http://www.espruino.com/Reference#l_DataView_getInt16 + */ + getInt16(byteOffset: number, littleEndian?: boolean): number; + + /** + * + * @param {number} byteOffset - The offset in bytes to read from + * @param {boolean} littleEndian - (optional) Whether to read in little endian - if false or undefined data is read as big endian + * @returns {any} the index of the value in the array, or -1 + * @url http://www.espruino.com/Reference#l_DataView_getInt32 + */ + getInt32(byteOffset: number, littleEndian?: boolean): number; + + /** + * + * @param {number} byteOffset - The offset in bytes to read from + * @param {boolean} littleEndian - (optional) Whether to read in little endian - if false or undefined data is read as big endian + * @returns {any} the index of the value in the array, or -1 + * @url http://www.espruino.com/Reference#l_DataView_getUint8 + */ + getUint8(byteOffset: number, littleEndian?: boolean): number; + + /** + * + * @param {number} byteOffset - The offset in bytes to read from + * @param {boolean} littleEndian - (optional) Whether to read in little endian - if false or undefined data is read as big endian + * @returns {any} the index of the value in the array, or -1 + * @url http://www.espruino.com/Reference#l_DataView_getUint16 + */ + getUint16(byteOffset: number, littleEndian?: boolean): number; + + /** + * + * @param {number} byteOffset - The offset in bytes to read from + * @param {boolean} littleEndian - (optional) Whether to read in little endian - if false or undefined data is read as big endian + * @returns {any} the index of the value in the array, or -1 + * @url http://www.espruino.com/Reference#l_DataView_getUint32 + */ + getUint32(byteOffset: number, littleEndian?: boolean): number; + + /** + * + * @param {number} byteOffset - The offset in bytes to read from + * @param {any} value - The value to write + * @param {boolean} littleEndian - (optional) Whether to read in little endian - if false or undefined data is read as big endian + * @url http://www.espruino.com/Reference#l_DataView_setFloat32 + */ + setFloat32(byteOffset: number, value: number, littleEndian?: boolean): void; + + /** + * + * @param {number} byteOffset - The offset in bytes to read from + * @param {any} value - The value to write + * @param {boolean} littleEndian - (optional) Whether to read in little endian - if false or undefined data is read as big endian + * @url http://www.espruino.com/Reference#l_DataView_setFloat64 + */ + setFloat64(byteOffset: number, value: number, littleEndian?: boolean): void; + + /** + * + * @param {number} byteOffset - The offset in bytes to read from + * @param {any} value - The value to write + * @param {boolean} littleEndian - (optional) Whether to read in little endian - if false or undefined data is read as big endian + * @url http://www.espruino.com/Reference#l_DataView_setInt8 + */ + setInt8(byteOffset: number, value: number, littleEndian?: boolean): void; + + /** + * + * @param {number} byteOffset - The offset in bytes to read from + * @param {any} value - The value to write + * @param {boolean} littleEndian - (optional) Whether to read in little endian - if false or undefined data is read as big endian + * @url http://www.espruino.com/Reference#l_DataView_setInt16 + */ + setInt16(byteOffset: number, value: number, littleEndian?: boolean): void; + + /** + * + * @param {number} byteOffset - The offset in bytes to read from + * @param {any} value - The value to write + * @param {boolean} littleEndian - (optional) Whether to read in little endian - if false or undefined data is read as big endian + * @url http://www.espruino.com/Reference#l_DataView_setInt32 + */ + setInt32(byteOffset: number, value: number, littleEndian?: boolean): void; + + /** + * + * @param {number} byteOffset - The offset in bytes to read from + * @param {any} value - The value to write + * @param {boolean} littleEndian - (optional) Whether to read in little endian - if false or undefined data is read as big endian + * @url http://www.espruino.com/Reference#l_DataView_setUint8 + */ + setUint8(byteOffset: number, value: number, littleEndian?: boolean): void; + + /** + * + * @param {number} byteOffset - The offset in bytes to read from + * @param {any} value - The value to write + * @param {boolean} littleEndian - (optional) Whether to read in little endian - if false or undefined data is read as big endian + * @url http://www.espruino.com/Reference#l_DataView_setUint16 + */ + setUint16(byteOffset: number, value: number, littleEndian?: boolean): void; + + /** + * + * @param {number} byteOffset - The offset in bytes to read from + * @param {any} value - The value to write + * @param {boolean} littleEndian - (optional) Whether to read in little endian - if false or undefined data is read as big endian + * @url http://www.espruino.com/Reference#l_DataView_setUint32 + */ + setUint32(byteOffset: number, value: number, littleEndian?: boolean): void; +} + +/** + * This class helps + * @url http://www.espruino.com/Reference#DataView + */ +declare const DataView: DataViewConstructor + +/** + * This class allows use of the built-in USARTs + * Methods may be called on the `USB`, `Serial1`, `Serial2`, `Serial3`, `Serial4`, + * `Serial5` and `Serial6` objects. While different processors provide different + * numbers of USARTs, on official Espruino boards you can always rely on at least + * `Serial1` being available + * @url http://www.espruino.com/Reference#Serial + */ +declare class Serial { + /** + * The `data` event is called when data is received. If a handler is defined with + * `X.on('data', function(data) { ... })` then it will be called, otherwise data + * will be stored in an internal buffer, where it can be retrieved with `X.read()` + * @param {string} event - The event to listen to. + * @param {(data: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `data` A string containing one or more characters of received data + * @url http://www.espruino.com/Reference#l_Serial_data + */ + static on(event: "data", callback: (data: any) => void): void; + + /** + * The `framing` event is called when there was activity on the input to the UART + * but the `STOP` bit wasn't in the correct place. This is either because there was + * noise on the line, or the line has been pulled to 0 for a long period of time. + * To enable this, you must initialise Serial with `SerialX.setup(..., { ..., + * errors:true });` + * **Note:** Even though there was an error, the byte will still be received and + * passed to the `data` handler. + * **Note:** This only works on STM32 and NRF52 based devices (eg. all official + * Espruino boards) + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_Serial_framing + */ + static on(event: "framing", callback: () => void): void; + + /** + * The `parity` event is called when the UART was configured with a parity bit, and + * this doesn't match the bits that have actually been received. + * To enable this, you must initialise Serial with `SerialX.setup(..., { ..., + * errors:true });` + * **Note:** Even though there was an error, the byte will still be received and + * passed to the `data` handler. + * **Note:** This only works on STM32 and NRF52 based devices (eg. all official + * Espruino boards) + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_Serial_parity + */ + static on(event: "parity", callback: () => void): void; + + /** + * Try and find a USART (Serial) hardware device that will work on this pin (eg. + * `Serial1`) + * May return undefined if no device can be found. + * + * @param {Pin} pin - A pin to search with + * @returns {any} An object of type `Serial`, or `undefined` if one couldn't be found. + * @url http://www.espruino.com/Reference#l_Serial_find + */ + static find(pin: Pin): any; + + /** + * Create a software Serial port. This has limited functionality (only low baud + * rates), but it can work on any pins. + * Use `Serial.setup` to configure this port. + * @constructor + * @returns {any} A Serial object + * @url http://www.espruino.com/Reference#l_Serial_Serial + */ + static new(): any; + + /** + * Set this Serial port as the port for the JavaScript console (REPL). + * Unless `force` is set to true, changes in the connection state of the board (for + * instance plugging in USB) will cause the console to change. + * See `E.setConsole` for a more flexible version of this function. + * + * @param {boolean} force - Whether to force the console to this port + * @url http://www.espruino.com/Reference#l_Serial_setConsole + */ + setConsole(force: boolean): void; + + /** + * Setup this Serial port with the given baud rate and options. + * eg. + * ``` + * Serial1.setup(9600,{rx:a_pin, tx:a_pin}); + * ``` + * The second argument can contain: + * ``` + * { + * rx:pin, // Receive pin (data in to Espruino) + * tx:pin, // Transmit pin (data out of Espruino) + * ck:pin, // (default none) Clock Pin + * cts:pin, // (default none) Clear to Send Pin + * bytesize:8, // (default 8)How many data bits - 7 or 8 + * parity:null/'none'/'o'/'odd'/'e'/'even', + * // (default none) Parity bit + * stopbits:1, // (default 1) Number of stop bits to use + * flow:null/undefined/'none'/'xon', // (default none) software flow control + * path:null/undefined/string // Linux Only - the path to the Serial device to use + * errors:false // (default false) whether to forward framing/parity errors + * } + * ``` + * You can find out which pins to use by looking at [your board's reference + * page](#boards) and searching for pins with the `UART`/`USART` markers. + * If not specified in options, the default pins are used for rx and tx (usually + * the lowest numbered pins on the lowest port that supports this peripheral). `ck` + * and `cts` are not used unless specified. + * Note that even after changing the RX and TX pins, if you have called setup + * before then the previous RX and TX pins will still be connected to the Serial + * port as well - until you set them to something else using `digitalWrite` or + * `pinMode`. + * Flow control can be xOn/xOff (`flow:'xon'`) or hardware flow control (receive + * only) if `cts` is specified. If `cts` is set to a pin, the pin's value will be 0 + * when Espruino is ready for data and 1 when it isn't. + * By default, framing or parity errors don't create `framing` or `parity` events + * on the `Serial` object because storing these errors uses up additional storage + * in the queue. If you're intending to receive a lot of malformed data then the + * queue might overflow `E.getErrorFlags()` would return `FIFO_FULL`. However if + * you need to respond to `framing` or `parity` errors then you'll need to use + * `errors:true` when initialising serial. + * On Linux builds there is no default Serial device, so you must specify a path to + * a device - for instance: `Serial1.setup(9600,{path:"/dev/ttyACM0"})` + * You can also set up 'software serial' using code like: + * ``` + * var s = new Serial(); + * s.setup(9600,{rx:a_pin, tx:a_pin}); + * ``` + * However software serial doesn't use `ck`, `cts`, `parity`, `flow` or `errors` + * parts of the initialisation object. + * + * @param {any} baudrate - The baud rate - the default is 9600 + * @param {any} options - An optional structure containing extra information on initialising the serial port - see below. + * @url http://www.espruino.com/Reference#l_Serial_setup + */ + setup(baudrate: any, options: any): void; + + /** + * If the serial (or software serial) device was set up, uninitialise it. + * @url http://www.espruino.com/Reference#l_Serial_unsetup + */ + unsetup(): void; + + /** + * Print a string to the serial port - without a line feed + * **Note:** This function replaces any occurances of `\n` in the string with + * `\r\n`. To avoid this, use `Serial.write`. + * + * @param {any} string - A String to print + * @url http://www.espruino.com/Reference#l_Serial_print + */ + print(string: any): void; + + /** + * Print a line to the serial port with a newline (`\r\n`) at the end of it. + * **Note:** This function converts data to a string first, eg + * `Serial.print([1,2,3])` is equivalent to `Serial.print("1,2,3"). If you'd like + * to write raw bytes, use `Serial.write`. + * + * @param {any} string - A String to print + * @url http://www.espruino.com/Reference#l_Serial_println + */ + println(string: any): void; + + /** + * Write a character or array of data to the serial port + * This method writes unmodified data, eg `Serial.write([1,2,3])` is equivalent to + * `Serial.write("\1\2\3")`. If you'd like data converted to a string first, use + * `Serial.print`. + * + * @param {any} data - One or more items to write. May be ints, strings, arrays, or special objects (see `E.toUint8Array` for more info). + * @url http://www.espruino.com/Reference#l_Serial_write + */ + write(...data: any[]): void; + + /** + * Add data to this device as if it came directly from the input - it will be + * returned via `serial.on('data', ...)`; + * ``` + * Serial1.on('data', function(d) { print("Got",d); }); + * Serial1.inject('Hello World'); + * // prints "Got Hel","Got lo World" (characters can be split over multiple callbacks) + * ``` + * This is most useful if you wish to send characters to Espruino's REPL (console) + * while it is on another device. + * + * @param {any} data - One or more items to write. May be ints, strings, arrays, or special objects (see `E.toUint8Array` for more info). + * @url http://www.espruino.com/Reference#l_Serial_inject + */ + inject(...data: any[]): void; + + /** + * Return how many bytes are available to read. If there is already a listener for + * data, this will always return 0. + * @returns {number} How many bytes are available + * @url http://www.espruino.com/Reference#l_Serial_available + */ + available(): number; + + /** + * Return a string containing characters that have been received + * + * @param {number} chars - The number of characters to read, or undefined/0 for all available + * @returns {any} A string containing the required bytes. + * @url http://www.espruino.com/Reference#l_Serial_read + */ + read(chars: number): any; + + /** + * Pipe this USART to a stream (an object with a 'write' method) + * + * @param {any} destination - The destination file/stream that will receive content from the source. + * @param {any} options + * An optional object `{ chunkSize : int=32, end : bool=true, complete : function }` + * chunkSize : The amount of data to pipe from source to destination at a time + * complete : a function to call when the pipe activity is complete + * end : call the 'end' function on the destination when the source is finished + * @url http://www.espruino.com/Reference#l_Serial_pipe + */ + pipe(destination: any, options: any): void; +} + +/** + * These objects are created from `require("Storage").open` and allow Storage items + * to be read/written. + * The `Storage` library writes into Flash memory (which can only be erased in + * chunks), and unlike a normal filesystem it allocates files in one long + * contiguous area to allow them to be accessed easily from Espruino. + * This presents a challenge for `StorageFile` which allows you to append to a + * file, so instead `StorageFile` stores files in chunks. It uses the last + * character of the filename to denote the chunk number (eg `"foobar\1"`, + * `"foobar\2"`, etc). + * This means that while `StorageFile` files exist in the same area as those from + * `Storage`, they should be read using `Storage.open` (and not `Storage.read`). + * ``` + * f = s.open("foobar","w"); + * f.write("Hell"); + * f.write("o World\n"); + * f.write("Hello\n"); + * f.write("World 2\n"); + * // there's no need to call 'close' + * // then + * f = s.open("foobar","r"); + * f.read(13) // "Hello World\nH" + * f.read(13) // "ello\nWorld 2\n" + * f.read(13) // "Hello World 3" + * f.read(13) // "\n" + * f.read(13) // undefined + * // or + * f = s.open("foobar","r"); + * f.readLine() // "Hello World\n" + * f.readLine() // "Hello\n" + * f.readLine() // "World 2\n" + * f.readLine() // "Hello World 3\n" + * f.readLine() // undefined + * // now get rid of file + * f.erase(); + * ``` + * **Note:** `StorageFile` uses the fact that all bits of erased flash memory are 1 + * to detect the end of a file. As such you should not write character code 255 + * (`"\xFF"`) to these files. + * @url http://www.espruino.com/Reference#StorageFile + */ +declare class StorageFile { + + + /** + * Read 'len' bytes of data from the file, and return a String containing those + * bytes. + * If the end of the file is reached, the String may be smaller than the amount of + * bytes requested, or if the file is already at the end, `undefined` is returned. + * + * @param {number} len - How many bytes to read + * @returns {any} A String, or undefined + * @url http://www.espruino.com/Reference#l_StorageFile_read + */ + read(len: number): string; + + /** + * Read a line of data from the file (up to and including `"\n"`) + * @returns {any} A line of data + * @url http://www.espruino.com/Reference#l_StorageFile_readLine + */ + readLine(): string; + + /** + * Return the length of the current file. + * This requires Espruino to read the file from scratch, which is not a fast + * operation. + * @returns {number} The current length in bytes of the file + * @url http://www.espruino.com/Reference#l_StorageFile_getLength + */ + getLength(): number; + + /** + * Append the given data to a file. You should not attempt to append `"\xFF"` + * (character code 255). + * + * @param {any} data - The data to write. This should not include `'\xFF'` (character code 255) + * @url http://www.espruino.com/Reference#l_StorageFile_write + */ + write(data: string): void; + + /** + * Erase this file + * @url http://www.espruino.com/Reference#l_StorageFile_erase + */ + erase(): void; +} + +interface processConstructor { + /** + * This event is called when an exception gets thrown and isn't caught (eg. it gets + * all the way back to the event loop). + * You can use this for logging potential problems that might occur during + * execution when you might not be able to see what is written to the console, for + * example: + * ``` + * var lastError; + * process.on('uncaughtException', function(e) { + * lastError=e; + * print(e,e.stack?"\n"+e.stack:"") + * }); + * function checkError() { + * if (!lastError) return print("No Error"); + * print(lastError,lastError.stack?"\n"+lastError.stack:"") + * } + * ``` + * **Note:** When this is used, exceptions will cease to be reported on the + * console - which may make debugging difficult! + * @param {string} event - The event to listen to. + * @param {(exception: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `exception` The uncaught exception + * @url http://www.espruino.com/Reference#l_process_uncaughtException + */ + on(event: "uncaughtException", callback: (exception: any) => void): void; + + /** + * Returns the version of Espruino as a String + * @returns {any} The version of Espruino + * @url http://www.espruino.com/Reference#l_process_version + */ + version: any; + + /** + * Returns an Object containing various pre-defined variables. + * * `VERSION` - is the Espruino version + * * `GIT_COMMIT` - is Git commit hash this firmware was built from + * * `BOARD` - the board's ID (eg. `PUCKJS`) + * * `RAM` - total amount of on-chip RAM in bytes + * * `FLASH` - total amount of on-chip flash memory in bytes + * * `SPIFLASH` - (on Bangle.js) total amount of off-chip flash memory in bytes + * * `HWVERSION` - For Puck.js this is the board revision (1, 2, 2.1), or for + * Bangle.js it's 1 or 2 + * * `STORAGE` - memory in bytes dedicated to the `Storage` module + * * `SERIAL` - the serial number of this chip + * * `CONSOLE` - the name of the current console device being used (`Serial1`, + * `USB`, `Bluetooth`, etc) + * * `MODULES` - a list of built-in modules separated by commas + * * `EXPTR` - The address of the `exportPtrs` structure in flash (this includes + * links to built-in functions that compiled JS code needs) + * * `APP_RAM_BASE` - On nRF5x boards, this is the RAM required by the Softdevice + * *if it doesn't exactly match what was allocated*. You can use this to update + * `LD_APP_RAM_BASE` in the `BOARD.py` file + * For example, to get a list of built-in modules, you can use + * `process.env.MODULES.split(',')` + * @returns {any} An object + * @url http://www.espruino.com/Reference#l_process_env + */ + env: any; + + /** + * Run a Garbage Collection pass, and return an object containing information on + * memory usage. + * * `free` : Memory that is available to be used (in blocks) + * * `usage` : Memory that has been used (in blocks) + * * `total` : Total memory (in blocks) + * * `history` : Memory used for command history - that is freed if memory is low. + * Note that this is INCLUDED in the figure for 'free' + * * `gc` : Memory freed during the GC pass + * * `gctime` : Time taken for GC pass (in milliseconds) + * * `blocksize` : Size of a block (variable) in bytes + * * `stackEndAddress` : (on ARM) the address (that can be used with peek/poke/etc) + * of the END of the stack. The stack grows down, so unless you do a lot of + * recursion the bytes above this can be used. + * * `flash_start` : (on ARM) the address of the start of flash memory (usually + * `0x8000000`) + * * `flash_binary_end` : (on ARM) the address in flash memory of the end of + * Espruino's firmware. + * * `flash_code_start` : (on ARM) the address in flash memory of pages that store + * any code that you save with `save()`. + * * `flash_length` : (on ARM) the amount of flash memory this firmware was built + * for (in bytes). **Note:** Some STM32 chips actually have more memory than is + * advertised. + * Memory units are specified in 'blocks', which are around 16 bytes each + * (depending on your device). The actual size is available in `blocksize`. See + * http://www.espruino.com/Performance for more information. + * **Note:** To find free areas of flash memory, see `require('Flash').getFree()` + * + * @param {any} gc - An optional boolean. If `undefined` or `true` Garbage collection is performed, if `false` it is not + * @returns {any} Information about memory usage + * @url http://www.espruino.com/Reference#l_process_memory + */ + memory(gc: any): any; +} + +interface process { + +} + +/** + * This class contains information about Espruino itself + * @url http://www.espruino.com/Reference#process + */ +declare const process: processConstructor + +/** + * Built-in class that caches the modules used by the `require` command + * @url http://www.espruino.com/Reference#Modules + */ +declare class Modules { + /** + * Return an array of module names that have been cached + * @returns {any} An array of module names + * @url http://www.espruino.com/Reference#l_Modules_getCached + */ + static getCached(): any; + + /** + * Remove the given module from the list of cached modules + * + * @param {any} id - The module name to remove + * @url http://www.espruino.com/Reference#l_Modules_removeCached + */ + static removeCached(id: any): void; + + /** + * Remove all cached modules + * @url http://www.espruino.com/Reference#l_Modules_removeAllCached + */ + static removeAllCached(): void; + + /** + * Add the given module to the cache + * + * @param {any} id - The module name to add + * @param {any} sourcecode - The module's sourcecode + * @url http://www.espruino.com/Reference#l_Modules_addCached + */ + static addCached(id: any, sourcecode: any): void; + + +} + +interface StringConstructor { + /** + * Return the character(s) represented by the given character code(s). + * + * @param {any} code - One or more character codes to create a string from (range 0-255). + * @returns {any} The character + * @url http://www.espruino.com/Reference#l_String_fromCharCode + */ + fromCharCode(...code: any[]): any; + + /** + * Create a new String + * @constructor + * + * @param {any} str - A value to turn into a string. If undefined or not supplied, an empty String is created. + * @returns {any} A String + * @url http://www.espruino.com/Reference#l_String_String + */ + new(...str: any[]): any; +} + +interface String { + /** + * Find the length of the string + * @returns {any} The value of the string + * @url http://www.espruino.com/Reference#l_String_length + */ + length: any; + + /** + * Return a single character at the given position in the String. + * + * @param {number} pos - The character number in the string. Negative values return characters from end of string (-1 = last char) + * @returns {any} The character in the string + * @url http://www.espruino.com/Reference#l_String_charAt + */ + charAt(pos: number): any; + + /** + * Return the integer value of a single character at the given position in the + * String. + * Note that this returns 0 not 'NaN' for out of bounds characters + * + * @param {number} pos - The character number in the string. Negative values return characters from end of string (-1 = last char) + * @returns {number} The integer value of a character in the string + * @url http://www.espruino.com/Reference#l_String_charCodeAt + */ + charCodeAt(pos: number): number; + + /** + * Return the index of substring in this string, or -1 if not found + * + * @param {any} substring - The string to search for + * @param {any} fromIndex - Index to search from + * @returns {number} The index of the string, or -1 if not found + * @url http://www.espruino.com/Reference#l_String_indexOf + */ + indexOf(substring: any, fromIndex: any): number; + + /** + * Return the last index of substring in this string, or -1 if not found + * + * @param {any} substring - The string to search for + * @param {any} fromIndex - Index to search from + * @returns {number} The index of the string, or -1 if not found + * @url http://www.espruino.com/Reference#l_String_lastIndexOf + */ + lastIndexOf(substring: any, fromIndex: any): number; + + /** + * Matches an occurrence `subStr` in the string. + * Returns `null` if no match, or: + * ``` + * "abcdef".match("b") == [ + * "b", // array index 0 - the matched string + * index: 1, // the start index of the match + * input: "b" // the input string + * ] + * "abcdefabcdef".match(/bcd/) == [ + * "bcd", index: 1, + * input: "abcdefabcdef" + * ] + * ``` + * 'Global' RegEx matches just return an array of matches (with no indices): + * ``` + * "abcdefabcdef".match(/bcd/g) = [ + * "bcd", + * "bcd" + * ] + * ``` + * + * @param {any} substr - Substring or RegExp to match + * @returns {any} A match array or `null` (see below): + * @url http://www.espruino.com/Reference#l_String_match + */ + match(substr: any): any; + + /** + * Search and replace ONE occurrance of `subStr` with `newSubStr` and return the + * result. This doesn't alter the original string. Regular expressions not + * supported. + * + * @param {any} subStr - The string to search for + * @param {any} newSubStr - The string to replace it with + * @returns {any} This string with `subStr` replaced + * @url http://www.espruino.com/Reference#l_String_replace + */ + replace(subStr: any, newSubStr: any): any; + + /** + * + * @param {number} start - The start character index (inclusive) + * @param {any} end - The end character index (exclusive) + * @returns {any} The part of this string between start and end + * @url http://www.espruino.com/Reference#l_String_substring + */ + substring(start: number, end: any): any; + + /** + * + * @param {number} start - The start character index + * @param {any} len - The number of characters + * @returns {any} Part of this string from start for len characters + * @url http://www.espruino.com/Reference#l_String_substr + */ + substr(start: number, len: any): any; + + /** + * + * @param {number} start - The start character index, if negative it is from the end of the string + * @param {any} end - The end character index, if negative it is from the end of the string, and if omitted it is the end of the string + * @returns {any} Part of this string from start for len characters + * @url http://www.espruino.com/Reference#l_String_slice + */ + slice(start: number, end: any): any; + + /** + * Return an array made by splitting this string up by the separator. eg. + * ```'1,2,3'.split(',')==['1', '2', '3']``` + * Regular Expressions can also be used to split strings, eg. `'1a2b3 + * 4'.split(/[^0-9]/)==['1', '2', '3', '4']`. + * + * @param {any} separator - The separator `String` or `RegExp` to use + * @returns {any} Part of this string from start for len characters + * @url http://www.espruino.com/Reference#l_String_split + */ + split(separator: any): any; + + /** + * + * @returns {any} The lowercase version of this string + * @url http://www.espruino.com/Reference#l_String_toLowerCase + */ + toLowerCase(): any; + + /** + * + * @returns {any} The uppercase version of this string + * @url http://www.espruino.com/Reference#l_String_toUpperCase + */ + toUpperCase(): any; + + /** + * Return a new string with any whitespace (tabs, space, form feed, newline, + * carriage return, etc) removed from the beginning and end. + * @returns {any} A String with Whitespace removed from the beginning and end + * @url http://www.espruino.com/Reference#l_String_trim + */ + trim(): string; + + /** + * Append all arguments to this `String` and return the result. Does not modify the + * original `String`. + * + * @param {any} args - Strings to append + * @returns {any} The result of appending all arguments to this string + * @url http://www.espruino.com/Reference#l_String_concat + */ + concat(...args: any[]): any; + + /** + * + * @param {any} searchString - The string to search for + * @param {number} position - The start character index (or 0 if not defined) + * @returns {boolean} `true` if the given characters are found at the beginning of the string, otherwise, `false`. + * @url http://www.espruino.com/Reference#l_String_startsWith + */ + startsWith(searchString: any, position: number): boolean; + + /** + * + * @param {any} searchString - The string to search for + * @param {any} length - The 'end' of the string - if left off the actual length of the string is used + * @returns {boolean} `true` if the given characters are found at the end of the string, otherwise, `false`. + * @url http://www.espruino.com/Reference#l_String_endsWith + */ + endsWith(searchString: any, length: any): boolean; + + /** + * + * @param {any} substring - The string to search for + * @param {any} fromIndex - The start character index (or 0 if not defined) + * @returns {boolean} `true` if the given characters are in the string, otherwise, `false`. + * @url http://www.espruino.com/Reference#l_String_includes + */ + includes(substring: any, fromIndex: any): boolean; + + /** + * Repeat this string the given number of times. + * + * @param {number} count - An integer with the amount of times to repeat this String + * @returns {any} A string containing repetitions of this string + * @url http://www.espruino.com/Reference#l_String_repeat + */ + repeat(count: number): string; + + /** + * Pad this string at the beginnind to the required number of characters + * ``` + * "Hello".padStart(10) == " Hello" + * "123".padStart(10,".-") == ".-.-.-.123" + * ``` + * + * @param {number} targetLength - The length to pad this string to + * @param {any} [padString] - [optional] The string to pad with, default is `' '` + * @returns {any} A string containing this string padded to the correct length + * @url http://www.espruino.com/Reference#l_String_padStart + */ + padStart(targetLength: number, padString?: any): string; + + /** + * Pad this string at the end to the required number of characters + * ``` + * "Hello".padEnd(10) == "Hello " + * "123".padEnd(10,".-") == "123.-.-.-." + * ``` + * + * @param {number} targetLength - The length to pad this string to + * @param {any} [padString] - [optional] The string to pad with, default is `' '` + * @returns {any} A string containing this string padded to the correct length + * @url http://www.espruino.com/Reference#l_String_padEnd + */ + padEnd(targetLength: number, padString?: any): string; +} + +/** + * This is the built-in class for Text Strings. + * Text Strings in Espruino are not zero-terminated, so you can store zeros in + * them. + * @url http://www.espruino.com/Reference#String + */ +declare const String: StringConstructor + +interface ArrayConstructor { + /** + * Returns true if the provided object is an array + * + * @param {any} var - The variable to be tested + * @returns {boolean} True if var is an array, false if not. + * @url http://www.espruino.com/Reference#l_Array_isArray + */ + isArray(arg: any): arg is any[]; + + /** + * Create an Array. Either give it one integer argument (>=0) which is the length + * of the array, or any number of arguments + * @constructor + * + * @param {any} args - The length of the array OR any number of items to add to the array + * @returns {any} An Array + * @url http://www.espruino.com/Reference#l_Array_Array + */ + new(arrayLength?: number): any[]; + new(arrayLength: number): T[]; + new(...items: T[]): T[]; + (arrayLength?: number): any[]; + (arrayLength: number): T[]; + (...items: T[]): T[]; +} + +interface Array { + /** + * Convert the Array to a string + * + * @param {any} radix - unused + * @returns {any} A String representing the array + * @url http://www.espruino.com/Reference#l_Array_toString + */ + toString(): string; + + /** + * Find the length of the array + * @returns {any} The length of the array + * @url http://www.espruino.com/Reference#l_Array_length + */ + length: number; + + /** + * Return the index of the value in the array, or -1 + * + * @param {any} value - The value to check for + * @param {number} startIndex - (optional) the index to search from, or 0 if not specified + * @returns {any} the index of the value in the array, or -1 + * @url http://www.espruino.com/Reference#l_Array_indexOf + */ + indexOf(value: T, startIndex?: number): number; + + /** + * Return `true` if the array includes the value, `false` otherwise + * + * @param {any} value - The value to check for + * @param {number} startIndex - (optional) the index to search from, or 0 if not specified + * @returns {boolean} `true` if the array includes the value, `false` otherwise + * @url http://www.espruino.com/Reference#l_Array_includes + */ + includes(value: T, startIndex?: number): boolean; + + /** + * Join all elements of this array together into one string, using 'separator' + * between them. e.g. ```[1,2,3].join(' ')=='1 2 3'``` + * + * @param {any} separator - The separator + * @returns {any} A String representing the Joined array + * @url http://www.espruino.com/Reference#l_Array_join + */ + join(separator?: string): string; + + /** + * Push a new value onto the end of this array' + * This is the opposite of `[1,2,3].unshift(0)`, which adds one or more elements to + * the beginning of the array. + * + * @param {any} arguments - One or more arguments to add + * @returns {number} The new size of the array + * @url http://www.espruino.com/Reference#l_Array_push + */ + push(...arguments: T[]): number; + + /** + * Remove and return the value on the end of this array. + * This is the opposite of `[1,2,3].shift()`, which removes an element from the + * beginning of the array. + * @returns {any} The value that is popped off + * @url http://www.espruino.com/Reference#l_Array_pop + */ + pop(): T | undefined; + + /** + * Return an array which is made from the following: ```A.map(function) = + * [function(A[0]), function(A[1]), ...]``` + * + * @param {any} function - Function used to map one item to another + * @param {any} thisArg - if specified, the function is called with 'this' set to thisArg (optional) + * @returns {any} An array containing the results + * @url http://www.espruino.com/Reference#l_Array_map + */ + map(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[]; + + /** + * Executes a provided function once per array element. + * + * @param {any} function - Function to be executed + * @param {any} [thisArg] - [optional] If specified, the function is called with 'this' set to thisArg (optional) + * @url http://www.espruino.com/Reference#l_Array_forEach + */ + forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void; + + /** + * Return an array which contains only those elements for which the callback + * function returns 'true' + * + * @param {any} function - Function to be executed + * @param {any} thisArg - if specified, the function is called with 'this' set to thisArg (optional) + * @returns {any} An array containing the results + * @url http://www.espruino.com/Reference#l_Array_filter + */ + filter(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[]; + filter(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): T[]; + + /** + * Return the array element where `function` returns `true`, or `undefined` if it + * doesn't returns `true` for any element. + * ``` + * ["Hello","There","World"].find(a=>a[0]=="T") + * // returns "There" + * ``` + * + * @param {any} function - Function to be executed + * @returns {any} The array element where `function` returns `true`, or `undefined` + * @url http://www.espruino.com/Reference#l_Array_find + */ + find(predicate: (this: void, value: T, index: number, obj: T[]) => value is S): S | undefined; + find(predicate: (value: T, index: number, obj: T[]) => unknown): T | undefined; + + /** + * Return the array element's index where `function` returns `true`, or `-1` if it + * doesn't returns `true` for any element. + * ``` + * ["Hello","There","World"].findIndex(a=>a[0]=="T") + * // returns 1 + * ``` + * + * @param {any} function - Function to be executed + * @returns {any} The array element's index where `function` returns `true`, or `-1` + * @url http://www.espruino.com/Reference#l_Array_findIndex + */ + findIndex(predicate: (value: T, index: number, obj: T[]) => unknown): number; + + /** + * Return 'true' if the callback returns 'true' for any of the elements in the + * array + * + * @param {any} function - Function to be executed + * @param {any} thisArg - if specified, the function is called with 'this' set to thisArg (optional) + * @returns {any} A boolean containing the result + * @url http://www.espruino.com/Reference#l_Array_some + */ + some(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean; + + /** + * Return 'true' if the callback returns 'true' for every element in the array + * + * @param {any} function - Function to be executed + * @param {any} thisArg - if specified, the function is called with 'this' set to thisArg (optional) + * @returns {any} A boolean containing the result + * @url http://www.espruino.com/Reference#l_Array_every + */ + every(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean; + + /** + * Execute `previousValue=initialValue` and then `previousValue = + * callback(previousValue, currentValue, index, array)` for each element in the + * array, and finally return previousValue. + * + * @param {any} callback - Function used to reduce the array + * @param {any} initialValue - if specified, the initial value to pass to the function + * @returns {any} The value returned by the last function called + * @url http://www.espruino.com/Reference#l_Array_reduce + */ + reduce(callback: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue?: T): T; + + /** + * Both remove and add items to an array + * + * @param {number} index - Index at which to start changing the array. If negative, will begin that many elements from the end + * @param {any} howMany - An integer indicating the number of old array elements to remove. If howMany is 0, no elements are removed. + * @param {any} elements - One or more items to add to the array + * @returns {any} An array containing the removed elements. If only one element is removed, an array of one element is returned. + * @url http://www.espruino.com/Reference#l_Array_splice + */ + splice(index: number, howMany?: number, ...elements: T[]): T[]; + + /** + * Remove and return the first element of the array. + * This is the opposite of `[1,2,3].pop()`, which takes an element off the end. + * + * @returns {any} The element that was removed + * @url http://www.espruino.com/Reference#l_Array_shift + */ + shift(): T | undefined; + + /** + * Add one or more items to the start of the array, and return its new length. + * This is the opposite of `[1,2,3].push(4)`, which puts one or more elements on + * the end. + * + * @param {any} elements - One or more items to add to the beginning of the array + * @returns {number} The new array length + * @url http://www.espruino.com/Reference#l_Array_unshift + */ + unshift(...elements: T[]): number; + + /** + * Return a copy of a portion of this array (in a new array) + * + * @param {number} start - Start index + * @param {any} end - End index (optional) + * @returns {any} A new array + * @url http://www.espruino.com/Reference#l_Array_slice + */ + slice(start?: number, end?: number): T[]; + + /** + * Do an in-place quicksort of the array + * + * @param {any} var - A function to use to compare array elements (or undefined) + * @returns {any} This array object + * @url http://www.espruino.com/Reference#l_Array_sort + */ + sort(compareFn?: (a: T, b: T) => number): T[]; + + /** + * Create a new array, containing the elements from this one and any arguments, if + * any argument is an array then those elements will be added. + * + * @param {any} args - Any items to add to the array + * @returns {any} An Array + * @url http://www.espruino.com/Reference#l_Array_concat + */ + concat(...args: (T | T[])[]): T[]; + + /** + * Fill this array with the given value, for every index `>= start` and `< end` + * + * @param {any} value - The value to fill the array with + * @param {number} start - Optional. The index to start from (or 0). If start is negative, it is treated as length+start where length is the length of the array + * @param {any} end - Optional. The index to end at (or the array length). If end is negative, it is treated as length+end. + * @returns {any} This array + * @url http://www.espruino.com/Reference#l_Array_fill + */ + fill(value: T, start: number, end?: number): T[]; + + /** + * Reverse all elements in this array (in place) + * @returns {any} The array, but reversed. + * @url http://www.espruino.com/Reference#l_Array_reverse + */ + reverse(): T[]; + + [index: number]: T +} + +/** + * This is the built-in JavaScript class for arrays. + * Arrays can be defined with ```[]```, ```new Array()```, or ```new + * Array(length)``` + * @url http://www.espruino.com/Reference#Array + */ +declare const Array: ArrayConstructor + +interface ObjectConstructor { + /** + * Return all enumerable keys of the given object + * + * @param {any} object - The object to return keys for + * @returns {any} An array of strings - one for each key on the given object + * @url http://www.espruino.com/Reference#l_Object_keys + */ + keys(object: any): any; + + /** + * Returns an array of all properties (enumerable or not) found directly on a given + * object. + * + * @param {any} object - The Object to return a list of property names for + * @returns {any} An array of the Object's own properties + * @url http://www.espruino.com/Reference#l_Object_getOwnPropertyNames + */ + getOwnPropertyNames(object: any): any; + + /** + * Return all enumerable values of the given object + * + * @param {any} object - The object to return values for + * @returns {any} An array of values - one for each key on the given object + * @url http://www.espruino.com/Reference#l_Object_values + */ + values(object: any): any; + + /** + * Return all enumerable keys and values of the given object + * + * @param {any} object - The object to return values for + * @returns {any} An array of `[key,value]` pairs - one for each key on the given object + * @url http://www.espruino.com/Reference#l_Object_entries + */ + entries(object: any): any; + + /** + * Creates a new object with the specified prototype object and properties. + * properties are currently unsupported. + * + * @param {any} proto - A prototype object + * @param {any} propertiesObject - An object containing properties. NOT IMPLEMENTED + * @returns {any} A new object + * @url http://www.espruino.com/Reference#l_Object_create + */ + create(proto: any, propertiesObject: any): any; + + /** + * Get information on the given property in the object, or undefined + * + * @param {any} obj - The object + * @param {any} name - The name of the property + * @returns {any} An object with a description of the property. The values of writable/enumerable/configurable may not be entirely correct due to Espruino's implementation. + * @url http://www.espruino.com/Reference#l_Object_getOwnPropertyDescriptor + */ + getOwnPropertyDescriptor(obj: any, name: any): any; + + /** + * Add a new property to the Object. 'Desc' is an object with the following fields: + * * `configurable` (bool = false) - can this property be changed/deleted (not + * implemented) + * * `enumerable` (bool = false) - can this property be enumerated (not + * implemented) + * * `value` (anything) - the value of this property + * * `writable` (bool = false) - can the value be changed with the assignment + * operator? + * * `get` (function) - the getter function, or undefined if no getter (only + * supported on some platforms) + * * `set` (function) - the setter function, or undefined if no setter (only + * supported on some platforms) + * **Note:** `configurable`, `enumerable` and `writable` are not implemented and + * will be ignored. + * + * @param {any} obj - An object + * @param {any} name - The name of the property + * @param {any} desc - The property descriptor + * @returns {any} The object, obj. + * @url http://www.espruino.com/Reference#l_Object_defineProperty + */ + defineProperty(obj: any, name: any, desc: any): any; + + /** + * Adds new properties to the Object. See `Object.defineProperty` for more + * information + * + * @param {any} obj - An object + * @param {any} props - An object whose fields represent property names, and whose values are property descriptors. + * @returns {any} The object, obj. + * @url http://www.espruino.com/Reference#l_Object_defineProperties + */ + defineProperties(obj: any, props: any): any; + + /** + * Get the prototype of the given object - this is like writing `object.__proto__` + * but is the 'proper' ES6 way of doing it + * + * @param {any} object - An object + * @returns {any} The prototype + * @url http://www.espruino.com/Reference#l_Object_getPrototypeOf + */ + getPrototypeOf(object: any): any; + + /** + * Set the prototype of the given object - this is like writing `object.__proto__ = + * prototype` but is the 'proper' ES6 way of doing it + * + * @param {any} object - An object + * @param {any} prototype - The prototype to set on the object + * @returns {any} The object passed in + * @url http://www.espruino.com/Reference#l_Object_setPrototypeOf + */ + setPrototypeOf(object: any, prototype: any): any; + + /** + * Appends all keys and values in any subsequent objects to the first object + * **Note:** Unlike the standard ES6 `Object.assign`, this will throw an exception + * if given raw strings, bools or numbers rather than objects. + * + * @param {any} args - The target object, then any items objects to use as sources of keys + * @returns {any} The target object + * @url http://www.espruino.com/Reference#l_Object_assign + */ + assign(...args: any[]): any; + + /** + * Creates an Object from the supplied argument + * @constructor + * + * @param {any} value - A single value to be converted to an object + * @returns {any} An Object + * @url http://www.espruino.com/Reference#l_Object_Object + */ + new(value: any): any; +} + +interface Object { + /** + * Find the length of the object + * @returns {any} The length of the object + * @url http://www.espruino.com/Reference#l_Object_length + */ + length: any; + + /** + * Returns the primitive value of this object. + * @returns {any} The primitive value of this object + * @url http://www.espruino.com/Reference#l_Object_valueOf + */ + valueOf(): any; + + /** + * Convert the Object to a string + * + * @param {any} radix - If the object is an integer, the radix (between 2 and 36) to use. NOTE: Setting a radix does not work on floating point numbers. + * @returns {any} A String representing the object + * @url http://www.espruino.com/Reference#l_Object_toString + */ + toString(radix: any): any; + + /** + * Copy this object completely + * @returns {any} A copy of this Object + * @url http://www.espruino.com/Reference#l_Object_clone + */ + clone(): any; + + /** + * Return true if the object (not its prototype) has the given property. + * NOTE: This currently returns false-positives for built-in functions in + * prototypes + * + * @param {any} name - The name of the property to search for + * @returns {boolean} True if it exists, false if it doesn't + * @url http://www.espruino.com/Reference#l_Object_hasOwnProperty + */ + hasOwnProperty(name: any): boolean; + + /** + * Register an event listener for this object, for instance `Serial1.on('data', + * function(d) {...})`. + * This is the same as Node.js's [EventEmitter](https://nodejs.org/api/events.html) + * but on Espruino the functionality is built into every object: + * * `Object.on` + * * `Object.emit` + * * `Object.removeListener` + * * `Object.removeAllListeners` + * ``` + * var o = {}; // o can be any object... + * // call an arrow function when the 'answer' event is received + * o.on('answer', x => console.log(x)); + * // call a named function when the 'answer' event is received + * function printAnswer(d) { + * console.log("The answer is", d); + * } + * o.on('answer', printAnswer); + * // emit the 'answer' event - functions added with 'on' will be executed + * o.emit('answer', 42); + * // prints: 42 + * // prints: The answer is 42 + * // If you have a named function, it can be removed by name + * o.removeListener('answer', printAnswer); + * // Now 'printAnswer' is removed + * o.emit('answer', 43); + * // prints: 43 + * // Or you can remove all listeners for 'answer' + * o.removeAllListeners('answer') + * // Now nothing happens + * o.emit('answer', 44); + * // nothing printed + * ``` + * + * @param {any} event - The name of the event, for instance 'data' + * @param {any} listener - The listener to call when this event is received + * @url http://www.espruino.com/Reference#l_Object_on + */ + on(event: any, listener: any): void; + + /** + * Call any event listeners that were added to this object with `Object.on`, for + * instance `obj.emit('data', 'Foo')`. + * For more information see `Object.on` + * + * @param {any} event - The name of the event, for instance 'data' + * @param {any} args - Optional arguments + * @url http://www.espruino.com/Reference#l_Object_emit + */ + emit(event: any, ...args: any[]): void; + + /** + * Removes the specified event listener. + * ``` + * function foo(d) { + * console.log(d); + * } + * Serial1.on("data", foo); + * Serial1.removeListener("data", foo); + * ``` + * For more information see `Object.on` + * + * @param {any} event - The name of the event, for instance 'data' + * @param {any} listener - The listener to remove + * @url http://www.espruino.com/Reference#l_Object_removeListener + */ + removeListener(event: any, listener: any): void; + + /** + * Removes all listeners (if `event===undefined`), or those of the specified event. + * ``` + * Serial1.on("data", function(data) { ... }); + * Serial1.removeAllListeners("data"); + * // or + * Serial1.removeAllListeners(); // removes all listeners for all event types + * ``` + * For more information see `Object.on` + * + * @param {any} event - The name of the event, for instance `'data'`. If not specified *all* listeners are removed. + * @url http://www.espruino.com/Reference#l_Object_removeAllListeners + */ + removeAllListeners(event: any): void; +} + +/** + * This is the built-in class for Objects + * @url http://www.espruino.com/Reference#Object + */ +declare const Object: ObjectConstructor + +interface FunctionConstructor { + /** + * Creates a function + * @constructor + * + * @param {any} args - Zero or more arguments (as strings), followed by a string representing the code to run + * @returns {any} A Number object + * @url http://www.espruino.com/Reference#l_Function_Function + */ + new(...args: any[]): any; +} + +interface Function { + /** + * This replaces the function with the one in the argument - while keeping the old + * function's scope. This allows inner functions to be edited, and is used when + * edit() is called on an inner function. + * + * @param {any} newFunc - The new function to replace this function with + * @url http://www.espruino.com/Reference#l_Function_replaceWith + */ + replaceWith(newFunc: any): void; + + /** + * This executes the function with the supplied 'this' argument and parameters + * + * @param {any} this - The value to use as the 'this' argument when executing the function + * @param {any} params - Optional Parameters + * @returns {any} The return value of executing this function + * @url http://www.espruino.com/Reference#l_Function_call + */ + call(this: any, ...params: any[]): any; + + /** + * This executes the function with the supplied 'this' argument and parameters + * + * @param {any} this - The value to use as the 'this' argument when executing the function + * @param {any} args - Optional Array of Arguments + * @returns {any} The return value of executing this function + * @url http://www.espruino.com/Reference#l_Function_apply + */ + apply(this: any, args: any): any; + + /** + * This executes the function with the supplied 'this' argument and parameters + * + * @param {any} this - The value to use as the 'this' argument when executing the function + * @param {any} params - Optional Default parameters that are prepended to the call + * @returns {any} The 'bound' function + * @url http://www.espruino.com/Reference#l_Function_bind + */ + bind(this: any, ...params: any[]): any; +} + +/** + * This is the built-in class for Functions + * @url http://www.espruino.com/Reference#Function + */ +declare const Function: FunctionConstructor + +/** + * This is the built-in JavaScript class for Espruino utility functions. + * @url http://www.espruino.com/Reference#E + */ +declare class E { + /** + * Setup the filesystem so that subsequent calls to `E.openFile` and + * `require('fs').*` will use an SD card on the supplied SPI device and pin. + * It can even work using software SPI - for instance: + * ``` + * // DI/CMD = C7 + * // DO/DAT0 = C8 + * // CK/CLK = C9 + * // CD/CS/DAT3 = C6 + * var spi = new SPI(); + * spi.setup({mosi:C7, miso:C8, sck:C9}); + * E.connectSDCard(spi, C6); + * console.log(require("fs").readdirSync()); + * ``` + * See [the page on File IO](http://www.espruino.com/File+IO) for more information. + * **Note:** We'd strongly suggest you add a pullup resistor from CD/CS pin to + * 3.3v. It is good practise to avoid accidental writes before Espruino is + * initialised, and some cards will not work reliably without one. + * **Note:** If you want to remove an SD card after you have started using it, you + * *must* call `E.unmountSD()` or you may cause damage to the card. + * + * @param {any} spi - The SPI object to use for communication + * @param {Pin} csPin - The pin to use for Chip Select + * @url http://www.espruino.com/Reference#l_E_connectSDCard + */ + static connectSDCard(spi: any, csPin: Pin): void; + + /** + * Unmount the SD card, so it can be removed. If you remove the SD card without + * calling this you may cause corruption, and you will be unable to access another + * SD card until you reset Espruino or call `E.unmountSD()`. + * @url http://www.espruino.com/Reference#l_E_unmountSD + */ + static unmountSD(): void; + + /** + * Open a file + * + * @param {any} path - the path to the file to open. + * @param {any} mode - The mode to use when opening the file. Valid values for mode are 'r' for read, 'w' for write new, 'w+' for write existing, and 'a' for append. If not specified, the default is 'r'. + * @returns {any} A File object + * @url http://www.espruino.com/Reference#l_E_openFile + */ + static openFile(path: any, mode: any): File; + + /** + * Change the parameters used for the flash filesystem. The default address is the + * last 1Mb of 4Mb Flash, 0x300000, with total size of 1Mb. + * Before first use the media needs to be formatted. + * ``` + * fs=require("fs"); + * try { + * fs.readdirSync(); + * } catch (e) { //'Uncaught Error: Unable to mount media : NO_FILESYSTEM' + * console.log('Formatting FS - only need to do once'); + * E.flashFatFS({ format: true }); + * } + * fs.writeFileSync("bang.txt", "This is the way the world ends\nnot with a bang but a whimper.\n"); + * fs.readdirSync(); + * ``` + * This will create a drive of 100 * 4096 bytes at 0x300000. Be careful with the + * selection of flash addresses as you can overwrite firmware! You only need to + * format once, as each will erase the content. + * `E.flashFatFS({ addr:0x300000,sectors:100,format:true });` + * + * @param {any} options + * An optional object `{ addr : int=0x300000, sectors : int=256, format : bool=false }` + * addr : start address in flash + * sectors: number of sectors to use + * format: Format the media + * @returns {boolean} True on success, or false on failure + * @url http://www.espruino.com/Reference#l_E_flashFatFS + */ + static flashFatFS(options: any): boolean; + + /** + * Display a menu on the screen, and set up the buttons to navigate through it. + * Supply an object containing menu items. When an item is selected, the function + * it references will be executed. For example: + * ``` + * var boolean = false; + * var number = 50; + * // First menu + * var mainmenu = { + * "" : { "title" : "-- Main Menu --" }, + * "Backlight On" : function() { LED1.set(); }, + * "Backlight Off" : function() { LED1.reset(); }, + * "Submenu" : function() { E.showMenu(submenu); }, + * "A Boolean" : { + * value : boolean, + * format : v => v?"On":"Off", + * onchange : v => { boolean=v; } + * }, + * "A Number" : { + * value : number, + * min:0,max:100,step:10, + * onchange : v => { number=v; } + * }, + * "Exit" : function() { E.showMenu(); }, // remove the menu + * }; + * // Submenu + * var submenu = { + * "" : { title : "-- SubMenu --", + * back : function() { E.showMenu(mainmenu); } }, + * "One" : undefined, // do nothing + * "Two" : undefined // do nothing + * }; + * // Actually display the menu + * E.showMenu(mainmenu); + * ``` + * The menu will stay onscreen and active until explicitly removed, which you can + * do by calling `E.showMenu()` without arguments. + * See http://www.espruino.com/graphical_menu for more detailed information. + * + * @param {any} menu - An object containing name->function mappings to to be used in a menu + * @returns {any} A menu object with `draw`, `move` and `select` functions + * @url http://www.espruino.com/Reference#l_E_showMenu + */ + static showMenu(menu: Menu): MenuInstance; + static showMenu(): void; + + /** + * A utility function for displaying a full screen message on the screen. + * Draws to the screen and returns immediately. + * ``` + * E.showMessage("These are\nLots of\nLines","My Title") + * ``` + * + * @param {any} message - A message to display. Can include newlines + * @param {any} title - (optional) a title for the message + * @url http://www.espruino.com/Reference#l_E_showMessage + */ + static showMessage(message: string, title?: string): void; + + /** + * Displays a full screen prompt on the screen, with the buttons requested (or + * `Yes` and `No` for defaults). + * When the button is pressed the promise is resolved with the requested values + * (for the `Yes` and `No` defaults, `true` and `false` are returned). + * ``` + * E.showPrompt("Do you like fish?").then(function(v) { + * if (v) print("'Yes' chosen"); + * else print("'No' chosen"); + * }); + * // Or + * E.showPrompt("How many fish\ndo you like?",{ + * title:"Fish", + * buttons : {"One":1,"Two":2,"Three":3} + * }).then(function(v) { + * print("You like "+v+" fish"); + * }); + * ``` + * To remove the prompt, call `E.showPrompt()` with no arguments. + * The second `options` argument can contain: + * ``` + * { + * title: "Hello", // optional Title + * buttons : {"Ok":true,"Cancel":false} // list of button text & return value + * } + * ``` + * + * @param {any} message - A message to display. Can include newlines + * @param {any} options - (optional) an object of options (see below) + * @returns {any} A promise that is resolved when 'Ok' is pressed + * @url http://www.espruino.com/Reference#l_E_showPrompt + */ + static showPrompt(message: string, options?: { title?: string, buttons?: { [key: string]: T } }): Promise; + static showPrompt(): void; + + /** + * Displays a full screen prompt on the screen, with a single 'Ok' button. + * When the button is pressed the promise is resolved. + * ``` + * E.showAlert("Hello").then(function() { + * print("Ok pressed"); + * }); + * // or + * E.showAlert("These are\nLots of\nLines","My Title").then(function() { + * print("Ok pressed"); + * }); + * ``` + * To remove the window, call `E.showAlert()` with no arguments. + * + * @param {any} message - A message to display. Can include newlines + * @param {any} options - (optional) a title for the message + * @returns {any} A promise that is resolved when 'Ok' is pressed + * @url http://www.espruino.com/Reference#l_E_showAlert + */ + static showAlert(message?: string, options?: string): Promise; + + /** + * Called when a notification arrives on an Apple iOS device Bangle.js is connected + * to + * ``` + * { + * event:"add", + * uid:42, + * category:4, + * categoryCnt:42, + * silent:true, + * important:false, + * preExisting:true, + * positive:false, + * negative:true + * } + * ``` + * You can then get more information with something like: + * ``` + * NRF.ancsGetNotificationInfo( event.uid ).then(a=>print("Notify",E.toJS(a))); + * ``` + * @param {string} event - The event to listen to. + * @param {(info: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `info` An object (see below) + * @url http://www.espruino.com/Reference#l_E_ANCS + */ + static on(event: "ANCS", callback: (info: any) => void): void; + + /** + * Called when a media event arrives on an Apple iOS device Bangle.js is connected + * to + * ``` + * { + * id : "artist"/"album"/"title"/"duration", + * value : "Some text", + * truncated : bool // the 'value' was too big to be sent completely + * } + * ``` + * @param {string} event - The event to listen to. + * @param {(info: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `info` An object (see below) + * @url http://www.espruino.com/Reference#l_E_AMS + */ + static on(event: "AMS", callback: (info: any) => void): void; + + /** + * Display a menu on the screen, and set up the buttons to navigate through it. + * Supply an object containing menu items. When an item is selected, the function + * it references will be executed. For example: + * ``` + * var boolean = false; + * var number = 50; + * // First menu + * var mainmenu = { + * "" : { title : "-- Main Menu --" }, // options + * "LED On" : function() { LED1.set(); }, + * "LED Off" : function() { LED1.reset(); }, + * "Submenu" : function() { E.showMenu(submenu); }, + * "A Boolean" : { + * value : boolean, + * format : v => v?"On":"Off", + * onchange : v => { boolean=v; } + * }, + * "A Number" : { + * value : number, + * min:0,max:100,step:10, + * onchange : v => { number=v; } + * }, + * "Exit" : function() { E.showMenu(); }, // remove the menu + * }; + * // Submenu + * var submenu = { + * "" : { title : "-- SubMenu --", + * back : function() { E.showMenu(mainmenu); } }, + * "One" : undefined, // do nothing + * "Two" : undefined // do nothing + * }; + * // Actually display the menu + * E.showMenu(mainmenu); + * ``` + * The menu will stay onscreen and active until explicitly removed, which you can + * do by calling `E.showMenu()` without arguments. + * See http://www.espruino.com/graphical_menu for more detailed information. + * On Bangle.js there are a few additions over the standard `graphical_menu`: + * * The options object can contain: + * * `back : function() { }` - add a 'back' button, with the function called when + * it is pressed + * * (Bangle.js 2) `scroll : int` - an integer specifying how much the initial + * menu should be scrolled by + * * The object returned by `E.showMenu` contains: + * * (Bangle.js 2) `scroller` - the object returned by `E.showScroller` - + * `scroller.scroll` returns the amount the menu is currently scrolled by + * * In the object specified for editable numbers: + * * (Bangle.js 2) the `format` function is called with `format(value)` in the + * main menu, `format(value,1)` when in a scrollable list, or `format(value,2)` + * when in a popup window. + * You can also specify menu items as an array (rather than an Object). This can be + * useful if you have menu items with the same title, or you want to `push` menu + * items onto an array: + * ``` + * var menu = [ + * { title:"Something", onchange:function() { print("selected"); } }, + * { title:"On or Off", value:false, onchange: v => print(v) }, + * { title:"A Value", value:3, min:0, max:10, onchange: v => print(v) }, + * ]; + * menu[""] = { title:"Hello" }; + * E.showMenu(menu); + * ``` + * + * @param {any} menu - An object containing name->function mappings to to be used in a menu + * @returns {any} A menu object with `draw`, `move` and `select` functions + * @url http://www.espruino.com/Reference#l_E_showMenu + */ + static showMenu(menu: Menu): MenuInstance; + static showMenu(): void; + + /** + * A utility function for displaying a full screen message on the screen. + * Draws to the screen and returns immediately. + * ``` + * E.showMessage("These are\nLots of\nLines","My Title") + * ``` + * or to display an image as well as text: + * ``` + * E.showMessage("Lots of text will wrap automatically",{ + * title:"Warning", + * img:atob("FBQBAfgAf+Af/4P//D+fx/n+f5/v+f//n//5//+f//n////3//5/n+P//D//wf/4B/4AH4A=") + * }) + * ``` + * + * @param {any} message - A message to display. Can include newlines + * @param {any} options - (optional) a title for the message, or an object of options `{title:string, img:image_string}` + * @url http://www.espruino.com/Reference#l_E_showMessage + */ + static showMessage(message: string, title?: string | { title?: string, img?: string }): void; + + /** + * Displays a full screen prompt on the screen, with the buttons requested (or + * `Yes` and `No` for defaults). + * When the button is pressed the promise is resolved with the requested values + * (for the `Yes` and `No` defaults, `true` and `false` are returned). + * ``` + * E.showPrompt("Do you like fish?").then(function(v) { + * if (v) print("'Yes' chosen"); + * else print("'No' chosen"); + * }); + * // Or + * E.showPrompt("How many fish\ndo you like?",{ + * title:"Fish", + * buttons : {"One":1,"Two":2,"Three":3} + * }).then(function(v) { + * print("You like "+v+" fish"); + * }); + * // Or + * E.showPrompt("Continue?", { + * title:"Alert", + * img:atob("FBQBAfgAf+Af/4P//D+fx/n+f5/v+f//n//5//+f//n////3//5/n+P//D//wf/4B/4AH4A=")}).then(function(v) { + * if (v) print("'Yes' chosen"); + * else print("'No' chosen"); + * }); + * ``` + * To remove the prompt, call `E.showPrompt()` with no arguments. + * The second `options` argument can contain: + * ``` + * { + * title: "Hello", // optional Title + * buttons : {"Ok":true,"Cancel":false}, // optional list of button text & return value + * img: "image_string" // optional image string to draw + * } + * ``` + * + * @param {any} message - A message to display. Can include newlines + * @param {any} options - (optional) an object of options (see below) + * @returns {any} A promise that is resolved when 'Ok' is pressed + * @url http://www.espruino.com/Reference#l_E_showPrompt + */ + static showPrompt(message: string, options?: { title?: string, buttons?: { [key: string]: T } }): Promise; + static showPrompt(): void; + + /** + * Display a scrollable menu on the screen, and set up the buttons/touchscreen to + * navigate through it and select items. + * Supply an object containing: + * ``` + * { + * h : 24, // height of each menu item in pixels + * c : 10, // number of menu items + * // a function to draw a menu item + * draw : function(idx, rect) { ... } + * // a function to call when the item is selected + * select : function(idx) { ... } + * // optional function to be called when 'back' is tapped + * back : function() { ...} + * } + * ``` + * For example to display a list of numbers: + * ``` + * E.showScroller({ + * h : 40, c : 8, + * draw : (idx, r) => { + * g.setBgColor((idx&1)?"#666":"#999").clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1); + * g.setFont("6x8:2").drawString("Item Number\n"+idx,r.x+10,r.y+4); + * }, + * select : (idx) => console.log("You selected ", idx) + * }); + * ``` + * To remove the scroller, just call `E.showScroller()` + * + * @param {any} options - An object containing `{ h, c, draw, select }` (see below) + * @returns {any} A menu object with `draw()` and `drawItem(itemNo)` functions + * @url http://www.espruino.com/Reference#l_E_showScroller + */ + static showScroller(options?: { h: number, c: number, draw: (idx: number, rect: { x: number, y: number, w: number, h: number }) => void, select: (idx: number) => void, back?: () => void }): { draw: () => void, drawItem: (itemNo: number) => void }; + static showScroller(): void; + + /** + * @url http://www.espruino.com/Reference#l_E_showMenu + */ + static showMenu(): void; + + /** + * @url http://www.espruino.com/Reference#l_E_showMenu + */ + static showMenu(): void; + + /** + * @url http://www.espruino.com/Reference#l_E_showPrompt + */ + static showPrompt(): void; + + /** + * @url http://www.espruino.com/Reference#l_E_showScroller + */ + static showScroller(): void; + + /** + * Displays a full screen prompt on the screen, with a single 'Ok' button. + * When the button is pressed the promise is resolved. + * ``` + * E.showAlert("Hello").then(function() { + * print("Ok pressed"); + * }); + * // or + * E.showAlert("These are\nLots of\nLines","My Title").then(function() { + * print("Ok pressed"); + * }); + * ``` + * To remove the window, call `E.showAlert()` with no arguments. + * + * @param {any} message - A message to display. Can include newlines + * @param {any} options - (optional) a title for the message + * @returns {any} A promise that is resolved when 'Ok' is pressed + * @url http://www.espruino.com/Reference#l_E_showAlert + */ + static showAlert(message?: string, options?: string): Promise; + + /** + * This event is called right after the board starts up, and has a similar effect + * to creating a function called `onInit`. + * For example to write `"Hello World"` every time Espruino starts, use: + * ``` + * E.on('init', function() { + * console.log("Hello World!"); + * }); + * ``` + * **Note:** that subsequent calls to `E.on('init', ` will **add** a new handler, + * rather than replacing the last one. This allows you to write modular code - + * something that was not possible with `onInit`. + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_E_init + */ + static on(event: "init", callback: () => void): void; + + /** + * This event is called just before the device shuts down for commands such as + * `reset()`, `load()`, `save()`, `E.reboot()` or `Bangle.off()` + * For example to write `"Bye!"` just before shutting down use: + * ``` + * E.on('kill', function() { + * console.log("Bye!"); + * }); + * ``` + * **NOTE:** This event is not called when the device is 'hard reset' - for example + * by removing power, hitting an actual reset button, or via a Watchdog timer + * reset. + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_E_kill + */ + static on(event: "kill", callback: () => void): void; + + /** + * This event is called when an error is created by Espruino itself (rather than JS + * code) which changes the state of the error flags reported by `E.getErrorFlags()` + * This could be low memory, full buffers, UART overflow, etc. `E.getErrorFlags()` + * has a full description of each type of error. + * This event will only be emitted when error flag is set. If the error flag was + * already set nothing will be emitted. To clear error flags so that you do get a + * callback each time a flag is set, call `E.getErrorFlags()`. + * @param {string} event - The event to listen to. + * @param {(errorFlags: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `errorFlags` An array of new error flags, as would be returned by `E.getErrorFlags()`. Error flags that were present before won't be reported. + * @url http://www.espruino.com/Reference#l_E_errorFlag + */ + static on(event: "errorFlag", callback: (errorFlags: ErrorFlag[]) => void): void; + + /** + * This event is called when a full touchscreen device on an Espruino is interacted + * with. + * **Note:** This event is not implemented on Bangle.js because it only has a two + * area touchscreen. + * To use the touchscreen to draw lines, you could do: + * ``` + * var last; + * E.on('touch',t=>{ + * if (last) g.lineTo(t.x, t.y); + * else g.moveTo(t.x, t.y); + * last = t.b; + * }); + * ``` + * @param {string} event - The event to listen to. + * @param {(x: number, y: number, b: number) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `x` X coordinate in display coordinates + * * `y` Y coordinate in display coordinates + * * `b` Touch count - 0 for released, 1 for pressed + * @url http://www.espruino.com/Reference#l_E_touch + */ + static on(event: "touch", callback: (x: number, y: number, b: number) => void): void; + + /** + * Use the microcontroller's internal thermistor to work out the temperature. + * On Puck.js v2.0 this will use the on-board PCT2075TP temperature sensor, but on + * other devices it may not be desperately well calibrated. + * While this is implemented on Espruino boards, it may not be implemented on other + * devices. If so it'll return NaN. + * **Note:** This is not entirely accurate and varies by a few degrees from chip + * to chip. It measures the **die temperature**, so when connected to USB it could + * be reading 10 over degrees C above ambient temperature. When running from + * battery with `setDeepSleep(true)` it is much more accurate though. + * @returns {number} The temperature in degrees C + * @url http://www.espruino.com/Reference#l_E_getTemperature + */ + static getTemperature(): number; + + /** + * Check the internal voltage reference. To work out an actual voltage of an input + * pin, you can use `analogRead(pin)*E.getAnalogVRef()` + * **Note:** This value is calculated by reading the voltage on an internal + * voltage reference with the ADC. It will be slightly noisy, so if you need this + * for accurate measurements we'd recommend that you call this function several + * times and average the results. + * While this is implemented on Espruino boards, it may not be implemented on other + * devices. If so it'll return NaN. + * @returns {number} The voltage (in Volts) that a reading of 1 from `analogRead` actually represents - usually around 3.3v + * @url http://www.espruino.com/Reference#l_E_getAnalogVRef + */ + static getAnalogVRef(): number; + + /** + * ADVANCED: This is a great way to crash Espruino if you're not sure what you are + * doing + * Create a native function that executes the code at the given address, e.g. + * `E.nativeCall(0x08012345,'double (double,double)')(1.1, 2.2)` + * If you're executing a thumb function, you'll almost certainly need to set the + * bottom bit of the address to 1. + * Note it's not guaranteed that the call signature you provide can be used - there + * are limits on the number of arguments allowed. + * When supplying `data`, if it is a 'flat string' then it will be used directly, + * otherwise it'll be converted to a flat string and used. + * + * @param {number} addr - The address in memory of the function (or offset in `data` if it was supplied + * @param {any} sig - The signature of the call, `returnType (arg1,arg2,...)`. Allowed types are `void`,`bool`,`int`,`double`,`Pin`,`JsVar` + * @param {any} data - (Optional) A string containing the function itself. If not supplied then 'addr' is used as an absolute address. + * @returns {any} The native function + * @url http://www.espruino.com/Reference#l_E_nativeCall + */ + static nativeCall(addr: number, sig: string, data?: string): any; + + /** + * Clip a number to be between min and max (inclusive) + * + * @param {number} x - A floating point value to clip + * @param {number} min - The smallest the value should be + * @param {number} max - The largest the value should be + * @returns {number} The value of x, clipped so as not to be below min or above max. + * @url http://www.espruino.com/Reference#l_E_clip + */ + static clip(x: number, min: number, max: number): number; + + /** + * Sum the contents of the given Array, String or ArrayBuffer and return the result + * + * @param {any} arr - The array to sum + * @returns {number} The sum of the given buffer + * @url http://www.espruino.com/Reference#l_E_sum + */ + static sum(arr: string | number[] | ArrayBuffer): number; + + /** + * Work out the variance of the contents of the given Array, String or ArrayBuffer + * and return the result. This is equivalent to `v=0;for (i in arr) + * v+=Math.pow(mean-arr[i],2)` + * + * @param {any} arr - The array to work out the variance for + * @param {number} mean - The mean value of the array + * @returns {number} The variance of the given buffer + * @url http://www.espruino.com/Reference#l_E_variance + */ + static variance(arr: string | number[] | ArrayBuffer, mean: number): number; + + /** + * Convolve arr1 with arr2. This is equivalent to `v=0;for (i in arr1) v+=arr1[i] * + * arr2[(i+offset) % arr2.length]` + * + * @param {any} arr1 - An array to convolve + * @param {any} arr2 - An array to convolve + * @param {number} offset - The mean value of the array + * @returns {number} The variance of the given buffer + * @url http://www.espruino.com/Reference#l_E_convolve + */ + static convolve(arr1: string | number[] | ArrayBuffer, arr2: string | number[] | ArrayBuffer, offset: number): number; + + /** + * Performs a Fast Fourier Transform (FFT) in 32 bit floats on the supplied data + * and writes it back into the original arrays. Note that if only one array is + * supplied, the data written back is the modulus of the complex result + * `sqrt(r*r+i*i)`. + * In order to perform the FFT, there has to be enough room on the stack to + * allocate two arrays of 32 bit floating point numbers - this will limit the + * maximum size of FFT possible to around 1024 items on most platforms. + * **Note:** on the Original Espruino board, FFTs are performed in 64bit arithmetic + * as there isn't space to include the 32 bit maths routines (2x more RAM is + * required). + * + * @param {any} arrReal - An array of real values + * @param {any} arrImage - An array of imaginary values (or if undefined, all values will be taken to be 0) + * @param {boolean} inverse - Set this to true if you want an inverse FFT - otherwise leave as 0 + * @url http://www.espruino.com/Reference#l_E_FFT + */ + static FFT(arrReal: string | number[] | ArrayBuffer, arrImage?: string | number[] | ArrayBuffer, inverse?: boolean): any; + + /** + * Enable the watchdog timer. This will reset Espruino if it isn't able to return + * to the idle loop within the timeout. + * If `isAuto` is false, you must call `E.kickWatchdog()` yourself every so often + * or the chip will reset. + * ``` + * E.enableWatchdog(0.5); // automatic mode + * while(1); // Espruino will reboot because it has not been idle for 0.5 sec + * ``` + * ``` + * E.enableWatchdog(1, false); + * setInterval(function() { + * if (everything_ok) + * E.kickWatchdog(); + * }, 500); + * // Espruino will now reset if everything_ok is false, + * // or if the interval fails to be called + * ``` + * **NOTE:** This is only implemented on STM32 and nRF5x devices (all official + * Espruino boards). + * **NOTE:** On STM32 (Pico, WiFi, Original) with `setDeepSleep(1)` you need to + * explicitly wake Espruino up with an interval of less than the watchdog timeout + * or the watchdog will fire and the board will reboot. You can do this with + * `setInterval("", time_in_milliseconds)`. + * + * @param {number} timeout - The timeout in seconds before a watchdog reset + * @param {any} isAuto - If undefined or true, the watchdog is kicked automatically. If not, you must call `E.kickWatchdog()` yourself + * @url http://www.espruino.com/Reference#l_E_enableWatchdog + */ + static enableWatchdog(timeout: number, isAuto?: boolean): void; + + /** + * Kicks a Watchdog timer set up with `E.enableWatchdog(..., false)`. See + * `E.enableWatchdog` for more information. + * **NOTE:** This is only implemented on STM32 and nRF5x devices (all official + * Espruino boards). + * @url http://www.espruino.com/Reference#l_E_kickWatchdog + */ + static kickWatchdog(): void; + + /** + * Get and reset the error flags. Returns an array that can contain: + * `'FIFO_FULL'`: The receive FIFO filled up and data was lost. This could be state + * transitions for setWatch, or received characters. + * `'BUFFER_FULL'`: A buffer for a stream filled up and characters were lost. This + * can happen to any stream - Serial,HTTP,etc. + * `'CALLBACK'`: A callback (`setWatch`, `setInterval`, `on('data',...)`) caused an + * error and so was removed. + * `'LOW_MEMORY'`: Memory is running low - Espruino had to run a garbage collection + * pass or remove some of the command history + * `'MEMORY'`: Espruino ran out of memory and was unable to allocate some data that + * it needed. + * `'UART_OVERFLOW'` : A UART received data but it was not read in time and was + * lost + * @returns {any} An array of error flags + * @url http://www.espruino.com/Reference#l_E_getErrorFlags + */ + static getErrorFlags(): ErrorFlag[] + + /** + * Get Espruino's interpreter flags that control the way it handles your JavaScript + * code. + * * `deepSleep` - Allow deep sleep modes (also set by setDeepSleep) + * * `pretokenise` - When adding functions, pre-minify them and tokenise reserved + * words + * * `unsafeFlash` - Some platforms stop writes/erases to interpreter memory to + * stop you bricking the device accidentally - this removes that protection + * * `unsyncFiles` - When writing files, *don't* flush all data to the SD card + * after each command (the default is *to* flush). This is much faster, but can + * cause filesystem damage if power is lost without the filesystem unmounted. + * @returns {any} An object containing flag names and their values + * @url http://www.espruino.com/Reference#l_E_getFlags + */ + static getFlags(): { [key in Flag]: boolean } + + /** + * Set the Espruino interpreter flags that control the way it handles your + * JavaScript code. + * Run `E.getFlags()` and check its description for a list of available flags and + * their values. + * + * @param {any} flags - An object containing flag names and boolean values. You need only specify the flags that you want to change. + * @url http://www.espruino.com/Reference#l_E_setFlags + */ + static setFlags(flags: { [key in Flag]?: boolean }): void + + /** + * + * @param {any} source - The source file/stream that will send content. + * @param {any} destination - The destination file/stream that will receive content from the source. + * @param {any} options + * An optional object `{ chunkSize : int=64, end : bool=true, complete : function }` + * chunkSize : The amount of data to pipe from source to destination at a time + * complete : a function to call when the pipe activity is complete + * end : call the 'end' function on the destination when the source is finished + * @url http://www.espruino.com/Reference#l_E_pipe + */ + static pipe(source: any, destination: any, options?: { chunkSize?: number, end?: boolean, complete?: () => void }): void + + /** + * Create an ArrayBuffer from the given string. This is done via a reference, not a + * copy - so it is very fast and memory efficient. + * Note that this is an ArrayBuffer, not a Uint8Array. To get one of those, do: + * `new Uint8Array(E.toArrayBuffer('....'))`. + * + * @param {any} str - The string to convert to an ArrayBuffer + * @returns {any} An ArrayBuffer that uses the given string + * @url http://www.espruino.com/Reference#l_E_toArrayBuffer + */ + static toArrayBuffer(str: string): ArrayBuffer; + + /** + * Returns a 'flat' string representing the data in the arguments, or return + * `undefined` if a flat string cannot be created. + * This creates a string from the given arguments. If an argument is a String or an + * Array, each element is traversed and added as an 8 bit character. If it is + * anything else, it is converted to a character directly. + * In the case where there's one argument which is an 8 bit typed array backed by a + * flat string of the same length, the backing string will be returned without + * doing a copy or other allocation. The same applies if there's a single argument + * which is itself a flat string. + * + * @param {any} args - The arguments to convert to a String + * @returns {any} A String (or `undefined` if a Flat String cannot be created) + * @url http://www.espruino.com/Reference#l_E_toString + */ + static toString(...args: any[]): string | undefined; + + /** + * This creates a Uint8Array from the given arguments. These are handled as + * follows: + * * `Number` -> read as an integer, using the lowest 8 bits + * * `String` -> use each character's numeric value (e.g. + * `String.charCodeAt(...)`) + * * `Array` -> Call itself on each element + * * `ArrayBuffer` or Typed Array -> use the lowest 8 bits of each element + * * `Object`: + * * `{data:..., count: int}` -> call itself `object.count` times, on + * `object.data` + * * `{callback : function}` -> call the given function, call itself on return + * value + * For example: + * ``` + * E.toUint8Array([1,2,3]) + * =new Uint8Array([1, 2, 3]) + * E.toUint8Array([1,{data:2,count:3},3]) + * =new Uint8Array([1, 2, 2, 2, 3]) + * E.toUint8Array("Hello") + * =new Uint8Array([72, 101, 108, 108, 111]) + * E.toUint8Array(["hi",{callback:function() { return [1,2,3] }}]) + * =new Uint8Array([104, 105, 1, 2, 3]) + * ``` + * + * @param {any} args - The arguments to convert to a Uint8Array + * @returns {any} A Uint8Array + * @url http://www.espruino.com/Reference#l_E_toUint8Array + */ + static toUint8Array(...args: Uint8ArrayResolvable[]): Uint8Array; + + /** + * This performs the same basic function as `JSON.stringify`, however + * `JSON.stringify` adds extra characters to conform to the JSON spec which aren't + * required if outputting JS. + * `E.toJS` will also stringify JS functions, whereas `JSON.stringify` ignores + * them. + * For example: + * * `JSON.stringify({a:1,b:2}) == '{"a":1,"b":2}'` + * * `E.toJS({a:1,b:2}) == '{a:1,b:2}'` + * **Note:** Strings generated with `E.toJS` can't be reliably parsed by + * `JSON.parse` - however they are valid JS so will work with `eval` (but this has + * security implications if you don't trust the source of the string). + * On the desktop [JSON5 parsers](https://github.com/json5/json5) will parse the + * strings produced by `E.toJS` without trouble. + * + * @param {any} arg - The JS variable to convert to a string + * @returns {any} A String + * @url http://www.espruino.com/Reference#l_E_toJS + */ + static toJS(arg: any): string; + + /** + * This creates and returns a special type of string, which actually references a + * specific memory address. It can be used in order to use sections of Flash memory + * directly in Espruino (for example to execute code straight from flash memory + * with `eval(E.memoryArea( ... ))`) + * **Note:** This is only tested on STM32-based platforms (Espruino Original and + * Espruino Pico) at the moment. + * + * @param {number} addr - The address of the memory area + * @param {number} len - The length (in bytes) of the memory area + * @returns {any} A String + * @url http://www.espruino.com/Reference#l_E_memoryArea + */ + static memoryArea(addr: number, len: number): string; + + /** + * This writes JavaScript code into Espruino's flash memory, to be executed on + * startup. It differs from `save()` in that `save()` saves the whole state of the + * interpreter, whereas this just saves JS code that is executed at boot. + * Code will be executed before `onInit()` and `E.on('init', ...)`. + * If `alwaysExec` is `true`, the code will be executed even after a call to + * `reset()`. This is useful if you're making something that you want to program, + * but you want some code that is always built in (for instance setting up a + * display or keyboard). + * To remove boot code that has been saved previously, use `E.setBootCode("")` + * **Note:** this removes any code that was previously saved with `save()` + * + * @param {any} code - The code to execute (as a string) + * @param {boolean} alwaysExec - Whether to always execute the code (even after a reset) + * @url http://www.espruino.com/Reference#l_E_setBootCode + */ + static setBootCode(code: string, alwaysExec?: boolean): void; + + /** + * This sets the clock frequency of Espruino's processor. It will return `0` if it + * is unimplemented or the clock speed cannot be changed. + * **Note:** On pretty much all boards, UART, SPI, I2C, PWM, etc will change + * frequency and will need setting up again in order to work. + * ### STM32F4 + * Options is of the form `{ M: int, N: int, P: int, Q: int }` - see the 'Clocks' + * section of the microcontroller's reference manual for what these mean. + * * System clock = 8Mhz * N / ( M * P ) + * * USB clock (should be 48Mhz) = 8Mhz * N / ( M * Q ) + * Optional arguments are: + * * `latency` - flash latency from 0..15 + * * `PCLK1` - Peripheral clock 1 divisor (default: 2) + * * `PCLK2` - Peripheral clock 2 divisor (default: 4) + * The Pico's default is `{M:8, N:336, P:4, Q:7, PCLK1:2, PCLK2:4}`, use `{M:8, + * N:336, P:8, Q:7, PCLK:1, PCLK2:2}` to halve the system clock speed while keeping + * the peripherals running at the same speed (omitting PCLK1/2 will lead to the + * peripherals changing speed too). + * On STM32F4 boards (e.g. Espruino Pico), the USB clock needs to be kept at 48Mhz + * or USB will fail to work. You'll also experience USB instability if the + * processor clock falls much below 48Mhz. + * ### ESP8266 + * Just specify an integer value, either 80 or 160 (for 80 or 160Mhz) + * + * @param {any} options - Platform-specific options for setting clock speed + * @returns {number} The actual frequency the clock has been set to + * @url http://www.espruino.com/Reference#l_E_setClock + */ + static setClock(options: number | { M: number, N: number, P: number, Q: number, latency?: number, PCLK?: number, PCLK2?: number }): number; + + /** + * Changes the device that the JS console (otherwise known as the REPL) is attached + * to. If the console is on a device, that device can be used for programming + * Espruino. + * Rather than calling `Serial.setConsole` you can call + * `E.setConsole("DeviceName")`. + * This is particularly useful if you just want to remove the console. + * `E.setConsole(null)` will make the console completely inaccessible. + * `device` may be `"Serial1"`,`"USB"`,`"Bluetooth"`,`"Telnet"`,`"Terminal"`, any + * other *hardware* `Serial` device, or `null` to disable the console completely. + * `options` is of the form: + * ``` + * { + * force : bool // default false, force the console onto this device so it does not move + * // if false, changes in connection state (e.g. USB/Bluetooth) can move + * // the console automatically. + * } + * ``` + * + * @param {any} device + * @param {any} options - (optional) object of options, see below + * @url http://www.espruino.com/Reference#l_E_setConsole + */ + static setConsole(device: "Serial1" | "USB" | "Bluetooth" | "Telnet" | "Terminal" | Serial | null, options?: { force?: boolean }): void; + + /** + * Returns the current console device - see `E.setConsole` for more information. + * @returns {any} The current console device as a string, or just `null` if the console is null + * @url http://www.espruino.com/Reference#l_E_getConsole + */ + static getConsole(): string | null + + /** + * Reverse the 8 bits in a byte, swapping MSB and LSB. + * For example, `E.reverseByte(0b10010000) == 0b00001001`. + * Note that you can reverse all the bytes in an array with: `arr = + * arr.map(E.reverseByte)` + * + * @param {number} x - A byte value to reverse the bits of + * @returns {number} The byte with reversed bits + * @url http://www.espruino.com/Reference#l_E_reverseByte + */ + static reverseByte(x: number): number; + + /** + * Output the current list of Utility Timer Tasks - for debugging only + * @url http://www.espruino.com/Reference#l_E_dumpTimers + */ + static dumpTimers(): void; + + /** + * Dump any locked variables that aren't referenced from `global` - for debugging + * memory leaks only. + * @url http://www.espruino.com/Reference#l_E_dumpLockedVars + */ + static dumpLockedVars(): void; + + /** + * Dump any locked variables that aren't referenced from `global` - for debugging + * memory leaks only. + * @url http://www.espruino.com/Reference#l_E_dumpFreeList + */ + static dumpFreeList(): void; + + /** + * Show fragmentation. + * * ` ` is free space + * * `#` is a normal variable + * * `L` is a locked variable (address used, cannot be moved) + * * `=` represents data in a Flat String (must be contiguous) + * @url http://www.espruino.com/Reference#l_E_dumpFragmentation + */ + static dumpFragmentation(): void; + + /** + * Dumps a comma-separated list of all allocated variables along with the variables + * they link to. Can be used to visualise where memory is used. + * @url http://www.espruino.com/Reference#l_E_dumpVariables + */ + static dumpVariables(): void; + + /** + * BETA: defragment memory! + * @url http://www.espruino.com/Reference#l_E_defrag + */ + static defrag(): void; + + /** + * Return the number of variable blocks used by the supplied variable. This is + * useful if you're running out of memory and you want to be able to see what is + * taking up most of the available space. + * If `depth>0` and the variable can be recursed into, an array listing all + * property names (including internal Espruino names) and their sizes is returned. + * If `depth>1` there is also a `more` field that inspects the objects' children's + * children. + * For instance `E.getSizeOf(function(a,b) { })` returns `5`. + * But `E.getSizeOf(function(a,b) { }, 1)` returns: + * ``` + * [ + * { + * "name": "a", + * "size": 1 }, + * { + * "name": "b", + * "size": 1 }, + * { + * "name": "\xFFcod", + * "size": 2 } + * ] + * ``` + * In this case setting depth to `2` will make no difference as there are no more + * children to traverse. + * See http://www.espruino.com/Internals for more information + * + * @param {any} v - A variable to get the size of + * @param {number} depth - The depth that detail should be provided for. If depth<=0 or undefined, a single integer will be returned + * @returns {any} Information about the variable size - see below + * @url http://www.espruino.com/Reference#l_E_getSizeOf + */ + static getSizeOf(v: any, depth?: 0): number; + static getSizeOf(v: any, depth: number): VariableSizeInformation; + + /** + * Return the address in memory of the given variable. This can then be used with + * `peek` and `poke` functions. However, changing data in JS variables directly + * (flatAddress=false) will most likely result in a crash. + * This functions exists to allow embedded targets to set up peripherals such as + * DMA so that they write directly to JS variables. + * See http://www.espruino.com/Internals for more information + * + * @param {any} v - A variable to get the address of + * @param {boolean} flatAddress - (boolean) If `true` and a Flat String or Flat ArrayBuffer is supplied, return the address of the data inside it - otherwise 0. If `false` (the default) return the address of the JsVar itself. + * @returns {number} The address of the given variable + * @url http://www.espruino.com/Reference#l_E_getAddressOf + */ + static getAddressOf(v: any, flatAddress: boolean): number; + + /** + * Take each element of the `from` array, look it up in `map` (or call + * `map(value,index)` if it is a function), and write it into the corresponding + * element in the `to` array. + * You can use an array to map: + * ``` + * var a = new Uint8Array([1,2,3,1,2,3]); + * var lut = new Uint8Array([128,129,130,131]); + * E.mapInPlace(a, a, lut); + * // a = [129, 130, 131, 129, 130, 131] + * ``` + * Or `undefined` to pass straight through, or a function to do a normal 'mapping': + * ``` + * var a = new Uint8Array([0x12,0x34,0x56,0x78]); + * var b = new Uint8Array(8); + * E.mapInPlace(a, b, undefined); // straight through + * // b = [0x12,0x34,0x56,0x78,0,0,0,0] + * E.mapInPlace(a, b, (value,index)=>index); // write the index in the first 4 (because a.length==4) + * // b = [0,1,2,3,4,0,0,0] + * E.mapInPlace(a, b, undefined, 4); // 4 bits from 8 bit input -> 2x as many outputs, msb-first + * // b = [1, 2, 3, 4, 5, 6, 7, 8] + * E.mapInPlace(a, b, undefined, -4); // 4 bits from 8 bit input -> 2x as many outputs, lsb-first + * // b = [2, 1, 4, 3, 6, 5, 8, 7] + * E.mapInPlace(a, b, a=>a+2, 4); + * // b = [3, 4, 5, 6, 7, 8, 9, 10] + * var b = new Uint16Array(4); + * E.mapInPlace(a, b, undefined, 12); // 12 bits from 8 bit input, msb-first + * // b = [0x123, 0x456, 0x780, 0] + * E.mapInPlace(a, b, undefined, -12); // 12 bits from 8 bit input, lsb-first + * // b = [0x412, 0x563, 0x078, 0] + * ``` + * + * @param {any} from - An ArrayBuffer to read elements from + * @param {any} to - An ArrayBuffer to write elements too + * @param {any} map - An array or `function(value,index)` to use to map one element to another, or `undefined` to provide no mapping + * @param {number} bits - If specified, the number of bits per element (MSB first) - otherwise use a 1:1 mapping. If negative, use LSB first. + * @url http://www.espruino.com/Reference#l_E_mapInPlace + */ + static mapInPlace(from: ArrayBuffer, to: ArrayBuffer, map?: number[] | ((value: number, index: number) => number) | undefined, bits?: number): void; + + /** + * Search in an Object, Array, or Function + * + * @param {any} haystack - The Array/Object/Function to search + * @param {any} needle - The key to search for + * @param {boolean} returnKey - If true, return the key, else return the value itself + * @returns {any} The value in the Object matching 'needle', or if `returnKey==true` the key's name - or undefined + * @url http://www.espruino.com/Reference#l_E_lookupNoCase + */ + static lookupNoCase(haystack: any[] | object | Function, needle: string, returnKey?: false): any; + static lookupNoCase(haystack: any[] | object | Function, needle: T, returnKey: true): T | undefined; + + /** + * Get the current interpreter state in a text form such that it can be copied to a + * new device + * @returns {any} A String + * @url http://www.espruino.com/Reference#l_E_dumpStr + */ + static dumpStr(): string; + + /** + * Set the seed for the random number generator used by `Math.random()`. + * + * @param {number} v - The 32 bit integer seed to use for the random number generator + * @url http://www.espruino.com/Reference#l_E_srand + */ + static srand(v: number): void; + + /** + * Unlike 'Math.random()' which uses a pseudo-random number generator, this method + * reads from the internal voltage reference several times, XOR-ing and rotating to + * try and make a relatively random value from the noise in the signal. + * @returns {number} A random number + * @url http://www.espruino.com/Reference#l_E_hwRand + */ + static hwRand(): number; + + /** + * Perform a standard 32 bit CRC (Cyclic redundancy check) on the supplied data + * (one byte at a time) and return the result as an unsigned integer. + * + * @param {any} data - Iterable data to perform CRC32 on (each element treated as a byte) + * @returns {any} The CRC of the supplied data + * @url http://www.espruino.com/Reference#l_E_CRC32 + */ + static CRC32(data: any): any; + + /** + * Convert hue, saturation and brightness to red, green and blue (packed into an + * integer if `asArray==false` or an array if `asArray==true`). + * This replaces `Graphics.setColorHSB` and `Graphics.setBgColorHSB`. On devices + * with 24 bit colour it can be used as: `Graphics.setColor(E.HSBtoRGB(h, s, b))` + * You can quickly set RGB items in an Array or Typed Array using + * `array.set(E.HSBtoRGB(h, s, b,true), offset)`, which can be useful with arrays + * used with `require("neopixel").write`. + * + * @param {number} hue - The hue, as a value between 0 and 1 + * @param {number} sat - The saturation, as a value between 0 and 1 + * @param {number} bri - The brightness, as a value between 0 and 1 + * @param {boolean} asArray - If true, return an array of [R,G,B] values betwen 0 and 255 + * @returns {any} A 24 bit number containing bytes representing red, green, and blue `0xBBGGRR`. Or if `asArray` is true, an array `[R,G,B]` + * @url http://www.espruino.com/Reference#l_E_HSBtoRGB + */ + static HSBtoRGB(hue: number, sat: number, bri: number, asArray?: false): number; + static HSBtoRGB(hue: number, sat: number, bri: number, asArray: true): [number, number, number]; + + /** + * Set a password on the console (REPL). When powered on, Espruino will then demand + * a password before the console can be used. If you want to lock the console + * immediately after this you can call `E.lockConsole()` + * To remove the password, call this function with no arguments. + * **Note:** There is no protection against multiple password attempts, so someone + * could conceivably try every password in a dictionary. + * **Note:** This password is stored in memory in plain text. If someone is able to + * execute arbitrary JavaScript code on the device (e.g., you use `eval` on input + * from unknown sources) or read the device's firmware then they may be able to + * obtain it. + * + * @param {any} password - The password - max 20 chars + * @url http://www.espruino.com/Reference#l_E_setPassword + */ + static setPassword(password: string): void; + + /** + * If a password has been set with `E.setPassword()`, this will lock the console so + * the password needs to be entered to unlock it. + * @url http://www.espruino.com/Reference#l_E_lockConsole + */ + static lockConsole(): void; + + /** + * Set the time zone to be used with `Date` objects. + * For example `E.setTimeZone(1)` will be GMT+0100 + * Note that `E.setTimeZone()` will have no effect when daylight savings time rules + * have been set with `E.setDST()`. The timezone value will be stored, but never + * used so long as DST settings are in effect. + * Time can be set with `setTime`. + * + * @param {number} zone - The time zone in hours + * @url http://www.espruino.com/Reference#l_E_setTimeZone + */ + static setTimeZone(zone: number): void; + + /** + * Set the daylight savings time parameters to be used with `Date` objects. + * The parameters are + * - dstOffset: The number of minutes daylight savings time adds to the clock + * (usually 60) - set to 0 to disable DST + * - timezone: The time zone, in minutes, when DST is not in effect - positive east + * of Greenwich + * - startDowNumber: The index of the day-of-week in the month when DST starts - 0 + * for first, 1 for second, 2 for third, 3 for fourth and 4 for last + * - startDow: The day-of-week for the DST start calculation - 0 for Sunday, 6 for + * Saturday + * - startMonth: The number of the month that DST starts - 0 for January, 11 for + * December + * - startDayOffset: The number of days between the selected day-of-week and the + * actual day that DST starts - usually 0 + * - startTimeOfDay: The number of minutes elapsed in the day before DST starts + * - endDowNumber: The index of the day-of-week in the month when DST ends - 0 for + * first, 1 for second, 2 for third, 3 for fourth and 4 for last + * - endDow: The day-of-week for the DST end calculation - 0 for Sunday, 6 for + * Saturday + * - endMonth: The number of the month that DST ends - 0 for January, 11 for + * December + * - endDayOffset: The number of days between the selected day-of-week and the + * actual day that DST ends - usually 0 + * - endTimeOfDay: The number of minutes elapsed in the day before DST ends + * To determine what the `dowNumber, dow, month, dayOffset, timeOfDay` parameters + * should be, start with a sentence of the form "DST starts on the last Sunday of + * March (plus 0 days) at 03:00". Since it's the last Sunday, we have + * startDowNumber = 4, and since it's Sunday, we have startDow = 0. That it is + * March gives us startMonth = 2, and that the offset is zero days, we have + * startDayOffset = 0. The time that DST starts gives us startTimeOfDay = 3*60. + * "DST ends on the Friday before the second Sunday in November at 02:00" would + * give us endDowNumber=1, endDow=0, endMonth=10, endDayOffset=-2 and + * endTimeOfDay=120. + * Using Ukraine as an example, we have a time which is 2 hours ahead of GMT in + * winter (EET) and 3 hours in summer (EEST). DST starts at 03:00 EET on the last + * Sunday in March, and ends at 04:00 EEST on the last Sunday in October. So + * someone in Ukraine might call `E.setDST(60,120,4,0,2,0,180,4,0,9,0,240);` + * Note that when DST parameters are set (i.e. when `dstOffset` is not zero), + * `E.setTimeZone()` has no effect. + * + * @param {any} params - An array containing the settings for DST + * @url http://www.espruino.com/Reference#l_E_setDST + */ + static setDST(dstOffset: number, timezone: number, startDowNumber: number, startDow: number, startMonth: number, startDayOffset: number, startTimeOfDay: number, endDowNumber: number, endDow: number, endMonth: number, endDayOffset: number, endTimeOfDay: number): void + + /** + * Create an object where every field accesses a specific 32 bit address in the + * microcontroller's memory. This is perfect for accessing on-chip peripherals. + * ``` + * // for NRF52 based chips + * var GPIO = E.memoryMap(0x50000000,{OUT:0x504, OUTSET:0x508, OUTCLR:0x50C, IN:0x510, DIR:0x514, DIRSET:0x518, DIRCLR:0x51C}); + * GPIO.DIRSET = 1; // set GPIO0 to output + * GPIO.OUT ^= 1; // toggle the output state of GPIO0 + * ``` + * + * @param {any} baseAddress - The base address (added to every address in `registers`) + * @param {any} registers - An object containing `{name:address}` + * @returns {any} An object where each field is memory-mapped to a register. + * @url http://www.espruino.com/Reference#l_E_memoryMap + */ + static memoryMap(baseAddress: number, registers: { [key in T]: number }): { [key in T]: number }; + + /** + * Provide assembly to Espruino. + * **This function is not part of Espruino**. Instead, it is detected by the + * Espruino IDE (or command-line tools) at upload time and is replaced with machine + * code and an `E.nativeCall` call. + * See [the documentation on the Assembler](http://www.espruino.com/Assembler) for + * more information. + * + * @param {any} callspec - The arguments this assembly takes - e.g. `void(int)` + * @param {any} assemblycode - One of more strings of assembler code + * @url http://www.espruino.com/Reference#l_E_asm + */ + static asm(callspec: string, ...assemblycode: string[]): any; + + /** + * Provides the ability to write C code inside your JavaScript file. + * **This function is not part of Espruino**. Instead, it is detected by the + * Espruino IDE (or command-line tools) at upload time, is sent to our web service + * to be compiled, and is replaced with machine code and an `E.nativeCall` call. + * See [the documentation on Inline C](http://www.espruino.com/InlineC) for more + * information and examples. + * + * @param {any} code - A Templated string of C code + * @url http://www.espruino.com/Reference#l_E_compiledC + */ + static compiledC(code: string): any; + + /** + * Forces a hard reboot of the microcontroller - as close as possible to if the + * reset pin had been toggled. + * **Note:** This is different to `reset()`, which performs a software reset of + * Espruino (resetting the interpreter and pin states, but not all the hardware) + * @url http://www.espruino.com/Reference#l_E_reboot + */ + static reboot(): void; + + /** + * USB HID will only take effect next time you unplug and re-plug your Espruino. If + * you're disconnecting it from power you'll have to make sure you have `save()`d + * after calling this function. + * + * @param {any} opts - An object containing at least reportDescriptor, an array representing the report descriptor. Pass undefined to disable HID. + * @url http://www.espruino.com/Reference#l_E_setUSBHID + */ + static setUSBHID(opts?: { reportDescriptor: any[] }): void; + + /** + * + * @param {any} data - An array of bytes to send as a USB HID packet + * @returns {boolean} 1 on success, 0 on failure + * @url http://www.espruino.com/Reference#l_E_sendUSBHID + */ + static sendUSBHID(data: string | ArrayBuffer | number[]): boolean; + + /** + * In devices that come with batteries, this function returns the battery charge + * percentage as an integer between 0 and 100. + * **Note:** this is an estimation only, based on battery voltage. The temperature + * of the battery (as well as the load being drawn from it at the time + * `E.getBattery` is called) will affect the readings. + * @returns {number} A percentage between 0 and 100 + * @url http://www.espruino.com/Reference#l_E_getBattery + */ + static getBattery(): number; + + /** + * Sets the RTC's prescaler's maximum value. This is the counter that counts up on + * each oscillation of the low speed oscillator. When the prescaler counts to the + * value supplied, one second is deemed to have passed. + * By default this is set to the oscillator's average speed as specified in the + * datasheet, and usually that is fine. However on early [Espruino Pico](/Pico) + * boards the STM32F4's internal oscillator could vary by as much as 15% from the + * value in the datasheet. In that case you may want to alter this value to reflect + * the true RTC speed for more accurate timekeeping. + * To change the RTC's prescaler value to a computed value based on comparing + * against the high speed oscillator, just run the following command, making sure + * it's done a few seconds after the board starts up: + * ``` + * E.setRTCPrescaler(E.getRTCPrescaler(true)); + * ``` + * When changing the RTC prescaler, the RTC 'follower' counters are reset and it + * can take a second or two before readings from getTime are stable again. + * To test, you can connect an input pin to a known frequency square wave and then + * use `setWatch`. If you don't have a frequency source handy, you can check + * against the high speed oscillator: + * ``` + * // connect pin B3 to B4 + * analogWrite(B3, 0.5, {freq:0.5}); + * setWatch(function(e) { + * print(e.time - e.lastTime); + * }, B4, {repeat:true}); + * ``` + * **Note:** This is only used on official Espruino boards containing an STM32 + * microcontroller. Other boards (even those using an STM32) don't use the RTC and + * so this has no effect. + * + * @param {number} prescaler - The amount of counts for one second of the RTC - this is a 15 bit integer value (0..32767) + * @url http://www.espruino.com/Reference#l_E_setRTCPrescaler + */ + static setRTCPrescaler(prescaler: number): void; + + /** + * Gets the RTC's current prescaler value if `calibrate` is undefined or false. + * If `calibrate` is true, the low speed oscillator's speed is calibrated against + * the high speed oscillator (usually +/- 20 ppm) and a suggested value to be fed + * into `E.setRTCPrescaler(...)` is returned. + * See `E.setRTCPrescaler` for more information. + * + * @param {boolean} calibrate - If `false`, the current value. If `true`, the calculated 'correct' value + * @returns {number} The RTC prescaler's current value + * @url http://www.espruino.com/Reference#l_E_getRTCPrescaler + */ + static getRTCPrescaler(calibrate: boolean): number; + + /** + * Decode a UTF8 string. + * * Any decoded character less than 256 gets passed straight through + * * Otherwise if `lookup` is an array and an item with that char code exists in `lookup` then that is used + * * Otherwise if `lookup` is an object and an item with that char code (as lowercase hex) exists in `lookup` then that is used + * * Otherwise `replaceFn(charCode)` is called and the result used if `replaceFn` is a function + * * If `replaceFn` is a string, that is used + * * Or finally if nothing else matches, the character is ignored + * For instance: + * ``` + * let unicodeRemap = { + * 0x20ac:"\u0080", // Euro symbol + * 0x2026:"\u0085", // Ellipsis + * }; + * E.decodeUTF8("UTF-8 Euro: \u00e2\u0082\u00ac", unicodeRemap, '[?]') == "UTF-8 Euro: \u0080" + * ``` + * + * @param {any} str - A string of UTF8-encoded data + * @param {any} lookup - An array containing a mapping of character code -> replacement string + * @param {any} replaceFn - If not in lookup, `replaceFn(charCode)` is called and the result used if it's a function, *or* if it's a string, the string value is used + * @returns {any} A string containing all UTF8 sequences flattened to 8 bits + * @url http://www.espruino.com/Reference#l_E_decodeUTF8 + */ + static decodeUTF8(str: string, lookup: string[], replaceFn: string | ((charCode: number) => string)): string; + + +} + +interface consoleConstructor { + /** + * Print the supplied string(s) to the console + * **Note:** If you're connected to a computer (not a wall adaptor) via USB but + * **you are not running a terminal app** then when you print data Espruino may + * pause execution and wait until the computer requests the data it is trying to + * print. + * + * @param {any} text - One or more arguments to print + * @url http://www.espruino.com/Reference#l_console_log + */ + log(...text: any[]): void; +} + +interface console { + +} + +/** + * An Object that contains functions for writing to the interactive console + * @url http://www.espruino.com/Reference#console + */ +declare const console: consoleConstructor + +interface ErrorConstructor { + /** + * Creates an Error object + * @constructor + * + * @param {any} message - An optional message string + * @returns {any} An Error object + * @url http://www.espruino.com/Reference#l_Error_Error + */ + new(message?: string): Error; +} + +interface Error { + /** + * @returns {any} A String + * @url http://www.espruino.com/Reference#l_Error_toString + */ + toString(): string; +} + +/** + * The base class for runtime errors + * @url http://www.espruino.com/Reference#Error + */ +declare const Error: ErrorConstructor + +interface SyntaxErrorConstructor { + /** + * Creates a SyntaxError object + * @constructor + * + * @param {any} message - An optional message string + * @returns {any} A SyntaxError object + * @url http://www.espruino.com/Reference#l_SyntaxError_SyntaxError + */ + new(message?: string): SyntaxError; +} + +interface SyntaxError { + /** + * @returns {any} A String + * @url http://www.espruino.com/Reference#l_SyntaxError_toString + */ + toString(): string; +} + +/** + * The base class for syntax errors + * @url http://www.espruino.com/Reference#SyntaxError + */ +declare const SyntaxError: SyntaxErrorConstructor + +interface TypeErrorConstructor { + /** + * Creates a TypeError object + * @constructor + * + * @param {any} message - An optional message string + * @returns {any} A TypeError object + * @url http://www.espruino.com/Reference#l_TypeError_TypeError + */ + new(message?: string): TypeError; +} + +interface TypeError { + /** + * @returns {any} A String + * @url http://www.espruino.com/Reference#l_TypeError_toString + */ + toString(): string; +} + +/** + * The base class for type errors + * @url http://www.espruino.com/Reference#TypeError + */ +declare const TypeError: TypeErrorConstructor + +/** + * The base class for internal errors + * @url http://www.espruino.com/Reference#InternalError + */ +declare class InternalError { + /** + * Creates an InternalError object + * @constructor + * + * @param {any} message - An optional message string + * @returns {any} An InternalError object + * @url http://www.espruino.com/Reference#l_InternalError_InternalError + */ + static new(message?: string): InternalError; + + /** + * @returns {any} A String + * @url http://www.espruino.com/Reference#l_InternalError_toString + */ + toString(): string; +} + +interface ReferenceErrorConstructor { + /** + * Creates a ReferenceError object + * @constructor + * + * @param {any} message - An optional message string + * @returns {any} A ReferenceError object + * @url http://www.espruino.com/Reference#l_ReferenceError_ReferenceError + */ + new(message?: string): ReferenceError; +} + +interface ReferenceError { + /** + * @returns {any} A String + * @url http://www.espruino.com/Reference#l_ReferenceError_toString + */ + toString(): string; +} + +/** + * The base class for reference errors - where a variable which doesn't exist has + * been accessed. + * @url http://www.espruino.com/Reference#ReferenceError + */ +declare const ReferenceError: ReferenceErrorConstructor + +interface JSONConstructor { + /** + * Convert the given object into a JSON string which can subsequently be parsed + * with JSON.parse or eval. + * **Note:** This differs from JavaScript's standard `JSON.stringify` in that: + * * The `replacer` argument is ignored + * * Typed arrays like `new Uint8Array(5)` will be dumped as if they were arrays, + * not as if they were objects (since it is more compact) + * + * @param {any} data - The data to be converted to a JSON string + * @param {any} replacer - This value is ignored + * @param {any} space - The number of spaces to use for padding, a string, or null/undefined for no whitespace + * @returns {any} A JSON string + * @url http://www.espruino.com/Reference#l_JSON_stringify + */ + stringify(data: any, replacer: any, space: any): any; + + /** + * Parse the given JSON string into a JavaScript object + * NOTE: This implementation uses eval() internally, and as such it is unsafe as it + * can allow arbitrary JS commands to be executed. + * + * @param {any} string - A JSON string + * @returns {any} The JavaScript object created by parsing the data string + * @url http://www.espruino.com/Reference#l_JSON_parse + */ + parse(string: any): any; +} + +interface JSON { + +} + +/** + * An Object that handles conversion to and from the JSON data interchange format + * @url http://www.espruino.com/Reference#JSON + */ +declare const JSON: JSONConstructor + +interface RegExpConstructor { + /** + * Creates a RegExp object, for handling Regular Expressions + * @constructor + * + * @param {any} regex - A regular expression as a string + * @param {any} flags - Flags for the regular expression as a string + * @returns {any} A RegExp object + * @url http://www.espruino.com/Reference#l_RegExp_RegExp + */ + new(regex: any, flags: any): RegExp; +} + +interface RegExp { + /** + * Test this regex on a string - returns a result array on success, or `null` + * otherwise. + * `/Wo/.exec("Hello World")` will return: + * ``` + * [ + * "Wo", + * "index": 6, + * "input": "Hello World" + * ] + * ``` + * Or with groups `/W(o)rld/.exec("Hello World")` returns: + * ``` + * [ + * "World", + * "o", "index": 6, + * "input": "Hello World" + * ] + * ``` + * + * @param {any} str - A string to match on + * @returns {any} A result array, or null + * @url http://www.espruino.com/Reference#l_RegExp_exec + */ + exec(str: any): any; + + /** + * Test this regex on a string - returns `true` on a successful match, or `false` + * otherwise + * + * @param {any} str - A string to match on + * @returns {boolean} true for a match, or false + * @url http://www.espruino.com/Reference#l_RegExp_test + */ + test(str: any): boolean; +} + +/** + * The built-in class for handling Regular Expressions + * **Note:** Espruino's regular expression parser does not contain all the features + * present in a full ES6 JS engine. However it does contain support for the all the + * basics. + * @url http://www.espruino.com/Reference#RegExp + */ +declare const RegExp: RegExpConstructor + +/** + * This is the built-in class for the Arduino-style pin namings on ST Nucleo boards + * @url http://www.espruino.com/Reference#Nucleo + */ +declare class Nucleo { + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_A0 + */ + static A0: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_A1 + */ + static A1: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_A2 + */ + static A2: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_A3 + */ + static A3: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_A4 + */ + static A4: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_A5 + */ + static A5: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_D0 + */ + static D0: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_D1 + */ + static D1: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_D2 + */ + static D2: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_D3 + */ + static D3: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_D4 + */ + static D4: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_D5 + */ + static D5: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_D6 + */ + static D6: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_D7 + */ + static D7: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_D8 + */ + static D8: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_D9 + */ + static D9: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_D10 + */ + static D10: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_D11 + */ + static D11: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_D12 + */ + static D12: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_D13 + */ + static D13: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_D14 + */ + static D14: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_Nucleo_D15 + */ + static D15: Pin; + + +} + +/** + * This is a built-in class to allow you to use the ESP8266 NodeMCU boards's pin + * namings to access pins. It is only available on ESP8266-based boards. + * @url http://www.espruino.com/Reference#NodeMCU + */ +declare class NodeMCU { + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_NodeMCU_A0 + */ + static A0: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_NodeMCU_D0 + */ + static D0: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_NodeMCU_D1 + */ + static D1: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_NodeMCU_D2 + */ + static D2: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_NodeMCU_D3 + */ + static D3: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_NodeMCU_D4 + */ + static D4: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_NodeMCU_D5 + */ + static D5: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_NodeMCU_D6 + */ + static D6: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_NodeMCU_D7 + */ + static D7: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_NodeMCU_D8 + */ + static D8: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_NodeMCU_D9 + */ + static D9: Pin; + + /** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l_NodeMCU_D10 + */ + static D10: Pin; + + +} + +/** + * Class containing utility functions for the + * [ESP32](http://www.espruino.com/ESP32) + * @url http://www.espruino.com/Reference#ESP32 + */ +declare class ESP32 { + /** + * + * @param {Pin} pin - Pin for Analog read + * @param {number} atten - Attenuate factor + * @url http://www.espruino.com/Reference#l_ESP32_setAtten + */ + static setAtten(pin: Pin, atten: number): void; + + /** + * Perform a hardware reset/reboot of the ESP32. + * @url http://www.espruino.com/Reference#l_ESP32_reboot + */ + static reboot(): void; + + /** + * Put device in deepsleep state for "us" microseconds. + * + * @param {number} us - Sleeptime in us + * @url http://www.espruino.com/Reference#l_ESP32_deepSleep + */ + static deepSleep(us: number): void; + + /** + * Returns an object that contains details about the state of the ESP32 with the + * following fields: + * * `sdkVersion` - Version of the SDK. + * * `freeHeap` - Amount of free heap in bytes. + * * `BLE` - Status of BLE, enabled if true. + * * `Wifi` - Status of Wifi, enabled if true. + * * `minHeap` - Minimum heap, calculated by heap_caps_get_minimum_free_size + * @returns {any} The state of the ESP32 + * @url http://www.espruino.com/Reference#l_ESP32_getState + */ + static getState(): any; + + /** + * + * @param {number} level - which events should be shown (GATTS, GATTC, GAP) + * @url http://www.espruino.com/Reference#l_ESP32_setBLE_Debug + */ + static setBLE_Debug(level: number): void; + + /** + * Switches Bluetooth off/on, removes saved code from Flash, resets the board, and + * on restart creates jsVars depending on available heap (actual additional 1800) + * + * @param {boolean} enable - switches Bluetooth on or off + * @url http://www.espruino.com/Reference#l_ESP32_enableBLE + */ + static enableBLE(enable: boolean): void; + + /** + * Switches Wifi off/on, removes saved code from Flash, resets the board, and on + * restart creates jsVars depending on available heap (actual additional 3900) + * + * @param {boolean} enable - switches Wifi on or off + * @url http://www.espruino.com/Reference#l_ESP32_enableWifi + */ + static enableWifi(enable: boolean): void; + + +} + +/** + * A class to support some simple Queue handling for RTOS queues + * @url http://www.espruino.com/Reference#Queue + */ +declare class Queue { + /** + * Creates a Queue Object + * @constructor + * + * @param {any} queueName - Name of the queue + * @returns {any} A Queue object + * @url http://www.espruino.com/Reference#l_Queue_Queue + */ + static new(queueName: any): any; + + /** + * reads one character from queue, if available + * @url http://www.espruino.com/Reference#l_Queue_read + */ + read(): void; + + /** + * Writes one character to queue + * + * @param {any} char - char to be send + * @url http://www.espruino.com/Reference#l_Queue_writeChar + */ + writeChar(char: any): void; + + /** + * logs list of queues + * @url http://www.espruino.com/Reference#l_Queue_log + */ + log(): void; +} + +/** + * A class to support some simple Task handling for RTOS tasks + * @url http://www.espruino.com/Reference#Task + */ +declare class Task { + /** + * Creates a Task Object + * @constructor + * + * @param {any} taskName - Name of the task + * @returns {any} A Task object + * @url http://www.espruino.com/Reference#l_Task_Task + */ + static new(taskName: any): any; + + /** + * Suspend task, be careful not to suspend Espruino task itself + * @url http://www.espruino.com/Reference#l_Task_suspend + */ + suspend(): void; + + /** + * Resumes a suspended task + * @url http://www.espruino.com/Reference#l_Task_resume + */ + resume(): void; + + /** + * returns name of actual task + * @returns {any} Name of current task + * @url http://www.espruino.com/Reference#l_Task_getCurrent + */ + getCurrent(): any; + + /** + * Sends a binary notify to task + * @url http://www.espruino.com/Reference#l_Task_notify + */ + notify(): void; + + /** + * logs list of tasks + * @url http://www.espruino.com/Reference#l_Task_log + */ + log(): void; +} + +/** + * A class to handle Timer on base of ESP32 Timer + * @url http://www.espruino.com/Reference#Timer + */ +declare class Timer { + /** + * Creates a Timer Object + * @constructor + * + * @param {any} timerName - Timer Name + * @param {number} group - Timer group + * @param {number} index - Timer index + * @param {number} isrIndex - isr (0 = Espruino, 1 = test) + * @returns {any} A Timer Object + * @url http://www.espruino.com/Reference#l_Timer_Timer + */ + static new(timerName: any, group: number, index: number, isrIndex: number): any; + + /** + * Starts a timer + * + * @param {number} duration - duration of timmer in micro secs + * @url http://www.espruino.com/Reference#l_Timer_start + */ + start(duration: number): void; + + /** + * Reschedules a timer, needs to be started at least once + * + * @param {number} duration - duration of timmer in micro secs + * @url http://www.espruino.com/Reference#l_Timer_reschedule + */ + reschedule(duration: number): void; + + /** + * logs list of timers + * @url http://www.espruino.com/Reference#l_Timer_log + */ + log(): void; +} + +interface BooleanConstructor { + /** + * Creates a boolean + * @constructor + * + * @param {any} value - A single value to be converted to a number + * @returns {boolean} A Boolean object + * @url http://www.espruino.com/Reference#l_Boolean_Boolean + */ + new(value: any): boolean; +} + +interface Boolean { + +} + + +declare const Boolean: BooleanConstructor + +// GLOBALS + +/** + * **Note:** This function is only available on the [BBC micro:bit](/MicroBit) + * board + * Show an image on the in-built 5x5 LED screen. + * Image can be: + * * A number where each bit represents a pixel (so 25 bits). eg. `5` or + * `0x1FFFFFF` + * * A string, eg: `show("10001")`. Newlines are ignored, and anything that is not + * a space or `0` is treated as a 1. + * * An array of 4 bytes (more will be ignored), eg `show([1,2,3,0])` + * For instance the following works for images: + * ``` + * show("# #"+ + * " # "+ + * " # "+ + * "# #"+ + * " ### ") + * ``` + * This means you can also use Espruino's graphics library: + * ``` + * var g = Graphics.createArrayBuffer(5,5,1) + * g.drawString("E",0,0) + * show(g.buffer) + * ``` + * + * @param {any} image - The image to show + * @url http://www.espruino.com/Reference#l__global_show + */ +declare function show(image: any): void; + +/** + * **Note:** This function is only available on the [BBC micro:bit](/MicroBit) + * board + * Get the current acceleration of the micro:bit from the on-board accelerometer + * **This is deprecated.** Please use `Microbit.accel` instead. + * @returns {any} An object with x, y, and z fields in it + * @url http://www.espruino.com/Reference#l__global_acceleration + */ +declare function acceleration(): any; + +/** + * **Note:** This function is only available on the [BBC micro:bit](/MicroBit) + * board + * Get the current compass position for the micro:bit from the on-board + * magnetometer + * **This is deprecated.** Please use `Microbit.mag` instead. + * @returns {any} An object with x, y, and z fields in it + * @url http://www.espruino.com/Reference#l__global_compass + */ +declare function compass(): any; + +/** + * The pin connected to the 'A' button. Reads as `1` when pressed, `0` when not + * @returns {Pin} + * @url http://www.espruino.com/Reference#l__global_BTNA + */ +declare const BTNA: Pin; + +/** + * The pin connected to the 'B' button. Reads as `1` when pressed, `0` when not + * @returns {Pin} + * @url http://www.espruino.com/Reference#l__global_BTNB + */ +declare const BTNB: Pin; + +/** + * The pin connected to the up button. Reads as `1` when pressed, `0` when not + * @returns {Pin} + * @url http://www.espruino.com/Reference#l__global_BTNU + */ +declare const BTNU: Pin; + +/** + * The pin connected to the down button. Reads as `1` when pressed, `0` when not + * @returns {Pin} + * @url http://www.espruino.com/Reference#l__global_BTND + */ +declare const BTND: Pin; + +/** + * The pin connected to the left button. Reads as `1` when pressed, `0` when not + * @returns {Pin} + * @url http://www.espruino.com/Reference#l__global_BTNL + */ +declare const BTNL: Pin; + +/** + * The pin connected to the right button. Reads as `1` when pressed, `0` when not + * @returns {Pin} + * @url http://www.espruino.com/Reference#l__global_BTNR + */ +declare const BTNR: Pin; + +/** + * The pin connected to Corner #1 + * @returns {Pin} + * @url http://www.espruino.com/Reference#l__global_CORNER1 + */ +declare const CORNER1: Pin; + +/** + * The pin connected to Corner #2 + * @returns {Pin} + * @url http://www.espruino.com/Reference#l__global_CORNER2 + */ +declare const CORNER2: Pin; + +/** + * The pin connected to Corner #3 + * @returns {Pin} + * @url http://www.espruino.com/Reference#l__global_CORNER3 + */ +declare const CORNER3: Pin; + +/** + * The pin connected to Corner #4 + * @returns {Pin} + * @url http://www.espruino.com/Reference#l__global_CORNER4 + */ +declare const CORNER4: Pin; + +/** + * The pin connected to Corner #5 + * @returns {Pin} + * @url http://www.espruino.com/Reference#l__global_CORNER5 + */ +declare const CORNER5: Pin; + +/** + * The pin connected to Corner #6 + * @returns {Pin} + * @url http://www.espruino.com/Reference#l__global_CORNER6 + */ +declare const CORNER6: Pin; + +/** + * On Puck.js V2 (not v1.0) this is the pin that controls the FET, for high-powered + * outputs. + * @returns {Pin} + * @url http://www.espruino.com/Reference#l__global_FET + */ +declare const FET: Pin; + +/** + * The pin marked SDA on the Arduino pin footprint. This is connected directly to + * pin A4. + * @returns {Pin} + * @url http://www.espruino.com/Reference#l__global_SDA + */ +declare const SDA: Pin; + +/** + * The pin marked SDA on the Arduino pin footprint. This is connected directly to + * pin A5. + * @returns {Pin} + * @url http://www.espruino.com/Reference#l__global_SCL + */ +declare const SCL: Pin; + +/** + * The Bangle.js's vibration motor. + * @returns {Pin} + * @url http://www.espruino.com/Reference#l__global_VIBRATE + */ +declare const VIBRATE: Pin; + +/** + * On most Espruino board there are LEDs, in which case `LED` will be an actual + * Pin. + * On Bangle.js there are no LEDs, so to remain compatible with example code that + * might expect an LED, this is an object that behaves like a pin, but which just + * displays a circle on the display + * @returns {any} A `Pin` object for a fake LED which appears on + * @url http://www.espruino.com/Reference#l__global_LED + */ +declare const LED: any; + +/** + * On most Espruino board there are LEDs, in which case `LED1` will be an actual + * Pin. + * On Bangle.js there are no LEDs, so to remain compatible with example code that + * might expect an LED, this is an object that behaves like a pin, but which just + * displays a circle on the display + * @returns {any} A `Pin` object for a fake LED which appears on + * @url http://www.espruino.com/Reference#l__global_LED1 + */ +declare const LED1: any; + +/** + * On most Espruino board there are LEDs, in which case `LED2` will be an actual + * Pin. + * On Bangle.js there are no LEDs, so to remain compatible with example code that + * might expect an LED, this is an object that behaves like a pin, but which just + * displays a circle on the display + * @returns {any} A `Pin` object for a fake LED which appears on + * @url http://www.espruino.com/Reference#l__global_LED2 + */ +declare const LED2: any; + +/** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l__global_MOS1 + */ +declare const MOS1: Pin; + +/** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l__global_MOS2 + */ +declare const MOS2: Pin; + +/** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l__global_MOS3 + */ +declare const MOS3: Pin; + +/** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l__global_MOS4 + */ +declare const MOS4: Pin; + +/** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l__global_IOEXT0 + */ +declare const IOEXT0: Pin; + +/** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l__global_IOEXT1 + */ +declare const IOEXT1: Pin; + +/** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l__global_IOEXT2 + */ +declare const IOEXT2: Pin; + +/** + * @returns {Pin} A Pin + * @url http://www.espruino.com/Reference#l__global_IOEXT3 + */ +declare const IOEXT3: Pin; + +/** + * @returns {number} Not a Number + * @url http://www.espruino.com/Reference#l__global_NaN + */ +declare const NaN: number; + +/** + * @returns {number} Positive Infinity (1/0) + * @url http://www.espruino.com/Reference#l__global_Infinity + */ +declare const Infinity: number; + +/** + * @returns {number} Logic 1 for Arduino compatibility - this is the same as just typing `1` + * @url http://www.espruino.com/Reference#l__global_HIGH + */ +declare const HIGH: 1; + +/** + * @returns {number} Logic 0 for Arduino compatibility - this is the same as just typing `0` + * @url http://www.espruino.com/Reference#l__global_LOW + */ +declare const LOW: 0; + +/** + * A variable containing the arguments given to the function: + * ``` + * function hello() { + * console.log(arguments.length, JSON.stringify(arguments)); + * } + * hello() // 0 [] + * hello("Test") // 1 ["Test"] + * hello(1,2,3) // 3 [1,2,3] + * ``` + * **Note:** Due to the way Espruino works this is doesn't behave exactly the same + * as in normal JavaScript. The length of the arguments array will never be less + * than the number of arguments specified in the function declaration: + * `(function(a){ return arguments.length; })() == 1`. Normal JavaScript + * interpreters would return `0` in the above case. + * @returns {any} An array containing all the arguments given to the function + * @url http://www.espruino.com/Reference#l__global_arguments + */ +declare const arguments: any; + +/** + * Evaluate a string containing JavaScript code + * + * @param {any} code + * @returns {any} The result of evaluating the string + * @url http://www.espruino.com/Reference#l__global_eval + */ +declare function eval(code: any): any; + +/** + * Convert a string representing a number into an integer + * + * @param {any} string + * @param {any} radix - The Radix of the string (optional) + * @returns {any} The integer value of the string (or NaN) + * @url http://www.espruino.com/Reference#l__global_parseInt + */ +declare function parseInt(string: any, radix: any): any; + +/** + * Convert a string representing a number into an float + * + * @param {any} string + * @returns {number} The value of the string + * @url http://www.espruino.com/Reference#l__global_parseFloat + */ +declare function parseFloat(string: any): number; + +/** + * Is the parameter a finite num,ber or not? If needed, the parameter is first + * converted to a number. + * + * @param {any} x + * @returns {boolean} True is the value is a Finite number, false if not. + * @url http://www.espruino.com/Reference#l__global_isFinite + */ +declare function isFinite(x: any): boolean; + +/** + * Whether the x is NaN (Not a Number) or not + * + * @param {any} x + * @returns {boolean} True is the value is NaN, false if not. + * @url http://www.espruino.com/Reference#l__global_isNaN + */ +declare function isNaN(x: any): boolean; + +/** + * Encode the supplied string (or array) into a base64 string + * + * @param {any} binaryData - A string of data to encode + * @returns {any} A base64 encoded string + * @url http://www.espruino.com/Reference#l__global_btoa + */ +declare function btoa(binaryData: any): any; + +/** + * Decode the supplied base64 string into a normal string + * + * @param {any} base64Data - A string of base64 data to decode + * @returns {any} A string containing the decoded data + * @url http://www.espruino.com/Reference#l__global_atob + */ +declare function atob(base64Data: any): any; + +/** + * Convert a string with any character not alphanumeric or `- _ . ! ~ * ' ( )` + * converted to the form `%XY` where `XY` is its hexadecimal representation + * + * @param {any} str - A string to encode as a URI + * @returns {any} A string containing the encoded data + * @url http://www.espruino.com/Reference#l__global_encodeURIComponent + */ +declare function encodeURIComponent(str: any): any; + +/** + * Convert any groups of characters of the form '%ZZ', into characters with hex + * code '0xZZ' + * + * @param {any} str - A string to decode from a URI + * @returns {any} A string containing the decoded data + * @url http://www.espruino.com/Reference#l__global_decodeURIComponent + */ +declare function decodeURIComponent(str: any): any; + +/** + * Load the given module, and return the exported functions and variables. + * For example: + * ``` + * var s = require("Storage"); + * s.write("test", "hello world"); + * print(s.read("test")); + * // prints "hello world" + * ``` + * Check out [the page on Modules](/Modules) for an explanation of what modules are + * and how you can use them. + * + * @param {any} moduleName - A String containing the name of the given module + * @returns {any} The result of evaluating the string + * @url http://www.espruino.com/Reference#l__global_require + */ +declare function require(moduleName: T): Libraries[T]; +declare function require>(moduleName: T): any; + +/** + * Read 8 bits of memory at the given location - DANGEROUS! + * + * @param {number} addr - The address in memory to read + * @param {number} count - (optional) the number of items to read. If >1 a Uint8Array will be returned. + * @returns {any} The value of memory at the given location + * @url http://www.espruino.com/Reference#l__global_peek8 + */ +declare function peek8(addr: number, count?: 1): number; +declare function peek8(addr: number, count: number): Uint8Array; + +/** + * Write 8 bits of memory at the given location - VERY DANGEROUS! + * + * @param {number} addr - The address in memory to write + * @param {any} value - The value to write, or an array of values + * @url http://www.espruino.com/Reference#l__global_poke8 + */ +declare function poke8(addr: number, value: number | number[]): void; + +/** + * Read 16 bits of memory at the given location - DANGEROUS! + * + * @param {number} addr - The address in memory to read + * @param {number} count - (optional) the number of items to read. If >1 a Uint16Array will be returned. + * @returns {any} The value of memory at the given location + * @url http://www.espruino.com/Reference#l__global_peek16 + */ +declare function peek16(addr: number, count?: 1): number; +declare function peek16(addr: number, count: number): Uint8Array; + +/** + * Write 16 bits of memory at the given location - VERY DANGEROUS! + * + * @param {number} addr - The address in memory to write + * @param {any} value - The value to write, or an array of values + * @url http://www.espruino.com/Reference#l__global_poke16 + */ +declare function poke16(addr: number, value: number | number[]): void; + +/** + * Read 32 bits of memory at the given location - DANGEROUS! + * + * @param {number} addr - The address in memory to read + * @param {number} count - (optional) the number of items to read. If >1 a Uint32Array will be returned. + * @returns {any} The value of memory at the given location + * @url http://www.espruino.com/Reference#l__global_peek32 + */ +declare function peek32(addr: number, count?: 1): number; +declare function peek32(addr: number, count: number): Uint8Array; + +/** + * Write 32 bits of memory at the given location - VERY DANGEROUS! + * + * @param {number} addr - The address in memory to write + * @param {any} value - The value to write, or an array of values + * @url http://www.espruino.com/Reference#l__global_poke32 + */ +declare function poke32(addr: number, value: number | number[]): void; + +/** + * Get the analog value of the given pin + * This is different to Arduino which only returns an integer between 0 and 1023 + * However only pins connected to an ADC will work (see the datasheet) + * **Note:** if you didn't call `pinMode` beforehand then this function will also + * reset pin's state to `"analog"` + * + * @param {Pin} pin + * The pin to use + * You can find out which pins to use by looking at [your board's reference page](#boards) and searching for pins with the `ADC` markers. + * @returns {number} The analog Value of the Pin between 0 and 1 + * @url http://www.espruino.com/Reference#l__global_analogRead + */ +declare function analogRead(pin: Pin): number; + +/** + * Set the analog Value of a pin. It will be output using PWM. + * Objects can contain: + * * `freq` - pulse frequency in Hz, eg. ```analogWrite(A0,0.5,{ freq : 10 });``` - + * specifying a frequency will force PWM output, even if the pin has a DAC + * * `soft` - boolean, If true software PWM is used if hardware is not available. + * * `forceSoft` - boolean, If true software PWM is used even if hardware PWM or a + * DAC is available + * **Note:** if you didn't call `pinMode` beforehand then this function will also + * reset pin's state to `"output"` + * + * @param {Pin} pin + * The pin to use + * You can find out which pins to use by looking at [your board's reference page](#boards) and searching for pins with the `PWM` or `DAC` markers. + * @param {number} value - A value between 0 and 1 + * @param {any} options + * An object containing options for analog output - see below + * @url http://www.espruino.com/Reference#l__global_analogWrite + */ +declare function analogWrite(pin: Pin, value: number, options?: { freq?: number, soft?: boolean, forceSoft?: boolean }): void; + +/** + * Pulse the pin with the value for the given time in milliseconds. It uses a + * hardware timer to produce accurate pulses, and returns immediately (before the + * pulse has finished). Use `digitalPulse(A0,1,0)` to wait until a previous pulse + * has finished. + * eg. `digitalPulse(A0,1,5);` pulses A0 high for 5ms. + * `digitalPulse(A0,1,[5,2,4]);` pulses A0 high for 5ms, low for 2ms, and high for + * 4ms + * **Note:** if you didn't call `pinMode` beforehand then this function will also + * reset pin's state to `"output"` + * digitalPulse is for SHORT pulses that need to be very accurate. If you're doing + * anything over a few milliseconds, use setTimeout instead. + * + * @param {Pin} pin - The pin to use + * @param {boolean} value - Whether to pulse high (true) or low (false) + * @param {any} time - A time in milliseconds, or an array of times (in which case a square wave will be output starting with a pulse of 'value') + * @url http://www.espruino.com/Reference#l__global_digitalPulse + */ +declare function digitalPulse(pin: Pin, value: boolean, time: number | number[]): void; + +/** + * Set the digital value of the given pin. + * **Note:** if you didn't call `pinMode` beforehand then this function will also + * reset pin's state to `"output"` + * If pin argument is an array of pins (eg. `[A2,A1,A0]`) the value argument will + * be treated as an array of bits where the last array element is the least + * significant bit. + * In this case, pin values are set least significant bit first (from the + * right-hand side of the array of pins). This means you can use the same pin + * multiple times, for example `digitalWrite([A1,A1,A0,A0],0b0101)` would pulse A0 + * followed by A1. + * If the pin argument is an object with a `write` method, the `write` method will + * be called with the value passed through. + * + * @param {any} pin - The pin to use + * @param {number} value - Whether to pulse high (true) or low (false) + * @url http://www.espruino.com/Reference#l__global_digitalWrite + */ +declare function digitalWrite(pin: Pin, value: typeof HIGH | typeof LOW): void; + +/** + * Get the digital value of the given pin. + * **Note:** if you didn't call `pinMode` beforehand then this function will also + * reset pin's state to `"input"` + * If the pin argument is an array of pins (eg. `[A2,A1,A0]`) the value returned + * will be an number where the last array element is the least significant bit, for + * example if `A0=A1=1` and `A2=0`, `digitalRead([A2,A1,A0]) == 0b011` + * If the pin argument is an object with a `read` method, the `read` method will be + * called and the integer value it returns passed back. + * + * @param {any} pin - The pin to use + * @returns {number} The digital Value of the Pin + * @url http://www.espruino.com/Reference#l__global_digitalRead + */ +declare function digitalRead(pin: Pin): number; + +/** + * Set the mode of the given pin. + * * `auto`/`undefined` - Don't change state, but allow `digitalWrite`/etc to + * automatically change state as appropriate + * * `analog` - Analog input + * * `input` - Digital input + * * `input_pullup` - Digital input with internal ~40k pull-up resistor + * * `input_pulldown` - Digital input with internal ~40k pull-down resistor + * * `output` - Digital output + * * `opendrain` - Digital output that only ever pulls down to 0v. Sending a + * logical `1` leaves the pin open circuit + * * `opendrain_pullup` - Digital output that pulls down to 0v. Sending a logical + * `1` enables internal ~40k pull-up resistor + * * `af_output` - Digital output from built-in peripheral + * * `af_opendrain` - Digital output from built-in peripheral that only ever pulls + * down to 0v. Sending a logical `1` leaves the pin open circuit + * **Note:** `digitalRead`/`digitalWrite`/etc set the pin mode automatically + * *unless* `pinMode` has been called first. If you want `digitalRead`/etc to set + * the pin mode automatically after you have called `pinMode`, simply call it again + * with no mode argument (`pinMode(pin)`), `auto` as the argument (`pinMode(pin, + * "auto")`), or with the 3rd 'automatic' argument set to true (`pinMode(pin, + * "output", true)`). + * + * @param {Pin} pin - The pin to set pin mode for + * @param {any} mode - The mode - a string that is either 'analog', 'input', 'input_pullup', 'input_pulldown', 'output', 'opendrain', 'af_output' or 'af_opendrain'. Do not include this argument or use 'auto' if you want to revert to automatic pin mode setting. + * @param {boolean} automatic - Optional, default is false. If true, subsequent commands will automatically change the state (see notes below) + * @url http://www.espruino.com/Reference#l__global_pinMode + */ +declare function pinMode(pin: Pin, mode?: PinMode | "auto", automatic?: boolean): void; + +/** + * Return the current mode of the given pin. See `pinMode` for more information on + * returned values. + * + * @param {Pin} pin - The pin to check + * @returns {any} The pin mode, as a string + * @url http://www.espruino.com/Reference#l__global_getPinMode + */ +declare function getPinMode(pin: Pin): PinMode; + +/** + * Shift an array of data out using the pins supplied *least significant bit + * first*, for example: + * ``` + * // shift out to single clk+data + * shiftOut(A0, { clk : A1 }, [1,0,1,0]); + * ``` + * ``` + * // shift out a whole byte (like software SPI) + * shiftOut(A0, { clk : A1, repeat: 8 }, [1,2,3,4]); + * ``` + * ``` + * // shift out via 4 data pins + * shiftOut([A3,A2,A1,A0], { clk : A4 }, [1,2,3,4]); + * ``` + * `options` is an object of the form: + * ``` + * { + * clk : pin, // a pin to use as the clock (undefined = no pin) + * clkPol : bool, // clock polarity - default is 0 (so 1 normally, pulsing to 0 to clock data in) + * repeat : int, // number of clocks per array item + * } + * ``` + * Each item in the `data` array will be output to the pins, with the first pin in + * the array being the MSB and the last the LSB, then the clock will be pulsed in + * the polarity given. + * `repeat` is the amount of times shift data out for each array item. For instance + * we may want to shift 8 bits out through 2 pins - in which case we need to set + * repeat to 4. + * + * @param {any} pins - A pin, or an array of pins to use + * @param {any} options - Options, for instance the clock (see below) + * @param {any} data - The data to shift out (see `E.toUint8Array` for info on the forms this can take) + * @url http://www.espruino.com/Reference#l__global_shiftOut + */ +declare function shiftOut(pins: Pin | Pin[], options: { clk?: Pin, clkPol?: boolean, repeat?: number }, data: Uint8ArrayResolvable): void; + +/** + * Call the function specified when the pin changes. Watches set with `setWatch` + * can be removed using `clearWatch`. + * If the `options` parameter is an object, it can contain the following + * information (all optional): + * ``` + * { + * // Whether to keep producing callbacks, or remove the watch after the first callback + * repeat: true/false(default), + * // Trigger on the rising or falling edge of the signal. Can be a string, or 1='rising', -1='falling', 0='both' + * edge:'rising'(default for built-in buttons)/'falling'/'both'(default for pins), + * // Use software-debouncing to stop multiple calls if a switch bounces + * // This is the time in milliseconds to wait for bounces to subside, or 0 to disable + * debounce:10 (0 is default for pins, 25 is default for built-in buttons), + * // Advanced: If the function supplied is a 'native' function (compiled or assembly) + * // setting irq:true will call that function in the interrupt itself + * irq : false(default) + * // Advanced: If specified, the given pin will be read whenever the watch is called + * // and the state will be included as a 'data' field in the callback + * data : pin + * // Advanced: On Nordic devices, a watch may be 'high' or 'low' accuracy. By default low + * // accuracy is used (which is better for power consumption), but this means that + * // high speed pulses (less than 25us) may not be reliably received. Setting hispeed=true + * // allows for detecting high speed pulses at the expense of higher idle power consumption + * hispeed : true + * } + * ``` + * The `function` callback is called with an argument, which is an object of type + * `{state:bool, time:float, lastTime:float}`. + * * `state` is whether the pin is currently a `1` or a `0` + * * `time` is the time in seconds at which the pin changed state + * * `lastTime` is the time in seconds at which the **pin last changed state**. + * When using `edge:'rising'` or `edge:'falling'`, this is not the same as when + * the function was last called. + * * `data` is included if `data:pin` was specified in the options, and can be + * used for reading in clocked data + * For instance, if you want to measure the length of a positive pulse you could + * use `setWatch(function(e) { console.log(e.time-e.lastTime); }, BTN, { + * repeat:true, edge:'falling' });`. This will only be called on the falling edge + * of the pulse, but will be able to measure the width of the pulse because + * `e.lastTime` is the time of the rising edge. + * Internally, an interrupt writes the time of the pin's state change into a queue + * with the exact time that it happened, and the function supplied to `setWatch` is + * executed only from the main message loop. However, if the callback is a native + * function `void (bool state)` then you can add `irq:true` to options, which will + * cause the function to be called from within the IRQ. When doing this, interrupts + * will happen on both edges and there will be no debouncing. + * **Note:** if you didn't call `pinMode` beforehand then this function will reset + * pin's state to `"input"` + * **Note:** The STM32 chip (used in the [Espruino Board](/EspruinoBoard) and + * [Pico](/Pico)) cannot watch two pins with the same number - eg `A0` and `B0`. + * **Note:** On nRF52 chips (used in Puck.js, Pixl.js, MDBT42Q) `setWatch` disables + * the GPIO output on that pin. In order to be able to write to the pin again you + * need to disable the watch with `clearWatch`. + * + * @param {any} function - A Function or String to be executed + * @param {Pin} pin - The pin to watch + * @param {any} options - If a boolean or integer, it determines whether to call this once (false = default) or every time a change occurs (true). Can be an object of the form `{ repeat: true/false(default), edge:'rising'/'falling'/'both'(default), debounce:10}` - see below for more information. + * @returns {any} An ID that can be passed to clearWatch + * @url http://www.espruino.com/Reference#l__global_setWatch + */ +declare function setWatch(func: ((arg: { state: boolean, time: number, lastTime: number }) => void) | string, pin: Pin, options?: boolean | { repeat?: boolean, edge?: "rising" | "falling" | "both", debounce?: number, irq?: boolean, data?: Pin, hispeed?: boolean }): number; + +/** + * Clear the Watch that was created with setWatch. If no parameter is supplied, all watches will be removed. + * To avoid accidentally deleting all Watches, if a parameter is supplied but is `undefined` then an Exception will be thrown. + * + * @param {any} id - The id returned by a previous call to setWatch. **Only one argument is allowed.** + * @url http://www.espruino.com/Reference#l__global_clearWatch + */ +declare function clearWatch(id: number): void; + +declare const global: { + show: typeof show; + acceleration: typeof acceleration; + compass: typeof compass; + BTNA: typeof BTNA; + BTNB: typeof BTNB; + BTNU: typeof BTNU; + BTND: typeof BTND; + BTNL: typeof BTNL; + BTNR: typeof BTNR; + CORNER1: typeof CORNER1; + CORNER2: typeof CORNER2; + CORNER3: typeof CORNER3; + CORNER4: typeof CORNER4; + CORNER5: typeof CORNER5; + CORNER6: typeof CORNER6; + FET: typeof FET; + SDA: typeof SDA; + SCL: typeof SCL; + VIBRATE: typeof VIBRATE; + LED: typeof LED; + LED1: typeof LED1; + LED2: typeof LED2; + MOS1: typeof MOS1; + MOS2: typeof MOS2; + MOS3: typeof MOS3; + MOS4: typeof MOS4; + IOEXT0: typeof IOEXT0; + IOEXT1: typeof IOEXT1; + IOEXT2: typeof IOEXT2; + IOEXT3: typeof IOEXT3; + NaN: typeof NaN; + Infinity: typeof Infinity; + HIGH: typeof HIGH; + LOW: typeof LOW; + arguments: typeof arguments; + eval: typeof eval; + parseInt: typeof parseInt; + parseFloat: typeof parseFloat; + isFinite: typeof isFinite; + isNaN: typeof isNaN; + btoa: typeof btoa; + atob: typeof atob; + encodeURIComponent: typeof encodeURIComponent; + decodeURIComponent: typeof decodeURIComponent; + require: typeof require; + peek8: typeof peek8; + poke8: typeof poke8; + peek16: typeof peek16; + poke16: typeof poke16; + peek32: typeof peek32; + poke32: typeof poke32; + analogRead: typeof analogRead; + analogWrite: typeof analogWrite; + digitalPulse: typeof digitalPulse; + digitalWrite: typeof digitalWrite; + digitalRead: typeof digitalRead; + pinMode: typeof pinMode; + getPinMode: typeof getPinMode; + shiftOut: typeof shiftOut; + setWatch: typeof setWatch; + clearWatch: typeof clearWatch; + global: typeof global; + setBusyIndicator: typeof setBusyIndicator; + setSleepIndicator: typeof setSleepIndicator; + setDeepSleep: typeof setDeepSleep; + trace: typeof trace; + dump: typeof dump; + load: typeof load; + save: typeof save; + reset: typeof reset; + print: typeof print; + edit: typeof edit; + echo: typeof echo; + getTime: typeof getTime; + setTime: typeof setTime; + getSerial: typeof getSerial; + setInterval: typeof setInterval; + setTimeout: typeof setTimeout; + clearInterval: typeof clearInterval; + clearTimeout: typeof clearTimeout; + changeInterval: typeof changeInterval; + [key: string]: any; +} + +/** + * When Espruino is busy, set the pin specified here high. Set this to undefined to + * disable the feature. + * + * @param {any} pin + * @url http://www.espruino.com/Reference#l__global_setBusyIndicator + */ +declare function setBusyIndicator(pin: any): void; + +/** + * When Espruino is asleep, set the pin specified here low (when it's awake, set it + * high). Set this to undefined to disable the feature. + * Please see http://www.espruino.com/Power+Consumption for more details on this. + * + * @param {any} pin + * @url http://www.espruino.com/Reference#l__global_setSleepIndicator + */ +declare function setSleepIndicator(pin: any): void; + +/** + * Set whether we can enter deep sleep mode, which reduces power consumption to + * around 100uA. This only works on STM32 Espruino Boards (nRF52 boards sleep + * automatically). + * Please see http://www.espruino.com/Power+Consumption for more details on this. + * + * @param {boolean} sleep + * @url http://www.espruino.com/Reference#l__global_setDeepSleep + */ +declare function setDeepSleep(sleep: boolean): void; + +/** + * Output debugging information + * Note: This is not included on boards with low amounts of flash memory, or the + * Espruino board. + * + * @param {any} root - The symbol to output (optional). If nothing is specified, everything will be output + * @url http://www.espruino.com/Reference#l__global_trace + */ +declare function trace(root: any): void; + +/** + * Output current interpreter state in a text form such that it can be copied to a + * new device + * Espruino keeps its current state in RAM (even if the function code is stored in + * Flash). When you type `dump()` it dumps the current state of code in RAM plus + * the hardware state, then if there's code saved in flash it writes "// Code saved + * with E.setBootCode" and dumps that too. + * **Note:** 'Internal' functions are currently not handled correctly. You will + * need to recreate these in the `onInit` function. + * @url http://www.espruino.com/Reference#l__global_dump + */ +declare function dump(): void; + +/** + * Restart and load the program out of flash - this has an effect similar to + * completely rebooting Espruino (power off/power on), but without actually + * performing a full reset of the hardware. + * This command only executes when the Interpreter returns to the Idle state - for + * instance ```a=1;load();a=2;``` will still leave 'a' as undefined (or what it was + * set to in the saved program). + * Espruino will resume from where it was when you last typed `save()`. If you want + * code to be executed right after loading (for instance to initialise devices + * connected to Espruino), add an `init` event handler to `E` with `E.on('init', + * function() { ... your_code ... });`. This will then be automatically executed by + * Espruino every time it starts. + * **If you specify a filename in the argument then that file will be loaded from + * Storage after reset** in much the same way as calling `reset()` then + * `eval(require("Storage").read(filename))` + * + * @param {any} filename - optional: The name of a text JS file to load from Storage after reset + * @url http://www.espruino.com/Reference#l__global_load + */ +declare function load(filename: any): void; + +/** + * Save the state of the interpreter into flash (including the results of calling + * `setWatch`, `setInterval`, `pinMode`, and any listeners). The state will then be + * loaded automatically every time Espruino powers on or is hard-reset. To see what + * will get saved you can call `dump()`. + * **Note:** If you set up intervals/etc in `onInit()` and you have already called + * `onInit` before running `save()`, when Espruino resumes there will be two copies + * of your intervals - the ones from before the save, and the ones from after - + * which may cause you problems. + * For more information about this and other options for saving, please see the + * [Saving code on Espruino](https://www.espruino.com/Saving) page. + * This command only executes when the Interpreter returns to the Idle state - for + * instance ```a=1;save();a=2;``` will save 'a' as 2. + * When Espruino powers on, it will resume from where it was when you typed + * `save()`. If you want code to be executed right after loading (for instance to + * initialise devices connected to Espruino), add a function called `onInit`, or + * add a `init` event handler to `E` with `E.on('init', function() { ... your_code + * ... });`. This will then be automatically executed by Espruino every time it + * starts. + * In order to stop the program saved with this command being loaded automatically, + * check out [the Troubleshooting + * guide](https://www.espruino.com/Troubleshooting#espruino-stopped-working-after-i-typed-save-) + * @url http://www.espruino.com/Reference#l__global_save + */ +declare function save(): void; + +/** + * Reset the interpreter - clear program memory in RAM, and do not load a saved + * program from flash. This does NOT reset the underlying hardware (which allows + * you to reset the device without it disconnecting from USB). + * This command only executes when the Interpreter returns to the Idle state - for + * instance ```a=1;reset();a=2;``` will still leave 'a' as undefined. + * The safest way to do a full reset is to hit the reset button. + * If `reset()` is called with no arguments, it will reset the board's state in RAM + * but will not reset the state in flash. When next powered on (or when `load()` is + * called) the board will load the previously saved code. + * Calling `reset(true)` will cause *all saved code in flash memory to be cleared + * as well*. + * + * @param {boolean} clearFlash - Remove saved code from flash as well + * @url http://www.espruino.com/Reference#l__global_reset + */ +declare function reset(clearFlash: boolean): void; + +/** + * Print the supplied string(s) to the console + * **Note:** If you're connected to a computer (not a wall adaptor) via USB but + * **you are not running a terminal app** then when you print data Espruino may + * pause execution and wait until the computer requests the data it is trying to + * print. + * + * @param {any} text + * @url http://www.espruino.com/Reference#l__global_print + */ +declare function print(...text: any[]): void; + +/** + * Fill the console with the contents of the given function, so you can edit it. + * NOTE: This is a convenience function - it will not edit 'inner functions'. For + * that, you must edit the 'outer function' and re-execute it. + * + * @param {any} funcName - The name of the function to edit (either a string or just the unquoted name) + * @url http://www.espruino.com/Reference#l__global_edit + */ +declare function edit(funcName: any): void; + +/** + * Should Espruino echo what you type back to you? true = yes (Default), false = + * no. When echo is off, the result of executing a command is not returned. + * Instead, you must use 'print' to send output. + * + * @param {boolean} echoOn + * @url http://www.espruino.com/Reference#l__global_echo + */ +declare function echo(echoOn: boolean): void; + +/** + * Return the current system time in Seconds (as a floating point number) + * @returns {number} + * @url http://www.espruino.com/Reference#l__global_getTime + */ +declare function getTime(): number; + +/** + * Set the current system time in seconds (`time` can be a floating point value). + * This is used with `getTime`, the time reported from `setWatch`, as well as when + * using `new Date()`. + * `Date.prototype.getTime()` reports the time in milliseconds, so you can set the + * time to a `Date` object using: + * ``` + * setTime((new Date("Tue, 19 Feb 2019 10:57")).getTime()/1000) + * ``` + * To set the timezone for all new Dates, use `E.setTimeZone(hours)`. + * + * @param {number} time + * @url http://www.espruino.com/Reference#l__global_setTime + */ +declare function setTime(time: number): void; + +/** + * Get the serial number of this board + * @returns {any} The board's serial number + * @url http://www.espruino.com/Reference#l__global_getSerial + */ +declare function getSerial(): any; + +/** + * Call the function (or evaluate the string) specified REPEATEDLY after the + * timeout in milliseconds. + * For instance: + * ``` + * setInterval(function () { + * console.log("Hello World"); + * }, 1000); + * // or + * setInterval('console.log("Hello World");', 1000); + * // both print 'Hello World' every second + * ``` + * You can also specify extra arguments that will be sent to the function when it + * is executed. For example: + * ``` + * setInterval(function (a,b) { + * console.log(a+" "+b); + * }, 1000, "Hello", "World"); + * // prints 'Hello World' every second + * ``` + * If you want to stop your function from being called, pass the number that was + * returned by `setInterval` into the `clearInterval` function. + * **Note:** If `setDeepSleep(true)` has been called and the interval is greater + * than 5 seconds, Espruino may execute the interval up to 1 second late. This is + * because Espruino can only wake from deep sleep every second - and waking early + * would cause Espruino to waste power while it waited for the correct time. + * + * @param {any} function - A Function or String to be executed + * @param {number} timeout - The time between calls to the function (max 3153600000000 = 100 years + * @param {any} args - Optional arguments to pass to the function when executed + * @returns {any} An ID that can be passed to clearInterval + * @url http://www.espruino.com/Reference#l__global_setInterval + */ +declare function setInterval(func: any, timeout: number, ...args: any[]): any; + +/** + * Call the function (or evaluate the string) specified ONCE after the timeout in + * milliseconds. + * For instance: + * ``` + * setTimeout(function () { + * console.log("Hello World"); + * }, 1000); + * // or + * setTimeout('console.log("Hello World");', 1000); + * // both print 'Hello World' after a second + * ``` + * You can also specify extra arguments that will be sent to the function when it + * is executed. For example: + * ``` + * setTimeout(function (a,b) { + * console.log(a+" "+b); + * }, 1000, "Hello", "World"); + * // prints 'Hello World' after 1 second + * ``` + * If you want to stop the function from being called, pass the number that was + * returned by `setTimeout` into the `clearTimeout` function. + * **Note:** If `setDeepSleep(true)` has been called and the interval is greater + * than 5 seconds, Espruino may execute the interval up to 1 second late. This is + * because Espruino can only wake from deep sleep every second - and waking early + * would cause Espruino to waste power while it waited for the correct time. + * + * @param {any} function - A Function or String to be executed + * @param {number} timeout - The time until the function will be executed (max 3153600000000 = 100 years + * @param {any} args - Optional arguments to pass to the function when executed + * @returns {any} An ID that can be passed to clearTimeout + * @url http://www.espruino.com/Reference#l__global_setTimeout + */ +declare function setTimeout(func: any, timeout: number, ...args: any[]): any; + +/** + * Clear the Interval that was created with `setInterval`, for example: + * ```var id = setInterval(function () { print('foo'); }, 1000);``` + * ```clearInterval(id);``` + * If no argument is supplied, all timeouts and intervals are stopped. + * To avoid accidentally deleting all Intervals, if a parameter is supplied but is `undefined` then an Exception will be thrown. + * + * @param {any} id - The id returned by a previous call to setInterval. **Only one argument is allowed.** + * @url http://www.espruino.com/Reference#l__global_clearInterval + */ +declare function clearInterval(...id: any[]): void; + +/** + * Clear the Timeout that was created with `setTimeout`, for example: + * ```var id = setTimeout(function () { print('foo'); }, 1000);``` + * ```clearTimeout(id);``` + * If no argument is supplied, all timeouts and intervals are stopped. + * To avoid accidentally deleting all Timeouts, if a parameter is supplied but is `undefined` then an Exception will be thrown. + * + * @param {any} id - The id returned by a previous call to setTimeout. **Only one argument is allowed.** + * @url http://www.espruino.com/Reference#l__global_clearTimeout + */ +declare function clearTimeout(...id: any[]): void; + +/** + * Change the Interval on a callback created with `setInterval`, for example: + * ```var id = setInterval(function () { print('foo'); }, 1000); // every second``` + * ```changeInterval(id, 1500); // now runs every 1.5 seconds``` + * This takes effect immediately and resets the timeout, so in the example above, + * regardless of when you call `changeInterval`, the next interval will occur + * 1500ms after it. + * + * @param {any} id - The id returned by a previous call to setInterval + * @param {number} time - The new time period in ms + * @url http://www.espruino.com/Reference#l__global_changeInterval + */ +declare function changeInterval(id: any, time: number): void; + +// LIBRARIES + +type Libraries = { + /** + * @url http://www.espruino.com/Reference#tensorflow + */ + tensorflow: { + /** + * + * @param {number} arenaSize - The TensorFlow Arena size + * @param {any} model - The model to use - this should be a flat array/string + * @returns {any} A tensorflow instance + * @url http://www.espruino.com/Reference#l_tensorflow_create + */ + create(arenaSize: number, model: any): TFMicroInterpreter; + } + + /** + * This library handles interfacing with a FAT32 filesystem on an SD card. The API + * is designed to be similar to node.js's - However Espruino does not currently + * support asynchronous file IO, so the functions behave like node.js's xxxxSync + * functions. Versions of the functions with 'Sync' after them are also provided + * for compatibility. + * To use this, you must type ```var fs = require('fs')``` to get access to the + * library + * See [the page on File IO](http://www.espruino.com/File+IO) for more information, + * and for examples on wiring up an SD card if your device doesn't come with one. + * **Note:** If you want to remove an SD card after you have started using it, you + * *must* call `E.unmountSD()` or you may cause damage to the card. + * @url http://www.espruino.com/Reference#fs + */ + fs: { + /** + * List all files in the supplied directory, returning them as an array of strings. + * NOTE: Espruino does not yet support Async file IO, so this function behaves like + * the 'Sync' version. + * + * @param {any} path - The path of the directory to list. If it is not supplied, '' is assumed, which will list the root directory + * @returns {any} An array of filename strings (or undefined if the directory couldn't be listed) + * @url http://www.espruino.com/Reference#l_fs_readdir + */ + readdir(path: any): any; + + /** + * List all files in the supplied directory, returning them as an array of strings. + * + * @param {any} path - The path of the directory to list. If it is not supplied, '' is assumed, which will list the root directory + * @returns {any} An array of filename strings (or undefined if the directory couldn't be listed) + * @url http://www.espruino.com/Reference#l_fs_readdirSync + */ + readdirSync(path: any): any; + + /** + * Write the data to the given file + * NOTE: Espruino does not yet support Async file IO, so this function behaves like + * the 'Sync' version. + * + * @param {any} path - The path of the file to write + * @param {any} data - The data to write to the file + * @returns {boolean} True on success, false on failure + * @url http://www.espruino.com/Reference#l_fs_writeFile + */ + writeFile(path: any, data: any): boolean; + + /** + * Write the data to the given file + * + * @param {any} path - The path of the file to write + * @param {any} data - The data to write to the file + * @returns {boolean} True on success, false on failure + * @url http://www.espruino.com/Reference#l_fs_writeFileSync + */ + writeFileSync(path: any, data: any): boolean; + + /** + * Append the data to the given file, created a new file if it doesn't exist + * NOTE: Espruino does not yet support Async file IO, so this function behaves like + * the 'Sync' version. + * + * @param {any} path - The path of the file to write + * @param {any} data - The data to write to the file + * @returns {boolean} True on success, false on failure + * @url http://www.espruino.com/Reference#l_fs_appendFile + */ + appendFile(path: any, data: any): boolean; + + /** + * Append the data to the given file, created a new file if it doesn't exist + * + * @param {any} path - The path of the file to write + * @param {any} data - The data to write to the file + * @returns {boolean} True on success, false on failure + * @url http://www.espruino.com/Reference#l_fs_appendFileSync + */ + appendFileSync(path: any, data: any): boolean; + + /** + * Read all data from a file and return as a string + * NOTE: Espruino does not yet support Async file IO, so this function behaves like + * the 'Sync' version. + * + * @param {any} path - The path of the file to read + * @returns {any} A string containing the contents of the file (or undefined if the file doesn't exist) + * @url http://www.espruino.com/Reference#l_fs_readFile + */ + readFile(path: any): any; + + /** + * Read all data from a file and return as a string. + * **Note:** The size of files you can load using this method is limited by the + * amount of available RAM. To read files a bit at a time, see the `File` class. + * + * @param {any} path - The path of the file to read + * @returns {any} A string containing the contents of the file (or undefined if the file doesn't exist) + * @url http://www.espruino.com/Reference#l_fs_readFileSync + */ + readFileSync(path: any): any; + + /** + * Delete the given file + * NOTE: Espruino does not yet support Async file IO, so this function behaves like + * the 'Sync' version. + * + * @param {any} path - The path of the file to delete + * @returns {boolean} True on success, or false on failure + * @url http://www.espruino.com/Reference#l_fs_unlink + */ + unlink(path: any): boolean; + + /** + * Delete the given file + * + * @param {any} path - The path of the file to delete + * @returns {boolean} True on success, or false on failure + * @url http://www.espruino.com/Reference#l_fs_unlinkSync + */ + unlinkSync(path: any): boolean; + + /** + * Return information on the given file. This returns an object with the following + * fields: + * size: size in bytes dir: a boolean specifying if the file is a directory or not + * mtime: A Date structure specifying the time the file was last modified + * + * @param {any} path - The path of the file to get information on + * @returns {any} An object describing the file, or undefined on failure + * @url http://www.espruino.com/Reference#l_fs_statSync + */ + statSync(path: any): any; + + /** + * Create the directory + * NOTE: Espruino does not yet support Async file IO, so this function behaves like + * the 'Sync' version. + * + * @param {any} path - The name of the directory to create + * @returns {boolean} True on success, or false on failure + * @url http://www.espruino.com/Reference#l_fs_mkdir + */ + mkdir(path: any): boolean; + + /** + * Create the directory + * + * @param {any} path - The name of the directory to create + * @returns {boolean} True on success, or false on failure + * @url http://www.espruino.com/Reference#l_fs_mkdirSync + */ + mkdirSync(path: any): boolean; + + /** + * + * @param {any} source - The source file/stream that will send content. + * @param {any} destination - The destination file/stream that will receive content from the source. + * @param {any} options + * An optional object `{ chunkSize : int=64, end : bool=true, complete : function }` + * chunkSize : The amount of data to pipe from source to destination at a time + * complete : a function to call when the pipe activity is complete + * end : call the 'end' function on the destination when the source is finished + * @url http://www.espruino.com/Reference#l_fs_pipe + */ + pipe(source: any, destination: any, options: any): void; + } + + /** + * Cryptographic functions + * **Note:** This library is currently only included in builds for boards where + * there is space. For other boards there is `crypto.js` which implements SHA1 in + * JS. + * @url http://www.espruino.com/Reference#crypto + */ + crypto: { + /** + * Class containing AES encryption/decryption + * @returns {any} + * @url http://www.espruino.com/Reference#l_crypto_AES + */ + AES: AES; + + /** + * Performs a SHA1 hash and returns the result as a 20 byte ArrayBuffer. + * **Note:** On some boards (currently only Espruino Original) there isn't space + * for a fully unrolled SHA1 implementation so a slower all-JS implementation is + * used instead. + * + * @param {any} message - The message to apply the hash to + * @returns {any} Returns a 20 byte ArrayBuffer + * @url http://www.espruino.com/Reference#l_crypto_SHA1 + */ + SHA1(message: any): ArrayBuffer; + + /** + * Performs a SHA224 hash and returns the result as a 28 byte ArrayBuffer + * + * @param {any} message - The message to apply the hash to + * @returns {any} Returns a 20 byte ArrayBuffer + * @url http://www.espruino.com/Reference#l_crypto_SHA224 + */ + SHA224(message: any): ArrayBuffer; + + /** + * Performs a SHA256 hash and returns the result as a 32 byte ArrayBuffer + * + * @param {any} message - The message to apply the hash to + * @returns {any} Returns a 20 byte ArrayBuffer + * @url http://www.espruino.com/Reference#l_crypto_SHA256 + */ + SHA256(message: any): ArrayBuffer; + + /** + * Performs a SHA384 hash and returns the result as a 48 byte ArrayBuffer + * + * @param {any} message - The message to apply the hash to + * @returns {any} Returns a 20 byte ArrayBuffer + * @url http://www.espruino.com/Reference#l_crypto_SHA384 + */ + SHA384(message: any): ArrayBuffer; + + /** + * Performs a SHA512 hash and returns the result as a 64 byte ArrayBuffer + * + * @param {any} message - The message to apply the hash to + * @returns {any} Returns a 32 byte ArrayBuffer + * @url http://www.espruino.com/Reference#l_crypto_SHA512 + */ + SHA512(message: any): ArrayBuffer; + + /** + * Password-Based Key Derivation Function 2 algorithm, using SHA512 + * + * @param {any} passphrase - Passphrase + * @param {any} salt - Salt for turning passphrase into a key + * @param {any} options - Object of Options, `{ keySize: 8 (in 32 bit words), iterations: 10, hasher: 'SHA1'/'SHA224'/'SHA256'/'SHA384'/'SHA512' }` + * @returns {any} Returns an ArrayBuffer + * @url http://www.espruino.com/Reference#l_crypto_PBKDF2 + */ + PBKDF2(passphrase: any, salt: any, options: any): ArrayBuffer; + } + + /** + * Library that initialises a network device that calls into JavaScript + * @url http://www.espruino.com/Reference#NetworkJS + */ + NetworkJS: { + /** + * Initialise the network using the callbacks given and return the first argument. + * For instance: + * ``` + * require("NetworkJS").create({ + * create : function(host, port, socketType, options) { + * // Create a socket and return its index, host is a string, port is an integer. + * // If host isn't defined, create a server socket + * console.log("Create",host,port); + * return 1; + * }, + * close : function(sckt) { + * // Close the socket. returns nothing + * }, + * accept : function(sckt) { + * // Accept the connection on the server socket. Returns socket number or -1 if no connection + * return -1; + * }, + * recv : function(sckt, maxLen, socketType) { + * // Receive data. Returns a string (even if empty). + * // If non-string returned, socket is then closed + * return null;//or ""; + * }, + * send : function(sckt, data, socketType) { + * // Send data (as string). Returns the number of bytes sent - 0 is ok. + * // Less than 0 + * return data.length; + * } + * }); + * ``` + * `socketType` is an integer - 2 for UDP, or see SocketType in + * https://github.com/espruino/Espruino/blob/master/libs/network/network.h for more + * information. + * + * @param {any} obj - An object containing functions to access the network device + * @returns {any} The object passed in + * @url http://www.espruino.com/Reference#l_NetworkJS_create + */ + create(obj: any): any; + } + + /** + * This library implements a telnet console for the Espruino interpreter. It + * requires a network connection, e.g. Wifi, and **currently only functions on the + * ESP8266 and on Linux **. It uses port 23 on the ESP8266 and port 2323 on Linux. + * **Note:** To enable on Linux, run `./espruino --telnet` + * @url http://www.espruino.com/Reference#TelnetServer + */ + TelnetServer: { + /** + * + * @param {any} options - Options controlling the telnet console server `{ mode : 'on|off'}` + * @url http://www.espruino.com/Reference#l_TelnetServer_setOptions + */ + setOptions(options: any): void; + } + + /** + * This library allows you to create TCPIP servers and clients + * In order to use this, you will need an extra module to get network connectivity. + * This is designed to be a cut-down version of the [node.js + * library](http://nodejs.org/api/net.html). Please see the [Internet](/Internet) + * page for more information on how to use it. + * @url http://www.espruino.com/Reference#net + */ + net: { + /** + * Create a Server + * When a request to the server is made, the callback is called. In the callback + * you can use the methods on the connection to send data. You can also add + * `connection.on('data',function() { ... })` to listen for received data + * + * @param {any} callback - A `function(connection)` that will be called when a connection is made + * @returns {any} Returns a new Server Object + * @url http://www.espruino.com/Reference#l_net_createServer + */ + createServer(callback: any): Server; + + /** + * Create a TCP socket connection + * + * @param {any} options - An object containing host,port fields + * @param {any} callback - A `function(sckt)` that will be called with the socket when a connection is made. You can then call `sckt.write(...)` to send data, and `sckt.on('data', function(data) { ... })` and `sckt.on('close', function() { ... })` to deal with the response. + * @returns {any} Returns a new net.Socket object + * @url http://www.espruino.com/Reference#l_net_connect + */ + connect(options: any, callback: any): Socket; + } + + /** + * This library allows you to create UDP/DATAGRAM servers and clients + * In order to use this, you will need an extra module to get network connectivity. + * This is designed to be a cut-down version of the [node.js + * library](http://nodejs.org/api/dgram.html). Please see the [Internet](/Internet) + * page for more information on how to use it. + * @url http://www.espruino.com/Reference#dgram + */ + dgram: { + /** + * Create a UDP socket + * + * @param {any} type - Socket type to create e.g. 'udp4'. Or options object { type: 'udp4', reuseAddr: true, recvBufferSize: 1024 } + * @param {any} callback - A `function(sckt)` that will be called with the socket when a connection is made. You can then call `sckt.send(...)` to send data, and `sckt.on('message', function(data) { ... })` and `sckt.on('close', function() { ... })` to deal with the response. + * @returns {any} Returns a new dgram.Socket object + * @url http://www.espruino.com/Reference#l_dgram_createSocket + */ + createSocket(type: any, callback: any): dgramSocket; + } + + /** + * This library allows you to create TCPIP servers and clients using TLS encryption + * In order to use this, you will need an extra module to get network connectivity. + * This is designed to be a cut-down version of the [node.js + * library](http://nodejs.org/api/tls.html). Please see the [Internet](/Internet) + * page for more information on how to use it. + * @url http://www.espruino.com/Reference#tls + */ + tls: { + /** + * Create a socket connection using TLS + * Options can have `ca`, `key` and `cert` fields, which should be the decoded + * content of the certificate. + * ``` + * var options = url.parse("localhost:1234"); + * options.key = atob("MIIJKQ ... OZs08C"); + * options.cert = atob("MIIFi ... Uf93rN+"); + * options.ca = atob("MIIFgDCC ... GosQML4sc="); + * require("tls").connect(options, ... ); + * ``` + * If you have the certificates as `.pem` files, you need to load these files, take + * the information between the lines beginning with `----`, remove the newlines + * from it so you have raw base64, and then feed it into `atob` as above. + * You can also: + * * Just specify the filename (<=100 characters) and it will be loaded and parsed + * if you have an SD card connected. For instance `options.key = "key.pem";` + * * Specify a function, which will be called to retrieve the data. For instance + * `options.key = function() { eeprom.load_my_info(); }; + * For more information about generating and using certificates, see: + * https://engineering.circle.com/https-authorized-certs-with-node-js/ + * (You'll need to use 2048 bit certificates as opposed to 4096 bit shown above) + * + * @param {any} options - An object containing host,port fields + * @param {any} callback - A function(res) that will be called when a connection is made. You can then call `res.on('data', function(data) { ... })` and `res.on('close', function() { ... })` to deal with the response. + * @returns {any} Returns a new net.Socket object + * @url http://www.espruino.com/Reference#l_tls_connect + */ + connect(options: any, callback: any): Socket; + } + + /** + * @url http://www.espruino.com/Reference#CC3000 + */ + CC3000: { + /** + * Initialise the CC3000 and return a WLAN object + * + * @param {any} spi - Device to use for SPI (or undefined to use the default). SPI should be 1,000,000 baud, and set to 'mode 1' + * @param {Pin} cs - The pin to use for Chip Select + * @param {Pin} en - The pin to use for Enable + * @param {Pin} irq - The pin to use for Interrupts + * @returns {any} A WLAN Object + * @url http://www.espruino.com/Reference#l_CC3000_connect + */ + connect(spi: any, cs: Pin, en: Pin, irq: Pin): WLAN; + } + + /** + * Library for communication with the WIZnet Ethernet module + * @url http://www.espruino.com/Reference#WIZnet + */ + WIZnet: { + /** + * Initialise the WIZnet module and return an Ethernet object + * + * @param {any} spi - Device to use for SPI (or undefined to use the default) + * @param {Pin} cs - The pin to use for Chip Select + * @returns {any} An Ethernet Object + * @url http://www.espruino.com/Reference#l_WIZnet_connect + */ + connect(spi: any, cs: Pin): Ethernet; + } + + /** + * The Wifi library is designed to control the Wifi interface. It supports + * functionality such as connecting to wifi networks, getting network information, + * starting an access point, etc. + * It is available on these devices: + * * [Espruino WiFi](http://www.espruino.com/WiFi#using-wifi) + * * [ESP8266](http://www.espruino.com/EspruinoESP8266) + * * [ESP32](http://www.espruino.com/ESP32) + * **Certain features may or may not be implemented on your device** however we + * have documented what is available and what isn't. + * If you're not using one of the devices above, a separate WiFi library is + * provided. For instance: + * * An [ESP8266 connected to an Espruino + * board](http://www.espruino.com/ESP8266#software) + * * An [CC3000 WiFi Module](http://www.espruino.com/CC3000) + * [Other ways of connecting to the + * net](http://www.espruino.com/Internet#related-pages) such as GSM, Ethernet and + * LTE have their own libraries. + * You can use the WiFi library as follows: + * ``` + * var wifi = require("Wifi"); + * wifi.connect("my-ssid", {password:"my-pwd"}, function(ap){ console.log("connected:", ap); }); + * ``` + * On ESP32/ESP8266 if you want the connection to happen automatically at boot, add + * `wifi.save();`. On other platforms, place `wifi.connect` in a function called + * `onInit`. + * @url http://www.espruino.com/Reference#Wifi + */ + Wifi: { + /** + * The 'associated' event is called when an association with an access point has + * succeeded, i.e., a connection to the AP's network has been established. + * On ESP32/ESP8266 there is a `details` parameter which includes: + * * ssid - The SSID of the access point to which the association was established + * * mac - The BSSID/mac address of the access point + * * channel - The wifi channel used (an integer, typ 1..14) + * @param {string} event - The event to listen to. + * @param {(details: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `details` An object with event details + * @url http://www.espruino.com/Reference#l_Wifi_associated + */ + on(event: "associated", callback: (details: any) => void): void; + + /** + * The 'disconnected' event is called when an association with an access point has + * been lost. + * On ESP32/ESP8266 there is a `details` parameter which includes: + * * ssid - The SSID of the access point from which the association was lost + * * mac - The BSSID/mac address of the access point + * * reason - The reason for the disconnection (string) + * @param {string} event - The event to listen to. + * @param {(details: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `details` An object with event details + * @url http://www.espruino.com/Reference#l_Wifi_disconnected + */ + on(event: "disconnected", callback: (details: any) => void): void; + + /** + * The 'auth_change' event is called when the authentication mode with the + * associated access point changes. The details include: + * * oldMode - The old auth mode (string: open, wep, wpa, wpa2, wpa_wpa2) + * * newMode - The new auth mode (string: open, wep, wpa, wpa2, wpa_wpa2) + * @param {string} event - The event to listen to. + * @param {(details: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `details` An object with event details + * @url http://www.espruino.com/Reference#l_Wifi_auth_change + */ + on(event: "auth_change", callback: (details: any) => void): void; + + /** + * The 'dhcp_timeout' event is called when a DHCP request to the connected access + * point fails and thus no IP address could be acquired (or renewed). + * @param {string} event - The event to listen to. + * @param {() => void} callback - A function that is executed when the event occurs. + * @url http://www.espruino.com/Reference#l_Wifi_dhcp_timeout + */ + on(event: "dhcp_timeout", callback: () => void): void; + + /** + * The 'connected' event is called when the connection with an access point is + * ready for traffic. In the case of a dynamic IP address configuration this is + * when an IP address is obtained, in the case of static IP address allocation this + * happens when an association is formed (in that case the 'associated' and + * 'connected' events are fired in rapid succession). + * On ESP32/ESP8266 there is a `details` parameter which includes: + * * ip - The IP address obtained as string + * * netmask - The network's IP range mask as string + * * gw - The network's default gateway as string + * @param {string} event - The event to listen to. + * @param {(details: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `details` An object with event details + * @url http://www.espruino.com/Reference#l_Wifi_connected + */ + on(event: "connected", callback: (details: any) => void): void; + + /** + * The 'sta_joined' event is called when a station establishes an association (i.e. + * connects) with the esp8266's access point. The details include: + * * mac - The MAC address of the station in string format (00:00:00:00:00:00) + * @param {string} event - The event to listen to. + * @param {(details: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `details` An object with event details + * @url http://www.espruino.com/Reference#l_Wifi_sta_joined + */ + on(event: "sta_joined", callback: (details: any) => void): void; + + /** + * The 'sta_left' event is called when a station disconnects from the esp8266's + * access point (or its association times out?). The details include: + * * mac - The MAC address of the station in string format (00:00:00:00:00:00) + * @param {string} event - The event to listen to. + * @param {(details: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `details` An object with event details + * @url http://www.espruino.com/Reference#l_Wifi_sta_left + */ + on(event: "sta_left", callback: (details: any) => void): void; + + /** + * The 'probe_recv' event is called when a probe request is received from some + * station by the esp8266's access point. The details include: + * * mac - The MAC address of the station in string format (00:00:00:00:00:00) + * * rssi - The signal strength in dB of the probe request + * @param {string} event - The event to listen to. + * @param {(details: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `details` An object with event details + * @url http://www.espruino.com/Reference#l_Wifi_probe_recv + */ + on(event: "probe_recv", callback: (details: any) => void): void; + + /** + * Disconnect the wifi station from an access point and disable the station mode. + * It is OK to call `disconnect` to turn off station mode even if no connection + * exists (for example, connection attempts may be failing). Station mode can be + * re-enabled by calling `connect` or `scan`. + * + * @param {any} callback - An optional `callback()` function to be called back on disconnection. The callback function receives no argument. + * @url http://www.espruino.com/Reference#l_Wifi_disconnect + */ + disconnect(callback: any): void; + + /** + * Stop being an access point and disable the AP operation mode. AP mode can be + * re-enabled by calling `startAP`. + * + * @param {any} callback - An optional `callback()` function to be called back on successful stop. The callback function receives no argument. + * @url http://www.espruino.com/Reference#l_Wifi_stopAP + */ + stopAP(callback: any): void; + + /** + * Connect to an access point as a station. If there is an existing connection to + * an AP it is first disconnected if the SSID or password are different from those + * passed as parameters. Put differently, if the passed SSID and password are + * identical to the currently connected AP then nothing is changed. When the + * connection attempt completes the callback function is invoked with one `err` + * parameter, which is NULL if there is no error and a string message if there is + * an error. If DHCP is enabled the callback occurs once an IP addres has been + * obtained, if a static IP is set the callback occurs once the AP's network has + * been joined. The callback is also invoked if a connection already exists and + * does not need to be changed. + * The options properties may contain: + * * `password` - Password string to be used to access the network. + * * `dnsServers` (array of String) - An array of up to two DNS servers in dotted + * decimal format string. + * * `channel` - Wifi channel of the access point (integer, typ 0..14, 0 means any + * channel), only on ESP8266. + * * `bssid` - Mac address of the access point (string, type "00:00:00:00:00:00"), + * only on ESP8266. + * Notes: + * * the options should include the ability to set a static IP and associated + * netmask and gateway, this is a future enhancement. + * * the only error reported in the callback is "Bad password", all other errors + * (such as access point not found or DHCP timeout) just cause connection + * retries. If the reporting of such temporary errors is desired, the caller must + * use its own timeout and the `getDetails().status` field. + * * the `connect` call automatically enabled station mode, it can be disabled + * again by calling `disconnect`. + * + * @param {any} ssid - The access point network id. + * @param {any} options - Connection options (optional). + * @param {any} callback - A `callback(err)` function to be called back on completion. `err` is null on success, or contains an error string on failure. + * @url http://www.espruino.com/Reference#l_Wifi_connect + */ + connect(ssid: any, options: any, callback: any): void; + + /** + * Perform a scan for access points. This will enable the station mode if it is not + * currently enabled. Once the scan is complete the callback function is called + * with an array of APs found, each AP is an object with: + * * `ssid`: SSID string. + * * `mac`: access point MAC address in 00:00:00:00:00:00 format. + * * `authMode`: `open`, `wep`, `wpa`, `wpa2`, or `wpa_wpa2`. + * * `channel`: wifi channel 1..13. + * * `hidden`: true if the SSID is hidden (ESP32/ESP8266 only) + * * `rssi`: signal strength in dB in the range -110..0. + * Notes: + * * in order to perform the scan the station mode is turned on and remains on, use + * Wifi.disconnect() to turn it off again, if desired. + * * only one scan can be in progress at a time. + * + * @param {any} callback - A `callback(err, ap_list)` function to be called back on completion. `err==null` and `ap_list` is an array on success, or `err` is an error string and `ap_list` is undefined on failure. + * @url http://www.espruino.com/Reference#l_Wifi_scan + */ + scan(callback: any): void; + + /** + * Create a WiFi access point allowing stations to connect. If the password is NULL + * or an empty string the access point is open, otherwise it is encrypted. The + * callback function is invoked once the access point is set-up and receives one + * `err` argument, which is NULL on success and contains an error message string + * otherwise. + * The `options` object can contain the following properties. + * * `authMode` - The authentication mode to use. Can be one of "open", "wpa2", + * "wpa", "wpa_wpa2". The default is open (but open access points are not + * recommended). + * * `password` - The password for connecting stations if authMode is not open. + * * `channel` - The channel to be used for the access point in the range 1..13. If + * the device is also connected to an access point as a station then that access + * point determines the channel. + * * `hidden` - The flag if visible or not (0:visible, 1:hidden), default is + * visible. + * Notes: + * * the options should include the ability to set the AP IP and associated + * netmask, this is a future enhancement. + * * the `startAP` call automatically enables AP mode. It can be disabled again by + * calling `stopAP`. + * + * @param {any} ssid - The network id. + * @param {any} options - Configuration options (optional). + * @param {any} callback - Optional `callback(err)` function to be called when the AP is successfully started. `err==null` on success, or an error string on failure. + * @url http://www.espruino.com/Reference#l_Wifi_startAP + */ + startAP(ssid: any, options: any, callback: any): void; + + /** + * Retrieve the current overall WiFi configuration. This call provides general + * information that pertains to both station and access point modes. The getDetails + * and getAPDetails calls provide more in-depth information about the station and + * access point configurations, respectively. The status object has the following + * properties: + * * `station` - Status of the wifi station: `off`, `connecting`, ... + * * `ap` - Status of the wifi access point: `disabled`, `enabled`. + * * `mode` - The current operation mode: `off`, `sta`, `ap`, `sta+ap`. + * * `phy` - Modulation standard configured: `11b`, `11g`, `11n` (the esp8266 docs + * are not very clear, but it is assumed that 11n means b/g/n). This setting + * limits the modulations that the radio will use, it does not indicate the + * current modulation used with a specific access point. + * * `powersave` - Power saving mode: `none` (radio is on all the time), `ps-poll` + * (radio is off between beacons as determined by the access point's DTIM + * setting). Note that in 'ap' and 'sta+ap' modes the radio is always on, i.e., + * no power saving is possible. + * * `savedMode` - The saved operation mode which will be applied at boot time: + * `off`, `sta`, `ap`, `sta+ap`. + * + * @param {any} callback - Optional `callback(status)` function to be called back with the current Wifi status, i.e. the same object as returned directly. + * @returns {any} An object representing the current WiFi status, if available immediately. + * @url http://www.espruino.com/Reference#l_Wifi_getStatus + */ + getStatus(callback: any): any; + + /** + * Sets a number of global wifi configuration settings. All parameters are optional + * and which are passed determines which settings are updated. The settings + * available are: + * * `phy` - Modulation standard to allow: `11b`, `11g`, `11n` (the esp8266 docs + * are not very clear, but it is assumed that 11n means b/g/n). + * * `powersave` - Power saving mode: `none` (radio is on all the time), `ps-poll` + * (radio is off between beacons as determined by the access point's DTIM + * setting). Note that in 'ap' and 'sta+ap' modes the radio is always on, i.e., + * no power saving is possible. + * Note: esp8266 SDK programmers may be missing an "opmode" option to set the + * sta/ap/sta+ap operation mode. Please use connect/scan/disconnect/startAP/stopAP, + * which all set the esp8266 opmode indirectly. + * + * @param {any} settings - An object with the configuration settings to change. + * @url http://www.espruino.com/Reference#l_Wifi_setConfig + */ + setConfig(settings: any): void; + + /** + * Retrieve the wifi station configuration and status details. The details object + * has the following properties: + * * `status` - Details about the wifi station connection, one of `off`, + * `connecting`, `wrong_password`, `no_ap_found`, `connect_fail`, or `connected`. + * The off, bad_password and connected states are stable, the other states are + * transient. The connecting state will either result in connected or one of the + * error states (bad_password, no_ap_found, connect_fail) and the no_ap_found and + * connect_fail states will result in a reconnection attempt after some interval. + * * `rssi` - signal strength of the connected access point in dB, typically in the + * range -110 to 0, with anything greater than -30 being an excessively strong + * signal. + * * `ssid` - SSID of the access point. + * * `password` - the password used to connect to the access point. + * * `authMode` - the authentication used: `open`, `wpa`, `wpa2`, `wpa_wpa2` (not + * currently supported). + * * `savedSsid` - the SSID to connect to automatically at boot time, null if none. + * + * @param {any} callback - An optional `callback(details)` function to be called back with the wifi details, i.e. the same object as returned directly. + * @returns {any} An object representing the wifi station details, if available immediately. + * @url http://www.espruino.com/Reference#l_Wifi_getDetails + */ + getDetails(callback: any): any; + + /** + * Retrieve the current access point configuration and status. The details object + * has the following properties: + * * `status` - Current access point status: `enabled` or `disabled` + * * `stations` - an array of the stations connected to the access point. This + * array may be empty. Each entry in the array is an object describing the + * station which, at a minimum contains `ip` being the IP address of the station. + * * `ssid` - SSID to broadcast. + * * `password` - Password for authentication. + * * `authMode` - the authentication required of stations: `open`, `wpa`, `wpa2`, + * `wpa_wpa2`. + * * `hidden` - True if the SSID is hidden, false otherwise. + * * `maxConn` - Max number of station connections supported. + * * `savedSsid` - the SSID to broadcast automatically at boot time, null if the + * access point is to be disabled at boot. + * + * @param {any} callback - An optional `callback(details)` function to be called back with the current access point details, i.e. the same object as returned directly. + * @returns {any} An object representing the current access point details, if available immediately. + * @url http://www.espruino.com/Reference#l_Wifi_getAPDetails + */ + getAPDetails(callback: any): any; + + /** + * On boards where this is not available, just issue the `connect` commands you + * need to run at startup from an `onInit` function. + * Save the current wifi configuration (station and access point) to flash and + * automatically apply this configuration at boot time, unless `what=="clear"`, in + * which case the saved configuration is cleared such that wifi remains disabled at + * boot. The saved configuration includes: + * * mode (off/sta/ap/sta+ap) + * * SSIDs & passwords + * * phy (11b/g/n) + * * powersave setting + * * DHCP hostname + * + * @param {any} what - An optional parameter to specify what to save, on the esp8266 the two supported values are `clear` and `sta+ap`. The default is `sta+ap` + * @url http://www.espruino.com/Reference#l_Wifi_save + */ + save(what: any): void; + + /** + * Restores the saved Wifi configuration from flash. See `Wifi.save()`. + * @url http://www.espruino.com/Reference#l_Wifi_restore + */ + restore(): void; + + /** + * Return the station IP information in an object as follows: + * * ip - IP address as string (e.g. "192.168.1.5") + * * netmask - The interface netmask as string (ESP8266/ESP32 only) + * * gw - The network gateway as string (ESP8266/ESP32 only) + * * mac - The MAC address as string of the form 00:00:00:00:00:00 + * Note that the `ip`, `netmask`, and `gw` fields are omitted if no connection is established: + * + * @param {any} callback - An optional `callback(err, ipinfo)` function to be called back with the IP information. + * @returns {any} An object representing the station IP information, if available immediately (**ONLY** on ESP8266/ESP32). + * @url http://www.espruino.com/Reference#l_Wifi_getIP + */ + getIP(callback: any): any; + + /** + * Return the access point IP information in an object which contains: + * * ip - IP address as string (typ "192.168.4.1") + * * netmask - The interface netmask as string + * * gw - The network gateway as string + * * mac - The MAC address as string of the form 00:00:00:00:00:00 + * + * @param {any} callback - An optional `callback(err, ipinfo)` function to be called back with the the IP information. + * @returns {any} An object representing the esp8266's Access Point IP information, if available immediately (**ONLY** on ESP8266/ESP32). + * @url http://www.espruino.com/Reference#l_Wifi_getAPIP + */ + getAPIP(callback: any): any; + + /** + * Lookup the hostname and invoke a callback with the IP address as integer + * argument. If the lookup fails, the callback is invoked with a null argument. + * **Note:** only a single hostname lookup can be made at a time, concurrent + * lookups are not supported. + * + * @param {any} hostname - The hostname to lookup. + * @param {any} callback - The `callback(ip)` to invoke when the IP is returned. `ip==null` on failure. + * @url http://www.espruino.com/Reference#l_Wifi_getHostByName + */ + getHostByName(hostname: any, callback: any): void; + + /** + * Returns the hostname announced to the DHCP server and broadcast via mDNS when + * connecting to an access point. + * + * @param {any} callback - An optional `callback(hostname)` function to be called back with the hostname. + * @returns {any} The currently configured hostname, if available immediately. + * @url http://www.espruino.com/Reference#l_Wifi_getHostname + */ + getHostname(callback: any): any; + + /** + * Set the hostname. Depending on implemenation, the hostname is sent with every + * DHCP request and is broadcast via mDNS. The DHCP hostname may be visible in the + * access point and may be forwarded into DNS as hostname.local. If a DHCP lease + * currently exists changing the hostname will cause a disconnect and reconnect in + * order to transmit the change to the DHCP server. The mDNS announcement also + * includes an announcement for the "espruino" service. + * + * @param {any} hostname - The new hostname. + * @param {any} callback - An optional `callback()` function to be called back when the hostname is set + * @url http://www.espruino.com/Reference#l_Wifi_setHostname + */ + setHostname(hostname: any, callback: any): void; + + /** + * Starts the SNTP (Simple Network Time Protocol) service to keep the clock + * synchronized with the specified server. Note that the time zone is really just + * an offset to UTC and doesn't handle daylight savings time. The interval + * determines how often the time server is queried and Espruino's time is + * synchronized. The initial synchronization occurs asynchronously after setSNTP + * returns. + * + * @param {any} server - The NTP server to query, for example, `us.pool.ntp.org` + * @param {any} tz_offset - Local time zone offset in the range -11..13. + * @url http://www.espruino.com/Reference#l_Wifi_setSNTP + */ + setSNTP(server: any, tz_offset: any): void; + + /** + * The `settings` object must contain the following properties. + * * `ip` IP address as string (e.g. "192.168.5.100") + * * `gw` The network gateway as string (e.g. "192.168.5.1") + * * `netmask` The interface netmask as string (e.g. "255.255.255.0") + * + * @param {any} settings - Configuration settings + * @param {any} callback - A `callback(err)` function to invoke when ip is set. `err==null` on success, or a string on failure. + * @url http://www.espruino.com/Reference#l_Wifi_setIP + */ + setIP(settings: any, callback: any): void; + + /** + * The `settings` object must contain the following properties. + * * `ip` IP address as string (e.g. "192.168.5.100") + * * `gw` The network gateway as string (e.g. "192.168.5.1") + * * `netmask` The interface netmask as string (e.g. "255.255.255.0") + * + * @param {any} settings - Configuration settings + * @param {any} callback - A `callback(err)` function to invoke when ip is set. `err==null` on success, or a string on failure. + * @url http://www.espruino.com/Reference#l_Wifi_setAPIP + */ + setAPIP(settings: any, callback: any): void; + + /** + * Issues a ping to the given host, and calls a callback with the time when the + * ping is received. + * + * @param {any} hostname - The host to ping + * @param {any} callback - A `callback(time)` function to invoke when a ping is received + * @url http://www.espruino.com/Reference#l_Wifi_ping + */ + ping(hostname: any, callback: any): void; + + /** + * Switch to using a higher communication speed with the WiFi module. + * * `true` = 921600 baud + * * `false` = 115200 + * * `1843200` (or any number) = use a specific baud rate. * eg. + * `wifi.turbo(true,callback)` or `wifi.turbo(1843200,callback)` + * + * @param {any} enable - true (or a baud rate as a number) to enable, false to disable + * @param {any} callback - A `callback()` function to invoke when turbo mode has been set + * @url http://www.espruino.com/Reference#l_Wifi_turbo + */ + turbo(enable: any, callback: any): void; + } + + /** + * This library allows you to create http servers and make http requests + * In order to use this, you will need an extra module to get network connectivity + * such as the [TI CC3000](/CC3000) or [WIZnet W5500](/WIZnet). + * This is designed to be a cut-down version of the [node.js + * library](http://nodejs.org/api/http.html). Please see the [Internet](/Internet) + * page for more information on how to use it. + * @url http://www.espruino.com/Reference#http + */ + http: { + /** + * Create an HTTP Server + * When a request to the server is made, the callback is called. In the callback + * you can use the methods on the response (`httpSRs`) to send data. You can also + * add `request.on('data',function() { ... })` to listen for POSTed data + * + * @param {any} callback - A function(request,response) that will be called when a connection is made + * @returns {any} Returns a new httpSrv object + * @url http://www.espruino.com/Reference#l_http_createServer + */ + createServer(callback: any): httpSrv; + + /** + * Create an HTTP Request - `end()` must be called on it to complete the operation. + * `options` is of the form: + * ``` + * var options = { + * host: 'example.com', // host name + * port: 80, // (optional) port, defaults to 80 + * path: '/', // path sent to server + * method: 'GET', // HTTP command sent to server (must be uppercase 'GET', 'POST', etc) + * protocol: 'http:', // optional protocol - https: or http: + * headers: { key : value, key : value } // (optional) HTTP headers + * }; + * var req = require("http").request(options, function(res) { + * res.on('data', function(data) { + * console.log("HTTP> "+data); + * }); + * res.on('close', function(data) { + * console.log("Connection closed"); + * }); + * }); + * // You can req.write(...) here if your request requires data to be sent. + * req.end(); // called to finish the HTTP request and get the response + * ``` + * You can easily pre-populate `options` from a URL using `var options = + * url.parse("http://www.example.com/foo.html")` + * There's an example of using [`http.request` for HTTP POST + * here](/Internet#http-post) + * **Note:** if TLS/HTTPS is enabled, options can have `ca`, `key` and `cert` + * fields. See `tls.connect` for more information about these and how to use them. + * + * @param {any} options - An object containing host,port,path,method,headers fields (and also ca,key,cert if HTTPS is enabled) + * @param {any} callback - A function(res) that will be called when a connection is made. You can then call `res.on('data', function(data) { ... })` and `res.on('close', function() { ... })` to deal with the response. + * @returns {any} Returns a new httpCRq object + * @url http://www.espruino.com/Reference#l_http_request + */ + request(options: any, callback: any): httpCRq; + + /** + * Request a webpage over HTTP - a convenience function for `http.request()` that + * makes sure the HTTP command is 'GET', and that calls `end` automatically. + * ``` + * require("http").get("http://pur3.co.uk/hello.txt", function(res) { + * res.on('data', function(data) { + * console.log("HTTP> "+data); + * }); + * res.on('close', function(data) { + * console.log("Connection closed"); + * }); + * }); + * ``` + * See `http.request()` and [the Internet page](/Internet) and ` for more usage + * examples. + * + * @param {any} options - A simple URL, or an object containing host,port,path,method fields + * @param {any} callback - A function(res) that will be called when a connection is made. You can then call `res.on('data', function(data) { ... })` and `res.on('close', function() { ... })` to deal with the response. + * @returns {any} Returns a new httpCRq object + * @url http://www.espruino.com/Reference#l_http_get + */ + get(options: any, callback: any): httpCRq; + } + + /** + * This library provides TV out capability on the Espruino and Espruino Pico. + * See the [Television](/Television) page for more information. + * @url http://www.espruino.com/Reference#tv + */ + tv: { + /** + * This initialises the TV output. Options for PAL are as follows: + * ``` + * var g = require('tv').setup({ type : "pal", + * video : A7, // Pin - SPI MOSI Pin for Video output (MUST BE SPI1) + * sync : A6, // Pin - Timer pin to use for video sync + * width : 384, + * height : 270, // max 270 + * }); + * ``` + * and for VGA: + * ``` + * var g = require('tv').setup({ type : "vga", + * video : A7, // Pin - SPI MOSI Pin for Video output (MUST BE SPI1) + * hsync : A6, // Pin - Timer pin to use for video sync + * vsync : A5, // Pin - pin to use for video sync + * width : 220, + * height : 240, + * repeat : 2, // amount of times to repeat each line + * }); + * ``` + * or + * ``` + * var g = require('tv').setup({ type : "vga", + * video : A7, // Pin - SPI MOSI Pin for Video output (MUST BE SPI1) + * hsync : A6, // Pin - Timer pin to use for video sync + * vsync : A5, // Pin - pin to use for video sync + * width : 220, + * height : 480, + * repeat : 1, // amount of times to repeat each line + * }); + * ``` + * See the [Television](/Television) page for more information. + * + * @param {any} options - Various options for the TV output + * @param {number} width + * @returns {any} A graphics object + * @url http://www.espruino.com/Reference#l_tv_setup + */ + setup(options: any, width: number): any; + } + + /** + * Simple library for compression/decompression using + * [heatshrink](https://github.com/atomicobject/heatshrink), an + * [LZSS](https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Storer%E2%80%93Szymanski) + * compression tool. + * Espruino uses heatshrink internally to compress RAM down to fit in Flash memory + * when `save()` is used. This just exposes that functionality. + * Functions here take and return buffers of data. There is no support for + * streaming, so both the compressed and decompressed data must be able to fit in + * memory at the same time. + * @url http://www.espruino.com/Reference#heatshrink + */ + heatshrink: { + /** + * + * @param {any} data - The data to compress + * @returns {any} Returns the result as an ArrayBuffer + * @url http://www.espruino.com/Reference#l_heatshrink_compress + */ + compress(data: any): ArrayBuffer; + + /** + * + * @param {any} data - The data to decompress + * @returns {any} Returns the result as an ArrayBuffer + * @url http://www.espruino.com/Reference#l_heatshrink_decompress + */ + decompress(data: any): ArrayBuffer; + } + + /** + * This library allows you to write to Neopixel/WS281x/APA10x/SK6812 LED strips + * These use a high speed single-wire protocol which needs platform-specific + * implementation on some devices - hence this library to simplify things. + * @url http://www.espruino.com/Reference#neopixel + */ + neopixel: { + /** + * Write to a strip of NeoPixel/WS281x/APA104/APA106/SK6812-style LEDs attached to + * the given pin. + * ``` + * // set just one pixel, red, green, blue + * require("neopixel").write(B15, [255,0,0]); + * ``` + * ``` + * // Produce an animated rainbow over 25 LEDs + * var rgb = new Uint8ClampedArray(25*3); + * var pos = 0; + * function getPattern() { + * pos++; + * for (var i=0;i = T extends U ? never : T; diff --git a/typescript/types/package.json b/typescript/types/package.json new file mode 100644 index 000000000..7259e2ab0 --- /dev/null +++ b/typescript/types/package.json @@ -0,0 +1,5 @@ +{ + "name": "banglejs", + "version": "1.0.0", + "typings": "main.d.ts" +} diff --git a/webtools b/webtools new file mode 160000 index 000000000..2ab71a33d --- /dev/null +++ b/webtools @@ -0,0 +1 @@ +Subproject commit 2ab71a33d69bfda40465174ffe57adb03c21fc42