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 index 1eb009153..7c0cfca3a 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -1,4 +1,4 @@ -name: Node CI +name: build on: [push, pull_request] @@ -6,29 +6,22 @@ jobs: build: runs-on: ubuntu-latest - strategy: - matrix: - node-version: [16.x] - steps: - name: Checkout repository and submodules - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: recursive - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + - name: Use Node.js 16.x + uses: actions/setup-node@v3 with: - node-version: ${{ matrix.node-version }} - - name: install testing dependencies - run: npm i - - name: test all apps and widgets - run: npm run test - - name: install typescript dependencies + 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 types + - name: Build all TS apps and widgets working-directory: ./typescript - run: npm run build:types - - name: build all TS apps and widgets - working-directory: ./typescript - run: npm run build \ No newline at end of file + run: npm run build diff --git a/.gitignore b/.gitignore index 231851dd6..f4588ac6f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ .htaccess node_modules -package-lock.json .DS_Store *.js.bak appdates.csv @@ -12,3 +11,6 @@ tests/Layout/testresult.bmp apps.local.json _site .jekyll-cache +.owncloudsync.log +Desktop.ini +.sync_*.db* diff --git a/README.md b/README.md index b3da9f685..d2f7022e9 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/) @@ -191,7 +191,7 @@ widget bar at the top of the screen they can add themselves to the global ``` 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 @@ -324,7 +324,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 diff --git a/apps/7x7dotsclock/7x7dotsclock.app.js b/apps/7x7dotsclock/7x7dotsclock.app.js index aa174b2d2..aa6672a4f 100644 --- a/apps/7x7dotsclock/7x7dotsclock.app.js +++ b/apps/7x7dotsclock/7x7dotsclock.app.js @@ -149,11 +149,11 @@ function drawHSeg(x1,y1,x2,y2,Num,Color,Size) { if (Color == "fg") { g.setColor(g.theme.fg); } else { - g.setColor(mColor[0],mColor[1],mColor[2]); + 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.setColor(bColor[0],bColor[1],bColor[2]); g.fillCircle(x1+Dx+(i-1)*(x2-x1)/7,y1+Dy+(j-1)*(y2-y1)/7,1); } } @@ -166,7 +166,7 @@ function drawSSeg(x1,y1,x2,y2,Num,Color,Size) { 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]); + g.setColor(sColor[0],sColor[1],sColor[2]); } else { g.setColor(g.theme.fg); //g.setColor(0.7,0.7,0.7); @@ -253,8 +253,8 @@ 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); @@ -269,7 +269,7 @@ function actions(v){ } // Get Messages status -var messages = require("Storage").readJSON("messages.json",1)||[]; +var messages_installed = require("Storage").read("messages") !== undefined; //var BTconnected = NRF.getSecurityStatus().connected; //NRF.on('connect',BTconnected = NRF.getSecurityStatus().connected); @@ -289,27 +289,27 @@ function drawWidgeds() { 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); + 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 @@ -318,25 +318,25 @@ function drawWidgeds() { var x2M = x1M + 25; var y2M = y2B; - if (messages.some(m=>m.new)) { + 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); - + } @@ -354,7 +354,7 @@ function SetFull(on) { } else { Ys = 30; Bangle.setUI("updown",actions); - Bangle.on('swipe', function(direction) { + Bangle.on('swipe', function(direction) { switch (direction) { case 1: print("swipe left event"); @@ -362,7 +362,7 @@ function SetFull(on) { print(settings.swleftApp); break; case -1: - print("swipe right event"); + print("swipe right event"); if(settings.swrightApp != "") load(settings.swrightApp); print(settings.swrightApp); break; @@ -374,7 +374,7 @@ function SetFull(on) { SegH = (Ye-Ys)/2; Dy = SegH/16; - + draw(); if (on != true) { diff --git a/apps/7x7dotsclock/ChangeLog b/apps/7x7dotsclock/ChangeLog index d2c98a472..5e8e48b0b 100644 --- a/apps/7x7dotsclock/ChangeLog +++ b/apps/7x7dotsclock/ChangeLog @@ -1,2 +1,3 @@ 0.01: Initial version for upload -0.02: better theme support, configurable colors, small improvements +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/metadata.json b/apps/7x7dotsclock/metadata.json index 41f0836d3..ba1996544 100644 --- a/apps/7x7dotsclock/metadata.json +++ b/apps/7x7dotsclock/metadata.json @@ -1,7 +1,7 @@ { "id": "7x7dotsclock", "name": "7x7 Dots Clock", "shortName":"7x7 Dots Clock", - "version":"0.02", + "version":"0.03", "description": "A clock with a big 7x7 dots Font", "icon": "dotsfontclock.png", "tags": "clock", diff --git a/apps/90sclk/ChangeLog b/apps/90sclk/ChangeLog index feb008f5f..057d6ff73 100644 --- a/apps/90sclk/ChangeLog +++ b/apps/90sclk/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! -0.02: Fullscreen settings. \ No newline at end of file +0.02: Fullscreen settings. +0.03: Tell clock widgets to hide. diff --git a/apps/90sclk/app.js b/apps/90sclk/app.js index 6babbfec2..351c235e0 100644 --- a/apps/90sclk/app.js +++ b/apps/90sclk/app.js @@ -115,6 +115,9 @@ function draw() { } } +// Show launcher when middle button pressed +Bangle.setUI("clock"); + Bangle.loadWidgets(); // Clear the screen once, at startup @@ -140,5 +143,3 @@ Bangle.on('lock', function(isLocked) { }); -// Show launcher when middle button pressed -Bangle.setUI("clock"); diff --git a/apps/90sclk/metadata.json b/apps/90sclk/metadata.json index fb2824a6f..59b627427 100644 --- a/apps/90sclk/metadata.json +++ b/apps/90sclk/metadata.json @@ -1,7 +1,7 @@ { "id": "90sclk", "name": "90s Clock", - "version": "0.02", + "version": "0.03", "description": "A 90s style watch-face", "readme": "README.md", "icon": "app.png", 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_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/activityreminder/ChangeLog b/apps/activityreminder/ChangeLog index da897b899..c913481ff 100644 --- a/apps/activityreminder/ChangeLog +++ b/apps/activityreminder/ChangeLog @@ -4,4 +4,6 @@ 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 \ No newline at end of file +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 \ No newline at end of file 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.js b/apps/activityreminder/app.js index c2b626fb3..97f03ce97 100644 --- a/apps/activityreminder/app.js +++ b/apps/activityreminder/app.js @@ -1,46 +1,54 @@ (function () { - // load variable before defining functions cause it can trigger a ReferenceError - const activityreminder = require("activityreminder"); - const storage = require("Storage"); - const activityreminder_settings = activityreminder.loadSettings(); - let activityreminder_data = activityreminder.loadData(); + // 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 drawAlert() { - 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); - } - - function run() { - if (activityreminder.mustAlert(activityreminder_data, activityreminder_settings)) { - drawAlert(); - } else { - eval(storage.read("activityreminder.settings.js"))(() => load()); - } - } + 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(); - run(); - + drawInfo(); + } + + run(); + })(); \ No newline at end of file diff --git a/apps/activityreminder/boot.js b/apps/activityreminder/boot.js index f97cf274d..5a11d73b8 100644 --- a/apps/activityreminder/boot.js +++ b/apps/activityreminder/boot.js @@ -1,70 +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; + // 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); - } - - 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 (activityreminder.mustAlert(activityreminder_data, activityreminder_settings)) { - load('activityreminder.app.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); - } - } - - 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 + 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 index 704d35641..a5c35190c 100644 --- a/apps/activityreminder/lib.js +++ b/apps/activityreminder/lib.js @@ -1,56 +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) || {}); + 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); + require("Storage").writeJSON("activityreminder.s.json", settings); }; exports.saveData = function (data) { - require("Storage").writeJSON("activityreminder.data.json", 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), - }, + 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); + 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; + return data; }; - -exports.mustAlert = function(activityreminder_data, activityreminder_settings) { - let now = new Date(); - 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; -} \ No newline at end of file diff --git a/apps/activityreminder/metadata.json b/apps/activityreminder/metadata.json index fb9423e7c..9e8c552b1 100644 --- a/apps/activityreminder/metadata.json +++ b/apps/activityreminder/metadata.json @@ -3,7 +3,7 @@ "name": "Activity Reminder", "shortName":"Activity Reminder", "description": "A reminder to take short walks for the ones with a sedentary lifestyle", - "version":"0.07", + "version":"0.09", "icon": "app.png", "type": "app", "tags": "tool,activity", @@ -13,11 +13,12 @@ {"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"} + {"name": "activityreminder.data.json", "storageFile": true} ] } diff --git a/apps/activityreminder/settings.js b/apps/activityreminder/settings.js index ce7cdc913..051c0dcd8 100644 --- a/apps/activityreminder/settings.js +++ b/apps/activityreminder/settings.js @@ -1,85 +1,86 @@ (function (back) { - // Load settings - const activityreminder = require("activityreminder"); - let settings = activityreminder.loadSettings(); + // Load settings + const activityreminder = require("activityreminder"); + let settings = activityreminder.loadSettings(); - // Show the menu - E.showMenu({ - "": { "title": "Activity Reminder" }, - "< Back": () => back(), - 'Enable': { - value: settings.enabled, - format: v => v ? "Yes" : "No", - 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 => { - return x + " min"; - } - }, - 'Dismiss delay': { - value: settings.dismissDelayMin, - min: 5, max: 60, - onchange: v => { - settings.dismissDelayMin = v; - activityreminder.writeSettings(settings); - }, - format: x => { - return x + " min"; - } - }, - 'Pause delay': { - value: settings.pauseDelayMin, - min: 30, max: 240, step: 5, - onchange: v => { - settings.pauseDelayMin = v; - activityreminder.writeSettings(settings); - }, - format: x => { - return x + " min"; - } - }, - '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); - } + 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..a1b528cf6 --- /dev/null +++ b/apps/advcasio/ChangeLog @@ -0,0 +1,3 @@ +0.01: AdvCasio first version +0.02: Remove un-needed fonts to improve memory usage +0.03: Tell clock widgets to hide. 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..8cb904f90 --- /dev/null +++ b/apps/advcasio/app.js @@ -0,0 +1,304 @@ +const storage = require('Storage'); + +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 getClockBg() { + return require("heatshrink").decompress(atob("icVgf/ABv8v4DBx4CB+PH8F+nAGB48fwEHBwXjxwqBuPH//+nAGBBwIjCAwI2D/wGBgIyDI4QGDwAGBHYX/4AGBn4UFEYQpCEYYpCAAMfMhP4FIgABwJ8OEBIA==")); +} + + +// sun, cloud, rain, thunder +var iconsWeather = [ + require("heatshrink").decompress(atob("i8Ugf/ACcfA434BA/AAwsAv0/8F/BAcDwEHHIpECFI3wn4GC/gOC+PAGoXggEH/+ODQgXBGQv/wAbBBAnguEACIn4gfxI4JXFwJmG/kPBA3jSynw")), require("heatshrink").decompress(atob("i0Ugf/AEXggIGE/0A/kPBAmBCIN/A4Y8CgAICwEHBYoUE/ACCj4sDn4CBC4YyDwBrDCgYA3A")), require("heatshrink").decompress(atob("h8Rgf/AAuBAgf8h4FDCwM/AgPA/gFC/0HgEBBQPwnEfDoWAg4jC/gOCAoQmBAQXjFIV//8f//4IQP4j/+gAIB4EcHII4CAoI+DLQJXF/AA==")), require("heatshrink").decompress(atob("h0Pgf/AA8fAYX+g4EC8EBAgXADAeAgAECgAOC/wrCDQIOBBYfwgAaC/kAn4EB/EAv4aDHAeBIg38")) +]; + + +function getBackgroundImage() { + return require("heatshrink").decompress(atob("2GwghC/AH4A/AH4AMl////wAwURiQECgUzmcxBQQCBiYUBBARW+LAcCAgcPBYgFBkAIFG7kQiAKIiIKBgISOAAJBD//zKQfxK4vyAoMQCgn/ERBhBBYR5BAwR1DB4Y2DgYPCGIQRCCQcP+EfGJI0FEgRSCGAQCCX4JXCkAhDn4lI+HyK4YWBFIPzJYJXHAIMSK4cwJ4I3CAYMzA4cfcRMBdwytBK4i6FK4IUCMgYAEGIITBK4cCaAPwgJXB+fzK4sAgYtCK5EfA4pXR+AmBaIZYCK6KcCAwSjDEYXx/8vK5QRCK4kPK6cDkJREBIMBfgIrDK5svUAIQBAwIaCK4w+DK4YGBK7IaBboIuCK4gFCJwYBBiBCCCgQhHHYgGDgArBK5IGDAYMgJ4Xwn53BGgLVDmBXKAAinDLpJXCAAYhHR4YODn/wJIPyTYZXDE4RXD+ECNILIDAIPwj4xIAAYNCR4fyVIYLFA4KEBBAglKAGUCmcykEAiMQBIURBYM/BgIUEgcz+bTKAH4A/AH4A/AHP/AGY1d+BWCh5X/LCpW1K74fgG/5X/AH5X/K9Bg/K63wK/5XWgBX/K6pWBK/5XU+BWBh5J/K6auCK/5XTVwRfFAH5XOKwRX/K6auDh5I/K6SuDWP5XSVwYADWX6vXK/5XQWQpW/K6auDJP5XWV35XT+Cu/K7Ku/K65H/K6hW/K7EPI35XWIv5XWAH5X/K/4A/K/5X/K/4A/K9cAAH4A/AFzz/AHRX/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/40VAH4A/AFzLb+EPDm4AdK/5X/K+PwgEAHy5X9HgMAK/5XXH6xX/H65X/K/5X/K98AK7sAgBX3DjBWFO644DSTHwGzJXED4RXaDoLqcK7weWDIQcXK8I6YK77KXK4o8DPbY6ZK7qvDDy6vdR7JXDh60EDyw5BAIRXYSwjMbAgIhUDwJZCHwJX0GwjRWNwIAEHSwBCDSpXFH4pXzDS5XIEARXVSYbQEDaYzCK+6vcKaxXNDypX9HwQkbHS40COSpXKK2A6CHgRXcPIhX0SwpXYVuQ6EgBX/K644YODBXkSDJX/K/5X/DtRX6gA3YOkRWbLDZX4KwYA/AG8F5vdABncKH4AGhpRJAYXNAgPAKP4AF5vMJwoDBAQIKE6BR/AAvc5vO9wAB7oCB9veAoPcAoPcK+kwh8AgcA98An//gH/+sD//wCISgBJ4IABAYpaC9vdK4UP/9AAQNQr/zgHwEYNQFYQAh+EP+FegH+A4QBCMQIKBAAPNK4yxBA4RXCV4YZBE4IjChwCDmApCK8VdmHggHgFYf0SQJXE5nMK4anCAoYHC5pXCaQJXBop+BqAGEK7f/AAQeEKwQrBqCtDAILjBCQfNK4JTCAYZXF7qvD//gV4S2DgEFFIYAECgIACMC8PKoIBB8n1K4ivF5vc5xOCWYZbBAYavHU4RXCr4pEAEMDfoNQGoMEgEwYQPwAoIBBAAPM5ipC7oDCVIIAE7hXCD4SdBiEP+gGBgihCFYIAz5pXBAAnN7oIB7nc5gOBK4QA/K4pNCWgSpCBInNK/4AGhncKIStC7gCBA4QAC4BR/AAysCABZW/AHwA=")); +} + + + +// 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; +} + +//////////////////////////////////////////// +// TIMER FUNC +// +var timer_time = 0; +var alreadyListenTouch = false; +function initTouchTimer () { + if (alreadyListenTouch) return; + alreadyListenTouch = true; + + Bangle.on('swipe', function(dirX,dirY) { + if (canTouch === false) return; + var njson = getDataJson(); + if (!njson) return; + + if (dirX === -1) { + timer_time = 0; + delete njson.timer; + setDataJson(njson); + } + else if (dirX === 1) { + var now = new Date().getTime(); + njson.timer = now + (timer_time * 1000 * 60); + Bangle.setLocked(true); + setDataJson(njson); + Bangle.buzz(200, 0); + timer_time = 0; + } + else if (dirY === -1) { + if (canTouch === false || njson.timer) return; + timer_time = timer_time + 5; + } + else if (dirY === 1) { + if (canTouch === false || njson.timer) return; + timer_time = timer_time - 5; + } + draw(); + }); +} +setTimeout(() => { + initTouchTimer (); +}); + +function getTimerTime() { + // if timer_time !== -1, take it + if (timer_time !== 0) { + return timer_time + "m"; + } else { + // else, show diff between njsontime and now + var njson = getDataJson(); + if (!njson) return false; + var now = new Date().getTime(); + var diff = Math.round((njson.timer - now) / (1000 * 60)); + //console.log(123, njson, diff, now, njson.timer - now); + if (diff > 0) return diff + "m"; + else if (njson.timer) { + Bangle.buzz(1000, 1); + console.log("END OF TIMER"); + delete njson.timer; + setDataJson(njson); + return false; + } else { + return false; + } + // if diff is <0, delete timer from json + } +} +function drawTimer() { + //g.drawString(getTimerTime(), 100, 100); + g.setFont("8x12", 2); + var t = 97; + var l = 105; + var time = getTimerTime(); + if (time || timer_time !== 0) g.drawString(time, l+5, t+0); + if (time && timer_time === 0) g.drawImage(getClockBg(), l-20, t+2, { scale: 1 }); +} + + +//////////////////////////////////////////// +// DATA READING +// +function getDataJson(){ + var res = {"tasks":"", "weather":[]}; + try { + res = storage.readJSON('advcasio.data.json'); + } catch(ex) { + return res; + } + return res; +} +function setDataJson(resJson){ + try { + res = storage.writeJSON('advcasio.data.json', resJson); + } catch(ex) { + return res; + } + return res; +} +var dataJson = getDataJson(); + +//////////////////////////////////////////// +// WEATHER! +// +function drawWeather(arr) { + g.setFont("6x8", 1); + var p = {l: 8, tText: 40, tIcon:20, decal:25}; + var today = new Date().getTime(); + var yesterday = today - (1000 * 60 * 60 * 24); + var testday = today + (1000 * 60 * 60 * 24 * 2); + //12h auj > 12h hier qui est sup a 0h auj + //23h59 hier est sup a 0h auj + var j = 0; + for(var i = 0; i yesterday && j < 4) { + g.drawString(arr[i][0], p.l + p.decal*j + 4, p.tText); + g.drawImage(iconsWeather[arr[i][1]], p.l + p.decal*j, p.tIcon, { scale: 1 }); + j++ + } + } +} + + +//////////////////////////////////////////// +// DRAWING FUNCS +// +function drawTasks(str) { + g.setFont("6x8", 1); + var t = 57; + var l = 0; + g.drawString(str, l+5, t+0); +} + +function drawSteps() { + g.setFont("8x12", 2); + var t = 132; + var l = 150; + g.drawString(getSteps(), l+5, t+0); +} + + +function drawClock() { + g.setFont("7x11Numeric7Seg", 3); + g.clearRect(80, 57, 170, 96); + g.setColor(255, 255, 255); + var l = 77; + var t = 57; + var w = 170; + var h = 116; + g.drawRect(l, t, w, h); + g.fillRect(l, t, w, h); + g.setColor(0, 0, 0); + g.drawString(require("locale").time(new Date(), 1), 76, 60); + + // day + //g.setFont("8x12", 1); + //g.setFont("9x18", 1); + //g.drawString(require("locale").dow(new Date(), 2).toUpperCase(), 25, 136); + g.setFont("8x12", 2); + g.drawString(require("locale").dow(new Date(), 2), 18, 130); + + // month + g.setFont("8x12"); + g.drawString(require("locale").month(new Date(), 2).toUpperCase(), 80, 127); + + // day nb + g.setFont("8x12", 2); + const time = new Date().getDate(); + g.drawString(time < 10 ? "0" + time : time, 78, 137); +} + +function drawBattery() { + bigThenSmall(E.getBattery(), "%", 140, 23); +} + + +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 "? k"; + } + + steps = Math.round(steps/1000); + return steps + "k"; +} + + + +function draw() { + + queueDraw(); + + g.reset(); + g.clear(); + g.setColor(255, 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); + if(dataJson && dataJson.weather) drawWeather(dataJson.weather); + if(dataJson && dataJson.tasks) drawTasks(dataJson.tasks); + + + g.setFontAlign(0,-1); + g.setFont("8x12", 2); + + drawSteps(); + g.setFontAlign(-1,-1); + drawClock(); + drawBattery(); + drawTimer(); + // Hide widgets + for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} +} + +// save batt power, does not seem to work although... +var canTouch = true; +Bangle.on("lcdPower", (on) => { + if (on) { + draw(); + } else { + canTouch = false; + clearIntervals(); + } +}); + + +Bangle.on("lock", (locked) => { + clearIntervals(); + draw(); + if (!locked) { + canTouch = true; + } else { + canTouch = false; + } +}); + + +Bangle.setUI("clock"); + +// Load widgets, but don't show them +Bangle.loadWidgets(); + +g.reset(); +g.clear(); +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..152f47132 --- /dev/null +++ b/apps/advcasio/metadata.json @@ -0,0 +1,25 @@ +{ "id": "advcasio", + "name": "Advanced Casio Clock", + "shortName":"advcasio", + "version":"0.03", + "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 index 56dfffa0d..c7ef32839 100644 --- a/apps/agenda/ChangeLog +++ b/apps/agenda/ChangeLog @@ -1 +1,4 @@ 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 diff --git a/apps/agenda/agenda.js b/apps/agenda/agenda.js index f39e31c75..be618ae68 100644 --- a/apps/agenda/agenda.js +++ b/apps/agenda/agenda.js @@ -24,19 +24,23 @@ 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) +CALENDAR=CALENDAR.sort((a,b)=>a.timestamp - b.timestamp); function getDate(timestamp) { return new Date(timestamp*1000); } -function formatDateLong(date, includeDay) { - if(includeDay) - return Locale.date(date)+" "+Locale.time(date,1); - return Locale.time(date,1); +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) { - return Locale.date(date).replace(/\d\d\d\d/,"")+Locale.time(date,1); +function formatDateShort(date, allDay) { + return Locale.date(date).replace(/\d\d\d\d/,"")+(allDay? + "" : Locale.time(date,1)+Locale.meridian(date)); } var lines = []; @@ -45,7 +49,7 @@ function showEvent(ev) { if(!ev) return; g.setFont(bodyFont); //var lines = []; - if (ev.title) lines = g.wrapString(ev.title, g.getWidth()-10) + 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)); @@ -53,17 +57,17 @@ function showEvent(ev) { if (titleCnt) lines.push(""); // add blank line after title if(start.getDay() == end.getDay() && start.getMonth() == end.getMonth()) includeDay = false; - if(includeDay) { + if(includeDay || ev.allDay) { lines = lines.concat( /*LANG*/"Start:", - g.wrapString(formatDateLong(start, includeDay), g.getWidth()-10), + g.wrapString(formatDateLong(start, includeDay, ev.allDay), g.getWidth()-10), /*LANG*/"End:", - g.wrapString(formatDateLong(end, includeDay), g.getWidth()-10)); + 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), g.getWidth()-10), - g.wrapString(/*LANG*/"End"+": "+formatDateLong(end, includeDay), 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)); @@ -89,6 +93,12 @@ function showEvent(ev) { } 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; @@ -101,10 +111,10 @@ function showList() { g.setColor(g.theme.fg); g.clearRect(r.x,r.y,r.x+r.w, r.y+r.h); if (!ev) return; - var isPast = ev.timestamp + ev.durationInSeconds < (new Date())/1000; + var isPast = false; var x = r.x+2, title = ev.title; - var body = formatDateShort(getDate(ev.timestamp))+"\n"+ev.location; - var m = ev.title+"\n"+ev.location, longBody=false; + 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,r.y+2); if (body) { @@ -114,10 +124,8 @@ function showList() { l = l.slice(0,3); l[l.length-1]+="..."; } - longBody = l.length>2; g.drawString(l.join("\n"), x+10,r.y+20); } - //if (!longBody && msg.src) g.setFontAlign(1,1).setFont("6x8").drawString(msg.src, r.x+r.w-2, r.y+r.h-2); 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 }, select : idx => showEvent(CALENDAR[idx]), diff --git a/apps/agenda/metadata.json b/apps/agenda/metadata.json index ce8438686..50ea54c10 100644 --- a/apps/agenda/metadata.json +++ b/apps/agenda/metadata.json @@ -1,7 +1,7 @@ { "id": "agenda", "name": "Agenda", - "version": "0.02", + "version": "0.04", "description": "Simple agenda", "icon": "agenda.png", "screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}], @@ -13,5 +13,6 @@ {"name":"agenda.app.js","url":"agenda.js"}, {"name":"agenda.settings.js","url":"settings.js"}, {"name":"agenda.img","url":"agenda-icon.js","evaluate":true} - ] + ], + "data": [{"name":"agenda.settings.json"}] } diff --git a/apps/agenda/settings.js b/apps/agenda/settings.js index fe9dab2d8..4220fcb63 100644 --- a/apps/agenda/settings.js +++ b/apps/agenda/settings.js @@ -3,6 +3,10 @@ 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" }, @@ -32,6 +36,13 @@ 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..ae26512de --- /dev/null +++ b/apps/agpsdata/ChangeLog @@ -0,0 +1,2 @@ +0.01: First, proof of concept +0.02: Load AGPS data on app start and automatically in background 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..647723bb4 --- /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..."); + 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..6415f0b52 --- /dev/null +++ b/apps/agpsdata/boot.js @@ -0,0 +1,33 @@ +(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) { + let lastUpdate = settings.lastUpdate; + if (!lastUpdate || lastUpdate + settings.refresh * 1000 * 60 < Date.now()){ + if (!waiting){ + waiting = true; + require("agpsdata").pull(successCallback, errorCallback); + } + } + 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..7d9758c0a --- /dev/null +++ b/apps/agpsdata/lib.js @@ -0,0 +1,75 @@ +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(data) { + var js = jsFromBase64(data); + try { + eval(js); + return true; + } + catch(e) { + console.log("error:", e); + } + return false; +} + +function jsFromBase64(b64) { + var bin = atob(b64); + var chunkSize = 128; + var js = "Bangle.setGPSPower(1);\n"; // turn GPS on + var gnsstype = settings.gnsstype || 1; // default GPS + js += `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) + + for (var i=0;i { + let result = setAGPS(event.resp); + if (result) { + updateLastUpdate(); + if (successCallback) successCallback(); + } else { + console.log("error applying AGPS data"); + if (failureCallback) failureCallback("Error applying AGPS data"); + } + }).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..d3863be52 --- /dev/null +++ b/apps/agpsdata/metadata.json @@ -0,0 +1,24 @@ +{ "id": "agpsdata", + "name": "A-GPS Data", + "shortName":"A-GPS Data", + "icon": "agpsdata.png", + "version":"0.02", + "description": "Download assisted GPS (A-GPS) data directly to your Bangle.js **using Gadgetbridge**", + "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..80a2f3956 --- /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 : 1, + 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/alarm/ChangeLog b/apps/alarm/ChangeLog index 1be0dcdd5..52ee8bf9c 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -31,3 +31,7 @@ 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 + diff --git a/apps/alarm/app.js b/apps/alarm/app.js index 0a7bb8f24..ed5aa608a 100644 --- a/apps/alarm/app.js +++ b/apps/alarm/app.js @@ -124,6 +124,10 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) { value: alarm.as, onchange: v => alarm.as = v }, + /*LANG*/"Hidden": { + value: alarm.hidden || false, + onchange: v => alarm.hidden = v + }, /*LANG*/"Cancel": () => showMainMenu() }; @@ -280,6 +284,14 @@ function showEditTimerMenu(selectedTimer, timerIndex) { 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() }; diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json index 54472a12c..31dd58ece 100644 --- a/apps/alarm/metadata.json +++ b/apps/alarm/metadata.json @@ -2,7 +2,7 @@ "id": "alarm", "name": "Alarms & Timers", "shortName": "Alarms", - "version": "0.31", + "version": "0.33", "description": "Set alarms and timers on your Bangle", "icon": "app.png", "tags": "tool,alarm,widget", diff --git a/apps/alarm/widget.js b/apps/alarm/widget.js index 052ac9ebd..964176fc7 100644 --- a/apps/alarm/widget.js +++ b/apps/alarm/widget.js @@ -2,7 +2,7 @@ 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() { // 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!==false)) ? 24 : 0; + 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/android/ChangeLog b/apps/android/ChangeLog index f13ccd95c..0cc7aedd4 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -9,3 +9,7 @@ 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) diff --git a/apps/android/README.md b/apps/android/README.md index c10718aac..f9ab73699 100644 --- a/apps/android/README.md +++ b/apps/android/README.md @@ -32,6 +32,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 9cdc019a6..bc8e3032d 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -118,11 +118,53 @@ 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 } }; 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 (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 }); } diff --git a/apps/android/metadata.json b/apps/android/metadata.json index 27b77cf2f..5d1b2f561 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -2,7 +2,7 @@ "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.11", + "version": "0.15", "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", diff --git a/apps/android/settings.js b/apps/android/settings.js index 695d483c6..c7c34a76f 100644 --- a/apps/android/settings.js +++ b/apps/android/settings.js @@ -18,7 +18,6 @@ }), /*LANG*/"Keep Msgs" : { value : !!settings.keep, - format : v=>v?/*LANG*/"Yes":/*LANG*/"No", onchange: v => { settings.keep = v; updateSettings(); 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 73a63f7c7..f7e95b5fa 100644 --- a/apps/antonclk/ChangeLog +++ b/apps/antonclk/ChangeLog @@ -9,4 +9,5 @@ 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 \ No newline at end of file +0.08: fixed calendar weeknumber not shortened to two digits +0.09: Use default Bangle formatter for booleans \ No newline at end of file diff --git a/apps/antonclk/metadata.json b/apps/antonclk/metadata.json index c58ee2a1b..16bdf3aa8 100644 --- a/apps/antonclk/metadata.json +++ b/apps/antonclk/metadata.json @@ -1,7 +1,7 @@ { "id": "antonclk", "name": "Anton Clock", - "version": "0.08", + "version": "0.09", "description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.", "readme":"README.md", "icon": "app.png", diff --git a/apps/antonclk/settings.js b/apps/antonclk/settings.js index 6882cbd0f..4448c00ed 100644 --- a/apps/antonclk/settings.js +++ b/apps/antonclk/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) || {}); @@ -41,7 +40,6 @@ "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,7 +47,6 @@ }, "Show CalWeek": { value: (settings.calWeek !== undefined ? settings.calWeek : false), - format: v => v ? "On" : "Off", onchange: v => { settings.calWeek = v; writeSettings(); @@ -57,7 +54,6 @@ }, "Uppercase": { value: (settings.upperCase !== undefined ? settings.upperCase : true), - format: v => v ? "On" : "Off", 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(); @@ -82,7 +77,6 @@ "Show": stringInSettings("secondsMode", ["Never", "Unlocked", "Always"]), "With \":\"": { value: (settings.secondsWithColon !== undefined ? settings.secondsWithColon : true), - format: v => v ? "On" : "Off", onchange: v => { settings.secondsWithColon = v; writeSettings(); @@ -90,7 +84,6 @@ }, "Color": { value: (settings.secondsColoured !== undefined ? settings.secondsColoured : true), - format: v => v ? "On" : "Off", onchange: v => { settings.secondsColoured = v; writeSettings(); @@ -99,9 +92,6 @@ "Date": stringInSettings("dateOnSecs", ["Year", "Weekday", "No"]) }; - // Actually display the menu E.showMenu(mainmenu); }); - -// end of file diff --git a/apps/astral/ChangeLog b/apps/astral/ChangeLog index a51c96760..c93c2b6c2 100644 --- a/apps/astral/ChangeLog +++ b/apps/astral/ChangeLog @@ -1,3 +1,4 @@ 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. diff --git a/apps/astral/app.js b/apps/astral/app.js index c445463f2..c4339bc09 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; diff --git a/apps/astral/metadata.json b/apps/astral/metadata.json index 3317092db..d7120878b 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.04", "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/barclock/ChangeLog b/apps/barclock/ChangeLog index ba44ecef8..a00ae9325 100644 --- a/apps/barclock/ChangeLog +++ b/apps/barclock/ChangeLog @@ -12,3 +12,4 @@ 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 \ No newline at end of file diff --git a/apps/barclock/README.md b/apps/barclock/README.md index ff66a5cbb..28572e37c 100644 --- a/apps/barclock/README.md +++ b/apps/barclock/README.md @@ -7,4 +7,5 @@ A simple digital clock showing seconds as a horizontal bar. ## Settings * `Show date`: display date at the bottom of screen -* `Font`: choose between bitmap or vector fonts \ No newline at end of file +* `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 61ce07dfb..5a7dfc8c0 100644 --- a/apps/barclock/clock-bar.js +++ b/apps/barclock/clock-bar.js @@ -13,16 +13,20 @@ let locale = require("locale"); locale.hasMeridian = (locale.meridian(date)!==""); } +let barW = 0, prevX = 0; 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); + "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 (x21; if (this.is12Hour && locale.hasMeridian) { @@ -89,17 +93,32 @@ const ClockFace = require("ClockFace"), 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); - const SECONDS_PER_MINUTE = 60; - if (c.s) this.layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE; - this.layout.render(); + 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(); }, }); + +// power saving: only update once a minute while locked, hide bar +if (clock.powerSave) { + Bangle.on("lock", lock => { + clock.precision = lock ? 60 : 1; + clock.tick(); + renderBar(); // hide/redraw bar right away + }); +} + clock.start(); diff --git a/apps/barclock/metadata.json b/apps/barclock/metadata.json index 0c227dc52..5b783dbda 100644 --- a/apps/barclock/metadata.json +++ b/apps/barclock/metadata.json @@ -1,7 +1,7 @@ { "id": "barclock", "name": "Bar Clock", - "version": "0.14", + "version": "0.15", "description": "A simple digital clock showing seconds as a bar", "icon": "clock-bar.png", "screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}], diff --git a/apps/barclock/settings.js b/apps/barclock/settings.js index dfe25581c..7b88b7021 100644 --- a/apps/barclock/settings.js +++ b/apps/barclock/settings.js @@ -17,10 +17,14 @@ onchange: v => save("font", v), }, }; - require("ClockFace_menu").addItems(menu, save, { + 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 index 4f99f15ac..7ab5d8587 100644 --- a/apps/barcode/ChangeLog +++ b/apps/barcode/ChangeLog @@ -7,3 +7,4 @@ 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/barcode.app.js b/apps/barcode/barcode.app.js index 89419f33c..0d9df78d5 100644 --- a/apps/barcode/barcode.app.js +++ b/apps/barcode/barcode.app.js @@ -416,13 +416,13 @@ var layout = new Layout( { // Clear the screen once, at startup g.clear(); +Bangle.setUI("clock"); Bangle.loadWidgets(); Bangle.drawWidgets(); -Bangle.setUI("clock"); layout.render(); Bangle.on('lock', function(locked) { if(!locked) { layout.render(); } -}); \ No newline at end of file +}); diff --git a/apps/barcode/metadata.json b/apps/barcode/metadata.json index cef267b2b..3f6bf06e6 100644 --- a/apps/barcode/metadata.json +++ b/apps/barcode/metadata.json @@ -2,7 +2,7 @@ "name": "Barcode clock", "shortName":"Barcode clock", "icon": "barcode.icon.png", - "version":"0.09", + "version":"0.10", "description": "EAN-8 compatible barcode clock.", "tags": "barcode,ean,ean-8,watchface,clock,clockface", "type": "clock", 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/bigdclock/ChangeLog b/apps/bigdclock/ChangeLog index 09cc978fb..b373f1876 100644 --- a/apps/bigdclock/ChangeLog +++ b/apps/bigdclock/ChangeLog @@ -3,3 +3,4 @@ 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. diff --git a/apps/bigdclock/bigdclock.app.js b/apps/bigdclock/bigdclock.app.js index c013c6188..3adf96984 100644 --- a/apps/bigdclock/bigdclock.app.js +++ b/apps/bigdclock/bigdclock.app.js @@ -85,7 +85,8 @@ Bangle.on('charging', (charging) => { draw(); }); +Bangle.setUI("clock"); + Bangle.loadWidgets(); draw(); -Bangle.setUI("clock"); diff --git a/apps/bigdclock/metadata.json b/apps/bigdclock/metadata.json index 7359bcf20..ce91d921e 100644 --- a/apps/bigdclock/metadata.json +++ b/apps/bigdclock/metadata.json @@ -1,7 +1,7 @@ { "id": "bigdclock", "name": "Big digit clock containing just the essentials", "shortName":"Big digit clk", - "version":"0.05", + "version":"0.06", "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", diff --git a/apps/bikespeedo/ChangeLog b/apps/bikespeedo/ChangeLog index 2a3023750..10752ee2b 100644 --- a/apps/bikespeedo/ChangeLog +++ b/apps/bikespeedo/ChangeLog @@ -1,2 +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/metadata.json b/apps/bikespeedo/metadata.json index c3de0487c..80b91427c 100644 --- a/apps/bikespeedo/metadata.json +++ b/apps/bikespeedo/metadata.json @@ -2,7 +2,7 @@ "id": "bikespeedo", "name": "Bike Speedometer (beta)", "shortName": "Bike Speedometer", - "version": "0.02", + "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"}], diff --git a/apps/bikespeedo/settings.js b/apps/bikespeedo/settings.js index a3921f4a3..f41524263 100644 --- a/apps/bikespeedo/settings.js +++ b/apps/bikespeedo/settings.js @@ -33,12 +33,10 @@ '< Back': function() { E.showMenu(appMenu); }, 'Speed' : { value : settings.spdFilt, - format : v => v?"On":"Off", onchange : () => { settings.spdFilt = !settings.spdFilt; writeSettings(); } }, 'Altitude' : { value : settings.altFilt, - format : v => v?"On":"Off", onchange : () => { settings.altFilt = !settings.altFilt; writeSettings(); } } }; 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/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 30ac31c61..0c6e8cb52 100644 --- a/apps/boldclk/ChangeLog +++ b/apps/boldclk/ChangeLog @@ -3,3 +3,4 @@ 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 9d3ea0756..763530a32 100644 --- a/apps/boldclk/bold_clock.js +++ b/apps/boldclk/bold_clock.js @@ -130,9 +130,10 @@ Bangle.on('lcdPower', (on) => { } }); +// 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 cf961347d..086203142 100644 --- a/apps/boldclk/metadata.json +++ b/apps/boldclk/metadata.json @@ -1,7 +1,7 @@ { "id": "boldclk", "name": "Bold Clock", - "version": "0.06", + "version": "0.07", "description": "Simple, readable and practical clock", "icon": "bold_clock.png", "screenshots": [{"url":"screenshot_bold.png"}], 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 a0bdfb8e9..bba15e5df 100644 --- a/apps/bowserWF/metadata.json +++ b/apps/bowserWF/metadata.json @@ -1,18 +1,18 @@ -{ +{ "id": "bowserWF", "name": "Bowser Watchface", - "shortName":"Bowser Watchface", - "version":"0.02", + "shortName": "Bowser Watchface", + "version": "0.03", "description": "Let bowser show you the time", "icon": "app.png", "type": "clock", "tags": "clock", - "supports" : ["BANGLEJS2"], + "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"}] + "data": [{ "name": "bowserWF.json" }] } diff --git a/apps/bthrm/ChangeLog b/apps/bthrm/ChangeLog index 7ca8319b6..57f0ecf3d 100644 --- a/apps/bthrm/ChangeLog +++ b/apps/bthrm/ChangeLog @@ -22,3 +22,10 @@ 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 diff --git a/apps/bthrm/README.md b/apps/bthrm/README.md index 8d5872670..f4eaf43af 100644 --- a/apps/bthrm/README.md +++ b/apps/bthrm/README.md @@ -19,7 +19,14 @@ Just install the app, then install an app that uses the heart rate monitor. 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 @@ -35,6 +42,10 @@ So far it has been tested on: * 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. diff --git a/apps/bthrm/boot.js b/apps/bthrm/boot.js index e9e640563..aa97d83b7 100644 --- a/apps/bthrm/boot.js +++ b/apps/bthrm/boot.js @@ -5,11 +5,11 @@ ); 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); - } + if (param) logline += ": " + JSON.stringify(param); print(logline); } }; @@ -30,7 +30,7 @@ }; var addNotificationHandler = function(characteristic) { - log("Setting notification handler: " + supportedCharacteristics[characteristic.uuid].handler); + log("Setting notification handler"/*supportedCharacteristics[characteristic.uuid].handler*/); characteristic.on('characteristicvaluechanged', (ev) => supportedCharacteristics[characteristic.uuid].handler(ev.target.value)); }; @@ -61,7 +61,8 @@ writeCache(cache); }; - var characteristicsFromCache = function() { + var characteristicsFromCache = function(device) { + var service = { device : device }; // fake a BluetoothRemoteGATTService log("Read cached characteristics"); var cache = getCache(); if (!cache.characteristics) return []; @@ -75,6 +76,7 @@ r.properties = {}; r.properties.notify = cached.notify; r.properties.read = cached.read; + r.service = service; addNotificationHandler(r); log("Restored characteristic: ", r); restored.push(r); @@ -92,13 +94,24 @@ "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); + if (supportedCharacteristics["0x2a37"].active) stopFallback(); + if (bpmTimeout) clearTimeout(bpmTimeout); + bpmTimeout = setTimeout(()=>{ + supportedCharacteristics["0x2a37"].active = false; + startFallback(); + }, 3000); var sensorContact; @@ -141,8 +154,8 @@ src: "bthrm" }; - log("Emitting HRM: ", repEvent); - Bangle.emit("HRM", repEvent); + log("Emitting HRM", repEvent); + Bangle.emit("HRM_int", repEvent); } var newEvent = { @@ -155,7 +168,7 @@ if (battery) newEvent.battery = battery; if (sensorContact) newEvent.contact = sensorContact; - log("Emitting BTHRM: ", newEvent); + log("Emitting BTHRM", newEvent); Bangle.emit("BTHRM", newEvent); } }, @@ -200,6 +213,10 @@ }; if (settings.enabled){ + Bangle.isBTHRMActive = function (){ + return supportedCharacteristics["0x2a37"].active; + }; + Bangle.isBTHRMOn = function(){ return (Bangle._PWR && Bangle._PWR.BTHRM && Bangle._PWR.BTHRM.length > 0); }; @@ -210,24 +227,28 @@ } if (settings.replace){ - var origIsHRMOn = Bangle.isHRMOn; + Bangle.origIsHRMOn = Bangle.isHRMOn; Bangle.isHRMOn = function() { if (settings.enabled && !settings.replace){ - return origIsHRMOn(); + return Bangle.origIsHRMOn(); } else if (settings.enabled && settings.replace){ return Bangle.isBTHRMOn(); } - return origIsHRMOn() || Bangle.isBTHRMOn(); + return Bangle.origIsHRMOn() || Bangle.isBTHRMOn(); }; } - var clearRetryTimeout = function() { + 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() { @@ -236,8 +257,8 @@ if (!currentRetryTimeout){ var clampedTime = retryTime < 100 ? 100 : retryTime; - - log("Set timeout for retry as " + clampedTime); + + log("Set timeout for retry as " + clampedTime); clearRetryTimeout(); currentRetryTimeout = setTimeout(() => { log("Retrying"); @@ -257,11 +278,11 @@ var buzzing = false; var onDisconnect = function(reason) { log("Disconnect: " + reason); - log("GATT: ", gatt); - log("Characteristics: ", characteristics); - retryTime = initialRetryTime; - clearRetryTimeout(); - switchInternalHrm(); + log("GATT", gatt); + log("Characteristics", characteristics); + clearRetryTimeout(reason != "Connection Timeout"); + supportedCharacteristics["0x2a37"].active = false; + startFallback(); blockInit = false; if (settings.warnDisconnect && !buzzing){ buzzing = true; @@ -273,13 +294,13 @@ }; var createCharacteristicPromise = function(newCharacteristic) { - log("Create characteristic promise: ", 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 for " + JSON.stringify(newCharacteristic)); + log("Reading data", newCharacteristic); return newCharacteristic.readValue().then((data)=>{ if (supportedCharacteristics[newCharacteristic.uuid] && supportedCharacteristics[newCharacteristic.uuid].handler) { supportedCharacteristics[newCharacteristic.uuid].handler(data); @@ -289,8 +310,8 @@ } if (newCharacteristic.properties.notify){ result = result.then(()=>{ - log("Starting notifications for: ", newCharacteristic); - var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started for ", newCharacteristic)); + log("Starting notifications", newCharacteristic); + var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic)); if (settings.gracePeriodNotification > 0){ log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications"); startPromise = startPromise.then(()=>{ @@ -301,7 +322,7 @@ return startPromise; }); } - return result.then(()=>log("Handled characteristic: ", newCharacteristic)); + return result.then(()=>log("Handled characteristic", newCharacteristic)); }; var attachCharacteristicPromise = function(promise, characteristic) { @@ -312,11 +333,11 @@ }; var createCharacteristicsPromise = function(newCharacteristics) { - log("Create characteristics promise: ", newCharacteristics); + log("Create characteristics promis ", newCharacteristics); var result = Promise.resolve(); for (var c of newCharacteristics){ if (!supportedCharacteristics[c.uuid]) continue; - log("Supporting characteristic: ", c); + log("Supporting characteristic", c); characteristics.push(c); if (c.properties.notify){ addNotificationHandler(c); @@ -328,10 +349,10 @@ }; var createServicePromise = function(service) { - log("Create service promise: ", service); + log("Create service promise", service); var result = Promise.resolve(); result = result.then(()=>{ - log("Handling service: " + service.uuid); + log("Handling service" + service.uuid); return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c)); }); return result.then(()=>log("Handled service" + service.uuid)); @@ -368,7 +389,7 @@ } promise = promise.then((d)=>{ - log("Got device: ", d); + log("Got device", d); d.on('gattserverdisconnected', onDisconnect); device = d; }); @@ -379,14 +400,14 @@ }); } else { promise = Promise.resolve(); - log("Reuse device: ", device); + log("Reuse device", device); } promise = promise.then(()=>{ if (gatt){ - log("Reuse GATT: ", gatt); + log("Reuse GATT", gatt); } else { - log("GATT is new: ", gatt); + log("GATT is new", gatt); characteristics = []; var cachedId = getCache().id; if (device.id !== cachedId){ @@ -404,7 +425,10 @@ promise = promise.then((gatt)=>{ if (!gatt.connected){ - var connectPromise = gatt.connect(connectSettings); + log("Connecting..."); + var connectPromise = gatt.connect(connectSettings).then(function() { + log("Connected."); + }); if (settings.gracePeriodConnect > 0){ log("Add " + settings.gracePeriodConnect + "ms grace period after connecting"); connectPromise = connectPromise.then(()=>{ @@ -432,7 +456,7 @@ promise = promise.then(()=>{ if (!characteristics || characteristics.length === 0){ - characteristics = characteristicsFromCache(); + characteristics = characteristicsFromCache(device); } }); @@ -445,11 +469,11 @@ }); characteristicsPromise = characteristicsPromise.then((services)=>{ - log("Got services:", 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); + log("Supporting service", service.uuid); result = attachServicePromise(result, service); } if (settings.gracePeriodService > 0) { @@ -473,7 +497,7 @@ return promise.then(()=>{ log("Connection established, waiting for notifications"); characteristicsToCache(characteristics); - clearRetryTimeout(); + clearRetryTimeout(true); }).catch((e) => { characteristics = []; log("Error:", e); @@ -491,12 +515,14 @@ isOn = Bangle._PWR.BTHRM.length; // so now we know if we're really on if (isOn) { + switchFallback(); if (!Bangle.isBTHRMConnected()) initBt(); } else { // not on log("Power off for " + app); + clearRetryTimeout(true); if (gatt) { if (gatt.connected){ - log("Disconnect with gatt: ", gatt); + log("Disconnect with gatt", gatt); try{ gatt.disconnect().then(()=>{ log("Successful disconnect"); @@ -511,7 +537,33 @@ } }; - var origSetHRMPower = Bangle.setHRMPower; + if (settings.replace){ + Bangle.on("HRM", (e) => { + e.modified = true; + Bangle.emit("HRM_int", e); + }); + + Bangle.origOn = Bangle.on; + Bangle.on = function(name, callback) { + if (name == "HRM") { + Bangle.origOn("HRM_int", callback); + } else { + Bangle.origOn(name, callback); + } + }; + + Bangle.origRemoveListener = Bangle.removeListener; + Bangle.removeListener = function(name, callback) { + if (name == "HRM") { + Bangle.origRemoveListener("HRM_int", callback); + } else { + Bangle.origRemoveListener(name, callback); + } + }; + + } + + Bangle.origSetHRMPower = Bangle.setHRMPower; if (settings.startWithHrm){ @@ -521,40 +573,54 @@ Bangle.setBTHRMPower(isOn, app); } if ((settings.enabled && !settings.replace) || !settings.enabled){ - origSetHRMPower(isOn, app); + Bangle.origSetHRMPower(isOn, app); } }; } - var fallbackInterval; + var fallbackActive = false; + var inSwitch = false; - var switchInternalHrm = function() { - 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); + 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); - origSetHRMPower(0, app); + Bangle.origSetHRMPower(0, app); Bangle.setBTHRMPower(1, app); if (Bangle._PWR.HRM===undefined) break; } } - switchInternalHrm(); } E.on("kill", ()=>{ diff --git a/apps/bthrm/bthrm.js b/apps/bthrm/bthrm.js index dd9230386..fadf2a5d8 100644 --- a/apps/bthrm/bthrm.js +++ b/apps/bthrm/bthrm.js @@ -42,12 +42,20 @@ function draw(y, type, event) { if (event.energy) str += " kJoule: " + event.energy.toFixed(0); g.setFontVector(12).drawString(str,px,y+60); } - } var firstEventBt = true; var firstEventInt = true; + +// 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); diff --git a/apps/bthrm/metadata.json b/apps/bthrm/metadata.json index 39c1ff8bb..4d2cb811b 100644 --- a/apps/bthrm/metadata.json +++ b/apps/bthrm/metadata.json @@ -2,7 +2,7 @@ "id": "bthrm", "name": "Bluetooth Heart Rate Monitor", "shortName": "BT HRM", - "version": "0.09", + "version": "0.12", "description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.", "icon": "app.png", "type": "app", diff --git a/apps/bthrm/recorder.js b/apps/bthrm/recorder.js index 21345a907..ed36b5aef 100644 --- a/apps/bthrm/recorder.js +++ b/apps/bthrm/recorder.js @@ -32,8 +32,45 @@ 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 = "", src=""; + function onHRM(h) { + bpmConfidence = h.confidence; + bpm = h.bpm; + srv = h.src; + if (h.bpm > 0){ + active = true; + print("active" + h.bpm); + if (bpmTimeout) clearTimeout(bpmTimeout); + bpmTimeout = setTimeout(()=>{ + print("inactive"); + active = false; + },3000); + } + } + return { + name : "HR int", + fields : ["Heartrate", "Confidence"], + getValues : () => { + var r = [bpm,bpmConfidence,src]; + bpm = ""; bpmConfidence = ""; src=""; + return r; + }, + start : () => { + Bangle.origOn('HRM', onHRM); + if (Bangle.origSetHRMPower) Bangle.origSetHRMPower(1,"recorder"); + }, + stop : () => { + Bangle.removeListener('HRM', 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/settings.js b/apps/bthrm/settings.js index b376d6a2d..2b19ea46a 100644 --- a/apps/bthrm/settings.js +++ b/apps/bthrm/settings.js @@ -17,6 +17,14 @@ var settings; readSettings(); + 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' }, @@ -35,7 +43,6 @@ case 1: writeSettings("enabled",true); writeSettings("replace",true); - writeSettings("debuglog",false); writeSettings("startWithHrm",true); writeSettings("allowFallback",true); writeSettings("fallbackTimeout",10); @@ -43,17 +50,11 @@ 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); + applyCustomSettings(); break; } writeSettings("mode",v); @@ -85,14 +86,12 @@ '< 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); } @@ -140,23 +139,23 @@ '< 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': { @@ -167,6 +166,7 @@ format: v=>v+"s", onchange: v => { writeSettings("custom_fallbackTimout",v*1000); + if (settings.mode == 3) applyCustomSettings(); } }, }; diff --git a/apps/bwclk/ChangeLog b/apps/bwclk/ChangeLog index ecd0c355f..81aab5050 100644 --- a/apps/bwclk/ChangeLog +++ b/apps/bwclk/ChangeLog @@ -6,4 +6,15 @@ 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. \ No newline at end of file +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. \ No newline at end of file diff --git a/apps/bwclk/README.md b/apps/bwclk/README.md index f6a1c6522..dfb9bf515 100644 --- a/apps/bwclk/README.md +++ b/apps/bwclk/README.md @@ -1,17 +1,46 @@ # BW Clock +A very minimalistic clock to mainly show date and time. ![](screenshot.png) ## Features -- Fullscreen on/off -- Tab left/right of screen to show steps, temperature etc. -- Enable / disable lock icon in the settings. -- If the "sched" app is installed tab top / bottom of the screen to set the timer. -- The design is adapted to the theme of your bangle. -- The colon (e.g. 7:35 = 735) can be hidden now in the settings. +The BW clock provides many features and also 3rd party integrations: +- Bangle data such as steps, heart rate, battery or charging state. +- A timer can be set directly. *Requirement: Scheduler library* +- 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 set a timer or send a HomeAssistant trigger. + +Simply click left / right to go through the menu entries such as Bangle, Timer 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. + +``` + +5min + | + Bangle -- Timer[Optional] -- Agenda 1[Optional] -- Weather[Optional] -- HomeAssistant [Optional] + | | | | | + Bpm -5min Agenda 2 Temperature Trigger1 + | | | | + Steps ... ... ... + | + Battery +``` + ## Thanks to Icons created by Flaticon ## Creator -- [David Peer](https://github.com/peerdavid) +[David Peer](https://github.com/peerdavid) diff --git a/apps/bwclk/app.js b/apps/bwclk/app.js index 5bfec4097..0a955cf7f 100644 --- a/apps/bwclk/app.js +++ b/apps/bwclk/app.js @@ -1,25 +1,27 @@ -/* +/************ * Includes */ const locale = require('locale'); const storage = require('Storage'); -/* +/************ * Statics */ const SETTINGS_FILE = "bwclk.setting.json"; -const TIMER_IDX = "bwclk"; +const TIMER_IDX = "bwclk_timer"; +const TIMER_AGENDA_IDX = "bwclk_agenda"; const W = g.getWidth(); const H = g.getHeight(); -/* +/************ * Settings */ let settings = { - fullscreen: false, + screen: "Normal", showLock: true, hideColon: false, - showInfo: 0, + menuPosX: 0, + menuPosY: 0, }; let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; @@ -28,31 +30,21 @@ for (const key in saved_settings) { } -/* +/************ * Assets */ - // Manrope font Graphics.prototype.setLargeFont = function(scale) { - // Actual height 48 (49 - 2) + // Actual height 47 (48 - 2) this.setFontCustom( - E.toString(require('heatshrink').decompress(atob('AFcH+AHFh/gA4sf4AHFn+AA4t/E43+AwsB/gHFgf4PH4AMgJ9Ngf/Pot//6bF/59F///PokfA4J9DEgIABEwYkB/7DDEgIlFCoRMDEgQsEDoRLEEgpoBA4JhGOIsHZ40PdwwA/L4SjHNAgGCP4cHA4wWDA4aVCA4gGDA4SNBe4IiBA4MPHYRBBEwScCA4d/EQUBaoRKDA4UBLQYECgb+EAgMHYYcHa4MPHoLBCBgMfYgcfBgM/PIc/BgN/A4YECIIQEDHwkDHwQHDGwQHENQUHA4d/QIQnCRIJJCSgYTCA4hqCA4hqCA4hiCA4ZCEA4RFBGYbrFAHxDGSohdDcgagFAAjPCEzicDToU/A4jPCAwbQCBwgrBgIHEFYKrDWoa7DaggA/AC0PAYV+AYSBCgKpCg4DDVIUfAYZ9BToIDDPoKVBAYfARoQDDXgMPFwTIBdYSYCv4LCv7zCXgYKCXAK8CHoUPXgY9Cn/vEYMPEwX/z46Bj4mBgf+n77CDwX4v54EIIIzCOgX/4I+CAQI9BHYQCCQ4I7CRASDBHYQHCv/Aj4+BGYIeBGAI+Bj/8AIIRBQIZjCRIiWBXgYHCPQgHBBgJ6DA4IEBPQaKBGYQ+BbgiCCAGZFDIIUBaAZBCgYHCQAQTBA4SACUwS8DDYQHBQAbVCQAYwBA4SABgYEBPoQCBFgU/CQWACgRDCHwKVCIYX+aYRDCHwMPAgY+Cn4EDHwX/AgY+B8bEFj/HA4RGCn+f94MBv45Cv+fA4J6C//+j5gBGIMBFoJWBQoRMB8E//4DBHIJcBv4HBEwJUCA4ImCj5MBA4KZCPYQHBZgRBCE4LICvwaCXAYA5PgQAEMIQAEUwQADQAJlCAARlBWYIACT4JtDAAMPA4IWESgg8CAwI+EEoPhHwYlCgY+DEoP4g4+DEoPAh4+CEoReBHwUfLYU/CwgMBXARqBHYQCCGoIjBgI+CgZSCHwcHAYY+Ch4lBJ4IbCjhACPwqUBPwqFCPwhQBIQZ+DOAKVFXooHCXop9DFAi8EFAT0GPoYAygwFEgOATISLDwBWDTQc/A4L6CTQKkCVQX+BYIHBDwX+BYIHBVQX8B4KqD+/wA4aBBj/AgK8CQIIJBA4a/BBIMBAgL/BAgUDYgL/BAII7BAQXgAII7BAQXAYQQxBYARrCMwQ0BAgV/HwYECHwgEBgY+EA4MPGwI8BA4UfGwI8BgYHBPofAQYOHPoeAR4QmBHwQHCEwI+CA4RVBHwQHCaggnBDwQHEHoIAEEQIA6v5NFfgSECBwZtEf4IHFOYQHEj4HGDwYHCDwPgv/jA4UHXQS8E/ED/AHDZ4MPSYKlCv+AYwIHDDwL7EgL7DAgTzCEwIpCeYTZBg4CBeYIJBAgICBFgIJBAgICBeYIEDHII0BAgg+EgI5CMocHGwJBCA4MfGwMD/h/BwF/PoQHC451CJIMDSgIjBA4PAA4QmBA4IhBA4JVBgEMA4bUDV4QeCAAf/HoIAENIIApOoIAEW4QAEW4QAEW4QAEWQRSFNIcDfYQMDny8DO4Q7BAQQjCewh+EHwcPToQ+Dv//ewkHUoI+En68DeIS0EHwMf/46CeYYlCHwQ0BKIY+BGgJ4Dh/nGgZZCAwKPEHYLpFDoKuFGgj4JgY0EHwQ0EYhIA6MAkf+BRBLIa5BQAJSCBgP4R4iVB/YHERoIACA4QGDE4SFBAoV/A4MH/ggBWIL7C8EfVoL4DwBHBFYIHBfYIRBAgT7CDgQEBgP4BgUBEIMDDgIMBgYMBg/gBgS5Ch/ABgUPFIMf4EHA4IEBHwUPCgJGCIIM/CgLgCAQJlBFIQFB44HBEIUBQYc/EIIHDAAIuBA4oeBRoSfBLAIHC/gHBEwIXC+AHBZghHBDwQADj4WCAHEPAwpWBKYYOCLwIHELYJUBghlDA4UcQogHBvgeDD4K0DDwIHBWgQeB4CyBh68CUAMf8DeCdIYHDdIfAfYjxCAgj2BAgbHCvwJCIIYCBBIMDHIX4BgUHFwMD+AMCA4Q0BAgg5CHwxICAQY5BdgQHBEgMDIYV/DgR1CA4PwP4KvDRgIACEYIHFWggABMQQHEZwd/Dwq1DHoTFEdooA/ACrBBcAZmC8DTCAATGBaYR+DwDTCRwbYDAASLBCIIGCFgQRBAG4='))), + 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("EhooGyUkJiUnISYnFQ=="), - 63+(scale<<8)+(1<<16) + atob("ExspGyUkJiQnISYnFQ=="), + 62+(scale<<8)+(1<<16) ); return this; }; -Graphics.prototype.setXLargeFont = function(scale) { - // Actual height 53 (55 - 3) - this.setFontCustom( - E.toString(require('heatshrink').decompress(atob('AHM/8AIG/+AA4sD/wQGh/4EWQA/AC8YA40HNA0BRY8/RY0P/6LFgf//4iFA4IiFj4HBEQkHCAQiDHIIZGv4HCFQY5BDAo5CAAIpDDAfACA3wLYv//hsFKYxcCMgoiBOooiBQwwiBS40AHIgA/ACS/DLYjYCBAjQEBAYQDBAgHDUAbyDZQi3CegoHEVQQZFagUfW4Y0DaAgECaIJSEFYMPbIYNDv5ACGAIrBCgJ1EFYILCAAQWCj4zDGgILCegcDEQRNDHIIiCHgZ2BEQShFIqUDFYidCh5ODg4NCn40DAgd/AYR5BDILZEAAIMDAAYVCh7aHdYhKDbQg4Dv7rGBAihFCAwIDCAgA/AB3/eoa7GAAk/dgbVGDJrvCDK67DDIjaGdYpbCdYonCcQjjDEVUBEQ4A/AEMcAYV/NAUHcYUDawd/cYUPRYSmBBgaLBToP8BgYiBSgIiCj4iCg//EQSuDW4IMDVwYiCBgIiBBgrRDCATeBaIYqCv70DCgT4CEQMfIgQZBBoRnDv/3EQIvBDIffEQMHFwReBRYUfOgX/+IiDKIeHEQRRECwUHKwIuB8AiDIoJEBCwZFCv/4HIZaBIgPAEQS2CUYQiCD4SABEQcfOwIZBEQaHBO4RcEAAI/BEQQgBSIQiDTIRZBEQZuBVYQiDHoKWCEQQICFQIiDBAQeCEQQA/AANwA40BLIJ5BO4JWCBAUPAYR5En7RBUIQECN4SYCQQIiEh6CCEQk/BoQiBgYeCBoTrCAgT0CCgIfCFYQiBg4IBGgIiDj6rBg4rCBYLRDFYIiBbYIfBLgQiBIQYiD4JCCLgf/bQIWDBYV/EQV/BYXz/5FBgIiD5//IowZBD4M/NAX/BIPgDIJoC//5GgKUDn//4f/8KLE/wTBAAI8BEQPwj4HBVwYmBDgIZDN4QZCGYKJCHQP/JoSgCBATrCh5dBKITVDG4gICAAbvDAH5SCL4QADK4J5CCAiTCCAp1BCAqCDCAgiGCAIiFCAQiFeoIiFg6/FCAgiECAXnEQgQB/kfEQYQC4F/EQYQCgIiDfoIQBg4iDCAUAEQZUCcgIiDDIIQBEQhuBBoIiENoYiFDwQiECAQiFwEBPQQNCAQKDDEYMDDoMfRh4iGUwqvEESBiBaQ5oEbgr0FNAo+EEIwA+oAHGgJoFRAMHe4L0CAALNBBAT0BfwScDCAXweAL0DWgUPQYQiDwF/QYQiC/zTB+C0FBAL0CEQYIBGgMPCgIxBg4rCJIKsCh5IBBwTPCj4WBgYLBZ4V/MAIiBBQQrBEQYtCBYQiCO4QLFCwgiDIQIiGIoMHEQpFBn5FFD4JoENwRoGDgSUCAoKfBw//DgIiCT4auCFwN/T4RRET4TaCEQKoCDIQiCGgK/DAAQICdYQACHoIqCBAoQFEwIhFAH4AFQIROEj4IGXwIIGNwIACbgIhEBAiRCVwoqDTogHEW4QZFXgIZB/z9Cv49CF4MPBwI0Ca4LlB8ATCJoP4AoINDfQPAg7PBg4cBBwUfD4MfFYILCCwgOCf4QLEwEPCwILCgJaBn4WBBYQxCIQQiD+EDCYI5CBYRQBIo4fBMQIuBC4N/NAv8AoIcBSgU/FYIIBZIYrCW4hOCXIQZCgYUBv7jEh4uBZAscewZ8CgEgUYT0EEoQIBA4gICFQQIEHYQA+KQzdDAArdCAArpCEScHaIQiEvwiGe4QiFUwQiEbgIiFYIL0DEQTkBEQrJEEQc/cYYiCg4HBDIQiCfoRoEHQLaDEQQHBbQYiBCAT8Dn/BCAoXBJYP/OgZKC/6OEEARLCEQZLEEQZLEEQjKFEQI6EEQZLDEQbsGEQLjGYYYA/JIxzEg/AfgJSDAoPgfgiDC8COFAoPnaQj6CAAR+CW4TCFA4i6CDIqhCDIfwHoYHCYIN/GgKuBJ4JDBFYUf/C5CBYIZBv/Ag4ZBg4rBBYQTBAQIcBg4FBn5UBAQUfFwIfCEQeAgYfBAQUBFAKbCAQQiCGwIiE+A2BwBFNwE/AoM/EQJoIWwKCCh4cBFYKUERYV/W46uHFYIZGaJA0B/glBGYT0JIITiEMIJvCFQQAEHYQA/ABBlEOIhdGQAIRFSgQIBgQICn4IB8EAjiBCUYglCbQYeBEoQZCTwM/CYIZD/gEBUwIzBJ4UHYAU/EwIrBh4rCAoIXCn4rBCgUDAQN/FYMfBYIXBCYJnCBYXggf8HgQLCwEPEQQuBgJOECwILDCwgiLHIUHBYJFGD4IxBgYWCn4rBBwJoFDIYNBCgPADgKHBRYfDBQN/GAIrBToTLDVwYACDILiCWAb8DAAYzBYAjTCAAI9BAARNCBAoqCBAgQDFgbYCAH4AufgQACf4T8CAAT/CfgQACBwITCAAYOBCYQioh4iEAHQA=='))), - 46, - atob("FR4uHyopKyksJSssGA=="), - 70+(scale<<8)+(1<<16) - ); -}; Graphics.prototype.setMediumFont = function(scale) { // Actual height 41 (42 - 2) @@ -60,96 +52,326 @@ Graphics.prototype.setMediumFont = function(scale) { return this; }; + Graphics.prototype.setSmallFont = function(scale) { // Actual height 28 (27 - 0) - this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//84D//zgP/+GAAAAAAAAAAAAAAAAAAAD4AAAPgAAA+AAAAAAAAAAAAA+AAAD4AAAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAg4AAHDgAAcOCABw54AHD/gAf/8AD/8AB//gAP8OAA9w4YCHD/gAcf+AB//gAf/gAP/uAA/w4ADnDgAAcOAABw4AAHAAAAcAAAAAAAAAAAAAAAIAA+A4AH8HwA/4PgHjgOAcHAcBwcBw/BwH78DgfvwOB8HA4HAOBw8A+HngB4P8ADgfgAAAYAAAAAAAAAAB4AAAf4AQB/gDgOHAeA4cDwDhweAOHDwA88eAB/nwAD88AAAHgAAA8AAAHn4AA8/wAHnvgA8cOAHhg4A8GDgHgcOA8B74BgD/AAAH4AAAAAAAAAAAAAAAAAMAAAH8AD8/4Af/3wB/8HgODwOA4HA4DgODgOAcOA4A44DwDzgHAH8AMAPwAQP+AAA/8AAAB4AAADAAAAAA+AAAD4AAAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/8AD//+A/+/+H4AD98AAB3gAADIAAAAAAAAAAAAAIAAABwAAAXwAAHPwAB8P8D/gP//4AH/8AAAAAAAAAAAAAAAAAAAAAAAAGAAAA4gAAB/AAAH8AAD/AAAP8AAAH4AAAfwAADiAAAOAAAAAAAAAAAAAAGAAAAYAAABgAAAGAAAAYAAABgAAD/+AAP/4AABgAAAGAAAAYAAABgAAAGAAAAYAAAAAAAAAAAAAADkAAAPwAAA/AAAAAAAAAAAAAAAAAAAAAAAAABgAAAGAAAAYAAABgAAAGAAAAYAAABgAAAGAAAAYAAAAAAAAAAAAAAAAAAAAAAADgAAAOAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAA4AAA/gAA/+AA//AA//AAP/AAA/AAADAAAAAAAAAAAAAAAAAAA//gAP//gB///AHgA8A8AB4DgADgOAAOA4AA4DgADgPAAeAeADwB///AD//4AD/+AAAAAAAAAAAAAAAA4AAAHgAAAcAAADwAAAP//+A///4D///gAAAAAAAAAAAAAAAAAAAAAAYAeADgD4AeAfAD4DwAfgOAD+A4Ae4DgDzgOAeOA4Dw4DweDgH/wOAP+A4AfwDgAAAAAAAAAAAAIAOAA4A4ADwDggHAOHgOA48A4DnwDgO/AOA7uA4D84HgPh/8A8H/gDgH8AAACAAAAAAAAAAAAAHgAAB+AAA/4AAP7gAD+OAA/g4AP4DgA+AOADAA4AAB/+AAH/4AAf/gAADgAAAOAAAAAAAAAAAAAAAAD4cAP/h4A/+HwDw4HgOHAOA4cA4DhwDgOHAOA4cA4Dh4HAOD58A4H/gAAP8AAAGAAAAAAAAAAAAAAAAD/+AAf/8AD//4AePDwDw4HgOHAOA4cA4DhwDgOHAOA4cB4Bw8PAHD/8AIH/gAAH4AAAAAAAAAADgAAAOAAAA4AAYDgAHgOAD+A4B/wDgf4AOP+AA7/AAD/gAAP4AAA8AAAAAAAAAAAAAAAAAAeH8AD+/4Af//wDz8HgOHgOA4OA4Dg4DgODgOA4eA4Dz8HgH//8AP7/gAeH8AAAAAAAAAAAAAAAA+AAAH+AgB/8HAHh4cA8Dg4DgODgOAcOA4Bw4DgODgPA4eAeHDwB///AD//4AD/+AAAAAAAAAAAAAAAAAAAAAAAAAAODgAA4OAADg4AAAAAAAAAAAAAAAAAAAAAAAAAAAAABwA5AHAD8AcAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAB8AAAP4AAB5wAAPDgAB4HAAHAOAAIAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGMAAAYwAABjAAAGMAAAYwAABjAAAGMAAAYwAABjAAAGMAAAYwAABjAAAGMAAAYwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAEAAcA4AB4HAADw4AADnAAAH4AAAPAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAB8AAAHgAAA4AAADgDzgOA/OA4D84DgeAAPHwAAf+AAA/wAAB8AAAAAAAAAAAAAAAAAAD+AAB/+AAP/8AB4B4AOABwBwADgHB8OA4P4cDhxxwMGDDAwYMMDBgwwOHHHA4f4cDh/xwHAHCAcAMAA8AwAB8PAAD/4AAD/AAAAAAAAAAAAAACAAAB4AAB/gAA/8AAf+AAP/wAH/nAA/gcADwBwAPwHAA/4cAA/9wAAf/AAAP/AAAD/gAAB+AAAA4AAAAAAAAAAAAAAD///gP//+A///4DgcDgOBwOA4HA4DgcDgOBwOA4HA4Dg8DgPHwOAf/h4A///AB8f4AAAfAAAAAAAP+AAD/+AAf/8AD4D4AeADwBwAHAOAAOA4AA4DgADgOAAOA4AA4DgADgOAAOAcABwB4APAD4D4AHgPAAOA4AAAAAAAAAAAAAAAP//+A///4D///gOAAOA4AA4DgADgOAAOA4AA4DgADgOAAOA8AB4BwAHAHwB8AP//gAP/4AAP+AAAAAAAAAAAAAAAA///4D///gP//+A4HA4DgcDgOBwOA4HA4DgcDgOBwOA4HA4DgcDgOBgOA4AA4AAAAAAAAAAAAAAD///gP//+A///4DgcAAOBwAA4HAADgcAAOBwAA4HAADgcAAOAwAA4AAAAAAAAAf+AAD/+AA//+ADwB4AeADwDwAHgOAAOA4AA4DgADgOAAOA4AA4DgMDgPAweAcDBwB8MfADw/4AHD/AAAPwAAAAAAAAAAAAAAAP//+A///4D///gABwAAAHAAAAcAAABwAAAHAAAAcAAABwAAAHAAAAcAAABwAA///4D///gP//+AAAAAAAAAAAAAAAAAAAD///gP//+A///4AAAAAAAAAAAADgAAAPAAAA+AAAA4AAADgAAAOAAAA4AAAHgP//8A///wD//8AAAAAAAAAAAAAAAAAAAA///4D///gP//+AAHAAAA+AAAP8AAB54AAPDwAB4HgAPAPAB4AfAPAA+A4AA4DAABgAAACAAAAAAAAAAP//+A///4D///gAAAOAAAA4AAADgAAAOAAAA4AAADgAAAOAAAA4AAADgAAAAAAAAAAAAAAP//+A///4D///gD+AAAD+AAAB+AAAB/AAAB/AAAB/AAAB+AAAH4AAB+AAA/gAAP4AAD+AAA/AAAfwAAD///gP//+A///4AAAAAAAAAAAAAAAAAAAP//+A///4D///gHwAAAPwAAAPgAAAfgAAAfAAAAfAAAA/AAAA+AAAB+AAAB8A///4D///gP//+AAAAAAAAAAAP+AAD/+AAf/8AD4D4AeADwBwAHAOAAOA4AA4DgADgOAAOA4AA4DgADgOAAOAcABwB4APAD4D4AH//AAP/4AAP+AAAAAAAAAAAP//+A///4D///gOAcAA4BwADgHAAOAcAA4BwADgHAAOAcAA4DgAD4eAAH/wAAP+AAAPgAAAAAAAA/4AAP/4AB//wAPgPgB4APAHAAcA4AA4DgADgOAAOA4AA4DgADgOAAOA4AO4BwA/AHgB8APgPwAf//gA//uAA/4QAAAAAAAAAA///4D///gP//+A4BwADgHAAOAcAA4BwADgHAAOAcAA4B8ADgP8APh/8Af/H4A/4HgA+AGAAAAAAAAAAAABgAHwHAA/g+AH/A8A8cBwDg4DgODgOA4OA4DgcDgOBwOA4HA4DwODgHg4cAPh/wAcH+AAwPwAAAAADgAAAOAAAA4AAADgAAAOAAAA4AAAD///gP//+A///4DgAAAOAAAA4AAADgAAAOAAAA4AAADgAAAAAAAAAAAAAAAAAP//AA///AD//+AAAB8AAABwAAADgAAAOAAAA4AAADgAAAOAAAA4AAAHgAAA8A///gD//8AP//gAAAAAAAAAAIAAAA8AAAD+AAAH/AAAD/wAAB/4AAA/8AAAf4AAAPgAAB+AAA/4AAf+AAP/AAH/gAD/wAAP4AAA4AAAAAAAAPAAAA/gAAD/4AAA/+AAAf/AAAH/gAAB+AAAf4AAf/AAf/AAP/gAD/gAAPwAAA/4AAA/+AAAf/AAAH/wAAB/gAAB+AAB/4AA/+AA/+AA/+AAD/AAAPAAAAgAAAAAAAAMAAGA4AA4D4APgHwB8APwfAAPn4AAf+AAAfwAAB/AAAf+AAD4+AA/B8AHwB8A+AD4DgADgMAAGAwAAADwAAAPwAAAPwAAAfgAAAfgAAAf/4AAf/gAH/+AB+AAAPwAAD8AAA/AAADwAAAMAAAAgAAAAAAAAMAACA4AA4DgAPgOAD+A4Af4DgH7gOB+OA4Pw4Dj8DgO/AOA/4A4D+ADgPgAOA4AA4DAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/////////gAAAOAAAA4AAADAAAAAAAAAAAAAAAAAAAAAAA4AAAD+AAAP/gAAH/4AAB/+AAAf+AAAH4AAABgAAAAAAAAADAAAAOAAAA4AAADgAAAP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAADgAAAcAAADgAAAcAAADgAAAcAAAB4AAADwAAADgAAAHAAAAOAAAAYAAAAAAAAAAAAAAAAAAAAMAAAAwAAADAAAAMAAAAwAAADAAAAMAAAAwAAADAAAAMAAAAwAAADAAAAMAAAAwAAADAAAAMAAAAwAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAHH8AA8/4AHzjgAcMOABxwYAHHBgAccOABxwwAHGHAAP/4AA//4AA//gAAAAAAAAAAAAAAAAAAA///4D///gP//+AA4BwAHADgAcAOABwA4AHADgAcAOAB4B4ADwPAAP/8AAf/AAAf4AAAAAAAAAAAAPwAAD/wAAf/gADwPAAeAeABwA4AHADgAcAOABwA4AHADgAeAeAA8DwABwOAADAwAAAAAAAAAAAA/AAAP/AAD//AAPA8AB4B4AHADgAcAOABwA4AHADgAcAOAA4BwD///gP//+A///4AAAAAAAAAAAAAAAAPwAAD/wAAf/gAD2PAAeYeABxg4AHGDgAcYOABxg4AHGDgAeYeAA/jwAB+OAAD4wAABgAAAAAAAAAAABgAAAGAAAB//+Af//4D///gPcAAA5gAADGAAAMYAAAAAAAAAPwAAD/wMA//w4DwPHgeAePBwA4cHADhwcAOHBwA4cHADhwOAcPB///4H///Af//wAAAAAAAAAAAAAAAAAAD///gP//+AA//4ADgAAAcAAABwAAAHAAAAcAAABwAAAHgAAAP/+AAf/4AA//gAAAAAAAAAAAAAAMf/+A5//4Dn//gAAAAAAAAAAAAAAAAAAHAAAAfn///+f//+5///wAAAAAAAAAAAAAAAAAAP//+A///4D///gAAcAAAD8AAAf4AADzwAAeHgAHwPAAeAeABgA4AEABgAAAAAAAAAD///gP//+A///4AAAAAAAAAAAAAAAAAAAAf/+AB//4AH//gAOAAABwAAAHAAAAcAAABwAAAHgAAAP/+AA//4AB//gAOAAABwAAAHAAAAcAAABwAAAHgAAAf/+AA//4AA//gAAAAAAAAAAAAAAAf/+AB//4AD//gAOAAABwAAAHAAAAcAAABwAAAHAAAAeAAAA//4AB//gAD/+AAAAAAAAAAAAAAAAD8AAA/8AAH/4AA8DwAHgHgAcAOABwA4AHADgAcAOABwA4AHgHgAPh8AAf/gAA/8AAA/AAAAAAAAAAAAAAAAB///8H///wf///A4BwAHADgAcAOABwA4AHADgAcAOAB4B4ADwPAAP/8AAf/AAAf4AAAAAAAAAAAAPwAAD/wAA//wADwPAAeAeABwA4AHADgAcAOABwA4AHADgAOAcAB///8H///wf///AAAAAAAAAAAAAAAAAAAH//gAf/+AB//4ADwAAAcAAABwAAAHAAAAcAAAAAAAAAAMAAHw4AA/jwAH+HgAcYOABxw4AHHDgAcMOABw44AHjjgAPH+AA8fwAAw+AAAAAABgAAAGAAAAcAAAf//wB///AH//+ABgA4AGADgAYAOABgA4AAAAAAAAAAAAAAAH/AAAf/wAB//wAAB/AAAAeAAAA4AAADgAAAOAAAA4AAADgAAAcAB//4AH//gAf/+AAAAAAAAAAAAAAABwAAAH4AAAf8AAAP8AAAH+AAAD+AAAD4AAA/gAAf8AAP+AAH/AAAfgAABwAAAAAAAAAAAABwAAAH8AAAf+AAAP/gAAD/gAAB+AAAf4AAP8AAP+AAB/AAAH4AAAf8AAAP+AAAD/gAAB+AAAf4AAf/AAP/AAB/gAAHgAAAQAAABAAIAHADgAeAeAA8HwAB8+AAD/gAAD8AAAPwAAD/gAAfPgADwfAAeAeABwA4AEAAgAAAAABAAAAHgAAAfwAAA/wAAAf4BwAP4/AAP/8AAP+AAD/AAB/wAA/4AAP8AAB+AAAHAAAAQAAAAAAIAHADgAcAeABwD4AHA/gAcHuABx84AHPDgAf4OAB/A4AHwDgAeAOABgA4AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAH4Af//////n//AAAA4AAADgAAAAAAAAAAAAAAAAAP//+A///4D///gAAAAAAAAAAAAAAAAAAA4AAADgAAAOAAAA//5/9////wAH4AAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAeAAAD4AAAOAAAA4AAADgAAAHAAAAcAAAA4AAADgAAAOAAAD4AAAPAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), 32, atob("BgkMGhEZEgYMDAwQCAwICxILEBAREBEOEREJCREVEQ8ZEhEUExAOFBQHDREPGBMUERQSEhEUERsREBIMCwwTEg4QERAREQoREQcHDgcYEREREQoPDBEPFg8PDwwIDBMc"), 28+(scale<<8)+(1<<16)); + 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; }; -var imgLock = { - width : 16, height : 16, bpp : 1, - transparent : 0, - buffer : E.toArrayBuffer(atob("A8AH4A5wDDAYGBgYP/w//D/8Pnw+fD58Pnw//D/8P/w=")) + +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; }; -var imgSteps = { - width : 24, height : 24, bpp : 1, - transparent : 1, - buffer : require("heatshrink").decompress(atob("/H///wv4CBn4CD8ACCj4IBj8f+Eeh/wjgCBngCCg/4nEH//4h/+jEP/gRBAQX+jkf/wgB//8GwP4FoICDHgICCBwIA==")) -}; -var imgBattery = { - width : 24, height : 24, bpp : 1, - transparent : 1, - buffer : require("heatshrink").decompress(atob("/4AN4EAg4TBgd///9oEAAQv8ARQRDDQQgCEwQ4OA")) -}; -var imgBpm = { - width : 24, height : 24, bpp : 1, - transparent : 1, - buffer : require("heatshrink").decompress(atob("/4AOn4CD/wCCjgCCv/8jF/wGYgOA5MB//BC4PDAQnjAQPnAQgANA")) -}; +function imgLock(){ + return { + width : 16, height : 16, bpp : 1, + transparent : 0, + buffer : E.toArrayBuffer(atob("A8AH4A5wDDAYGBgYP/w//D/8Pnw+fD58Pnw//D/8P/w=")) + } +} -var imgTemperature = { - width : 24, height : 24, bpp : 1, - transparent : 1, - buffer : require("heatshrink").decompress(atob("//D///wICBjACBngCNkgCP/0kv/+s1//nDn/8wICEBAIOC/08v//IYJECA==")) -}; +function imgSteps(){ + return { + width : 24, height : 24, bpp : 1, + transparent : 1, + buffer : require("heatshrink").decompress(atob("/H///wv4CBn4CD8ACCj4IBj8f+Eeh/wjgCBngCCg/4nEH//4h/+jEP/gRBAQX+jkf/wgB//8GwP4FoICDHgICCBwIA==")) + } +} -var imgWind = { - width : 24, height : 24, bpp : 1, - transparent : 1, - buffer : require("heatshrink").decompress(atob("/0f//8h///Pn//zAQXzwf/88B//mvGAh18gEevn/DIICB/PwgEBAQMHBAIADFwM/wEAGAP/54CD84CE+eP//wIQU/A==")) -}; +function imgBattery(){ + return { + width : 24, height : 24, bpp : 1, + transparent : 1, + buffer : require("heatshrink").decompress(atob("/4AN4EAg4TBgd///9oEAAQv8ARQRDDQQgCEwQ4OA")) + } +} -var imgTimer = { - width : 24, height : 24, bpp : 1, - transparent : 1, - buffer : require("heatshrink").decompress(atob("/+B/4CD84CEBAPygFP+F+h/x/+P+fz5/n+HnAQNn5/wuYCBmYCC5kAAQfOgFz80As/ngHn+fD54mC/F+j/+gF/HAQA==")) -}; +function imgCharging() { + return { + width : 24, height : 24, bpp : 1, + transparent : 1, + buffer : require("heatshrink").decompress(atob("//+v///k///4AQPwBANgBoMxBoMb/P+h/w/kH8H4gfB+EBwfggHH4EAt4CBn4CBj4CBh4FCCIO/8EB//Agf/wEH/8Gh//x////fAQIA=")) + } +} -var imgCharging = { - width : 24, height : 24, bpp : 1, - transparent : 1, - buffer : require("heatshrink").decompress(atob("//+v///k///4AQPwBANgBoMxBoMb/P+h/w/kH8H4gfB+EBwfggHH4EAt4CBn4CBj4CBh4FCCIO/8EB//Agf/wEH/8Gh//x////fAQIA=")) -}; +function imgBpm() { + return { + width : 24, height : 24, bpp : 1, + transparent : 1, + buffer : require("heatshrink").decompress(atob("/4AOn4CD/wCCjgCCv/8jF/wGYgOA5MB//BC4PDAQnjAQPnAQgANA")) + } +} -var imgWatch = { - width : 24, height : 24, bpp : 1, - transparent : 1, - buffer : require("heatshrink").decompress(atob("/8B//+ARANB/l4//5/1/+f/n/n5+fAQnf9/P44CC8/n7/n+YOB/+fDQQgCEwQsCHBBEC")) -}; +function imgTemperature() { + return { + width : 24, height : 24, bpp : 1, + transparent : 1, + buffer : require("heatshrink").decompress(atob("//D///wICBjACBngCNkgCP/0kv/+s1//nDn/8wICEBAIOC/08v//IYJECA==")) + } +} +function imgWeather(){ + return { + width : 24, height : 24, bpp : 1, + transparent : 0, + buffer : require("heatshrink").decompress(atob("AAcYAQ0MgEwAQUAngLB/8AgP/wACCgf/4Fz//OAQQICCIoaCEAQpGHA4ACA=")) + } +} + +function imgWind () { + return { + width : 24, height : 24, bpp : 1, + transparent : 1, + buffer : require("heatshrink").decompress(atob("/0f//8h///Pn//zAQXzwf/88B//mvGAh18gEevn/DIICB/PwgEBAQMHBAIADFwM/wEAGAP/54CD84CE+eP//wIQU/A==")) + } +} + +function imgHumidity () { + return { + width : 24, height : 24, bpp : 1, + transparent : 1, + buffer : require("heatshrink").decompress(atob("//7///+YCB+ICB8ACE4F/AQX9AQP54H//AOB+F/34CBj/gn8f4E+h/Aj0H4Ecg+AjED4ACE8E4gfwvEDEgICB/kHGwMP")) + } +} + +function imgTimer() { + return { + width : 24, height : 24, bpp : 1, + transparent : 1, + buffer : require("heatshrink").decompress(atob("/+B/4CD84CEBAPygFP+F+h/x/+P+fz5/n+HnAQNn5/wuYCBmYCC5kAAQfOgFz80As/ngHn+fD54mC/F+j/+gF/HAQA==")) + } +} + +function imgWatch() { + return { + width : 24, height : 24, bpp : 1, + transparent : 1, + buffer : require("heatshrink").decompress(atob("/8B//+ARANB/l4//5/1/+f/n/n5+fAQnf9/P44CC8/n7/n+YOB/+fDQQgCEwQsCHBBEC")) + } +} + +function imgHomeAssistant() { + return { + width : 24, height : 24, bpp : 1, + transparent : 1, + buffer : require("heatshrink").decompress(atob("/4AF84CB4YCBwICBCAP+jFH/k8g/4kkH+AFB8ACB4cY4eHzPhgmZkHnzPn8fb4/gvwUD8EYARhAC")) + } +} + +function imgAgenda() { + return { + width : 24, height : 24, bpp : 1, + transparent : 1, + buffer : require("heatshrink").decompress(atob("/4AFnPP+ALBAQX4CIgLFAQvggEBAQvAgEDAQMCwEAgwTBhgiB/AlCGQ8BGQQ")) + } +} + +function imgMountain() { + return { + width : 24, height : 24, bpp : 1, + transparent : 1, + buffer : atob("//////////////////////3///n///D//uZ//E8//A+/+Z+f8//P5//n7//3z//zn//5AAAAAAAA////////////////////") + } +} + +/************ + * 2D MENU with entries of: + * [name, icon, opt[customDownFun], opt[customUpFun], opt[customCenterFun]] + * + */ +var menu = [ + [ + function(){ return [ null, null ] }, + ], + [ + function(){ return [ "Bangle", imgWatch() ] }, + function(){ return [ E.getBattery() + "%", Bangle.isCharging() ? imgCharging() : imgBattery() ] }, + function(){ return [ getSteps(), imgSteps() ] }, + function(){ return [ Math.round(Bangle.getHealthStatus("last").bpm) + " bpm", imgBpm()] }, + function(){ return [ measureAltitude, imgMountain() ]}, + ] +] /* - * INFO ENTRIES + * Timer Menu */ -var infoArray = [ - function(){ return [ null, null, "left" ] }, - function(){ return [ "Bangle", imgWatch, "right" ] }, - function(){ return [ E.getBattery() + "%", imgBattery, "left" ] }, - function(){ return [ getSteps(), imgSteps, "left" ] }, - function(){ return [ Math.round(Bangle.getHealthStatus("last").bpm) + " bpm", imgBpm, "left"] }, - function(){ return [ getWeather().temp, imgTemperature, "left" ] }, - function(){ return [ getWeather().wind, imgWind, "left" ] }, -]; -const NUM_INFO=infoArray.length; - - -function getInfoEntry(){ - if(isAlarmEnabled()){ - return [getAlarmMinutes() + " min.", imgTimer, "left"] - } else if(Bangle.isCharging()){ - return [E.getBattery() + "%", imgCharging, "left"] - } else{ - return infoArray[settings.showInfo](); - } +try{ + require('sched'); + menu.push([ + function(){ + var text = isAlarmEnabled(TIMER_IDX) ? getAlarmMinutes(TIMER_IDX) + " min." : "Timer"; + return [text, imgTimer(), () => decreaseAlarm(TIMER_IDX), () => increaseAlarm(TIMER_IDX), null ] + }, + ]); +} catch(ex) { + // If sched is not installed, we hide this menu item } /* + * AGENDA MENU + * Note that we handle the agenda differently in order to hide old entries... + */ +var agendaIdx = 0; +var agendaTimerIdx = 0; +if(storage.readJSON("android.calendar.json") !== undefined){ + function nextAgendaEntry(){ + agendaIdx += 1; + } + + function previousAgendaEntry(){ + agendaIdx -= 1; + } + + menu.push([ + function(){ + var now = new Date(); + var agenda = storage.readJSON("android.calendar.json") + .filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000) + .sort((a,b)=>a.timestamp - b.timestamp); + + if(agenda.length <= 0){ + return ["All done", imgAgenda()] + } + + agendaIdx = agendaIdx < 0 ? 0 : agendaIdx; + agendaIdx = agendaIdx >= agenda.length ? agendaIdx -1 : agendaIdx; + + var entry = agenda[agendaIdx]; + var title = entry.title.slice(0,14); + 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) : ""; + + function dynImgAgenda(){ + if(isAlarmEnabled(TIMER_AGENDA_IDX) && agendaTimerIdx == agendaIdx){ + return imgTimer(); + } else { + return imgAgenda(); + } + } + + return [title + "\n" + dateStr, dynImgAgenda(), () => nextAgendaEntry(), () => previousAgendaEntry(), function(){ + try{ + var alarm = require('sched') + + // If other time, we disable the old one and enable this one. + if(agendaIdx != agendaTimerIdx){ + agendaTimerIdx = -1; + alarm.setAlarm(TIMER_AGENDA_IDX, undefined); + } + + // Disable alarm if enabled + if(isAlarmEnabled(TIMER_AGENDA_IDX)){ + agendaTimerIdx = -1; + alarm.setAlarm(TIMER_AGENDA_IDX, undefined); + alarm.reload(); + return + } + + // Otherwise, set alarm for given event + agendaTimerIdx = agendaIdx; + alarm.setAlarm(TIMER_AGENDA_IDX, { + msg: title, + timer : parseInt((date - now)), + }); + alarm.reload(); + } catch(ex){ } + }] + }, + ]); +} + + +/* + * WEATHER MENU + */ +if(storage.readJSON('weather.json') !== undefined){ + menu.push([ + function(){ return [ "Weather", imgWeather() ] }, + function(){ return [ getWeather().temp, imgTemperature() ] }, + function(){ return [ getWeather().hum, imgHumidity() ] }, + function(){ return [ getWeather().wind, imgWind() ] }, + ]); +} + + +/* + * HOME ASSISTANT MENU + */ +try{ + var triggers = require("ha.lib.js").getTriggers(); + var haMenu = [ + function(){ return [ "Home", imgHomeAssistant() ] }, + ]; + + triggers.forEach(trigger => { + haMenu.push(function(){ + return [trigger.display, trigger.getIcon(), () => {}, () => {}, function(){ + var ha = require("ha.lib.js"); + ha.sendTrigger("TRIGGER_BW"); + ha.sendTrigger(trigger.trigger); + }] + }); + }) + menu.push(haMenu); +} catch(ex){ + // If HomeAssistant is not installed, we hide this item +} + + +function getMenuEntry(){ + // In case the user removes HomeAssistant entries, showInfo + // could be larger than infoArray.length... + settings.menuPosX = settings.menuPosX % menu.length; + settings.menuPosY = settings.menuPosY % menu[settings.menuPosX].length; + var menuEntry = menu[settings.menuPosX][settings.menuPosY](); + + if(menuEntry[0] == null){ + return menuEntry; + } + + // For the first entry we always convert it into a callback function + // such that the menu is compatible with async functions such as + // measuring the pressure, altitude or sending http requests... + if(typeof menuEntry[0] !== 'function'){ + var value = menuEntry[0]; + menuEntry[0] = function(callbackFun){ + callbackFun(String(value), settings.menuPosX, settings.menuPosY); + } + } + return menuEntry; +} + + +/************ * Helper */ +function isFullscreen(){ + var s = settings.screen.toLowerCase(); + if(s == "dynamic"){ + return Bangle.isLocked() + } else { + return s == "full" + } +} + function getSteps() { var steps = 0; try{ @@ -164,8 +386,7 @@ function getSteps() { // In case we failed, we can only show 0 steps. } - steps = Math.round(steps/100) / 10; // This ensures that we do not show e.g. 15.0k and 15k instead - return steps + "k"; + return steps; } @@ -184,7 +405,7 @@ function getWeather(){ // Wind const wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/); - weather.wind = Math.round(wind[1]) + " km/h"; + weather.wind = Math.round(wind[1]) + "kph"; return weather @@ -193,19 +414,20 @@ function getWeather(){ } return { - temp: "? °C", - hum: "-", - txt: "-", - wind: "? km/h", - wdir: "-", - wrose: "-" + temp: " ? ", + hum: " ? ", + txt: " ? ", + wind: " ? ", + wdir: " ? ", + wrose: " ? " }; } -function isAlarmEnabled(){ + +function isAlarmEnabled(idx){ try{ var alarm = require('sched'); - var alarmObj = alarm.getAlarm(TIMER_IDX); + var alarmObj = alarm.getAlarm(idx); if(alarmObj===undefined || !alarmObj.on){ return false; } @@ -216,37 +438,40 @@ function isAlarmEnabled(){ return false; } -function getAlarmMinutes(){ - if(!isAlarmEnabled()){ + +function getAlarmMinutes(idx){ + if(!isAlarmEnabled(idx)){ return -1; } var alarm = require('sched'); - var alarmObj = alarm.getAlarm(TIMER_IDX); + var alarmObj = alarm.getAlarm(idx); return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000)); } -function increaseAlarm(){ + +function increaseAlarm(idx){ try{ - var minutes = isAlarmEnabled() ? getAlarmMinutes() : 0; - var alarm = require('sched') - alarm.setAlarm(TIMER_IDX, { + var minutes = isAlarmEnabled(idx) ? getAlarmMinutes(idx) : 0; + var alarm = require('sched'); + alarm.setAlarm(idx, { timer : (minutes+5)*60*1000, }); alarm.reload(); } catch(ex){ } } -function decreaseAlarm(){ + +function decreaseAlarm(idx){ try{ - var minutes = getAlarmMinutes(); + var minutes = getAlarmMinutes(idx); minutes -= 5; var alarm = require('sched') - alarm.setAlarm(TIMER_IDX, undefined); + alarm.setAlarm(idx, undefined); if(minutes > 0){ - alarm.setAlarm(TIMER_IDX, { + alarm.setAlarm(idx, { timer : minutes*60*1000, }); } @@ -256,10 +481,26 @@ function decreaseAlarm(){ } -/* - * DRAW functions - */ +function measureAltitude(callbackFun){ + var oldX = settings.menuPosX; + var oldY = settings.menuPosY; + try{ + Bangle.getPressure().then(data=>{ + if(data && data.altitude && data.altitude > -100){ + callbackFun(Math.round(data.altitude) + "m", oldX, oldY); + } else { + callbackFun("???", oldX, oldY); + } + }); + }catch(ex){ + callbackFun("err", oldX, oldY); + } +} + +/************ + * DRAW + */ function draw() { // Queue draw again queueDraw(); @@ -278,8 +519,8 @@ function drawDate(){ g.reset().clearRect(0,0,W,W); // Draw date - y = parseInt(y/2); - y += settings.fullscreen ? 2 : 15; + y = parseInt(y/2)+4; + y += isFullscreen() ? 0 : 13; var date = new Date(); var dateStr = date.getDate(); dateStr = ("0" + dateStr).substr(-2); @@ -293,19 +534,18 @@ function drawDate(){ var fullDateW = dateW + 10 + dayW; g.setFontAlign(-1,0); - g.setMediumFont(); - g.setColor(g.theme.fg); - g.drawString(dateStr, W/2 - fullDateW / 2, y+1); - - g.setSmallFont(); 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(){ // Draw background - var y = H/5*2 + (settings.fullscreen ? 0 : 8); + var y = H/5*2 + (isFullscreen() ? 0 : 8); g.setColor(g.theme.fg); g.fillRect(0,y,W,H); var date = new Date(); @@ -323,56 +563,69 @@ function drawTime(){ // Set y coordinates correctly y += parseInt((H - y)/2) + 5; - var infoEntry = getInfoEntry(); - var infoStr = infoEntry[0]; - var infoImg = infoEntry[1]; - var printImgLeft = infoEntry[2] == "left"; + var menuEntry = getMenuEntry(); + var menuTextFun = menuEntry[0]; + var menuImg = menuEntry[1]; + var printImgLeft = settings.menuPosY != 0; // Show large or small time depending on info entry - if(infoStr == null){ - if(settings.hideColon){ - g.setXLargeFont(); - } else { - g.setLargeFont(); - } + if(menuTextFun == null){ + g.setLargeFont(); + g.drawString(timeStr, W/2, y); + return; } else { y -= 15; g.setMediumFont(); - } - g.drawString(timeStr, W/2, y); - - // Draw info if set - if(infoStr == null){ - return; + g.drawString(timeStr, W/2, y); } - y += 35; - g.setFontAlign(0,0); - g.setSmallFont(); - var imgWidth = 0; - if(infoImg !== undefined){ - imgWidth = infoImg.width; - var strWidth = g.stringWidth(infoStr); - g.drawImage( - infoImg, - W/2 + (printImgLeft ? -strWidth/2-2 : strWidth/2+2) - infoImg.width/2, - y - infoImg.height/2 - ); - } - g.drawString(infoStr, printImgLeft ? W/2 + imgWidth/2 + 2 : W/2 - imgWidth/2 - 2, y+3); + // Async set the menu (could be that some data is async fetched) + menuTextFun((menuText, oldX, oldY) => { + + // We display the text IFF the user did not change the menu + if(settings.menuPosX != oldX || settings.menuPosY != oldY){ + return; + } + + // As its a callback, we have to ensure that the color + // font etc. is still correct... + g.setColor(g.theme.bg); + g.setFontAlign(0,0); + y += 35; + + if(menuText.split('\n').length > 1){ + g.setMiniFont(); + } else { + g.setSmallFont(); + } + + var imgWidth = 0; + if(menuImg !== undefined){ + imgWidth = 24.0; + var strWidth = g.stringWidth(menuText); + var scale = imgWidth / menuImg.width; + g.drawImage( + menuImg, + W/2 + (printImgLeft ? -strWidth/2-4 : strWidth/2+4) - parseInt(imgWidth/2), + y - parseInt(imgWidth/2), + { scale: scale } + ); + } + g.drawString(menuText, printImgLeft ? W/2 + imgWidth/2 + 2 : W/2 - imgWidth/2 - 2, y+3); + }); } function drawLock(){ if(settings.showLock && Bangle.isLocked()){ g.setColor(g.theme.fg); - g.drawImage(imgLock, W-16, 2); + g.drawImage(imgLock(), W-16, 2); } } function drawWidgets(){ - if(settings.fullscreen){ + if(isFullscreen()){ for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} } else { Bangle.drawWidgets(); @@ -410,55 +663,117 @@ Bangle.on('lcdPower',on=>{ 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 = 1; + settings.menuPosY = 1; draw(); }); Bangle.on('touch', function(btn, e){ - var left = parseInt(g.getWidth() * 0.2); + 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.2); + var upper = parseInt(g.getHeight() * 0.22) + widget_size; var lower = g.getHeight() - upper; - var is_left = e.x < left; - var is_right = e.x > right; var is_upper = e.y < upper; var is_lower = e.y > lower; - - if(is_upper){ - Bangle.buzz(40, 0.6); - increaseAlarm(); - drawTime(); - } + 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(is_lower){ Bangle.buzz(40, 0.6); - decreaseAlarm(); + settings.menuPosY = (settings.menuPosY+1) % menu[settings.menuPosX].length; + + // Handle custom menu entry function + var menuEntry = getMenuEntry(); + if(menuEntry.length > 2){ + menuEntry[2](); + } + + drawTime(); + } + + 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].length-1 : settings.menuPosY; + + // Handle custom menu entry function + var menuEntry = getMenuEntry(); + if(menuEntry.length > 3){ + menuEntry[3](); + } + drawTime(); } if(is_right){ + // A bit hacky but we ensure that always the first agenda entry is shown... + agendaIdx = 0; + Bangle.buzz(40, 0.6); - settings.showInfo = (settings.showInfo+1) % NUM_INFO; + settings.menuPosX = (settings.menuPosX+1) % menu.length; + settings.menuPosY = 0; drawTime(); } if(is_left){ + // A bit hacky but we ensure that always the first agenda entry is shown... + agendaIdx = 0; + Bangle.buzz(40, 0.6); - settings.showInfo = settings.showInfo-1; - settings.showInfo = settings.showInfo < 0 ? NUM_INFO-1 : settings.showInfo; + settings.menuPosY = 0; + settings.menuPosX = settings.menuPosX-1; + settings.menuPosX = settings.menuPosX < 0 ? menu.length-1 : settings.menuPosX; drawTime(); } + + if(is_center){ + var menuEntry = getMenuEntry(); + if(menuEntry.length > 4 && menuEntry[4] != null){ + Bangle.buzz(80, 0.6).then(()=>{ + try{ + menuEntry[4](); + setTimeout(()=>{ + Bangle.buzz(80, 0.6); + drawTime(); + }, 250); + } catch(ex){ + // In case it fails, we simply ignore it. + } + } + ); + } + } }); E.on("kill", function(){ - storage.write(SETTINGS_FILE, settings); + try{ + storage.write(SETTINGS_FILE, settings); + } catch(ex){ + // If this fails, we still kill the app... + } }); @@ -470,9 +785,15 @@ E.on("kill", function(){ // dark/light theme as well as the colors. g.setTheme({bg:g.theme.fg,fg:g.theme.bg, dark:!g.theme.dark}).clear(); -// Load widgets and draw clock the first time -Bangle.loadWidgets(); -draw(); - // 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/metadata.json b/apps/bwclk/metadata.json index eba1449a6..919ecad13 100644 --- a/apps/bwclk/metadata.json +++ b/apps/bwclk/metadata.json @@ -1,11 +1,11 @@ { "id": "bwclk", "name": "BW Clock", - "version": "0.09", - "description": "BW Clock.", + "version": "0.20", + "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"}], + "screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}, {"url":"screenshot_4.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS2"], diff --git a/apps/bwclk/screenshot.png b/apps/bwclk/screenshot.png index 550913422..3a75f13d1 100644 Binary files a/apps/bwclk/screenshot.png and b/apps/bwclk/screenshot.png differ diff --git a/apps/bwclk/screenshot_2.png b/apps/bwclk/screenshot_2.png index ccbc9aae1..31bf6373e 100644 Binary files a/apps/bwclk/screenshot_2.png and b/apps/bwclk/screenshot_2.png differ diff --git a/apps/bwclk/screenshot_3.png b/apps/bwclk/screenshot_3.png index 5bf7083f0..8d982cac4 100644 Binary files a/apps/bwclk/screenshot_3.png 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 index a421e81a9..116253fda 100644 --- a/apps/bwclk/settings.js +++ b/apps/bwclk/settings.js @@ -4,7 +4,7 @@ // initialize with default settings... const storage = require('Storage') let settings = { - fullscreen: false, + screen: "Normal", showLock: true, hideColon: false, }; @@ -17,15 +17,16 @@ storage.write(SETTINGS_FILE, settings) } - + var screenOptions = ["Normal", "Dynamic", "Full"]; E.showMenu({ '': { 'title': 'BW Clock' }, '< Back': back, - 'Fullscreen': { - value: settings.fullscreen, - format: () => (settings.fullscreen ? 'Yes' : 'No'), - onchange: () => { - settings.fullscreen = !settings.fullscreen; + 'Screen': { + value: 0 | screenOptions.indexOf(settings.screen), + min: 0, max: 2, + format: v => screenOptions[v], + onchange: v => { + settings.screen = screenOptions[v]; save(); }, }, diff --git a/apps/calclock/ChangeLog b/apps/calclock/ChangeLog new file mode 100644 index 000000000..b67f29e94 --- /dev/null +++ b/apps/calclock/ChangeLog @@ -0,0 +1,3 @@ +0.01: Initial version +0.02: More compact rendering & app icon +0.03: Tell clock widgets to hide. 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..343d95c04 --- /dev/null +++ b/apps/calclock/calclock.js @@ -0,0 +1,119 @@ +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) { + g.setFont("Vector", 24); + + var time = isActive(event) ? new Date() : new Date(event.timestamp * 1000); + var timeStr = zp(time.getHours()) + ":" + zp(time.getMinutes()); + g.drawString(timeStr, 5, y); + y += 24; + + g.setFont("12x20", 1); + if (isActive(event)) { + g.drawString(zp(time.getDate())+". " + require("locale").month(time,1),15*timeStr.length,y-21); + } else { + var offset = 0-time.getTimezoneOffset()/1440; + var days = Math.floor((time.getTime()/1000)/86400+offset)-Math.floor(getTime()/86400+offset); + if(days > 0) { + var daysStr = days===1?/*LANG*/"tomorrow":/*LANG*/"in "+days+/*LANG*/" days"; + g.drawString(daysStr,15*timeStr.length,y-21); + } + } + return y; +} + +function drawEventBody(event, y) { + g.setFont("12x20", 1); + var lines = g.wrapString(event.title, g.getWidth()-10); + if (lines.length > 2) { + lines = lines.slice(0,2); + lines[1] = lines[1].slice(0,-3)+"..."; + } + g.drawString(lines.join('\n'), 5, y); + y+=20 * lines.length; + if(event.location) { + g.drawImage(atob("DBSBAA8D/H/nDuB+B+B+B3Dn/j/B+A8A8AYAYAYAAAAAAA=="),5,y); + g.drawString(event.location, 20, y); + y+=20; + } + 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("#0ff"); + g.clearRect(5, 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(5,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..7bac5c721 --- /dev/null +++ b/apps/calclock/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "calclock", + "name": "Calendar Clock", + "shortName": "CalClock", + "version": "0.03", + "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.png b/apps/calclock/screenshot.png new file mode 100644 index 000000000..4ab503f2b Binary files /dev/null and b/apps/calclock/screenshot.png differ diff --git a/apps/calendar/ChangeLog b/apps/calendar/ChangeLog index 873f90de6..0583ea45f 100644 --- a/apps/calendar/ChangeLog +++ b/apps/calendar/ChangeLog @@ -8,3 +8,4 @@ 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 diff --git a/apps/calendar/metadata.json b/apps/calendar/metadata.json index 65a54c097..48fd52d3e 100644 --- a/apps/calendar/metadata.json +++ b/apps/calendar/metadata.json @@ -1,7 +1,7 @@ { "id": "calendar", "name": "Calendar", - "version": "0.09", + "version": "0.10", "description": "Simple calendar", "icon": "calendar.png", "screenshots": [{"url":"screenshot_calendar.png"}], diff --git a/apps/calendar/settings.js b/apps/calendar/settings.js index 192d2ece0..54ed50a64 100644 --- a/apps/calendar/settings.js +++ b/apps/calendar/settings.js @@ -17,7 +17,6 @@ "< Back": () => back(), '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 index 0e22605af..64bff2b31 100644 --- a/apps/calibration/ChangeLog +++ b/apps/calibration/ChangeLog @@ -1,2 +1,3 @@ -1.00: New App! -1.01: Use fractional numbers and scale the points to keep working consistently on whole screen +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/metadata.json b/apps/calibration/metadata.json index b7a719e1c..b60650300 100644 --- a/apps/calibration/metadata.json +++ b/apps/calibration/metadata.json @@ -2,7 +2,7 @@ "name": "Touchscreen Calibration", "shortName":"Calibration", "icon": "calibration.png", - "version":"1.01", + "version":"0.03", "description": "A simple calibration app for the touchscreen", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", diff --git a/apps/calibration/settings.js b/apps/calibration/settings.js index 6db8dd3bb..08c728d96 100644 --- a/apps/calibration/settings.js +++ b/apps/calibration/settings.js @@ -13,7 +13,6 @@ "< Back" : () => back(), 'Active': { value: !!settings.active, - format: v => v? "On":"Off", onchange: v => { settings.active = v; writeSettings(); diff --git a/apps/cassioWatch/ChangeLog b/apps/cassioWatch/ChangeLog index 419810021..883bd2585 100644 --- a/apps/cassioWatch/ChangeLog +++ b/apps/cassioWatch/ChangeLog @@ -8,4 +8,5 @@ 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. \ No newline at end of file +0.10: Show daily steps, heartrate and the temperature if weather information is available. +0.11: Tell clock widgets to hide. diff --git a/apps/cassioWatch/app.js b/apps/cassioWatch/app.js index 6bbb9e823..91f6737ad 100644 --- a/apps/cassioWatch/app.js +++ b/apps/cassioWatch/app.js @@ -165,11 +165,11 @@ Bangle.on("lock", (locked) => { } }); +Bangle.setUI("clock"); // Load widgets, but don't show them Bangle.loadWidgets(); -Bangle.setUI("clock"); g.reset(); g.clear(); -draw(); \ No newline at end of file +draw(); diff --git a/apps/cassioWatch/metadata.json b/apps/cassioWatch/metadata.json index dabdc2c93..abfee9b93 100644 --- a/apps/cassioWatch/metadata.json +++ b/apps/cassioWatch/metadata.json @@ -4,7 +4,7 @@ "description": "Animated Clock with Space Cassio Watch Style", "screenshots": [{ "url": "screens/screen_night.png" },{ "url": "screens/screen_day.png" }], "icon": "app.png", - "version": "0.10", + "version": "0.11", "type": "clock", "tags": "clock, weather, cassio, retro", "supports": ["BANGLEJS2"], 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/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 c3e7918e7..c398a89b6 100644 --- a/apps/circlesclock/ChangeLog +++ b/apps/circlesclock/ChangeLog @@ -24,3 +24,5 @@ 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 diff --git a/apps/circlesclock/app.js b/apps/circlesclock/app.js index 48e3a1a1a..fc501a5d0 100644 --- a/apps/circlesclock/app.js +++ b/apps/circlesclock/app.js @@ -1,10 +1,5 @@ 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 temperatureIcon = atob("EBCBAAAAAYADwAJAAkADwAPAA8ADwAfgB+AH4AfgA8ABgAAA"); - 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)); @@ -22,10 +17,16 @@ let settings = Object.assign( storage.readJSON("circlesclock.default.json", true) || {}, storage.readJSON(SETTINGS_FILE, true) || {} ); -// Load step goal from pedometer widget as fallback + +// 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; + } } /* @@ -125,20 +126,11 @@ function draw() { g.setFontAlign(0, 0); g.drawString(locale.date(new Date()), w / 2, h2); g.drawString(locale.dow(new Date()), w / 2, h2 + dowOffset); - - // draw the circles a little bit delayed so we decrease the blocking time - setTimeout(function() { - drawCircle(1); - }, 1); - setTimeout(function() { - drawCircle(2); - }, 1); - setTimeout(function() { - drawCircle(3); - }, 1); - setTimeout(function() { - if (circleCount >= 4) drawCircle(4); - }, 1); + + drawCircle(1); + drawCircle(2); + drawCircle(3); + if (circleCount >= 4) drawCircle(4); } function drawCircle(index) { @@ -294,7 +286,7 @@ function drawSteps(w) { writeCircleText(w, shortValue(steps)); - g.drawImage(getImage(shoesIcon, getCircleIconColor("steps", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); + g.drawImage(getImage(atob("EBCBAAAACAAcAB4AHgAeABwwADgGeAZ4AHgAMAAAAHAAIAAA"), getCircleIconColor("steps", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); } function drawStepsDistance(w) { @@ -319,7 +311,7 @@ function drawStepsDistance(w) { writeCircleText(w, shortValue(stepsDistance)); - g.drawImage(getImage(shoesIcon, getCircleIconColor("stepsDistance", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); + g.drawImage(getImage(atob("EBCBAAAACAAcAB4AHgAeABwwADgGeAZ4AHgAMAAAAHAAIAAA"), getCircleIconColor("stepsDistance", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); } function drawHeartRate(w) { @@ -490,8 +482,8 @@ function drawTemperature(w) { if (temperature) writeCircleText(w, locale.temp(temperature)); - - g.drawImage(getImage(temperatureIcon, getCircleIconColor("temperature", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); + + g.drawImage(getImage(atob("EBCBAAAAAYADwAJAAkADwAPAA8ADwAfgB+AH4AfgA8ABgAAA"), getCircleIconColor("temperature", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); }); } @@ -517,7 +509,7 @@ function drawPressure(w) { if (pressure) writeCircleText(w, Math.round(pressure)); - g.drawImage(getImage(temperatureIcon, getCircleIconColor("pressure", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); + g.drawImage(getImage(atob("EBCBAAAAAYADwAJAAkADwAPAA8ADwAfgB+AH4AfgA8ABgAAA"), getCircleIconColor("pressure", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); }); } @@ -543,7 +535,7 @@ function drawAltitude(w) { if (altitude) writeCircleText(w, locale.distance(Math.round(altitude))); - g.drawImage(getImage(temperatureIcon, getCircleIconColor("altitude", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); + g.drawImage(getImage(atob("EBCBAAAAAYADwAJAAkADwAPAA8ADwAfgB+AH4AfgA8ABgAAA"), getCircleIconColor("altitude", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); }); } @@ -614,8 +606,8 @@ function getWeatherIconByCode(code) { default: return weatherCloudy; } - default: - return undefined; + default: + return undefined; } } @@ -641,6 +633,7 @@ function formatSeconds(s) { function getSunData() { if (location != undefined && location.lat != undefined) { + const SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js"); // get today's sunlight times for lat/lon return SunCalc ? SunCalc.getTimes(new Date(), location.lat, location.lon) : undefined; } diff --git a/apps/circlesclock/metadata.json b/apps/circlesclock/metadata.json index c35d99334..837fcaa88 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.12", + "version":"0.13", "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/clockcal/ChangeLog b/apps/clockcal/ChangeLog index 8b40a87ac..27d4fc7f4 100644 --- a/apps/clockcal/ChangeLog +++ b/apps/clockcal/ChangeLog @@ -1,3 +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/app.js b/apps/clockcal/app.js index 5e8c7f796..58ddd7ef5 100644 --- a/apps/clockcal/app.js +++ b/apps/clockcal/app.js @@ -1,3 +1,4 @@ +Bangle.setUI("clock"); Bangle.loadWidgets(); var s = Object.assign({ @@ -123,7 +124,7 @@ function drawMinutes() { 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 ? '#fff' : '#f00'; + var textColor = NRF.getSecurityStatus().connected ? '#99f' : '#fff'; var size = 50; var clock_x = (w - 20) / 2; if (dimSeconds) { @@ -307,4 +308,4 @@ NRF.on('disconnect', BTevent); dimSeconds = Bangle.isLocked(); drawWatch(); -Bangle.setUI("clock"); + diff --git a/apps/clockcal/metadata.json b/apps/clockcal/metadata.json index 3998215d7..872211495 100644 --- a/apps/clockcal/metadata.json +++ b/apps/clockcal/metadata.json @@ -1,7 +1,7 @@ { "id": "clockcal", "name": "Clock & Calendar", - "version": "0.03", + "version": "0.06", "description": "Clock with Calendar", "readme":"README.md", "icon": "app.png", diff --git a/apps/clockcal/settings.js b/apps/clockcal/settings.js index abedad99b..d4cc4df68 100644 --- a/apps/clockcal/settings.js +++ b/apps/clockcal/settings.js @@ -26,7 +26,6 @@ "< Back": () => back(), 'Buzz(dis)conn.?': { value: settings.BUZZ_ON_BT, - format: v => v ? "On" : "Off", onchange: v => { settings.BUZZ_ON_BT = v; writeSettings(); @@ -59,7 +58,6 @@ }, 'Red Saturday?': { value: settings.REDSAT, - format: v => v ? "On" : "Off", onchange: v => { settings.REDSAT = v; writeSettings(); @@ -67,7 +65,6 @@ }, 'Red Sunday?': { value: settings.REDSUN, - format: v => v ? "On" : "Off", onchange: v => { settings.REDSUN = v; writeSettings(); 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/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/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 4f48bdd14..1c4f6f43b 100644 --- a/apps/crowclk/ChangeLog +++ b/apps/crowclk/ChangeLog @@ -1,3 +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 eee1653cb..7e608ef19 100644 --- a/apps/crowclk/crow_clock.js +++ b/apps/crowclk/crow_clock.js @@ -136,9 +136,9 @@ Bangle.on('lcdPower', (on) => { 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 6985cf11a..265a0398b 100644 --- a/apps/crowclk/metadata.json +++ b/apps/crowclk/metadata.json @@ -1,7 +1,7 @@ { "id": "crowclk", "name": "Crow Clock", - "version": "0.03", + "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/daisy/ChangeLog b/apps/daisy/ChangeLog index d5844c62b..829ff3d13 100644 --- a/apps/daisy/ChangeLog +++ b/apps/daisy/ChangeLog @@ -4,3 +4,4 @@ 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 diff --git a/apps/daisy/metadata.json b/apps/daisy/metadata.json index 5073db603..802ba6834 100644 --- a/apps/daisy/metadata.json +++ b/apps/daisy/metadata.json @@ -1,6 +1,6 @@ { "id": "daisy", "name": "Daisy", - "version":"0.06", + "version":"0.07", "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", diff --git a/apps/daisy/settings.js b/apps/daisy/settings.js index 044eee0d1..6397a81f4 100644 --- a/apps/daisy/settings.js +++ b/apps/daisy/settings.js @@ -41,7 +41,6 @@ }, 'Idle Warning': { value: !!s.idle_check, - format: v => v ? /*LANG*/"Yes":/*LANG*/"No", 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/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/distortclk/ChangeLog b/apps/distortclk/ChangeLog new file mode 100644 index 000000000..9bec1d7a4 --- /dev/null +++ b/apps/distortclk/ChangeLog @@ -0,0 +1 @@ +0.01: New face! 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..ff44063d4 --- /dev/null +++ b/apps/distortclk/app.js @@ -0,0 +1,115 @@ +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(); + +const locale = require("locale"); + +var fgTime = 0xf800; +var bgTime = 0x3333ff; +var dayDate = 0x000; + +function time() { //numbers + require("Font4x5").add(Graphics); + + const d = new Date(); + const h = d.getHours(), + m = d.getMinutes(); + + const day = Date.now(); + const mo = d.getMonth()+1; + + var middle= ":"; + + const date = january(d.getMonth())+" "+d.getDate(); + const time = h + " " + ("0" + m).substr(-2); + + // time + //g.setColor(0, 0, 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(date,width-50, height-16); +} + +function january(month){ //switch case for month names + switch (month){ + case 0: + middle="January"; + return middle; + case 1: + middle="February"; + return middle; + case 2: + middle="March"; + return middle; + case 3: + middle="April"; + return middle; + case 4: + middle="May"; + return middle; + case 5: + middle="June"; + return middle; + case 6: + middle="July"; + return middle; + case 7: + middle="August"; + return middle; + case 8: + middle="September"; + return middle; + case 9: + middle="October"; + return middle; + case 10: + middle="November"; + return middle; + case 11: + middle="December"; + return middle; + } +} + +function draw() { + g.setColor(bgTime).fillRect(0,40,width,height-offset); + time(); +} + +//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 +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"); +// 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..0ede203b5 --- /dev/null +++ b/apps/distortclk/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "distortclk", + "name": "Distort Clock", + "shortName":"Distort Clock", + "version": "0.01", + "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/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 09804b82e..16c550334 100644 --- a/apps/dtlaunch/ChangeLog +++ b/apps/dtlaunch/ChangeLog @@ -11,4 +11,6 @@ 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. +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 diff --git a/apps/dtlaunch/README.md b/apps/dtlaunch/README.md index 55c9f53b8..1835bc842 100644 --- a/apps/dtlaunch/README.md +++ b/apps/dtlaunch/README.md @@ -27,7 +27,7 @@ Bangle 2: ## Controls- Bangle 2 -**Touch** - icon to select, scond touch launches app +**Touch** - icon to select, second touch launches app **Swipe Left/Up** - move to next page of app icons diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js index 46194ec5d..8cd5790bb 100644 --- a/apps/dtlaunch/app-b2.js +++ b/apps/dtlaunch/app-b2.js @@ -89,7 +89,7 @@ function drawPage(p){ Bangle.on("swipe",(dirLeftRight, dirUpDown)=>{ selected = 0; oldselected=-1; - if(settings.swipeExit && dirLeftRight==1) showClock(); + if(settings.swipeExit && dirLeftRight==1) load(); if (dirUpDown==-1||dirLeftRight==-1){ ++page; if (page>maxPage) page=0; drawPage(page); @@ -99,12 +99,6 @@ Bangle.on("swipe",(dirLeftRight, dirUpDown)=>{ } }); -function showClock(){ - var app = require("Storage").readJSON('setting.json', 1).clock; - if (app) load(app); - else E.showMessage("clock\nnot found"); -} - function isTouched(p,n){ if (n<0 || n>3) return false; var x1 = (n%2)*72+XOFF; var y1 = n>1?72+YOFF:YOFF; diff --git a/apps/dtlaunch/metadata.json b/apps/dtlaunch/metadata.json index 4a0b8067c..36728f342 100644 --- a/apps/dtlaunch/metadata.json +++ b/apps/dtlaunch/metadata.json @@ -1,7 +1,7 @@ { "id": "dtlaunch", "name": "Desktop Launcher", - "version": "0.14", + "version": "0.16", "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 7ead63be0..fac9c0fff 100644 --- a/apps/dtlaunch/settings-b2.js +++ b/apps/dtlaunch/settings-b2.js @@ -18,7 +18,6 @@ "< Back" : () => back(), 'Show clocks': { value: settings.showClocks, - format: v => v?"On":"Off", onchange: v => { settings.showClocks = v; writeSettings(); @@ -26,7 +25,6 @@ }, 'Show launchers': { value: settings.showLaunchers, - format: v => v?"On":"Off", onchange: v => { settings.showLaunchers = v; writeSettings(); @@ -34,7 +32,6 @@ }, 'Direct launch': { value: settings.direct, - format: v => v?"On":"Off", onchange: v => { settings.direct = v; writeSettings(); @@ -42,7 +39,6 @@ }, 'Swipe Exit': { value: settings.swipeExit, - format: v => v?"On":"Off", onchange: v => { settings.swipeExit = v; writeSettings(); @@ -50,7 +46,6 @@ }, 'One click exit': { value: settings.oneClickExit, - format: v => v?"On":"Off", onchange: v => { settings.oneClickExit = 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/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 253307fa0..5107bc6ae 100644 --- a/apps/espruinoctrl/metadata.json +++ b/apps/espruinoctrl/metadata.json @@ -5,7 +5,7 @@ "version": "0.01", "description": "Send commands to other Espruino devices via the Bluetooth UART interface. Customisable commands!", "icon": "app.png", - "tags": "", + "tags": "tool,bluetooth", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "custom": "custom.html", 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..ebb55b23d --- /dev/null +++ b/apps/espruinoprog/metadata.json @@ -0,0 +1,17 @@ +{ + "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}, + {"name":"espruinoprog.json"} + ] +} diff --git a/apps/nato/changelog.txt b/apps/espruinoterm/ChangeLog similarity index 100% rename from apps/nato/changelog.txt rename to apps/espruinoterm/ChangeLog 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/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/files/files.js b/apps/files/files.js index e7b42c101..e81e9589f 100644 --- a/apps/files/files.js +++ b/apps/files/files.js @@ -1,7 +1,5 @@ const store = require('Storage'); -const boolFormat = (v) => v ? "On" : "Off"; - function showMainMenu() { const mainmenu = { '': { 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/football/metadata.json b/apps/football/metadata.json index 253026c39..43e7ac1bf 100644 --- a/apps/football/metadata.json +++ b/apps/football/metadata.json @@ -2,7 +2,7 @@ "id": "football", "name": "football", "shortName": "football", - "version": "1.00", + "version": "1.01", "type": "app", "description": "Classic football game of the CASIO chronometer", "icon": "app.png", diff --git a/apps/ftclock/ChangeLog b/apps/ftclock/ChangeLog index 83ec21ee6..c30dae69f 100644 --- a/apps/ftclock/ChangeLog +++ b/apps/ftclock/ChangeLog @@ -1,3 +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/metadata.json b/apps/ftclock/metadata.json index 876feb1bb..96a4f84b9 100644 --- a/apps/ftclock/metadata.json +++ b/apps/ftclock/metadata.json @@ -1,7 +1,7 @@ { "id": "ftclock", "name": "Four Twenty Clock", - "version": "0.03", + "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/fwupdate/custom.html b/apps/fwupdate/custom.html index 3f8f50b3f..8ef117933 100644 --- a/apps/fwupdate/custom.html +++ b/apps/fwupdate/custom.html @@ -83,6 +83,7 @@ function onInit(device) { if (crc==3508163280 || crc==1418074094) version = "2v12"; if (crc==4056371285) version = "2v13"; if (crc==1038322422) version = "2v14"; + if (crc==2560806221) version = "2v15"; if (!ok) { version += `(⚠ update required)`; } diff --git a/apps/gallifr/ChangeLog b/apps/gallifr/ChangeLog index 0e1f45042..32c1057b0 100644 --- a/apps/gallifr/ChangeLog +++ b/apps/gallifr/ChangeLog @@ -1,2 +1,3 @@ 0.01: First released version 0.02: Changed setWatch to Bangle.setUI +0.03: Tell clock widgets to hide. diff --git a/apps/gallifr/app.js b/apps/gallifr/app.js index d327bcdc1..8468eee48 100644 --- a/apps/gallifr/app.js +++ b/apps/gallifr/app.js @@ -238,10 +238,12 @@ Bangle.on('lcdPower', (on) => { } }); +// Show launcher when button pressed +Bangle.setUI("clock"); + g.clear(); startTimers(); Bangle.loadWidgets(); drawAll(); -// Show launcher when button pressed -Bangle.setUI("clock"); + diff --git a/apps/gallifr/metadata.json b/apps/gallifr/metadata.json index 9ce7d7f97..96fe243ed 100644 --- a/apps/gallifr/metadata.json +++ b/apps/gallifr/metadata.json @@ -2,7 +2,7 @@ "id": "gallifr", "name": "Time Traveller's Chronometer", "shortName": "Time Travel Clock", - "version": "0.02", + "version": "0.03", "description": "A clock for time travellers. The light pie segment shows the minutes, the black circle, the hour. The dial itself reads 'time' just in case you forget.", "icon": "gallifr.png", "screenshots": [{"url":"screenshot_time.png"}], diff --git a/apps/game1024/ChangeLog b/apps/game1024/ChangeLog index 800fa6b9d..df36b6456 100644 --- a/apps/game1024/ChangeLog +++ b/apps/game1024/ChangeLog @@ -8,3 +8,4 @@ 0.08: Bug fix at end of the game with victorious splash and glorious orchestra 0.09: Added settings menu, removed symbol selection button (*), added highscore reset 0.10: fixed clockmode in settings +0.11: Use default Bangle formatter for booleans diff --git a/apps/game1024/metadata.json b/apps/game1024/metadata.json index 728b5dc0e..f3b72aad3 100644 --- a/apps/game1024/metadata.json +++ b/apps/game1024/metadata.json @@ -1,7 +1,7 @@ { "id": "game1024", "name": "1024 Game", "shortName" : "1024 Game", - "version": "0.10", + "version": "0.11", "icon": "game1024.png", "screenshots": [ {"url":"screenshot.png" } ], "readme":"README.md", diff --git a/apps/game1024/settings.js b/apps/game1024/settings.js index 24a972600..b52e060b1 100644 --- a/apps/game1024/settings.js +++ b/apps/game1024/settings.js @@ -32,7 +32,7 @@ } }, "Exit press:": { - value: !settings.clockMode, // ! converts undefined to true + value: !settings.clockMode, format: v => v?"short":"long", onchange: v => { settings.clockMode = v; @@ -40,8 +40,7 @@ }, }, "Debug mode:": { - value: !!settings.debugMode, // !! converts undefined to false - format: v => v?"On":"Off", + value: !!settings.debugMode, onchange: v => { settings.debugMode = v; writeSettings(); diff --git a/apps/gbmusic/ChangeLog b/apps/gbmusic/ChangeLog index e2ee53ede..d8379b317 100644 --- a/apps/gbmusic/ChangeLog +++ b/apps/gbmusic/ChangeLog @@ -9,3 +9,4 @@ 0.09: Move event listener from widget to boot code, stops music from showing up in messages 0.10: Simplify touch events Remove date+time +0.11: Use default Bangle formatter for booleans diff --git a/apps/gbmusic/metadata.json b/apps/gbmusic/metadata.json index 0ded80452..bbe2a158d 100644 --- a/apps/gbmusic/metadata.json +++ b/apps/gbmusic/metadata.json @@ -2,7 +2,7 @@ "id": "gbmusic", "name": "Gadgetbridge Music Controls", "shortName": "Music Controls", - "version": "0.10", + "version": "0.11", "description": "Control the music on your Gadgetbridge-connected phone", "icon": "icon.png", "screenshots": [{"url":"screenshot_v1_d.png"},{"url":"screenshot_v1_l.png"}, diff --git a/apps/gbmusic/settings.js b/apps/gbmusic/settings.js index ae013fda5..6619eab1c 100644 --- a/apps/gbmusic/settings.js +++ b/apps/gbmusic/settings.js @@ -25,19 +25,16 @@ } } - const yesNo = (v) => translate(v ? "Yes" : "No"); let menu = { "": {"title": "Music Control"}, }; menu[translate("< Back")] = back; menu[translate("Auto start")] = { value: !!s.autoStart, - format: yesNo, onchange: save("autoStart"), }; menu[translate("Simple button")] = { value: !!s.simpleButton, - format: yesNo, onchange: save("simpleButton"), }; diff --git a/apps/gbridge/ChangeLog b/apps/gbridge/ChangeLog index 059767ece..f707ffb94 100644 --- a/apps/gbridge/ChangeLog +++ b/apps/gbridge/ChangeLog @@ -27,3 +27,4 @@ 0.25: workaround call notification Fix inflated step number 0.26: Include charging status in battery updates to phone +0.27: Use default Bangle formatter for booleans diff --git a/apps/gbridge/metadata.json b/apps/gbridge/metadata.json index db7119758..e6130b06b 100644 --- a/apps/gbridge/metadata.json +++ b/apps/gbridge/metadata.json @@ -1,7 +1,7 @@ { "id": "gbridge", "name": "Gadgetbridge", - "version": "0.26", + "version": "0.27", "description": "(NOT RECOMMENDED) Displays Gadgetbridge notifications from Android. Please use the 'Android Integration' Bangle.js app instead.", "icon": "app.png", "type": "widget", diff --git a/apps/gbridge/settings.js b/apps/gbridge/settings.js index f9c7cde90..cf6c84c73 100644 --- a/apps/gbridge/settings.js +++ b/apps/gbridge/settings.js @@ -27,13 +27,11 @@ "Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" }, "Show Icon" : { value: settings().showIcon, - format: v => v?"Yes":"No", onchange: setIcon }, "Find Phone" : function() { E.showMenu(findPhone); }, "Record HRM" : { value: !!settings().hrm, - format: v => v?"Yes":"No", onchange: v => updateSetting('hrm', v) } }; diff --git a/apps/geissclk/ChangeLog b/apps/geissclk/ChangeLog index 7458fadee..cd46173f7 100644 --- a/apps/geissclk/ChangeLog +++ b/apps/geissclk/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: BTN2->launcher, use smaller text to allow "20:00" to fit on screen 0.03: Changed setWatch to Bangle.setUI +0.04: Tell clock widgets to hide. diff --git a/apps/geissclk/clock.js b/apps/geissclk/clock.js index f14ea5f39..5401fb142 100644 --- a/apps/geissclk/clock.js +++ b/apps/geissclk/clock.js @@ -142,11 +142,13 @@ Bangle.on('lcdPower',function(on) { animInterval = setInterval(iterate, 50); } }); -g.clear(); + +// Show launcher when button pressed +Bangle.setUI("clock");g.clear(); + Bangle.loadWidgets(); Bangle.drawWidgets(); iterate(); animInterval = setInterval(iterate, 50); -// Show launcher when button pressed -Bangle.setUI("clock"); + diff --git a/apps/geissclk/metadata.json b/apps/geissclk/metadata.json index 456854dbd..68bd2a970 100644 --- a/apps/geissclk/metadata.json +++ b/apps/geissclk/metadata.json @@ -1,7 +1,7 @@ { "id": "geissclk", "name": "Geiss Clock", - "version": "0.03", + "version": "0.04", "description": "7 segment clock with animated background in the style of Ryan Geiss' music visualisation. NOTE: The first run will take ~1 minute to do some precalculation", "icon": "clock.png", "type": "clock", diff --git a/apps/glbasic/ChangeLog b/apps/glbasic/ChangeLog index 89aee01f8..d97fd44d5 100644 --- a/apps/glbasic/ChangeLog +++ b/apps/glbasic/ChangeLog @@ -1,2 +1,3 @@ 0.20: New App! +0.21: Tell clock widgets to hide. diff --git a/apps/glbasic/glbasic.app.js b/apps/glbasic/glbasic.app.js index ff7837ada..c1f82f74c 100644 --- a/apps/glbasic/glbasic.app.js +++ b/apps/glbasic/glbasic.app.js @@ -178,6 +178,8 @@ function draw() { //////////////////////////////////////////////////// // Bangle.setBarometerPower(true); +Bangle.setUI("clock"); + Bangle.loadWidgets(); draw(); @@ -197,6 +199,5 @@ Bangle.on('lcdPower', on => { } }); -Bangle.setUI("clock"); Bangle.drawWidgets(); diff --git a/apps/glbasic/metadata.json b/apps/glbasic/metadata.json index 7c79097da..6d4c562a3 100644 --- a/apps/glbasic/metadata.json +++ b/apps/glbasic/metadata.json @@ -2,7 +2,7 @@ "id": "glbasic", "name": "GLBasic Clock", "shortName": "GLBasic", - "version": "0.20", + "version": "0.21", "description": "A clock with large numbers", "dependencies": {"widpedom":"app"}, "icon": "icon48.png", diff --git a/apps/gpsautotime/ChangeLog b/apps/gpsautotime/ChangeLog index 97b80ecdf..de7af4fc7 100644 --- a/apps/gpsautotime/ChangeLog +++ b/apps/gpsautotime/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Set Bangle.js 2 compatible 0.03: Add setting to hide the widget +0.04: Use default Bangle formatter for booleans diff --git a/apps/gpsautotime/metadata.json b/apps/gpsautotime/metadata.json index 217a27931..c852c6a3e 100644 --- a/apps/gpsautotime/metadata.json +++ b/apps/gpsautotime/metadata.json @@ -2,7 +2,7 @@ "id": "gpsautotime", "name": "GPS auto time", "shortName": "GPS auto time", - "version": "0.03", + "version": "0.04", "description": "A widget that automatically updates the Bangle.js time to the GPS time whenever there is a valid GPS fix.", "icon": "widget.png", "type": "widget", diff --git a/apps/gpsautotime/settings.js b/apps/gpsautotime/settings.js index dbdd121d1..be6e3bbec 100644 --- a/apps/gpsautotime/settings.js +++ b/apps/gpsautotime/settings.js @@ -13,9 +13,8 @@ E.showMenu({ "" : { "title" : "GPS auto time" }, "< Back" : () => back(), - 'Show widget?': { - value: !!settings.show, // !! converts undefined to false - format: v => v?"Show":"Hide", + 'Show Widgets': { + value: !!settings.show, onchange: v => { settings.show = v; writeSettings(); diff --git a/apps/gpsnav/ChangeLog b/apps/gpsnav/ChangeLog index b4eaf5472..6f327f364 100644 --- a/apps/gpsnav/ChangeLog +++ b/apps/gpsnav/ChangeLog @@ -3,4 +3,5 @@ 0.03: Add Waypoint Editor 0.04: Fix great circle formula 0.05: Use locale for speed and distance + fix Vector font sizes - +0.06: Move waypoints.json (and editor) to 'waypoints' app +0.07: Add support for b2 \ No newline at end of file diff --git a/apps/gpsnav/README.md b/apps/gpsnav/README.md index af239b233..2b67799b8 100644 --- a/apps/gpsnav/README.md +++ b/apps/gpsnav/README.md @@ -4,9 +4,12 @@ The app is aimed at small boat navigation although it can also be used to mark t The app displays direction of travel (course), speed, direction to waypoint (bearing) and distance to waypoint. The screen shot below is before the app has got a GPS fix. +[Bangle.js 2] Button mappings in brackests. One additional feature: +On swiping on the main screen you can change the displayed metrics: Right changes to nautical metrics, left to the default locale metrics. + ![](first_screen.jpg) -The large digits are the course and speed. The top of the display is a linear compass which displays the direction of travel when a fix is received and you are moving. The blue text is the name of the current waypoint. NONE means that there is no waypoint set and so bearing and distance will remain at 0. To select a waypoint, press BTN2 (middle) and wait for the blue text to turn white. Then use BTN1 and BTN3 to select a waypoint. The waypoint choice is fixed by pressing BTN2 again. In the screen shot below a waypoint giving the location of Stone Henge has been selected. +The large digits are the course and speed. The top of the display is a linear compass which displays the direction of travel when a fix is received and you are moving. The blue text is the name of the current waypoint. NONE means that there is no waypoint set and so bearing and distance will remain at 0. To select a waypoint, press BTN2 (middle) [touch / BTN] and wait for the blue text to turn white. Then use BTN1 and BTN3 [swipe left/right] to select a waypoint. The waypoint choice is fixed by pressing BTN2 [touch / BTN] again. In the screen shot below a waypoint giving the location of Stone Henge has been selected. ![](waypoint_screen.jpg) @@ -18,7 +21,7 @@ The app lets you mark your current location as follows. There are vacant slots i ![](select_screen.jpg) -Bearing and distance are both zero as WP1 has currently no GPS location associated with it. To mark the location, press BTN2. +Bearing and distance are both zero as WP1 has currently no GPS location associated with it. To mark the location, press BTN2 [touch / BTN]. ![](marked_screen.jpg) diff --git a/apps/gpsnav/app.js b/apps/gpsnav/app.js index 8903e07fb..e2b6ee6f1 100644 --- a/apps/gpsnav/app.js +++ b/apps/gpsnav/app.js @@ -51,10 +51,10 @@ function drawCompass(course) { //displayed heading var heading = 0; -function newHeading(m,h){ +function 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>=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; @@ -125,7 +125,7 @@ function drawN(){ g.setColor(0,0,0); g.fillRect(10,230,60,239); g.setColor(1,1,1); - g.drawString("Sats " + satellites.toString(),10,230); + g.drawString("Sats " + satellites.toString(),10,230); } var savedfix; @@ -193,11 +193,11 @@ var SCREENACCESS = { }, release:function(){ this.withApp=true; - startdraw(); + startdraw(); setButtons(); } -} - +} + Bangle.on('lcdPower',function(on) { if (!SCREENACCESS.withApp) return; if (on) { @@ -207,7 +207,7 @@ Bangle.on('lcdPower',function(on) { } }); -var waypoints = require("Storage").readJSON("waypoints.json")||[{name:"NONE"}]; +var waypoints = require("waypoints").load(); wp=waypoints[0]; function nextwp(inc){ @@ -223,7 +223,7 @@ function doselect(){ if (selected && wpindex!=0 && waypoints[wpindex].lat===undefined && savedfix.fix) { waypoints[wpindex] ={name:"@"+wp.name, lat:savedfix.lat, lon:savedfix.lon}; wp = waypoints[wpindex]; - require("Storage").writeJSON("waypoints.json", waypoints); + require("waypoints").save(waypoints); } selected=!selected; drawN(); diff --git a/apps/gpsnav/app.min.js b/apps/gpsnav/app.min.js index 7771e2b98..a01b251e0 100644 --- a/apps/gpsnav/app.min.js +++ b/apps/gpsnav/app.min.js @@ -6,6 +6,6 @@ function drawN(){var a=loc.speed(speed);buf.setColor(1);buf.setFont("6x8",2);buf 0,30);buf.setColor(selected?1:2);buf.drawString(wp.name,140,0);buf.setColor(1);buf.drawString(a,60,0);buf.drawString(loc.distance(dist),60,30);flip(buf,Yoff+130);g.setFont("6x8",1);g.setColor(0,0,0);g.fillRect(10,230,60,239);g.setColor(1,1,1);g.drawString("Sats "+satellites.toString(),10,230)}var savedfix; function onGPS(a){savedfix=a;void 0!==a&&(course=isNaN(a.course)?course:Math.round(a.course),speed=isNaN(a.speed)?speed:a.speed,satellites=a.satellites);candraw&&(void 0!==a&&1==a.fix&&(dist=distance(a,wp),isNaN(dist)&&(dist=0),brg=bearing(a,wp),isNaN(brg)&&(brg=0)),drawN())}var intervalRef;function stopdraw(){candraw=!1;intervalRef&&clearInterval(intervalRef)} function startTimers(){candraw=!0;intervalRefSec=setInterval(function(){heading=newHeading(course,heading);course!=heading&&drawCompass(heading)},200)}function drawAll(){g.setColor(1,.5,.5);g.fillPoly([120,Yoff+50,110,Yoff+70,130,Yoff+70]);g.setColor(1,1,1);drawN();drawCompass(heading)}function startdraw(){g.clear();Bangle.drawWidgets();startTimers();drawAll()} -function setButtons(){setWatch(nextwp.bind(null,-1),BTN1,{repeat:!0,edge:"falling"});setWatch(doselect,BTN2,{repeat:!0,edge:"falling"});setWatch(nextwp.bind(null,1),BTN3,{repeat:!0,edge:"falling"})}var SCREENACCESS={withApp:!0,request:function(){this.withApp=!1;stopdraw();clearWatch()},release:function(){this.withApp=!0;startdraw();setButtons()}};Bangle.on("lcdPower",function(a){SCREENACCESS.withApp&&(a?startdraw():stopdraw())});var waypoints=require("Storage").readJSON("waypoints.json")||[{name:"NONE"}]; -wp=waypoints[0];function nextwp(a){selected&&(wpindex+=a,wpindex>=waypoints.length&&(wpindex=0),0>wpindex&&(wpindex=waypoints.length-1),wp=waypoints[wpindex],drawN())}function doselect(){selected&&0!=wpindex&&void 0===waypoints[wpindex].lat&&savedfix.fix&&(waypoints[wpindex]={name:"@"+wp.name,lat:savedfix.lat,lon:savedfix.lon},wp=waypoints[wpindex],require("Storage").writeJSON("waypoints.json",waypoints));selected=!selected;drawN()}g.clear();Bangle.setLCDBrightness(1);Bangle.loadWidgets();Bangle.drawWidgets(); +function setButtons(){setWatch(nextwp.bind(null,-1),BTN1,{repeat:!0,edge:"falling"});setWatch(doselect,BTN2,{repeat:!0,edge:"falling"});setWatch(nextwp.bind(null,1),BTN3,{repeat:!0,edge:"falling"})}var SCREENACCESS={withApp:!0,request:function(){this.withApp=!1;stopdraw();clearWatch()},release:function(){this.withApp=!0;startdraw();setButtons()}};Bangle.on("lcdPower",function(a){SCREENACCESS.withApp&&(a?startdraw():stopdraw())});var waypoints=require("waypoints").load(); +wp=waypoints[0];function nextwp(a){selected&&(wpindex+=a,wpindex>=waypoints.length&&(wpindex=0),0>wpindex&&(wpindex=waypoints.length-1),wp=waypoints[wpindex],drawN())}function doselect(){selected&&0!=wpindex&&void 0===waypoints[wpindex].lat&&savedfix.fix&&(waypoints[wpindex]={name:"@"+wp.name,lat:savedfix.lat,lon:savedfix.lon},wp=waypoints[wpindex],require("waypoints").save(waypoints));selected=!selected;drawN()}g.clear();Bangle.setLCDBrightness(1);Bangle.loadWidgets();Bangle.drawWidgets(); Bangle.setGPSPower(1);drawAll();startTimers();Bangle.on("GPS",onGPS);setButtons(); diff --git a/apps/gpsnav/app_b2.js b/apps/gpsnav/app_b2.js new file mode 100644 index 000000000..e46be649f --- /dev/null +++ b/apps/gpsnav/app_b2.js @@ -0,0 +1,268 @@ +var candraw = true; +var brg = 0; +var wpindex = 0; +var locindex = 0; +const labels = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"]; +var loc = { + speed: [ + require("locale").speed, + (kph) => { + return (kph / 1.852).toFixed(1) + "kn "; + } + ], + distance: [ + require("locale").distance, + (m) => { + return (m / 1.852).toFixed(3) + "nm "; + } + ] +}; + + +function drawCompass(course) { + if (!candraw) return; + g.setColor(g.theme.fg); + g.setFont("Vector", 18); + var start = course - 90; + if (start < 0) start += 360; + g.fillRect(14, 67, 162, 71); + var xpos = 16; + var frag = 15 - start % 15; + if (frag < 15) xpos += Math.floor((frag * 4) / 5); + else frag = 0; + for (var i = frag; i <= 180 - frag; i += 15) { + var res = start + i; + if (res % 90 == 0) { + g.drawString(labels[Math.floor(res / 45) % 8], xpos - 6, 28); + g.fillRect(xpos - 2, 47, xpos + 2, 67); + } else if (res % 45 == 0) { + g.drawString(labels[Math.floor(res / 45) % 8], xpos - 9, 28); + g.fillRect(xpos - 2, 52, xpos + 2, 67); + } else if (res % 15 == 0) { + g.fillRect(xpos, 58, xpos + 1, 67); + } + xpos += 12; + } + if (wpindex != 0) { + var bpos = brg - course; + if (bpos > 180) bpos -= 360; + if (bpos < -180) bpos += 360; + bpos = Math.round((bpos * 4) / 5) + 88; + if (bpos < 16) bpos = 7; + if (bpos > 160) bpos = 169; + g.setColor(g.theme.bgH); + g.fillCircle(bpos, 63, 8); + } +} + +//displayed heading +var heading = 0; + +function 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; +} + +var course = 0; +var speed = 0; +var satellites = 0; +var wp; +var dist = 0; + +function radians(a) { + return a * Math.PI / 180; +} + +function degrees(a) { + var d = a * 180 / Math.PI; + return (d + 360) % 360; +} + +function bearing(a, b) { + var delta = radians(b.lon - a.lon); + var alat = radians(a.lat); + var blat = radians(b.lat); + var y = Math.sin(delta) * Math.cos(blat); + var x = Math.cos(alat) * Math.sin(blat) - + Math.sin(alat) * Math.cos(blat) * Math.cos(delta); + return Math.round(degrees(Math.atan2(y, x))); +} + +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); + return Math.round(Math.sqrt(x * x + y * y) * 6371000); +} + +var selected = false; + +function drawN() { + g.clearRect(0, 89, 175, 175); + var txt = loc.speed[locindex](speed); + g.setColor(g.theme.fg); + g.setFont("6x8", 2); + g.drawString("o", 68, 87); + g.setFont("6x8", 1); + g.drawString(txt.substring(txt.length - 3), 156, 119); + g.setFont("Vector", 36); + var cs = course.toString().padStart(3, "0"); + g.drawString(cs, 2, 89); + g.drawString(txt.substring(0, txt.length - 3), 92, 89); + g.setColor(g.theme.fg); + g.setFont("Vector", 18); + var bs = brg.toString().padStart(3, "0"); + g.setColor(g.theme.fg); + g.drawString("Brg:", 1, 128); + g.drawString("Dist:", 1, 148); + g.setColor(selected ? g.theme.bgH : g.theme.bg); + g.fillRect(90, 127, 175, 143); + g.setColor(selected ? g.theme.fgH : g.theme.fg); + g.drawString(wp.name, 92, 128); + g.setColor(g.theme.fg); + g.drawString(bs, 42, 128); + g.drawString(loc.distance[locindex](dist), 42, 148); + g.setFont("6x8", 0.5); + g.drawString("o", 75, 127); + g.setFont("6x8", 1); + g.setColor(satellites ? g.theme.bg : g.theme.bgH); + g.fillRect(0, 167, 75, 175); + g.setColor(satellites ? g.theme.fg : g.theme.fgH); + g.drawString("Sats:", 1, 168); + g.drawString(satellites.toString(), 42, 168); +} + +var savedfix; + +function onGPS(fix) { + savedfix = fix; + if (fix !== undefined) { + course = isNaN(fix.course) ? course : Math.round(fix.course); + speed = isNaN(fix.speed) ? speed : fix.speed; + satellites = fix.satellites; + } + if (candraw) { + if (fix !== undefined && fix.fix == 1) { + dist = distance(fix, wp); + if (isNaN(dist)) dist = 0; + brg = bearing(fix, wp); + if (isNaN(brg)) brg = 0; + } + drawN(); + } +} + +var intervalRef; + +function stopdraw() { + candraw = false; + if (intervalRef) { + clearInterval(intervalRef); + } +} + +function startTimers() { + candraw = true; + intervalRefSec = setInterval(function() { + heading = newHeading(course, heading); + if (course != heading) drawCompass(heading); + }, 200); +} + +function drawAll() { + g.setColor(1, 0, 0); + g.fillPoly([88, 71, 78, 88, 98, 88]); + drawN(); + drawCompass(heading); +} + +function startdraw() { + g.clear(); + Bangle.drawWidgets(); + startTimers(); + drawAll(); +} + +function setButtons() { + Bangle.setUI("leftright", btn => { + if (!btn) { + doselect(); + } else { + nextwp(btn); + } + }); +} + +var SCREENACCESS = { + withApp: true, + request: function() { + this.withApp = false; + stopdraw(); + clearWatch(); + }, + release: function() { + this.withApp = true; + startdraw(); + setButtons(); + } +}; + +Bangle.on('lcdPower', function(on) { + if (!SCREENACCESS.withApp) return; + if (on) { + startdraw(); + } else { + stopdraw(); + } +}); + +var waypoints = require("waypoints").load(); +wp = waypoints[0]; + +function nextwp(inc) { + if (selected) { + wpindex += inc; + if (wpindex >= waypoints.length) wpindex = 0; + if (wpindex < 0) wpindex = waypoints.length - 1; + wp = waypoints[wpindex]; + drawN(); + } else { + locindex = inc > 0 ? 1 : 0; + drawN(); + } +} + +function doselect() { + if (selected && wpindex != 0 && waypoints[wpindex].lat === undefined && savedfix.fix) { + waypoints[wpindex] = { + name: "@" + wp.name, + lat: savedfix.lat, + lon: savedfix.lon + }; + wp = waypoints[wpindex]; + require("waypoints").save(waypoints); + } + selected = !selected; + print("selected = " + selected); + drawN(); +} + +g.clear(); +Bangle.setLCDBrightness(1); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +// load widgets can turn off GPS +Bangle.setGPSPower(1); +drawAll(); +startTimers(); +Bangle.on('GPS', onGPS); +// Toggle selected +setButtons(); \ No newline at end of file diff --git a/apps/gpsnav/metadata.json b/apps/gpsnav/metadata.json index 5c1830318..dce80112f 100644 --- a/apps/gpsnav/metadata.json +++ b/apps/gpsnav/metadata.json @@ -1,16 +1,17 @@ { "id": "gpsnav", "name": "GPS Navigation", - "version": "0.05", + "version": "0.07", "description": "Displays GPS Course and Speed, + Directions to waypoint and waypoint recording, now with waypoint editor", + "screenshots": [{"url":"screenshot-b2.png"}], "icon": "icon.png", "tags": "tool,outdoors,gps", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", - "interface": "waypoints.html", + "dependencies" : { "waypoints":"type" }, "storage": [ - {"name":"gpsnav.app.js","url":"app.min.js"}, + {"name":"gpsnav.app.js","url":"app.min.js","supports":["BANGLEJS"]}, + {"name":"gpsnav.app.js","url":"app_b2.js","supports":["BANGLEJS2"]}, {"name":"gpsnav.img","url":"app-icon.js","evaluate":true} - ], - "data": [{"name":"waypoints.json","url":"waypoints.json"}] + ] } diff --git a/apps/gpsnav/screenshot-b2.png b/apps/gpsnav/screenshot-b2.png new file mode 100644 index 000000000..34ad23679 Binary files /dev/null and b/apps/gpsnav/screenshot-b2.png differ diff --git a/apps/gpsnav/waypoints.html b/apps/gpsnav/waypoints.html deleted file mode 100644 index d02260732..000000000 --- a/apps/gpsnav/waypoints.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - -

List of waypoints

- - - - - - - - - - - - -
NameLat.Long.Actions
-
-

Add a new waypoint

-
-
-
- -
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- - - - - - - diff --git a/apps/gpsnav/waypoints.json b/apps/gpsnav/waypoints.json deleted file mode 100644 index 98a670c0d..000000000 --- a/apps/gpsnav/waypoints.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "name":"NONE" - }, - { - "name":"No10", - "lat":51.5032, - "lon":-0.1269 - }, - { - "name":"Stone", - "lat":51.1788, - "lon":-1.8260 - }, - { "name":"WP0" }, - { "name":"WP1" }, - { "name":"WP2" }, - { "name":"WP3" }, - { "name":"WP4" } -] \ No newline at end of file diff --git a/apps/gpsrec/ChangeLog b/apps/gpsrec/ChangeLog index f923739f0..5867140fb 100644 --- a/apps/gpsrec/ChangeLog +++ b/apps/gpsrec/ChangeLog @@ -30,3 +30,4 @@ 0.26: Multiple bugfixes 0.27: Map drawing with light theme (fix #1023) 0.28: Show distance more accurately in conjunction with new locale app (fix #1523) +0.29: Use default Bangle formatter for booleans diff --git a/apps/gpsrec/app.js b/apps/gpsrec/app.js index 4595f616d..acd5433b2 100644 --- a/apps/gpsrec/app.js +++ b/apps/gpsrec/app.js @@ -22,7 +22,6 @@ function showMainMenu() { '': { 'title': 'GPS Record' }, 'RECORD': { value: !!settings.recording, - format: v=>v?"On":"Off", onchange: v => { settings.recording = v; updateSettings(); diff --git a/apps/gpsrec/metadata.json b/apps/gpsrec/metadata.json index c870157df..192b05edf 100644 --- a/apps/gpsrec/metadata.json +++ b/apps/gpsrec/metadata.json @@ -1,7 +1,7 @@ { "id": "gpsrec", "name": "GPS Recorder", - "version": "0.28", + "version": "0.29", "description": "(NOT RECOMMENDED) - please use the more flexible 'Recorder' app instead. Application that allows you to record a GPS track. Can run in background", "icon": "app.png", "tags": "tool,outdoors,gps,widget", diff --git a/apps/gpstouch/Changelog b/apps/gpstouch/ChangeLog similarity index 100% rename from apps/gpstouch/Changelog rename to apps/gpstouch/ChangeLog diff --git a/apps/ha/ChangeLog b/apps/ha/ChangeLog new file mode 100644 index 000000000..e78b4ccd0 --- /dev/null +++ b/apps/ha/ChangeLog @@ -0,0 +1,2 @@ +0.01: Release +0.02: Includeas the ha.lib.js library that can be used by other apps or clocks. \ No newline at end of file diff --git a/apps/ha/README.md b/apps/ha/README.md new file mode 100644 index 000000000..654a262c8 --- /dev/null +++ b/apps/ha/README.md @@ -0,0 +1,90 @@ +# Home Assistant +This app integrates your BangleJs into the HomeAssistant. + + +# How to use +Click on the left and right side of the screen to select the triggers that you +configured. Click in the middle of the screen to send the trigger to HomeAssistant. + +![](screenshot.png) + + +# Initial Setup +1.) First of all, make sure that HomeAssistant and the HomeAssistant Android App works. + +2.) Open your BangleJs Gadgetbridge App, click on the Settings icon of your BangleJs and enable "Allow Intent Access" + +3.) Enable sensor in HomeAssistant Andoird App/Configuration/Companion App/Manage Sensors/LastUpdate Trigger + +4.) At the bottom of the same screen click on "Add New Intent" and enter "com.espruino.gadgetbridge.banglejs.HA" + +5.) The HomeAssistant Android app must be restarted in order to listen for those actions + -- a "Force Stop" is necessary (through Android App settings) or restart your phone! + +This setup must be done only once -- now you are ready to configure your BangleJS to +control some devices or entities in your HomeAssistant :) + + +# Setup Trigger +1.) Upload the app and all corresponding triggers through the AppStore UI. You must specify +the display name, the trigger as well as an icon. +The following icons are currently supported: +- ha (default) +- light +- door +- fire + + +2.) Create an "automation" in the HomeAssistant WebUI for each trigger that you created on your BangleJs in order to tell HomeAssistant what you want to control. A sample configuration is shown in the image below -- I use this trigger to open the door: + +![](ha_automation.png) + +3.) Don't forget to select the action that should be executed at the bottom of each automation. + + +# Default Trigger +This app also implements two default trigger that can always be used: +- APP_STARTED -- Will be sent whenever the app is started. So you could do some actions already when the app is sarted without the need of any user interaction. +- TRIGGER -- Will be sent whenever some trigger is executed. So you could generically listen to that. + + +# How to use the library (ha.lib.js) in my own app/clk +This app inlcludes a library that can be used by other apps or clocks +to read all configured intents or to send a trigger. Example code: + +```js +// First of all impport the library +var ha = require("ha.lib.js"); + +// You can read all triggers that a user configured simply via +var triggers = ha.getTriggers(); + +// Get display name and icon of trigger +var display = triggers[0].display; +var icon = triggers[0].getIcon(); + +// Trigger the first configured trigger +ha.sendTrigger(triggers[0].trigger); + +// Send a custom trigger that is not configured by a user +ha.sendTrigger("MY_CUSTOM_TRIGGER"); +``` + + +# FAQ + +## Sometimes the trigger is not executed +While playing and testing a bit I found that it is very important that you allow the android HomeAssistant app, as well as BangleJs Gadgetbridge app to (1) run in background and (2), disable energy optimizations for both apps. +Otherwise, Android could stop one of both apps and the trigger will never be sent to HomeAssistant... + +If you still have problems, you can try another trick: +Install "MacroDroid" from the Android AppStore and start the HomeAssistant App +each time the "com.espruino.gadgetbridge.banglejs.HA" intent is send together +with the extra trigger: APP_STARTED. Then whenever you open the app on your BangleJs +it is ensured that HomeAssistant is running... + +## Thanks to +Icons created by Flaticon + +## Creator +- [David Peer](https://github.com/peerdavid). diff --git a/apps/ha/custom.html b/apps/ha/custom.html new file mode 100644 index 000000000..49f5a2eb8 --- /dev/null +++ b/apps/ha/custom.html @@ -0,0 +1,51 @@ + + + + + +

Upload Tigger

+

+

+ + + + + + diff --git a/apps/ha/ha.app.js b/apps/ha/ha.app.js new file mode 100644 index 000000000..d9199fb0e --- /dev/null +++ b/apps/ha/ha.app.js @@ -0,0 +1,75 @@ +/** + * This app uses the ha library to send trigger to HomeAssistant. + */ +var ha = require("ha.lib.js"); +var W = g.getWidth(), H = g.getHeight(); +var position=0; +var triggers = ha.getTriggers(); + + +function draw() { + g.reset().clearRect(Bangle.appRect); + + var h = 22; + g.setFont("Vector", h); + var trigger = triggers[position]; + var w = g.stringWidth(trigger.display); + + g.setFontAlign(-1,-1); + var icon = trigger.getIcon(); + g.setColor(g.theme.fg).drawImage(icon, 12, H/5-2); + g.drawString("Home", icon.width + 20, H/5); + g.drawString("Assistant", icon.width + 18, H/5+24); + + g.setFontAlign(0,0); + var ypos = H/5*3+20; + g.drawRect(W/2-w/2-8, ypos-h/2-8, W/2+w/2+5, ypos+h/2+5); + g.fillRect(W/2-w/2-6, ypos-h/2-6, W/2+w/2+3, ypos+h/2+3); + g.setColor(g.theme.bg).drawString(trigger.display, W/2, ypos); +} + + +Bangle.on('touch', function(btn, e){ + var left = parseInt(g.getWidth() * 0.3); + var right = g.getWidth() - left; + var isLeft = e.x < left; + var isRight = e.x > right; + + if(isRight){ + Bangle.buzz(40, 0.6); + position += 1; + position = position >= triggers.length ? 0 : position; + draw(); + } + + if(isLeft){ + Bangle.buzz(40, 0.6); + position -= 1; + position = position < 0 ? triggers.length-1 : position; + draw(); + } + + if(!isRight && !isLeft){ + ha.sendTrigger("TRIGGER"); + + // Now send the selected trigger + Bangle.buzz(80, 0.6).then(()=>{ + ha.sendTrigger(triggers[position].trigger); + setTimeout(()=>{ + Bangle.buzz(80, 0.6); + }, 250); + }); + } +}); + + +// Send intent that the we started the app. +ha.sendTrigger("APP_STARTED"); + +// Next load the widgets and draw the app +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +// Draw app +draw(); +setWatch(_=>load(), BTN1); diff --git a/apps/ha/ha.icon.js b/apps/ha/ha.icon.js new file mode 100644 index 000000000..9bf6af796 --- /dev/null +++ b/apps/ha/ha.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwIjggOAAocH8AFDh/wAocfAok//gFDv/+Aof+vwoD/Ef3gFBgfwh4YCg/xx4FCh/z54FCj4LEn4XEv4jBGAX//k//4uBAokDAofAg/wnk8h/gLoIFE8ccnHH+Ef8+cnPn/EAAoYvB8ARBg4FB+EMmEPAoP4gkgj5BCwA+BAoXAHwIFC8EHAoZfBAoZfBAoZZDAsgAiA==")) \ No newline at end of file diff --git a/apps/ha/ha.lib.js b/apps/ha/ha.lib.js new file mode 100644 index 000000000..b09cbeab2 --- /dev/null +++ b/apps/ha/ha.lib.js @@ -0,0 +1,80 @@ +/** + * This library can be used to read all triggers that a user + * configured and send a trigger to homeassistant. + */ +function _getIcon(trigger){ + icon = trigger.icon; + if(icon == "light"){ + return { + width : 48, height : 48, bpp : 1, + transparent : 0, + buffer : require("heatshrink").decompress(atob("AAMBwAFE4AFDgYFJjgFBnAFBjwXBvAFBh4jBuAFCAQPwAQMHAQPgEQQCBEgcf/AvDn/8Aof//5GDAoJOBh+BAoOB+EP8YFB4fwgfnAoPnGANHAoPjHYQFBHYQFd44pDg47C4/gh/DIIZNFLIplGgF//wFIgZ9BRIUHRII7Ch4FBUIUOAoKzCjwFEhgCBmDpIVooFFh4oCAA4LFC5b7BAob1BAYI=")) + }; + } else if(icon == "door"){ + return { + width : 48, height : 48, bpp : 1, + transparent : 0, + buffer : require("heatshrink").decompress(atob("AAM4Aok/4AED///Aov4Aon8DgQGBAv4FpnIFKJv4FweAQFFAgQFB8AFDnADC")) + }; + } else if (icon == "fire"){ + return { + width : 48, height : 48, bpp : 1, + transparent : 0, + buffer : require("heatshrink").decompress(atob("ABsDAokBwAFE4AFE8AFE+AFE/AFJgf8Aon+AocHAokP/8QAokYAoUfAok//88ApF//4kDAo//AgMQAgIFCjgFEjwFCOYIFFHQIFDn/+AoJ/BAoIqBAoN//xCBAoI5BDIPAgP//gFB8AFChYFBgf//EJAogOBAoSgBAoMHAQIFEFgXAAoJEBv4FCNoQFGVYd/wAFEYYIFIvwCBDoV8UwQCBcgUPwDwDfQMBaIYADA")) + }; + } + + // Default is always the HA icon + return { + width : 48, height : 48, bpp : 1, + transparent : 0, + buffer : require("heatshrink").decompress(atob("AD8BwAFDg/gAocP+AFDj4FEn/8Aod//wFD/1+FAf4j+8AoMD+EPDAUH+OPAoUP+fPAoUfBYk/C4l/EYIwC//8n//FwIFEgYFD4EH+E8nkP8BdBAonjjk44/wj/nzk58/4gAFDF4PgCIMHAoPwhkwh4FB/EEkEfIIWAHwIFC4A+BAoXgg4FDL4IFDL4IFDLIYFkAEQA==")) + }; +} + +exports.getTriggers = function(){ + var triggers = [ + {display: "Empty", trigger: "NOP", icon: "ha"}, + ]; + + try{ + triggers = require("Storage").read("ha.trigger.json"); + triggers = JSON.parse(triggers); + + // We lazy load all icons, otherwise, we have to keep + // all the icons n times in memory which can be + // problematic for embedded devices. Therefore, + // we lazy load icons only if needed using the getIcon + // method of each trigger... + triggers.forEach(trigger => { + trigger.getIcon = function(){ + return _getIcon(trigger); + } + }) + } catch(e) { + // In case there are no user triggers yet, we show the default... + } + + return triggers; +} + +exports.sendTrigger = function(triggerName){ + var retries=3; + + while(retries > 0){ + try{ + // Now lets send the trigger that we sould send. + Bluetooth.println(JSON.stringify({ + t:"intent", + action:"com.espruino.gadgetbridge.banglejs.HA", + extra:{ + trigger: triggerName + }}) + ); + retries = -1; + + } catch(e){ + retries--; + } + } +} \ No newline at end of file diff --git a/apps/ha/ha.png b/apps/ha/ha.png new file mode 100644 index 000000000..8fce958e4 Binary files /dev/null and b/apps/ha/ha.png differ diff --git a/apps/ha/ha_automation.png b/apps/ha/ha_automation.png new file mode 100644 index 000000000..9372cfa15 Binary files /dev/null and b/apps/ha/ha_automation.png differ diff --git a/apps/ha/metadata.json b/apps/ha/metadata.json new file mode 100644 index 000000000..63308b933 --- /dev/null +++ b/apps/ha/metadata.json @@ -0,0 +1,25 @@ +{ + "id": "ha", + "name": "HomeAssistant", + "version": "0.02", + "description": "Integrates your BangleJS into HomeAssistant.", + "icon": "ha.png", + "type": "app", + "tags": "tool", + "readme": "README.md", + "supports": ["BANGLEJS2"], + "custom": "custom.html", + "screenshots": [ + {"url":"screenshot.png"}, + {"url":"screenshot_2.png"}, + {"url":"screenshot_3.png"} + ], + "data": [ + {"name":"ha.trigger.json" } + ], + "storage": [ + {"name":"ha.app.js","url":"ha.app.js"}, + {"name":"ha.lib.js","url":"ha.lib.js"}, + {"name":"ha.img","url":"ha.icon.js","evaluate":true} + ] +} diff --git a/apps/ha/screenshot.png b/apps/ha/screenshot.png new file mode 100644 index 000000000..dc059e2de Binary files /dev/null and b/apps/ha/screenshot.png differ diff --git a/apps/ha/screenshot_2.png b/apps/ha/screenshot_2.png new file mode 100644 index 000000000..55019c3b1 Binary files /dev/null and b/apps/ha/screenshot_2.png differ diff --git a/apps/ha/screenshot_3.png b/apps/ha/screenshot_3.png new file mode 100644 index 000000000..b9eae0b74 Binary files /dev/null and b/apps/ha/screenshot_3.png differ diff --git a/apps/hardalarm/ChangeLog b/apps/hardalarm/ChangeLog index dac7d317e..fea8770fc 100644 --- a/apps/hardalarm/ChangeLog +++ b/apps/hardalarm/ChangeLog @@ -1,3 +1,4 @@ 0.01: Add a number to match to turn off alarm 0.02: Respect Quiet Mode 0.03: Fix hour/minute wrapping code for new menu system +0.04: Use default Bangle formatter for booleans diff --git a/apps/hardalarm/app.js b/apps/hardalarm/app.js index 0c72a2c8f..0aa33b21b 100644 --- a/apps/hardalarm/app.js +++ b/apps/hardalarm/app.js @@ -66,17 +66,14 @@ function editAlarm(alarmIndex) { }, /*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 } }; diff --git a/apps/hardalarm/metadata.json b/apps/hardalarm/metadata.json index 1dab4501d..df287b426 100644 --- a/apps/hardalarm/metadata.json +++ b/apps/hardalarm/metadata.json @@ -2,7 +2,7 @@ "id": "hardalarm", "name": "Hard Alarm", "shortName": "HardAlarm", - "version": "0.03", + "version": "0.04", "description": "Make sure you wake up! Count to the right number to turn off the alarm", "icon": "app.png", "tags": "tool,alarm,widget", diff --git a/apps/hcclock/ChangeLog b/apps/hcclock/ChangeLog index f70653d58..289c7ac2d 100644 --- a/apps/hcclock/ChangeLog +++ b/apps/hcclock/ChangeLog @@ -1,3 +1,4 @@ 0.01: Base code 0.02: Saved settings when switching color scheme -0.03: Added Button 3 opening messages (if app is installed) \ No newline at end of file +0.03: Added Button 3 opening messages (if app is installed) +0.04: Use `messages` library to check for new messages \ No newline at end of file diff --git a/apps/hcclock/hcclock.app.js b/apps/hcclock/hcclock.app.js index de5163996..9558c052b 100644 --- a/apps/hcclock/hcclock.app.js +++ b/apps/hcclock/hcclock.app.js @@ -3,7 +3,7 @@ // Numbers Rect order (left, top, right, bottom) // Each number defines a set of rects to draw -const numbers = +const numbers = [ [// Zero [0, 0, 1, 0.2], @@ -64,7 +64,7 @@ const numbers = [0, 0.8, 1, 1], [0, 0, 0.1, 0.6], [0.9, 0, 1, 1] - ] + ] ]; const months = [ "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" ]; @@ -103,7 +103,7 @@ function updateTime() let mo = now.getMonth(); let y = now.getFullYear(); let d = now.getDate(); - + if(h != hour) { hour = h; @@ -127,7 +127,7 @@ function updateTime() day = d; g.setFont("6x8", 2); g.setFontAlign(0, -1, 0); - g.drawString(fmtDate(d,mo,y,hour), 120, 120); + g.drawString(fmtDate(d,mo,y,hour), 120, 120); } drawMessages(); } @@ -136,7 +136,7 @@ function drawDigits(x, value) { if(!Bangle.isLCDOn()) // No need to draw when LCD Off return; - + drawChar(Math.floor(value/10), 15, x, 115, x+50); if(value%10 == 1) drawChar(value%10, 55, x, 155, x+50); @@ -228,27 +228,18 @@ function flipColors() // MESSAGE HANDLING() // -let messages_installed = require("Storage").read("messages.app.js") != undefined; +let messages_installed = require("Storage").read("messages") !== undefined; function handleMessages() { - if(messages_installed && hasMessages() > 0) - { - E.showMessage("Loading Messages..."); - load("messages.app.js"); - } + if(!hasMessages()) return; + E.showMessage("Loading Messages..."); + load("messages.app.js"); } function hasMessages() { - if(!messages_installed) - return false; - - var messages = require("Storage").readJSON("messages.json",1)||[]; - if (messages.some(m=>m.new)) - return true; - else - return false; + return messages_installed && require("messages").status() === 'new'; } let msg = atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+DAADDAADDAADDwAPD8A/DOBzDDn/DA//DAHvDAPvjAPvjAPvjAPvh///gf/vAAD+AAB8AAAAA=="); @@ -256,20 +247,21 @@ let had_messages = false; function drawMessages() { - if(!had_messages && hasMessages()) { + const has_messages = hasMessages(); + if(has_messages === had_messages) return; + if(has_messages) { g.setColor(255,255,255); g.drawImage(msg, 184, 212); g.setFont("6x8", 2); g.setFontAlign(0, -1, 0); g.drawString(">", 224, 216); - had_messages = true; - } - else if (had_messages && !hasMessages()) + } + else { g.setColor(0,0,0); g.fillRect(180, 210, 240, 240); - had_messages = false; } + had_messages = has_messages; } ////////////////////////////////////////// diff --git a/apps/hcclock/metadata.json b/apps/hcclock/metadata.json index 0d4cbe0cd..b8f8c14b9 100644 --- a/apps/hcclock/metadata.json +++ b/apps/hcclock/metadata.json @@ -1,7 +1,7 @@ { "id": "hcclock", "name": "Hi-Contrast Clock", - "version": "0.03", + "version": "0.04", "description": "Hi-Contrast Clock : A simple yet very bold clock that aims to be readable in high luninosity environments. Uses big 10x5 pixel digits. Use BTN 1 to switch background and foreground colors.", "icon": "hcclock-icon.png", "type": "clock", diff --git a/apps/health/interface.html b/apps/health/interface.html index 0791acd24..a708e2645 100644 --- a/apps/health/interface.html +++ b/apps/health/interface.html @@ -113,7 +113,7 @@ function getMonthList() { Util.showModal("Deleting..."); Util.eraseStorage(filename,()=>{ Util.hideModal(); - getTrackList(); + getMonthList(); }); } if (task=="downloadcsv") { diff --git a/apps/heart/ChangeLog b/apps/heart/ChangeLog index f6fd9793e..fe03575c9 100644 --- a/apps/heart/ChangeLog +++ b/apps/heart/ChangeLog @@ -11,5 +11,6 @@ Reduce memory usage by ~30% Generate scale based on defined minimum and maximum measurement Added background line on 50% to ease estimation of drawn values -0.06: tag HRM power requests to allow this ot work alongside other widgets/apps (fix #799) -0.07: theme support +0.06: Tag HRM power requests to allow this ot work alongside other widgets/apps (fix #799) +0.07: Theme support +0.08: Use default Bangle formatter for booleans diff --git a/apps/heart/app.js b/apps/heart/app.js index 5428ea06b..c10185b5f 100644 --- a/apps/heart/app.js +++ b/apps/heart/app.js @@ -28,7 +28,6 @@ function showMainMenu() { '': { 'title': 'Heart Recorder' }, 'RECORD': { value: !!settings.isRecording, - format: v=>v?"On":"Off", onchange: v => { settings.isRecording = v; updateSettings(); diff --git a/apps/heart/metadata.json b/apps/heart/metadata.json index 6265dbfef..2071bdf08 100644 --- a/apps/heart/metadata.json +++ b/apps/heart/metadata.json @@ -2,7 +2,7 @@ "id": "heart", "name": "Heart Rate Recorder", "shortName": "HRM Record", - "version": "0.07", + "version": "0.08", "description": "Application that allows you to record your heart rate. Can run in background", "icon": "app.png", "tags": "tool,health,widget", diff --git a/apps/hebrew_calendar/customizer.html b/apps/hebrew_calendar/customizer.html index bea860e53..c6d529414 100644 --- a/apps/hebrew_calendar/customizer.html +++ b/apps/hebrew_calendar/customizer.html @@ -7,6 +7,333 @@ Hebrew Calendar Customizer + + @@ -20,11 +347,15 @@
- + - -
get your latitude and longitude from plus.codes or:
- + +
get your latitude and longitude from plus.codes or: +
+
- - diff --git a/apps/hebrew_calendar/customizer.mjs b/apps/hebrew_calendar/customizer.mjs deleted file mode 100644 index 06716a63b..000000000 --- a/apps/hebrew_calendar/customizer.mjs +++ /dev/null @@ -1,329 +0,0 @@ -import { - HebrewCalendar, - HDate, - Location, - Zmanim, -} from "https://cdn.skypack.dev/@hebcal/core@^3?min"; - -function onload(event) { - event.preventDefault(); - const latLon = getLatLonFromForm(); - const events = generateHebCal(latLon); - const calendar = serializeEvents(events); - console.debug(calendar); - globalThis["cal"] = calendar; - loadWatch(calendar); -} - -function loadWatch(json) { - sendCustomizedApp({ - id: "hebrew_calendar", - - storage: [ - { - name: "hebrew_calendar.app.js", - url: "app.js", - // content below is same as app.js except for the first line which customizes the hebrewCalendar object used - content: ` -let hebrewCalendar = ${json}; - -const dayInMS = 86400000; - -const DateProvider = { now: () => Date.now() }; - -const Layout = require("Layout"); -const Locale = require("locale"); - -let nextEndingEvent; - -function getCurrentEvents() { - const now = DateProvider.now(); - - const current = hebrewCalendar.filter( - (x) => x.startEvent <= now && x.endEvent >= now - ); - - nextEndingEvent = current.reduce((acc, ev) => { - return Math.min(acc, ev.endEvent); - }, Infinity); - - return current.map((event, i) => { - return { - type: "txt", - font: "12x20", - id: "currentEvents" + i, - label: event.desc, - pad: 2, - bgCol: g.theme.bg, - }; - }); -} - -function getUpcomingEvents() { - const now = DateProvider.now(); - - const futureEvents = hebrewCalendar.filter( - (x) => x.startEvent >= now && x.startEvent <= now + dayInMS - ); - - let warning; - let eventsLeft = hebrewCalendar.filter( - (x) => x.startEvent >= now && x.startEvent <= now + dayInMS * 14 - ).length; - - if (eventsLeft < 14) { - warning = { - startEvent: 0, - type: "txt", - font: "4x6", - id: "warning", - label: "only " + eventsLeft + " events left in calendar; update soon", - pad: 2, - bgCol: g.theme.bg, - }; - } - - return futureEvents - .slice(0, 2) - .map((event, i) => { - return { - startEvent: event.startEvent, - type: "txt", - font: "6x8", - id: "upcomingEvents" + 1, - label: event.desc + " at " + Locale.time(new Date(event.startEvent), 1), - pad: 2, - bgCol: g.theme.bg, - }; - }) - .concat(warning) - .sort(function (a, b) { - return a.startEvent - b.startEvent; - }); -} - -function dateTime() { - return ( - Locale.dow(new Date(), 1) + - " " + - Locale.date(new Date(), 1) + - " " + - Locale.time(new Date(), 1) - ); -} - -function makeLayout() { - return new Layout( - { - type: "v", - c: [ - { - type: "txt", - font: "6x8", - id: "title", - label: "-- Hebrew Calendar Events --", - pad: 2, - bgCol: g.theme.bg2, - }, - { - type: "txt", - font: "6x8", - id: "currently", - label: "Currently", - pad: 2, - bgCol: g.theme.bgH, - }, - ] - .concat(getCurrentEvents()) - .concat([ - { - type: "txt", - font: "6x8", - label: "Upcoming", - id: "upcoming", - pad: 2, - bgCol: g.theme.bgH, - }, - ]) - .concat(getUpcomingEvents()) - .concat([ - { - type: "txt", - font: "Vector14", - id: "time", - label: dateTime(), - pad: 2, - bgCol: undefined, - }, - ]), - }, - { lazy: true } - ); -} -let layout = makeLayout(); -// see also https://www.espruino.com/Bangle.js+Layout#updating-the-screen - -// timeout used to update every minute -let drawTimeout; - -function draw() { - layout.time.label = dateTime(); - layout.render(); - - // schedule a draw for the next minute - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = setTimeout(function () { - drawTimeout = undefined; - draw(); - }, 60000 - (DateProvider.now() % 60000)); - console.log("updated time"); -} - -// update time and draw -g.clear(); -Bangle.loadWidgets(); -Bangle.drawWidgets(); -draw(); - -function findNextEvent() { - return hebrewCalendar.find((ev) => { - return ev.startEvent > DateProvider.now(); - }); -} - -function updateCalendar() { - layout.clear(); - layout = makeLayout(); - layout.forgetLazyState(); - layout.render(); - - let nextChange = Math.min( - findNextEvent().startEvent - DateProvider.now() + 5000, - nextEndingEvent - DateProvider.now() + 5000 - ); - setTimeout(updateCalendar, nextChange); - console.log("updated events"); -} - -updateCalendar(); - -Bangle.setUI("clock"); - `, - }, - ], - }); -} - -document - .querySelector("button[type=submit]") - .addEventListener("click", onload, false); - -document.querySelector("#geoloc")?.addEventListener("click", (event) => { - event.preventDefault(); - navigator.geolocation.getCurrentPosition( - (pos) => { - const { - coords: { latitude, longitude }, - } = pos; - locationElements[0].value = latitude; - locationElements[1].value = longitude; - console.debug(pos); - }, - (err) => { - if (err.PERMISSION_DENIED) { - alert("permission required to use geolocation api; enter manually"); - } - if (err.POSITION_UNAVAILABLE) { - alert("position unavailable; enter manually"); - } - }, - { enableHighAccuracy: false } - ); -}); - -document.querySelector( - "#hDate" -).innerText = `Today is ${new Date().toLocaleDateString()} & ${new HDate().toString()}`; - -const locationElements = [ - document.querySelector("#lat"), - document.querySelector("#lon"), -]; - -function getLatLonFromForm() { - const latLon = locationElements.map((el) => el.value); - if (locationElements.every((x) => x.checkValidity())) { - return latLon; - } else { - console.debug("lat lon invalid error"); - return [0, 0]; - } -} - -function groupBy(arr, fn) { - return arr - .map(typeof fn === "function" ? fn : (val) => val[fn]) - .reduce((acc, val, i) => { - acc[val] = (acc[val] || []).concat(arr[i]); - return acc; - }, {}); -} - -function generateHebCal(latLon) { - const location = new Location( - ...latLon, - document.querySelector("#inIL").checked - ); - - const now = new Date(); - - const options = { - year: now.getFullYear(), - isHebrewYear: false, - candlelighting: true, - location, - addHebrewDates: true, - addHebrewDatesForEvents: true, - sedrot: true, - start: now, - end: new Date(now.getFullYear(), now.getMonth() + 3), - }; - - const events = HebrewCalendar.calendar(options).map((ev) => { - const { desc, eventTime, startEvent, endEvent } = ev; - - const zman = new Zmanim(ev.date, ...latLon.map(Number)); - - let output = { - desc, - startEvent: startEvent?.eventTime?.getTime() || zman.gregEve().getTime(), - endEvent: endEvent?.eventTime?.getTime() || zman.shkiah().getTime(), - }; - - if (eventTime) { - delete output.startEvent; - delete output.endEvent; - output.startEvent = eventTime.getTime(); - output.endEvent = eventTime.getTime() + 60000 * 15; - } - - return output; - }); - - // console.table(events) - - return events.sort((a, b) => { - return a.startEvent - b.startEvent; - }); -} - -function enc(data) { - return btoa(heatshrink.compress(new TextEncoder().encode(data))); -} - -function serializeEvents(events) { - // const splitByGregorianMonth = groupBy(events, (evt) => { - // return new Date(evt.startEvent).getMonth(); - // }); - return JSON.stringify(events); -} diff --git a/apps/hidcam/ChangeLog b/apps/hidcam/ChangeLog index 480d7d448..b0ba92aef 100644 --- a/apps/hidcam/ChangeLog +++ b/apps/hidcam/ChangeLog @@ -1,3 +1,4 @@ 0.01: Core functionnality 0.02: Offer to enable HID if disabled 0.03: Adds Readme and tags to be used by App Loader +0.04: Adds Bangle.js 2 support, Buzz and Touch diff --git a/apps/hidcam/README.md b/apps/hidcam/README.md index 5e8d40817..fa8e0153b 100644 --- a/apps/hidcam/README.md +++ b/apps/hidcam/README.md @@ -7,7 +7,7 @@ Control the camera shutter from your phone using your watch 1. In settings, enable HID for "Keyboard & Media". 2. Pair your watch to your phone. 3. Load your camera app on your phone. -4. There you go, launch the app on your watch and press button 2 to trigger the shutter ! +4. There you go, launch the app on your watch and press the button (button 2 on Bangle.js 1) to trigger the shutter ! ## How does it work ? diff --git a/apps/hidcam/app.js b/apps/hidcam/app.js index bb8ddf7e9..639018db3 100644 --- a/apps/hidcam/app.js +++ b/apps/hidcam/app.js @@ -1,25 +1,29 @@ var storage = require('Storage'); const settings = storage.readJSON('setting.json',1) || { HID: false }; - +const isB2 = process.env.HWVERSION === 2; var sendHid, camShot, profile; if (settings.HID=="kbmedia") { profile = 'camShutter'; sendHid = function (code, cb) { - try { - NRF.sendHIDReport([1,code], () => { - NRF.sendHIDReport([1,0], () => { - if (cb) cb(); - }); - }); - } catch(e) { - print(e); - } + + try { + NRF.sendHIDReport([1,code], () => { + NRF.sendHIDReport([1,0], () => { + if (cb) cb(); + }); + }); + } catch(e) { + print(e); + } + }; camShot = function (cb) { sendHid(0x80, cb); }; } else { + E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) { + if (enable) { settings.HID = "kbmedia"; require("Storage").write('setting.json', settings); @@ -31,10 +35,15 @@ function drawApp() { g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); - g.fillCircle(122,127,60); - g.drawImage(storage.read("hidcam.img"),100,105); - const d = g.getWidth() - 18; - + if (!isB2) { // Bangle.js 1 + g.fillCircle(122,127,60); + g.drawImage(storage.read("hidcam.img"),100,105); + const d = g.getWidth() - 18; + } else { + g.fillCircle(90,95,60); + g.drawImage(storage.read("hidcam.img"),65,70); + const d = g.getWidth() - 18; + } function c(a) { return { width: 8, @@ -46,12 +55,27 @@ function drawApp() { g.fillRect(180,130, 240, 124); } -if (camShot) { - setWatch(function(e) { - E.showMessage('camShot !'); - setTimeout(drawApp, 1000); - camShot(() => {}); - }, BTN2, { edge:"falling",repeat:true,debounce:50}); - + if (camShot) { + if (!isB2) { // Bangle.js 1 + setWatch(function(e) { + E.showMessage('camShot !'); + Bangle.buzz(300, 1); + setTimeout(drawApp, 1000); + camShot(() => {}); + }, BTN2, { edge:"falling",repeat:true,debounce:50}); + } else { // Bangle.js 2 + setWatch(function(e) { + E.showMessage('camShot !'); + Bangle.buzz(300, 1); + setTimeout(drawApp, 1000); + camShot(() => {}); + }, BTN, { edge:"falling",repeat:true,debounce:50}); + Bangle.on('touch', function (wat, tap) { + E.showMessage('camShot !'); + Bangle.buzz(300, 1); + setTimeout(drawApp, 1000); + camShot(() => {}); + }); + } drawApp(); -} + } diff --git a/apps/hidcam/metadata.json b/apps/hidcam/metadata.json index b2ef33229..b57d41ed1 100644 --- a/apps/hidcam/metadata.json +++ b/apps/hidcam/metadata.json @@ -2,11 +2,11 @@ "id": "hidcam", "name": "Camera shutter", "shortName": "Cam shutter", - "version": "0.03", + "version": "0.04", "description": "Enable HID, connect to your phone, start your camera and trigger the shot on your Bangle", "icon": "app.png", "tags": "bluetooth,tool", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"hidcam.app.js","url":"app.js"}, diff --git a/apps/hidmsicswipe/changelog b/apps/hidmsicswipe/ChangeLog similarity index 100% rename from apps/hidmsicswipe/changelog rename to apps/hidmsicswipe/ChangeLog diff --git a/apps/homework/ChangeLog b/apps/homework/ChangeLog new file mode 100644 index 000000000..b9a5425d1 --- /dev/null +++ b/apps/homework/ChangeLog @@ -0,0 +1,2 @@ +... +0.10: First update with ChangeLog Added diff --git a/apps/homework/metadata.json b/apps/homework/metadata.json index a46c74dad..d2af99fd4 100644 --- a/apps/homework/metadata.json +++ b/apps/homework/metadata.json @@ -2,7 +2,7 @@ { "id": "homework", "name": "Homework", "shortName":"Homework", - "version":"0.1", + "version":"0.10", "description": "A simple app to manage homework", "icon": "app.png", "tags": "tool", diff --git a/apps/hralarm/ChangeLog b/apps/hralarm/ChangeLog index 4c21f3ace..11e79d703 100644 --- a/apps/hralarm/ChangeLog +++ b/apps/hralarm/ChangeLog @@ -1 +1,2 @@ 0.01: New Widget! +0.02: Use default Bangle formatter for booleans diff --git a/apps/hralarm/metadata.json b/apps/hralarm/metadata.json index 1fae68084..4c661b3fc 100644 --- a/apps/hralarm/metadata.json +++ b/apps/hralarm/metadata.json @@ -2,7 +2,7 @@ "id": "hralarm", "name": "Heart rate alarm", "shortName":"HR Alarm", - "version":"0.01", + "version":"0.02", "description": "This invisible widget vibrates whenever the heart rate gets close to the upper limit or goes over or under the configured limits", "icon": "widget.png", "type": "widget", diff --git a/apps/hralarm/settings.js b/apps/hralarm/settings.js index 3158ab8b7..02cdccdaf 100644 --- a/apps/hralarm/settings.js +++ b/apps/hralarm/settings.js @@ -17,7 +17,6 @@ '< Back': back, 'Enabled': { value: !!settings.enabled, - format: v => settings.enabled ? "On" : "Off", onchange: v => { settings.enabled = v; writeSettings(); diff --git a/apps/hrm/ChangeLog b/apps/hrm/ChangeLog index f05a9dc13..62956e8cd 100644 --- a/apps/hrm/ChangeLog +++ b/apps/hrm/ChangeLog @@ -7,3 +7,4 @@ 0.07: Update scaling for new firmware 0.08: Don't force backlight on/watch unlocked on Bangle 2 0.09: Grey out BPM until confidence is over 50% +0.10: Autoscale raw graph to maximum value seen diff --git a/apps/hrm/heartrate.js b/apps/hrm/heartrate.js index 703b60c01..386341e6d 100644 --- a/apps/hrm/heartrate.js +++ b/apps/hrm/heartrate.js @@ -4,7 +4,7 @@ if (process.env.HWVERSION == 1) { } Bangle.setHRMPower(1); -var hrmInfo, hrmOffset = 0; +var hrmInfo = {}, hrmOffset = 0; var hrmInterval; var btm = g.getHeight()-1; var lastHrmPt = []; // last xy coords we draw a line to @@ -29,19 +29,34 @@ function onHRM(h) { hrmInterval = setInterval(readHRM,41); }, 40); } + updateHrm(); +} +Bangle.on('HRM', onHRM); +function updateHrm(){ var px = g.getWidth()/2; - g.setFontAlign(0,0); + g.setFontAlign(0,-1); g.clearRect(0,24,g.getWidth(),80); - g.setFont("6x8").drawString("Confidence "+hrmInfo.confidence+"%", px, 75); - var str = hrmInfo.bpm; + g.setFont("6x8").drawString("Confidence "+(hrmInfo.confidence || "--")+"%", px, 70); + + updateScale(); + + g.setFontAlign(0,0); + var str = hrmInfo.bpm || "--"; g.setFontVector(40).setColor(hrmInfo.confidence > 50 ? g.theme.fg : "#888").drawString(str,px,45); px += g.stringWidth(str)/2; g.setFont("6x8").setColor(g.theme.fg); g.drawString("BPM",px+15,45); } -Bangle.on('HRM', onHRM); +function updateScale(){ + g.setFontAlign(-1,-1); + g.clearRect(2,70,40,78); + g.setFont("6x8").drawString(scale, 2, 70); +} + +var rawMax = 0; +var scale = 2000; var MID = (g.getHeight()+80)/2; /* On newer (2v10) firmwares we can subscribe to get HRM events as they happen */ @@ -49,19 +64,27 @@ Bangle.on('HRM-raw', function(v) { h=v; hrmOffset++; if (hrmOffset>g.getWidth()) { - hrmOffset=0; + let thousands = Math.round(rawMax / 1000) * 1000; + if (thousands > scale) scale = thousands; + g.clearRect(0,80,g.getWidth(),g.getHeight()); + updateScale(); + + hrmOffset=0; lastHrmPt = [-100,0]; } - - y = E.clip(btm-(8+v.filt/2000),btm-16,btm); + if (rawMax < v.raw) { + rawMax = v.raw; + } + y = E.clip(btm-(8+v.filt/3000),btm-24,btm); g.setColor(1,0,0).fillRect(hrmOffset,btm, hrmOffset, y); - y = E.clip(btm - (v.raw/45),84,btm); + y = E.clip(btm - (v.raw/scale*84),84,btm); g.setColor(g.theme.fg).drawLine(lastHrmPt[0],lastHrmPt[1],hrmOffset, y); lastHrmPt = [hrmOffset, y]; if (counter !==undefined) { counter = undefined; g.clearRect(0,24,g.getWidth(),g.getHeight()); + updateHrm(); } }); @@ -76,7 +99,8 @@ function countDown() { g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); -g.reset().setFont("6x8",2).setFontAlign(0,0); +g.setColor(g.theme.fg); +g.reset().setFont("6x8",2).setFontAlign(0,-1); g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16); countDown(); diff --git a/apps/hrm/metadata.json b/apps/hrm/metadata.json index 10821d094..c5a5f4f4d 100644 --- a/apps/hrm/metadata.json +++ b/apps/hrm/metadata.json @@ -1,7 +1,7 @@ { "id": "hrm", "name": "Heart Rate Monitor", - "version": "0.09", + "version": "0.10", "description": "Measure your heart rate and see live sensor data", "icon": "heartrate.png", "tags": "health", diff --git a/apps/hrmaccevents/ChangeLog b/apps/hrmaccevents/ChangeLog index 5560f00bc..b8519d272 100644 --- a/apps/hrmaccevents/ChangeLog +++ b/apps/hrmaccevents/ChangeLog @@ -1 +1,5 @@ 0.01: New App! +0.02: Show status info on display + Allow recording to Bangle +0.03: Allow downloading recorded files + Make it work with more BTHRM configs diff --git a/apps/hrmaccevents/README.md b/apps/hrmaccevents/README.md new file mode 100644 index 000000000..ecd619152 --- /dev/null +++ b/apps/hrmaccevents/README.md @@ -0,0 +1,33 @@ +# Record HRM and accelerometer events + +Record events as they happen via bluetooth or to a file. +This app can use [BTHRM](https://banglejs.com/apps/#bthrm) as a reference. + +## Steps for usage + +* (Optional) Install [BTHRM](https://banglejs.com/apps/#bthrm) as reference (use ECG based sensor for best accuracy). + * Configure BTHRM to "Both"-Mode or use a version >= 0.12. This prevents data beeing lost because BTHRM can replace the HRM-events data with BTHRM data. +* Click "Start" in browser. +* Wait until the "Events" number starts to grow, that means there are events recorded. +* Record for some time, since BTHRM and HRM often need some seconds to start getting useful values. Consider 2000 events a useful minimum. +* (Recording to file) Stop the recording with a long press of the button and download log.csv with the Espruino IDE. +* (Recording to browser) Click "Stop" followed by "Save" and store the resulting file on your device. + + +## CSV data format + +The CSV data contains the following columns: + +* Time - Current time (milliseconds since 1970) +* Acc_x,Acc_y,Acc_z - X,Y,Z acceleration in Gs +* HRM_b - BPM figure reported by internal HRM algorithm in Bangle.js +* HRM_c - BPM confidence figure (0..100%) reported by internal HRM algorithm in Bangle.js +* HRM_r - `e.raw` from the `Bangle.on("HRM-raw"` event. This is the value that gets passed to the HRM algorithm. +* HRM_f - `e.filt` from the `Bangle.on("HRM-raw"` event. This is the filtered value that comes from the Bangle's HRM algorithm and which is used for peak detection +* PPG_r - `e.vcPPG` from the `Bangle.on("HRM-raw"` event. This is the PPG value direct from the sensor +* PPG_o - `e.vcPPGoffs` from the `Bangle.on("HRM-raw"` event. This is the PPG offset used to map `e.vcPPG` to `e.raw` so there are no glitches when the exposure values in the sensor change. +* BTHRM - BPM figure from external Bluetooth HRM device (this is our reference BPM) + +## Creator + +[halemmerich](https://github.com/halemmerich) diff --git a/apps/hrmaccevents/custom.html b/apps/hrmaccevents/custom.html index c0098eb12..a5bf9796f 100644 --- a/apps/hrmaccevents/custom.html +++ b/apps/hrmaccevents/custom.html @@ -1,166 +1,286 @@ Bangle.js Accelerometer streaming + - - - - +

+

+ +
+

+

+ + + + + +

diff --git a/apps/imgclock/metadata.json b/apps/imgclock/metadata.json index 799d11acc..94dff5f17 100644 --- a/apps/imgclock/metadata.json +++ b/apps/imgclock/metadata.json @@ -2,18 +2,19 @@ "id": "imgclock", "name": "Image background clock", "shortName": "Image Clock", - "version": "0.08", + "version": "0.10", "description": "A clock with an image as a background", "icon": "app.png", "type": "clock", "tags": "clock", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "custom": "custom.html", + "customConnect": true, "storage": [ {"name":"imgclock.app.js","url":"app.js"}, {"name":"imgclock.img","url":"app-icon.js","evaluate":true}, {"name":"imgclock.face.img"}, {"name":"imgclock.face.json"}, - {"name":"imgclock.face.bg","content":""} + {"name":"imgclock.face.bg","content":"X"} ] } diff --git a/apps/impwclock/ChangeLog b/apps/impwclock/ChangeLog index 6555fcc8f..0af7c99d6 100644 --- a/apps/impwclock/ChangeLog +++ b/apps/impwclock/ChangeLog @@ -3,3 +3,4 @@ 0.03: Move to Bangle.setUI to launcher support 0.04: Tweaks for compatibility with BangleJS2 0.05: Time-word now readable on Bangle.js 2 +0.06: Tell clock widgets to hide. diff --git a/apps/impwclock/clock-impword.js b/apps/impwclock/clock-impword.js index c42dbda44..04421017b 100644 --- a/apps/impwclock/clock-impword.js +++ b/apps/impwclock/clock-impword.js @@ -154,6 +154,9 @@ Bangle.on('lcdPower', function(on) { if (on) drawWordClock(); }); +// Show launcher when button pressed +Bangle.setUI("clock"); + g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); @@ -172,5 +175,4 @@ Bangle.on('touch',e=>{ } }); -// Show launcher when button pressed -Bangle.setUI("clock"); + diff --git a/apps/impwclock/metadata.json b/apps/impwclock/metadata.json index 733dbb957..1b92ea3ae 100644 --- a/apps/impwclock/metadata.json +++ b/apps/impwclock/metadata.json @@ -1,7 +1,7 @@ { "id": "impwclock", "name": "Imprecise Word Clock", - "version": "0.05", + "version": "0.06", "description": "Imprecise word clock for vacations, weekends, and those who never need accurate time.", "icon": "clock-impword.png", "type": "clock", diff --git a/apps/info/ChangeLog b/apps/info/ChangeLog index 07afedd21..093dd4606 100644 --- a/apps/info/ChangeLog +++ b/apps/info/ChangeLog @@ -1 +1,3 @@ -0.01: Release \ No newline at end of file +0.01: Release +0.02: Recfactoring and show weather data +0.03: Show sizes for used, free and trash through storage.getStats \ No newline at end of file diff --git a/apps/info/info.app.js b/apps/info/info.app.js index c61a88045..ade3f3ebb 100644 --- a/apps/info/info.app.js +++ b/apps/info/info.app.js @@ -1,27 +1,99 @@ -var s = require("Storage"); +const storage = require("Storage"); const locale = require('locale'); var ENV = process.env; var W = g.getWidth(), H = g.getHeight(); var screen = 0; -const maxScreen = 2; + + +var screens = [ + { + name: "General", + items: [ + {name: "Steps", fun: () => getSteps()}, + {name: "HRM", fun: () => getBpm()}, + {name: "", fun: () => ""}, + {name: "Temp.", fun: () => getWeatherTemp()}, + {name: "Humidity", fun: () => getWeatherHumidity()}, + {name: "Wind", fun: () => getWeatherWind()}, + ] + }, + { + name: "Hardware", + items: [ + {name: "Battery", fun: () => E.getBattery() + "%"}, + {name: "Charge?", fun: () => Bangle.isCharging() ? "Yes" : "No"}, + {name: "TempInt.", fun: () => locale.temp(parseInt(E.getTemperature()))}, + {name: "Bluetooth", fun: () => NRF.getSecurityStatus().connected ? "Conn" : "NoConn"}, + {name: "GPS", fun: () => Bangle.isGPSOn() ? "On" : "Off"}, + {name: "Compass", fun: () => Bangle.isCompassOn() ? "On" : "Off"}, + ] + }, + { + name: "Software", + items: [ + {name: "Firmw.", fun: () => ENV.VERSION}, + {name: "Git", fun: () => ENV.GIT_COMMIT}, + {name: "Boot.", fun: () => getVersion("boot.info")}, + {name: "Settings.", fun: () => getVersion("setting.info")}, + ] + }, + { + name: "Storage [kB]", + items: [ + {name: "Total", fun: () => storage.getStats().totalBytes>>10}, + {name: "Free", fun: () => storage.getStats().freeBytes>>10}, + {name: "Trash", fun: () => storage.getStats().trashBytes>>10}, + {name: "", fun: () => ""}, + {name: "#File", fun: () => storage.getStats().fileCount}, + {name: "#Trash", fun: () => storage.getStats().trashCount}, + ] + }, +]; + + +function getWeatherTemp(){ + try { + var weather = storage.readJSON('weather.json').weather; + return locale.temp(weather.temp-273.15); + } catch(ex) { } + + return "?"; +} + + +function getWeatherHumidity(){ + try { + var weather = storage.readJSON('weather.json').weather; + return weather.hum = weather.hum + "%"; + } catch(ex) { } + + return "?"; +} + + +function getWeatherWind(){ + try { + var weather = storage.readJSON('weather.json').weather; + var speed = locale.speed(weather.wind).replace("mph", ""); + return Math.round(speed * 1.609344) + "kph"; + } catch(ex) { } + + return "?"; +} + function getVersion(file) { - var j = s.readJSON(file,1); + var j = storage.readJSON(file,1); var v = ("object"==typeof j)?j.version:false; return v?((v?"v"+v:"Unknown")):"NO "; } -function drawData(name, value, y){ - g.drawString(name, 5, y); - g.drawString(value, 100, y); -} - function getSteps(){ try{ return Bangle.getHealthStatus("day").steps; } catch(e) { - return ">= 2v12"; + return ">2v12"; } } @@ -29,53 +101,36 @@ function getBpm(){ try{ return Math.round(Bangle.getHealthStatus("day").bpm) + "bpm"; } catch(e) { - return ">= 2v12"; + return ">2v12"; } } +function drawData(name, value, y){ + g.drawString(name, 10, y); + g.drawString(value, 100, y); +} + function drawInfo() { g.reset().clearRect(Bangle.appRect); var h=18, y = h;//-h; // Header - g.setFont("Vector", h+2).setFontAlign(0,-1); - g.drawString("--==|| INFO ||==--", W/2, 0); + g.drawLine(0,25,W,25); + g.drawLine(0,26,W,26); + + // Info body depending on screen g.setFont("Vector",h).setFontAlign(-1,-1); + screens[screen].items.forEach(function (item, index){ + drawData(item.name, item.fun(), y+=h); + }); - // Dynamic data - if(screen == 0){ - drawData("Steps", getSteps(), y+=h); - drawData("HRM", getBpm(), y+=h); - drawData("Battery", E.getBattery() + "%", y+=h); - drawData("Voltage", E.getAnalogVRef().toFixed(2) + "V", y+=h); - drawData("IntTemp.", locale.temp(parseInt(E.getTemperature())), y+=h); - } - - if(screen == 1){ - drawData("Charging?", Bangle.isCharging() ? "Yes" : "No", y+=h); - drawData("Bluetooth", NRF.getSecurityStatus().connected ? "Conn." : "Disconn.", y+=h); - drawData("GPS", Bangle.isGPSOn() ? "On" : "Off", y+=h); - drawData("Compass", Bangle.isCompassOn() ? "On" : "Off", y+=h); - drawData("HRM", Bangle.isHRMOn() ? "On" : "Off", y+=h); - } - - // Static data - if(screen == 2){ - drawData("Firmw.", ENV.VERSION, y+=h); - drawData("Boot.", getVersion("boot.info"), y+=h); - drawData("Settings", getVersion("setting.info"), y+=h); - drawData("Storage", "", y+=h); - drawData(" Total", ENV.STORAGE>>10, y+=h); - drawData(" Free", require("Storage").getFree()>>10, y+=h); - } - - if(Bangle.isLocked()){ - g.setFont("Vector",h-2).setFontAlign(-1,-1); - g.drawString("Locked", 0, H-h+2); - } - + // Bottom + g.drawLine(0,H-h-3,W,H-h-3); + g.drawLine(0,H-h-2,W,H-h-2); + g.setFont("Vector",h-2).setFontAlign(-1,-1); + g.drawString(screens[screen].name, 2, H-h+2); g.setFont("Vector",h-2).setFontAlign(1,-1); - g.drawString((screen+1) + "/3", W, H-h+2); + g.drawString((screen+1) + "/" + screens.length, W, H-h+2); } drawInfo(); @@ -88,14 +143,15 @@ Bangle.on('touch', function(btn, e){ var isRight = e.x > right; if(isRight){ - screen = (screen + 1) % (maxScreen+1); + screen = (screen + 1) % screens.length; } if(isLeft){ screen -= 1; - screen = screen < 0 ? maxScreen : screen; + screen = screen < 0 ? screens.length-1 : screen; } + Bangle.buzz(40, 0.6); drawInfo(); }); @@ -104,5 +160,4 @@ Bangle.on('lock', function(isLocked) { }); Bangle.loadWidgets(); -for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} -// Bangle.drawWidgets(); \ No newline at end of file +Bangle.drawWidgets(); \ No newline at end of file diff --git a/apps/info/metadata.json b/apps/info/metadata.json index f05f0e134..ac56cd5c3 100644 --- a/apps/info/metadata.json +++ b/apps/info/metadata.json @@ -1,7 +1,7 @@ { "id": "info", "name": "Info", - "version": "0.01", + "version": "0.03", "description": "An application that displays information such as battery level, steps etc.", "icon": "info.png", "type": "app", @@ -11,7 +11,8 @@ "screenshots": [ {"url":"screenshot_1.png"}, {"url":"screenshot_2.png"}, - {"url":"screenshot_3.png"}], + {"url":"screenshot_3.png"}, + {"url":"screenshot_4.png"}], "storage": [ {"name":"info.app.js","url":"info.app.js"}, {"name":"info.img","url":"info.icon.js","evaluate":true} diff --git a/apps/info/screenshot_1.png b/apps/info/screenshot_1.png index 97d42a896..6661c122c 100644 Binary files a/apps/info/screenshot_1.png and b/apps/info/screenshot_1.png differ diff --git a/apps/info/screenshot_2.png b/apps/info/screenshot_2.png index 2d25dd4e6..3d91fcabe 100644 Binary files a/apps/info/screenshot_2.png and b/apps/info/screenshot_2.png differ diff --git a/apps/info/screenshot_3.png b/apps/info/screenshot_3.png index 782e4a195..86bbb67cf 100644 Binary files a/apps/info/screenshot_3.png and b/apps/info/screenshot_3.png differ diff --git a/apps/info/screenshot_4.png b/apps/info/screenshot_4.png new file mode 100644 index 000000000..b8b59b1ef Binary files /dev/null and b/apps/info/screenshot_4.png differ diff --git a/apps/invader/ChangeLog b/apps/invader/ChangeLog index 5560f00bc..6c5a33e59 100644 --- a/apps/invader/ChangeLog +++ b/apps/invader/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.11: Changes... diff --git a/apps/ios/ChangeLog b/apps/ios/ChangeLog index b6a386bcb..87a71a1ed 100644 --- a/apps/ios/ChangeLog +++ b/apps/ios/ChangeLog @@ -7,3 +7,4 @@ 0.07: Added more details from music (instead of Undefined), added more app identifiers 0.08: Added more app identifiers, added 'cannot display' in case a message goes empty because of replacements 0.09: Enable 'ams' on new firmwares (ams/ancs can now be enabled individually) (fix #1365) +0.10: Added more bundleIds diff --git a/apps/ios/boot.js b/apps/ios/boot.js index 5ea7550eb..6b4ad7c5b 100644 --- a/apps/ios/boot.js +++ b/apps/ios/boot.js @@ -63,6 +63,7 @@ E.on('notify',msg=>{ "name" : string, */ var appNames = { + "ch.publisheria.bring": "Bring", "com.apple.facetime": "FaceTime", "com.apple.mobilecal": "Calendar", "com.apple.mobilemail": "Mail", @@ -73,6 +74,9 @@ E.on('notify',msg=>{ "com.apple.podcasts": "Podcasts", "com.apple.reminders": "Reminders", "com.apple.shortcuts": "Shortcuts", + "com.apple.TestFlight": "TestFlight", + "com.apple.ScreenTimeNotifications": "ScreenTime", + "com.apple.wifid.usernotification": "WiFi", "com.atebits.Tweetie2": "Twitter", "com.burbn.instagram" : "Instagram", "com.facebook.Facebook": "Facebook", @@ -99,19 +103,22 @@ E.on('notify',msg=>{ "com.toyopagroup.picaboo": "Snapchat", "com.ubercab.UberClient": "Uber", "com.ubercab.UberEats": "UberEats", + "com.unitedinternet.mmc.mobile.gmx.iosmailer": "GMX", + "com.valvesoftware.Steam": "Steam", "com.vilcsak.bitcoin2": "Coinbase", "com.wordfeud.free": "WordFeud", + "com.yourcompany.PPClient": "PayPal", "com.zhiliaoapp.musically": "TikTok", + "de.no26.Number26": "N26", "io.robbie.HomeAssistant": "Home Assistant", + "net.superblock.Pushover": "Pushover", "net.weks.prowl": "Prowl", "net.whatsapp.WhatsApp": "WhatsApp", - "net.superblock.Pushover": "Pushover", "nl.ah.Appie": "Albert Heijn", "nl.postnl.TrackNTrace": "PostNL", "org.whispersystems.signal": "Signal", "ph.telegra.Telegraph": "Telegram", "tv.twitch": "Twitch", - // could also use NRF.ancsGetAppInfo(msg.appId) here }; var unicodeRemap = { diff --git a/apps/ios/metadata.json b/apps/ios/metadata.json index eb75a6dbc..63f02262f 100644 --- a/apps/ios/metadata.json +++ b/apps/ios/metadata.json @@ -1,7 +1,7 @@ { "id": "ios", "name": "iOS Integration", - "version": "0.09", + "version": "0.10", "description": "Display notifications/music/etc from iOS devices", "icon": "app.png", "tags": "tool,system,ios,apple,messages,notifications", diff --git a/apps/isoclock/ChangeLog b/apps/isoclock/ChangeLog index 809091ce4..7b57ecfa9 100644 --- a/apps/isoclock/ChangeLog +++ b/apps/isoclock/ChangeLog @@ -1,2 +1,3 @@ 0.01: Created app based on digiclock with some small tweaks. 0.02: Swap to Bangle.setUI for launcher/buttons +0.03: Tell clock widgets to hide. diff --git a/apps/isoclock/checkout b/apps/isoclock/checkout new file mode 100644 index 000000000..e69de29bb diff --git a/apps/isoclock/isoclock.js b/apps/isoclock/isoclock.js index 59f28e66e..7526660b9 100644 --- a/apps/isoclock/isoclock.js +++ b/apps/isoclock/isoclock.js @@ -89,8 +89,8 @@ Bangle.on('lcdPower',on=>{ } }); -Bangle.loadWidgets(); -Bangle.drawWidgets(); - // Show launcher when button pressed Bangle.setUI("clock"); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/isoclock/metadata.json b/apps/isoclock/metadata.json index 313153dde..488afcb41 100644 --- a/apps/isoclock/metadata.json +++ b/apps/isoclock/metadata.json @@ -2,7 +2,7 @@ "id": "isoclock", "name": "ISO Compliant Clock Face", "shortName": "ISO Clock", - "version": "0.02", + "version": "0.03", "description": "Tweaked fork of digiclock for ISO date and time", "icon": "isoclock.png", "type": "clock", diff --git a/apps/kanawatch/ChangeLog b/apps/kanawatch/ChangeLog new file mode 100644 index 000000000..f2c991fd0 --- /dev/null +++ b/apps/kanawatch/ChangeLog @@ -0,0 +1,5 @@ +0.01: First release +0.02: Improve battery life, sprite resolution, fix launcher issue and unaligned text bug +0.03: Reduce code size, refresh once a minute and faster refresh +0.04: Show a random kana every minute to improve learning +0.05: Tell clock widgets to hide. diff --git a/apps/kanawatch/README.md b/apps/kanawatch/README.md new file mode 100644 index 000000000..e213949dc --- /dev/null +++ b/apps/kanawatch/README.md @@ -0,0 +1,19 @@ +# kanawatch + +A simple watchface design with hiragana and katakana +cards for learning. + +## Changelog + +0.01: First release +0.02: Improve battery life, sprite resolution, fix launcher issue and unaligned text bug +0.03: Reduce code size, refresh once a minute and faster refresh +0.04: Show a random kana every minute to improve learning + +## Author + +Written by pancake in 2022, powered by insomnia + +## Screenshots + +![hiragana and katakana](screenshot.png) diff --git a/apps/kanawatch/app-icon.js b/apps/kanawatch/app-icon.js new file mode 100644 index 000000000..a17f21d56 --- /dev/null +++ b/apps/kanawatch/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxEBAH4A/AEn/AAgrrAA4ttGL4hF9fGsU1pMNmti43rGLwcD/3MxEAud413p6uuvFzgGI5n+GDQaD6F8i2p8KKH8Opi186AwYC4Xv08A0fnXhfn0cA0/vGCoVC7+ItHNE4vQ+oxH5toxHfGCYTC8t/xaKH5VY+CUIxd/8owSCIPxymB8wkH8UA2yTI82Byn4F6AXCwNH7YjI7UATAwAD7dHHgYuP4sAc5XLgHrBpXAjngGBwOCrmJ/whJ1syBgXw7v6Bov+xObF5rWDgHWKJWEt3l4mQjkAoHzBwvWgHhGBgMC1WIDQuw1/L427z8ygAABp+R3vqH4+I1QvO/1R5YZF+t1FINWuMAy/W+BuKZ4NRT4ReL7kc+waG/fy/n/9kA74tLAAP2jncAgPBF5W5yIeLZgPxEgf3CJOR3JTCF5WU3wvL6sA/YFC7e0CJO+ygDB94vKt3aF5fHoQDB+/dzdL4nb+YRG7VuAYP5F5VF9ovL3dP3t8pOKgFw0+CjmT84RE9tFAYP+F6/uwMm1Hd/vCk3oQYWGl3XF6aPK/e0oVwrohCmu9Bof5sVF+yPSd5PtuWA9m7o///uCwH9B4m9gHKd6W5yIuG9NV3v+//Gjn/2VA9wQF6UA2AFCyO5AYPcF5Xcjh1DAAPnp/SEYnJiy2EAAXTgGvAgP2jncAgPBF44wC/1R5a7EsZHCAAPegEA3afH4sA4wEB5dROgP/FxBgD1WIPgky/QGD5MAxYfCAAuGjnvAgNHuBLCF5nhgHWAoWvuwEC9mWLwN+Fw6aB1wEB60A44EB6ovJGAebxJSC1lF4/AyMNoXBzUN/IuF5kmyP8VgOJrgKCFxUB8QOB8Ec4CnCLIMAmWr+v/9Vy/otD+WWmu7BAXAjnFF5xgD21H7f//u+0vN/CKH9Ojse4+QHC7dH2wuPgPVCAP4yk98wqHAAf734OF82ByhCDF5pgD/9/xfhGBYAF8OLv/lFyIABU4XfxFo5ouP5toxHfFyZhE9+ngGj84tL8+jgGn94uVSQvQvkW1KUI8Opi186AIDFygwF/3MxEAuew6fp9PT2FzgGI5n+FzQwFAAPr42fu9JpN3z/G9YPFFzAxIABYtbGKItfGZYrlAH4A+A")) diff --git a/apps/kanawatch/app.js b/apps/kanawatch/app.js new file mode 100644 index 000000000..088dab785 --- /dev/null +++ b/apps/kanawatch/app.js @@ -0,0 +1,273 @@ +const stripe_width = 32; +const stripe_pos = 40; +const stripe2_pos = 110; +const h = g.getHeight(); +const w = g.getWidth(); + +/// ///////////////////////////////////////// +const katakana = {}; +const hiragana = {}; +function image(x,y,b) { + return { + bpp:1, width:x,height:y, + buffer:require('heatshrink').decompress(atob(b)) + }; +} +katakana['A'] = image(56, 51, "v//AAfwAon//AGF/wGT/gGM/A3F/BDEn/wJQoGCj4RB//gAxUB//AAwcDAwsH/+AAwcP/4tCAwMf/wGEn/8Awl/JYYGBKQkf/I9DAwJgBGwQGDGwRlBAwJsE+42DAwPzGwYGB+J7EQIIvDQIIFEAw5DEAwRDDgCIEAxCPBKIcAR4IhER4hnCLAg9BLAgoBAwgoBcQiCBMwj0BHogGBHogGBfoooEQQREFEIgGBAokAhAGFA="); +katakana['I'] = image(54, 55, "AAkEAws+AokB/wGEg//Awk//gTE//gAwcPCYt/CYkDCYsfCYv//A0F4A0ECYg0BCYggBCYn/KwhBBGgl/EAgtBEAgMBEAZOBEAgMBEAYZB/+ABggTDBgQnDAoIaDJoIaDFgIABDQQFC74aBBgX8v4aBEwWBDQQgB/EHDQQ6BwEfGoX/+AJBDQMDWAKMBDQMPAQIaDiBFCPAgaDU4hrDDQiuDDX4acSAIaCA="); +katakana['U'] = image(52, 55, "AAMP/gGE//ABlH/AAnvAon+Bk5EDv/vIgcHBkHPBgZwBBgn/Bi8B/+PBgcf/AMFw/wBgYEDgED/6qEv4MEKYK3F8AFDj7EED4LREv/4CQn/wASEFginBDAgfEDAIfDn67BC4YABH4QXBCQcHZoQkEEoYMCHAYlBFYZEBLwk/MgpQEAAw"); +katakana['E'] = image(58, 45, "h//AAfwgYGE/0AAwn/wE/AwngDgv4DjhDCv/wJQkf/gGEg//AwkB//AA4gc/Dn4cjbAv/34GF94GF/YGF/wcjwA="); +katakana['O'] = image(57, 54, "AAcf+AGEh/8AwkH/wGEgf/AwkB/+AA4n/4AGEv/gAwk/GIsf/A4P/4AE+F/Awn4n4GE/kfAwn+h4cFg4GFwYGF4IGFKwYFBMQpxFAwJxEAwJxEAwJxEAwJxEK4JxEAwKqEMoQGE/o4En/8HAl//iqEAwKqEv/+VQgNBVQgNBcYgNBcYhLBcYhSCHAQKBAwI4CAwY4CD4IGBHASxBAYI4CAwY4CYwIGBHAQGBD4I4CBIJfCHASmDHAV/PYQ4Cj5QCHAUPLwQ4CgQGCOIgABOIgABHAIGEAAY="); +katakana['KA'] = image(54, 54, "AAMP/AGEv/gAocB/+AAwcH/wTEj4arg//AAf+j4GE/F/AwnhAon/w4aZHAMP/hTEn/wKYn/4BTDgf/KYgQCDQYQCBIQQDBIQQCBIc/DQouCDQQuCEghJBEhITBH4RTBLoRTEBIJTGCAUPNwoTCDQQWBDoIuCj4TCJIX/CYQ/BZQInBH4U//0HwBTBGgPwXAXwh4PBXAXAv4PCZIIgBEYTJBn5SBDQXABAIzBCYJcCDQXwgbOCAwIDBQgI4CgEOJwIADkAGFA"); +katakana['KI'] = image(58, 55, "AAU+Awv/4AGEn/wAwkP/gGEgf/Dkk/CAc//4ABwAGBj4GC8ATBAAf4h4GE/woBAAmAAwvgFAYcIwAcD/BFDFARFD/kBIoYACv5FBAAcfRL94DgkfHgf/95EBD4RgDD4MHLwf8AogAd+CPFGwiJCS4XHJgSGB8CJEkCJJUwYABg5pDD4amTNwKmXYbgcDLoY="); +katakana['KU'] = image(55, 55, "AAMHwAGEh/8Awkf/AGEv/wAwn/4AFDgf/EQkH/whF/4ACAwM/AoQQCBgY5BgIGDHIMHAwY5Bh4GD8AhEIAQFDIAIhBBIJACEIJpEj45CNIV/NgRpBDQIrBEoPgDQJlBEoQaDEoV/RwUP/wPBQ4Uf/gPBQ4QsBKAKSD8BvCSQXDDQYYBNYIaCGYIqBDQU//kPXoYYBj5QCEIPgj60DKoMcWga7FKoYABKogaDbojPBbojMDGob/ECYJBCbgYaDE4IaEPoIaDEAI1EbYQZECYgtBCZQGCLol/KwxxEAwJqEgIMFgIZEgA="); +katakana['KE'] = image(60, 54, "AAMcAwsD/4HFn/wBxl/8AGEg/+BxkP/gOF//ABxcB/+AA4kf/BCGAAZOBv4HEIQIOGAwgOBh4OFGYIOFn4OFEgoOBAwvgh52BKgYDBOwJUDv5nBBwY6BAYM/BwIKBJgJjBBQSbCWoQVBRgK1D/4oDBwJJBWos/WIS1CgIVCJoRGBWowCCj61HYgpRCdIjEGLgTLEIwTLEfAv/GYqtBEghyBGYjoCAwwkDAwQVEYwYjEHQt/CopeBQgQOEIIgOBPgxeFgZ7FA"); +katakana['KO'] = image(49, 46, "v//AAYFF34FE74FE94FE+4FE/IFE/gFE/w0Dgf/AocB/+AAwf/4BHE8AFDn/wAocf/AFDh/8AocHGH4w6YZf7Aon9YYoFEejBhEAAIA="); +katakana['SA'] = image(58, 53, "AAcD/wDBg4DC//AgEB/+AgE/+AKBv/ggEP/gGBj/4DgP/DnU//4A34CQ+DAIcEDAIcDDAQDDDAYDCDAYDD/4cDIgJADAAUfIAQACh4jCAAUHD4QACJwIfBAAQtBEYgGBI4QUDFQkP/4qEVYQvEAAIxCEIK5CBwV/AwsfAwocCAwYcCJogcBNIp3F"); +katakana['SI'] = image(56, 52, "gFwAwt+Awv/8AGF/gFDgP//4GGCocDAwIVDBoX/wAHCn4VFg4GB4AxEAwsfAworBEQYABv4GFj4DCjgrCBQYRFn/4JQfAIgIGD+F/JQcD/gGBMARQCOwcH/wNBCoUP/0PAwIrBj/8OwQGBn4fBGIIGCAQIlB+BcBAQKvDBIQRB8AfBIQUH4AXBP4RXBGgJmERoJsFAwv//yaFbYghBQIYaCeAi9FPQTZGdxKFCFASECFAZPBEIgNCJQaZEAwhDDAwRJDTAYGEQAiQBPIgAGA"); +katakana['SU'] = image(60, 51, "gH/AAYGBh4GD/AOG4AOF/gONDo+ABxAACgY7CAAd/+AGEg4OG//gAwkP/wGEgJCCAAcfKIQzEIQIzEIQozOj4zFEgIzFn4kHGYv/M4okIGYt/IQqXBFghuBHYs/bAY6DCwrJECod/HgYVB8ZLEcoMfLQYECCwYVB+BTBCwT7CCwYrBAYIKCCoQDC8BXBEIQSBNoQVBBYP4EAIoCOQPHCoYTB/xdBIwQ8B+6SET4N/dYn/4aCFFgKRFgC+EgPghivEAoI"); +katakana['SE'] = image(57, 53, "gEH/wGEgf/AwkB/+AA4n/4AGEv/gAwk/+AGEj/4AwkP/g4JjA4EBQQ4D/4DD4E/AwIuBv/vAoP/FwILCAAIuBv4GEBgn//wFEAwITEh//CgfwAwMfCIRGB/4BB/5xBAgJTBIQQGBwP/75CBAwOAD4JCBAwRmDDIKYBOIQGDOIQGDOIQbBAwSqBAwiqBAwiqBDYg4Cv4GCHAUfAwQ4Cg4GCHAUBbwbjnHAgADcYYADUYQxEEYq6CVwbDBdQi6CZQYqBAAZcCAwY1BEYi5DAAQ8CegfgA="); +katakana['SO'] = image(52, 52, "gGAAol8AYUD/Ef4AGCn/3/wFCg/+v/wAwV/8//Bgk//AMD8f/FoQMBj/8Bgfg//gBgcPFoYMBFocP/kHFof/4AtDBgMDFoYMBFoYMBgIIBgADBwAtDj4dBHQQMCFoYqCHQQqCFoc/BIIPCCwQtDKYIpBB4IwDIAQwCh45CBIVAFgSmDFIaaDOIYfCVgYfBRYYfCTASTCUoY1BQgZPCD4l/D4kfH4g4BH4YYBH4gFBGQd//4yDBYIyDn4SEJQIlEBgRXEHAg+BFYZRGZYQADBYgAG"); +katakana['TA'] = image(55, 56, "AAMHwAGEh/8Awkf/AGEv/gAwn/4AFDgf/EQkH/4oF/4ACAwM/AoX+FAQGCHIMBCYY5BEIIAC+AhFIAIhDHIQFDF4IhBJQMHF4JDDNIUfHIRpCv5sCn/wDQJsCDwIaBEIIKBwEf/9gOAQaB/gbBFAIPB+YsC/AaB54RBFAIaBAIOAEoJvBOgPh/+DNAJWB+//DQPBQIZyBM4f4LQSQC8EPKAIpBFAMPPgKKCgEcYIZwBiAGDbohwEZ4bdEFILxFf4ghBXwLjEDQhLBCYoaEE4IaDdIQaDBgLBCDIRQENYYTIewRkEAwJCFHYicBOIkAEAhDBS4IAJ"); +katakana['TI'] = image(57, 54, "AAkGAwsfwAGE//gAocP//wBgn//gEBgIFBAAIeBAof/wAYBAwkHAof+gEDAwf4E4YAB4AGBv4TDAAM/AwoxDKQhABLQwiCAAV/MIglBMIglBHwRwDNARbF//3Awv7Awv9Awv+Awv/MQQAD34GF74GFKAUHOIYABSAJxGaYp4Uv54FP40/P4oGHQwQGKKgt/AwrUEMIQGEVYIGLg4bMFII+Fv5TGNAsPQgsHTIoAG"); +katakana['TU'] = image(54, 53, "AAMBwAGEj4FEgf8AYPwgFgn/4BIP/g+Av/ggEP/n/gP/4EAv/v/wQBFQP/z/4CAMAg/+DAMfEIICBDAN/FgN/8YYBBAIaBw4hDDQIVBAYMAn/wDAIhCCwIhDCwIBBwAIBHAIYBEIQYDBAIuBwAjBFQghCJgQhEAIIhDEYQPBh5HBM4IhDQQQhCwYeBCwMBCoSPB/0CIQQhBAQKWDvytBCYTBDv5tBZYYTCAAQTCAAYTFHAITEj4TF/4TEh4TFv4TEg//JgIMDMYIMEO4ImD/53BAAM/AwIsEEAgFBEAZNBIIgTCFocfJwo6BPgpHEgZAEgEOAogAGA=="); +katakana['TE'] = image(57, 51, "h//AAfwg4GE/kDAwn+gIGE/8AAwuAv4GE4E/Awngj4GFNWJNF/gGF/5UF/+/AwvfAwvvAwv3Awv7GJn8IQV/4BJEv59Fn/wAwkf/DJFEAYABg/+AwjJBAxbQBwAGFH4gGBH4gGIIwgGNG4IGEg//LYjyBAwiyBAxc/EQoGGFIJTLdYJvEgF+fIsYAwo="); +katakana['TO'] = image(42, 54, "//AAgU/+AECh/8AgUD/4U/CgYPDn//wAUC/4VCCgIlDAgIKCCgIKCCgP//wUD//gCgQKCn/zBQQ+BDYP8CgMBEAQBBj4KBKYIKC54yBBQP7KYIKCG4QKB35YBBQIUCGQPjNAUD+BXDnB9Dgy8/CicAA="); +katakana['MA'] = image(57, 50, "/4AE/l/A4s/AwvfAwoAN/YGF/oxGHokf/wGLh4GN/4GSg4GChgGDwARBAw3gAwv4Awo7BAwn/4ACBAwIKB+AGDgJtBAwcAUgOPAwYLB94GDgaFCAwTBDAwcfAwoyBAwgyBAwgyCAwgcBAwgyBNgL0ENgIADn6oHDijhFW4wcB4AGDKwPwBwl/fwzUJDgZOFgAGGngGFhADCA"); +katakana['MI'] = image(52, 53, "gPwAwkf/wFDgf///gAwU/AwIVCBgX//AME//8gEHAoQGCBgYGCv4GDFIMPBggoE4A2CCoIuCAweAAwc/BghYBMwswNw0PNwkBGAIbEG4gMCOoYMCOoQMDAwRnE4BYDKYQTEKYRuCKYY8GgCjDAAV+LAtgcTMDbYhTCHobICBwbBDBghZDZwmAZoYGCAogGBCYgiBEIidCBwQ2DS4QMCVYT2CSAb2DBoLpFn72EdJAA=="); +katakana['MU'] = image(59, 54, "AAMDwAHFv/AAwkf/gVF/4VG8AGEh4VHFgoVPFdZBdRogVBgP4CokBFogVBn/wTIkHEwYrCv4ODCoMP/wVDFIP/JYQVCBwgVBGYLICCoTIDCoQCBBwQhCn5RCCoR/DNoZCDDIRRDCoQODg4+CIQYvGCoZCCCoZRDAQV//4SBRAM//4ABwEfAgQAB/ARBAAkPAwvxAwv+Dgv/8YGF/gkD/xCB543DH4P5AoaBBewsAvgGFhgGFAAQ="); +katakana['ME'] = image(55, 54, "AAcB8AGEgf/AwkP/wGEj/8Awk/+AGEv4iF//AFAuAAwcHFAsPFA34AYNwFAQvBgICCFAUHCAIoDDwQoDn4DBKIf/MYIoCDwIGB/5RBAwWDKIYGB456Dv//75RDAwP/JQQmBAwJ6Dj4GBOYYGCOYcP/5zEg//OYgGNDYw3BAwgvBAwaABAwgaBOARZC/wGDOoP8MQI1D+AGDFwPAAwJaBDAQNCJIc/AQJsBTYL3COQc/4ATBXoYdCSgU8J4SNCmCNCNQqoDAwQuBAwgFDFAITEAwK1DAAKZEAAIMFAA4="); +katakana['MO'] = image(55, 49, "j//AAfAv4GFAon/wIGFgYFE/0HAwn8h4GE/AvF8A4Bv4DCAAQzBAocB/+AAwYxBCYkH/wGEh/8MIv4Awk/+AGEGyJfFAFP9AwpOBNuikeAwxfEHoLpFNoZACAwZABIgIACJYYABIAYGCIAYwCHIoABA="); +katakana['NA'] = image(57, 55, "AAV/8AGEn/wAwkf/AGEh/8AwkH/wGEgf/AwkB/+AA4n/4A4rGoIAE/IGF/wGF/9/Awu/AwvfAwvvAwv3AwpQCOOqqEWLV/H4pGGn5GFAw0fJosfJooGGn4GGKgq6BLQoGEg4GFh4GFPoIpEDYIwFv5MFLQ4GFg6EFgaZFAAw"); +katakana['NI'] = image(56, 43, "h//AAf4A25+/AH4AuWggA5A="); +katakana['NU'] = image(55, 51, "g//AAcAh4GFj4FD/0An4GD/kAv4GD/EADQnwgIGE8EDAwnAAwuAIIgvBAAcPF4IADn4vBAAd/8AGEFAIDBAQIsBFAMDCAIoDh4eBj4oCj4GBFAd/CIJRBgBZCAQIlD/+HQIIGD54oCNwZKDPQZPDOYRdDOYqmBOYi0BOYjCBBogGGYQSAEAwimDGATdDAwQTBH4JFBLIP8AwYTB+AqBAwITB4AGBE4bADBIJyBUIJ6CVgXgJAQzBg+BAoJkCgxcBCYRIEPArlEH4YGDO4ibBeQs+AokAsAGF"); +katakana['NE'] = image(61, 55, "AAX/4AGEg/+Bws/+AGEgP/wAHEh/8Cwt/8AGEgf/Bwsf/AMEAAYnBj4GDHwQOEDAMHA4hVBn4WFJIIADHwMPA4hgCAwZkFCQKCGBwpHBPQwOFFAJyGBwt/BwozBBwpwDGYiYEEgP+iAkF4IPDCoP8j7WCUAXhbwYVB/4RBU4n4QISfD54vBS4f+FASPD+AEB+AFB/IjBFIPnA4LzCGAfAeYIjBGAP4eYQCBwZuBeYUH/EfIwJRCAoIDBg6ACnCmDR4oqBDIKfEHgKuFS4g5CBwo8CWwqOCAAQ8DcYg8Vn48FAAo="); +katakana['NO'] = image(47, 52, "AAcHAokP/gFDj/4Aod/+AFD//gAgUB//AAoUD/4oE/woJn4oLEQYoBwAoIh4oEj4oFJZ8HERU/EQhFEDgIiDH4JFDh4iEH4t/NAYcFHII/Dj4cEv4/DCwIcDCwIcDCwI5DCwhEBHIYQBKwf/GYYhBCwc/FoYKBFoYEBFoQKCE4RrBE4YFCHwQyBHAYnBJ4YFBcBN/AgcAPgYABA="); +katakana['HA'] = image(62, 52, "AAP/wEH/gGCgf/gE/+AHCh4MB//AA4QMBCIQeD4ARCDwv4Dwt/8AeEgI4BDwkH/weFj4eEAgIeF8AeEAgQeEAgQeEAgQeEAgQeGMggeCMggeCQYiACQYYbCDwgbCIogbCIoZZDIoYTCMggTCEwn/CYJFDBYZFDBYYmDv4LBEwYDDg4aCh5JCDQYiDaIQWBNAQ5CMAYLDcgYmCCwgqCGIYTBFwL7EJIIWEAgPgh4WDNAPACwgMBCwiHB/wWEFwV/CwZVB/YWEDgPHXgYuBDwLbDKQPwh60CGwWAngGDgAFBkAHEsAFEAAQA=="); +katakana['HI'] = image(47, 51, "//AAgUB/+AAoUD/4QDg/+AocP/gFDj/4Aoc/+AFDv/gFw8BwIuDj+DFwf/FwcP/4uD///FwQKB/wuBJwIFBFwM/AoP8//PAgP/+IDCAAJdBAAXwg4FDEoQKCIIIgCLoQFBKYV//5qDB4aMuF1YFDFwIRDUIQAC+YFE8YFE44FEw4FEUgn+Aon8WwhKBXggA="); +katakana['HU'] = image(49, 50, "/4AEv4FE34FE74FE94FE+4FE/YFE/oFE/w0Dg//AocD/+AAoUB//AI4ngAod/+AFDn4FEj/4Aon8AocPAokHHgg2BHhYFDHgJCLJBZCEAopIFAoxIEAoxOEApc/AojSBbwplEAoZxBAocPAojICBQhBCGYIFDBYRZCa4P/NYQuCPoYFBSoZGFZYsPAgYABA="); +katakana['HE'] = image(61, 43, "AAMH8AHF/4HFh//wAOF/wOG/AHEv4eFg//DwoOBDwgOCDwk//YeEgf/x4eEn/8n4eDgP/4AeEj/8DAIeCBwPgLgkfDYIeECYQeDh4LBIwIeC//wDIIeCBYJdCDwV/BwIwBDwIOBCQYeBn4pCDwRIBIAQeCMIJPD/AOB4CED4BhBMwf/MISbD/kHPovwj4ODDwV/UYhYBKQJ2DRoIGDHQINEcARCCWYgGEDwIOFgb+FDwL2EDwQGFIQoeCBw0YA40AA=="); +katakana['HO'] = image(61, 54, "AAV/8AGEgf/Bwsf/AHF//AAwkH/wOFn/wAwkB/+AA4kP/g8Rg//AAngv4HFCYIAE/EfA4vAAwv+Eo3wn4HFwAGFJwZ5UgfAPIJzDn/x/+PEgR/BAoJzDP4N/8JzD//D/6KDFYI8BCwYrCCAItBPQOH/wWDCgIQBCwf/4P/wIWCCQIBDWgYBCZ4KJBE4LPDEYInBh5sBBgKLBNgQ0CJoIWB4ACCBgIiBBwP8EYU/TQLXBHQQECFAI8BCwIqB8DzCDYMPAgQbCMoI3BF4IRB44OBWwQUBv4TBJIV//InBHgQCBw4OBHgUH/EfNgKOCj0A3BsCQwNgeaSdCABA="); +katakana['N'] = image(54, 50, "ggGFngFEgP+AwkPAws/AwkB/4GEh4GFn4Gaj///gNF/AGF4BEJAwITBgOAAwQTBh4GCnwJCCgVwLgRwMHAgTBHAgTGv4TEgYTFMIITEMAsHMBY0B+ClFCYiPFEAITEv//OIQMCTg3gBgggEDIIgDGYIgDMIJVDDAIABIIILCFoYYCJwZ0BHQgsBBgZnBBggnCKgYhBMIi3FgAFFgAA=="); +katakana['WA'] = image(51, 50, "/4Ay4A3E/AFCh4GBAoUBAoPgAwU///8AoUHBgOAD4nwAoUf//+AoUDGRYSBGQYSCGQd/94yDh/9GQZFB34yDn/zGQcPAgYSCG4YSBC4YSNv4SKJYJwDLwISEn5QDS4QSDDAJjDDAJ2DGIJ2DUYQ+DQYKcFFYYXBDASOCGIQFDGIQRCDwTaCG4YFBEgbHHN4hiFg6HEA="); +katakana['WO'] = image(50, 52, "/4AE34FE94FE/YFE/wYYGocB/+AAwd/8AFDn/4AocP/gFDgf/KovADAnwDB43B45EE+IFE/F/KAkfBgmHAonhAonwDAn8h4MEN5X/N4l/N4k/KwkfRwgoBDwcHOohoBOoYFBEgY2BEgYFBEgYFBJIYXBFQYpBFQZ3CAoIWCKoQQCGwQLDHgR8CAoQdCAoQvCOYYFFn5gENgKREbYgAGA"); +katakana['RA'] = image(51, 50, "n//AAcHAongAon8j4GEwYFE+F/Aof+h4ME4IFE/BYr+4FE/wFE//fAon7BgpYE//vAon9CQo3Ev/gAocP/gFDgP/wASX+ASJgYSFXwJ2ECQivBDAoSEWIs//wFDbYIrDAoI+DAoIYDQ4IYCFIIABDALlDGIJhBewS/EJQQYCG4YkED4QFDD4JJF4AFDA"); +katakana['RI'] = image(43, 53, "AAf/7/4AgMf/f/AgMD/9/8AFBv/v/gEBh/9/+AgEB/+/+AKBn/3/wEBg/+//AFX4q3v4qDh/8FQQPBz4PDAYQvBEYQvCEYI/CGYRPBB4cfIYQpBB4cH/5TCDwJjD/4kCn4EBCgN/AgIUBDoP/FIJHBAAIyCDIYjBIYYaBQ4QaBJoZHDAAoA="); +katakana['RU'] = image(61, 53, "AAUH/wHFn/wAgUB/+B/+AA4UP/gBBCgd/8ABBAwUD/4BBBwcf/ABBA4f/4ABBHQg8FHQI8/HksYHgwYBHgkPF4I8EvwlCHwOAg4gBEYI8CCIQjBHgITBCIP+HgU/CwIRBDAIgB4AMCAgMfEAIMBDAIOCBgQYCIwQMCPYJTBAQI8BBwUHEoN/8P/IYN/+AvBj4LBBwOAj/7BwZGB/4ABBwXAAQIODM4QOFHgIOC/4OBh4OCAYJGBv4OCn4OBHgJKBAYJkBIQISBaIYhCCwIOBSoTqBJQISBeYUHd4U+bYUwcAYAKA"); +katakana['RE'] = image(51, 51, "//AAocf/AFDgf/CQl/8AFDh/8AocB/+AAwc/+AFDg/+GX4ECgwyEgPgGQk+GQkP+IyDC4IyE//3GQc//gyDh//GQYYB8YyD//4GQc//wyDDAOBGQUH//gGQRvB/BlD/4DBGQU/CwIyCj4YBMoQkBBIIyBBAIYBGQIkBDAIDBGgIiD+AFBGoIyBv4eCGQIABJwQvBAAJnDEgTLCEgY8CIYLLDEgZVCAoZuBb4iaBfAj+EgE4AokAA"); +katakana['RO'] = image(50, 47, "/4AEn4FE94FE/YFE/wYF34YS4A1BgIYB+A8Cv/v/gFCj4YBAoUHDH4Y/DEbglDBQ8CAAYA=="); +katakana['YU'] = image(59, 46, "gP/AAX+A4M/A4fggEHAwf8BwIGD/4GBj4VFgYVGv4HDwEAh4GD+A+Eg46CAAf/4AGEj/4Coo6CCqJFBCot/KAIADh5QCQAhQBCrM/Myk/M3JQGh5QFMyIRBAH6NB"); +katakana['YO'] = image(50, 49, "v//AAefAonnAon5Aon+DDA1DgP/wA8E8AFDj/4AocHDFZjfDCJjxDD5WE/+/AonvAon7PgoYX/g3DAAQ"); +hiragana['A'] = image(52, 50, "gEB/wGEn/AAocD/gMcg//AAfgv4FD/wMYFIRNa54HDgYyCBgYsEBgX/+AGBHQYpBCQQaCh4JBJQPwgIdBBAP/wASB4H/j/8MIP8j5fBBIP/4P8gf+j/7/hVBj/jA4PH/C/Bn4RBv8Aj/3/Ef55FB/9/wI+D+/wj40BHwIWBL4QJB+BFBwAmB/4MBD4M/94MBD4JAB/4cBNYN/BgM//AsB/n/z4bBQgOHX4QVB/B3B/CQCAQTSC8BFCB4Q4CB4UAgIIBRQOAXojREn/gaIgAC"); +hiragana['I'] = image(58, 50, "v/gAgUggEf/AGCnkAg/+AwU/gEB/+AAwQZBDgcP/gcECQIcFCQIJCCol/4AGBgYLBj/wCokHCAIABFAIQCCon/DgQECn4cDCoItCAAI+BDggVCLoZeB+BgCCocPPQZUBwZdDJAQcEGAIcEGAIcEGQPDDghIBDggyBDggyBx4cBjxIC8aaCCAIyBLAMDM4IyBSARnC//HUIk/+IyBCASdBLAJKCGQOf/kDJQV/GQRKCJ4XgEYRPC/CoCDgOHNwl/8P/84jCDgM//5HCDgMHAwIjBgP8DwIsBQgYVBSQgVBaYZnCTIgtBbQhDCUAYkCfwYOCGIgAHA"); +hiragana['U'] = image(46, 50, "h//Aoc////8AFBAgIABgEDAofACwIAB/wWD//4CwgdBCIeAFQUfCwIADCwIAMj//+AEBv4tDAgQLBHAYFBAgf/8YFE54FECwRTB/wkCAoP7IAd/OgR2CKwcBQ4kH/hMEJYQcC4AWIh4WEn4tJg6EEj6EEVgIQDE4l/CAbABCAZqBBQgQDBQIQCXwIyCYYTIFeIhlCBQjxCLIQWBMgbdFvzYJ"); +hiragana['E'] = image(55, 50, "gF//4GE/4AB+AFBgIGC/+AgEDAwYNBg4FC/wGBh4GC/gGF/ArFFIQAD4BRVn42FLAIGEJQYGBLAhEBLAhEBLAf/8ArDBIIyEj5fCRYZYEEgJYEN4JNFDQouFDQKcBFwYGFMIIGDLQRJFAwgaBOYQuC8Y2DFwODAwcP/0HXAc//EPcQnAj5LCPAU/MwR4Cv5ECPAQ9CLoUBd4auE/guBVwf5PARaC+5qCAwXnJwSXB//HI4QGCw5ACAwUHNIn+gj/HAAg"); +hiragana['O'] = image(54, 50, "gEB/0AggGCg/4gE8AwUf8EA/gGCv+AB4QaDv/wDQn/CwIaCgP/4AaDgf/wAaCgPn/4PBAAXv/0HAwef/kfAoX+n/4v4GCAgPxCYfg/4jBAAWBGwQ1BgEDJoJQCJoJRBLYcPCAJrCgEcKAaGEHgSGDF4QPCJYYxCHoYMBn5YDBgoGBDIP8FQKiBDwabBFoIzCv/gEAJQCMwWfKAIbBh58BDQMH/l/4IaCh/xTgIaCn/P/BrD/8/4CGD/i3BDQfz/gaDv/P+AaCCAIaEHQQaDv/hGoV4h//g4VB8JnBa4ePZYRkBBwKNCbwPwCYR/C44CB4BtBfgSaD8ACBYQQWBAAYA=="); +hiragana['KA'] = image(55, 49, "gEH/AGEh/wAwkf8AGEn/AAwl/wEAhgGC/4CBngCBgP+AQP8AwMDAYIyDAYUPAwQ2CAwY2Cj/4gP/AAP4j/wgYGC/gGBg4GC/0/8EPAwsfCgd/4E/Awt/FIf/LgJmBE4IGCMwMf8JjBHwIPB4IDBgZmBv+DAYMHMwP/BQRfBOwIKCL4J2BOIQvBAgJxCGQIEBHAKPCCwIYDCwQBBQoRGBviIDIQJRC4AdCXAYdCKIcHboQ/CboY4BboghBboZKCFAYhBjAoDh/8nzME+CfBF4V/RgP/EgKVBwYGBFAMH/zIBFAQeBAwIoDboRRD4DrBJQUHAQJsDAAwA="); +hiragana['KI'] = image(48, 50, "AAMB+AFDh4FL/AFDg4FIn//AAX4ArpHC/xNEAov/LQgFCDgYAlF4UfPx8/g/8CoQbBKgQhCAoMDFAkHAoeAh4FEDgQAB4E/FgIUBwE/HwQdBn/gAoM+AoPAAoMMAohFCAqIpCgI7C4BEBI4oICAoZfE4C9BAob2EAoISCaQgACA="); +hiragana['KU'] = image(33, 45, "AAsB4ADC+ADC/wDBgf/wADMg//CYIDDh4DDD4UfAY/8AY34AZRDCh4DCg4DCgYbCgI/CgH/BgU/BgREBBgIQB8AMCFIRNDLoJ2Cv42DJwQdDFQIdDFQQdDFQIdDHYRkDgYhCgADDnwDChyzE"); +hiragana['KE'] = image(50, 49, "AAUB/0Ag/gAwN/wAICgEfBIIIBB4P4BAYPCh/wDAcD/gYE/4FBDAU/4AYEGIgOCDAQOBh//AAP+v+DAoX/7/AAof3+E/AoX9/gYD/9/gYFD/4YE/5QCGIJQDHYRvCJQU/N4JKCKAYYCKAQYWmAYEjwYEx6lDh/zUocDMgIYDv6cBKgUf/4yBBAMH/4eC4EBNQUfAQN/DYMPE4TjCAQQkCYgSJBDYLEBn7QCAQIbCE4UDDYP/PIV/CgLpD4EPP4UH+AkBAoIACCgIADh6LCAAMDAoYA=="); +hiragana['KO'] = image(52, 50, "h//AAX+gAFD//gBgn/BgvwBiWAAon4GwUBDIQACCQQFCn//4AFCg4lBCQc/DwYfBKQJdEDwYAB8CIihAFEgJJDIgQFEg5KEMgITEj/8D4hwED4JqEOIIfEv5eEg4fEFg0PHIwsEBigmFCYkOv65CJYPnbgn+ZgIAD8IMFewvgCYjRBE4IMDegQABIoUfAoK7HA=="); +hiragana['SA'] = image(51, 50, "AAMB/gFE/+AAwcf+AFDgf+DIl/4AFDg4fEgAfLgIfCj//AFQzCn/gLJYMELI5mEh6GGBgUHGAP4CAQ3COYILCBgUDIgYZBAoYmBn5REDwPgQQPgDAIVBj4fBJ4d+CQI1CgeAXhgSDKoYSEQQp1GQQpFBawXwD4IGBg42BaQngBgRlDBgmABgjzBRYZDCPIYvCv//MQoACA=="); +hiragana['SI'] = image(45, 50, "v/AAgUD/wKDj/wAof/wAECg/8BQc/8AbD/4bE/AbEFgcHFgk/FgcBFgkPDYhIgFgIKDFh8eFgn+FgcH/4sDv+/FgUD/osDn/vFgQ2BFgcf+YsD/+fFgUP/gsDv/HFgSKBLId/8IsCHgIXBSod/EIIKBwIhCv/4h4WBAQOAv/+IIP8AQIAC4AYBAAIkBn4KDJQIKDCwYpBCwRWCAoJhDAoK1DAAg="); +hiragana['SU'] = image(52, 50, "AAUf8AFDgP+BjH/AYP/AAnvAon+BjJAUgf9BgZFB/4MDn4kEg4MFGIwMED4QME+E/+AyC/x0DFgPABwIMC/gMGDIn8gYMFv/4EwcP/+AKYf/BgRACBgYRB/4mCgF/AwJ6DBgoTCRohNDTZE/VAkP/gFDE4PAUQhGCI4YeEUIgYBD4gMBEpI4GgIFEAAo"); +hiragana['SE'] = image(56, 50, "AAcP/ADB//AAwP8AwkHA34FBAAn+A1JalmAGFvinFv4GF//PXghEBAwfBAwoNGEQP/+AGDn4GFh//8AGDg5PCgF/AYP/wAGEgj/CAwQADAw4mCAwZCCAAQ8BFQgGBAAQGBj4GFJQIGEJQIGEgYGFGIIGCIQQVDHQgACA"); +hiragana['SO'] = image(53, 50, "gP/AAXggEPAweAgF/AoX+gEDBgfwgEfCYoFD/EAg4MFAAQMCAAQwBBhQpBJQozBAAU/IAIACIYJUBAAV//gsJD4IsEn4sEOAn+NIn/+4FEAA39AwvvAwqQDAAP7UYhmCx5bDuBVB4BCDg5bEJ4JoEgJ1EEQKCESwIFEg5vEEA4TFh4TFv4TGYgiLBCYrFG/5dDd4YHCOQKkBDQjbDDQQwDWgR5DAwSGEEAgAEA=="); +hiragana['TA'] = image(52, 50, "gEP+AGE/4Mjgf/AAXAgE/AoX8BjUAgP+GYkf8AFDBhHnEIQMBEQQhBn/jFAWAgYMD/AMH/gMF4f/F4UH/kQGYd/KIIACg4VBBgmAQ4gMFUJcB/8DDQZgBv6iD/wuEn/gKIJGDEIl/4KCDC4KPE/+BBgYXBBgY5BAIImCj4MBTIKFB/wMBAAKSB8EPAwXnUYIMDCwLYD95RBEAIZCFQN/AwPBKISpBwEGQAgAGA=="); +hiragana['TI'] = image(51, 49, "gED/wGEv/AAocP/AFDgP/CQk/8AFDg/8Bgn/wAFDj/wBQYAqJ4M/LBZrMJYZ+Ch5aDv/f/4bCBQIABCoMDHAYTBv4+Ej4MEg4DB4IMCAoIcCwE/TwU/+ASBEQI8BVQJLCv/gS4cP/kBMgYWBjyoEgLbJEYYSCQQkHCQg2EHASCEv4SBgYOBOQ70BQoYrBEQIABFYR/DJASRED4YFCBgJDDA="); +hiragana['TU'] = image(59, 45, "AAUP/4FFAAIGCAoX//EAg4GD//ACYYAB/kBAwgOBn4OFDgoOBAYX+BYP8j4GBwEAAgPDGwQ+C/F/BgIABCwOMLQl/+AGEg/+NIv/8BwF/gGEKwIqDAAM/HAYzDEhkfEgsDEgxJGh5JFHQPACqQrBCpkfCopXBCogcBCog5BK4jSCAwxtDDYK8EZIQcCAoQcDCYTjCJgQGCEYT0DIAYGGEgQGDEgRcEv5UEA="); +hiragana['TE'] = image(57, 50, "/4AFv4GF34GF74GF94GF+4GF/YGF/oGF/w7Cn//4BCDAwOAAwpQEj4ZDAxP8AyUPAwwiFg4GMgZFFAw0BLQqlBNAkAv4GG8AGEn/wKgv4KhZGGHALeGH4oxNh4xFOJBjGEYt/VQwVFg//BwhOBAAI7Dv4GBHYYcBCwgcB/5CEDgQyFGYgrCUwkPKAwAC"); +hiragana['TO'] = image(46, 49, "gEH/AFDj/wAod/4AECgP/Cwn8C0cICwcDBoIWC/4NBCwMfEgV/4f/BoIWBv//LAMH/4AB8AWBAoWAgE/BQYlBDYUAh4FBHwQPEEIJQDFYJhCgYwCLQQqCDYQKDDYIKDn5xEEAYQB/x8JDYkDCAkPYIk/JoQWTAol/AocZQwR6B8aNCAAOPAgf+TIZqBAongT4QfCBYY9BW4R1BA="); +hiragana['NA'] = image(55, 50, "AAd/wEAn4CBgH/BIXAgEB/wJEgf8AQIJCg/4AQIJBgEP+ACBBIMAj/gAQYsBEoIoCGwf/GwkB/8P/4AC4f+j4GDw/4n4GDj/wv4FC/0/8AMD/l/4IGD/H/wYGD+P/g4vELARtCMQRtDMQQKDL4YKCMQQKDMQQKDR4QKCTIYKCFYQ2bOoI2C4BgCGwWASAQ2BGQKJC8DNBBAIAB+DNBPYf4ZoKrDAgPwT4K7BAwRdBB4K3BVYIqCVYY6BAwKrB/0DVY3+v/hAwf8n4SBdIXwnxEBAwXgnBEBAwShBO4IbBSYSVCOYQAHA"); +hiragana['NI'] = image(57, 50, "AAMPwAGE//gAocf//wgFwgEH////kH/AZBAwP+gf+Bof/wP/gEDAwWAAIMBAwc/FgIGDj4sBv4GBE4P8HAIdBE4IqBAwYgBKAIGCKAYKBAwN/EYIGDn4jBAwZfBDAQfBLIPAAwZZBDgItENYN/CAIfBIAIGCLIRfDLIXwAwc/RQJmCHAPv/0PEoI4B+f/AwcH/P/w50D/l/wZ0CgP+j/BK4Q4Bg/gJoQ4BwIGBIwU/4EwAQI4CIYICCAYY/EJQMHHATcCbAQKEHARGBGgQqBCIc/D4IGDaITCDT4PAAQJfCQQRYDeQQGDSIIGEYYIGEE4IGEDgYFCcAQ+CGQZsCABAA="); +hiragana['NU'] = image(58, 50, "gEP/AGEgf//wHE/4ABAwc/AwIPDh4OC8AGBg4GCEwUBAwX8Dod/EgoHC4AsF+BJFjAGDg4iEFgRfF/+AAwk/IwQjDFIgjDvAjDMYJlCgRHB4ABBFIUf/ABBFIXH/0HCoUf+BcBLwQpBCogpBCYIVDv+ACohNBn/wCoRxBCohNCMoIVBOIQVBAIJNCCAIVCEYIQBCoOAb4QtDCAQtC/gjCdIIXCN4QwBC4SVBDQIXBEYUP/gXBI4QEBHwPD/8ODgR/CwZNCCYN/8P/5/4GQOf+DtBKgXv/jtBKgX5/0PAwJxB/0/DAL8CvkDJYP/IYMMgFgg//fot/VYQACgYGFAAoA=="); +hiragana['NE'] = image(67, 45, "AAXwA43/4AHFn/8A4sPCA0B//+CAt///gA4kfCA0H/4QGA4IyFn4IBGQg5BIYsD//nCAt//F/CAkf/wzBCAYFBwH//BaE8ArBwBzFCAgNBLoQQCHIPADYIQD/6dBCAk/OQIQEHIQQEHIQkCCARaBO4YUCSYQQDHIQQFHIQQERQgQCLQQQEHIKBDCAPAn5fDCAP8gbNECAaJDCAbVECAPgvj+Gg72GdoqYFCAgHFKIoQDDA0AKIjODDA0ARYQAEhwHGAAIA=="); +hiragana['NO'] = image(54, 50, "h4GFn+AAocB/0IAwcH/F//4AB+Ef8IFC//A/+PAwcD/0fAoX8h/wDQk/4ITDAgMDAwcH/hGC/EAj/wIwXggF/4AGB/+AJIIFBGQJJCDQoWBDQf/wZlBDQIWBh41Dx5kE/0/Mgn4IgIGD8f8MgYaBL4IaEPQJrD/6RCGoRkCKAR/BKAgaBKAoaFNYoWCKIIaC8BKCDQWAIYQaCgJCCDQRyDDQRXDEoOBK4ahBW4K+CAgKcBDgLcBMwIwC/1/4JHBCYP5CoQwC4aND/atBRofDAgPgdQaSBHgX4hxXBHQXAhAOBAwKXCAAJlBbIIAH"); +hiragana['HA'] = image(50, 50, "AAMH/gFDgP/Bgl/4AFDj/wDBsH/4AD/oFE/9/AwoARJVXhAon4JQn+j4MEw4YLn4YEJTIfCAooYCAoX4DgQwCwBdEBgMDHoYMB//3Bgd/8AUC4A7BJQP//kHBwQGB4JYBFoX8KgMP/gGBz/+h//AIPjGAXA//wAoXwh/4DgX4gP8IgQnCF4QFBgOAEIKIEv6SCAA4A=="); +hiragana['HI'] = image(59, 50, "gP/AAOAA4U/AwPwAwUHAwP+CwYVC4AGCj4GB/AGCgYOCCod/AwPgGokH/g8GHQY8CHQYVCHQg8CwEfCAYEBgYQDAgV/JYYEBh5LDj/4GoJKEGoJLCAwP4JYZ9C/BLCNwSGDQgSGDOoaGDAwg6BEYQHDh//EomDAIP+ToaQBEIIvCKoJyCJgPH/yDCEIIVB4BNBMwIgB+CZCn/n4f+h5jBAQMw/+BOgKyCCoN/PIICBS4I0BCoQJBJQJqCBIP5NQfgD4KACn5tDGQSDEwADBTIJaBGQKZEDISvCToR8BeAQDBAQLbCb4RSCAAcHcQYACvwGFg45BAAj/DAAw="); +hiragana['HU'] = image(55, 50, "gED/gGEg/4AwkP+EAhwGCj/ggF+AwU/4EB/wGCv+Ag4GD/4kBAwM//4AB84GBv4GC54GBAoX/x/+gIGDh/+gYFC/0P/kHAwX8AwMPAwX4j5cCGwJOBAwJIDj5jBv4QCAwIpBNoU/+AiBNoIGCJYJtBAwPhFwPANQXjAwOAgEEv+P/A2C/H+CoI2BTIIhBwY2Bh/xwH+UgUf+CwBUgSgBBYKkCn/gh/gToI1B4Ef4AvCBIM/4ZmCIAN/44oBSgKdCFAJ3CLAY0BUgQoBGgIGBEIUPAwSID+AGBQIZHBJQRECd4Q9DI4QvBJwQ2Cj4sBGATRBJwLcDFgTcDC4QGEEILqEAwIbDIARoCBgQAGA="); +hiragana['HE'] = image(55, 50, "AAUf+AGEn/gAwl/4AECBQP/wAYC4EB/4YDwED/wYDwEH/gGCCIMP/AFBgIRBGwcDCIN/GwUH/EP/4bCDAP/AAI2C+4GCHwMfAoX/JgM/AwYjBv4GI8YGCFoN/wIGBgYCBFwIiBHYJfBNAPAn/8IwIGBwAaBh/wAwOD//4R4IfBg//+B2BDoJKB+AoBg/+JQPjOwMP/n/z/nQIMf/IOB76BBn/3/gVBMgN/94nBOQX/7/gAwKbBOwSOCHoJMCEIMH/v/CAJxBh/7/hcCF4X4KYLEC5/wj5KBEIOfGwJRCL4PzF4V/JIQvBCYJJCH4JxB4AGB/xCCFQIJDDoIMBBIRNBAQJdCIwKUCeAb5CPgQACSgIFDSgIFEAAg="); +hiragana['HO'] = image(51, 50, "AAN+AokP+AFDgf+Bgl/4ASE/ASVv//AAX8h4FD/+BAonwn4FD/0HBgnAAogoBgP/HAk/8AFDg5LEgASM/gSFwADBFQIAC8E4Iof+/5FE5/wAof5/0fAwc/8YFD8f8PAYEB54MDJ4SRDJ4KRDj/gNYaoCLAYWBLAYWCLAQWCDYJvDgYSCCwV/NYQWBGQc/+AyDg4yBj4MBgYSBAQP4OwPwbIglBQAgpBBgZiBBgYYBBgY1CU4S0DFoIRCAAo="); +hiragana['MA'] = image(55, 49, "gEP+AGEj/gAwk/4EAkAGCv+AgAPD/8AgYdCgP+EgkD/gdB/AGBg4DBv4GCj/w/wGCv////8AwQFB//4AwMBAwXwEQMDAwXgAwMHAwXAAwMPAwWAG4QvBLgQGBL4X/AwRfBKgIGCL4X8n/gLARUBn5YDMwM8NQaLBQYIoCAQSIDAQRZBRYaBDRYQhBFAIJCKIYyCDwKoBToZkBOAIJBPYKLCGwMH/h2CAwMfKoKKCI4PgSIYYB4afDJQMP/gpB+AhBMgIjB/AhC4EfAwIhCEoIGCwJdBaIIZBMgSkCjhMBgakBG4LICUgKDBAwQuBPgRKCjgGE4EQAwgEBAAIbBRAQACQgIDB"); +hiragana['MI'] = image(50, 50, "h+AAocD/gFDgP/CQl/4AFDn/gv//AAOP/E/AoXj/0HAoX4/+BAoX+DAuf+EfAoXn/gYD/P/gYEBG48f+AFDg5QMMYkf8BvE/BvE/wYE/4YEKAIYYgZSCDAMBJgQYCCgYDBFoYDBj4tCDAJlDDAMBGYYYBNYYYBn4xCg/4h6ECPgIHBPgfBDwaVBQgYvBToYYCFYauBaIIwB5/wcAfz/0PAoX8cAn/IgQFC55dBAoXxFILtC/grBGgL5BYIoAGA=="); +hiragana['MU'] = image(58, 50, "AAV/4AGEj/wAwkH/gGEgP/Aod+Dgv/wAcEj/gDgkH/AcEgP+Dgt/Dg3wn4mBHwYGBDAIyCAwP/8AGBAoQODh4GC/4sBgYGD/AcCAAO/IQQcC4IkCDgI7Bj5YBg//w/8EAIjCwIEBv/gMQPgLAMPFYP//h1BgZpC/4LCNwIxB4YoBFoIxB/AjBNIMH/v+n5UB/4qBn/fIoIJBv+PLYUPQwPhOIUD/gvBGYMH/3/BAX/457CBAP/84GBDgIlB/YGBCYJwB/qECDgKREwBCC34YBDgfvLYP+HIM/+YYCIwM/MoIYB/hGBMoQEBz4nBKQfDAwODGQXwKQQMB/P4j4GBAQP+ngtBUgIRBg6aBRwKiBwOAf4TNBAobjCAogAEA"); +hiragana['ME'] = image(57, 50, "gEP+AGEg/4AwkD/gGEgP+Dgv/Awt/wAGEn/Agf/BIUf8EP/40CHAMf/4tBAYP4AQImBCIP8n4GB4EH//+AwXgEwP/v4CB/EBAYIPBg4jBAwX8BYJFBCQRKDFYIGBJQJxBIgUfAQIrBAYMPCAIfBBQR8CAwR8DMAZ8Cv4GCGIQGDGIU/AwR8BAwKqCWoU/FoS1Cj4tCHASEBWogGBUAQKBAwItBHARpB8BlBBQKuCAQIKBO4SqCBQX8AwX4h/9/wGC/kP/n/DYSlCv+P/ArB4K+B4/4SIV+j/jWIX8n0P+JSBDoMOMwJWBAwOCMwM//ZOCMwI4C75nB/5bC45nBv+DAwPhTgXAb4PAoCfCQQifBYoYAHA"); +hiragana['MO'] = image(60, 50, "AAX//4GEv4HFj4GB/wGCg4GB//4AwMBAwX/4AcEDwcPAwYWBgYGDCwQVC54tCCoX8F4PgFYP4CYI+BgE//0P/gaB/ARB4F/4ApBwAVBg4OBj/8EgITB4AiB4InBBwQgBCAIOCPQPjD4MPJ4MH/0/+ALBwARB84kBBwQ0Bv/gBwc/+5bBj5tEHAR8Bn5lBBwInBBxY2CBwcDWIQOEGwIODJwIOFIoRKC4CNCBQP3AgKwCDIIOBKIQKB8/8IQJgBj4OB8E/MAfD/ytBEgX8J4KeBZwWDIgJCBCoP4ZgIzCAYIqBeYRQB8DnCK4gGBGoIDBwAyBF4IKCCQWBAwIVBEoPgF4RFBg/4F4Q2BAAQOBTwIADHoQADbIQAIA"); +hiragana['YA'] = image(54, 50, "gEf+AGEv/AAocB/4MEg/8DUv///Aj//wEDAwIcBAwMP//8BgIGBn//+IFBAwICB54GCDQQAC/0HAgXAn45BD4IDBn45Bv4MBAYPgGYJKCFAIbB8EAgf+DQRbEv/4LYYaBOQU/4EPCwIhCCYJrCgf8CYkP+BlBCYQaBv6GDOwQaECYIaEKwIaD4JWDgP+CYIaCg/4NQYTB8Z+BFwef+4aCMgN/74aCn/z/zXCIAOH/IaCh5CB44aBJoU+a4QyBwFwDQLGBCAOBX4adBGIJMBRIQaBUYI4CDQJnDFYJ7EDQKzCDQYECAA4"); +hiragana['YU'] = image(52, 49, "AAMf+AFDgP+Bgk/8AFDgYMM/gkD/4AC+EBAof/BkA5FhEAg45Cg/AgF/AQMBBIMP/4DB//gE4Xwn5dBn4GB74IBgY0Fv4FD8AfBAoYfB/gbBIAIiBg///A7B/+A/4rBCQIxBBAISB/ghBCQeBEoIMBCQI0BBgQSCDIYSB54MBgIlB+AMCj0H/0PBgIABHQQMBOgP4BgZBBBwTDCMYIMDKIIMRWQQmDAwUMYYqyBAoaxBN4IMEV4QMCcggMBWwbZCAweA"); +hiragana['YO'] = image(55, 50, "AAMHAwsP+AGEn/gAwl/4AFDgP/BgkD/whF/AGEj4oFEIsA/+AEIgoFg/8EIooFJQ3/JRcHJSgoGJQxEEg//FIkfAws/Cgv/AwUGJQX/HwMP8AoB74GBj/gh/+IoU/4BzBBQJBCJQIKBNQRzBv+AWoIIDJAP4SoMBIgIkBOYMDHoKTBAIIRBXgQBBB4IfBEIQYBFALgCCwMP/iVCJAXwJ4QfDcAX/4JRBSoRvBEIZ2DcAQGCFQIhBPoIYBcAQGBDAJqBCgQ6Bg7rIAAY="); +hiragana['RA'] = image(48, 50, "gEP4AFDj//wAFE/gFE/4TCn4FBBgQFCBgQRC//gBgN/BYUP/EBAog3BGIIFCgH/BAIFCh4FEgQFEBoXwAqsfAoIuBAoROBEwIFBIwP+AoPnLIWALwZfBNQf/+AFE/AFBEIM/AoR6Bh/8OoIzBg4FBRgQFCL4UD/wlBAoikCAoM/W4QFBj5dCAoMGAohpDg4FEHYJ1EAog5DDgJWCb4Y/Cg7RDaARFCAoZFBAobiEeoruCAoQtCAoI+DAAgA="); +hiragana['RI'] = image(40, 49, "ngEDn/AAg9/4Ef/AEBwF//4EBwP//4HBw4EB4F/x4EB8F/z4EB+H/n4EDAQIjBCwUPAgUAAgX+gEH/n//gEDHIMDAg3wAgP+AgvgAhBeBAhmAAiJ3BAhf8AgRUBAhBXBAAJtBAgSgCVgRcBAAJXCEwIEDj5SCBoJDCBAKSBBASSBXwKICAgQmCAgIcCv4SCAgI0DeAY="); +hiragana['RU'] = image(51, 50, "gf/AAXAgF/AoX8gEPBgeAgIFD/EAn4MEg4FD8EACQoACn4lBAAUf/4FDDYOAAoQuBHwIACv/wDwgkEh/+DwoFDDw5ECDwRLDMwg5BLIZMBNgh/FGgIeB+AVB4AeBEYJmBBAJQBDgPBOocf/AoCVIU/Kwc/+5WDg/+Kwl/5/wh4mBh/4/A2CFgMOAoJDC8GBMgUHGAJQCCQKpCBgISBgf+SQMPCQN/4H/4YSBGIIwBCgMBDoTMCn/AEIROCLoKFEAIJvBTwZvCTAarFNIQFCXASyCYoYxBAoYAEA="); +hiragana['RE'] = image(56, 50, "gEf8AGF+AGigP/wAGDg//GYQGBh//C4M/AYICB/AGDv///gGC+P/AwQKB+YGB/wNC+//w4GDBYMDAwn4AwQ3BFQIGF8AGF4AGFgAGEAYMDHwIGBAYIGDn5XBAwhlBAwd/Axh6CAwSPBAwMHAxEDAwqdBAwidDAw5IBOoQGDU4QGDUAIGE//fAwufCgrmCh4iCAwk4nwGE/EcAwbSBjAGFegReCUgIGJOYIUEQIYGCIYOAAwPgAwIAIA="); +hiragana['RO'] = image(50, 50, "AAf4gEB/4AC8EAv4FC/kAj4MDwEHAofwDAgSBDAoACn/+AocfAokP/4FDE4OAApED//AAohJBAAI5BAocAIQIFEHghFCD4QFCBoU/KIQMBNQZ9BOAhOCQYYFE/B8CE4QFBM4JGB4YuDj/7AocD/xIE/+fP4c/84FDh/8QoZyBj5mE4aFDn5yEDAIFDGIIFDIgIXDDwKREv4eEv4eBiAFCDwMH+A8BIQLnEEgLnDSooqBQYQFCDgQ2DAoolCJAgAD"); +hiragana['WA'] = image(51, 50, "AAV/4AFDh/4AocB/4DBj/ggE/AQMD/0Ag/8DgWAgH/AQMP+ASB//AgISBAoIDC4Ef///+ASBh4FB/4SBgYFC+E/4IFC/8H/F///9//g/8f/3/x/+j/nAQPwv/j/H/wf+I4N/KAJlBv+P9/4MoMP/f9/xlBAIIqBwAUBn/vFwIdBg40BNIIOBIIR7B+BbC8B7BKoX4uAyCAwM+GQX5//f8IyCn/z/hHCK4N/4/8h/8/4EB/4lBF4P/z5wB8f+RYJjBPoPAFwO/BQP4IQX/wJkCTAUfVYf4gf4BgS4BbQRiCcgbSCAAILEcALkCAAM/DoYeCC4ZLBfoIeD/ASEDAhoBAoYlBDwcAg/ABggAEA="); +hiragana['N'] = image(54, 50, "AAVgAYUP8EHwAGCv/Av4RD/8D/wFCgf8g/8DQf4j/4AwU/8E/+AaDwF//4VBgIfB/4GCD4MPAwcf+YFB/4jBn4FC/4jBAof/4AYC//n/+DBYeD/wZC/f/FgIrCGIQsCKYU/444CKYP/z4xCvxOBv+/8EBQQP4B4KFCCoJeCNIYPBQgQKBj53CAYSbBCYQDBHgJbCTYUDOQZHBM4QTBTYX/GQQxBP4Y8BDQRGBTYY4Eh5MDHgZTDAojdEbAYGEHgIGEv7/DHgIhFfAh1EEIg8GEIg8GTYYhDHhYAF"); +/// ///////////////////////////////////////// + +let kana = katakana.KA; +let scroll = 0; + +let hiramode = false; +let curkana = 'KA'; +function next () { + let found = false; + for (const k of Object.keys(katakana).sort()) { + if (found) { + kana = hiramode ? hiragana[k] : katakana[k]; + curkana = k; + return; + } + if (curkana === k) { + found = true; + } + } + curkana = 'KA'; + kana = hiramode ? hiragana[curkana] : katakana[curkana]; + updateWatch(ohhmm); +} + +function randKana() { + try { + const keys = Object.keys(katakana); + const total = keys.length; + let index = 0 | (Math.random() * total); + curkana = keys[index]; + kana = hiramode ? hiragana[curkana] : katakana[curkana]; + } catch (e) { + randKana(); + } +} + +function prev () { + let oldk = ''; + let count = 0; + for (const k of Object.keys(katakana).sort()) { + if (curkana === k) { + if (count > 0) { + curkana = oldk; + kana = katakana[curkana]; + return; + } + } + oldk = k; + count++; + } + curkana = oldk; + kana = katakana[curkana]; + updateWatch(ohhmm); +} + +const kanacolors = { + A: [] +}; + + +function updateWatch (hhmm) { + g.setFontAlign(-1, -1, 0); + g.setBgColor(0, 0, 0); + g.setColor(0, 0, 0); + var whitecolor = false; + if (curkana.indexOf('A') != -1) { + g.setColor(1, 0, 0); + whitecolor = true; + } else if (curkana.indexOf('I') != -1) { + g.setColor(0, 1, 0); + } else if (curkana.indexOf('U') != -1) { + g.setColor(0, 0, 1); + whitecolor = true; + } else if (curkana.indexOf('E') != -1) { + g.setColor(1, 1, 0); + } else { + g.setColor(0, 1, 1); + } + g.fillRect(0, 0, w, h); + + g.setFont('Vector', 50); + if (whitecolor) { + g.setColor(0, 0, 0); + } else { + g.setColor(0.5, 0.5, 0.5); + } + x = 26; + y = h - 42; + g.drawString(hhmm, x - 3, y - 3); + if (whitecolor) { + g.setColor(1, 1, 1); + } else { + g.setColor(0, 0, 0); + } + g.drawString(hhmm, x, y - 1); + + drawKana(4 + (g.getWidth() / 6), 60); + drawMonthDay(); + Bangle.drawWidgets(); +} + +function drawMonthDay() { + g.setFont('Vector', 20); + g.setColor(1,1,1); + g.setFontAlign(-1, -1, 0); + g.drawString(month, 4, 112); + g.setFontAlign(1, -1, 0); + g.drawString(day, w, 112); +} + +function getPhoneme(k) { + switch (k) { + case "TU": return "TSU"; + case "TI": return "CHI"; + case "SI": return "SHI"; + case "HU": return "FU"; + } + return k; +} + +function drawKana (x, y) { + g.setColor(0, 0, 0); + g.fillRect(0, 0, g.getWidth(), 6 * (h / 8) + 1); + g.setColor(1, 1, 1); + g.drawImage(kana, x + 20, 40, { scale: 1.6 }); + g.setColor(1, 1, 1); + g.setFont('Vector', 24); + g.drawString(getPhoneme(curkana), 4, 32); + g.drawString(hiramode ? 'H' : 'K', w - 20, 32); +} + +var ohhmm = ''; + +function tickWatch () { + const now = Date(); + month = now.getMonth() + 1; + day = now.getDate(); + function zpad (n) { + return (n < 10) ? '0' + n : n; + } + const hhmm = zpad(now.getHours()) + ':' + zpad(now.getMinutes()); + if (hhmm !== ohhmm) { + randKana(); + updateWatch(hhmm); + ohhmm = hhmm; + } +} + +Bangle.on('touch', function (tap, top) { + if (top.x < w / 4) { + prev(); + } else if (top.x > (w - (w / 4))) { + next(); + } else { + hiramode = !hiramode; + } + kana = hiramode ? hiragana[curkana] : katakana[curkana]; + updateWatch(ohhmm); +}); + +g.clear(true); +// show launcher when button pressed +Bangle.setUI('clock'); +Bangle.loadWidgets(); +tickWatch(); +setInterval(tickWatch, 1000 * 60); + + diff --git a/apps/kanawatch/app.png b/apps/kanawatch/app.png new file mode 100644 index 000000000..cf081937b Binary files /dev/null and b/apps/kanawatch/app.png differ diff --git a/apps/kanawatch/fontmaker.zip b/apps/kanawatch/fontmaker.zip new file mode 100644 index 000000000..39c7d5d53 Binary files /dev/null and b/apps/kanawatch/fontmaker.zip differ diff --git a/apps/kanawatch/metadata.json b/apps/kanawatch/metadata.json new file mode 100644 index 000000000..b14703979 --- /dev/null +++ b/apps/kanawatch/metadata.json @@ -0,0 +1,31 @@ +{ + "id": "kanawatch", + "name": "Kanawatch", + "shortName": "Kanawatch", + "version": "0.05", + "type": "clock", + "description": "Learn Hiragana and Katakana", + "icon": "app.png", + "allow_emulator": true, + "tags": "clock", + "supports": [ + "BANGLEJS2" + ], + "readme": "README.md", + "storage": [ + { + "name": "kanawatch.app.js", + "url": "app.js" + }, + { + "name": "kanawatch.img", + "url": "app-icon.js", + "evaluate": true + } + ], + "screenshots": [ + { + "url": "screenshot.png" + } + ] +} diff --git a/apps/kanawatch/screenshot.png b/apps/kanawatch/screenshot.png new file mode 100644 index 000000000..b1ed879aa Binary files /dev/null and b/apps/kanawatch/screenshot.png differ diff --git a/apps/kbmorse/ChangeLog b/apps/kbmorse/ChangeLog index f62348ec8..c85361374 100644 --- a/apps/kbmorse/ChangeLog +++ b/apps/kbmorse/ChangeLog @@ -1 +1,2 @@ -0.01: New Keyboard! \ No newline at end of file +0.01: New Keyboard! +0.02: Temporarily fix because of firmware bug. diff --git a/apps/kbmorse/lib.js b/apps/kbmorse/lib.js index 8bc177a46..997f2cb16 100644 --- a/apps/kbmorse/lib.js +++ b/apps/kbmorse/lib.js @@ -82,6 +82,36 @@ exports.input = function(options) { } return new Promise((resolve, reject) => { + const Layout = require("Layout"); + let layout = new Layout({ + type: "h", c: [ + { + type: "v", width: Bangle.appRect.w-8, bgCol: g.theme.bg, c: [ + {id: "dots", type: "txt", font: "6x8:2", label: "", fillx: 1, bgCol: g.theme.bg}, + {filly: 1, bgCol: g.theme.bg}, + { + type: "h", fillx: 1, c: [ + {id: "del", type: "txt", font: "6x8", label: " + ({type: "txt", font: "6x8", height: Math.floor(Bangle.appRect.h/3), r: 1, label: l}) + ) + } + ] + }); function update() { let dots = [], dashes = []; @@ -157,36 +187,6 @@ exports.input = function(options) { } } - const Layout = require("Layout"); - let layout = new Layout({ - type: "h", c: [ - { - type: "v", width: Bangle.appRect.w-8, bgCol: g.theme.bg, c: [ - {id: "dots", type: "txt", font: "6x8:2", label: "", fillx: 1, bgCol: g.theme.bg}, - {filly: 1, bgCol: g.theme.bg}, - { - type: "h", fillx: 1, c: [ - {id: "del", type: "txt", font: "6x8", label: " - ({type: "txt", font: "6x8", height: Math.floor(Bangle.appRect.h/3), r: 1, label: l}) - ) - } - ] - }); g.reset().clear(); update(); @@ -244,4 +244,4 @@ exports.input = function(options) { }; Bangle.on("swipe", Bangle.swipeHandler); }); -}; \ No newline at end of file +}; diff --git a/apps/kbmorse/metadata.json b/apps/kbmorse/metadata.json index f9c5354f1..9111d514d 100644 --- a/apps/kbmorse/metadata.json +++ b/apps/kbmorse/metadata.json @@ -1,7 +1,7 @@ { "id": "kbmorse", "name": "Morse keyboard", - "version": "0.01", + "version": "0.02", "description": "A library for text input as morse code", "icon": "app.png", "type": "textinput", diff --git a/apps/kbmulti/ChangeLog b/apps/kbmulti/ChangeLog index 709aa3203..19739fa64 100644 --- a/apps/kbmulti/ChangeLog +++ b/apps/kbmulti/ChangeLog @@ -1,2 +1,4 @@ 0.01: New keyboard 0.02: Introduce setting "Show help button?". Make setting firstLaunch invisible by removing corresponding code from settings.js. Add marker that shows when character selection timeout has run out. Display opened text on launch when editing existing text string. Perfect horizontal alignment of buttons. Tweak help message letter casing. +0.03: Use default Bangle formatter for booleans +0.04: Allow moving the cursor diff --git a/apps/kbmulti/README.md b/apps/kbmulti/README.md index 4c83d378e..80b2b077a 100644 --- a/apps/kbmulti/README.md +++ b/apps/kbmulti/README.md @@ -2,7 +2,7 @@ A library that provides the ability to input text in a style familiar to anyone who had a mobile phone before they went all touchscreen. -Swipe right for Space, left for Backspace, and up/down for Caps lock. Tap the '?' button in the app if you need a reminder! +Swipe right for Space, left for Backspace, down for cursor moving mode, and up for Caps lock. Swipe left and right to move the cursor in moving mode. Tap the '?' button in the app if you need a reminder! At time of writing, only the [Noteify app](http://microco.sm/out/Ffe9i) uses a keyboard. diff --git a/apps/kbmulti/lib.js b/apps/kbmulti/lib.js index 5ccab4204..aa54dab9c 100644 --- a/apps/kbmulti/lib.js +++ b/apps/kbmulti/lib.js @@ -17,18 +17,50 @@ exports.input = function(options) { "4":"GHI4","5":"JKL5","6":"MNO6", "7":"PQRS7","8":"TUV80","9":"WXYZ9", }; - var helpMessage = 'Swipe:\nRight: Space\nLeft:Backspace\nUp/Down: Caps lock\n'; + var helpMessage = 'Swipe:\nRight: Space\nLeft:Backspace\nUp: Caps lock\nDown:Move mode'; var charTimeout; // timeout after a key is pressed var charCurrent; // current character (index in letters) var charIndex; // index in letters[charCurrent] + var textIndex = text.length; + var textWidth = settings.showHelpBtn ? 10 : 14; var caps = true; var layout; - var btnWidth = g.getWidth()/3 + var btnWidth = g.getWidth()/3; + + function getMoveChar(){ + return "\x00\x0B\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00@\x1F\xE1\x00\x10\x00\x10\x01\x0F\xF0\x04\x01\x00"; + } + + function getMoreChar(){ + return "\x00\x0B\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xDB\x1B`\x00\x00\x00"; + } + + + function getCursorChar(){ + return "\x00\x0B\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xAA\xAA\x80"; } function displayText(hideMarker) { layout.clear(layout.text); - layout.text.label = text.slice(settings.showHelpBtn ? -11 : -13) + (hideMarker ? " " : "_"); + + let charsBeforeCursor = textIndex; + let charsAfterCursor = Math.min(text.length - textIndex, (textWidth)/2); + + + let start = textIndex - Math.ceil(textWidth - charsAfterCursor); + let startMore = false; + if (start > 0) {start++; startMore = true} + if (start < 0) start = 0; + let cursor = textIndex + 1; + + let end = cursor + Math.floor(start + textWidth - cursor); + if (end <= text.length) {end--; if (startMore) end--;} + if (end > text.length) end = text.length; + + let pre = (start > 0 ? getMoreChar() : "") + text.slice(start, cursor); + let post = text.slice(cursor, end) + (end < text.length - 1 ? getMoreChar() : ""); + + layout.text.label = pre + (hideMarker ? " " : (moveMode? getMoveChar():getCursorChar())) + post; layout.render(layout.text); } @@ -41,8 +73,11 @@ exports.input = function(options) { function backspace() { deactivateTimeout(charTimeout); - text = text.slice(0, -1); - newCharacter(); + if (textIndex > -1){ + text = text.slice(0, textIndex) + text.slice(textIndex + 1); + if (textIndex > -1) textIndex --; + newCharacter(); + } } function setCaps() { @@ -55,6 +90,7 @@ exports.input = function(options) { function newCharacter(ch) { displayText(); + if (ch && textIndex < text.length) textIndex ++; charCurrent = ch; charIndex = 0; } @@ -69,7 +105,11 @@ exports.input = function(options) { newCharacter(key); } var newLetter = letters[charCurrent][charIndex]; - text += (caps ? newLetter.toUpperCase() : newLetter.toLowerCase()); + let pre = text.slice(0, textIndex); + let post = text.slice(textIndex, text.length); + + text = pre + (caps ? newLetter.toUpperCase() : newLetter.toLowerCase()) + post; + // set a timeout charTimeout = setTimeout(function() { charTimeout = undefined; @@ -78,14 +118,29 @@ exports.input = function(options) { displayText(charTimeout); } + var moveMode = false; + function onSwipe(dirLeftRight, dirUpDown) { - if (dirUpDown) { + if (dirUpDown == -1) { setCaps(); + } else if (dirUpDown == 1) { + moveMode = !moveMode; + displayText(false); } else if (dirLeftRight == 1) { - text += ' '; - newCharacter(); + if (!moveMode){ + text = text.slice(0, textIndex + 1) + " " + text.slice(++textIndex); + newCharacter(); + } else { + if (textIndex < text.length) textIndex++; + displayText(false); + } } else if (dirLeftRight == -1) { - backspace(); + if (!moveMode){ + backspace(); + } else { + if (textIndex > -1) textIndex--; + displayText(false); + } } } diff --git a/apps/kbmulti/metadata.json b/apps/kbmulti/metadata.json index 1efdb8847..a1f6ffa81 100644 --- a/apps/kbmulti/metadata.json +++ b/apps/kbmulti/metadata.json @@ -1,6 +1,6 @@ { "id": "kbmulti", "name": "Multitap keyboard", - "version":"0.02", + "version":"0.04", "description": "A library for text input via multitap/T9 style keypad", "icon": "app.png", "type":"textinput", diff --git a/apps/kbmulti/settings.js b/apps/kbmulti/settings.js index 8a66cd8f0..96e72b290 100644 --- a/apps/kbmulti/settings.js +++ b/apps/kbmulti/settings.js @@ -23,7 +23,6 @@ }, /*LANG*/'Show help button?': { value: !!settings().showHelpBtn, - format: v => v?"Yes":"No", onchange: v => updateSetting("showHelpBtn", v) } }; diff --git a/apps/kbtouch/metadata.json b/apps/kbtouch/metadata.json index f6d6d5228..89d121d63 100644 --- a/apps/kbtouch/metadata.json +++ b/apps/kbtouch/metadata.json @@ -6,10 +6,11 @@ "type":"textinput", "tags": "keyboard", "supports" : ["BANGLEJS2"], - "screenshots": [{"url":"screenshot.png"}], + "screenshots": [{"url":"screenshot.png"}], "readme": "README.md", "storage": [ {"name":"textinput","url":"lib.js"}, {"name":"kbtouch.settings.js","url":"settings.js"} - ] + ], + "sortorder":-1 } diff --git a/apps/kitchen/ChangeLog b/apps/kitchen/ChangeLog index 3767a9548..4e8c49c50 100644 --- a/apps/kitchen/ChangeLog +++ b/apps/kitchen/ChangeLog @@ -11,3 +11,4 @@ 0.11: Detect when waypoints.json is not present, error E-WPT 0.12: Added stepo2 as a replacement for stepo and digi 0.13: Added long press BTN2 toggle gpsrec status in GPS clock +0.14: Move waypoints.json (and editor) to 'waypoints' app diff --git a/apps/kitchen/README.md b/apps/kitchen/README.md index 102881d15..3049d9c6d 100644 --- a/apps/kitchen/README.md +++ b/apps/kitchen/README.md @@ -60,7 +60,7 @@ The following buttons depend on which face is currently in use ![](screenshot_stepo.jpg) - now replaced by Stepo2 but still available if you install manually -- Requires one of the pedominter widgets to be installed +- Requires one of the pedominter widgets to be installed - Displays the time in large font - Display current step count in a doughnut gauge - Show step count in the middle of the doughnut gauge @@ -208,14 +208,8 @@ which will obviously limit this. ### Waypoint Editor -Clicking on the download icon of gpsnav in the app loader invokes the -waypoint editor. The editor downloads and displays the current -`waypoints.json` file. Clicking the `Edit` button beside an entry -causes the entry to be deleted from the list and displayed in the -edit boxes. It can be restored - by clicking the `Add waypoint` -button. A new markable entry is created by using the `Add name` -button. The edited `waypoints.json` file is uploaded to the Bangle by -clicking the `Upload` button. +Clicking on the download icon of `Waypoints` in the app loader invokes the +waypoint editor. See the `Waypoints` app for more information. ### Calibration of the Compass diff --git a/apps/kitchen/kitchen.app.js b/apps/kitchen/kitchen.app.js index 5564b2807..2c2cebaef 100644 --- a/apps/kitchen/kitchen.app.js +++ b/apps/kitchen/kitchen.app.js @@ -23,7 +23,7 @@ function nextFace(){ iface += 1 iface = iface % FACES.length; face = FACES[iface](); - + g.clear(); g.reset(); face.init(gpsObj, swObj, hrmObj, tripObject); @@ -64,7 +64,7 @@ function buttonReleased(btn) { clearInterval(pressTimer); pressTimer = undefined; } - + if ( dur >= 1.5 ) { switch(btn) { case 1: @@ -165,11 +165,11 @@ GPS.prototype.getLastFix = function() { GPS.prototype.determineGPSState = function() { this.log_debug("determineGPSState"); gpsPowerState = Bangle.isGPSOn(); - + //this.log_debug("last_fix.fix " + this.last_fix.fix); //this.log_debug("gpsPowerState " + this.gpsPowerState); //this.log_debug("last_fix.satellites " + this.last_fix.satellites); - + if (!gpsPowerState) { this.gpsState = this.GPS_OFF; this.resetLastFix(); @@ -178,9 +178,9 @@ GPS.prototype.determineGPSState = function() { } else { this.gpsState = this.GPS_SATS; } - + this.log_debug("gpsState=" + this.gpsState); - + if (this.gpsState !== this.GPS_OFF) { if (this.listenerCount === 0) { Bangle.on('GPS', processFix); @@ -196,9 +196,9 @@ GPS.prototype.determineGPSState = function() { } }; -GPS.prototype.getGPSTime = function() { +GPS.prototype.getGPSTime = function() { var time; - + if (this.last_fix !== undefined && this.last_fix.time !== undefined && this.last_fix.time.toUTCString !== undefined && (this.gpsState == this.GPS_SATS || this.gpsState == this.GPS_RUNNING)) { time = this.last_fix.time.toUTCString().split(" "); @@ -216,7 +216,7 @@ GPS.prototype.toggleGPSPower = function() { this.gpsPowerState = Bangle.isGPSOn(); this.gpsPowerState = !this.gpsPowerState; Bangle.setGPSPower((this.gpsPowerState ? 1 : 0), 'kitchen'); - + this.resetLastFix(); this.determineGPSState(); @@ -247,11 +247,11 @@ GPS.prototype.processFix = function(fix) { //this.log_debug("GPS:processFix()"); //this.log_debug(fix); this.last_fix.time = fix.time; - + if (this.gpsState == this.GPS_TIME) { this.gpsState = this.GPS_SATS; } - + if (fix.fix) { //this.log_debug("Got fix - setting state to GPS_RUNNING"); this.gpsState = this.GPS_RUNNING; @@ -271,10 +271,10 @@ GPS.prototype.formatTime = function(now) { GPS.prototype.timeSince = function(t) { var hms = t.split(":"); var now = new Date(); - + var sn = 3600*(now.getHours()) + 60*(now.getMinutes()) + 1*(now.getSeconds()); var st = 3600*(hms[0]) + 60*(hms[1]) + 1*(hms[2]); - + return (sn - st); }; @@ -313,7 +313,7 @@ GPS.prototype.getWPdistance = function() { GPS.prototype.getWPbearing = function() { //log_debug(this.last_fix); //log_debug(this.wp_current); - + if (this.wp_current.name === "E-WPT" || this.wp_current.name === "NONE" || this.wp_current.lat === undefined || this.wp_current.lat === 0) return 0; else @@ -321,7 +321,7 @@ GPS.prototype.getWPbearing = function() { } GPS.prototype.loadFirstWaypoint = function() { - var waypoints = require("Storage").readJSON("waypoints.json")||[{name:"E-WPT"}]; + var waypoints = require("waypoints").load(); this.wp_index = 0; this.wp_current = waypoints[this.wp_index]; log_debug(this.wp_current); @@ -345,10 +345,10 @@ GPS.prototype.markWaypoint = function() { return; log_debug("GPS::markWaypoint()"); - - var waypoints = require("Storage").readJSON("waypoints.json")||[{name:"E-WPT"}]; + + var waypoints = require("waypoints").load(); this.wp_current = waypoints[this.wp_index]; - + if (this.waypointHasLocation()) { waypoints[this.wp_index] = {name:this.wp_current.name, lat:0, lon:0}; } else { @@ -356,12 +356,12 @@ GPS.prototype.markWaypoint = function() { } this.wp_current = waypoints[this.wp_index]; - require("Storage").writeJSON("waypoints.json", waypoints); + require("waypoints").save(waypoints); log_debug("GPS::markWaypoint() written"); } GPS.prototype.nextWaypoint = function(inc) { - var waypoints = require("Storage").readJSON("waypoints.json")||[{name:"E-WPT"}]; + var waypoints = require("waypoints").load(); this.wp_index+=inc; if (this.wp_index>=waypoints.length) this.wp_index=0; if (this.wp_index<0) this.wp_index = waypoints.length-1; @@ -520,7 +520,7 @@ function STOPWATCH() { this.redrawLaps = true; this.redrawTime = true; } - + STOPWATCH.prototype.log_debug = function(o) { //console.log(o); } @@ -531,7 +531,7 @@ STOPWATCH.prototype.timeToText = function(t) { let secs = Math.floor(t/1000)%60; let text; - if (hrs === 0) + if (hrs === 0) text = ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2); else text = (""+hrs) + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2); @@ -551,7 +551,7 @@ STOPWATCH.prototype.stopStart = function() { if (this.running) this.tStart = Date.now() + this.tStart - this.tCurrent; - + this.tTotal = Date.now() + this.tTotal - this.tCurrent; this.tCurrent = Date.now(); this.redrawButtons = true; @@ -623,7 +623,7 @@ STOPWATCH.prototype.drawLaptimes = function() { g.setFont("Vector",24); g.setFontAlign(-1,-1); g.clearRect(4, 205, 239, 229); // clear the last line of the lap times - + let laps = 0; for (let i in this.lapTimes) { g.drawString(this.lapTimes.length-i + ": " + this.timeToText(this.lapTimes[i]), 4, this.timeY + 40 + i*24); @@ -645,7 +645,7 @@ STOPWATCH.prototype.drawTime = function() { g.setFont("Vector",38); g.setFontAlign(0,0); g.clearRect(0, this.timeY-21, 200, this.timeY+21); - g.setColor(0xFFC0); + g.setColor(0xFFC0); g.drawString(txtTotal, xTotal, this.timeY); // current lap time @@ -691,7 +691,7 @@ function HRM() { this.bpm = 0; this.confidence = 0; } - + HRM.prototype.log_debug = function(o) { //console.log(o); } @@ -782,7 +782,7 @@ Debug Object function DEBUG() { this.logfile = require("Storage").open("debug.log","a"); } - + DEBUG.prototype.log = function(msg) { let timestamp = new Date().toString().split(" ")[4]; let line = timestamp + ", " + msg + "\n"; diff --git a/apps/kitchen/metadata.json b/apps/kitchen/metadata.json index ab2e7183c..9c9f7b2ec 100644 --- a/apps/kitchen/metadata.json +++ b/apps/kitchen/metadata.json @@ -1,14 +1,14 @@ { "id": "kitchen", "name": "Kitchen Combo", - "version": "0.13", + "version": "0.14", "description": "Combination of the Stepo, Walkersclock, Arrow and Waypointer apps into a multiclock format. 'Everything but the kitchen sink'", "icon": "kitchen.png", "type": "clock", "tags": "tool,outdoors,gps", "supports": ["BANGLEJS"], "readme": "README.md", - "interface": "waypoints.html", + "dependencies" : { "waypoints":"type" }, "storage": [ {"name":"kitchen.app.js","url":"kitchen.app.js"}, {"name":"stepo2.kit.js","url":"stepo2.kit.js"}, @@ -16,6 +16,5 @@ {"name":"gps.kit.js","url":"gps.kit.js"}, {"name":"compass.kit.js","url":"compass.kit.js"}, {"name":"kitchen.img","url":"kitchen.icon.js","evaluate":true} - ], - "data": [{"name":"waypoints.json","url":"waypoints.json"}] + ] } diff --git a/apps/kitchen/waypoints.html b/apps/kitchen/waypoints.html deleted file mode 100644 index d02260732..000000000 --- a/apps/kitchen/waypoints.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - -

List of waypoints

- - - - - - - - - - - - -
NameLat.Long.Actions
-
-

Add a new waypoint

-
-
-
- -
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- - - - - - - diff --git a/apps/kitchen/waypoints.json b/apps/kitchen/waypoints.json deleted file mode 100644 index 98a670c0d..000000000 --- a/apps/kitchen/waypoints.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "name":"NONE" - }, - { - "name":"No10", - "lat":51.5032, - "lon":-0.1269 - }, - { - "name":"Stone", - "lat":51.1788, - "lon":-1.8260 - }, - { "name":"WP0" }, - { "name":"WP1" }, - { "name":"WP2" }, - { "name":"WP3" }, - { "name":"WP4" } -] \ No newline at end of file diff --git a/apps/largeclock/ChangeLog b/apps/largeclock/ChangeLog index 8c9b24be9..f35a02c54 100644 --- a/apps/largeclock/ChangeLog +++ b/apps/largeclock/ChangeLog @@ -8,3 +8,4 @@ 0.08: Use Bangle.setUI for button/launcher handling 0.09: fix font size for latest firmwares 0.10: Configure the side text direction based on the wrist on which you wear your watch +0.11: Use default Bangle formatter for booleans diff --git a/apps/largeclock/metadata.json b/apps/largeclock/metadata.json index dde790786..204243089 100644 --- a/apps/largeclock/metadata.json +++ b/apps/largeclock/metadata.json @@ -1,7 +1,7 @@ { "id": "largeclock", "name": "Large Clock", - "version": "0.10", + "version": "0.11", "description": "A readable and informational digital watch, with date, seconds and moon phase", "icon": "largeclock.png", "type": "clock", diff --git a/apps/largeclock/settings.js b/apps/largeclock/settings.js index f996666ab..4ebf842ce 100644 --- a/apps/largeclock/settings.js +++ b/apps/largeclock/settings.js @@ -74,7 +74,6 @@ "BTN3 app": () => showApps("BTN3"), "On right hand": { value: !!settings.right_hand, - format: v=>v?"Yes":"No", onchange: v=>{ settings.right_hand = v; s.writeJSON("largeclock.json", settings); diff --git a/apps/launch/ChangeLog b/apps/launch/ChangeLog index 7248f69c3..44866b9f3 100644 --- a/apps/launch/ChangeLog +++ b/apps/launch/ChangeLog @@ -13,3 +13,4 @@ 0.12: Add an option to hide clocks from the app list (fix #1015) Add /*LANG*/ tags for internationalisation 0.13: Add fullscreen mode +0.14: Use default Bangle formatter for booleans diff --git a/apps/launch/metadata.json b/apps/launch/metadata.json index da76fc4bb..19ca74e73 100644 --- a/apps/launch/metadata.json +++ b/apps/launch/metadata.json @@ -2,7 +2,7 @@ "id": "launch", "name": "Launcher", "shortName": "Launcher", - "version": "0.13", + "version": "0.14", "description": "This is needed to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.", "readme": "README.md", "icon": "app.png", diff --git a/apps/launch/settings.js b/apps/launch/settings.js index 5d37e1c1b..496a6d77e 100644 --- a/apps/launch/settings.js +++ b/apps/launch/settings.js @@ -26,12 +26,10 @@ }, /*LANG*/"Show Clocks": { value: settings.showClocks == true, - format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", onchange: (m) => { save("showClocks", m) } }, /*LANG*/"Fullscreen": { value: settings.fullscreen == true, - format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", onchange: (m) => { save("fullscreen", m) } } }; diff --git a/apps/lcars/ChangeLog b/apps/lcars/ChangeLog index 9a8ac4008..f97ddf540 100644 --- a/apps/lcars/ChangeLog +++ b/apps/lcars/ChangeLog @@ -21,3 +21,4 @@ 0.21: Add custom theming. 0.22: Fix alarm and add build in function for step counting. 0.23: Add warning for low flash memory +0.24: Add ability to disable alarm functionality \ No newline at end of file diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index e81c0d6f3..06a89a957 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -12,6 +12,7 @@ let settings = { themeColor1BG: "#FF9900", themeColor2BG: "#FF00DC", themeColor3BG: "#0094FF", + disableAlarms: false, }; let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; for (const key in saved_settings) { @@ -722,12 +723,12 @@ Bangle.on('touch', function(btn, e){ } if(lcarsViewPos == 0){ - if(is_upper){ + if(is_upper && !settings.disableAlarms){ feedback(); increaseAlarm(); drawState(); return; - } if(is_lower){ + } if(is_lower && !settings.disableAlarms){ feedback(); decreaseAlarm(); drawState(); diff --git a/apps/lcars/lcars.settings.js b/apps/lcars/lcars.settings.js index b64feb30e..e4b9b0a78 100644 --- a/apps/lcars/lcars.settings.js +++ b/apps/lcars/lcars.settings.js @@ -13,6 +13,7 @@ themeColor1BG: "#FF9900", themeColor2BG: "#FF00DC", themeColor3BG: "#0094FF", + disableAlarms: false, }; let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; for (const key in saved_settings) { @@ -102,6 +103,14 @@ settings.themeColor3BG = bg_code[v]; save(); }, - } + }, + 'Disable alarm functionality': { + value: settings.disableAlarms, + format: () => (settings.disableAlarms ? 'Yes' : 'No'), + onchange: () => { + settings.disableAlarms = !settings.disableAlarms; + save(); + }, + }, }); }) diff --git a/apps/lcars/metadata.json b/apps/lcars/metadata.json index 62a1c67db..6533ddd52 100644 --- a/apps/lcars/metadata.json +++ b/apps/lcars/metadata.json @@ -3,7 +3,7 @@ "name": "LCARS Clock", "shortName":"LCARS", "icon": "lcars.png", - "version":"0.23", + "version":"0.24", "readme": "README.md", "supports": ["BANGLEJS2"], "description": "Library Computer Access Retrieval System (LCARS) clock.", diff --git a/apps/limelight/ChangeLog b/apps/limelight/ChangeLog index 9db0e26c5..8fe3a0b2c 100644 --- a/apps/limelight/ChangeLog +++ b/apps/limelight/ChangeLog @@ -1 +1,2 @@ 0.01: first release +0.02: Tell clock widgets to hide. diff --git a/apps/limelight/limelight.app.js b/apps/limelight/limelight.app.js index 20d79deeb..84ded1039 100644 --- a/apps/limelight/limelight.app.js +++ b/apps/limelight/limelight.app.js @@ -10,6 +10,8 @@ * */ +Bangle.setUI('clock'); + g.clear(); const SETTINGS_FILE = "limelight.json"; @@ -259,5 +261,4 @@ Bangle.on('lcdPower',on=>{ } }); -Bangle.setUI('clock'); draw(); diff --git a/apps/limelight/metadata.json b/apps/limelight/metadata.json index 7c3736e1a..e484a2825 100644 --- a/apps/limelight/metadata.json +++ b/apps/limelight/metadata.json @@ -1,7 +1,7 @@ { "id": "limelight", "name": "Limelight", - "version": "0.01", + "version": "0.02", "description": "Simple analogue clock (with configurable fonts) based on the work of @Andreas_Rozek (Simple_Clock)", "icon": "limelight.png", "readme":"README.md", diff --git a/apps/locale/locales.js b/apps/locale/locales.js index de56503fd..7b3146e15 100644 --- a/apps/locale/locales.js +++ b/apps/locale/locales.js @@ -97,6 +97,25 @@ var locales = { day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday", // No translation for english... }, + "en_IE": { + lang: "en_IE", + decimal_point: ".", + thousands_sep: ",", + currency_symbol: "€", + int_curr_symbol: "EUR", + currency_first: true, + speed: 'kmh', + distance: { "0": "m", "1": "km" }, + temperature: '°C', + ampm: { 0: "am", 1: "pm" }, + timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + datePattern: { 0: "%d %b %Y", 1: "%d/%m/%Y" }, // 28 Feb 2020" // "28/03/2020"(short) + abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", + month: "January,February,March,April,May,June,July,August,September,October,November,December", + abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat", + day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday", + // No translation for english... + }, "en_NAV": { // navigation units nautical miles and knots lang: "en_NAV", decimal_point: ".", @@ -141,7 +160,7 @@ var locales = { currency_symbol: "$", currency_first: true, int_curr_symbol: "USD", speed: "mph", - distance: { 0: "yd", 1: "mi" }, + distance: { 0: "m", 1: "mi" }, temperature: "°F", ampm: { 0: "am", 1: "pm" }, timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, diff --git a/apps/ltherm/ChangeLog b/apps/ltherm/ChangeLog new file mode 100644 index 000000000..374bd5cd6 --- /dev/null +++ b/apps/ltherm/ChangeLog @@ -0,0 +1,2 @@ +0.01: New App! +0.02: Use barometer temperature if available diff --git a/apps/ltherm/app.js b/apps/ltherm/app.js index 2cbf26e5f..552420a85 100644 --- a/apps/ltherm/app.js +++ b/apps/ltherm/app.js @@ -1,11 +1,10 @@ -function drawTemperature() { +function drawTemperature(h) { g.reset(1).clearRect(0,24,g.getWidth(),g.getHeight()); g.setFont("6x8",2).setFontAlign(0,0); var x = g.getWidth()/2; var y = g.getHeight()/2 + 10; g.drawString("Temp", x, y - 45); g.setFontVector(70).setFontAlign(0,0); - var h = E.getTemperature(); if (avg.length < 10) { avg[avg.length] = h; } else { @@ -18,7 +17,13 @@ function drawTemperature() { } const avg = []; setInterval(function() { - drawTemperature(); + if (Bangle.getPressure){ + Bangle.getPressure().then((p)=>{ + drawTemperature(p.temperature); + }); + } else { + drawTemperature(E.getTemperature()); + } }, 2000); E.showMessage(/*LANG*/"Loading..."); Bangle.loadWidgets(); diff --git a/apps/ltherm/metadata.json b/apps/ltherm/metadata.json index 83b295a3d..58c1f613c 100644 --- a/apps/ltherm/metadata.json +++ b/apps/ltherm/metadata.json @@ -2,7 +2,7 @@ "id": "ltherm", "name": "Localized Thermometer", "shortName": "Thermometer", - "version": "0.01", + "version": "0.02", "description": "Displays the current temperature in localized units.", "icon": "thermf.png", "tags": "tool", diff --git a/apps/macwatch/ChangeLog b/apps/macwatch/ChangeLog new file mode 100644 index 000000000..221d3fb57 --- /dev/null +++ b/apps/macwatch/ChangeLog @@ -0,0 +1 @@ +0.01: Created my first BangleJS app! diff --git a/apps/macwatch/README.md b/apps/macwatch/README.md new file mode 100644 index 000000000..1b7add3e5 --- /dev/null +++ b/apps/macwatch/README.md @@ -0,0 +1,26 @@ +# MacWatch + +A very simple clock using big numbers in the original Macintosh Chicago font. + +Touch the screen to show the date in numerical format. + +Touch the screen again to revert to the time. + +Time updates every 15 seconds. + +## In dark mode + +![](screenshot-dark-time.png) +![](screenshot-dark-date.png) + +## In light mode + +![](screenshot-light-time.png) +![](screenshot-light-date.png) + +## The watch in use +![](photo-watch-in-use.jpg) + +## Creator + +Written by Giles Booth | [twitter](https://twitter.com/blogmywiki) | [blog](http://www.suppertime.co.uk/blogmywiki/) diff --git a/apps/macwatch/app-icon.js b/apps/macwatch/app-icon.js new file mode 100644 index 000000000..f4592b508 --- /dev/null +++ b/apps/macwatch/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkBIf4AOh//AggADC8YRCBIwXlI4oX/C5QWOC74WPC/4X/C6AA/AH4AmXwK5DAobBNC60P+DqHFxwXWCJAWRC44vUPwouRJKQXWBxAXlOAIgQbBDeLFxAXXAAa/RC64A/AH4AKA==")) diff --git a/apps/macwatch/app.js b/apps/macwatch/app.js new file mode 100644 index 000000000..fb9712a36 --- /dev/null +++ b/apps/macwatch/app.js @@ -0,0 +1,54 @@ +var font = atob("f3/gMB/7+AAAACA///AAAAAAQcHhsZ+LhAAAgUhsPh38eAAADAoJCI///BAA8XhkMhn8eAAAPz/0Mhn4eAAAgEAh8f+HgAAAb3/kMh/7eAAAeH5hML/z8AAAAAADYbAAAAAA"); + +function draw() { + g.reset(); + g.setFontCustom(font, 48, 8, 1801); + g.setFontAlign(0, -1, 0); + if (showDate) { + if (g.theme.dark) { + g.setColor("#00ffff"); // cyan date numbers for dark mode + } + else { + g.setColor("#0000ff"); // blue date numbers for light mode + } + line1 = ("0"+(new Date()).getDate()).substr(-2); + line2 = ("0"+((new Date()).getMonth()+1)).substr(-2); + } + else { + if (g.theme.dark) { + g.setColor(1,1,1); // white time numbers for dark mode + } + else { + g.setColor(0); // black time numbers for light mode + } + var d = new Date(); + var da = d.toString().split(" "); + line1 = da[4].substr(0,2); + line2 = da[4].substr(3,2); + } + g.drawString(line1, 95, 30, true); + g.drawString(line2, 95, 106, true); + } + +// handle switch display on by pressing BTN1 +Bangle.on('lcdPower', function(on) { + if (on) draw(); +}); + +Bangle.on('touch', function(on) { + if (on) { + showDate = !showDate; // toggle date mode on and off + draw(); + } +}); + +g.clear(); +var showDate = 0; +setInterval(draw, 15000); // refresh display every 15s +draw(); + +// Show launcher when button pressed +Bangle.setUI("clock"); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/macwatch/app.png b/apps/macwatch/app.png new file mode 100644 index 000000000..9618b8b50 Binary files /dev/null and b/apps/macwatch/app.png differ diff --git a/apps/macwatch/metadata.json b/apps/macwatch/metadata.json new file mode 100644 index 000000000..0a2558380 --- /dev/null +++ b/apps/macwatch/metadata.json @@ -0,0 +1,17 @@ +{ "id": "macwatch", + "name": "MacWatch", + "shortName":"MacWatch", + "icon": "app.png", + "version":"0.01", + "description": "Simple clock with classic Mac font", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "screenshots": [{"url":"screenshot-dark-date.png"}], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"macwatch.app.js","url":"app.js"}, + {"name":"macwatch.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/macwatch/photo-watch-in-use.jpg b/apps/macwatch/photo-watch-in-use.jpg new file mode 100644 index 000000000..16ae57c3e Binary files /dev/null and b/apps/macwatch/photo-watch-in-use.jpg differ diff --git a/apps/macwatch/screenshot-dark-date.png b/apps/macwatch/screenshot-dark-date.png new file mode 100644 index 000000000..68d1d80f9 Binary files /dev/null and b/apps/macwatch/screenshot-dark-date.png differ diff --git a/apps/macwatch/screenshot-dark-time.png b/apps/macwatch/screenshot-dark-time.png new file mode 100644 index 000000000..dc9e32df2 Binary files /dev/null and b/apps/macwatch/screenshot-dark-time.png differ diff --git a/apps/macwatch/screenshot-light-date.png b/apps/macwatch/screenshot-light-date.png new file mode 100644 index 000000000..e4bee235f Binary files /dev/null and b/apps/macwatch/screenshot-light-date.png differ diff --git a/apps/macwatch/screenshot-light-time.png b/apps/macwatch/screenshot-light-time.png new file mode 100644 index 000000000..45abff84a Binary files /dev/null and b/apps/macwatch/screenshot-light-time.png differ diff --git a/apps/macwatch2/ChangeLog b/apps/macwatch2/ChangeLog new file mode 100644 index 000000000..5eafe64d2 --- /dev/null +++ b/apps/macwatch2/ChangeLog @@ -0,0 +1,5 @@ +0.01: Created first version of the app with numeric date, only works in light mode +0.02: New icon, shimmied date right a bit +0.03: Incorporated improvements from Peer David for accuracy, fix dark mode, widgets run in background +0.04: Changed clock to use 12/24 hour format based on locale +0.05: Tell clock widgets to hide. diff --git a/apps/macwatch2/IMG_3782 crop.JPG b/apps/macwatch2/IMG_3782 crop.JPG new file mode 100644 index 000000000..2c0bc375a Binary files /dev/null and b/apps/macwatch2/IMG_3782 crop.JPG differ diff --git a/apps/macwatch2/README.md b/apps/macwatch2/README.md new file mode 100644 index 000000000..4fdb9b6e9 --- /dev/null +++ b/apps/macwatch2/README.md @@ -0,0 +1,19 @@ +# MacWatch2 + +A clock inspired by Susan Kare's original 1984 Macintosh desktop design. + +Ideas for development: +- [x] fix dark mode either by forcing black on white or doing proper inverse display and text +- [ ] date in text format +- [ ] tap to load different info in window +- [ ] unlock to show seconds, perhaps flip time and date? +- [ ] incorporate widgets somehow either by leaving space or adding Chicago font widgets of my own in top bar + +![](screenshot.png) + +![](IMG_3782%20crop.JPG) + +## Creator + +Written by Giles Booth | [twitter](https://twitter.com/blogmywiki) | [blog](http://www.suppertime.co.uk/blogmywiki/) +Improvements for accuracy, dark mode and widgets running in background by [Peer David](https://gist.github.com/peerdavid) diff --git a/apps/macwatch2/app-icon.js b/apps/macwatch2/app-icon.js new file mode 100644 index 000000000..be7d5e060 --- /dev/null +++ b/apps/macwatch2/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgn/AH4A/AH4AfdIoX/C/4X4CIwYQC6wPIDBwX/C6IYNC7TVFC9IEFO6YX/VBQWPC7CmDVIoX/C/4X2AH4A/ADwA==")) diff --git a/apps/macwatch2/app.js b/apps/macwatch2/app.js new file mode 100644 index 000000000..4556e06ac --- /dev/null +++ b/apps/macwatch2/app.js @@ -0,0 +1,64 @@ +// 68k Mac Finder desktop themed clock +// by Giles Booth @blogmywiki +// improvements by Peer David + +var img = require("heatshrink").decompress(atob("2GwgP4C6cf8AVTg/ACqcDwADBDCMBCoICCCqACEj8zAwXwmcYgEGswYHhxwBjEDGocwCoVgQxHwCoMzjwVBwPzngrCnlmDAsfNoIVBIQMBwZBEAAIVIjwVD8YVNIIc/FY9+CpcwCo9gCo0PQYUzmIVGo1is1ACokGNoaDC+PzhkAg+Gnl/aiIA/AD//AClVACmqACgr/Fd2vVqP+FYNUbKMNFYOsCqMOFa+t/f/35LC/AODK43uFYUCgGACAUB/IFDFZP6gArEsArTgFhz9w+ArRsOZzOYFaQVCFan4FaiFHFZuIFaeYQZbbVf5LbK1gVRhwrX15MGABX+K/4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Fe+v/4AQ/wrBq4VR/orBAClVACgr/Ff4r/AAmr6or/q/6Fae/A=")); + +var font = atob("f3/gMB/7+AAAACA///AAAAAAQcHhsZ+LhAAAgUhsPh38eAAADAoJCI///BAA8XhkMhn8eAAAPz/0Mhn4eAAAgEAh8f+HgAAAb3/kMh/7eAAAeH5hML/z8AAAAAADYbAAAAAA"); + +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() { + queueDraw(); + + // Fix theme to "light" + g.setTheme({bg:"#fff", fg:"#000", dark:false}).clear(); + g.reset(); + g.drawImage(img,0,0); + + g.setFontCustom(font, 48, 8, 1033); + g.setFontAlign(0, -1, 0); + g.setColor(0,0,0); + var d = new Date(); + var dt = require("locale").time(d, 1); + var hh = dt.split(":")[0]; + var mm = dt.split(":")[1]; + g.drawString(hh, 52, 65, true); + g.drawString(mm, 132, 65, true); + g.drawString(':', 93,65); + dd = ("0"+(new Date()).getDate()).substr(-2); + mo = ("0"+((new Date()).getMonth()+1)).substr(-2); + yy = ("0"+((new Date()).getFullYear())).substr(-2); + g.setFontCustom(font, 48, 8, 521); + g.drawString(dd + ':' + mo + ':' + yy, 88, 120, true); + + // Hide widgets + for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} +} + + +// handle switch display on by pressing BTN1 +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +Bangle.setUI("clock"); + +// Load widgets but hide them +Bangle.loadWidgets(); +draw(); diff --git a/apps/macwatch2/app.png b/apps/macwatch2/app.png new file mode 100644 index 000000000..efcdead19 Binary files /dev/null and b/apps/macwatch2/app.png differ diff --git a/apps/macwatch2/metadata.json b/apps/macwatch2/metadata.json new file mode 100644 index 000000000..14c48c749 --- /dev/null +++ b/apps/macwatch2/metadata.json @@ -0,0 +1,17 @@ +{ "id": "macwatch2", + "name": "MacWatch2", + "shortName":"MacWatch2", + "icon": "app.png", + "version":"0.05", + "description": "Classic Mac Finder clock", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "screenshots": [{"url":"screenshot.png"}], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"macwatch2.app.js","url":"app.js"}, + {"name":"macwatch2.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/macwatch2/screenshot.png b/apps/macwatch2/screenshot.png new file mode 100644 index 000000000..732b29d7f Binary files /dev/null and b/apps/macwatch2/screenshot.png differ diff --git a/apps/matrixclock/ChangeLog b/apps/matrixclock/ChangeLog index 52f705301..02f7d109b 100644 --- a/apps/matrixclock/ChangeLog +++ b/apps/matrixclock/ChangeLog @@ -1,4 +1,7 @@ 0.01: Initial Release 0.02: Support for Bangle 2 0.03: Keep the date from being overwritten, use correct colour from theme for clearing -0.04: Removed "wake LCD on face-up"-feature: A watch-face should not set things like "wake LCD on face-up". +0.04: Removed "wake LCD on face-up"-feature: A watch-face should not set things like "wake LCD on face-up". +0.05: Added support to other color themes (other then black) +0.06: Added support for 24 hour clock enabled from settings +0.07: Tell clock widgets to hide. diff --git a/apps/matrixclock/README.md b/apps/matrixclock/README.md index 010524b60..01aef6544 100644 --- a/apps/matrixclock/README.md +++ b/apps/matrixclock/README.md @@ -2,6 +2,25 @@ ![](app.png) +## Settings +Please use the setting->App->Matrix Clock Menu to change the settings + +| Setting | Description | +|-------------|--------------------------------------------------------------------------------------------------------------------| +| Color | By default set to **'theme'** to follow the theme colors. Selector also offers a selection of other colour schemes | +| Time Format | Choose between 12 hour and 24 hour time format | +| Intensity | Changes the number of matrix streams that are falling | + +## Colour Themes + +Some of the colours schemes that are available from the settings screen + +| ![](matrix_green_on_black.jpg) | ![](matrix_black_on_white.jpg) | ![](matrix_white_on_gray.jpg) | +|-------------------------------|-------------------------------|-----| +| green on black | white on black | white on gray | + + + ## Requests Please reach out to adrian@adriankirk.com if you have feature requests or notice bugs. diff --git a/apps/matrixclock/matrix_black_on_white.jpg b/apps/matrixclock/matrix_black_on_white.jpg new file mode 100644 index 000000000..545545c65 Binary files /dev/null and b/apps/matrixclock/matrix_black_on_white.jpg differ diff --git a/apps/matrixclock/matrix_green_on_black.jpg b/apps/matrixclock/matrix_green_on_black.jpg new file mode 100644 index 000000000..7caa38bec Binary files /dev/null and b/apps/matrixclock/matrix_green_on_black.jpg differ diff --git a/apps/matrixclock/matrix_white_on_gray.jpg b/apps/matrixclock/matrix_white_on_gray.jpg new file mode 100644 index 000000000..dc9d2f3ba Binary files /dev/null and b/apps/matrixclock/matrix_white_on_gray.jpg differ diff --git a/apps/matrixclock/matrixclock.js b/apps/matrixclock/matrixclock.js index 2e4ba1ac4..9618c3a47 100644 --- a/apps/matrixclock/matrixclock.js +++ b/apps/matrixclock/matrixclock.js @@ -3,24 +3,107 @@ * * Matrix Clock * - * A simple clock inspired by the movie. - * Text shards move down the screen as a background to the + * A simple clock inspired by the movie. + * Text shards move down the screen as a background to the * time and date **/ const Locale = require('locale'); -const SHARD_COLOR =[0,1.0,0]; +const PREFERENCE_FILE = "matrixclock.settings.json"; +const settings = Object.assign({color: "theme", time_format: '12 hour', intensity: 'light'}, + require('Storage').readJSON(PREFERENCE_FILE, true) || {}); + +var format_time; +if(settings.time_format == '24 hour'){ + format_time = (t) => format_time_24_hour(t); +} else { + format_time = (t) => format_time_12_hour(t); +} + +const colors = { + 'gray' :[0.5,0.5,0.5], + 'green': [0,1.0,0], + 'red' : [1.0,0.0,0.0], + 'blue' : [0.0,0.0,1.0], + 'black': [0.0,0.0,0.0], + 'purple': [1.0,0.0,1.0], + 'white': [1.0,1.0,1.0], + 'yellow': [1.0,1.0,0.0] +}; + +const color_schemes = { + 'black on white': ['white','black'], + 'green on white' : ['white','green'], + 'green on black' : ['black','green'], + 'red on black' : ['black', 'red'], + 'red on white' : ['white', 'red'], + 'white on gray' : ['gray', 'white'], + 'white on red' : ['red', 'white'], + 'white on blue': ['blue','white'], + 'white on purple': ['purple', 'white'] +}; + +function int2Color(color_int){ + var blue_int = color_int & 31; + var blue = (blue_int)/31.0; + + var green_int = (color_int >> 5) & 31; + var green = (green_int)/31.0; + + var red_int = (color_int >> 11) & 31; + var red = red_int/ 31.0; + return [red,green,blue]; +} + +var fg_color = colors.black; +var bg_color = colors.white; + +// now lets deal with the settings +if(settings.color === "theme"){ + bg_color = int2Color(g.theme.bg); + if(g.theme.bg === 0) { + fg_color = colors.green; + } else { + fg_color = int2Color(g.theme.fg); + } +} else { + var color_scheme = color_schemes[settings.color]; + bg_color = colors[color_scheme[0]]; + fg_color = colors[color_scheme[1]]; + g.setBgColor(bg_color[0],bg_color[1],bg_color[2]); +} +if(fg_color === undefined) + fg_color = colors.black; + +if(bg_color === undefined) + bg_color = colors.white; + +const intensity_schemes = { + 'light': 3, + 'medium': 4, + 'high': 5 +}; + +var noShards = intensity_schemes.light; +if(settings.intensity !== undefined){ + noShards = intensity_schemes[settings.intensity]; +} +if(noShards === undefined){ + noShards = intensity_schemes.light; +} + const SHARD_FONT_SIZE = 12; const SHARD_Y_START = 30; + const w = g.getWidth(); /** -* The text shard object is responsible for creating the -* shards of text that move down the screen. As the -* shard moves down the screen the latest character added -* is brightest with characters being coloured darker and darker -* going back to the eldest -*/ + * The text shard object is responsible for creating the + * shards of text that move down the screen. As the + * shard moves down the screen the latest character added + * is brightest with characters being coloured darker and darker + * going back to the eldest + */ class TextShard { constructor(x,y,length){ @@ -34,44 +117,46 @@ class TextShard { this.txt = []; } /** - * The add method call adds another random character to - * the chain - */ + * The add method call adds another random character to + * the chain + */ add(){ this.txt.push(randomChar()); } /** - * The show method displays the latest shard image to the - * screen with the following rules: - * - latest addition is brightest, oldest is darker - * - display up to defined length of characters only - * of the shard to save cpu - */ + * The show method displays the latest shard image to the + * screen with the following rules: + * - latest addition is brightest, oldest is darker + * - display up to defined length of characters only + * of the shard to save cpu + */ show(){ g.setFontAlign(-1,-1,0); for(var i=0; i this.length - 2){ color_strength = 0; - } - g.setColor(color_strength*SHARD_COLOR[0], - color_strength*SHARD_COLOR[1], - color_strength*SHARD_COLOR[2]); + } + var bg_color_strength = 1 - color_strength; + g.setColor(Math.abs(color_strength*fg_color[0] - bg_color_strength*bg_color[0]), + Math.abs(color_strength*fg_color[1] - bg_color_strength*bg_color[1]), + Math.abs(color_strength*fg_color[2] - bg_color_strength*bg_color[2]) + ); g.setFont("Vector",SHARD_FONT_SIZE); - g.drawString(this.txt[idx], this.x, this.y + idx*SHARD_FONT_SIZE); + g.drawString(this.txt[idx], this.x, this.y + idx*SHARD_FONT_SIZE); } } /** - * Method tests to see if any part of the shard chain is still - * visible on the screen - */ + * Method tests to see if any part of the shard chain is still + * visible on the screen + */ isVisible(){ - return (this.y + (this.txt.length - this.length - 2)*SHARD_FONT_SIZE < g.getHeight()); + return (this.y + (this.txt.length - this.length - 2)*SHARD_FONT_SIZE < g.getHeight()); } /** - * resets the shard back to the top of the screen - */ + * resets the shard back to the top of the screen + */ reset(){ this.y = SHARD_Y_START; this.txt = []; @@ -79,8 +164,8 @@ class TextShard { } /** -* random character chooser to be called by the shard when adding characters -*/ + * random character chooser to be called by the shard when adding characters + */ const CHAR_CODE_START = 33; const CHAR_CODE_LAST = 126; const CHAR_CODE_LENGTH = CHAR_CODE_LAST - CHAR_CODE_START; @@ -90,11 +175,10 @@ function randomChar(){ // Now set up the shards // we are going to have a limited no of shards (to save cpu) -// but randomize the x value and length every reset to make it look as if there +// but randomize the x value and length every reset to make it look as if there // are more var shards = []; -const NO_SHARDS = 3; -const channel_width = g.getWidth()/NO_SHARDS; +const channel_width = g.getWidth()/noShards; function shard_x(i){ return i*channel_width + Math.random() * channel_width; @@ -104,7 +188,7 @@ function shard_length(){ return Math.floor(Math.random()*5) + 3; } -for(var i=0; i 99 || value < 0) - throw "must be between in range 0-99"; - if(value < 10) - return "0" + value.toString(); - else - return value.toString(); + var value = (num | 0); + if(value > 99 || value < 0) + throw "must be between in range 0-99"; + if(value < 10) + return "0" + value.toString(); + else + return value.toString(); } // The interval reference for updating the clock @@ -215,12 +304,12 @@ function startTimers(){ clearTimers(); if (Bangle.isLCDOn()) { intervalRef = setInterval(() => { - if (!shouldRedraw()) { - //console.log("draw clock callback - skipped redraw"); - } else { - draw_clock(); - } - }, 100 + if (!shouldRedraw()) { + //console.log("draw clock callback - skipped redraw"); + } else { + draw_clock(); + } + }, 100 ); draw_clock(); } else { @@ -239,11 +328,9 @@ Bangle.on('lcdPower', (on) => { } }); +Bangle.setUI("clock"); g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); startTimers(); -Bangle.setUI("clock"); - - diff --git a/apps/matrixclock/matrixclock.settings.js b/apps/matrixclock/matrixclock.settings.js new file mode 100644 index 000000000..1f22a045f --- /dev/null +++ b/apps/matrixclock/matrixclock.settings.js @@ -0,0 +1,52 @@ +(function(back) { + const PREFERENCE_FILE = "matrixclock.settings.json"; + var settings = Object.assign({color : "theme", time_format: '12 hour', intensity: "light"}, + require('Storage').readJSON(PREFERENCE_FILE, true) || {}); + + console.log("loaded:" + JSON.stringify(settings)); + + function writeSettings() { + console.log("saving:" + JSON.stringify(settings)); + require('Storage').writeJSON(PREFERENCE_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); + } + + // Show the menu + E.showMenu({ + "" : { "title" : "Matrix Clock" }, + "< Back" : () => back(), + "Colour": stringInSettings("color", ['theme', + 'black on white', + 'green on white', + 'green on black', + 'red on white', + 'white on gray', + 'white on red', + 'white on blue' + ]), + "Time Format": stringInSettings("time_format", ['12 hour','24 hour']), + "Intensity": stringInSettings("intensity", ['light', + 'medium', + 'high']) + }); +}) \ No newline at end of file diff --git a/apps/matrixclock/metadata.json b/apps/matrixclock/metadata.json index 122cee3a1..718b878e5 100644 --- a/apps/matrixclock/metadata.json +++ b/apps/matrixclock/metadata.json @@ -1,10 +1,10 @@ { "id": "matrixclock", "name": "Matrix Clock", - "version": "0.04", + "version": "0.07", "description": "inspired by The Matrix, a clock of the same style", "icon": "matrixclock.png", - "screenshots": [{"url":"screenshot_matrix.png"}], + "screenshots": [{"url":"matrix_green_on_black.jpg"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], @@ -12,6 +12,8 @@ "allow_emulator": true, "storage": [ {"name":"matrixclock.app.js","url":"matrixclock.js"}, + { "name":"matrixclock.settings.js","url":"matrixclock.settings.js"}, {"name":"matrixclock.img","url":"matrixclock-icon.js","evaluate":true} - ] + ], + "data": [{"name": "matrixclock.settings.json"}] } diff --git a/apps/matrixclock/screenshot_matrix.png b/apps/matrixclock/screenshot_matrix.png deleted file mode 100644 index 3d843848c..000000000 Binary files a/apps/matrixclock/screenshot_matrix.png and /dev/null differ diff --git a/apps/mclock/ChangeLog b/apps/mclock/ChangeLog index 05b422406..e3b164942 100644 --- a/apps/mclock/ChangeLog +++ b/apps/mclock/ChangeLog @@ -5,3 +5,4 @@ Fix issue where first digit could get stuck going from "2x:xx" to " x:xx" (fix #365) 0.06: Support 12 hour time 0.07: Use Bangle.setUI for button/launcher handling +0.08: Tell clock widgets to hide. diff --git a/apps/mclock/clock-morphing.js b/apps/mclock/clock-morphing.js index f1254860b..bd133206e 100644 --- a/apps/mclock/clock-morphing.js +++ b/apps/mclock/clock-morphing.js @@ -209,6 +209,9 @@ Bangle.on('lcdPower',function(on) { } }); +// Show launcher when button pressed +Bangle.setUI("clock"); + g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); @@ -216,5 +219,3 @@ Bangle.drawWidgets(); timeInterval = setInterval(showTime, 1000); showTime(); -// Show launcher when button pressed -Bangle.setUI("clock"); diff --git a/apps/mclock/metadata.json b/apps/mclock/metadata.json index 513f823a1..a7d56f752 100644 --- a/apps/mclock/metadata.json +++ b/apps/mclock/metadata.json @@ -1,7 +1,7 @@ { "id": "mclock", "name": "Morphing Clock", - "version": "0.07", + "version": "0.08", "description": "7 segment clock that morphs between minutes and hours", "icon": "clock-morphing.png", "type": "clock", diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index 759d32b05..262cba1fa 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -52,6 +52,18 @@ 0.37: Now use the setUI 'back' icon in the top left rather than specific buttons/menu items 0.38: Add telegram foss handling 0.39: Set default color for message icons according to theme - Don't turn on the screen after unread timeout expires (#1873) -0.40: Improved buzzing implementation when receiving notifications - +0.40: Use default Bangle formatter for booleans +0.41: Add notification icons in the widget +0.42: Fix messages ignoring "Vibrate: Off" setting +0.43: Add new Icons (Airbnb, warnwetter) +0.44: Separate buzz pattern for incoming calls +0.45: Added new app colors and icons +0.46: Add 'Vibrate Timer' option to set how long to vibrate for, and fix Repeat:off + Fix message removal from widget bar (previously caused exception as .hide has been removed) +0.47: Add new Icons (Nextbike, Mattermost, etc.) +0.48: When getting new message from the clock, only buzz once the messages app is loaded +0.49: Change messages icon (to fit within 24px) and ensure widget renders icons centrally +0.50: Add `getMessages` and `status` functions to library + Option to disable auto-open of messages + Option to make message icons monochrome (not colored) + messages widget buzz now returns a promise \ No newline at end of file diff --git a/apps/messages/README.md b/apps/messages/README.md index da2701f35..2e583d1c2 100644 --- a/apps/messages/README.md +++ b/apps/messages/README.md @@ -12,8 +12,11 @@ You can change settings by going to the global `Settings` app, then `App Setting and `Messages`: * `Vibrate` - This is the pattern of buzzes that should be made when a new message is received +* `Vibrate for calls` - This is the pattern of buzzes that should be made when an incoming call is received * `Repeat` - How often should buzzes repeat - the default of 4 means the Bangle will buzz every 4 seconds -* `Unread Timer` - When a new message is received we go into the Messages app. +* `Vibrate Timer` - When a new message is received when in a non-clock app, we display the message icon and +buzz every `Repeat` seconds. This is how long we continue to do that. +* `Unread Timer` - When a new message is received when showing the clock we go into the Messages app. If there is no user input for this amount of time then the app will exit and return to the clock where a ringing bell will be shown in the Widget bar. * `Min Font` - The minimum font size used when displaying messages on the screen. A bigger font @@ -22,12 +25,13 @@ it starts getting clipped. * `Auto-Open Music` - Should the app automatically open when the phone starts playing music? * `Unlock Watch` - Should the app unlock the watch when a new message arrives, so you can touch the buttons at the bottom of the app? * `Flash Icon` - Toggle flashing of the widget icon. +* `Widget messages` - The maximum amount of message icons to show on the widget. ## New Messages When a new message is received: -* If you're in an app, the Bangle will buzz and a 'new message' icon appears in the Widget bar. You can tap this bar to view the message. +* If you're in an app, the Bangle will buzz and a message icon appears in the Widget bar. You can tap this icon to view the message. * If you're in a clock, the Messages app will automatically start and show the message When a message is shown, you'll see a screen showing the message title and text. diff --git a/apps/messages/app-newmessage.js b/apps/messages/app-newmessage.js new file mode 100644 index 000000000..328927c70 --- /dev/null +++ b/apps/messages/app-newmessage.js @@ -0,0 +1,5 @@ +/* Called when we have a new message when we're in the clock... +BUZZ_ON_NEW_MESSAGE is set so when messages.app.js loads it knows +that it should buzz */ +global.BUZZ_ON_NEW_MESSAGE = true; +eval(require("Storage").read("messages.app.js")); diff --git a/apps/messages/app.js b/apps/messages/app.js index d4540b797..40dff9635 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -48,13 +48,13 @@ we should start a timeout for settings.unreadTimeout to return to the clock. */ var unreadTimeout; /// List of all our messages -var MESSAGES = require("Storage").readJSON("messages.json",1)||[]; +var MESSAGES = require("messages").getMessages(); if (!Array.isArray(MESSAGES)) MESSAGES=[]; var onMessagesModified = function(msg) { // TODO: if new, show this new one if (msg && msg.id!=="music" && msg.new && active!="map" && !((require('Storage').readJSON('setting.json', 1) || {}).quiet)) { - if (WIDGETS["messages"]) WIDGETS["messages"].buzz(); + if (WIDGETS["messages"]) WIDGETS["messages"].buzz(msg.src); else Bangle.buzz(); } if (msg && msg.id=="music") { @@ -286,6 +286,7 @@ function showMessage(msgid) { } } function goBack() { + layout = undefined; msg.new = false; saveMessages(); // read mail cancelReloadTimeout(); // don't auto-reload to clock now checkMessages({clockIfNoMsg:1,clockIfAllRead:0,showMsgIfUnread:0,openMusic:openMusic}); @@ -353,8 +354,18 @@ function checkMessages(options) { // we have >0 messages var newMessages = MESSAGES.filter(m=>m.new&&m.id!="music"); // If we have a new message, show it - if (options.showMsgIfUnread && newMessages.length) - return showMessage(newMessages[0].id); + if (options.showMsgIfUnread && newMessages.length) { + showMessage(newMessages[0].id); + // buzz after showMessage, so beingbusy during layout doesn't affect the buzz pattern + if (global.BUZZ_ON_NEW_MESSAGE) { + // this is set if we entered the messages app by loading `messages.new.js` + // ... but only buzz the first time we view a new message + global.BUZZ_ON_NEW_MESSAGE = false; + // messages.buzz respects quiet mode - no need to check here + WIDGETS.messages.buzz(newMessages[0].src); + } + return; + } // no new messages: show playing music? (only if we have playing music to show) if (options.openMusic && MESSAGES.some(m=>m.id=="music" && m.track && m.state=="play")) return showMessage('music'); diff --git a/apps/messages/lib.js b/apps/messages/lib.js index 5e1572384..d8599c93d 100644 --- a/apps/messages/lib.js +++ b/apps/messages/lib.js @@ -40,12 +40,12 @@ exports.pushMessage = function(event) { require("Storage").writeJSON("messages.json",messages); // if in app, process immediately if (inApp) return onMessagesModified(mIdx<0 ? {id:event.id} : messages[mIdx]); - // if we've removed the last new message, hide the widget - if (event.t=="remove" && !messages.some(m=>m.new)) { - if (global.WIDGETS && WIDGETS.messages) WIDGETS.messages.hide(); + // update the widget icons shown + if (global.WIDGETS && WIDGETS.messages) WIDGETS.messages.update(messages,true); // if no new messages now, make sure we don't load the messages app - if (exports.messageTimeout && !messages.some(m=>m.new)) - clearTimeout(exports.messageTimeout); + if (event.t=="remove" && exports.messageTimeout && !messages.some(m=>m.new)) { + clearTimeout(exports.messageTimeout); + delete exports.messageTimeout; } // ok, saved now if (event.id=="music" && Bangle.CLOCK && messages[mIdx].new && openMusic()) { @@ -62,39 +62,25 @@ exports.pushMessage = function(event) { var quiet = (require('Storage').readJSON('setting.json',1)||{}).quiet; var appSettings = require('Storage').readJSON('messages.settings.json',1)||{}; var unlockWatch = appSettings.unlockWatch; - var quietNoAutOpn = appSettings.quietNoAutOpn; - delete appSettings; // don't auto-open messages in quiet mode if quietNoAutOpn is true - if(quiet && quietNoAutOpn) { - loadMessages = false; - } - if (!quiet && loadMessages && unlockWatch != false){ - Bangle.setLocked(false); - Bangle.setLCDPower(1); // turn screen on - } + if((quiet && appSettings.quietNoAutOpn) || appSettings.noAutOpn) + loadMessages = false; + delete appSettings; // after a delay load the app, to ensure we have all the messages if (exports.messageTimeout) clearTimeout(exports.messageTimeout); exports.messageTimeout = setTimeout(function() { exports.messageTimeout = undefined; - var cont = function() { - // if we're in a clock or it's important, go straight to messages app - if (loadMessages){ - return load("messages.app.js"); + // if we're in a clock or it's important, go straight to messages app + if (loadMessages){ + if(!quiet && unlockWatch){ + Bangle.setLocked(false); + Bangle.setLCDPower(1); // turn screen on } - if (global.WIDGETS && WIDGETS.messages) { // show messages if widgets are loaded - WIDGETS.messages.show(); - } - }; - if (quiet) { - //Be quiet and cont()inue displaying. - cont(); - } else { - //We have to wait for buzzing to complete before cont()inuing - if(global.WIDGETS && WIDGETS.messages) - WIDGETS.messages.buzz().then(()=>cont()); - else - Bangle.buzz().then(()=>cont()); + // we will buzz when we enter the messages app + return load("messages.new.js"); } + if (!quiet && (!global.WIDGETS || !WIDGETS.messages)) return Bangle.buzz(); // no widgets - just buzz once to let someone know + if (global.WIDGETS && WIDGETS.messages) WIDGETS.messages.update(messages); }, 500); } /// Remove all messages @@ -111,54 +97,94 @@ exports.clearAll = function(event) { if (inApp) return onMessagesModified(); // if we have a widget, update it if (global.WIDGETS && WIDGETS.messages) - WIDGETS.messages.hide(); + WIDGETS.messages.update(messages); } +/** + * @returns {array} All messages + */ +exports.getMessages = function() { + if ("undefined"!=typeof MESSAGES) return MESSAGES; // loaded/managed by app + return require("Storage").readJSON("messages.json",1)||[]; +} + +/** + * Check if there are any messages + * @returns {string} "new"/"old"/"none" + */ + exports.status = function() { + try { + let status= "none"; + for(const m of exports.getMessages()) { + if (["music", "map"].includes(m.id)) continue; + if (m.new) return "new"; + status = "old"; + } + return status; + } catch(e) { + return "none"; // don't bother e.g. the widget with errors + } +}; + exports.getMessageImage = function(msg) { /* - * icons should be 24x24px with 1bpp colors and 'Transparency to Color' + * icons should be 24x24px or less with 1bpp colors and 'Transparency to Color' * http://www.espruino.com/Image+Converter */ if (msg.img) return atob(msg.img); - var s = (msg.src||"").toLowerCase(); + const s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase(); + if (s=="airbnb") return atob("GBgBAAAAAAAAAAAAADwAAH4AAGYAAMMAAIEAAYGAAYGAAzzAA2bABmZgBmZgDGYwDDwwCDwQCBgQDDwwB+fgA8PAAAAAAAAAAAAA"); if (s=="alarm" || s =="alarmclockreceiver") return atob("GBjBAP////8AAAAAAAACAEAHAOAefng5/5wTgcgHAOAOGHAMGDAYGBgYGBgYGBgYGBgYDhgYBxgMATAOAHAHAOADgcAB/4AAfgAAAAAAAAA="); if (s=="bibel") return atob("GBgBAAAAA//wD//4D//4H//4H/f4H/f4H+P4H4D4H4D4H/f4H/f4H/f4H/f4H/f4H//4H//4H//4GAAAEAAAEAAACAAAB//4AAAA"); - if (s=="calendar") return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA=="); + if (s=="bring") return atob("GBgBAAAAAAAAAAAAAAAAAHwAAFoAAf+AA/+AA/+AA/+AA/eAA+eAA0+AAx+AA7+AA/+AA//AA/+AAf8AAAIAAAAAAAAAAAAAAAAA"); + if (s=="calendar" || s=="etar") return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA=="); if (s=="corona-warn") return atob("GBgBAAAAABwAAP+AAf/gA//wB/PwD/PgDzvAHzuAP8EAP8AAPAAAPMAAP8AAH8AAHzsADzuAB/PAB/PgA//wAP/gAH+AAAwAAAAA"); if (s=="discord") return atob("GBgBAAAAAAAAAAAAAIEABwDgDP8wH//4H//4P//8P//8P//8Pjx8fhh+fzz+f//+f//+e//ePH48HwD4AgBAAAAAAAAAAAAAAAAA"); if (s=="facebook" || s=="messenger") return atob("GBiBAAAAAAAAAAAYAAD/AAP/wAf/4A/48A/g8B/g+B/j+B/n+D/n/D8A/B8A+B+B+B/n+A/n8A/n8Afn4APnwADnAAAAAAAAAAAAAA=="); - if (s=="google home") return atob("GBiCAAAAAAAAAAAAAAAAAAAAAoAAAAAACqAAAAAAKqwAAAAAqroAAAACquqAAAAKq+qgAAAqr/qoAACqv/6qAAKq//+qgA6r///qsAqr///6sAqv///6sAqv///6sAqv///6sA6v///6sA6v///qsA6qqqqqsA6qqqqqsA6qqqqqsAP7///vwAAAAAAAAAAAAAAAAA=="); - if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA="); + if (s=="gmx") return atob("GBgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEJmfmd8Zuc85v847/88Z9s8fttmHIHiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + if (s=="google") return atob("GBiBAAAAAAD/AAP/wAf/4A/D4B8AwDwAADwAAHgAAHgAAHAAAHAH/nAH/nAH/ngH/ngAHjwAPDwAfB8A+A/D8Af/4AP/wAD/AAAAAA=="); + if (s=="google home") return atob("GBiCAAAAAAAAAAAAAAAAAAAAAoAAAAAACqAAAAAAKqwAAAAAqroAAAACquqAAAAKq+qgAAAqr/qoAACqv/6qAAKq//+qgA6r///qsAqr///6sAqv///6sAqv///6sAqv///6sA6v///6sA6v///qsA6qqqqqsA6qqqqqsA6qqqqqsAP7///vwAAAAAAAAAAAAAAAAA=="); // 2 bit unpaletted if (s=="home assistant") return atob("FhaBAAAAAADAAAeAAD8AAf4AD/3AfP8D7fwft/D/P8ec572zbzbNsOEhw+AfD8D8P4fw/z/D/P8P8/w/z/AAAAA="); if (s=="instagram") return atob("GBiBAAAAAAAAAAAAAAAAAAP/wAYAYAwAMAgAkAh+EAjDEAiBEAiBEAiBEAiBEAjDEAh+EAgAEAwAMAYAYAP/wAAAAAAAAAAAAAAAAA=="); if (s=="kalender") return atob("GBgBBgBgBQCgff++RQCiRgBiQAACf//+QAACQAACR//iRJkiRIEiR//iRNsiRIEiRJkiR//iRIEiRIEiR//iQAACQAACf//+AAAA"); if (s=="lieferando") return atob("GBgBABgAAH5wAP9wAf/4A//4B//4D//4H//4P/88fV8+fV4//V4//Vw/HVw4HVw4HBg4HBg4HBg4HDg4Hjw4Hj84Hj44Hj44Hj44"); + if (s=="mattermost") return atob("GBgBAAAAAPAAA+EAB4MADgcYHAcYOA8MOB8OeD8GcD8GcH8GcD8HcD8HeBwHeAAOfAAOfgAePwA8P8D8H//4D//wB//gAf/AAH4A"); + if (s=="n26") return atob("GBgBAAAAAAAAAAAAAAAAAP8AAAAAAAAAAAAAAOIAAOIAAPIAANoAANoAAM4AAMYAAMYAAAAAAAAAAAAAAP8AAAAAAAAAAAAAAAAA"); + if (s=="nextbike") return atob("GBgBAAAAAAAAAAAAAAAAAAAAAACAfgDAPwDAP4HAH4N4H8f8D82GMd8CMDsDMGMDMGGGGMHOD4D8AAAAAAAAAAAAAAAAAAAAAAAA"); if (s=="nina") return atob("GBgBAAAABAAQCAAICAAIEAAEEgAkJAgSJBwSKRxKSj4pUn8lVP+VVP+VUgAlSgApKQBKJAASJAASEgAkEAAECAAICAAIBAAQAAAA"); if (s=="outlook mail") return atob("HBwBAAAAAAAAAAAIAAAfwAAP/gAB/+AAP/5/A//v/D/+/8P/7/g+Pv8Dye/gPd74w5znHDnOB8Oc4Pw8nv/Dwe/8Pj7/w//v/D/+/8P/7/gf/gAA/+AAAfwAAACAAAAAAAAAAAA="); + if (s=="paypal") return atob("GBgBAAAAAAAAAAAAAf+AAf/AAf/gA//gA//gA//wA//wA//wA//wB//wB//wB//gB/+AB/gAB/gAB/gAAPgAAPgAAAAAAAAAAAAA"); if (s=="phone") return atob("FxeBABgAAPgAAfAAB/AAD+AAH+AAP8AAP4AAfgAA/AAA+AAA+AAA+AAB+AAB+AAB+OAB//AB//gB//gA//AA/8AAf4AAPAA="); if (s=="post & dhl") return atob("GBgBAPgAE/5wMwZ8NgN8NgP4NgP4HgP4HgPwDwfgD//AB/+AAf8AAAAABs7AHcdgG4MwAAAAGESAFESAEkSAEnyAEkSAFESAGETw"); if (s=="signal") return atob("GBgBAAAAAGwAAQGAAhggCP8QE//AB//oJ//kL//wD//0D//wT//wD//wL//0J//kB//oA//ICf8ABfxgBYBAADoABMAABAAAAAAA"); if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA=="); if (s=="slack") return atob("GBiBAAAAAAAAAABAAAHvAAHvAADvAAAPAB/PMB/veD/veB/mcAAAABzH8B3v+B3v+B3n8AHgAAHuAAHvAAHvAADGAAAAAAAAAAAAAA=="); if (s=="snapchat") return atob("GBgBAAAAAAAAAH4AAf+AAf+AA//AA//AA//AA//AA//AH//4D//wB//gA//AB//gD//wH//4f//+P//8D//wAf+AAH4AAAAAAAAA"); + if (s=="steam") return atob("GBgBAAAAAAAAAAAAAAAAAAAAAAfgAAwwAAvQABvQABvQADvQgDww4H/g+f8A/zwAf9gAH9AAB8AAACAAAcAAAAAAAAAAAAAAAAAA"); if (s=="teams") return atob("GBgBAAAAAAAAAAQAAB4AAD8IAA8cP/M+f/scf/gIeDgAfvvefvvffvvffvvffvvff/vff/veP/PeAA/cAH/AAD+AAD8AAAQAAAAA"); if (s=="telegram" || s=="telegram foss") return atob("GBiBAAAAAAAAAAAAAAAAAwAAHwAA/wAD/wAf3gD/Pgf+fh/4/v/z/P/H/D8P/Acf/AM//AF/+AF/+AH/+ADz+ADh+ADAcAAAMAAAAA=="); if (s=="threema") return atob("GBjB/4Yx//8AAAAAAAAAAAAAfgAB/4AD/8AH/+AH/+AP//AP2/APw/APw/AHw+AH/+AH/8AH/4AH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); - if (s=="to do") return atob("GBgBAAAAAAAAAAAwAAB4AAD8AAH+AAP/DAf/Hg//Px/+f7/8///4///wf//gP//AH/+AD/8AB/4AA/wAAfgAAPAAAGAAAAAAAAAA"); + if (s=="to do" || s=="opentasks") return atob("GBgBAAAAAAAAAAAwAAB4AAD8AAH+AAP/DAf/Hg//Px/+f7/8///4///wf//gP//AH/+AD/8AB/4AA/wAAfgAAPAAAGAAAAAAAAAA"); if (s=="twitch") return atob("GBgBH//+P//+P//+eAAGeAAGeAAGeDGGeDOGeDOGeDOGeDOGeDOGeDOGeAAOeAAOeAAcf4/4f5/wf7/gf//Af/+AA/AAA+AAAcAA"); if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA"); + if (s=="warnapp") return atob("GBgBAAAAAAAAAAAAAH4AAP8AA//AA//AD//gP//gf//4f//+/+P+/8H//8n//4n/fxh/fzg+Pj88Dn44AA4AAAwAAAwAAAgAAAAA"); if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA=="); if (s=="wordfeud") return atob("GBgCWqqqqqqlf//////9v//////+v/////++v/////++v8///Lu+v8///L++v8///P/+v8v//P/+v9v//P/+v+fx/P/+v+Pk+P/+v/PN+f/+v/POuv/+v/Ofdv/+v/NvM//+v/I/Y//+v/k/k//+v/i/w//+v/7/6//+v//////+v//////+f//////9Wqqqqqql"); - if (s=="youtube") return atob("GBgBAAAAAAAAAAAAAAAAAf8AH//4P//4P//8P//8P5/8P4/8f4P8f4P8P4/8P5/8P//8P//8P//4H//4Af8AAAAAAAAAAAAAAAAA"); + if (s=="youtube" || s=="newpipe") return atob("GBgBAAAAAAAAAAAAAAAAAf8AH//4P//4P//8P//8P5/8P4/8f4P8f4P8P4/8P5/8P//8P//8P//4H//4Af8AAAAAAAAAAAAAAAAA"); if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A="); // if (s=="sms message" || s=="mail" || s=="gmail") // .. default icon (below) - return atob("HBKBAD///8H///iP//8cf//j4//8f5//j/x/8//j/H//H4//4PB//EYj/44HH/Hw+P4//8fH//44///xH///g////A=="); + return atob("FhKBAH//+P//yf/+c//z5/+fz/z/n+f/Pz/+ef/8D///////////////////////f//4///A"); }; exports.getMessageImageCol = function(msg,def) { + let iconColorMode = (require('Storage').readJSON("messages.settings.json", 1) || {}).iconColorMode; + if (iconColorMode == 'mono') + return g.theme.fg; + const s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase(); return { // generic colors, using B2-safe colors - "alarm": "#fff", + // DO NOT USE BLACK OR WHITE HERE, just leave the declaration out and then the theme's fg color will be used + "airbnb": "#f00", "mail": "#ff0", "music": "#f0f", "phone": "#0f0", @@ -166,31 +192,40 @@ exports.getMessageImageCol = function(msg,def) { // brands, according to https://www.schemecolor.com/?s (picking one for multicolored logos) // all dithered on B2, but we only use the color for the icons. (Could maybe pick the closest 3-bit color for B2?) "bibel": "#54342c", + "bring": "#455a64", "discord": "#738adb", + "etar": "#36a18b", "facebook": "#4267b2", "gmail": "#ea4335", + "gmx": "#1c449b", + "google": "#4285F4", "google home": "#fbbc05", - "hangouts": "#1ba261", - "home assistant": "#fff", // ha-blue is #41bdf5, but that's the background +// "home assistant": "#41bdf5", // ha-blue is #41bdf5, but that's the background "instagram": "#dd2a7b", - "liferando": "#ee5c00", + "lieferando": "#ee5c00", "messenger": "#0078ff", + "mattermost": "#00f", + "n26": "#36a18b", + "nextbike": "#00f", + "newpipe": "#f00", "nina": "#e57004", + "opentasks": "#409f8f", "outlook mail": "#0072c6", + "paypal": "#003087", "post & dhl": "#f2c101", "signal": "#00f", "skype": "#00aff0", "slack": "#e51670", "snapchat": "#ff0", + "steam": "#171a21", "teams": "#464eb8", "telegram": "#0088cc", "telegram foss": "#0088cc", - "threema": "#000", "to do": "#3999e5", "twitch": "#6441A4", "twitter": "#1da1f2", "whatsapp": "#4fce5d", "wordfeud": "#e7d3c7", "youtube": "#f00", - }[(msg.src||"").toLowerCase()]||(def !== undefined?def:g.theme.fg); + }[s]||(def !== undefined?def:g.theme.fg); }; diff --git a/apps/messages/metadata.json b/apps/messages/metadata.json index 4bc9f59e4..da2e0945a 100644 --- a/apps/messages/metadata.json +++ b/apps/messages/metadata.json @@ -1,7 +1,7 @@ { "id": "messages", "name": "Messages", - "version": "0.39", + "version": "0.50", "description": "App to display notifications from iOS and Gadgetbridge/Android", "icon": "app.png", "type": "app", @@ -10,6 +10,7 @@ "readme": "README.md", "storage": [ {"name":"messages.app.js","url":"app.js"}, + {"name":"messages.new.js","url":"app-newmessage.js"}, {"name":"messages.settings.js","url":"settings.js"}, {"name":"messages.img","url":"app-icon.js","evaluate":true}, {"name":"messages.wid.js","url":"widget.js"}, diff --git a/apps/messages/screenshot-notify.gif b/apps/messages/screenshot-notify.gif index 3d0ed0b32..e5cc669bd 100644 Binary files a/apps/messages/screenshot-notify.gif and b/apps/messages/screenshot-notify.gif differ diff --git a/apps/messages/settings.js b/apps/messages/settings.js index 1b166dcf6..0edb17797 100644 --- a/apps/messages/settings.js +++ b/apps/messages/settings.js @@ -1,9 +1,15 @@ (function(back) { + const iconColorModes = ['color', 'mono']; + function settings() { let settings = require('Storage').readJSON("messages.settings.json", true) || {}; if (settings.vibrate===undefined) settings.vibrate=":"; + if (settings.vibrateCalls===undefined) settings.vibrateCalls=":"; if (settings.repeat===undefined) settings.repeat=4; + if (settings.vibrateTimeout===undefined) settings.vibrateTimeout=60; if (settings.unreadTimeout===undefined) settings.unreadTimeout=60; + if (settings.maxMessages===undefined) settings.maxMessages=3; + if (settings.iconColorMode === undefined) settings.iconColorMode = iconColorModes[0]; settings.unlockWatch=!!settings.unlockWatch; settings.openMusic=!!settings.openMusic; settings.maxUnreadTimeout=240; @@ -20,12 +26,19 @@ "" : { "title" : /*LANG*/"Messages" }, "< Back" : back, /*LANG*/'Vibrate': require("buzz_menu").pattern(settings().vibrate, v => updateSetting("vibrate", v)), + /*LANG*/'Vibrate for calls': require("buzz_menu").pattern(settings().vibrateCalls, v => updateSetting("vibrateCalls", v)), /*LANG*/'Repeat': { value: settings().repeat, min: 0, max: 10, format: v => v?v+"s":/*LANG*/"Off", onchange: v => updateSetting("repeat", v) }, + /*LANG*/'Vibrate timer': { + value: settings().vibrateTimeout, + min: 0, max: settings().maxUnreadTimeout, step : 10, + format: v => v?v+"s":/*LANG*/"Off", + onchange: v => updateSetting("vibrateTimeout", v) + }, /*LANG*/'Unread timer': { value: settings().unreadTimeout, min: 0, max: settings().maxUnreadTimeout, step : 10, @@ -40,24 +53,35 @@ }, /*LANG*/'Auto-Open Music': { value: !!settings().openMusic, - format: v => v?/*LANG*/'Yes':/*LANG*/'No', onchange: v => updateSetting("openMusic", v) }, /*LANG*/'Unlock Watch': { value: !!settings().unlockWatch, - format: v => v?/*LANG*/'Yes':/*LANG*/'No', onchange: v => updateSetting("unlockWatch", v) }, /*LANG*/'Flash Icon': { value: !!settings().flash, - format: v => v?/*LANG*/'Yes':/*LANG*/'No', onchange: v => updateSetting("flash", v) }, /*LANG*/'Quiet mode disables auto-open': { value: !!settings().quietNoAutOpn, - format: v => v?/*LANG*/'Yes':/*LANG*/'No', onchange: v => updateSetting("quietNoAutOpn", v) }, + /*LANG*/'Disable auto-open': { + value: !!settings().noAutOpn, + onchange: v => updateSetting("noAutOpn", v) + }, + /*LANG*/'Widget messages': { + value:0|settings().maxMessages, + min: 1, max: 5, + onchange: v => updateSetting("maxMessages", v) + }, + /*LANG*/'Icon color mode': { + value: Math.max(0,iconColorModes.indexOf(settings().iconColorMode)), + min: 0, max: iconColorModes.length - 1, + format: v => iconColorModes[v], + onchange: v => updateSetting("iconColorMode", iconColorModes[v]) + } }; E.showMenu(mainmenu); -}) +}); diff --git a/apps/messages/widget.js b/apps/messages/widget.js index 014faf12e..99d98ca77 100644 --- a/apps/messages/widget.js +++ b/apps/messages/widget.js @@ -1,5 +1,12 @@ -WIDGETS["messages"]={area:"tl", width:0, iconwidth:24, -draw:function(recall) { +(() => { + +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); +} + +WIDGETS["messages"]={area:"tl", width:0, 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); @@ -8,42 +15,67 @@ draw:function(recall) { Bangle.removeListener('touch', this.touch); if (!this.width) return; var c = (Date.now()-this.t)/1000; - let settings = require('Storage').readJSON("messages.settings.json", true) || {}; - if (settings.flash===undefined) settings.flash = true; + let settings = Object.assign({flash:true, maxMessages:3, repeat:4, vibrateTimeout:60},require('Storage').readJSON("messages.settings.json", true) || {}); if (recall !== true || settings.flash) { + var msgsShown = E.clip(this.msgs.length, 0, settings.maxMessages); g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+23); - g.drawImage(settings.flash && (c&1) ? atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+DAADDAADDAADDwAPD8A/DOBzDDn/DA//DAHvDAPvjAPvjAPvjAPvh///gf/vAAD+AAB8AAAAA==") : atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+D///D///A//8CP/xDj/HD48DD+B8D/D+D/3vD/vvj/vvj/vvj/vvh/v/gfnvAAD+AAB8AAAAA=="), this.x, this.y-1); + for(let i = 0;i < msgsShown;i++) { + const msg = this.msgs[i]; + const colors = [g.theme.bg, g.setColor(require("messages").getMessageImageCol(msg)).getColor()]; + if (settings.flash && (c&1)) { + if (colors[1] == g.theme.fg) { + colors.reverse(); + } else { + colors[1] = g.theme.fg; + } + } + g.setColor(colors[1]).setBgColor(colors[0]); + // draw the icon, or '...' if too many messages + g.drawImage(i == (settings.maxMessages - 1) && this.msgs.length > settings.maxMessages ? atob("EASBAGGG88/zz2GG") : require("messages").getMessageImage(msg), + this.x + 12 + i * 24, this.y + 12, {rotate:0/*force centering*/}); + } } - if (settings.repeat===undefined) settings.repeat = 4; - if (c<120 && settings.repeat>0 && (Date.now()-this.l)>settings.repeat*1000) { + if (csettings.repeat*1000) { // the period between vibrations this.l = Date.now(); WIDGETS["messages"].buzz(); // buzz every 4 seconds } WIDGETS["messages"].i=setTimeout(()=>WIDGETS["messages"].draw(true), 1000); if (process.env.HWVERSION>1) Bangle.on('touch', this.touch); -},show:function(quiet) { - WIDGETS["messages"].t=Date.now(); // first time - WIDGETS["messages"].l=Date.now()-10000; // last buzz - if (quiet) WIDGETS["messages"].t -= 500000; // if quiet, set last time in the past so there is no buzzing - WIDGETS["messages"].width=this.iconwidth; +},update:function(rawMsgs, quiet) { + const settings = Object.assign({maxMessages:3},require('Storage').readJSON("messages.settings.json", true) || {}); + this.msgs = filterMessages(rawMsgs); + if (this.msgs.length === 0) { + delete this.t; + delete this.l; + } else { + this.t=Date.now(); // first time + this.l=Date.now()-10000; // last buzz + if (quiet) this.t -= 500000; // if quiet, set last time in the past so there is no buzzing + } + this.width = 24 * E.clip(this.msgs.length, 0, settings.maxMessages); Bangle.drawWidgets(); -},hide:function() { - delete WIDGETS["messages"].t; - delete WIDGETS["messages"].l; - WIDGETS["messages"].width=0; - Bangle.drawWidgets(); -},buzz:function() { - if ((require('Storage').readJSON('setting.json',1)||{}).quiet) return new Promise((success) => { success(); }); // never buzz during Quiet Mode - return require("buzz").pattern((require('Storage').readJSON("messages.settings.json", true) || {}).vibrate || ":"); +},buzz:function(msgSrc) { // return a promise + if ((require('Storage').readJSON('setting.json',1)||{}).quiet) return Promise.,resolve(); // never buzz during Quiet Mode + var pattern; + if (msgSrc != undefined && msgSrc.toLowerCase() == "phone") { + // special vibration pattern for incoming calls + pattern = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrateCalls; + } else { + pattern = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrate; + } + if (pattern === undefined) { pattern = ":"; } // pattern may be "", so we can't use || ":" here + return require("buzz").pattern(pattern); },touch:function(b,c) { var w=WIDGETS["messages"]; - if (!w||!w.width||c.xw.x+w.width||c.yw.y+w.iconwidth) return; + if (!w||!w.width||c.xw.x+w.width||c.yw.y+24) return; load("messages.app.js"); }}; + /* We might have returned here if we were in the Messages app for a message but then the watch was never viewed. In that case we don't want to buzz but should still show that there are unread messages. */ -if (global.MESSAGES===undefined) (function() { - var messages = require("Storage").readJSON("messages.json",1)||[]; - if (messages.some(m=>m.new&&m.id!="music")) WIDGETS["messages"].show(true); +if (global.MESSAGES===undefined) + WIDGETS["messages"].update(require("messages").getMessages(), true); })(); diff --git a/apps/messagesmusic/ChangeLog b/apps/messagesmusic/ChangeLog index 5560f00bc..9f4cafb0e 100644 --- a/apps/messagesmusic/ChangeLog +++ b/apps/messagesmusic/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Remove one line of code that didn't do anything other than in some instances hinder the function of the app. diff --git a/apps/messagesmusic/README.md b/apps/messagesmusic/README.md index 7aa9209df..85608118d 100644 --- a/apps/messagesmusic/README.md +++ b/apps/messagesmusic/README.md @@ -6,9 +6,9 @@ Making the music controls accessible this way lets one start a music stream on t It is suggested to use Messages Music along side the app Quick Launch. -Messages Music v0.01 has been verified to work with Messages v0.31 on Bangle.js 2 fw2v13. +Messages Music v0.02 has been verified to work with Messages v0.41 on Bangle.js 2 fw2v14. -Music Messages should work with forks of the original Messages app. At least as long as functions pushMessage() in the library and showMusicMessage() in app.js hasn't been changed too much. +Messages Music should work with forks of the original Messages app. At least as long as functions pushMessage() in the library and showMusicMessage() in app.js hasn't been changed too much. Messages app is created by Gordon Williams with contributions from [Jeroen Peters](https://github.com/jeroenpeters1986). diff --git a/apps/messagesmusic/app.js b/apps/messagesmusic/app.js index a6f7e075e..27f3f6e4d 100644 --- a/apps/messagesmusic/app.js +++ b/apps/messagesmusic/app.js @@ -1,7 +1,6 @@ let showMusic = () => { Bangle.CLOCK = 1; // To pass condition in messages library require('messages').pushMessage({"t":"add","artist":" ","album":" ","track":" ","dur":0,"c":-1,"n":-1,"id":"music","title":"Music","state":"play","new":true}); - Bangle.CLOCK = undefined; }; var settings = require('Storage').readJSON('messages.settings.json', true) || {}; //read settings if they exist else set to empty dict diff --git a/apps/messagesmusic/metadata.json b/apps/messagesmusic/metadata.json index edc6835ed..c29ffbc34 100644 --- a/apps/messagesmusic/metadata.json +++ b/apps/messagesmusic/metadata.json @@ -1,7 +1,7 @@ { "id": "messagesmusic", "name":"Messages Music", - "version":"0.01", + "version":"0.02", "description": "Uses Messages library to push a music message which in turn displays Messages app music controls", "icon":"app.png", "type": "app", diff --git a/apps/miclock/ChangeLog b/apps/miclock/ChangeLog index e92bad2e3..d1ac3e388 100644 --- a/apps/miclock/ChangeLog +++ b/apps/miclock/ChangeLog @@ -2,3 +2,4 @@ 0.03: Localization 0.04: move jshint to the top 0.05: Use Bangle.setUI for button/launcher handling +0.06: Tell clock widgets to hide. diff --git a/apps/miclock/clock-mixed.js b/apps/miclock/clock-mixed.js index b3d6bea8d..cb3235406 100644 --- a/apps/miclock/clock-mixed.js +++ b/apps/miclock/clock-mixed.js @@ -77,11 +77,13 @@ Bangle.on('lcdPower', function(on) { drawMixedClock(); }); +// Show launcher when button pressed +Bangle.setUI("clock"); + g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); setInterval(drawMixedClock, 5E3); drawMixedClock(); -// Show launcher when button pressed -Bangle.setUI("clock"); + diff --git a/apps/miclock/metadata.json b/apps/miclock/metadata.json index 6eece46b0..2c216dc33 100644 --- a/apps/miclock/metadata.json +++ b/apps/miclock/metadata.json @@ -1,7 +1,7 @@ { "id": "miclock", "name": "Mixed Clock", - "version": "0.05", + "version": "0.06", "description": "A mix of analog and digital Clock", "icon": "clock-mixed.png", "type": "clock", diff --git a/apps/minimal_clock/ChangeLog b/apps/minimal_clock/ChangeLog new file mode 100644 index 000000000..54ee389e3 --- /dev/null +++ b/apps/minimal_clock/ChangeLog @@ -0,0 +1,3 @@ +... +0.03: First update with ChangeLog Added +0.04: Tell clock widgets to hide. diff --git a/apps/minimal_clock/app.js b/apps/minimal_clock/app.js index d78790347..47eca3c66 100644 --- a/apps/minimal_clock/app.js +++ b/apps/minimal_clock/app.js @@ -3,6 +3,7 @@ let outerRadius = Math.min(CenterX,CenterY) * 0.9; + Bangle.setUI('clock'); Bangle.loadWidgets(); /**** updateClockFaceSize ****/ @@ -225,6 +226,5 @@ } }); - Bangle.loadWidgets(); - Bangle.setUI('clock'); + Bangle.loadWidgets(); diff --git a/apps/minimal_clock/metadata.json b/apps/minimal_clock/metadata.json index 1702d97a9..3089780ce 100644 --- a/apps/minimal_clock/metadata.json +++ b/apps/minimal_clock/metadata.json @@ -1,7 +1,7 @@ { "id": "minimal_clock", "name": "Minimal Analog Clock", "shortName":"Minimal Clock", - "version":"0.03", + "version":"0.04", "description": "a minimal analog clock - just with some hands and no clock face", "icon": "app-icon.png", "type": "clock", diff --git a/apps/minionclk/ChangeLog b/apps/minionclk/ChangeLog index a8b6efc81..5949a786d 100644 --- a/apps/minionclk/ChangeLog +++ b/apps/minionclk/ChangeLog @@ -3,3 +3,4 @@ 0.03: Fixed rendering for Espruino v2.06 0.04: Fixed overlapped rendering of dates 0.05: Use Bangle.setUI for button/launcher handling +0.06: Tell clock widgets to hide. diff --git a/apps/minionclk/app b/apps/minionclk/app new file mode 100644 index 000000000..e69de29bb diff --git a/apps/minionclk/app.js b/apps/minionclk/app.js index 9648e3d89..c61f8d3bf 100644 --- a/apps/minionclk/app.js +++ b/apps/minionclk/app.js @@ -78,8 +78,10 @@ Bangle.on('lcdPower', (on) => { } }); +// Show launcher when button pressed +Bangle.setUI("clock"); + Bangle.loadWidgets(); startDrawing(); -// Show launcher when button pressed -Bangle.setUI("clock"); + diff --git a/apps/minionclk/metadata.json b/apps/minionclk/metadata.json index 44fc2a82d..4df2ddc6b 100644 --- a/apps/minionclk/metadata.json +++ b/apps/minionclk/metadata.json @@ -1,7 +1,7 @@ { "id": "minionclk", "name": "Minion clock", - "version": "0.05", + "version": "0.06", "description": "Minion themed clock.", "icon": "minionclk.png", "type": "clock", diff --git a/apps/multitimer/ChangeLog b/apps/multitimer/ChangeLog index 9b60f403a..9a2ab0ff4 100644 --- a/apps/multitimer/ChangeLog +++ b/apps/multitimer/ChangeLog @@ -1,2 +1,3 @@ 0.01: Initial version 0.02: Update for time_utils module +0.03: Use default Bangle formatter for booleans diff --git a/apps/multitimer/app.js b/apps/multitimer/app.js index e5d77d860..8832d1a25 100644 --- a/apps/multitimer/app.js +++ b/apps/multitimer/app.js @@ -267,7 +267,6 @@ function editTimer(idx, a) { }, "Enabled": { value: a.on, - format: v => v ? "On" : "Off", onchange: v => a.on = v }, "Hours": { @@ -293,7 +292,6 @@ function editTimer(idx, a) { }, "Hard Mode": { value: a.data.hm, - format: v => v ? "On" : "Off", onchange: v => a.data.hm = v }, "Vibrate": require("buzz_menu").pattern(a.vibrate, v => a.vibrate = v), @@ -535,7 +533,6 @@ function editDOW(dow, onchange) { var dayOfWeek = require("locale").dow({ getDay: () => i }); menu[dayOfWeek] = { value: !!(dow&(1< v ? "Yes" : "No", onchange: v => v ? dow |= 1< v ? "On" : "Off", onchange: v => a.on = v }, "Hours": { @@ -614,7 +610,6 @@ function editAlarm(idx, a) { }, "Repeat": { value: a.rp, - format: v => v ? "Yes" : "No", onchange: v => a.rp = v }, "Days": { @@ -623,13 +618,11 @@ function editAlarm(idx, a) { }, "Hard Mode": { value: a.data.hm, - format: v => v ? "On" : "Off", onchange: v => a.data.hm = v }, "Vibrate": require("buzz_menu").pattern(a.vibrate, v => a.vibrate = v), "Auto Snooze": { value: a.as, - format: v => v ? "Yes" : "No", onchange: v => a.as = v }, "Msg": { diff --git a/apps/multitimer/metadata.json b/apps/multitimer/metadata.json index abb958b90..ee77d2ecb 100644 --- a/apps/multitimer/metadata.json +++ b/apps/multitimer/metadata.json @@ -1,7 +1,7 @@ { "id": "multitimer", "name": "Multi Timer", - "version": "0.02", + "version": "0.03", "description": "Set timers and chronographs (stopwatches) and watch them count down in real time. Pause, create, edit, and delete timers and chronos, and add custom labels/messages. Also sets alarms.", "icon": "app.png", "screenshots": [ diff --git a/apps/mysticclock/ChangeLog b/apps/mysticclock/ChangeLog index b486a29a1..cd91abe00 100644 --- a/apps/mysticclock/ChangeLog +++ b/apps/mysticclock/ChangeLog @@ -1,2 +1,3 @@ 1.00: First published version. 1.01: Use Bangle.setUI for Launcher/buttons +1.02: Tell clock widgets to hide. diff --git a/apps/mysticclock/metadata.json b/apps/mysticclock/metadata.json index 571a55ecd..bd2df2f8d 100644 --- a/apps/mysticclock/metadata.json +++ b/apps/mysticclock/metadata.json @@ -1,7 +1,7 @@ { "id": "mysticclock", "name": "Mystic Clock", - "version": "1.01", + "version": "1.02", "description": "A retro-inspired watchface featuring time, date, and an interactive data display line.", "icon": "mystic-clock.png", "type": "clock", diff --git a/apps/mysticclock/mystic-clock-app.js b/apps/mysticclock/mystic-clock-app.js index 2d95633fe..d7f4ab1c3 100644 --- a/apps/mysticclock/mystic-clock-app.js +++ b/apps/mysticclock/mystic-clock-app.js @@ -189,6 +189,13 @@ Bangle.on('touch', (button) => { if (button === 3 && Bangle.isLCDOn()) Bangle.setLCDPower(false); }); +// Show launcher when button pressed +Bangle.setUI("clockupdown", btn=>{ + if (btn<0) prevInfo(); + if (btn>0) nextInfo(); + drawAll(); +}); + // clean app screen g.clear(); Bangle.loadWidgets(); @@ -200,9 +207,3 @@ if (Bangle.isLCDOn()) { drawAll(); // draw immediately } -// Show launcher when button pressed -Bangle.setUI("clockupdown", btn=>{ - if (btn<0) prevInfo(); - if (btn>0) nextInfo(); - drawAll(); -}); diff --git a/apps/nato/ChangeLog b/apps/nato/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/nato/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/ncrclk/ChangeLog b/apps/ncrclk/ChangeLog index 31e5d42c8..0c326161a 100644 --- a/apps/ncrclk/ChangeLog +++ b/apps/ncrclk/ChangeLog @@ -1,2 +1,3 @@ 0.01: A copy of the analogimgclk to work for NodeConf Remote 0.02: Use Bangle.setUI for button/launcher handling +0.03: Tell clock widgets to hide. diff --git a/apps/ncrclk/app.js b/apps/ncrclk/app.js index 16724fa5e..805ac1b95 100644 --- a/apps/ncrclk/app.js +++ b/apps/ncrclk/app.js @@ -120,10 +120,10 @@ Bangle.on('lcdPower', (on) => { } }); +// Show launcher when button pressed +Bangle.setUI("clock"); + g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); drawHands(true); - -// Show launcher when button pressed -Bangle.setUI("clock"); diff --git a/apps/ncrclk/metadata.json b/apps/ncrclk/metadata.json index b50b554e1..fdab77450 100644 --- a/apps/ncrclk/metadata.json +++ b/apps/ncrclk/metadata.json @@ -2,7 +2,7 @@ "id": "ncrclk", "name": "NCR Clock", "shortName": "NCR Clock", - "version": "0.02", + "version": "0.03", "description": "NodeConf Remote clock", "icon": "app.png", "type": "clock", diff --git a/apps/noteify/ChangeLog b/apps/noteify/ChangeLog index ec66c5568..d7bc46dcd 100644 --- a/apps/noteify/ChangeLog +++ b/apps/noteify/ChangeLog @@ -1 +1,2 @@ 0.01: Initial version +0.02: Use default Bangle formatter for booleans diff --git a/apps/noteify/README.md b/apps/noteify/README.md index c846709de..dbdceb399 100644 --- a/apps/noteify/README.md +++ b/apps/noteify/README.md @@ -1,6 +1,6 @@ # WARNING -This app uses the [Scheduler library](https://banglejs.com/apps/?id=sched) and requires a keyboard such as [Swipe keyboard](https://banglejs.com/apps/?id=kbswipe). +This app uses the [Scheduler library](https://banglejs.com/apps/?id=sched) and requires a [keyboard library](https://banglejs.com/apps/?c=textinput#). ## Usage diff --git a/apps/noteify/app.js b/apps/noteify/app.js index 2b3ee64f0..02d43c065 100644 --- a/apps/noteify/app.js +++ b/apps/noteify/app.js @@ -171,7 +171,6 @@ function editDOW(dow, onchange) { var dayOfWeek = require("locale").dow({ getDay: () => i }); menu[dayOfWeek] = { value: !!(dow&(1< v ? "Yes" : "No", onchange: v => v ? dow |= 1<v?"On":"Off", onchange: v=>a.on=v }, 'Repeat': { value: a.rp, - format: v=>v?"Yes":"No", onchange: v=>a.rp=v }, 'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ), 'Auto snooze': { value: a.as, - format: v=>v?"Yes":"No", onchange: v=>a.as=v } }; @@ -278,7 +274,6 @@ function editTimer(alarmIndex, alarm) { }, 'Enabled': { value: a.on, - format: v=>v?"On":"Off", onchange: v=>a.on=v }, 'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ), diff --git a/apps/noteify/metadata.json b/apps/noteify/metadata.json index 7e897d1f0..fbd5a88f1 100644 --- a/apps/noteify/metadata.json +++ b/apps/noteify/metadata.json @@ -1,11 +1,11 @@ { "id": "noteify", "name": "Noteify", - "version": "0.01", + "version": "0.02", "description": "Write notes using an onscreen keyboard and use them as custom messages for alarms or timers.", "icon": "app.png", "tags": "tool,alarm", - "supports": ["BANGLEJS2"], + "supports": ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"noteify.app.js","url":"app.js"}, diff --git a/apps/novaclock/ChangeLog b/apps/novaclock/ChangeLog new file mode 100644 index 000000000..8b05ff9ec --- /dev/null +++ b/apps/novaclock/ChangeLog @@ -0,0 +1,3 @@ +... +0.10: First update with ChangeLog Added +0.11: Tell clock widgets to hide. diff --git a/apps/novaclock/app.js b/apps/novaclock/app.js index e5bd37b06..52bee0dbd 100644 --- a/apps/novaclock/app.js +++ b/apps/novaclock/app.js @@ -249,13 +249,13 @@ var open = false; var timemode = true; var clockmode; var novaYPos = -7; +Bangle.setUI("clock"); g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); g.drawImage(nova(), -10, -10, { scale: 2.2 }); -Bangle.setUI("clock"); g.drawImage(star(), 5, -5, {scale:0.8}); g.drawImage(star(), -10, 120, {scale:0.8}); diff --git a/apps/novaclock/metadata.json b/apps/novaclock/metadata.json index c1dad60a1..69b7627f8 100644 --- a/apps/novaclock/metadata.json +++ b/apps/novaclock/metadata.json @@ -3,7 +3,7 @@ "shortName":"Nova Clock", "icon": "app.png", "type": "clock", - "version":"0.1", + "version":"0.11", "description": "A clock inspired by the Kirby series", "tags": "clock", "supports": ["BANGLEJS2"], diff --git a/apps/openstmap/ChangeLog b/apps/openstmap/ChangeLog index 6cb9d061e..2a6c15a09 100644 --- a/apps/openstmap/ChangeLog +++ b/apps/openstmap/ChangeLog @@ -10,3 +10,5 @@ 0.10: Improve scale factor calculation to fix scaling issues (#984) 0.11: Add slight offset to OSM data to align it properly (fix #984) Fix alignment of satellite info text +0.12: switch to using normal OpenStreetMap tiles (opentopomap was too slow) +0.13: Use a single image file with 'frames' of data (drastically reduces file count, possibility of >1 map on device) diff --git a/apps/openstmap/custom.html b/apps/openstmap/custom.html index 6e79a6e9a..6f611dd86 100644 --- a/apps/openstmap/custom.html +++ b/apps/openstmap/custom.html @@ -64,9 +64,10 @@ TODO: var OSMTILECOUNT = 3; // how many tiles do we download in each direction (Math.floor(MAPSIZE / OSMTILESIZE)+1) /* Can see possible tiles on http://leaflet-extras.github.io/leaflet-providers/preview/ However some don't allow cross-origin use */ - var TILELAYER = 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png'; // simple, high contrast - var PREVIEWTILELAYER = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; + //var TILELAYER = 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png'; // simple, high contrast, TOO SLOW //var TILELAYER = 'http://a.tile.stamen.com/toner/{z}/{x}/{y}.png'; // black and white + var TILELAYER = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; + var PREVIEWTILELAYER = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; // Create map and try and set the location to where the browser thinks we are var map = L.map('map').locate({setView: true, maxZoom: 16, enableHighAccuracy:true}); @@ -128,7 +129,7 @@ TODO: var min = Math.min(rgba[i+0],rgba[i+1],rgba[i+2]); var max = Math.max(rgba[i+0],rgba[i+1],rgba[i+2]); var d = max-min; - if (max<120) { // black + if (max<120 || (d<8 && max<215)) { // black, or a darker grey rgba[i+0]=0; rgba[i+1]=0; rgba[i+2]=0; @@ -147,7 +148,7 @@ TODO: console.log("Compression options", options); var w = Math.round(width / TILESIZE); var h = Math.round(height / TILESIZE); - var tiles = []; + var tiledImage; for (var y=0;y { document.getElementById("uploadbuttons").style.display=""; mapFiles = tilesLoaded(ctx, canvas.width, canvas.height); - mapFiles.unshift({name:"openstmap.json",content:JSON.stringify({ + mapFiles.unshift({name:"openstmap.0.json",content:JSON.stringify({ imgx : canvas.width, imgy : canvas.height, tilesize : TILESIZE, scale : scale, // how much of Bangle.project(latlon) does one pixel equate to? lat : centerlatlon.lat, - lon : centerlatlon.lng + lon : centerlatlon.lng, + w : Math.round(canvas.width / TILESIZE), // width in tiles + h : Math.round(canvas.height / TILESIZE), // height in tiles + fn : "openstmap.0.img" })}); console.log(mapFiles); }); diff --git a/apps/openstmap/metadata.json b/apps/openstmap/metadata.json index 2dc9bd427..32093f70e 100644 --- a/apps/openstmap/metadata.json +++ b/apps/openstmap/metadata.json @@ -2,7 +2,7 @@ "id": "openstmap", "name": "OpenStreetMap", "shortName": "OpenStMap", - "version": "0.11", + "version": "0.13", "description": "Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are. Once installed this also adds map functionality to `GPS Recorder` and `Recorder` apps", "icon": "app.png", "tags": "outdoors,gps,osm", diff --git a/apps/openstmap/openstmap.js b/apps/openstmap/openstmap.js index d995aca25..2bd7d2e2e 100644 --- a/apps/openstmap/openstmap.js +++ b/apps/openstmap/openstmap.js @@ -22,7 +22,7 @@ function center() { */ -var map = require("Storage").readJSON("openstmap.json"); +var map = require("Storage").readJSON("openstmap.0.json"); map.center = Bangle.project({lat:map.lat,lon:map.lon}); exports.map = map; exports.lat = map.lat; // actual position of middle of screen @@ -30,7 +30,7 @@ exports.lon = map.lon; // actual position of middle of screen var m = exports; exports.draw = function() { - var s = require("Storage"); + var img = require("Storage").read(map.fn); var cx = g.getWidth()/2; var cy = g.getHeight()/2; var p = Bangle.project({lat:m.lat,lon:m.lon}); @@ -41,13 +41,11 @@ exports.draw = function() { var ty = 0|(iy/map.tilesize); var ox = (tx*map.tilesize)-ix; var oy = (ty*map.tilesize)-iy; - for (var x=ox,ttx=tx;x=0 && ttx=0 && tty v ? /*LANG*/"On" : /*LANG*/"Off"; (function(back) { const SETTINGS_FILE = 'openwindsettings.json' // initialize with default settings... @@ -29,7 +28,6 @@ const boolFormat = v => v ? /*LANG*/"On" : /*LANG*/"Off"; '< Back': back, 'True wind': { value: settings.truewind, - format: boolFormat, onchange: save('truewind'), }, 'Mounting angle': { diff --git a/apps/owmweather/ChangeLog b/apps/owmweather/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/owmweather/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/owmweather/README.md b/apps/owmweather/README.md new file mode 100644 index 000000000..ba0427455 --- /dev/null +++ b/apps/owmweather/README.md @@ -0,0 +1,13 @@ +# OpenWeatherMap weather provider + +This updates [Weather](https://banglejs.com/apps/#weather) with data from the OpenWeatherMap API + +## Usage + +Just install and configure the app. This needs an internet-enabled Gadgetbridge version. +Install [My Location](https://banglejs.com/apps/#mylocation) to change the location for the weather requests. +Install one of the text input libraries to set the API key in the app settings or use the web interface. + +## Creator + +[halemmerich](https://github.com/halemmerich) diff --git a/apps/owmweather/app.png b/apps/owmweather/app.png new file mode 100644 index 000000000..bbfc0ace0 Binary files /dev/null and b/apps/owmweather/app.png differ diff --git a/apps/owmweather/boot.js b/apps/owmweather/boot.js new file mode 100644 index 000000000..64d2df3e9 --- /dev/null +++ b/apps/owmweather/boot.js @@ -0,0 +1,28 @@ +(function() { + let waiting = false; + let settings = require("Storage").readJSON("owmweather.json", 1) || { + enabled: false + }; + + function completion(){ + waiting = false; + } + + if (settings.enabled) { + let weather = require("Storage").readJSON('weather.json') || {}; + let lastUpdate; + if (weather && weather.weather && weather.weather.time) lastUpdate = weather.weather.time; + if (!lastUpdate || lastUpdate + settings.refresh * 1000 * 60 < Date.now()){ + if (!waiting){ + waiting = true; + require("owmweather").pull(completion); + } + } + setInterval(() => { + if (!waiting && NRF.getSecurityStatus().connected){ + waiting = true; + require("owmweather").pull(completion); + } + }, settings.refresh * 1000 * 60); + } +})(); diff --git a/apps/owmweather/default.json b/apps/owmweather/default.json new file mode 100644 index 000000000..9d8998867 --- /dev/null +++ b/apps/owmweather/default.json @@ -0,0 +1 @@ +{"enabled":false,"refresh":180} diff --git a/apps/owmweather/interface.html b/apps/owmweather/interface.html new file mode 100644 index 000000000..3f9467a83 --- /dev/null +++ b/apps/owmweather/interface.html @@ -0,0 +1,63 @@ + + + + + +

Set OpenWeatherMap (OWM) API key

+

+ +

Where to get your personal API key?

+

Go to https://home.openweathermap.org/users/sign_up and sign up for a free account.
+ After registration you can login and optain your personal API key.

+ + + + + + + diff --git a/apps/owmweather/lib.js b/apps/owmweather/lib.js new file mode 100644 index 000000000..6ba52b498 --- /dev/null +++ b/apps/owmweather/lib.js @@ -0,0 +1,53 @@ +function parseWeather(response) { + let owmData = JSON.parse(response); + + let isOwmData = owmData.coord && owmData.weather && owmData.main; + + if (isOwmData) { + let json = require("Storage").readJSON('weather.json') || {}; + let weather = {}; + weather.time = Date.now(); + weather.hum = owmData.main.humidity; + weather.temp = owmData.main.temp; + weather.code = owmData.weather[0].id; + weather.wdir = owmData.wind.deg; + weather.wind = owmData.wind.speed; + weather.loc = owmData.name; + weather.txt = owmData.weather[0].main; + + if (weather.wdir != null) { + let deg = weather.wdir; + while (deg < 0 || deg > 360) { + deg = (deg + 360) % 360; + } + weather.wrose = ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'n'][Math.floor((deg + 22.5) / 45)]; + } + + json.weather = weather; + require("Storage").writeJSON('weather.json', json); + require("weather").emit("update", json.weather); + return undefined; + } else { + return /*LANG*/"Not OWM data"; + } +} + +exports.pull = function(completionCallback) { + let location = require("Storage").readJSON("mylocation.json", 1) || { + "lat": 51.50, + "lon": 0.12, + "location": "London" + }; + let settings = require("Storage").readJSON("owmweather.json", 1); + let uri = "https://api.openweathermap.org/data/2.5/weather?lat=" + location.lat.toFixed(2) + "&lon=" + location.lon.toFixed(2) + "&exclude=hourly,daily&appid=" + settings.apikey; + if (Bangle.http){ + Bangle.http(uri, {timeout:10000}).then(event => { + let result = parseWeather(event.resp); + if (completionCallback) completionCallback(result); + }).catch((e)=>{ + if (completionCallback) completionCallback(e); + }); + } else { + if (completionCallback) completionCallback(/*LANG*/"No http method found"); + } +}; diff --git a/apps/owmweather/metadata.json b/apps/owmweather/metadata.json new file mode 100644 index 000000000..013d345a5 --- /dev/null +++ b/apps/owmweather/metadata.json @@ -0,0 +1,22 @@ +{ "id": "owmweather", + "name": "OpenWeatherMap weather provider", + "shortName":"OWM Weather", + "version":"0.01", + "description": "Pulls weather from OpenWeatherMap (OWM) API", + "icon": "app.png", + "type": "bootloader", + "tags": "boot,tool,weather", + "supports" : ["BANGLEJS2"], + "interface": "interface.html", + "readme": "README.md", + "data": [ + {"name":"owmweather.json"}, + {"name":"weather.json"} + ], + "storage": [ + {"name":"owmweather.default.json","url":"default.json"}, + {"name":"owmweather.boot.js","url":"boot.js"}, + {"name":"owmweather","url":"lib.js"}, + {"name":"owmweather.settings.js","url":"settings.js"} + ] +} diff --git a/apps/owmweather/settings.js b/apps/owmweather/settings.js new file mode 100644 index 000000000..a4d21dd7c --- /dev/null +++ b/apps/owmweather/settings.js @@ -0,0 +1,84 @@ +(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("owmweather.default.json", true) || {}, + require('Storage').readJSON(FILE, true) || {} + ); + } + + var FILE="owmweather.json"; + var settings; + readSettings(); + + function buildMainMenu(){ + var mainmenu = { + '': { 'title': 'OWM weather' }, + '< Back': back, + "Enabled": { + value: !!settings.enabled, + onchange: v => { + writeSettings("enabled", v); + } + }, + "Refresh every": { + value: settings.refresh / 60, + min: 1, + max: 48, + step: 1, + format: v=>v+"h", + onchange: v => { + writeSettings("refresh",Math.round(v * 60)); + } + }, + "Force refresh": ()=>{ + if (!settings.apikey){ + E.showAlert("API key is needed","Hint").then(()=>{ + E.showMenu(buildMainMenu()); + }); + } else { + E.showMessage("Reloading weather"); + require("owmweather").pull((e)=>{ + if (e) { + E.showAlert(e,"Error").then(()=>{ + E.showMenu(buildMainMenu()); + }); + } else { + E.showAlert("Success").then(()=>{ + E.showMenu(buildMainMenu()); + }); + } + }); + } + } + }; + + mainmenu["API key"] = function (){ + if (require("textinput")){ + require("textinput").input({text:settings.apikey}).then(result => { + if (result != "") { + print("Result is", result); + settings.apikey = result; + writeSettings("apikey",result); + } + E.showMenu(buildMainMenu()); + }); + } else { + E.showPrompt("Install a text input lib"),then(()=>{ + E.showMenu(buildMainMenu()); + }); + } + }; + + + return mainmenu; + } + + E.showMenu(buildMainMenu()); +}); diff --git a/apps/pastel/ChangeLog b/apps/pastel/ChangeLog index a77fa758f..f4640426b 100644 --- a/apps/pastel/ChangeLog +++ b/apps/pastel/ChangeLog @@ -17,3 +17,4 @@ 0.15: fixed tendancy for mylocation to default to London added setting to enable/disable idle timer warning 0.16: make check_idle boolean setting work properly with new B2 menu +0.17: Use default Bangle formatter for booleans diff --git a/apps/pastel/metadata.json b/apps/pastel/metadata.json index f04a7ae54..1fe176d5f 100644 --- a/apps/pastel/metadata.json +++ b/apps/pastel/metadata.json @@ -2,7 +2,7 @@ "id": "pastel", "name": "Pastel Clock", "shortName": "Pastel", - "version": "0.16", + "version": "0.17", "description": "A Configurable clock with custom fonts, background and weather display. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times", "icon": "pastel.png", "dependencies": {"mylocation":"app","weather":"app"}, diff --git a/apps/pastel/pastel.settings.js b/apps/pastel/pastel.settings.js index afe461f15..14b3d2037 100644 --- a/apps/pastel/pastel.settings.js +++ b/apps/pastel/pastel.settings.js @@ -39,7 +39,6 @@ }, 'Show Grid': { value: !!s.grid, - format: v => v ? /*LANG*/"Yes":/*LANG*/"No", onchange: v => { s.grid = v; save(); @@ -47,7 +46,6 @@ }, 'Show Weather': { value: !!s.weather, - format: v => v ? /*LANG*/"Yes":/*LANG*/"No", onchange: v => { s.weather = v; save(); @@ -55,7 +53,6 @@ }, 'Idle Warning': { value: !!s.idle_check, - format: v => v ? /*LANG*/"Yes":/*LANG*/"No", onchange: v => { s.idle_check = v; save(); diff --git a/apps/pebble/ChangeLog b/apps/pebble/ChangeLog index 274c34a34..ac894fc00 100644 --- a/apps/pebble/ChangeLog +++ b/apps/pebble/ChangeLog @@ -8,3 +8,4 @@ 0.08: Add theme options and optional lock symbol 0.09: Add support for internationalization (LANG placeholders + "locale" module) Get steps from built-in step counter (widpedom no more needed, fix #1697) +0.10: Tell clock widgets to hide. diff --git a/apps/pebble/metadata.json b/apps/pebble/metadata.json index f3c1fcc12..c5faa8857 100644 --- a/apps/pebble/metadata.json +++ b/apps/pebble/metadata.json @@ -2,7 +2,7 @@ "id": "pebble", "name": "Pebble Clock", "shortName": "Pebble", - "version": "0.09", + "version": "0.10", "description": "A pebble style clock to keep the rebellion going", "readme": "README.md", "icon": "pebble.png", diff --git a/apps/pebble/pebble.app.js b/apps/pebble/pebble.app.js index 774b24c3f..729d0a896 100644 --- a/apps/pebble/pebble.app.js +++ b/apps/pebble/pebble.app.js @@ -133,6 +133,8 @@ Bangle.on('lock', function(on) { drawLock(); }); +Bangle.setUI("clock"); + g.clear(); Bangle.loadWidgets(); @@ -149,4 +151,3 @@ loadThemeColors(); setInterval(draw, 15000); // refresh every 15s draw(); -Bangle.setUI("clock"); diff --git a/apps/pebbled/ChangeLog b/apps/pebbled/ChangeLog index 9db0e26c5..8fe3a0b2c 100644 --- a/apps/pebbled/ChangeLog +++ b/apps/pebbled/ChangeLog @@ -1 +1,2 @@ 0.01: first release +0.02: Tell clock widgets to hide. diff --git a/apps/pebbled/metadata.json b/apps/pebbled/metadata.json index c16025f6f..70820f2c8 100644 --- a/apps/pebbled/metadata.json +++ b/apps/pebbled/metadata.json @@ -2,7 +2,7 @@ "id": "pebbled", "name": "Pebble Clock with distance", "shortName": "Pebble + distance", - "version": "0.01", + "version": "0.02", "description": "Fork of Pebble Clock with distance in KM. Both step count and the distance are on the main screen. Default step length = 0.75m (can be changed in settings).", "readme": "README.md", "icon": "pebbled.png", diff --git a/apps/pebbled/pebbled.app.js b/apps/pebbled/pebbled.app.js index bbe98823f..472ac8fff 100644 --- a/apps/pebbled/pebbled.app.js +++ b/apps/pebbled/pebbled.app.js @@ -115,6 +115,7 @@ function getSteps() { return '0'; } +Bangle.setUI("clock"); g.clear(); Bangle.loadWidgets(); /* @@ -126,4 +127,3 @@ for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} loadSettings(); setInterval(draw, 15000); // refresh every 15s draw(); -Bangle.setUI("clock"); diff --git a/apps/planetarium/ChangeLog b/apps/planetarium/ChangeLog index 78288f646..9eedad602 100644 --- a/apps/planetarium/ChangeLog +++ b/apps/planetarium/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Major speed improvement. Added more stars. Up to 500! -0.03: Added more stars and constellations. Now it shows 20 constellations. \ No newline at end of file +0.03: Added more stars and constellations. Now it shows 20 constellations. +0.04: Use default Bangle formatter for booleans diff --git a/apps/planetarium/metadata.json b/apps/planetarium/metadata.json index 00e998634..78add2cea 100644 --- a/apps/planetarium/metadata.json +++ b/apps/planetarium/metadata.json @@ -2,7 +2,7 @@ "id": "planetarium", "name": "Planetarium", "shortName": "Planetarium", - "version": "0.03", + "version": "0.04", "description": "Planetarium showing up to 500 stars using the watch location and time", "icon": "planetarium.png", "tags": "", diff --git a/apps/planetarium/settings.js b/apps/planetarium/settings.js index caadb4016..524901a09 100644 --- a/apps/planetarium/settings.js +++ b/apps/planetarium/settings.js @@ -10,19 +10,16 @@ '< Back': back, 'Star names': { value: !!settings.starnames, - format: v =>v?'On':'Off', onchange: v => { save('starnames',v); }}, 'Constellations': { value: !!settings.constellations, - format: v =>v?'On':'Off', onchange: v => { save('constellations',v); }}, 'Const. names': { value: !!settings.consnames, - format: v =>v?'On':'Off', onchange: v => { save('consnames',v); }}, diff --git a/apps/pongclock/ChangeLog b/apps/pongclock/ChangeLog index 9b83b345f..93217e76f 100644 --- a/apps/pongclock/ChangeLog +++ b/apps/pongclock/ChangeLog @@ -1,2 +1,3 @@ 0.01: First release 0.02: added missing type i metadata +0.03: Use default Bangle formatter for booleans diff --git a/apps/pongclock/metadata.json b/apps/pongclock/metadata.json index c714e9a10..3447893ab 100644 --- a/apps/pongclock/metadata.json +++ b/apps/pongclock/metadata.json @@ -2,7 +2,7 @@ "name": "Pong Clock", "shortName":"Pong Clock", "icon": "pongclock.png", - "version":"0.02", + "version":"0.03", "description": "A Pong playing clock", "type": "clock", "tags": "", diff --git a/apps/pongclock/settings.js b/apps/pongclock/settings.js index 0d61d013d..7e8511da7 100644 --- a/apps/pongclock/settings.js +++ b/apps/pongclock/settings.js @@ -16,24 +16,22 @@ E.showMenu({ "" : { "title" : "Pong Clock" }, "< Back" : () => back(), - 'Widgets?': { - value: !!settings.withWidgets, // !! converts undefined to false - format: v => v?"Show":"Hide", + 'Show Widgets': { + value: !!settings.withWidgets, onchange: v => { settings.withWidgets = v; writeSettings(); } }, 'Inverted?': { - value: !!settings.isInvers, // !! converts undefined to false - format: v => v?"Yes":"No", + value: !!settings.isInvers, onchange: v => { settings.isInvers = v; writeSettings(); } }, 'On Lock?': { - value: !!settings.playLocked, // !! converts undefined to false + value: !!settings.playLocked, format: v => v?"Play":"Pause", onchange: v => { settings.playLocked = v; diff --git a/apps/powermanager/ChangeLog b/apps/powermanager/ChangeLog index 8ccf678de..f0b60a45a 100644 --- a/apps/powermanager/ChangeLog +++ b/apps/powermanager/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Allow forcing monotonic battery voltage/percentage +0.03: Use default Bangle formatter for booleans diff --git a/apps/powermanager/metadata.json b/apps/powermanager/metadata.json index 2bb531099..dd1727657 100644 --- a/apps/powermanager/metadata.json +++ b/apps/powermanager/metadata.json @@ -2,7 +2,7 @@ "id": "powermanager", "name": "Power Manager", "shortName": "Power Manager", - "version": "0.02", + "version": "0.03", "description": "Allow configuration of warnings and thresholds for battery charging and display.", "icon": "app.png", "type": "bootloader", diff --git a/apps/powermanager/settings.js b/apps/powermanager/settings.js index 8af873e5f..7cc683024 100644 --- a/apps/powermanager/settings.js +++ b/apps/powermanager/settings.js @@ -26,14 +26,12 @@ '< Back': back, 'Monotonic percentage': { value: !!settings.forceMonoPercentage, - format: v => settings.forceMonoPercentage ? "On" : "Off", onchange: v => { writeSettings("forceMonoPercentage", v); } }, 'Monotonic voltage': { value: !!settings.forceMonoVoltage, - format: v => settings.forceMonoVoltage ? "On" : "Off", onchange: v => { writeSettings("forceMonoVoltage", v); } diff --git a/apps/presentation_timer/ChangeLog b/apps/presentation_timer/ChangeLog new file mode 100644 index 000000000..2ed460931 --- /dev/null +++ b/apps/presentation_timer/ChangeLog @@ -0,0 +1,2 @@ +0.01: first release +0.02: added interface for configuration from app loader diff --git a/apps/presentation_timer/README.md b/apps/presentation_timer/README.md new file mode 100644 index 000000000..4539fc2f9 --- /dev/null +++ b/apps/presentation_timer/README.md @@ -0,0 +1,47 @@ +# Presentation Timer + +*Forked from Stopwatch Touch* + +Simple application to keep track of slides and +time during a presentation. Useful for conferences, +lectures or any presentation with a somewhat strict timing. + +The interface is pretty simple, it shows a stopwatch +and the number of the current slide (based on the time), +when the time for the last slide is approaching, +the button becomes red, when it passed, +the time will go on for another half a minute and stop automatically. + +You can set personalized timings from the web interface +by uploading a CSV to the bangle (floppy disk button in the app loader). + +Each line in the file (`presentation_timer.csv`) +contains the time in minutes at which the slide +is supposed to finish and the slide number, +separated by a semicolon. +For instance the line `1.5;1` means that slide 1 +is lasting until 1 minutes 30 seconds (yes it's decimal), +after another slide will start. +The only requirement is that timings are increasing, +so slides number don't have to be consecutive, +some can be skipped and they can even be short texts. + +At the moment the app is just quick and dirty +but it should do its job. + +## Screenshots + +![](screenshot1.png) +![](screenshot2.png) +![](screenshot3.png) +![](screenshot4.png) + +## Example configuration file + +_presentation_timer.csv_ +```csv +1.5;1 +2;2 +2.5;3 +3;4 +``` diff --git a/apps/presentation_timer/interface.html b/apps/presentation_timer/interface.html new file mode 100644 index 000000000..137ea8475 --- /dev/null +++ b/apps/presentation_timer/interface.html @@ -0,0 +1,136 @@ + + + + + + + +
+ + + + + +

+
+    
+    
+  
+
diff --git a/apps/presentation_timer/metadata.json b/apps/presentation_timer/metadata.json
new file mode 100644
index 000000000..8790d6208
--- /dev/null
+++ b/apps/presentation_timer/metadata.json
@@ -0,0 +1,17 @@
+{
+  "id": "presentation_timer",
+  "name": "Presentation Timer",
+  "version": "0.02",
+  "description": "A touch based presentation timer for Bangle JS 2",
+  "icon": "presentation_timer.png",
+  "screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot4.png"}],
+  "tags": "tools,app",
+  "supports": ["BANGLEJS2"],
+  "readme": "README.md",
+  "interface": "interface.html",
+  "storage": [
+    {"name":"presentation_timer.app.js","url":"presentation_timer.app.js"},
+    {"name":"presentation_timer.img","url":"presentation_timer.icon.js","evaluate":true}
+  ],
+  "data": [{ "name": "presentation_timer.csv" }]
+}
diff --git a/apps/presentation_timer/presentation_timer.app.js b/apps/presentation_timer/presentation_timer.app.js
new file mode 100644
index 000000000..1d0e5945d
--- /dev/null
+++ b/apps/presentation_timer/presentation_timer.app.js
@@ -0,0 +1,272 @@
+let w = g.getWidth();
+let h = g.getHeight();
+let tTotal = Date.now();
+let tStart = tTotal;
+let tCurrent = tTotal;
+let running = false;
+let timeY = 2*h/5;
+let displayInterval;
+let redrawButtons = true;
+const iconScale = g.getWidth() / 178; // scale up/down based on Bangle 2 size
+
+// 24 pixel images, scale to watch
+// 1 bit optimal, image string, no E.toArrayBuffer()
+const pause_img = atob("GBiBAf////////////////wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP////////////////w==");
+const play_img = atob("GBjBAP//AAAAAAAAAAAIAAAOAAAPgAAP4AAP+AAP/AAP/wAP/8AP//AP//gP//gP//AP/8AP/wAP/AAP+AAP4AAPgAAOAAAIAAAAAAAAAAA=");
+const reset_img = atob("GBiBAf////////////AAD+AAB+f/5+f/5+f/5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+f/5+f/5+f/5+AAB/AAD////////////w==");
+
+const margin = 0.5; //half a minute tolerance
+
+//dummy default values
+var slides = [
+  [1.5, 1],
+  [2, 2],
+  [2.5, 3],
+  [3,4]
+];
+
+function log_debug(o) {
+  //console.log(o);
+}
+
+//first must be a number
+function readSlides() {
+  let csv = require("Storage").read("presentation_timer.csv");
+  if(!csv) return;
+  let lines = csv.split("\n").filter(e=>e);
+  log_debug("Loading "+lines.length+" slides");
+  slides = lines.map(line=>{let s=line.split(";");return [+s[0],s[1]];});
+}
+
+
+function timeToText(t) {
+  let hrs = Math.floor(t/3600000);
+  let mins = Math.floor(t/60000)%60;
+  let secs = Math.floor(t/1000)%60;
+  let tnth = Math.floor(t/100)%10;
+  let text;
+
+  if (hrs === 0)
+    text = ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + "." + tnth;
+  else
+    text = ("0"+hrs) + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2);
+
+  //log_debug(text);
+  return text;
+}
+
+function drawButtons() {
+  log_debug("drawButtons()");
+  if (!running && tCurrent == tTotal) {
+    bigPlayPauseBtn.draw();
+  } else if (!running && tCurrent != tTotal) {
+    resetBtn.draw();
+    smallPlayPauseBtn.draw();
+  } else {
+    bigPlayPauseBtn.draw();
+  }
+
+  redrawButtons = false;
+}
+
+//not efficient but damn easy
+function findSlide(time) {
+  time /= 60000;
+  //change colour for the last 30 seconds
+  if(time > slides[slides.length-1][0] - margin && bigPlayPauseBtn.color!="#f00") {
+    bigPlayPauseBtn.color="#f00";
+    drawButtons();
+  }
+  for(let i=0; i time)
+      return slides[i][1];
+  }
+  //stop automatically
+  if(time > slides[slides.length-1][0] + margin) {
+    bigPlayPauseBtn.color="#0ff"; //restore
+    stopTimer();
+  }
+  return /*LANG*/"end!";
+}
+
+function drawTime() {
+  log_debug("drawTime()");
+  let Tt = tCurrent-tTotal;
+  let Ttxt = timeToText(Tt);
+
+  Ttxt += "\n"+findSlide(Tt);
+  // total time
+  g.setFont("Vector",38);  // check
+  g.setFontAlign(0,0);
+  g.clearRect(0, timeY - 42, w, timeY + 42);
+  g.setColor(g.theme.fg);
+  g.drawString(Ttxt, w/2, timeY);
+}
+
+function draw() {
+  let last = tCurrent;
+  if (running) tCurrent = Date.now();
+  g.setColor(g.theme.fg);
+  if (redrawButtons) drawButtons();
+  drawTime();
+}
+
+function startTimer() {
+  log_debug("startTimer()");
+  draw();
+  displayInterval = setInterval(draw, 100);
+}
+
+function stopTimer() {
+  log_debug("stopTimer()");
+  if (displayInterval) {
+    clearInterval(displayInterval);
+    displayInterval = undefined;
+  }
+}
+
+// BTN stop start
+function stopStart() {
+  log_debug("stopStart()");
+
+  if (running)
+    stopTimer();
+
+  running = !running;
+  Bangle.buzz();
+
+  if (running)
+    tStart = Date.now() + tStart- tCurrent;  
+  tTotal = Date.now() + tTotal - tCurrent;
+  tCurrent = Date.now();
+
+  setButtonImages();
+  redrawButtons = true;
+  if (running) {
+    startTimer();
+  } else {
+    draw();
+  }
+}
+
+function setButtonImages() {
+  if (running) {
+    bigPlayPauseBtn.setImage(pause_img);
+    smallPlayPauseBtn.setImage(pause_img);
+    resetBtn.setImage(reset_img);
+  } else {
+    bigPlayPauseBtn.setImage(play_img);
+    smallPlayPauseBtn.setImage(play_img);
+    resetBtn.setImage(reset_img);
+  }
+}
+
+// lap or reset
+function lapReset() {
+  log_debug("lapReset()");
+  if (!running && tStart != tCurrent) {
+    redrawButtons = true;
+    Bangle.buzz();
+    tStart = tCurrent = tTotal = Date.now();
+    g.clearRect(0,24,w,h);
+    draw();
+  }
+}
+
+// simple on screen button class
+function BUTTON(name,x,y,w,h,c,f,i) {
+  this.name = name;
+  this.x = x;
+  this.y = y;
+  this.w = w;
+  this.h = h;
+  this.color = c;
+  this.callback = f;
+  this.img = i;
+}
+
+BUTTON.prototype.setImage = function(i) {
+  this.img = i;
+}
+
+// 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
+  if (this.img != undefined) {
+    let iw = iconScale * 24;  // the images were loaded as 24 pixels, we will scale
+    let ix = this.x + ((this.w - iw) /2);
+    let iy = this.y + ((this.h - iw) /2);
+    log_debug("g.drawImage(" + ix + "," + iy + "{scale: " + iconScale + "})");
+    g.drawImage(this.img, ix, iy, {scale: iconScale}); 
+  }
+  g.drawRect(this.x, this.y, this.x + this.w, this.y + this.h);
+};
+
+
+var bigPlayPauseBtn = new BUTTON("big",0, 3*h/4 ,w, h/4, "#0ff", stopStart, play_img);
+var smallPlayPauseBtn = new BUTTON("small",w/2, 3*h/4 ,w/2, h/4, "#0ff", stopStart, play_img);
+var resetBtn = new BUTTON("rst",0, 3*h/4, w/2, h/4, "#ff0", lapReset, pause_img);
+
+bigPlayPauseBtn.setImage(play_img);
+smallPlayPauseBtn.setImage(play_img);
+resetBtn.setImage(pause_img);
+
+
+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;
+
+  // not running, and reset
+  if (!running && tCurrent == tTotal && bigPlayPauseBtn.check(x, y)) return;
+
+  // paused and hit play
+  if (!running && tCurrent != tTotal && smallPlayPauseBtn.check(x, y)) return;
+
+  // paused and press reset
+  if (!running && tCurrent != tTotal && resetBtn.check(x, y)) return;
+
+  // must be running
+  if (running && bigPlayPauseBtn.check(x, y)) return;
+});
+
+// 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;
+  }
+});
+
+// Clear the screen once, at startup
+g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
+// above not working, hence using next 2 lines
+g.setColor("#000");
+g.fillRect(0,0,w,h);
+
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+readSlides();
+draw();
+setWatch(() => load(), BTN, { repeat: false, edge: "falling" });
diff --git a/apps/presentation_timer/presentation_timer.icon.js b/apps/presentation_timer/presentation_timer.icon.js
new file mode 100644
index 000000000..f18768b2b
--- /dev/null
+++ b/apps/presentation_timer/presentation_timer.icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwxH+AC1WwIZXACmBF7FWAH4Ae/0WAFiQBF9+sAFgv/AAvXAAgvmFgoyWF6IuLGCIvPFpoxRF5wlIwIKJF8lWwIvjQpIvKGBgv8cpWBF5QwLF/4vrEJQvNGBQv/F5FWSCq/XGAVWB5DviEgRiJF8gxDF9q+SF5owWEJYv9GCggMF5wwSD5ovPGCAeOF6AwODp4vRGJYbRF6YAbF/4v/F8eBAYYECAYYvRACFWqwEGwNWwIeSF7IEFAD5VBGhpekAo6QiEYo1LR0QpGBgyOhAxCQfKIIhFGxpegA44+HF85gRA=="))
diff --git a/apps/presentation_timer/presentation_timer.png b/apps/presentation_timer/presentation_timer.png
new file mode 100644
index 000000000..7db9866d7
Binary files /dev/null and b/apps/presentation_timer/presentation_timer.png differ
diff --git a/apps/presentation_timer/screenshot1.png b/apps/presentation_timer/screenshot1.png
new file mode 100644
index 000000000..d26720d7e
Binary files /dev/null and b/apps/presentation_timer/screenshot1.png differ
diff --git a/apps/presentation_timer/screenshot2.png b/apps/presentation_timer/screenshot2.png
new file mode 100644
index 000000000..cbd6f0bd1
Binary files /dev/null and b/apps/presentation_timer/screenshot2.png differ
diff --git a/apps/presentation_timer/screenshot3.png b/apps/presentation_timer/screenshot3.png
new file mode 100644
index 000000000..40b375b37
Binary files /dev/null and b/apps/presentation_timer/screenshot3.png differ
diff --git a/apps/presentation_timer/screenshot4.png b/apps/presentation_timer/screenshot4.png
new file mode 100644
index 000000000..7c43cf91f
Binary files /dev/null and b/apps/presentation_timer/screenshot4.png differ
diff --git a/apps/ptlaunch/ChangeLog b/apps/ptlaunch/ChangeLog
index eec3610ed..5871b1fdc 100644
--- a/apps/ptlaunch/ChangeLog
+++ b/apps/ptlaunch/ChangeLog
@@ -6,3 +6,4 @@
 0.12: Improve pattern detection code readability by PaddeK http://forum.espruino.com/profiles/117930/
 0.13: Improve pattern rendering by HughB http://forum.espruino.com/profiles/167235/
 0.14: Update setUI to work with new Bangle.js 2v13 menu style
+0.15: Update to support clocks in custom setUI mode
diff --git a/apps/ptlaunch/boot.js b/apps/ptlaunch/boot.js
index 748d564f3..885962761 100644
--- a/apps/ptlaunch/boot.js
+++ b/apps/ptlaunch/boot.js
@@ -76,13 +76,8 @@
   var sui = Bangle.setUI;
   Bangle.setUI = function (mode, cb) {
     sui(mode, cb);
-    if ("object"==typeof mode) mode = mode.mode;
-    if (!mode) {
-      Bangle.removeListener("drag", dragHandler);
-      storedPatterns = {};
-      return;
-    }
-    if (!mode.startsWith("clock")) {
+    if (typeof mode === "object") mode = (mode.clock ? "clock" : "") + mode.mode;
+    if (!mode || !mode.startsWith("clock")) {
       storedPatterns = {};
       Bangle.removeListener("drag", dragHandler);
       return;
diff --git a/apps/ptlaunch/metadata.json b/apps/ptlaunch/metadata.json
index 0b6dce3d1..6f8a9e16f 100644
--- a/apps/ptlaunch/metadata.json
+++ b/apps/ptlaunch/metadata.json
@@ -2,7 +2,7 @@
   "id": "ptlaunch",
   "name": "Pattern Launcher",
   "shortName": "Pattern Launcher",
-  "version": "0.14",
+  "version": "0.15",
   "description": "Directly launch apps from the clock screen with custom patterns.",
   "icon": "app.png",
   "screenshots": [{"url":"manage_patterns_light.png"}],
diff --git a/apps/qalarm/ChangeLog b/apps/qalarm/ChangeLog
index b9be6039d..173765967 100644
--- a/apps/qalarm/ChangeLog
+++ b/apps/qalarm/ChangeLog
@@ -4,3 +4,4 @@
       Fix app icon
       Change menu order so 'back' is at the top
 0.04: Fix alarm not activating sometimes.
+0.05: Use default Bangle formatter for booleans
diff --git a/apps/qalarm/app.js b/apps/qalarm/app.js
index ad071adf0..b04a635b5 100644
--- a/apps/qalarm/app.js
+++ b/apps/qalarm/app.js
@@ -115,22 +115,18 @@ function showEditAlarmMenu(alarmIndex, alarm) {
     },
     Enabled: {
       value: alarm.on,
-      format: (v) => (v ? "On" : "Off"),
       onchange: (v) => (alarm.on = v),
     },
     Repeat: {
       value: alarm.rp,
-      format: (v) => (v ? "Yes" : "No"),
       onchange: (v) => (alarm.rp = v),
     },
     "Auto snooze": {
       value: alarm.as,
-      format: (v) => (v ? "Yes" : "No"),
       onchange: (v) => (alarm.as = v),
     },
     Hard: {
       value: alarm.hard,
-      format: (v) => (v ? "Yes" : "No"),
       onchange: (v) => (alarm.hard = v),
     },
     "Days of week": () => showDaysMenu(alarmIndex, getAlarm()),
@@ -175,7 +171,6 @@ function showDaysMenu(alarmIndex, alarm) {
     let dayOfWeek = require("locale").dow({ getDay: () => i });
     menu[dayOfWeek] = {
       value: alarm.daysOfWeek[i],
-      format: (v) => (v ? "Yes" : "No"),
       onchange: (v) => (alarm.daysOfWeek[i] = v),
     };
   }
@@ -235,12 +230,10 @@ function showEditTimerMenu(timerIndex) {
     },
     Enabled: {
       value: alarm.on,
-      format: (v) => (v ? "On" : "Off"),
       onchange: (v) => (alarm.on = v),
     },
     Hard: {
       value: alarm.hard,
-      format: (v) => (v ? "On" : "Off"),
       onchange: (v) => (alarm.hard = v),
     },
   };
diff --git a/apps/qalarm/metadata.json b/apps/qalarm/metadata.json
index 2039af4bf..841c10e6f 100644
--- a/apps/qalarm/metadata.json
+++ b/apps/qalarm/metadata.json
@@ -3,7 +3,7 @@
   "name": "Q Alarm and Timer",
   "shortName": "Q Alarm",
   "icon": "app.png",
-  "version": "0.04",
+  "version": "0.05",
   "description": "[Not recommended - use 'Alarm & Timer' app] Alarm and timer app with days of week and 'hard' option.",
   "tags": "tool,alarm,widget",
   "supports": ["BANGLEJS", "BANGLEJS2"],
diff --git a/apps/qmsched/ChangeLog b/apps/qmsched/ChangeLog
index 94fcffe1a..88185f337 100644
--- a/apps/qmsched/ChangeLog
+++ b/apps/qmsched/ChangeLog
@@ -6,4 +6,5 @@
 0.06: Fix: don't try to redraw widget when widgets not loaded
 0.07: Option to switch theme
       Changed time selection to 5-minute intervals
-0.08: Support new Bangle.js 2 menu
\ No newline at end of file
+0.08: Support new Bangle.js 2 menu
+0.09: Use default Bangle formatter for booleans
diff --git a/apps/qmsched/app.js b/apps/qmsched/app.js
index 8cd0fa8d9..da43dd7d6 100644
--- a/apps/qmsched/app.js
+++ b/apps/qmsched/app.js
@@ -125,7 +125,6 @@ function showMainMenu() {
   menu[/*LANG*/"Add Schedule"] = () => showEditMenu(-1);
   menu[/*LANG*/"Switch Theme"] = {
     value: !!get("switchTheme"),
-    format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
     onchange: v => v ? set("switchTheme", v) : unset("switchTheme"),
   };
   menu[/*LANG*/"LCD Settings"] = () => showOptionsMenu();
diff --git a/apps/qmsched/metadata.json b/apps/qmsched/metadata.json
index 326a8fc4f..23bdbd2e4 100644
--- a/apps/qmsched/metadata.json
+++ b/apps/qmsched/metadata.json
@@ -2,7 +2,7 @@
   "id": "qmsched",
   "name": "Quiet Mode Schedule and Widget",
   "shortName": "Quiet Mode",
-  "version": "0.08",
+  "version": "0.09",
   "description": "Automatically turn Quiet Mode on or off at set times, change theme and LCD options while Quiet Mode is active.",
   "icon": "app.png",
   "screenshots": [{"url":"screenshot_b1_main.png"},{"url":"screenshot_b1_edit.png"},{"url":"screenshot_b1_lcd.png"},
diff --git a/apps/qrcode/ChangeLog b/apps/qrcode/ChangeLog
index 6d9cc0569..52eadbcf9 100644
--- a/apps/qrcode/ChangeLog
+++ b/apps/qrcode/ChangeLog
@@ -3,3 +3,4 @@
 0.03: Forces integer scaling and adds more configuration (error correction, description, display)
 0.04: Allow scanning of QR codes from camera or file
 0.05: Change brightness on touch
+0.06: Add ability to generate contact info (MeCard format) QR code
diff --git a/apps/qrcode/custom.html b/apps/qrcode/custom.html
index 7ae3eb3af..9955ea6c9 100644
--- a/apps/qrcode/custom.html
+++ b/apps/qrcode/custom.html
@@ -8,6 +8,8 @@
     

+ +

@@ -64,6 +66,14 @@ +
+

First Name:

+

Last Name:

+

Phone Number:

+

Email:

+

Website:

+
+

Try your QR Code:

@@ -156,7 +166,7 @@ function toggleVis(id){ console.info("Got id", id); - ["srcScanFile", "srcText", "srcWifi", "srcScanCam"].forEach(function (item){ + ["srcScanFile", "srcText", "srcWifi", "srcScanCam", "srcMeCard"].forEach(function (item){ document.getElementById(item).style.display = "none"; }); if (id != undefined && id != null) document.getElementById(id).style.display = "block"; @@ -188,6 +198,37 @@ } return qrstring; } + + function generateMeCardString(meNameFirst, meNameLast, mePhoneNumber, meEmail, meWebsite){ + var meCardStringOutput = 'MECARD:'; + + //first & Last name part of string, can have one or both + if (meNameFirst.trim().length != 0 && meNameLast.trim().length != 0) { + meCardStringOutput += 'N:'+meNameLast.trim()+','+meNameFirst.trim()+';'; + } + else if (meNameLast.trim().length != 0) { + meCardStringOutput += 'N:'+meNameLast.trim()+';'; + } + else if (meNameFirst.trim().length != 0) { + meCardStringOutput += 'N:'+meNameFirst.trim()+';'; + } + + if (mePhoneNumber.trim().length != 0) { + meCardStringOutput += 'TEL:'+mePhoneNumber.trim()+';'; + } + + if (meEmail.trim().length != 0) { + meCardStringOutput += 'EMAIL:'+meEmail.trim()+';'; + } + + if (meWebsite.trim().length != 0) { + meCardStringOutput += 'URL:'+meWebsite.trim()+';'; + } + + meCardStringOutput += ';'; + return meCardStringOutput; + } + function refreshQRCode(){ if (qrcode == null){ qrcode = new QRCode("qrcode", { @@ -206,6 +247,14 @@ const hidden = document.getElementById("hidden").checked; const wifiString = generateWifiString(ssid, password, hidden, encryption); qrText= wifiString; + } else if (document.getElementById("useMECARD").checked) { + const meNameFirst = document.getElementById("meNameFirst").value; + const meNameLast = document.getElementById("meNameLast").value; + const mePhoneNumber = document.getElementById("mePhoneNumber").value; + const meEmail = document.getElementById("meEmail").value; + const meWebsite = document.getElementById("meWebsite").value; + const meCardString = generateMeCardString(meNameFirst, meNameLast, mePhoneNumber, meEmail, meWebsite); + qrText = meCardString; } else if (document.getElementById("useCAM").checked) { qrText= document.getElementById("camQrResult").innerText; } else if (document.getElementById("useFILE").checked) { @@ -258,6 +307,14 @@ } document.getElementById("useTEXT").addEventListener("change",function(){toggleVis("srcText");}); + + document.getElementById("useMECARD").addEventListener("change",function(){toggleVis("srcMeCard");}); + document.getElementById("meNameFirst").addEventListener("change",refreshQRCode); + document.getElementById("meNameLast").addEventListener("change",refreshQRCode); + document.getElementById("mePhoneNumber").addEventListener("change",refreshQRCode); + document.getElementById("meEmail").addEventListener("change",refreshQRCode); + document.getElementById("meWebsite").addEventListener("change",refreshQRCode); + document.getElementById("useCAM").addEventListener("change",function(){ initQrScanner(); initQrCam(); @@ -314,7 +371,6 @@ g.setColor(1,1,1); }); - document.getElementById('camList').addEventListener('change', event => { scanner.setCamera(event.target.value).then(updateFlashAvailability); }); diff --git a/apps/qrcode/metadata.json b/apps/qrcode/metadata.json index 22f8f7b53..24af7b813 100644 --- a/apps/qrcode/metadata.json +++ b/apps/qrcode/metadata.json @@ -1,7 +1,7 @@ { "id": "qrcode", "name": "Custom QR Code", - "version": "0.05", + "version": "0.06", "description": "Use this to upload a customised QR code to Bangle.js", "icon": "app.png", "tags": "qrcode", diff --git a/apps/ratchet_launch/metadata.json b/apps/ratchet_launch/metadata.json index 14ffec34a..45057b0b9 100644 --- a/apps/ratchet_launch/metadata.json +++ b/apps/ratchet_launch/metadata.json @@ -11,6 +11,5 @@ "storage": [ {"name":"ratchet_launch.app.js","url":"app.js"} ], - "sortorder": -10, "readme":"README.md" } diff --git a/apps/rclock/ChangeLog b/apps/rclock/ChangeLog index 915fbc5d7..0a86ee3e3 100644 --- a/apps/rclock/ChangeLog +++ b/apps/rclock/ChangeLog @@ -5,3 +5,4 @@ 0.05: Changes which circle show minutes and seconds 0.06: Avoid function wrapper, use setUI for launcher Clock face smaller so no longer breaks widgets +0.07: Tell clock widgets to hide. diff --git a/apps/rclock/metadata.json b/apps/rclock/metadata.json index 77a036481..e5478aa6a 100644 --- a/apps/rclock/metadata.json +++ b/apps/rclock/metadata.json @@ -2,7 +2,7 @@ "id": "rclock", "name": "Round clock with seconds, minutes and date", "shortName": "Round Clock", - "version": "0.06", + "version": "0.07", "description": "Designed round clock with ticks for minutes and seconds and heart rate indication", "icon": "app.png", "type": "clock", diff --git a/apps/rclock/rclock.app.js b/apps/rclock/rclock.app.js index 9c219ab3d..2d6f84dc4 100644 --- a/apps/rclock/rclock.app.js +++ b/apps/rclock/rclock.app.js @@ -196,6 +196,9 @@ const drawHR = function () { } }; +// Show launcher when button pressed +Bangle.setUI("clock"); + // clean app screen g.clear(); Bangle.loadWidgets(); @@ -222,5 +225,3 @@ Bangle.on('HRM', function (d) { // draw now drawClock(); -// Show launcher when button pressed -Bangle.setUI("clock"); diff --git a/apps/rebble/ChangeLog b/apps/rebble/ChangeLog index 4e2e76484..cb2108e8b 100644 --- a/apps/rebble/ChangeLog +++ b/apps/rebble/ChangeLog @@ -6,4 +6,5 @@ 0.06: Add 12h support and autocycle control 0.07: added localization, removed deprecated code 0.08: removed unused font, fix autocycle, imported suncalc and trimmed, removed pedometer dependency, "tap to cycle" setting -0.09: fix battery icon size \ No newline at end of file +0.09: fix battery icon size +0.10: Tell clock widgets to hide. diff --git a/apps/rebble/metadata.json b/apps/rebble/metadata.json index ec7650f53..91d66df3d 100644 --- a/apps/rebble/metadata.json +++ b/apps/rebble/metadata.json @@ -2,7 +2,7 @@ "id": "rebble", "name": "Rebble Clock", "shortName": "Rebble", - "version": "0.09", + "version": "0.10", "description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion", "readme": "README.md", "icon": "rebble.png", diff --git a/apps/rebble/rebble.app.js b/apps/rebble/rebble.app.js index fc91fe0ac..1102aa93f 100644 --- a/apps/rebble/rebble.app.js +++ b/apps/rebble/rebble.app.js @@ -292,18 +292,6 @@ function queueDraw() { log_debug("starting.."); -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="";} -loadSettings(); -loadLocation(); - - if(settings.autoCycle || settings.sideTap==0) { @@ -318,6 +306,19 @@ else{ } +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="";} +loadSettings(); +loadLocation(); + + + draw(); // queues the next draw for a minutes time @@ -331,4 +332,4 @@ Bangle.on('charging', function(charging) { drawSideBar2(); break; } -}); \ No newline at end of file +}); diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog index 140567068..1941a435b 100644 --- a/apps/recorder/ChangeLog +++ b/apps/recorder/ChangeLog @@ -20,3 +20,4 @@ 0.14: Remove unneeded variable assignment 0.15: Show distance more accurately in conjunction with new locale app (fix #1523) 0.16: Ability to append to existing track (fix #1712) +0.17: Use default Bangle formatter for booleans diff --git a/apps/recorder/app.js b/apps/recorder/app.js index fb3dfab4f..9006d2236 100644 --- a/apps/recorder/app.js +++ b/apps/recorder/app.js @@ -40,11 +40,9 @@ function getTrackNumber(filename) { } function showMainMenu() { - function boolFormat(v) { return v?"Yes":"No"; } function menuRecord(id) { return { value: settings.record.includes(id), - format: boolFormat, onchange: v => { settings.recording = false; // stop recording if we change anything settings.record = settings.record.filter(r=>r!=id); diff --git a/apps/recorder/interface.html b/apps/recorder/interface.html index 8cf339e85..cc9762d20 100644 --- a/apps/recorder/interface.html +++ b/apps/recorder/interface.html @@ -12,9 +12,22 @@ + + + \ No newline at end of file diff --git a/apps/sleeplog/lib.js b/apps/sleeplog/lib.js index 752139e27..1919e7483 100644 --- a/apps/sleeplog/lib.js +++ b/apps/sleeplog/lib.js @@ -1,199 +1,465 @@ +// define accessable functions exports = { // define en-/disable function, restarts the service to make changes take effect - setEnabled: function(enable, logfile, powersaving) { - // check if sleeplog is available - if (typeof global.sleeplog !== "object") return; - - // set default logfile - if ((typeof logfile !== "string" || !logfile.endsWith(".log")) && - logfile !== false) logfile = "sleeplog.log"; - + setEnabled: function(enable) { // stop if enabled - if (global.sleeplog.enabled) global.sleeplog.stop(); + if (global.sleeplog && sleeplog.enabled) sleeplog.stop(); - // define storage and filename - var storage = require("Storage"); - var filename = "sleeplog.json"; + // define settings filename + var settings = "sleeplog.json"; // change enabled value in settings - storage.writeJSON(filename, Object.assign(storage.readJSON(filename, true) || {}, { - enabled: enable, - logfile: logfile, - powersaving: powersaving || false - })); + require("Storage").writeJSON(settings, Object.assign( + require("Storage").readJSON(settings, true) || {}, { + enabled: enable + } + )); // force changes to take effect by executing the boot script - eval(storage.read("sleeplog.boot.js")); + eval(require("Storage").read("sleeplog.boot.js")); - // clear variables - storage = undefined; - filename = undefined; return true; }, - // define read log function - // sorting: latest first, format: - // [[number, int, float, string], [...], ... ] - // - number // timestamp in ms - // - int // status: 0 = unknown, 1 = not worn, 2 = awake, 3 = sleeping - // - float // internal temperature - // - string // additional information - readLog: function(logfile, since, until) { - // check/set logfile - if (typeof logfile !== "string" || !logfile.endsWith(".log")) { - logfile = (global.sleeplog || {}).logfile || "sleeplog.log"; + // define read log function, returns log array + // sorting: ascending (latest first), format: + // [[number, int, int], [...], ... ] + // - number // timestamp in 10min + // - int // status: 0 = unknown, 1 = not worn, 2 = awake, 3 = light sleep, 4 = deep sleep + // - int // consecutive: 0 = unknown, 1 = no consecutive sleep, 2 = consecutive sleep + readLog: function(since, until) { + // set now and check if now is before since + var now = Date.now(); + if (now < since) return []; + + // set defaults and convert since, until and now to 10min steps + since = Math.floor((since || 0) / 6E5); + until = Math.ceil((until || now) / 6E5); + now = Math.ceil(now / 6E5); + + // define output log + var log = []; + + // open StorageFile + var file = require("Storage").open("sleeplog.log", "r"); + // cache StorageFile size + var storageFileSize = file.getLength(); + // check if a Storage File needs to be read + if (storageFileSize) { + // define previous line cache + var prevLine; + // loop through StorageFile entries + while (true) { + // cache new line + var line = file.readLine(); + // exit loop if all lines are read + if (!line) break; + // skip lines starting with "," + if (line.startsWith(",")) continue; + // parse line + line = line.trim().split(",").map(e => parseInt(e)); + // exit loop if new line timestamp is not before until + if (line[0] >= until) break; + // check if new line timestamp is 24h before since or not after since + if (line[0] + 144 < since) { + // skip roughly the next 10 lines + file.read(118); + file.readLine(); + } else if (line[0] <= since) { + // cache line for next cycle + prevLine = line; + } else { + // add previous line if it was cached + if (prevLine) log.push(prevLine); + // add new line at the end of log + log.push(line); + // clear previous line cache + prevLine = undefined; + } + } + // add previous line if it was cached + if (prevLine) log.push(prevLine); + // set unknown consecutive statuses + log = log.reverse().map((entry, index) => { + if (entry[2] === 0) entry[2] = (log[index - 1] || [])[2] || 0; + return entry; + }).reverse(); + // remove duplicates + log = log.filter((entry, index) => + !(index > 0 && entry[1] === log[index - 1][1] && entry[2] === log[index - 1][2]) + ); } - // check if since is in the future - if (since > Date()) return []; + // check if log empty or first entry is after since + if (!log[0] || log[0][0] > since) { + // look for all needed storage files + var files = require("Storage").list(/^sleeplog_\d\d\d\d\.log$/, { + sf: false + }); - // read logfile - var log = require("Storage").read(logfile); - // return empty log - if (!log) return []; - // decode data if needed - if (log[0] !== "[") log = atob(log); - // do a simple check before parsing - if (!log.startsWith("[[") || !log.endsWith("]]")) return []; - log = JSON.parse(log) || []; + // check if any file available + if (files.length) { + // generate start and end times in 10min steps + files = files.map(file => { + var start = this.fnToMs(parseInt(file.substr(9, 4))) / 6E5; + return { + name: file, + start: start, + end: start + 2016 + }; + }).sort((a, b) => b.start - a.start); - // check if filtering is needed - if (since || until) { - // search for latest entry befor since - if (since) since = (log.find(element => element[0] <= since) || [0])[0]; - // filter selected time period - log = log.filter(element => (element[0] >= since) && (element[0] <= (until || 1E14))); + // read all neccessary files + var filesLog = []; + files.some(file => { + // exit loop if since after end + if (since >= file.end) return true; + // read file if until after start and since before end + if (until > file.start || since < file.end) { + var thisLog = require("Storage").readJSON(file.name, 1) || []; + if (thisLog.length) filesLog = thisLog.concat(filesLog); + } + }); + // free ram + files = undefined; + + // check if log from files is available + if (filesLog.length) { + // remove unwanted entries + filesLog = filesLog.filter((entry, index, filesLog) => ( + (filesLog[index + 1] || [now])[0] >= since && entry[0] <= until + )); + // add to log as previous entries + log = filesLog.concat(log); + } + // free ram + filesLog = undefined; + } } - // output log + // define last index + var lastIndex = log.length - 1; + // set timestamp of first entry to since if first entry before since + if (log[0] && log[0][0] < since) log[0][0] = since; + // add timestamp at now with unknown status if until after now + if (until > now) log.push([now, 0, 0]); + return log; }, - // define write log function, append or replace log depending on input - // append input if array length >1 and element[0] >9E11 - // replace log with input if at least one entry like above is inside another array - writeLog: function(logfile, input) { - // check/set logfile - if (typeof logfile !== "string" || !logfile.endsWith(".log")) { - if (!global.sleeplog || sleeplog.logfile === false) return; - logfile = sleeplog.logfile || "sleeplog.log"; + // define move log function, move StorageFile content into files seperated by fortnights + moveLog: function(force) { + /** convert old logfile (< v0.10) if present **/ + if (require("Storage").list("sleeplog.log", { + sf: false + }).length) { + convertOldLog(); } + /** may be removed in later versions **/ - // check if input is an array - if (typeof input !== "object" || typeof input.length !== "number") return; + // first day of this fortnight period + var thisFirstDay = this.fnToMs(this.msToFn(Date.now())); - // check for entry plausibility - if (input.length > 1 && input[0] * 1 > 9E11) { - // read log - var log = this.readLog(logfile); + // read timestamp of the first StorageFile entry + var firstDay = (require("Storage").open("sleeplog.log", "r").read(47) || "").match(/\n\d*/); + // calculate the first day of the fortnight period + if (firstDay) firstDay = this.fnToMs(this.msToFn(parseInt(firstDay[0].trim()) * 6E5)); - // remove last state if it was unknown and less then 5min ago - if (log.length > 0 && log[0][1] === 0 && - Math.floor(Date.now()) - log[0][0] < 3E5) log.shift(); + // check if moving is neccessary or forced + if (force || firstDay && firstDay < thisFirstDay) { + // read log for each fortnight period + while (firstDay) { + // calculate last day + var lastDay = firstDay + 12096E5; + // read log of the fortnight period + var log = require("sleeplog").readLog(firstDay, lastDay); - // add entry at the first position if it has changed - if (log.length === 0 || input.some((e, index) => index > 0 && input[index] !== log[0][index])) log.unshift(input); + // check if before this fortnight period + if (firstDay < thisFirstDay) { + // write log in seperate file + require("Storage").writeJSON("sleeplog_" + this.msToFn(firstDay) + ".log", log); + // set last day as first + firstDay = lastDay; + } else { + // rewrite StorageFile + require("Storage").open("sleeplog.log", "w").write(log.map(e => e.join(",")).join("\n")); + // clear first day to exit loop + firstDay = undefined; + } - // map log as input - input = log; - } - - // check and if neccessary reduce logsize to prevent low mem - if (input.length > 750) input = input.slice(-750); - - // simple check for log plausibility - if (input[0].length > 1 && input[0][0] * 1 > 9E11) { - // write log to storage - require("Storage").write(logfile, btoa(JSON.stringify(input))); - return true; - } - }, - - // define log to humanreadable string function - // sorting: latest last, format: - // "{substring of ISO date} - {status} for {duration}min\n..." - getReadableLog: function(printLog, since, until, logfile) { - // read log and check - var log = this.readLog(logfile, since, until); - if (!log.length) return; - // reverse array to set last timestamp to the end - log.reverse(); - - // define status description and log string - var statusText = ["unknown ", "not worn", "awake ", "sleeping"]; - var logString = []; - - // rewrite each entry - log.forEach((element, index) => { - logString[index] = "" + - Date(element[0] - Date().getTimezoneOffset() * 6E4).toISOString().substr(0, 19).replace("T", " ") + " - " + - statusText[element[1]] + - (index === log.length - 1 ? - element.length < 3 ? "" : " ".repeat(12) : - " for " + ("" + Math.round((log[index + 1][0] - element[0]) / 60000)).padStart(4) + "min" - ) + - (element[2] ? " | Temp: " + ("" + element[2]).padEnd(5) + "°C" : "") + - (element[3] ? " | " + element[3] : ""); - }); - logString = logString.join("\n"); - - // if set print and return string - if (printLog) { - print(logString); - print("- first", Date(log[0][0])); - print("- last", Date(log[log.length - 1][0])); - var period = log[log.length - 1][0] - log[0][0]; - print("- period= " + Math.floor(period / 864E5) + "d " + Math.floor(period % 864E5 / 36E5) + "h " + Math.floor(period % 36E5 / 6E4) + "min"); - } - return logString; - }, - - // define function to eliminate some errors inside the log - restoreLog: function(logfile) { - // read log and check - var log = this.readLog(logfile); - if (!log.length) return; - - // define output variable to show number of changes - var output = log.length; - - // remove non decremental entries - log = log.filter((element, index) => log[index][0] >= (log[index + 1] || [0])[0]); - - // write log - this.writeLog(logfile, log); - - // return difference in length - return output - log.length; - }, - - // define function to reinterpret worn status based on given temperature threshold - reinterpretTemp: function(logfile, tempthresh) { - // read log and check - var log = this.readLog(logfile); - if (!log.length) return; - - // set default tempthresh - tempthresh = tempthresh || (global.sleeplog ? sleeplog.tempthresh : 27); - - // define output variable to show number of changes - var output = 0; - - // remove non decremental entries - log = log.map(element => { - if (element[2]) { - var tmp = element[1]; - element[1] = element[2] > tempthresh ? 3 : 1; - if (tmp !== element[1]) output++; + // free ram + log = undefined; } - return element; + } + }, + + // define function to return stats from the last date [ms] for a specific duration [ms] or for the complete log + getStats: function(until, duration, log) { + // define stats variable + var stats = { + calculatedAt: // [date] timestamp of the calculation + Math.round(Date.now()), + deepSleep: 0, // [min] deep sleep duration + lightSleep: 0, // [min] light sleep duration + awakeSleep: 0, // [min] awake duration inside consecutive sleep + consecSleep: 0, // [min] consecutive sleep duration + awakeTime: 0, // [min] awake duration outside consecutive sleep + notWornTime: 0, // [min] duration of not worn status + unknownTime: 0, // [min] duration of unknown status + logDuration: 0, // [min] duration of all entries taken into account + firstDate: undefined, // [date] first entry taken into account + lastDate: undefined // [date] last entry taken into account + }; + + // set default inputs + until = until || stats.calculatedAt; + if (!duration) duration = 864E5; + + // read log for the specified duration or complete log if not handed over + if (!log) log = this.readLog(duration ? until - duration : 0, until); + + // check if log not empty or corrupted + if (log && log.length && log[0] && log[0].length === 3) { + // calculate and set first log date from 10min steps + stats.firstDate = log[0][0] * 6E5; + stats.lastDate = log[log.length - 1][0] * 6E5; + + // cycle through log to calculate sums til end or duration is exceeded + log.forEach((entry, index, log) => { + // calculate duration of this entry from 10min steps to minutes + var duration = ((log[index + 1] || [until / 6E5 | 0])[0] - entry[0]) * 10; + + // check if duration greater 0 + if (duration) { + // calculate sums + if (entry[1] === 4) stats.deepSleep += duration; + else if (entry[1] === 3) stats.lightSleep += duration; + else if (entry[1] === 2) { + if (entry[2] === 2) stats.awakeSleep += duration; + else if (entry[2] === 1) stats.awakeTime += duration; + } + if (entry[2] === 2) stats.consecSleep += duration; + if (entry[1] === 1) stats.notWornTime += duration; + if (entry[1] === 0) stats.unknownTime += duration; + stats.logDuration += duration; + } + }); + } + + // free ram + log = undefined; + + // return stats of the last day + return stats; + }, + + // define function to return last break time of day from date or now (default: 12 o'clock) + getLastBreak: function(date, ToD) { + // set default date or correct date type if needed + if (!date || !date.getDay) date = date ? new Date(date) : new Date(); + // set default ToD as set in sleeplog.conf or settings if available + if (ToD === undefined) ToD = (global.sleeplog && sleeplog.conf ? sleeplog.conf.breakToD : + (require("Storage").readJSON("sleeplog.json", true) || {}).breakToD) || 12; + // calculate last break time and return + return new Date(date.getFullYear(), date.getMonth(), date.getDate(), ToD); + }, + + // define functions to convert ms to the number of fortnights since the first Sunday at noon: 1970-01-04T12:00 + fnToMs: function(no) { + return (no + 0.25) * 12096E5; + }, + msToFn: function(ms) { + return (ms / 12096E5 - 0.25) | 0; + }, + + // define set debug function, options: + // enable as boolean, start/stop debugging + // duration in hours, generate csv log if set, max: 96h + setDebug: function(enable, duration) { + // check if global variable accessable + if (!global.sleeplog) return new Error("sleeplog: Can't set debugging, global object missing!"); + + // check if nothing has to be changed + if (!duration && + (enable && sleeplog.debug === true) || + (!enable && !sleeplog.debug)) return; + + // check if en- or disable debugging + if (enable) { + // define debug object + sleeplog.debug = {}; + + // check if a file should be generated + if (typeof duration === "number") { + // check duration boundaries, 0 => 8 + duration = duration > 96 ? 96 : duration || 12; + // calculate and set writeUntil in 10min steps + sleeplog.debug.writeUntil = ((Date.now() / 6E5 | 0) + duration * 6) * 6E5; + // set fileid to "{hours since 1970}" + sleeplog.debug.fileid = Date.now() / 36E5 | 0; + // write csv header on empty file + var file = require("Storage").open("sleeplog_" + sleeplog.debug.fileid + ".csv", "a"); + if (!file.getLength()) file.write( + "timestamp,movement,status,consecutive,asleepSince,awakeSince,bpm,bpmConfidence\n" + ); + // free ram + file = undefined; + } else { + // set debug as active + sleeplog.debug = true; + } + } else { + // disable debugging + delete sleeplog.debug; + } + + // save status forced + sleeplog.saveStatus(true); + }, + + // define debugging function, called after logging if debug is set + debug: function(data) { + // check if global variable accessable and debug active + if (!global.sleeplog || !sleeplog.debug) return; + + // set functions to convert timestamps + function localTime(timestamp) { + return timestamp ? Date(timestamp).toString().split(" ")[4].substr(0, 5) : "- - -"; + } + function officeTime(timestamp) { + // days since 30.12.1899 + return timestamp / 864E5 + 25569; + } + + // generate console output + var console = "sleeplog: " + + localTime(data.timestamp) + " > " + + "movement: " + ("" + data.movement).padStart(4) + ", " + + "unknown ,non consec.,consecutive".split(",")[sleeplog.consecutive] + " " + + "unknown,not worn,awake,light sleep,deep sleep".split(",")[data.status].padEnd(12) + ", " + + "asleep since: " + localTime(sleeplog.info.asleepSince) + ", " + + "awake since: " + localTime(sleeplog.info.awakeSince); + // add bpm if set + if (data.bpm) console += ", " + + "bpm: " + ("" + data.bpm).padStart(3) + ", " + + "confidence: " + data.bpmConfidence; + // output to console + print(console); + + // check if debug is set as object with a file id and it is not past writeUntil + if (typeof sleeplog.debug === "object" && sleeplog.debug.fileid && + Date.now() < sleeplog.debug.writeUntil) { + // generate next csv line + var csv = [ + officeTime(data.timestamp), + data.movement, + data.status, + sleeplog.consecutive, + sleeplog.info.asleepSince ? officeTime(sleeplog.info.asleepSince) : "", + sleeplog.info.awakeSince ? officeTime(sleeplog.info.awakeSince) : "", + data.bpm || "", + data.bpmConfidence || "" + ].join(","); + // write next line to log if set + require("Storage").open("sleeplog_" + sleeplog.debug.fileid + ".csv", "a").write(csv + "\n"); + } else { + // clear file setting in debug + sleeplog.debug = true; + } + + }, + + // print log as humanreadable output similar to debug output + printLog: function(since, until) { + // set default until + until = until || Date.now(); + // print each entry inside log + this.readLog(since, until).forEach((entry, index, log) => { + // calculate duration of this entry from 10min steps to minutes + var duration = ((log[index + 1] || [until / 6E5 | 0])[0] - entry[0]) * 10; + // print this entry + print((index + ")").padStart(4) + " " + + Date(entry[0] * 6E5).toString().substr(0, 21) + " > " + + "unknown ,non consec.,consecutive".split(",")[entry[2]] + " " + + "unknown,not worn,awake,light sleep,deep sleep".split(",")[entry[1]].padEnd(12) + + "for" + (duration + "min").padStart(8)); }); + }, - // write log - this.writeLog(logfile, log); + /** convert old (< v0.10) to new logfile data **/ + convertOldLog: function() { + // read old logfile + var oldLog = require("Storage").read("sleeplog.log") || ""; + // decode data if needed + if (!oldLog.startsWith("[")) oldLog = atob(oldLog); + // delete old logfile and return if it is empty or corrupted + if (!oldLog.startsWith("[[") || !oldLog.endsWith("]]")) { + require("Storage").erase("sleeplog.log"); + return; + } - // return output - return output; + // transform into StorageFile and clear oldLog to have more free ram accessable + require("Storage").open("sleeplog_old.log", "w").write(JSON.parse(oldLog).reverse().join("\n")); + oldLog = undefined; + + // calculate fortnight from now + var fnOfNow = this.msToFn(Date.now()); + + // open StorageFile with old log data + var file = require("Storage").open("sleeplog_old.log", "r"); + // define active fortnight and file cache + var activeFn = true; + var fileCache = []; + // loop through StorageFile entries + while (activeFn) { + // define fortnight for this entry + var thisFn = false; + // cache new line + var line = file.readLine(); + // check if line is filled + if (line) { + // parse line + line = line.substr(0, 15).split(",").map(e => parseInt(e)); + // calculate fortnight for this entry + thisFn = this.msToFn(line[0]); + // convert timestamp into 10min steps + line[0] = line[0] / 6E5 | 0; + // set consecutive to unknown + line.push(0); + } + // check if active fortnight and file cache is set, fortnight has changed and + // active fortnight is not fortnight from now + if (activeFn && fileCache.length && activeFn !== thisFn && activeFn !== fnOfNow) { + // write file cache into new file according to fortnight + require("Storage").writeJSON("sleeplog_" + activeFn + ".log", fileCache); + // clear file cache + fileCache = []; + } + // add line to file cache if it is filled + if (line) fileCache.push(line); + // set active fortnight + activeFn = thisFn; + } + // check if entries are leftover + if (fileCache.length) { + // format fileCache entries into a string + fileCache = fileCache.map(e => e.join(",")).join("\n"); + // read complete new log StorageFile as string + file = require("Storage").open("sleeplog.log", "r"); + var newLogString = file.read(file.getLength()); + // add entries at the beginning of the new log string + newLogString = fileCache + "\n" + newLogString; + // rewrite new log StorageFile + require("Storage").open("sleeplog.log", "w").write(newLogString); + } + + // free ram + file = undefined; + fileCache = undefined; + + // clean up old files + require("Storage").erase("sleeplog.log"); + require("Storage").open("sleeplog_old.log", "w").erase(); } - + /** may be removed in later versions **/ }; diff --git a/apps/sleeplog/metadata.json b/apps/sleeplog/metadata.json index c4dbe8631..f6ce661e8 100644 --- a/apps/sleeplog/metadata.json +++ b/apps/sleeplog/metadata.json @@ -2,27 +2,32 @@ "id":"sleeplog", "name":"Sleep Log", "shortName": "SleepLog", - "version": "0.06", - "description": "Log and view your sleeping habits. This app derived from SleepPhaseAlarm and uses also the principe of Estimation of Stationary Sleep-segments (ESS). It also provides a power saving mode using the built in movement calculation.", + "version": "0.11", + "description": "Log and view your sleeping habits. This app is using the built in movement calculation.", "icon": "app.png", "type": "app", "tags": "tool,boot", "supports": ["BANGLEJS2"], "readme": "README.md", + "interface": "interface.html", "storage": [ {"name": "sleeplog.app.js", "url": "app.js"}, - {"name": "sleeplog.img", "url": "app-icon.js", "evaluate":true}, + {"name": "sleeplog.img", "url": "app-icon.js", "evaluate": true}, {"name": "sleeplog.boot.js", "url": "boot.js"}, {"name": "sleeplog", "url": "lib.js"}, {"name": "sleeplog.settings.js", "url": "settings.js"} ], "data": [ - {"name": "sleeplog.json"}, - {"name": "sleeplog.log"} + {"name": "sleeplog.json"} ], "screenshots": [ - {"url": "screenshot1.png"}, - {"url": "screenshot2.png"}, - {"url": "screenshot3.png"} - ] + {"url": "screenshot-1_app_light.png"}, + {"url": "screenshot-2_day_light.png"}, + {"url": "screenshot-3_graph_light.png"}, + {"url": "screenshot-4_graph2_light.png"}, + {"url": "screenshot-5_app_dark.png"}, + {"url": "screenshot-6_day_dark.png"}, + {"url": "screenshot-7_graph_dark.png"}, + {"url": "screenshot-8_graph2_dark.png"} + ] } diff --git a/apps/sleeplog/nolog.png b/apps/sleeplog/nolog.png deleted file mode 100644 index b153b5769..000000000 Binary files a/apps/sleeplog/nolog.png and /dev/null differ diff --git a/apps/sleeplog/off_20x20.png b/apps/sleeplog/off_20x20.png new file mode 100644 index 000000000..abf3d3bfc Binary files /dev/null and b/apps/sleeplog/off_20x20.png differ diff --git a/apps/sleeplog/powersaving.png b/apps/sleeplog/powersaving.png deleted file mode 100644 index ea487b48c..000000000 Binary files a/apps/sleeplog/powersaving.png and /dev/null differ diff --git a/apps/sleeplog/screenshot-1_app_light.png b/apps/sleeplog/screenshot-1_app_light.png new file mode 100644 index 000000000..f4c01773c Binary files /dev/null and b/apps/sleeplog/screenshot-1_app_light.png differ diff --git a/apps/sleeplog/screenshot-2_day_light.png b/apps/sleeplog/screenshot-2_day_light.png new file mode 100644 index 000000000..61e0a60f6 Binary files /dev/null and b/apps/sleeplog/screenshot-2_day_light.png differ diff --git a/apps/sleeplog/screenshot-3_graph_light.png b/apps/sleeplog/screenshot-3_graph_light.png new file mode 100644 index 000000000..4b74afd25 Binary files /dev/null and b/apps/sleeplog/screenshot-3_graph_light.png differ diff --git a/apps/sleeplog/screenshot-4_graph2_light.png b/apps/sleeplog/screenshot-4_graph2_light.png new file mode 100644 index 000000000..300be5d05 Binary files /dev/null and b/apps/sleeplog/screenshot-4_graph2_light.png differ diff --git a/apps/sleeplog/screenshot-5_app_dark.png b/apps/sleeplog/screenshot-5_app_dark.png new file mode 100644 index 000000000..82e1f8c2f Binary files /dev/null and b/apps/sleeplog/screenshot-5_app_dark.png differ diff --git a/apps/sleeplog/screenshot-6_day_dark.png b/apps/sleeplog/screenshot-6_day_dark.png new file mode 100644 index 000000000..a727f73e0 Binary files /dev/null and b/apps/sleeplog/screenshot-6_day_dark.png differ diff --git a/apps/sleeplog/screenshot-7_graph_dark.png b/apps/sleeplog/screenshot-7_graph_dark.png new file mode 100644 index 000000000..71612fa8e Binary files /dev/null and b/apps/sleeplog/screenshot-7_graph_dark.png differ diff --git a/apps/sleeplog/screenshot-8_graph2_dark.png b/apps/sleeplog/screenshot-8_graph2_dark.png new file mode 100644 index 000000000..09c19e95c Binary files /dev/null and b/apps/sleeplog/screenshot-8_graph2_dark.png differ diff --git a/apps/sleeplog/screenshot1.png b/apps/sleeplog/screenshot1.png deleted file mode 100644 index 200a305c4..000000000 Binary files a/apps/sleeplog/screenshot1.png and /dev/null differ diff --git a/apps/sleeplog/screenshot2.png b/apps/sleeplog/screenshot2.png deleted file mode 100644 index 61f580336..000000000 Binary files a/apps/sleeplog/screenshot2.png and /dev/null differ diff --git a/apps/sleeplog/screenshot3.png b/apps/sleeplog/screenshot3.png deleted file mode 100644 index 4a29b5008..000000000 Binary files a/apps/sleeplog/screenshot3.png and /dev/null differ diff --git a/apps/sleeplog/settings.js b/apps/sleeplog/settings.js index 11c7c0adb..9bf37ed69 100644 --- a/apps/sleeplog/settings.js +++ b/apps/sleeplog/settings.js @@ -1,144 +1,431 @@ (function(back) { + // define settings filename var filename = "sleeplog.json"; + // define logging prompt display status + var thresholdsPrompt = true; - // set storage and load settings - var storage = require("Storage"); - var settings = Object.assign({ - breaktod: 10, // time of day when to start/end graphs - maxawake: 36E5, // 60min in ms - minconsec: 18E5, // 30min in ms - tempthresh: 27, // every temperature above ist registered as worn - powersaving: false, // disables ESS and uses build in movement detection - maxmove: 100, // movement threshold on power saving mode - nomothresh: 0.012, // values lower than 0.008 getting triggert by noise - sleepthresh: 577, // 577 times no movement * 1.04s window width > 10min - winwidth: 13, // 13 values, read with 12.5Hz = every 1.04s - enabled: true, // en-/disable completely - logfile: "sleeplog.log", // logfile - }, storage.readJSON(filename, true) || {}); + // define default vaules + var defaults = { + // main settings + enabled: true, // en-/disable completely + // threshold settings + maxAwake: 36E5, // [ms] maximal awake time to count for consecutive sleep + minConsec: 18E5, // [ms] minimal time to count for consecutive sleep + deepTh: 100, // threshold for deep sleep + lightTh: 200, // threshold for light sleep + // app settings + breakToD: 12, // [h] time of day when to start/end graphs + appTimeout: 0 // lock and backlight timeouts for the app + }; - // write change to global.sleeplog and storage - function writeSetting(key, value) { - // change key in global.sleeplog - if (typeof global.sleeplog === "object") global.sleeplog[key] = value; - // reread settings to only change key - settings = Object.assign(settings, storage.readJSON(filename, true) || {}); - // change the value of key - settings[key] = value; - // write to storage - storage.writeJSON(filename, settings); + // assign loaded settings to default values + var settings = Object.assign(defaults, require("Storage").readJSON(filename, true) || {}); + + // write change to storage + function writeSetting() { + require("Storage").writeJSON(filename, settings); } - // define function to change values that need a restart of the service - function changeRestart() { - require("sleeplog").setEnabled(settings.enabled, settings.logfile, settings.powersaving); + // plot a debug file + function plotDebug(filename) { + // handle swipe events + function swipeHandler(x, y) { + if (x) { + start -= x; + if (start < 0 || maxStart && start > maxStart) { + start = start < 0 ? 0 : maxStart; + } else { + drawGraph(); + } + } else { + minMove += y * 10; + if (minMove < 0 || minMove > 300) { + minMove = minMove < 0 ? 0 : 300; + } else { + drawGraph(); + } + } + } + // handle touch events + function touchHandler() { + invert = !invert; + drawGraph(); + } + + // read required entries + function readEntries(count) { + // extract usabble data from line + function extract(line) { + if (!line) return; + line = line.trim().split(","); + return [Math.round((parseFloat(line[0]) - 25569) * 144), parseInt(line[1])]; + } + + // open debug file + var file = require("Storage").open(filename, "r"); + // skip title + file.readLine(); + // skip past entries + for (var i = 0; i < start * count; i++) { file.readLine(); } + // define data with first entry + var data = [extract(file.readLine())]; + // get start time in 10min steps + var start10min = data[0][0]; + // read first required entry + var line = extract(file.readLine()); + + // read next count entries from file + while (data.length < count) { + // check if line is immediately after the last entry + if (line[0] === start10min + data.length) { + // add line to data + data.push(line); + // read new line + line = extract(file.readLine()); + // stop if no more data available + if (!line) break; + } else { + // add line with unknown movement + data.push([start10min + data.length, 0]); + } + } + + // free ram + file = undefined + // set this start as max, if less entries than expected + if (data.length < count) maxStart = start; + return data; + } + + // draw graph at starting point + function drawGraph() { + // set correct or inverted drawing + function rect(fill, x0, y0, x1, y1) { + if (fill ^ invert) { + g.fillRect(x0, y0, x1, y1); + } else { + g.clearRect(x0, y0, x1, y1); + } + } + + // set witdh + var width = g.getWidth(); + // calculate entries to display (+ set width zero based) + var count = (width--) / 4; + // read required entries + var data = readEntries(count); + + // clear app area + g.reset().clearRect(0, width - 13, width, width); + rect(false, 0, 24, width, width - 14); + // draw x axis + g.drawLine(0, width - 13, width, width - 13); + // draw x label + data.forEach((e, i) => { + var startTime = new Date(e[0] * 6E5); + if (startTime.getMinutes() === 0) { + g.fillRect(4 * i, width - 12, 4 * i, width - 9); + g.setFontAlign(-1, -1).setFont("6x8") + .drawString(startTime.getHours(), 4 * i + 1, width - 8); + } else if (startTime.getMinutes() === 30) { + g.fillRect(4 * i, width - 12, 4 * i, width - 11); + } + }); + + // calculate max height + var height = width - 38; + // cycle through entries + data.forEach((e, i) => { + // check if movement available + if (e[1]) { + // set color depending on recognised status + var color = e[1] < deepTh ? 31 : e[1] < lightTh ? 2047 : 2016; + // correct according to min movement + e[1] -= minMove; + // keep movement in bounderies + e[1] = e[1] < 0 ? 0 : e[1] > height ? height : e[1]; + // draw line and rectangle + g.reset(); + rect(true, 4 * i, width - 14, 4 * i, width - 14 - e[1]); + g.setColor(color).fillRect(4 * i + 1, width - 14, 4 * i + 3, width - 14 - e[1]); + } else { + // draw error in red + g.setColor(63488).fillRect(4 * i, width - 14, 4 * i, width - 14 - height); + } + }); + // draw threshold lines + [deepTh, lightTh].forEach(th => { + th -= minMove; + if (th > 0 && th < height) { + // draw line + g.reset(); + rect(true, 0, width - 14 - th, width, width - 14 - th); + // draw value above or below line + var yAlign = th < height / 2 ? -1 : 1; + if (invert) g.setColor(1); + g.setFontAlign(1, yAlign).setFont("6x8") + .drawString(th + minMove, width - 2, width - 13 - th + 10 * yAlign); + } + }); + + // free ram + data = undefined; + } + + // get thresholds + var deepTh = global.sleeplog ? sleeplog.conf.deepTh : defaults.deepTh; + var lightTh = global.sleeplog ? sleeplog.conf.lightTh : defaults.lightTh; + // set lowest movement displayed + var minMove = deepTh - 20; + // set start point + var start = 0; + // define max start point value + var maxStart = 0; + // define inverted color status + var invert = false; + + // setup UI + Bangle.setUI({ + mode: "custom", + back: selectDebug, + touch: touchHandler, + swipe: swipeHandler + }); + + // first draw + drawGraph(start); } - // calculate sleepthresh factor - var stFactor = settings.winwidth / 12.5 / 60; + // select a debug logfile + function selectDebug() { + // load debug files + var files = require("Storage").list(/^sleeplog_\d\d\d\d\d\d\.csv$/, {sf:true}); + + // check if no files found + if (!files.length) { + // show prompt + E.showPrompt( /*LANG*/"No debug files found.", { + title: /*LANG*/"Debug log", + buttons: { + /*LANG*/"Back": 0 + } + }).then(showDebug); + } else { + // prepare scroller + const H = 40; + var menuIcon = "\0\f\f\x81\0\xFF\xFF\xFF\0\0\0\0\x0F\xFF\xFF\xF0\0\0\0\0\xFF\xFF\xFF"; + // show scroller + E.showScroller({ + h: H, c: files.length, + back: showDebug, + scrollMin : -24, scroll : -24, // title is 24px, rendered at -1 + draw : (idx, r) => { + if (idx < 0) { + return g.setFont("12x20").setFontAlign(-1,0).drawString(menuIcon + " Select file", r.x + 12, r.y + H - 12); + } else { + g.setColor(g.theme.bg2).fillRect({x: r.x + 4, y: r.y + 2, w: r.w - 8, h: r.h - 4, r: 5}); + var name = new Date(parseInt(files[idx].match(/\d\d\d\d\d\d/)[0]) * 36E5); + name = name.toString().slice(0, -12).split(" ").filter((e, i) => i !== 3).join(" "); + g.setColor(g.theme.fg2).setFont("12x20").setFontAlign(-1, 0).drawString(name, r.x + 12, r.y + H / 2); + } + }, + select: (idx) => plotDebug(files[idx]) + }); + } + } + + // show menu or promt to change debugging + function showDebug() { + // check if sleeplog is available + if (global.sleeplog) { + // get debug status, file and duration + var enabled = !!sleeplog.debug; + var file = typeof sleeplog.debug === "object"; + var duration = 0; + // setup debugging menu + var debugMenu = { + "": { + title: /*LANG*/"Debugging" + }, + /*LANG*/"< Back": () => { + // check if some value has changed + if (enabled !== !!sleeplog.debug || file !== (typeof sleeplog.debug === "object") || duration) + require("sleeplog").setDebug(enabled, file ? duration || 12 : undefined); + // redraw main menu + showMain(7); + }, + /*LANG*/"View log": () => selectDebug(), + /*LANG*/"Enable": { + value: enabled, + onchange: v => enabled = v + }, + /*LANG*/"write File": { + value: file, + onchange: v => file = v + }, + /*LANG*/"Duration": { + value: file ? (sleeplog.debug.writeUntil - Date.now()) / 36E5 | 0 : 12, + min: 1, + max: 96, + wrap: true, + format: v => v + /*LANG*/ "h", + onchange: v => duration = v + }, + /*LANG*/"Cancel": () => showMain(7), + }; + // show menu + var menu = E.showMenu(debugMenu); + } else { + // show error prompt + E.showPrompt("Sleeplog" + /*LANG*/"not enabled!", { + title: /*LANG*/"Debugging", + buttons: { + /*LANG*/"Back": 7 + } + }).then(showMain); + } + } + + // show menu to change thresholds + function showThresholds() { + // setup logging menu + var menu; + var thresholdsMenu = { + "": { + title: /*LANG*/"Thresholds" + }, + /*LANG*/"< Back": () => showMain(2), + /*LANG*/"Max Awake": { + value: settings.maxAwake / 6E4, + step: 10, + min: 10, + max: 120, + wrap: true, + noList: true, + format: v => v + /*LANG*/"min", + onchange: v => { + settings.maxAwake = v * 6E4; + writeSetting(); + } + }, + /*LANG*/"Min Consecutive": { + value: settings.minConsec / 6E4, + step: 10, + min: 10, + max: 120, + wrap: true, + noList: true, + format: v => v + /*LANG*/"min", + onchange: v => { + settings.minConsec = v * 6E4; + writeSetting(); + } + }, + /*LANG*/"Deep Sleep": { + value: settings.deepTh, + step: 1, + min: 30, + max: 200, + wrap: true, + noList: true, + onchange: v => { + settings.deepTh = v; + writeSetting(); + } + }, + /*LANG*/"Light Sleep": { + value: settings.lightTh, + step: 10, + min: 100, + max: 400, + wrap: true, + noList: true, + onchange: v => { + settings.lightTh = v; + writeSetting(); + } + }, + /*LANG*/"Reset to Default": () => { + settings.maxAwake = defaults.maxAwake; + settings.minConsec = defaults.minConsec; + settings.deepTh = defaults.deepTh; + settings.lightTh = defaults.lightTh; + writeSetting(); + showThresholds(); + } + }; + + // display info/warning prompt or menu + if (thresholdsPrompt) { + thresholdsPrompt = false; + E.showPrompt("Changes take effect from now on, not retrospective", { + title: /*LANG*/"Thresholds", + buttons: { + /*LANG*/"Ok": 0 + } + }).then(() => menu = E.showMenu(thresholdsMenu)); + } else { + menu = E.showMenu(thresholdsMenu); + } + } // show main menu function showMain(selected) { + // set debug image + var debugImg = !global.sleeplog ? + "FBSBAOAAfwAP+AH3wD4+B8Hw+A+fAH/gA/wAH4AB+AA/wAf+APnwHw+D4Hx8A++AH/AA/gAH" : // X + typeof sleeplog.debug === "object" ? + "FBSBAB/4AQDAF+4BfvAX74F+CBf+gX/oFJKBf+gUkoF/6BSSgX/oFJ6Bf+gX/oF/6BAAgf/4" : // file + sleeplog.debug ? + "FBSBAP//+f/V///4AAGAABkAAZgAGcABjgAYcAGDgBhwAY4AGcABmH+ZB/mAABgAAYAAH///" : // console + 0; // off + debugImg = debugImg ? "\0" + atob(debugImg) : false; + // set menu var mainMenu = { "": { title: "Sleep Log", selected: selected }, - "Exit": () => load(), - "< Back": () => back(), - "Break Tod": { - value: settings.breaktod, + /*LANG*/"< Back": () => back(), + /*LANG*/"Thresholds": () => showThresholds(), + /*LANG*/"Break ToD": { + value: settings.breakToD, step: 1, min: 0, max: 23, wrap: true, - onchange: v => writeSetting("breaktod", v), - }, - "Max Awake": { - value: settings.maxawake / 6E4, - step: 5, - min: 15, - max: 120, - wrap: true, - format: v => v + "min", - onchange: v => writeSetting("maxawake", v * 6E4), - }, - "Min Consec": { - value: settings.minconsec / 6E4, - step: 5, - min: 15, - max: 120, - wrap: true, - format: v => v + "min", - onchange: v => writeSetting("minconsec", v * 6E4), - }, - "Temp Thresh": { - value: settings.tempthresh, - step: 0.5, - min: 20, - max: 40, - wrap: true, - format: v => v + "°C", - onchange: v => writeSetting("tempthresh", v), - }, - "Power Saving": { - value: settings.powersaving, - format: v => v ? "on" : "off", - onchange: function(v) { - settings.powersaving = v; - changeRestart(); - // redraw menu with changed entries subsequent to onchange - // https://github.com/espruino/Espruino/issues/2149 - setTimeout(showMain, 1, 6); + noList: true, + format: v => v + ":00", + onchange: v => { + settings.breakToD = v; + writeSetting(); } }, - "Max Move": { - value: settings.maxmove, - step: 1, - min: 50, - max: 200, + /*LANG*/"App Timeout": { + value: settings.appTimeout / 1E3, + step: 10, + min: 0, + max: 120, wrap: true, - onchange: v => writeSetting("maxmove", v), + noList: true, + format: v => v ? v + "s" : "-", + onchange: v => { + settings.appTimeout = v * 1E3; + writeSetting(); + } }, - "NoMo Thresh": { - value: settings.nomothresh, - step: 0.001, - min: 0.006, - max: 0.02, - wrap: true, - format: v => ("" + v).padEnd(5, "0"), - onchange: v => writeSetting("nomothresh", v), - }, - "Min Duration": { - value: Math.floor(settings.sleepthresh * stFactor), - step: 1, - min: 5, - max: 15, - wrap: true, - format: v => v + "min", - onchange: v => writeSetting("sleepthresh", Math.ceil(v / stFactor)), - }, - "Enabled": { + /*LANG*/"Enabled": { value: settings.enabled, - format: v => v ? "on" : "off", - onchange: function(v) { + onchange: v => { settings.enabled = v; - changeRestart(); + require("sleeplog").setEnabled(v); } }, - "Logfile ": { - value: settings.logfile === "sleeplog.log" ? true : (settings.logfile || "").endsWith(".log") ? "custom" : false, - format: v => v === true ? "default" : v ? "custom" : "off", - onchange: function(v) { - if (v !== "custom") { - settings.logfile = v ? "sleeplog.log" : false; - changeRestart(); - } - } + /*LANG*/"Debugging": { + value: debugImg, + onchange: () => setTimeout(showDebug, 10) } }; - // check power saving mode to delete unused entries - (settings.powersaving ? ["NoMo Thresh", "Min Duration"] : ["Max Move"]).forEach(property => delete mainMenu[property]); var menu = E.showMenu(mainMenu); } diff --git a/apps/sleepphasealarm/ChangeLog b/apps/sleepphasealarm/ChangeLog index 208058472..6bf296342 100644 --- a/apps/sleepphasealarm/ChangeLog +++ b/apps/sleepphasealarm/ChangeLog @@ -7,3 +7,7 @@ use Layout library and display ETA 0.07: Add check for day of week 0.08: Update to new time_utils module +0.09: Vibrate with configured pattern + Add setting to defer start of algorithm + Add setting to disable scheduler alarm + diff --git a/apps/sleepphasealarm/README.md b/apps/sleepphasealarm/README.md index c33c9c807..ecb3feb06 100644 --- a/apps/sleepphasealarm/README.md +++ b/apps/sleepphasealarm/README.md @@ -4,10 +4,19 @@ The alarm must be in the next 24h. The display shows: -- the current time -- time of the next alarm or timer -- time difference between current time and alarm time (ETA) -- current state of the ESS algorithm, "Sleep" or "Awake", useful for debugging +- The current time. +- Time of the next alarm or timer. +- Time difference between current time and alarm time (ETA). +- Current state of the ESS algorithm, "Sleep" or "Awake", useful for debugging. State can also be "Deferred", see the "Run before alarm"-option. + +## Settings + +* **Keep alarm enabled** + - Yes: (default) Alert will stay enabled, e.g. for an alarm at 7:00 the clock will buzz at the calculated time from the ESS algorithm (for example 6:45) and again at 7:00. + - No: No action at configured alarm time from scheduler. +* **Run before alarm** + - disabled: (default) The ESS algorithm starts immediately when the application starts. + - 1..23: The ESS algorithm starts the configured time before the alarm. E.g. when set to 1h for an alarm at 7:00 the ESS algorithm will start at 6:00. This improves battery life. ## Logging diff --git a/apps/sleepphasealarm/app.js b/apps/sleepphasealarm/app.js index febc8a259..b19799c4b 100644 --- a/apps/sleepphasealarm/app.js +++ b/apps/sleepphasealarm/app.js @@ -1,9 +1,18 @@ const BANGLEJS2 = process.env.HWVERSION == 2; // check for bangle 2 +const CONFIGFILE = "sleepphasealarm.json"; const Layout = require("Layout"); const locale = require('locale'); const alarms = require("Storage").readJSON("sched.json",1) || []; -const config = require("Storage").readJSON("sleepphasealarm.json",1) || {logs: []}; +const config = Object.assign({ + logs: [], // array of length 31 with one entry for each day of month + settings: { + startBeforeAlarm: 0, // 0 = start immediately, 1..23 = start 1h..23h before alarm time + disableAlarm: false, + } +}, require("Storage").readJSON(CONFIGFILE,1) || {}); const active = alarms.filter(a=>a.on); +const schedSettings = require("sched").getSettings(); +let buzzCount = schedSettings.buzzCount; let logs = []; // Sleep/Wake detection with Estimation of Stationary Sleep-segments (ESS): @@ -43,7 +52,8 @@ function calc_ess(acc_magn) { } // locate next alarm -var nextAlarm; +var nextAlarmDate; +var nextAlarmConfig; active.forEach(alarm => { const now = new Date(); const time = require("time_utils").decodeTime(alarm.t); @@ -52,8 +62,9 @@ active.forEach(alarm => { dateAlarm.setTime(dateAlarm.getTime() + (24*60*60*1000)); } if ((alarm.dow >> dateAlarm.getDay()) & 1) { // check valid day of week - if (nextAlarm === undefined || dateAlarm < nextAlarm) { - nextAlarm = dateAlarm; + if (nextAlarmDate === undefined || dateAlarm < nextAlarmDate) { + nextAlarmDate = dateAlarm; + nextAlarmConfig = alarm; } } }); @@ -69,8 +80,8 @@ var layout = new Layout({ }, {lazy:true}); function drawApp() { - var alarmHour = nextAlarm.getHours(); - var alarmMinute = nextAlarm.getMinutes(); + var alarmHour = nextAlarmDate.getHours(); + var alarmMinute = nextAlarmDate.getMinutes(); if (alarmHour < 10) alarmHour = "0" + alarmHour; if (alarmMinute < 10) alarmMinute = "0" + alarmMinute; layout.alarm_date.label = "Alarm at " + alarmHour + ":" + alarmMinute; @@ -80,82 +91,108 @@ function drawApp() { if (Bangle.isLCDOn()) { const now = new Date(); layout.date.label = locale.time(now, BANGLEJS2 && Bangle.isLocked() ? 1 : 0); // hide seconds on bangle 2 - const diff = nextAlarm - now; + const diff = nextAlarmDate - now; const diffHour = Math.floor((diff % 86400000) / 3600000).toString(); const diffMinutes = Math.floor(((diff % 86400000) % 3600000) / 60000).toString(); layout.eta.label = "ETA: -"+ diffHour + ":" + diffMinutes.padStart(2, '0'); layout.render(); } + + setTimeout(()=>{ + drawTime(); + }, 1000 - (Date.now() % 1000)); } drawTime(); - setInterval(drawTime, 500); // 2Hz } -var buzzCount = 19; function buzz() { if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence - Bangle.setLCDPower(1); - Bangle.buzz().then(()=>{ - if (buzzCount--) { - setTimeout(buzz, 500); - } else { - // back to main after finish - setTimeout(load, 1000); - } - }); + Bangle.setLCDPower(1); + require("buzz").pattern(nextAlarmConfig.vibrate || ";"); + if (buzzCount--) { + setTimeout(buzz, schedSettings.buzzIntervalMillis); + } else { + // back to main after finish + setTimeout(load, 1000); + } } function addLog(time, type) { logs.push({time: time, type: type}); - require("Storage").writeJSON("sleepphasealarm.json", config); + if (logs.length > 1) { // Do not write if there is only one state + require("Storage").writeJSON(CONFIGFILE, config); + } } // run var minAlarm = new Date(); var measure = true; -if (nextAlarm !== undefined) { - config.logs[nextAlarm.getDate()] = []; // overwrite log on each day of month - logs = config.logs[nextAlarm.getDate()]; +if (nextAlarmDate !== undefined) { + config.logs[nextAlarmDate.getDate()] = []; // overwrite log on each day of month + logs = config.logs[nextAlarmDate.getDate()]; g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); let swest_last; // minimum alert 30 minutes early - minAlarm.setTime(nextAlarm.getTime() - (30*60*1000)); - Bangle.on('accel', (accelData) => { // 12.5Hz - const now = new Date(); - const acc = accelData.mag; - const swest = calc_ess(acc); + minAlarm.setTime(nextAlarmDate.getTime() - (30*60*1000)); + run = () => { + layout.state.label = "Start"; + layout.render(); + Bangle.setOptions({powerSave: false}); // do not dynamically change accelerometer poll interval + Bangle.setPollInterval(80); // 12.5Hz + Bangle.on('accel', (accelData) => { + const now = new Date(); + const acc = accelData.mag; + const swest = calc_ess(acc); - if (swest !== undefined) { - if (Bangle.isLCDOn()) { - layout.state.label = swest ? "Sleep" : "Awake"; - layout.render(); - } - // log - if (swest_last != swest) { - if (swest) { - addLog(new Date(now - sleepthresh*13/12.5*1000), "sleep"); // calculate begin of no motion phase, 13 values/second at 12.5Hz - } else { - addLog(now, "awake"); + if (swest !== undefined) { + if (Bangle.isLCDOn()) { + layout.state.label = swest ? "Sleep" : "Awake"; + layout.render(); + } + // log + if (swest_last != swest) { + if (swest) { + addLog(new Date(now - sleepthresh*13/12.5*1000), "sleep"); // calculate begin of no motion phase, 13 values/second at 12.5Hz + } else { + addLog(now, "awake"); + } + swest_last = swest; } - swest_last = swest; } - } - if (now >= nextAlarm) { - // The alarm widget should handle this one - addLog(now, "alarm"); - setTimeout(load, 1000); - } else if (measure && now >= minAlarm && swest === false) { - addLog(now, "alarm"); - buzz(); - measure = false; - } - }); + if (now >= nextAlarmDate) { + // The alarm widget should handle this one + addLog(now, "alarm"); + setTimeout(load, 1000); + } else if (measure && now >= minAlarm && swest_last === false) { + addLog(now, "alarm"); + buzz(); + measure = false; + if (config.settings.disableAlarm) { + // disable alarm for scheduler + nextAlarmConfig.last = now.getDate(); + require("Storage").writeJSON("sched.json", alarms); + } + } + }); + }; drawApp(); + if (config.settings.startBeforeAlarm === 0) { + // Start immediately + run(); + } else { + // defer start + layout.state.label = "Deferred"; + layout.render(); + const diff = nextAlarmDate - Date.now(); + let timeout = diff-config.settings.startBeforeAlarm*60*60*1000; + if (timeout < 0) timeout = 0; + setTimeout(run, timeout); + } } else { E.showMessage('No Alarm'); setTimeout(load, 1000); diff --git a/apps/sleepphasealarm/interface.html b/apps/sleepphasealarm/interface.html index 9a7cb0f93..f45c183e1 100644 --- a/apps/sleepphasealarm/interface.html +++ b/apps/sleepphasealarm/interface.html @@ -1,7 +1,6 @@ -

Please select a wakeup day:

diff --git a/apps/sleepphasealarm/metadata.json b/apps/sleepphasealarm/metadata.json index c74a617ab..6ec5f4180 100644 --- a/apps/sleepphasealarm/metadata.json +++ b/apps/sleepphasealarm/metadata.json @@ -2,7 +2,7 @@ "id": "sleepphasealarm", "name": "SleepPhaseAlarm", "shortName": "SleepPhaseAlarm", - "version": "0.08", + "version": "0.09", "description": "Uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments (ESS, see https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en). This app will read the next alarm from the alarm application and will wake you up to 30 minutes early at the best guessed time when you are almost already awake.", "icon": "app.png", "tags": "alarm", @@ -11,6 +11,7 @@ "dependencies": {"scheduler":"type"}, "storage": [ {"name":"sleepphasealarm.app.js","url":"app.js"}, + {"name":"sleepphasealarm.settings.js","url":"settings.js"}, {"name":"sleepphasealarm.img","url":"app-icon.js","evaluate":true} ], "data": [{"name":"sleepphasealarm.json","storageFile":true}], diff --git a/apps/sleepphasealarm/settings.js b/apps/sleepphasealarm/settings.js new file mode 100644 index 000000000..a79abb598 --- /dev/null +++ b/apps/sleepphasealarm/settings.js @@ -0,0 +1,37 @@ +(function(back) { + const CONFIGFILE = "sleepphasealarm.json"; + // Load settings + const config = Object.assign({ + logs: [], // array of length 31 with one entry for each day of month + settings: { + startBeforeAlarm: 0, // 0 = start immediately, 1..23 = start 1h..23h before alarm time + disableAlarm: false, + } + }, require("Storage").readJSON(CONFIGFILE,1) || {}); + + function writeSettings() { + require('Storage').writeJSON(CONFIGFILE, config); + } + + // Show the menu + E.showMenu({ + "" : { "title" : "SleepPhaseAlarm" }, + 'Keep alarm enabled': { + value: !!config.settings.disableAlarm, + format: v => v?"No":"Yes", + onchange: v => { + config.settings.disableAlarm = v; + writeSettings(); + } + }, "< Back" : () => back(), + 'Run before alarm': { + format: v => v === 0 ? 'disabled' : v+'h', + value: config.settings.startBeforeAlarm, + min: 0, max: 23, + onchange: v => { + config.settings.startBeforeAlarm = v; + writeSettings(); + } + }, + }); +}) diff --git a/apps/slidingtext/ChangeLog b/apps/slidingtext/ChangeLog index 0327ff387..1b45c36cb 100644 --- a/apps/slidingtext/ChangeLog +++ b/apps/slidingtext/ChangeLog @@ -5,4 +5,6 @@ 0.05: BUGFIX: pedometer widget interfered with the clock Font Alignment 0.06: Use Bangle.setUI for button/launcher handling 0.07: Support for Bangle.js 2 and themes -0.08: Removed "wake LCD on face-up"-feature: A watch-face should not set things like "wake LCD on face-up". +0.08: Removed "wake LCD on face-up"-feature: A watch-face should not set things like "wake LCD on face-up". +0.09: Added button control toggle and other live controls to new settings screen. +0.10: Tell clock widgets to hide. diff --git a/apps/slidingtext/README.md b/apps/slidingtext/README.md index d2d2fb5b6..d5a561634 100644 --- a/apps/slidingtext/README.md +++ b/apps/slidingtext/README.md @@ -6,22 +6,49 @@ Inspired by the Pebble sliding clock, old times are scrolled off the screen and ## Usage -### Button 1 +### Bangle 2 + +The Bangle 2 has Live Controls switched **off** by default so the colour and language have to be changed from the setting Menu. +Please locate the Sliding Text clock under the setting->apps menu. + +With the Live Controls switched on: +#### Bottom right hand corner press +press the bottom right hand corner of the screen to change the colour + +| White | Black | Gray | Red | +|----------------------|----------------------|----------------------|----------------------| +| ![](b2_color-01.jpg) | ![](b2_color-02.jpg) | ![](b2_color-03.jpg) | ![](b2_color-04.jpg) | + +#### Top right hand corner press +press the top right hand corner of the screen to change the language + +### Bangle 1 + +By Default the Live Controls (The side buttons) are switched on, which means the clock face can be controlled dynamically using the 2 side buttons on the right hand side + +#### Button 1 Use Button 1 (the top right button) to change the language | English | English (Traditional) | French | Japanese (Romanji) | | ---- | ---- | ---- | ---- | -| ![](./format-01.jpg) | ![](format-02.jpg) | ![](format-03.jpg) |![](format-04.jpg) | +| ![](format-01.jpg) | ![](format-02.jpg) | ![](format-03.jpg) |![](format-04.jpg) | | **German** | **Spanish** | | | -| ![](./format-05.jpg) | ![](format-06.jpg) | | | +| ![](format-05.jpg) | ![](format-06.jpg) | | | -### Button 3 +#### Button 3 Button 3 (bottom right button) is used to change the colour | Black | Red | Gray | Purple | | ---- | ---- | ---- | ---- | -| ![](./color-01.jpg) | ![](color-02.jpg) | ![](color-03.jpg) | ![](color-04.jpg) | +| ![](b1_color-01.jpg) | ![](b1_color-02.jpg) | ![](b1_color-03.jpg) | ![](b1_color-04.jpg) | + +#### Settings + +To turn off the Live Controls and change the settings statically please visit the settings menu. The settings menu will allow you to: +- Colour Scheme +- Language +- Live Controls (On or Off) ## Further Details diff --git a/apps/slidingtext/color-01.jpg b/apps/slidingtext/b1_color-01.jpg similarity index 100% rename from apps/slidingtext/color-01.jpg rename to apps/slidingtext/b1_color-01.jpg diff --git a/apps/slidingtext/color-02.jpg b/apps/slidingtext/b1_color-02.jpg similarity index 100% rename from apps/slidingtext/color-02.jpg rename to apps/slidingtext/b1_color-02.jpg diff --git a/apps/slidingtext/color-03.jpg b/apps/slidingtext/b1_color-03.jpg similarity index 100% rename from apps/slidingtext/color-03.jpg rename to apps/slidingtext/b1_color-03.jpg diff --git a/apps/slidingtext/color-04.jpg b/apps/slidingtext/b1_color-04.jpg similarity index 100% rename from apps/slidingtext/color-04.jpg rename to apps/slidingtext/b1_color-04.jpg diff --git a/apps/slidingtext/b2_color-01.jpg b/apps/slidingtext/b2_color-01.jpg new file mode 100644 index 000000000..7428f7623 Binary files /dev/null and b/apps/slidingtext/b2_color-01.jpg differ diff --git a/apps/slidingtext/b2_color-02.jpg b/apps/slidingtext/b2_color-02.jpg new file mode 100644 index 000000000..7e3b8666f Binary files /dev/null and b/apps/slidingtext/b2_color-02.jpg differ diff --git a/apps/slidingtext/b2_color-03.jpg b/apps/slidingtext/b2_color-03.jpg new file mode 100644 index 000000000..96c8655cf Binary files /dev/null and b/apps/slidingtext/b2_color-03.jpg differ diff --git a/apps/slidingtext/b2_color-04.jpg b/apps/slidingtext/b2_color-04.jpg new file mode 100644 index 000000000..dac36365a Binary files /dev/null and b/apps/slidingtext/b2_color-04.jpg differ diff --git a/apps/slidingtext/metadata.json b/apps/slidingtext/metadata.json index 2937a618b..49ffaf16f 100644 --- a/apps/slidingtext/metadata.json +++ b/apps/slidingtext/metadata.json @@ -1,7 +1,7 @@ { "id": "slidingtext", "name": "Sliding Clock", - "version": "0.08", + "version": "0.10", "description": "Inspired by the Pebble sliding clock, old times are scrolled off the screen and new times on. You are also able to change language on the fly so you can see the time written in other languages using button 1. Currently English, French, Japanese, Spanish and German are supported", "icon": "slidingtext.png", "type": "clock", @@ -12,6 +12,7 @@ "allow_emulator": false, "storage": [ {"name":"slidingtext.app.js","url":"slidingtext.js"}, + {"name":"slidingtext.settings.js","url":"slidingtext.settings.js"}, {"name":"slidingtext.img","url":"slidingtext-icon.js","evaluate":true}, {"name":"slidingtext.locale.en.js","url":"slidingtext.locale.en.js"}, {"name":"slidingtext.locale.en2.js","url":"slidingtext.locale.en2.js"}, @@ -21,5 +22,6 @@ {"name":"slidingtext.locale.jp.js","url":"slidingtext.locale.jp.js"}, {"name":"slidingtext.locale.de.js","url":"slidingtext.locale.de.js"}, {"name":"slidingtext.dtfmt.js","url":"slidingtext.dtfmt.js"} - ] + ], + "data": [{"name": "slidingtext.settings.json"}] } diff --git a/apps/slidingtext/slidingtext.dtfmt.js b/apps/slidingtext/slidingtext.dtfmt.js index 865ea47e6..266ed0b35 100644 --- a/apps/slidingtext/slidingtext.dtfmt.js +++ b/apps/slidingtext/slidingtext.dtfmt.js @@ -7,6 +7,7 @@ class DateFormatter { * to the lines of text on the screen */ name(){return "no name";} + shortName(){return "no short name"} formatDate(date){ return ["no","date","defined"]; } diff --git a/apps/slidingtext/slidingtext.js b/apps/slidingtext/slidingtext.js index 7b56af1a1..2f24e5596 100644 --- a/apps/slidingtext/slidingtext.js +++ b/apps/slidingtext/slidingtext.js @@ -5,11 +5,17 @@ */ const color_schemes = [ + { + name: "white", + background : [1.0,1.0,1.0], + main_bar: [0.0,0.0,0.0], + other_bars: [0.1,0.1,0.1], + }, { name: "black", background : [0.0,0.0,0.0], main_bar: [1.0,1.0,1.0], - other_bars: [0.85,0.85,0.85], + other_bars: [0.9,0.9,0.9], }, { name: "red", @@ -81,7 +87,7 @@ function reset_commands(){ function has_commands(){ return command_stack_high_priority.length > 0 || - command_stack_low_priority.lenth > 0; + command_stack_low_priority.length > 0; } class ShiftText { @@ -244,15 +250,19 @@ function setRowDisplays(y, heights) { y += heights[i]; } } -if (g.getHeight()>200) + +function bangleVersion(){ + return (g.getHeight()>200)? 1 : 2; +} + +if (bangleVersion()<2) setRowDisplays(50, [40,30,30,30,40]); else setRowDisplays(34, [35,25,25,25,35]); function nextColorTheme(){ - //console.log("next color theme"); color_scheme_index += 1; - if(color_scheme_index >= row_displays.length){ + if(color_scheme_index > row_displays.length){ color_scheme_index = 0; } setColorScheme(color_schemes[color_scheme_index]); @@ -411,8 +421,7 @@ function draw_clock(){ reset_commands(); date = display_time(date); console.log("draw_clock:" + last_draw_time.toISOString() + " display:" + date.toISOString()); - // for debugging only - //date.setMinutes(37); + var rows = date_formatter.formatDate(date); var display; for (var i = 0; i < rows.length; i++) { @@ -495,7 +504,7 @@ function set_colorscheme(colorscheme_name){ function set_dateformat(dateformat_name){ console.log("setting date format:" + dateformat_name); for (var i=0; i < date_formatters.length; i++) { - if(date_formatters[i].name() == dateformat_name){ + if(date_formatters[i].shortName() == dateformat_name){ date_formatter_idx = i; date_formatter = date_formatters[date_formatter_idx]; console.log("match"); @@ -503,6 +512,7 @@ function set_dateformat(dateformat_name){ } } +var enable_live_controls = false; const PREFERENCE_FILE = "slidingtext.settings.json"; /** * Called on startup to set the watch to the last preference settings @@ -510,7 +520,7 @@ const PREFERENCE_FILE = "slidingtext.settings.json"; function load_settings(){ var setScheme = false; try{ - settings = require("Storage").readJSON(PREFERENCE_FILE); + var settings = require("Storage").readJSON(PREFERENCE_FILE); if(settings != null){ console.log("loaded:" + JSON.stringify(settings)); if(settings.color_scheme != null){ @@ -520,9 +530,15 @@ function load_settings(){ if(settings.date_format != null){ set_dateformat(settings.date_format); } + if(settings.enable_live_controls == null){ + settings.enable_live_controls = (bangleVersion() <= 1); + } + enable_live_controls = settings.enable_live_controls; } else { console.log("no settings to load"); + enable_live_controls = (bangleVersion() <= 1); } + console.log("enable_live_controls=" + enable_live_controls); } catch(e){ console.log("failed to load settings:" + e); } @@ -536,24 +552,30 @@ function load_settings(){ */ function save_settings(){ var settings = { - date_format : date_formatter.name(), + date_format : date_formatter.shortName(), color_scheme : color_schemes[color_scheme_index].name, + enable_live_controls: enable_live_controls }; console.log("saving:" + JSON.stringify(settings)); require("Storage").writeJSON(PREFERENCE_FILE,settings); } function button1pressed() { - changeFormatter(); - save_settings(); + console.log("button1pressed"); + if (enable_live_controls) { + changeFormatter(); + save_settings(); + } } function button3pressed() { console.log("button3pressed"); - nextColorTheme(); - reset_clock(true); - draw_clock(); - save_settings(); + if (enable_live_controls) { + nextColorTheme(); + reset_clock(true); + draw_clock(); + save_settings(); + } } // The interval reference for updating the clock @@ -625,12 +647,13 @@ Bangle.on('lcdPower', (on) => { g.clear(); load_settings(); -Bangle.loadWidgets(); -Bangle.drawWidgets(); - -startTimers(); // Show launcher when button pressed Bangle.setUI("clockupdown", d=>{ if (d<0) button1pressed(); if (d>0) button3pressed(); }); +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +startTimers(); + diff --git a/apps/slidingtext/slidingtext.locale.de.js b/apps/slidingtext/slidingtext.locale.de.js index 3cb178232..da5c2f01d 100644 --- a/apps/slidingtext/slidingtext.locale.de.js +++ b/apps/slidingtext/slidingtext.locale.de.js @@ -66,6 +66,7 @@ function germanMinsToText(mins) { class GermanDateFormatter extends DateFormatter { constructor() { super();} name(){return "German";} + shortName(){return "de"} formatDate(date){ var mins = date.getMinutes(); var hourOfDay = date.getHours(); diff --git a/apps/slidingtext/slidingtext.locale.en.js b/apps/slidingtext/slidingtext.locale.en.js index 7d37fcae1..6414ef7a9 100644 --- a/apps/slidingtext/slidingtext.locale.en.js +++ b/apps/slidingtext/slidingtext.locale.en.js @@ -5,6 +5,7 @@ const numberToText = require("slidingtext.utils.en.js").numberToText; class EnglishDateFormatter extends DateFormatter { constructor() { super();} name(){return "English";} + shortName(){return "en"} formatDate(date){ var hours_txt = hoursToText(date.getHours()); var mins_txt = numberToText(date.getMinutes()); diff --git a/apps/slidingtext/slidingtext.locale.en2.js b/apps/slidingtext/slidingtext.locale.en2.js index cd07e8848..d7d7ff6a8 100644 --- a/apps/slidingtext/slidingtext.locale.en2.js +++ b/apps/slidingtext/slidingtext.locale.en2.js @@ -7,6 +7,7 @@ class EnglishTraditionalDateFormatter extends DateFormatter { super(); } name(){return "English (Traditional)";} + shortName(){return "en2"} formatDate(date){ var mins = date.getMinutes(); var hourOfDay = date.getHours(); diff --git a/apps/slidingtext/slidingtext.locale.es.js b/apps/slidingtext/slidingtext.locale.es.js index 1b6f6d11b..62c68b64d 100644 --- a/apps/slidingtext/slidingtext.locale.es.js +++ b/apps/slidingtext/slidingtext.locale.es.js @@ -47,6 +47,7 @@ function spanishMinsToText(mins){ class SpanishDateFormatter extends DateFormatter { constructor() { super();} name(){return "Spanish";} + shortName(){return "es"} formatDate(date){ var mins = date.getMinutes(); var hourOfDay = date.getHours(); diff --git a/apps/slidingtext/slidingtext.locale.fr.js b/apps/slidingtext/slidingtext.locale.fr.js index 5844c1a4e..d4c1dc9d6 100644 --- a/apps/slidingtext/slidingtext.locale.fr.js +++ b/apps/slidingtext/slidingtext.locale.fr.js @@ -31,6 +31,7 @@ function frenchHeures(hours){ class FrenchDateFormatter extends DateFormatter { constructor() { super(); } name(){return "French";} + shortName(){return "fr"} formatDate(date){ var hours = frenchHoursToText(date.getHours()); var heures = frenchHeures(date.getHours()); diff --git a/apps/slidingtext/slidingtext.locale.jp.js b/apps/slidingtext/slidingtext.locale.jp.js index c28780e88..0f6e46a21 100644 --- a/apps/slidingtext/slidingtext.locale.jp.js +++ b/apps/slidingtext/slidingtext.locale.jp.js @@ -61,6 +61,7 @@ function japaneseMinsToText(mins){ class JapaneseDateFormatter extends DateFormatter { constructor() { super(); } name(){return "Japanese (Romanji)";} + shortName(){return "jp"} formatDate(date){ var hours_txt = japaneseHoursToText(date.getHours()); var mins_txt = japaneseMinsToText(date.getMinutes()); diff --git a/apps/slidingtext/slidingtext.settings.js b/apps/slidingtext/slidingtext.settings.js new file mode 100644 index 000000000..d1006990e --- /dev/null +++ b/apps/slidingtext/slidingtext.settings.js @@ -0,0 +1,65 @@ +(function(back) { + const PREFERENCE_FILE = "slidingtext.settings.json"; + var settings = Object.assign({}, + require('Storage').readJSON(PREFERENCE_FILE, true) || {}); + // the screen controls are defaulted on for a bangle 1 and off for a bangle 2 + if(settings.enable_live_controls == null){ + settings.enable_live_controls = (g.getHeight()> 200); + } + console.log("loaded:" + JSON.stringify(settings)); + + const LANGUAGES_FILE = "slidingtext.languages.json"; + const LANGUAGES_DEFAULT = ["en","en2"]; + var locales = null; + try { + locales = require("Storage").readJSON(LANGUAGES_FILE); + } catch(e) { + console.log("failed to load languages:" + e); + } + if(locales == null || locales.length == 0){ + locales = LANGUAGES_DEFAULT; + console.log("defaulting languages to locale:" + locales); + } + + function writeSettings() { + console.log("saving:" + JSON.stringify(settings)); + require('Storage').writeJSON(PREFERENCE_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); + } + + // Show the menu + E.showMenu({ + "" : { "title" : "Sliding Text" }, + "< Back" : () => back(), + "Colour": stringInSettings("color_scheme", ["white", "black", "red","grey","purple","blue"]), + "Languages": stringInSettings("date_format", locales), + "Live Control": { + value: (settings.enable_live_controls !== undefined ? settings.enable_live_controls : true), + format: v => v ? "On" : "Off", + onchange: v => { + settings.enable_live_controls = v; + writeSettings(); + } + }, + }); +}) \ No newline at end of file diff --git a/apps/smclock/ChangeLog b/apps/smclock/ChangeLog index 2a3874d34..42d50d3b9 100644 --- a/apps/smclock/ChangeLog +++ b/apps/smclock/ChangeLog @@ -4,3 +4,4 @@ 0.04: Add support for settings 0.05: Add ability to change background (3bit or 4bit) 0.06: Replace battery text with image +0.07: Use default Bangle formatter for booleans diff --git a/apps/smclock/metadata.json b/apps/smclock/metadata.json index ca40193a2..6790c2030 100644 --- a/apps/smclock/metadata.json +++ b/apps/smclock/metadata.json @@ -4,7 +4,7 @@ "shortName": "MonoClock", "icon": "app.png", "screenshots": [{ "url": "screenshot0.png" }, {"url": "screenshot1.png" }], - "version": "0.06", + "version": "0.07", "description": "A simple watchface based on my stylised monogram.", "type": "clock", "tags": "clock", diff --git a/apps/smclock/settings.js b/apps/smclock/settings.js index ee4a35a26..30119e48d 100644 --- a/apps/smclock/settings.js +++ b/apps/smclock/settings.js @@ -46,7 +46,6 @@ "Analog Face": { value: settings.showAnalogFace !== undefined ? settings.showAnalogFace : false, - format: v => v ? "On" : "Off", onchange: v => { settings.showAnalogFace = v; writeSettings(); @@ -71,7 +70,6 @@ "Week Info": { value: settings.showWeekInfo !== undefined ? settings.showWeekInfo : false, - format: v => v ? "On" : "Off", onchange: v => { settings.showWeekInfo = v; writeSettings(); @@ -80,7 +78,6 @@ "Vector Font": { value: settings.useVectorFont !== undefined ? settings.useVectorFont : false, - format: v => v ? "On" : "Off", onchange: v => { settings.useVectorFont = v; writeSettings(); diff --git a/apps/sonicclk/ChangeLog b/apps/sonicclk/ChangeLog new file mode 100644 index 000000000..5cdaa3764 --- /dev/null +++ b/apps/sonicclk/ChangeLog @@ -0,0 +1,6 @@ +0.01: [MAJOR] Added sonic clock app +0.02: [PATCH] Fixed text alignment issue; Increased acceleration required to activate twist; +0.03: [MINOR] Added settings menu to control twist threshold and LCD Activity +0.04: [PATCH] Call `Bangle.setUI` when exiting settings menu, settings tap moved to top +0.05: [PATCH] Firmware 2v11 - use `wakeOnTwist` rather than manual `setLCDPower`; Reset sonic on `fullReset` +0.06: Use default Bangle formatter for booleans diff --git a/apps/sonicclk/Changelog b/apps/sonicclk/Changelog deleted file mode 100644 index d78fe291f..000000000 --- a/apps/sonicclk/Changelog +++ /dev/null @@ -1,5 +0,0 @@ -0.01 [MAJOR] Added sonic clock app -0.02 [PATCH] Fixed text alignment issue; Increased acceleration required to activate twist; -0.03 [MINOR] Added settings menu to control twist threshold and LCD Activity -0.04 [PATCH] Call `Bangle.setUI` when exiting settings menu, settings tap moved to top -0.05 [PATCH] Firmware 2v11 - use `wakeOnTwist` rather than manual `setLCDPower`; Reset sonic on `fullReset` diff --git a/apps/sonicclk/app.js b/apps/sonicclk/app.js index eddb971f8..2d72de68e 100644 --- a/apps/sonicclk/app.js +++ b/apps/sonicclk/app.js @@ -268,7 +268,6 @@ const settingsMenu = { "": { title: "Settings" }, "Active Mode": { value: settings.activeMode, - format: (v) => (v ? "On" : "Off"), onchange: (v) => (settings.activeMode = v), }, "Twist Thresh": { diff --git a/apps/sonicclk/metadata.json b/apps/sonicclk/metadata.json index 5a2d64db1..ad3e52fdb 100644 --- a/apps/sonicclk/metadata.json +++ b/apps/sonicclk/metadata.json @@ -1,7 +1,7 @@ { "id": "sonicclk", "name": "Sonic Clock", - "version": "0.05", + "version": "0.06", "description": "A classic sonic clock featuring run, stop and wait animations.", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], diff --git a/apps/speedalt/ChangeLog b/apps/speedalt/ChangeLog index 78c14594b..224d2711d 100644 --- a/apps/speedalt/ChangeLog +++ b/apps/speedalt/ChangeLog @@ -10,3 +10,4 @@ 0.10: Add Kalman filter to smooth the speed and altitude values. Can be disabled in settings. 0.11: Now also runs on Bangle.js 2 with basic functionality 0.12: Full functionality on Bangle.js 2: Bangle.js 1 buttons mapped to touch areas. +0.13: Use default Bangle formatter for booleans diff --git a/apps/speedalt/README.md b/apps/speedalt/README.md index 6f0d4efe5..e629f94bb 100644 --- a/apps/speedalt/README.md +++ b/apps/speedalt/README.md @@ -2,7 +2,7 @@ You can switch between three display modes. One showing speed and altitude (A), one showing speed and distance to waypoint (D) and a large dispay of time and selected waypoint. -Within the [A]ltitude and [D]istance displays modes one figure is displayed on the watch face using the largest possible characters depending on the number of digits. The other is in a smaller characters below that. Both are always visible. You can display the current or maximum observed speed/altitude values. Current time is always displayed. +Within the [A]ltitude and [D]istance displays modes one figure is displayed on the watch face using the largest possible characters depending on the number of digits. The other is in a smaller characters below that. Both are always visible. You can display the current or maximum observed speed/altitude values. Current time is always displayed. The waypoints list is the same as that used with the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app so the same set of waypoints can be used across both apps. Refer to that app for waypoint file information. @@ -36,7 +36,7 @@ BTN4 : Left Display Tap : Swaps which figure is in the large display. You can ha ## App Settings -Select the desired display units. Speed can be as per the default locale, kph, knots, mph or m/s. Distance can be km, miles or nautical miles. Altitude can be feet or metres. Select one of three colour schemes. Default (three colours), high contrast (all white on black) or night ( all red on black ). +Select the desired display units. Speed can be as per the default locale, kph, knots, mph or m/s. Distance can be km, miles or nautical miles. Altitude can be feet or metres. Select one of three colour schemes. Default (three colours), high contrast (all white on black) or night ( all red on black ). ## Kalman Filter @@ -67,13 +67,11 @@ This app will work quite happily on its own but will use the [GPS Setup App](htt When using the GPS Setup App this app switches the GPS to SuperE (default) mode while the display is lit and showing fix information. This ensures that that fixes are updated every second or so. 10 seconds after the display is blanked by the watch this app will switch the GPS to PSMOO mode and will only attempt to get a fix every two minutes. This improves power saving while the display is off and the delay gives an opportunity to restore the display before the GPS power mode is switched. -The MAX values continue to be collected with the display off so may appear a little odd after the intermittent fixes of the low power mode. +The MAX values continue to be collected with the display off so may appear a little odd after the intermittent fixes of the low power mode. ## Waypoints -Waypoints are used in [D]istance mode. Create a file waypoints.json and write to storage on the Bangle.js using the IDE. The first 6 characters of the name are displayed in Speed+[D]istance mode. - -The [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app in the App Loader has a really nice waypoints file editor. (Must be connected to your Bangle.JS and then click on the Download icon.) +Waypoints are used in Distance and VMG modes. See the `Waypoints` app for information on how to create/edit waypoints. The first 6 characters of the name are displayed in Speed+[D]istance mode. Sample waypoints.json (My sailing waypoints) @@ -149,5 +147,3 @@ Developed for my use in sailing, cycling and motorcycling. If you find this soft Many thanks to Gordon Williams. Awesome job. Special thanks also to @jeffmer, for the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app and @hughbarney for the Low power GPS code development and Wouter Bulten for the Kalman filter code. - - diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 79db932db..4587252c2 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -209,7 +209,7 @@ function nxtWp(inc){ } function loadWp() { - var w = require("Storage").readJSON('waypoints.json')||[{name:"NONE"}]; + var w = require("waypoints").load(); if (cfg.wp>=w.length) cfg.wp=0; if (cfg.wp<0) cfg.wp = w.length-1; savSettings(); diff --git a/apps/speedalt/metadata.json b/apps/speedalt/metadata.json index e03d23c8b..89bfd4a57 100644 --- a/apps/speedalt/metadata.json +++ b/apps/speedalt/metadata.json @@ -2,18 +2,18 @@ "id": "speedalt", "name": "GPS Adventure Sports", "shortName": "GPS Adv Sport", - "version": "0.12", + "version": "0.13", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "icon": "app.png", "type": "app", "tags": "tool,outdoors", "supports": ["BANGLEJS","BANGLEJS2"], + "dependencies" : { "waypoints":"type" }, "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"speedalt.app.js","url":"app.js"}, {"name":"speedalt.img","url":"app-icon.js","evaluate":true}, {"name":"speedalt.settings.js","url":"settings.js"} - ], - "data": [{"name":"speedalt.json"}] + ] } diff --git a/apps/speedalt/settings.js b/apps/speedalt/settings.js index 8906e2e2c..aeaa84f2c 100644 --- a/apps/speedalt/settings.js +++ b/apps/speedalt/settings.js @@ -37,12 +37,7 @@ '< Load GPS Adv Sport': ()=>{load('speedalt.app.js');}, 'Units' : function() { E.showMenu(unitsMenu); }, 'Colours' : function() { E.showMenu(colMenu); }, - 'Kalman Filter' : function() { E.showMenu(kalMenu); }/*, - 'Vibrate' : { - value : settings.buzz, - format : v => v?"On":"Off", - onchange : () => { settings.buzz = !settings.buzz; writeSettings(); } - }*/ + 'Kalman Filter' : function() { E.showMenu(kalMenu); } }; const unitsMenu = { @@ -73,12 +68,10 @@ '< Back': function() { E.showMenu(appMenu); }, 'Speed' : { value : settings.spdFilt, - format : v => v?"On":"Off", onchange : () => { settings.spdFilt = !settings.spdFilt; writeSettings(); } }, 'Altitude' : { value : settings.altFilt, - format : v => v?"On":"Off", onchange : () => { settings.altFilt = !settings.altFilt; writeSettings(); } } }; diff --git a/apps/speedalt2/ChangeLog b/apps/speedalt2/ChangeLog index 9e2abb4ef..ec76c6a16 100644 --- a/apps/speedalt2/ChangeLog +++ b/apps/speedalt2/ChangeLog @@ -8,9 +8,11 @@ 0.08: New features. Added waypoints file and distance to selected waypoint display. Added integration with GPS Setup module to switch GPS to low power mode when screen off. Save display settings and restore when app restarted. 0.09: Add third screen mode with large clock and waypoint selection display to ease visibility in bright daylight. 0.10: Add Kalman filter to smooth the speed and altitude values. Can be disabled in settings. -1.06: Misc memory and screen optimisations. -1.10: Adds Kalman filter. -1.14: Add VMG and coordinates screens -1.43: Adds mirroring of the watch face to an Android device. See README.md -1.49: Droidscript mirroring prog automatically uses last connection address. Auto connects when run. -1.50: Add configuration item Wpt File Suffix. A one character suffix to append to the waypoints.json file. A number of other apps also use this file name. Using the file name suffix allows the speedalt2 waypoints to be retained if one of these other apps is installed for a different use. +0.11: Misc memory and screen optimisations. +0.12: Adds Kalman filter. +0.13: Add VMG and coordinates screens +0.14: Adds mirroring of the watch face to an Android device. See README.md +0.15: Droidscript mirroring prog automatically uses last connection address. Auto connects when run. +0.16: Add configuration item Wpt File Suffix. A one character suffix to append to the waypoints.json file. A number of other apps also use this file name. Using the file name suffix allows the speedalt2 waypoints to be retained if one of these other apps is installed for a different use. +0.17: Use default Bangle formatter for booleans +0.18: Move waypoints.json to 'waypoints' app (with editor) diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md index a86f787cf..d6a5e1d71 100644 --- a/apps/speedalt2/README.md +++ b/apps/speedalt2/README.md @@ -25,7 +25,7 @@ Touch functions as BTN1 short press. ## App Settings -Select the desired display units. Speed can be as per the default locale, kph, knots, mph or m/s. Distance can be km, miles or nautical miles. Altitude can be feet or metres. Select one of three colour schemes. Default (three colours), high contrast (all white on black) or night ( all red on black ). +Select the desired display units. Speed can be as per the default locale, kph, knots, mph or m/s. Distance can be km, miles or nautical miles. Altitude can be feet or metres. Select one of three colour schemes. Default (three colours), high contrast (all white on black) or night ( all red on black ). ## Kalman Filter @@ -43,7 +43,7 @@ This app will work quite happily on its own but will use the [GPS Setup App](htt When using the GPS Setup App this app switches the GPS to SuperE (default) mode while the display is lit and showing fix information. This ensures that that fixes are updated every second or so. 10 seconds after the display is blanked by the watch this app will switch the GPS to PSMOO mode and will only attempt to get a fix every two minutes. This improves power saving while the display is off and the delay gives an opportunity to restore the display before the GPS power mode is switched. -The MAX values continue to be collected with the display off so may appear a little odd after the intermittent fixes of the low power mode. +The MAX values continue to be collected with the display off so may appear a little odd after the intermittent fixes of the low power mode. ## Velocity Made Good - VMG @@ -63,9 +63,9 @@ The Droidscript script file is called : **GPS Adv Sports II.js** **Important Gotcha :** For the BLE comms to find and connect to the Bangle.js it must be paired with the Android device but **NOT** connected. The Bangle.js must have been set in the Bluetooth settings as connectable. -Start/Stop buttons tell the Bangle.js to start or stop sending BLE data packets to the Android device. While stopped the Bangle.js reverts to full power saving mode when the screen is asleep. +Start/Stop buttons tell the Bangle.js to start or stop sending BLE data packets to the Android device. While stopped the Bangle.js reverts to full power saving mode when the screen is asleep. -When running a blue 'led' will flash each time a data packet is recieved to refresh the android display. +When running a blue 'led' will flash each time a data packet is recieved to refresh the android display. An orange 'led' will flash for each reconnection attempt if no data is received for 30 seconds. It will keep trying to reconnect so you can restart the Bangle, run another Bangle app or temprarily turn off bluetooth. The android mirror display will automatically reconnect when the GPS Adv Sports II app is running on the Bangle again. ( Designed to leave the Android device running as the display mirror in a sealed case all day while retaining the ability to do other functions on the Bangle.js and returning to the GPS Speed Alt II app. ) @@ -74,14 +74,12 @@ Android Screen Mirroring:
## Waypoints -Waypoints are used in Distance and VMG modes. Create a file waypoints.json and write to storage on the Bangle.js using the IDE. The first 6 characters of the name are displayed in Speed+[D]istance mode. - -The [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app in the App Loader has a really nice waypoints file editor. (Must be connected to your Bangle.JS and then click on the Download icon.) +Waypoints are used in Distance and VMG modes. See the `Waypoints` app for information on how to create/edit waypoints. The first 6 characters of the name are displayed in Speed+[D]istance mode. By default the waypoints file is called waypoints.json -**Note** : The waypoints.json file is used by a number of different gps apps. The setting 'Wpt File Suffix' allows one of waypoints1.json, waypoints2.json or waypoints3.json to be used instead. This allows the other apps to be used with a different set of waypoints without losing the speedalt2 waypoint set. - +**Note** : The waypoints.json file is used by a number of different gps apps. The setting 'Wpt File Suffix' allows one of waypoints.1.json, waypoints.2.json or waypoints.3.json to be used instead. This allows the other apps to be used with a different set of waypoints without losing the speedalt2 waypoint set. + Sample waypoints.json (My sailing waypoints)
@@ -156,4 +154,3 @@ Developed for my use in sailing, cycling and motorcycling. If you find this soft
 Many thanks to Gordon Williams. Awesome job.
 
 Special thanks also to @jeffmer, for the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app and @hughbarney for the Low power GPS code development and Wouter Bulten for the Kalman filter code.
-
diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js
index 4cdf71913..839ac63eb 100644
--- a/apps/speedalt2/app.js
+++ b/apps/speedalt2/app.js
@@ -185,7 +185,7 @@ let LED = // LED as minimal and only definition (as instance / singleton)
 , reset: function() { this.set(false); } // turn off
 , write: function(v) { this.set(v); }  // turn on w/ no arg or truey, else off
 , toggle: function() { this.set( ! this.isOn); } // toggle the LED
-}, LED1 = LED; // LED1 as 'synonym' for LED 
+}, LED1 = LED; // LED1 as 'synonym' for LED
 
 
 var lf = {fix:0,satellites:0};
@@ -210,7 +210,7 @@ function nxtWp(){
 }
 
 function loadWp() {
-  var w = require("Storage").readJSON('waypoints'+cfg.wptSfx+'.json')||[{name:"NONE"}];
+  var w = require("waypoints").load(cfg.wptSfx);
   if (cfg.wp>=w.length) cfg.wp=0;
   if (cfg.wp<0) cfg.wp = w.length-1;
   savSettings();
@@ -239,7 +239,7 @@ function bearing(a,b){
 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 metres
   var d = Math.sqrt(x*x + y*y) * 6371000;
   return d;
@@ -251,38 +251,38 @@ function drawScrn(dat) {
 
   buf.clear();
   buf.setBgColor(0);
-  
+
   var n;
   n = dat.val.toString();
-  
+
   var s=50;    // Font size
   var l=n.length;
-  
+
   if ( l <= 7 ) s=55;
   if ( l <= 6 ) s=60;
   if ( l <= 5 ) s=80;
   if ( l <= 4 ) s=100;
   if ( l <= 3 ) s=120;
-        
-  buf.setFontAlign(0,0); //Centre 
-  buf.setColor(1);  
+
+  buf.setFontAlign(0,0); //Centre
+  buf.setColor(1);
   buf.setFontVector(s);
   buf.drawString(n,126,52);
 
-  
+
   // Primary Units
   buf.setFontAlign(-1,1); //left, bottom
-  buf.setColor(2);  
+  buf.setColor(2);
   buf.setFontVector(35);
-  buf.drawString(dat.unit,5,164);  
-  
+  buf.drawString(dat.unit,5,164);
+
   drawMax(dat.max); // MAX display indicator
   drawWP(dat.wp);  // Waypoint name
   drawSats(dat.sats);
-   
+
   g.reset();
   g.drawImage(img,0,40);
-  
+
   LED1.write(!pwrSav);
 
 }
@@ -295,7 +295,7 @@ function drawPosn(dat) {
   var x, y;
   x=210;
   y=0;
-  buf.setFontAlign(1,-1); 
+  buf.setFontAlign(1,-1);
   buf.setFontVector(60);
   buf.setColor(1);
 
@@ -320,45 +320,45 @@ function drawPosn(dat) {
 
 function drawClock() {
   if (!canDraw) return;
-  
+
   buf.clear();
   buf.setBgColor(0);
-  
+
   var x, y;
   x=185;
   y=0;
-  buf.setFontAlign(1,-1); 
+  buf.setFontAlign(1,-1);
   buf.setFontVector(94);
   time = require("locale").time(new Date(),1);
-  
+
   buf.setColor(1);
-  
+
   buf.drawString(time.substring(0,2),x,y);
   buf.drawString(time.substring(3,5),x,y+80);
-  
+
   g.reset();
   g.drawImage(img,0,40);
-  
+
   LED1.write(!pwrSav);
 }
 
 function drawWP(wp) {
-  buf.setColor(3);  
+  buf.setColor(3);
   buf.setFontAlign(0,1); //left, bottom
   buf.setFontVector(40);
-  buf.drawString(wp,120,132);  
+  buf.drawString(wp,120,132);
 }
 
 function drawSats(sats) {
-  buf.setColor(3);  
+  buf.setColor(3);
   buf.setFont("6x8", 2);
   buf.setFontAlign(1,1); //right, bottom
-  buf.drawString(sats,240,160);  
+  buf.drawString(sats,240,160);
 }
 
 function drawMax(max) {
   buf.setFontVector(30);
-  buf.setColor(2); 
+  buf.setColor(2);
   buf.setFontAlign(0,1); //centre, bottom
   buf.drawString(max,120,164);
 }
@@ -369,7 +369,7 @@ if ( emulator ) {
     fix.speed = 10 + (Math.random()*5);
     fix.alt = 354 + (Math.random()*50);
     fix.lat = -38.92;
-    fix.lon = 175.7613350;   
+    fix.lon = 175.7613350;
     fix.course = 245;
     fix.satellites = 12;
     fix.time = new Date();
@@ -378,7 +378,7 @@ if ( emulator ) {
 
   var m;
 
-  var sp = '---';        
+  var sp = '---';
   var al = sp;
   var di = sp;
   var brg = ''; // bearing
@@ -390,15 +390,15 @@ if ( emulator ) {
   var lon = '---.--';
   var sats = sp;
   var vmg = sp;
-  
-  
+
+
   // Waypoint name
   var wpName = wp.name;
   if ( wpName == undefined || wpName == 'NONE' ) wpName = '';
-  wpName = wpName.substring(0,8);  
+  wpName = wpName.substring(0,8);
 
   if (fix.fix) lf = fix;
-  
+
   if (lf.fix) {
 
     // Smooth data
@@ -408,10 +408,10 @@ if ( emulator ) {
       lf.smoothed = 1;
       if ( maxN <= 15 ) maxN++;
     }
-    
+
     // Bearing to waypoint
     brg = bearing(lf,wp);
-    
+
     // Current course
     crs = lf.course;
 
@@ -447,19 +447,19 @@ if ( emulator ) {
     if ( di >= 100 ) di = parseFloat(di).toFixed(1);
     if ( di >= 1000 ) di = parseFloat(di).toFixed(0);
     if (isNaN(di)) di = '------';
-    
+
     // Age of last fix (secs)
     age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000));
 
     // Lat / Lon
     ns = 'N';
     if ( lf.lat < 0 ) ns = 'S';
-    lat = Math.abs(lf.lat.toFixed(2)); 
+    lat = Math.abs(lf.lat.toFixed(2));
 
     ew = 'E';
     if ( lf.lon < 0 ) ew = 'W';
-    lon = Math.abs(lf.lon.toFixed(2)); 
-    
+    lon = Math.abs(lf.lon.toFixed(2));
+
     // Sats
     if ( age > 10 ) {
       sats = 'Age:'+Math.round(age);
@@ -573,7 +573,7 @@ if ( emulator ) {
         ew:ew
       });
   }
-  
+
   if ( cfg.modeA == 5 )  {
     // Large clock
     drawClock();
@@ -585,14 +585,14 @@ function prevScrn() {
     cfg.modeA = cfg.modeA-1;
     if ( cfg.modeA < 0 ) cfg.modeA = 5;
     savSettings();
-    onGPS(lf); 
+    onGPS(lf);
 }
 
 function nextScrn() {
     cfg.modeA = cfg.modeA+1;
     if ( cfg.modeA > 5 ) cfg.modeA = 0;
     savSettings();
-    onGPS(lf); 
+    onGPS(lf);
 }
 
 // Next function on a screen
@@ -610,7 +610,7 @@ function nextFunc(dur) {
 function updateClock() {
   if (!canDraw) return;
   if ( cfg.modeA != 5 )  return;
-  drawClock(); 
+  drawClock();
   if ( emulator ) {maxSpd++;maxAlt++;}
 }
 
@@ -661,10 +661,10 @@ function setButtons(){
     var dur = e.time - e.lastTime;
     nextFunc(dur);
   }, BTN1, { edge:"falling",repeat:true});
-  
-  // Power saving on/off 
+
+  // Power saving on/off
   setWatch(function(e){
-    pwrSav=!pwrSav; 
+    pwrSav=!pwrSav;
     if ( pwrSav ) {
       var s = require('Storage').readJSON('setting.json',1)||{};
       var t = s.timeout||10;
@@ -676,7 +676,7 @@ function setButtons(){
     }
       LED1.write(!pwrSav);
   }, BTN2, {repeat:true,edge:"falling"});
-  
+
   // BTN3 - next screen
   setWatch(function(e){
     nextScrn();
@@ -685,7 +685,7 @@ function setButtons(){
 
 Bangle.on('lcdPower',function(on) {
   if (!SCREENACCESS.withApp) return;
-  if (on) startDraw(); 
+  if (on) startDraw();
   else stopDraw();
 });
 
@@ -702,7 +702,7 @@ Bangle.on('touch', function(button){
 
 // == Main Prog
 
-// Read settings. 
+// Read settings.
 let cfg = require('Storage').readJSON('speedalt2.json',1)||{};
 
 cfg.spd = cfg.spd||1;  // Multiplier for speed unit conversions. 0 = use the locale values for speed
@@ -713,13 +713,13 @@ cfg.dist = cfg.dist||1000;// Multiplier for distnce unit conversions.
 cfg.dist_unit = cfg.dist_unit||'km';  // Displayed altitude units
 cfg.colour = cfg.colour||0;          // Colour scheme.
 cfg.wp = cfg.wp||0;        // Last selected waypoint for dist
-cfg.modeA = cfg.modeA||0;    // 0=Speed 1=Alt 2=Dist 3 = vmg 4=Position 5=Clock 
+cfg.modeA = cfg.modeA||0;    // 0=Speed 1=Alt 2=Dist 3 = vmg 4=Position 5=Clock
 cfg.primSpd = cfg.primSpd||0;    // 1 = Spd in primary, 0 = Spd in secondary
 
-cfg.spdFilt = cfg.spdFilt==undefined?true:cfg.spdFilt; 
+cfg.spdFilt = cfg.spdFilt==undefined?true:cfg.spdFilt;
 cfg.altFilt = cfg.altFilt==undefined?true:cfg.altFilt;
 cfg.touch = cfg.touch==undefined?true:cfg.touch;
-cfg.wptSfx = cfg.wptSfx==undefined?'':cfg.wptSfx; 
+cfg.wptSfx = cfg.wptSfx==undefined?'':cfg.wptSfx;
 
 if ( cfg.spdFilt ) var spdFilter = new KalmanFilter({R: 0.1 , Q: 1 });
 if ( cfg.altFilt ) var altFilter = new KalmanFilter({R: 0.01, Q: 2 });
@@ -749,7 +749,7 @@ var SCREENACCESS = {
       withApp:true,
       request:function(){this.withApp=false;stopDraw();},
       release:function(){this.withApp=true;startDraw();}
-}; 
+};
 
 var gpssetup;
 try {
diff --git a/apps/speedalt2/metadata.json b/apps/speedalt2/metadata.json
index 2a111af28..2c8d37f79 100644
--- a/apps/speedalt2/metadata.json
+++ b/apps/speedalt2/metadata.json
@@ -2,12 +2,13 @@
   "id": "speedalt2",
   "name": "GPS Adventure Sports II",
   "shortName":"GPS Adv Sport II",
-  "version":"1.50",
+  "version":"0.18",
   "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.",
   "icon": "app.png",
   "type": "app",
   "tags": "tool,outdoors",
   "supports": ["BANGLEJS"],
+  "dependencies" : { "waypoints":"type" },
   "readme": "README.md",
   "allow_emulator": true,
   "storage": [
@@ -16,10 +17,6 @@
     {"name":"speedalt2.settings.js","url":"settings.js"}
   ],
   "data": [
-    {"name":"speedalt2.json"},
-    {"name":"waypoints.json"},
-    {"name":"waypoints1.json"},
-    {"name":"waypoints2.json"},
-    {"name":"waypoints3.json"}
+    {"name":"speedalt2.json"}
   ]
 }
diff --git a/apps/speedalt2/settings.js b/apps/speedalt2/settings.js
index 1bdb58f9d..63fa424ba 100644
--- a/apps/speedalt2/settings.js
+++ b/apps/speedalt2/settings.js
@@ -46,7 +46,6 @@
     'Wpt File Suffix' : function() { E.showMenu(sfxMenu); },
     'Touch' : {
        value : settings.touch,
-       format : v => v?"On":"Off",
        onchange : () => { settings.touch = !settings.touch; writeSettings(); }
     }
   };
@@ -89,12 +88,10 @@
     '< Back': function() { E.showMenu(appMenu); },
     'Speed' : {
     value : settings.spdFilt,
-    format : v => v?"On":"Off",
     onchange : () => { settings.spdFilt = !settings.spdFilt; writeSettings(); }
     },
     'Altitude' : {
     value : settings.altFilt,
-    format : v => v?"On":"Off",
     onchange : () => { settings.altFilt = !settings.altFilt; writeSettings(); }
     }
   };
diff --git a/apps/stardateclock/ChangeLog b/apps/stardateclock/ChangeLog
index 431463bc8..bb6430b65 100644
--- a/apps/stardateclock/ChangeLog
+++ b/apps/stardateclock/ChangeLog
@@ -1 +1,3 @@
 0.01: Initial release on the app repository for Bangle.js 1 and 2
+0.02: Fixed let/const usage while using firmware version >=2v14
+0.03: Tell clock widgets to hide.
diff --git a/apps/stardateclock/app.js b/apps/stardateclock/app.js
index 70f1070fc..adf2c14c7 100644
--- a/apps/stardateclock/app.js
+++ b/apps/stardateclock/app.js
@@ -1,10 +1,10 @@
 // Stardate clock face, by KaiRo.at, 2021-2022
 
-var redrawClock = true;
-var clockface = "digital";
+let redrawClock = true;
+let clockface = "digital";
 
 // note: Bangle.js 1 has 240x240x16, 2 has 176x176x3 screen
-var bpp = g.getBPP ? g.getBPP() : 16;
+const bpp = g.getBPP ? g.getBPP() : 16;
 
 // Load fonts
 Graphics.prototype.setFontAntonio27 = function(scale) {
@@ -22,16 +22,15 @@ const fontSizeLarge = 1;
 const fontHeightLarge = 42 * fontSizeLarge;
 
 // LCARS dimensions
+let baseUnit1 = 5;
+let baseUnit2 = 3;
+let baseUnit3 = 10;
 if (g.getWidth() < 200) { // Bangle.js 2
-  const baseUnit1 = 3;
-  const baseUnit2 = 2;
-  const baseUnit3 = 7;
-}
-else {
-  const baseUnit1 = 5;
-  const baseUnit2 = 3;
-  const baseUnit3 = 10;
+  baseUnit1 = 3;
+  baseUnit2 = 2;
+  baseUnit3 = 7;
 }
+
 const widgetsHeight = 24;
 const sbarWid = baseUnit3 * 5;
 const hbarHt = baseUnit1;
@@ -86,19 +85,19 @@ const colorLCARSPurple = "#A06060";
 const colorLCARSBrown = "#C09070";
 // More colors: teal #008484, yellow FFCF00, purple #6050B0
 
-var lastSDateString;
-var lastTimeStringToMin;
-var lastTimeStringSec;
-var lastDateString;
-var lastAnalogDate;
+let lastSDateString;
+let lastTimeStringToMin;
+let lastTimeStringSec;
+let lastDateString;
+let lastAnalogDate;
 
 function updateStardate() {
-  var curDate = new Date();
+  const curDate = new Date();
 
   // Note that the millisecond division and the 1000-unit multiplier cancel each other out.
-  var sdateval = (curDate - gSDBase) / secondsPerYear;
+  const sdateval = (curDate - gSDBase) / secondsPerYear;
 
-  var sdatestring = (Math.floor(sdateval * sdateDecFactor) / sdateDecFactor).toFixed(sdateDecimals);
+  const sdatestring = (Math.floor(sdateval * sdateDecFactor) / sdateDecFactor).toFixed(sdateDecimals);
 
   // Reset the state of the graphics library.
   g.reset();
@@ -120,34 +119,33 @@ function updateStardate() {
   lastSDateString = sdatestring;
 
   // Schedule next when an update to the last decimal is due.
-  var mstonextUpdate = (Math.ceil(sdateval * sdateDecFactor) / sdateDecFactor - sdateval) * secondsPerYear;
+  const mstonextUpdate = (Math.ceil(sdateval * sdateDecFactor) / sdateDecFactor - sdateval) * secondsPerYear;
   if (redrawClock) {
     setTimeout(updateStardate, mstonextUpdate);
   }
 }
 
 function updateConventionalTime() {
-  var curDate = new Date();
+  const curDate = new Date();
 
   if (clockface == "digital") {
     drawDigitalClock(curDate);
-  }
-  else {
+  } else {
     drawAnalogClock(curDate);
   }
 
   // Schedule next when an update to the last second is due.
-  var mstonextUpdate = Math.ceil(curDate / 1000) * 1000 - curDate;
+  const mstonextUpdate = Math.ceil(curDate / 1000) * 1000 - curDate;
   if (redrawClock) {
     setTimeout(updateConventionalTime, mstonextUpdate);
   }
 }
 
 function drawDigitalClock(curDate) {
-  var timestringToMin = ("0" + curDate.getHours()).substr(-2) + ":"
+  const timestringToMin = ("0" + curDate.getHours()).substr(-2) + ":"
     + ("0" + curDate.getMinutes()).substr(-2) + ":";
-  var timestringSec = ("0" + curDate.getSeconds()).substr(-2);
-  var datestring = "" + curDate.getFullYear() + "-"
+  const timestringSec = ("0" + curDate.getSeconds()).substr(-2);
+  const datestring = "" + curDate.getFullYear() + "-"
     + ("0" + (curDate.getMonth() + 1)).substr(-2) + "-"
     + ("0" + curDate.getDate()).substr(-2);
 
@@ -156,7 +154,7 @@ function drawDigitalClock(curDate) {
   g.setBgColor(colorBg);
   // Set Font
   g.setFont(fontNameLarge, fontSizeLarge);
-  var ctimePosLeft = ctimePosCenter - g.stringWidth("12:34:56") / 2;
+  let ctimePosLeft = ctimePosCenter - g.stringWidth("12:34:56") / 2;
   if (ctimePosLeft + g.stringWidth("00:00:00") > g.getWidth()) {
     ctimePosLeft = g.getWidth() - g.stringWidth("00:00:00");
   }
@@ -174,7 +172,7 @@ function drawDigitalClock(curDate) {
     g.drawString(timestringToMin, ctimePosLeft, ctimePosTop);
     lastTimeStringToMin = timestringToMin;
   }
-  var ctimePosLeftSec = ctimePosLeft + g.stringWidth(timestringToMin);
+  const ctimePosLeftSec = ctimePosLeft + g.stringWidth(timestringToMin);
   if (lastTimeStringSec) {
     // Clear the area where we want to draw the seconds.
     //g.setBgColor("#FF6600"); // for debugging
@@ -190,7 +188,7 @@ function drawDigitalClock(curDate) {
   if (datestring != lastDateString) {
     // Set Font
     g.setFont(fontName, fontSize);
-    var cdatePosLeft = cdatePosCenter - g.stringWidth("1234-56-78") / 2;
+    const cdatePosLeft = cdatePosCenter - g.stringWidth("1234-56-78") / 2;
     if (lastDateString) {
       // Clear the area where we want to draw the time.
       //g.setBgColor("#FF6600"); // for debugging
@@ -212,8 +210,7 @@ function drawLine(x1, y1, x2, y2, color) {
   // On high-bpp devices, use anti-aliasing. Low-bpp (Bangle.js 2) doesn't clear nicely with AA.
   if (bpp > 3 && g.drawLineAA) {
     g.drawLineAA(x1, y1, x2, y2);
-  }
-  else {
+  } else {
     g.drawLine(x1, y1, x2, y2);
   }
 }
@@ -228,17 +225,17 @@ function drawAnalogClock(curDate) {
   g.setBgColor(colorBg);
   // Init variables for drawing any seconds we have not drawn.
   // If minute changed, we'll set for the full wheel below.
-  var firstDrawSecond = lastAnalogDate ? lastAnalogDate.getSeconds() + 1 : curDate.getSeconds();
-  var lastDrawSecond = curDate.getSeconds();
+  let firstDrawSecond = lastAnalogDate ? lastAnalogDate.getSeconds() + 1 : curDate.getSeconds();
+  let lastDrawSecond = curDate.getSeconds();
   if (!lastAnalogDate || curDate.getMinutes() != lastAnalogDate.getMinutes()) {
     // Draw the main hour lines.
     //g.setColor("#9C9CFF");
     //g.drawCircle(clockCtrX, clockCtrY, analogRad);
     for (let i = 0; i < 60; i = i + 15) {
-      let edgeX = clockCtrX + analogRad * Math.sin(i * Math.PI / 30);
-      let edgeY = clockCtrY - analogRad * Math.cos(i * Math.PI / 30);
-      let innerX = clockCtrX + (analogRad - analogMainLineLength) * Math.sin(i * Math.PI / 30);
-      let innerY = clockCtrY - (analogRad - analogMainLineLength) * Math.cos(i * Math.PI / 30);
+      const edgeX = clockCtrX + analogRad * Math.sin(i * Math.PI / 30);
+      const edgeY = clockCtrY - analogRad * Math.cos(i * Math.PI / 30);
+      const innerX = clockCtrX + (analogRad - analogMainLineLength) * Math.sin(i * Math.PI / 30);
+      const innerY = clockCtrY - (analogRad - analogMainLineLength) * Math.cos(i * Math.PI / 30);
       drawLine(edgeX, edgeY, innerX, innerY, colorHours);
     }
     // Set for drawing the full second wheel.
@@ -247,17 +244,15 @@ function drawAnalogClock(curDate) {
   }
   // Draw the second wheel, or the parts of it that we haven't done yet.
   for (let i = firstDrawSecond; i <= lastDrawSecond; i++) {
-    let edgeX = clockCtrX + analogRad * Math.sin(i * Math.PI / 30);
-    let edgeY = clockCtrY - analogRad * Math.cos(i * Math.PI / 30);
-    let innerX = clockCtrX + (analogRad - analogSubLineLength) * Math.sin(i * Math.PI / 30);
-    let innerY = clockCtrY - (analogRad - analogSubLineLength) * Math.cos(i * Math.PI / 30);
+    const edgeX = clockCtrX + analogRad * Math.sin(i * Math.PI / 30);
+    const edgeY = clockCtrY - analogRad * Math.cos(i * Math.PI / 30);
+    const innerX = clockCtrX + (analogRad - analogSubLineLength) * Math.sin(i * Math.PI / 30);
+    const innerY = clockCtrY - (analogRad - analogSubLineLength) * Math.cos(i * Math.PI / 30);
     if (i <= curDate.getSeconds()) {
       drawLine(edgeX, edgeY, innerX, innerY, colorSeconds);
-    }
-    else if (i % 5 == 0) {
+    } else if (i % 5 == 0) {
       drawLine(edgeX, edgeY, innerX, innerY, colorHours);
-    }
-    else {
+    } else {
       clearLine(edgeX, edgeY, innerX, innerY);
     }
   }
@@ -265,25 +260,25 @@ function drawAnalogClock(curDate) {
     // Clear previous hands.
     if (curDate.getMinutes() != lastAnalogDate.getMinutes()) {
       // Clear hour hand.
-      let HhAngle = (lastAnalogDate.getHours() + lastAnalogDate.getMinutes() / 60) * Math.PI / 6;
-      let HhEdgeX = clockCtrX + analogHourHandLength * Math.sin(HhAngle);
-      let HhEdgeY = clockCtrY - analogHourHandLength * Math.cos(HhAngle);
+      const HhAngle = (lastAnalogDate.getHours() + lastAnalogDate.getMinutes() / 60) * Math.PI / 6;
+      const HhEdgeX = clockCtrX + analogHourHandLength * Math.sin(HhAngle);
+      const HhEdgeY = clockCtrY - analogHourHandLength * Math.cos(HhAngle);
       clearLine(HhEdgeX, HhEdgeY, clockCtrX, clockCtrY);
       // Clear minute hand.
-      let MhEdgeX = clockCtrX + analogMinuteHandLength * Math.sin(lastAnalogDate.getMinutes() * Math.PI / 30);
-      let MhEdgeY = clockCtrY - analogMinuteHandLength * Math.cos(lastAnalogDate.getMinutes() * Math.PI / 30);
+      const MhEdgeX = clockCtrX + analogMinuteHandLength * Math.sin(lastAnalogDate.getMinutes() * Math.PI / 30);
+      const MhEdgeY = clockCtrY - analogMinuteHandLength * Math.cos(lastAnalogDate.getMinutes() * Math.PI / 30);
       clearLine(MhEdgeX, MhEdgeY, clockCtrX, clockCtrY);
     }
   }
   if (!lastAnalogDate || curDate.getMinutes() != lastAnalogDate.getMinutes()) {
     // Draw hour hand.
-    let HhAngle = (curDate.getHours() + curDate.getMinutes() / 60) * Math.PI / 6;
-    let HhEdgeX = clockCtrX + analogHourHandLength * Math.sin(HhAngle);
-    let HhEdgeY = clockCtrY - analogHourHandLength * Math.cos(HhAngle);
+    const HhAngle = (curDate.getHours() + curDate.getMinutes() / 60) * Math.PI / 6;
+    const HhEdgeX = clockCtrX + analogHourHandLength * Math.sin(HhAngle);
+    const HhEdgeY = clockCtrY - analogHourHandLength * Math.cos(HhAngle);
     drawLine(HhEdgeX, HhEdgeY, clockCtrX, clockCtrY, colorHands);
     // Draw minute hand.
-    let MhEdgeX = clockCtrX + analogMinuteHandLength * Math.sin(curDate.getMinutes() * Math.PI / 30);
-    let MhEdgeY = clockCtrY - analogMinuteHandLength * Math.cos(curDate.getMinutes() * Math.PI / 30);
+    const MhEdgeX = clockCtrX + analogMinuteHandLength * Math.sin(curDate.getMinutes() * Math.PI / 30);
+    const MhEdgeY = clockCtrY - analogMinuteHandLength * Math.cos(curDate.getMinutes() * Math.PI / 30);
     drawLine(MhEdgeX, MhEdgeY, clockCtrX, clockCtrY, colorHands);
   }
   lastAnalogDate = curDate;
@@ -292,8 +287,7 @@ function drawAnalogClock(curDate) {
 function switchClockface() {
   if (clockface == "digital") {
     clockface = "analog";
-  }
-  else {
+  } else {
     clockface = "digital";
   }
   // Clear whole lower area.
@@ -340,10 +334,10 @@ updateStardate();
 updateConventionalTime();
 // Make sure widgets can be shown.
 //g.setColor("#FF0000"); g.fillRect(0, 0, g.getWidth(), widgetsHeight); // debug
-Bangle.loadWidgets();
-Bangle.drawWidgets();
 // Show launcher on button press as usual for a clock face
 Bangle.setUI("clock", Bangle.showLauncher);
+Bangle.loadWidgets();
+Bangle.drawWidgets();
 // Stop updates when LCD is off, restart when on
 Bangle.on('lcdPower', on => {
   if (on) {
@@ -351,8 +345,7 @@ Bangle.on('lcdPower', on => {
     // Draw immediately to kick things off.
     updateStardate();
     updateConventionalTime();
-  }
-  else {
+  } else {
     redrawClock = false;
   }
 });
diff --git a/apps/stardateclock/metadata.json b/apps/stardateclock/metadata.json
index 2f4f27425..d27b14512 100644
--- a/apps/stardateclock/metadata.json
+++ b/apps/stardateclock/metadata.json
@@ -3,7 +3,7 @@
   "name":"Stardate Clock",
   "shortName":"Stardate Clock",
   "description": "A clock displaying a stardate along with a 'standard' digital/analog clock in LCARS design",
-  "version":"0.01",
+  "version":"0.03",
   "icon": "app.png",
   "type":"clock",
   "tags": "clock",
diff --git a/apps/tabanchi/ChangeLog b/apps/tabanchi/ChangeLog
index 3889ade8e..4e2facf6f 100644
--- a/apps/tabanchi/ChangeLog
+++ b/apps/tabanchi/ChangeLog
@@ -1,2 +1,3 @@
 0.01: Initial implementation
 0.02: Fix app icon
+0.03: Fix clock animation issue and reduce source size
diff --git a/apps/tabanchi/app.js b/apps/tabanchi/app.js
index c87a08817..f159052b7 100644
--- a/apps/tabanchi/app.js
+++ b/apps/tabanchi/app.js
@@ -2,14 +2,18 @@
 // TABANCHI -- たばんち
 
 const scale = 6;
-let tool = -1;
 const w = g.getWidth();
 const h = g.getHeight();
+const yy = 34;
+const y = 40 - scale;
+let tool = -1;
 let hd = 1;
 let vd = 1;
 let x = 20;
 let sx = 0; // screen scroll x position
-const y = 40 - scale;
+let cacaLevel = 0;
+let cacaBirth = null;
+let angryState = 0;
 let animated = true;
 let transition = false;
 let caca = null;
@@ -22,9 +26,38 @@ let oldMode = '';
 let gameChoice = 0;
 let gameTries = 0;
 let gameWins = 0;
+let statusMode = 0;
+let lightSelect = 0;
+let lightMode = 0; // on is zero
+let frame = 0;
+
+const tama = {
+  age: 0,
+  weight: 1,
+  aspect: 6,
+  discipline: 0,
+  happy: 3,
+  sick: false,
+  hungry: 3,
+  cacas: 0,
+  // hidden
+  sickness: 0,
+  defenses: 100,
+  tummy: 100,
+  awake: 3
+};
+
 
 g.setBgColor(0);
 
+const sun = {
+  width: 8,
+  height: 8,
+  bpp: 1,
+  transparent: 1,
+  buffer: atob('773nW9rnvfc=')
+};
+
 const tama06eat0 = {
   width: 16,
   height: 16,
@@ -387,7 +420,6 @@ const caca01 = {
   buffer: atob('////v/33v7+3+f4v0HwH////')
 };
 
-// var img = hs.decompress(atob("sFggP/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A+A"));
 const tama00 = {
   width: 16,
   height: 16,
@@ -574,23 +606,6 @@ g.drawString('Loading...', 10, 10);
 egg = egg00;
 n = tama00;
 
-const tama = {
-  // visible
-  age: 0,
-  weight: 1,
-  aspect: 6,
-  discipline: 0,
-  happy: 3,
-  sick: false,
-  hungry: 3,
-  cacas: 0, // move from cacas
-  // hidden
-  sickness: 0,
-  defenses: 100,
-  tummy: 100,
-  awake: 3
-};
-
 function drawHearts (n) {
   for (i = 0; i < 4; i++) {
     const himg = (i < n) ? heart1 : heart0;
@@ -599,7 +614,6 @@ function drawHearts (n) {
 }
 
 function drawLinebar (n, arrow) { // 0-100
-  const yy = 34;
   g.drawImage(linebar, 0, yy + (scale * 8), { scale: scale });
 
   let wop = scale * 2; // (frame++%2)? scale*3:scale*2;
@@ -631,7 +645,6 @@ function drawLinebar (n, arrow) { // 0-100
 }
 
 function drawStatus () {
-  const yy = 34;
   switch (statusMode) {
     case 0:
       g.drawImage(face, scale, yy, { scale: scale });
@@ -774,11 +787,6 @@ function drawScene () {
   }
 }
 
-var statusMode = 0;
-var lightSelect = 0;
-var lightMode = 0; // on is zero
-let frame = 0;
-
 function drawAngry () {
   const one = angryState % 2;
   g.drawImage(one ? tama06no0 : tama06no1, (scale * 5), 40, { scale: scale });
@@ -833,14 +841,6 @@ function drawMedicine () { // food eating animation
   g.drawImage(tama06no0, (scale * 10), 40, { scale: scale });
 }
 
-var sun = {
-  width: 8,
-  height: 8,
-  bpp: 1,
-  transparent: 1,
-  buffer: atob('773nW9rnvfc=')
-};
-
 function drawEating () { // food eating animation
   const one = angryState % 2;
   const snack = [snack0, snack1, snack2];
@@ -953,6 +953,7 @@ function nextItem () {
   tool++;
   if (tool > 6) tool = 0;
 }
+
 function prevItem () {
   tool--;
   if (tool < 0) tool = 7;
@@ -1099,7 +1100,6 @@ function drawCaca () {
     }
   }
 }
-var angryState = 0;
 
 function animateHappy () {
   if (transition || mode == 'happy') {
@@ -1208,7 +1208,7 @@ function animateShower () {
 }
 
 function animateToGame () {
-  if (transition || mode == 'game') {
+  if (transition || mode === 'game') {
     return;
   }
   mode = 'game';
@@ -1298,14 +1298,6 @@ function button (n) {
   }
 
   if (mode == 'game') {
-    /*
-    if (gameTries > 3) {
-      mode = "";
-      gameWins = 0;
-      gameTries = 0;
-      //tama.tired++;
-    }
-    */
     switch (n) {
       case 1:
         // pick left
@@ -1345,8 +1337,7 @@ function button (n) {
           drawScene();
           break;
         case 'status':
-          if (oldMode == 'clock') {
-          } else {
+          if (oldMode != 'clock') {
             statusMode++;
             drawScene();
           }
@@ -1363,8 +1354,7 @@ function button (n) {
           animateFromClock();
           break;
         case 'status':
-          if (oldMode == 'clock') {
-          } else {
+          if (oldMode != 'clock') {
             statusMode++;
             drawScene();
           }
@@ -1433,7 +1423,6 @@ function drawGame () {
       }
       mode = oldMode;
       oldMode = '';
-    //  g.drawImage();
     } else {
       g.drawImage(one ? tama06no1 : tama06no0, (scale * 7) + sx, 40, { scale: scale });
     }
@@ -1467,7 +1456,6 @@ function drawClock () {
     const s1 = numbers[ts[1] - '0'];
     const s2 = numbers[ts[3] - '0'];
     const s3 = numbers[ts[4] - '0'];
-    const yy = 34;
     // hours
     if (s0) {
       g.drawImage(s0, wsx, yy, { scale: scale });
@@ -1515,17 +1503,11 @@ function drawClock () {
 }
 
 setInterval(function () {
-  // if (animated) {
   updateAnimation();
   drawScene();
-  // }
 }, 1000);
 
-let cacaLevel = 0;
-let cacaBirth = null;
-
-setInterval(function () {
-  // poo maker
+function pooMaker() {
   if (tama.hungry > 0 && !tama.sleep) {
     const a = 0 | (cacaLevel / tama.tummy);
     const b = 0 | ((cacaLevel + tama.hungry) / tama.tummy);
@@ -1545,9 +1527,8 @@ setInterval(function () {
     tama.awake--;
     tama.sleep = false;
   }
-}, 5000);
-
-setInterval(function () {
+}
+function sickMaker() {
   if (tama.sleep) {
     return;
   }
@@ -1569,8 +1550,10 @@ setInterval(function () {
   if (tama.sick > 0) {
     callForAttention = true;
   }
-}, 2000);
+}
 
+setInterval(pooMaker, 5e3);
+setInterval(sickMaker, 2e3);
 updateAnimation();
 
 Bangle.on('touch', function (r, s) {
@@ -1600,4 +1583,3 @@ Bangle.on('touch', function (r, s) {
     button(2);
   }
 });
-
diff --git a/apps/tabanchi/metadata.json b/apps/tabanchi/metadata.json
index 335dd0326..f72147162 100644
--- a/apps/tabanchi/metadata.json
+++ b/apps/tabanchi/metadata.json
@@ -2,7 +2,7 @@
   "id": "tabanchi",
   "name": "Tabanchi",
   "shortName": "Tabanchi",
-  "version": "0.02",
+  "version": "0.03",
   "type": "app",
   "description": "Tamagotchi WatchApp",
   "icon": "app.png",
diff --git a/apps/teatimer/ChangeLog b/apps/teatimer/ChangeLog
new file mode 100644
index 000000000..db8dd270b
--- /dev/null
+++ b/apps/teatimer/ChangeLog
@@ -0,0 +1,3 @@
+0.01: New App!
+0.02: Fix issue setting colors after showMessage
+0.03: Fix BG/FG Color if e.g. theme background is black
diff --git a/apps/teatimer/app.js b/apps/teatimer/app.js
index dd7afdadb..c394b5e00 100644
--- a/apps/teatimer/app.js
+++ b/apps/teatimer/app.js
@@ -67,9 +67,9 @@ function startTimer() {
   - hint for help in state start
 */ 
 function showCounter(withHint) {
-  //g.clear();
+  g.reset(); // workaround for E.showMessage bg color in 2v14 and earlier
   E.showMessage("", appTitle());
-  g.setFontAlign(0,0); // center font
+  g.reset().setFontAlign(0,0); // center font
   // draw the current counter value
   g.setBgColor(-1).setColor(0,0,1); // blue
   g.setFont("Vector",20); // vector font, 20px  
@@ -123,9 +123,9 @@ function countUp() {
     outOfTime();
     return;
   }
-  g.clear();
+  g.reset(); // workaround for E.showMessage bg color in 2v14 and earlier
   E.showMessage("", appTitle());
-  g.setFontAlign(0,0); // center font
+  g.reset().setFontAlign(0,0); // center font
   g.setBgColor(-1).setColor(0,0,1); // blue
   g.setFont("Vector",20); // vector font, 20px
   g.drawString("Timer: " + timeFormated(counterStart),80,55);
@@ -216,6 +216,8 @@ function initDragEvents() {
 function showHelp() {
   if (state == states.start) {
     state = states.help;
+	g.setBgColor(g.theme.bg);
+	g.setColor(g.theme.fg);
     E.showMessage("Swipe up/down\n+/- one minute\n\nSwipe left/right\n+/- 15 seconds\n\nPress Btn1 to start","Tea timer help");
   }
   // return to start
diff --git a/apps/teatimer/metadata.json b/apps/teatimer/metadata.json
index acace0402..b5cdce92e 100644
--- a/apps/teatimer/metadata.json
+++ b/apps/teatimer/metadata.json
@@ -1,7 +1,7 @@
 {
   "id": "teatimer",
   "name": "Tea Timer",
-  "version": "0.01",
+  "version": "0.03",
   "description": "A simple timer. You can easyly set up the time.",
   "icon": "teatimer.png",
   "type": "app",
diff --git a/apps/terminalclock/ChangeLog b/apps/terminalclock/ChangeLog
index ce31583e9..75d1a760e 100644
--- a/apps/terminalclock/ChangeLog
+++ b/apps/terminalclock/ChangeLog
@@ -4,3 +4,4 @@
 0.04: Fix settings bug
 0.05: Add altitude display (only Bangle.js 2)
 0.06: Add power related settings to control the HR and pressure(altitude) sensor from the watchface
+0.07: Use ClockFace module and rework the settings to be able to personnalize the order of the lines
diff --git a/apps/terminalclock/app.js b/apps/terminalclock/app.js
index 7dc3bf1d1..b60a32094 100644
--- a/apps/terminalclock/app.js
+++ b/apps/terminalclock/app.js
@@ -1,8 +1,8 @@
-var locale = require("locale");
-var fontColor = g.theme.dark ? "#0f0" : "#000";
+const locale = require("locale");
 var heartRate = 0;
 var altitude = -9001;
 
+const fontColor = g.theme.dark ? "#0f0" : "#000";
 // handling the differents versions of the Banglejs smartwatch screen sizes
 if (process.env.HWVERSION == 1){
   var paddingY = 3;
@@ -18,32 +18,82 @@ if (process.env.HWVERSION == 1){
   var font6x8DefaultTextSize = 2;
 }
 
-function setFontSize(pos){
+// initialising the clockface
+const ClockFace = require("ClockFace");
+const clock = new ClockFace({
+  precision: 60,
+  settingsFile: "terminalclock.json",
+
+  init: function () {
+    // check settings and set default if needed
+    this.showHRM = false;
+    this.showAltitude = false;
+    this.lock_precision = this.precision;
+    this.unlock_precision = 1;
+    if (this.HRMinConfidence === undefined) this.HRMinConfidence = 50;
+    if (this.PowerOnInterval === undefined) this.PowerOnInterval = 15;
+    if (this.powerSaving===undefined) this.powerSaving = true;
+    ["L2", "L3", "L4", "L5", "L6", "L7", "L8", "L9"].forEach(k => {
+      if (this[k]===undefined){
+        if(k == "L2") this[k] = "Date";
+        else if(k == "L3") {
+          this[k] = "HR";
+          this.showHRM = true;
+        }else if(k == "L4") this[k] = "Motion";
+        else if(k == "L5") this[k] = "Steps";
+        else if(k == "L6") this[k] = ">";
+        else this[k] = "Empty"; 
+      } 
+      else if (this[k]==="HR") this.showHRM = true;
+      else if (this[k]==="Alt") this.showAltitude = true && process.env.HWVERSION == 2;
+    });
+
+    // set the lock and unlock actions
+    Bangle.on("lock", on => {
+      if (on) lock();
+      else unlock();
+    });
+
+    // set the services (HRM, pressure sensor, etc....)
+    if(!this.powerSaving){
+      turnOnServices();
+    } else{
+      setInterval(turnOnServices, this.PowerOnInterval*60000); // every PowerOnInterval min
+    }
+    // start the clock unlocked
+    unlock();
+  },
+
+  draw: function (date) {
+    var curPos = 1;
+    g.setFontAlign(-1, -1);
+    g.setColor(fontColor);
+    drawTime(date, curPos);
+    curPos++;
+
+    ["L2", "L3", "L4", "L5", "L6", "L7", "L8", "L9"].forEach(line => {
+      if (this[line]==='Date') drawDate(date, curPos);
+      else if (this[line]==='HR') drawHRM(curPos);
+      else if (this[line]==='Motion') drawMotion(curPos);
+      else if (this[line]==='Alt') drawAltitude(curPos);
+      else if (this[line]==='Steps') drawStepCount(curPos);
+      else if (this[line]==='>') drawInput(curPos);
+      curPos++;
+    });
+  },
+});
+
+
+/* ---------------------------- 
+Draw related of specific lines
+-------------------------------- */
+
+function drawLine(line, pos){
   if(pos == 1)
     g.setFont("6x8", font6x8FirstTextSize);
   else
     g.setFont("6x8", font6x8DefaultTextSize);
-}
 
-function clearField(pos){
-  var yStartPos = Bangle.appRect.y +
-      paddingY * (pos - 1) +
-      font6x8At4Size * Math.min(1, pos-1) +
-      font6x8At2Size * Math.max(0, pos-2);
-    var yEndPos = Bangle.appRect.y +
-      paddingY * (pos - 1) +
-      font6x8At4Size * Math.min(1, pos) +
-      font6x8At2Size * Math.max(0, pos-1);
-    g.clearRect(Bangle.appRect.x, yStartPos, Bangle.appRect.x2, yEndPos);
-}
-
-function clearWatchIfNeeded(now){
-  if(now.getMinutes() % 10 == 0)
-    g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2);
-}
-
-function drawLine(line, pos){
-  setFontSize(pos);
   var yPos = Bangle.appRect.y +
       paddingY * (pos - 1) +
       font6x8At4Size * Math.min(1, pos-1) +
@@ -66,7 +116,6 @@ function drawDate(now, pos){
 }
 
 function drawInput(pos){
-  clearField(pos);
   drawLine(">", pos);
 }
 
@@ -77,7 +126,6 @@ function drawStepCount(pos){
 }
 
 function drawHRM(pos){
-  clearField(pos);
   if(heartRate != 0)
     drawLine(">HR: " + parseInt(heartRate), pos);
   else
@@ -85,60 +133,30 @@ function drawHRM(pos){
 }
 
 function drawAltitude(pos){
-  clearField(pos);
   if(altitude > 0)
     drawLine(">Alt: " + altitude.toFixed(1) + "m", pos);
   else
     drawLine(">Alt: unknown", pos);
 }
-
-function drawActivity(pos){
-  clearField(pos);
+ 
+function drawMotion(pos){
   var health = Bangle.getHealthStatus('last');
   var steps_formated = ">Motion: " + parseInt(health.movement);
   drawLine(steps_formated, pos);
 }
 
-function draw(){
-  var curPos = 1;
-  g.reset();
-  g.setFontAlign(-1, -1);
-  g.setColor(fontColor);
-  var now = new Date();
-  clearWatchIfNeeded(now); // mostly to not have issues when changing days
-  drawTime(now, curPos);
-  curPos++;
-  if(settings.showDate){
-    drawDate(now, curPos);
-    curPos++;
-  }
-  if(settings.showAltitude){
-    drawAltitude(curPos);
-    curPos++;
-  }
-  if(settings.showHRM){
-    drawHRM(curPos);
-    curPos++;
-  }
-  if(settings.showActivity){
-    drawActivity(curPos);
-    curPos++;
-  }
-  if(settings.showStepCount){
-    drawStepCount(curPos);
-    curPos++;
-  }
-  drawInput(curPos);
-}
+/* -----------------------------------------------
+Services functions (HRM, pressure, etc...)
+-------------------------------------------------- */
 
 function turnOnServices(){
-  if(settings.showHRM){
+  if(clock.showHRM){
     Bangle.setHRMPower(true, "terminalclock");
   }
-  if(settings.showAltitude && process.env.HWVERSION != 1){
+  if(clock.showAltitude){
     Bangle.setBarometerPower(true, "terminalclock");
   }
-  if(settings.powerSaving){
+  if(clock.powerSaving){
     setTimeout(function () {
       turnOffServices();
     }, 45000);
@@ -146,33 +164,20 @@ function turnOnServices(){
 }
 
 function turnOffServices(){
-  if(settings.showHRM){
+  if(clock.showHRM){
     Bangle.setHRMPower(false, "terminalclock");
   }
-  if(settings.showAltitude && process.env.HWVERSION != 1){
+  if(clock.showAltitude){
     Bangle.setBarometerPower(false, "terminalclock");
   }
 }
 
-var unlockDrawIntervalID = -1;
-Bangle.on('lock', function(on){
-  if(!on){ // unclock
-    if(settings.powerSaving){
-      turnOnServices();
-    }
-    unlockDrawIntervalID = setInterval(draw, 1000); // every second
-  }
-  if(on && unlockDrawIntervalID != -1){ // lock
-    clearInterval(unlockDrawIntervalID);
-  }
-});
-
 Bangle.on('HRM',function(hrmInfo) {
-  if(hrmInfo.confidence >= settings.HRMinConfidence)
+  if(hrmInfo.confidence >= clock.HRMinConfidence)
     heartRate = hrmInfo.bpm;
 });
 
-var MEDIANLENGTH = 20; // technical
+const MEDIANLENGTH = 20; // technical
 var avr = [], median; // technical
 Bangle.on('pressure', function(e) {
   while (avr.length>MEDIANLENGTH) avr.pop();
@@ -184,32 +189,22 @@ Bangle.on('pressure', function(e) {
   }
 });
 
+/* -------------------------------------------------
+Clock related functions but not in the ClockFace module
+---------------------------------------------------- */
 
-// Clear the screen once, at startup
-g.clear();
-// load the settings
-var settings = Object.assign({
-  // default values
-  HRMinConfidence: 50,
-  showDate: true,
-  showHRM: true,
-  showActivity: true,
-  showStepCount: true,
-  showAltitude: process.env.HWVERSION != 1 ? true : false,
-  powerSaving: true,
-  PowerOnInterval: 15,
-}, require('Storage').readJSON("terminalclock.json", true) || {});
-
-// turn the services before drawing anything
-turnOnServices();
-if(settings.powerSaving){
-  setInterval(turnOnServices, settings.PowerOnInterval*60000); // every PowerOnInterval min
+function unlock(){
+  if(clock.powerSaving){
+    turnOnServices();
+  }
+  clock.precision = clock.unlock_precision;
+  clock.tick();
 }
-// Show launcher when middle button pressed
-Bangle.setUI("clock");
-// Load and draw widgets
-Bangle.loadWidgets();
-Bangle.drawWidgets();
-// draw immediately at first
-draw();
-setInterval(draw, 10000); // every 10 seconds
+
+function lock(){
+  clock.precision = clock.lock_precision;
+  clock.tick();
+}
+
+// starting the clock
+clock.start();
diff --git a/apps/terminalclock/metadata.json b/apps/terminalclock/metadata.json
index 9f76ed8f2..a8682f9a8 100644
--- a/apps/terminalclock/metadata.json
+++ b/apps/terminalclock/metadata.json
@@ -3,7 +3,7 @@
   "name": "Terminal Clock",
   "shortName":"Terminal Clock",
   "description": "A terminal cli like clock displaying multiple sensor data",
-  "version":"0.06",
+  "version":"0.07",
   "icon": "app.png",
   "type": "clock",
   "tags": "clock",
diff --git a/apps/terminalclock/settings.js b/apps/terminalclock/settings.js
index bd860b491..f347e8ee3 100644
--- a/apps/terminalclock/settings.js
+++ b/apps/terminalclock/settings.js
@@ -2,94 +2,100 @@
   var FILE = "terminalclock.json";
   // Load settings
   var settings = Object.assign({
+    // ClockFace lib
+    loadWidgets: true,
+    // TerminalClock specific
     HRMinConfidence: 50,
-    showDate: true,
-    showAltitude: process.env.HWVERSION != 1 ? true : false,
-    showHRM: true,
-    showActivity: true,
-    showStepCount: true,
     powerSaving: true,
     PowerOnInterval: 15,
+    L2: 'Date',
+    L3: 'HR',
+    L4: 'Motion',
+    L5: 'Steps',
+    L6: '>',
+    L7: 'Empty',
+    L8: 'Empty',
+    L9: 'Empty',
   }, require('Storage').readJSON(FILE, true) || {});
 
   function writeSettings() {
     require('Storage').writeJSON(FILE, settings);
   }
 
-  // Show the menu
-  var menu = {
-    "" : { "title" : "Terminal Clock" },
-    "< Back" : () => back(),
-    'HR confidence': {
-      value: settings.HRMinConfidence,
-      min: 0, max: 100,
+  if(process.env.HWVERSION == 2) {
+    var lineType = ['Date', 'HR', 'Motion', 'Alt', 'Steps', '>', 'Empty'];
+  } else{
+    var lineType = ['Date', 'HR', 'Motion', 'Steps', '>', 'Empty'];
+  }
+  function getLineChooser(lineID){
+    return {
+      value: lineType.indexOf(settings[lineID]),
+      min: 0, max: lineType.length-1,
+      format: v => lineType[v],
       onchange: v => {
-        settings.HRMinConfidence = v;
-        writeSettings();
-      }
-   },
-   'Show date': {
-      value: settings.showDate,
-      format: v => v?"Yes":"No",
-      onchange: v => {
-        settings.showDate = v;
-        writeSettings();
-      }
-    },
-    'Show Altitude': {
-      value: settings.showAltitude,
-      format: v => v?"Yes":"No",
-      onchange: v => {
-        settings.showAltitude = v;
-        writeSettings();
-      }
-    },
-    'Show HRM': {
-      value: settings.showHRM,
-      format: v => v?"Yes":"No",
-      onchange: v => {
-        settings.showHRM = v;
-        writeSettings();
-      }
-    },
-    'Show Activity': {
-      value: settings.showActivity,
-      format: v => v?"Yes":"No",
-      onchange: v => {
-        settings.showActivity = v;
-        writeSettings();
-      }
-    },
-    'Show Steps': {
-      value: settings.showStepCount,
-      format: v => v?"Yes":"No",
-      onchange: v => {
-        settings.showStepCount = v;
-        writeSettings();
-      }
-    },
-    'Power saving': {
-      value: settings.powerSaving,
-      format: v => v?"On":"Off",
-      onchange: v => {
-        settings.powerSaving = v;
-        writeSettings();
-      }
-    },
-    'Power on interval': {
-      value: settings.PowerOnInterval,
-      min: 3, max: 60,
-      onchange: v => {
-        settings.PowerOnInterval = v;
+        settings[lineID] = lineType[v];
         writeSettings();
       },
-      format: x => {
-          return x + " min";
+    };
+  }
+
+  var lineMenu = {
+    '< Back': function() { E.showMenu(getMainMenu());},
+    'Line 2': getLineChooser('L2'),
+    'Line 3': getLineChooser('L3'),
+    'Line 4': getLineChooser('L4'),
+    'Line 5': getLineChooser('L5'),
+    'Line 6': getLineChooser('L6'),
+    'Line 7': getLineChooser('L7'),
+    'Line 8': getLineChooser('L8'),
+    'Line 9': getLineChooser('L9'),
+  };
+
+  function getMainMenu(){
+    var mainMenu = {
+      "" : { "title" : "Terminal Clock" },
+      "< Back" : () => back(),
+      'HR confidence': {
+        value: settings.HRMinConfidence,
+        min: 0, max: 100,
+        onchange: v => {
+          settings.HRMinConfidence = v;
+          writeSettings();
+        }
+     },
+     'Show widgets': {
+        value: settings.loadWidgets,
+        onchange: v => {
+          settings.loadWidgets = v;
+          writeSettings();
+        }
+      },
+      'Power saving': {
+        value: settings.powerSaving,
+        onchange: v => {
+          settings.powerSaving = v;
+          writeSettings();
+          setTimeout(function() {
+            E.showMenu(getMainMenu());
+          },0);
+        }
       }
+    };
+    if(settings.powerSaving){
+      mainMenu['Power on interval'] = {
+        value: settings.PowerOnInterval,
+        min: 3, max: 60,
+        onchange: v => {
+          settings.PowerOnInterval = v;
+          writeSettings();
+        },
+        format: x => x + "m"
+      };
     }
+
+    mainMenu['Lines'] = function() { E.showMenu(lineMenu);};
+    return mainMenu;
   }
-  if (process.env.HWVERSION == 1) {
-    delete menu['Show Altitude']
-  }
-  E.showMenu(menu);
+
+  E.showMenu(getMainMenu());
 })
\ No newline at end of file
diff --git a/apps/themesetter/ChangeLog b/apps/themesetter/ChangeLog
new file mode 100644
index 000000000..ddbd06706
--- /dev/null
+++ b/apps/themesetter/ChangeLog
@@ -0,0 +1,2 @@
+...
+0.04: First update with ChangeLog Added
diff --git a/apps/thermom/ChangeLog b/apps/thermom/ChangeLog
index 6d3a966e3..0b8c325e5 100644
--- a/apps/thermom/ChangeLog
+++ b/apps/thermom/ChangeLog
@@ -4,3 +4,5 @@
 0.05: Use temperature from current locale
       Update every 10s, average last 5 readings
       Changes based on #1092
+0.06: Minor tweaks for stability. Update every 5 seconds
+0.07: Add back button
diff --git a/apps/thermom/app.js b/apps/thermom/app.js
index 0e45ed3e7..3aa99c015 100644
--- a/apps/thermom/app.js
+++ b/apps/thermom/app.js
@@ -24,7 +24,7 @@ function onTemperature(p) {
 // Gets the temperature in the most accurate way (pressure sensor or inbuilt thermistor)
 function drawTemperature() {
   if (Bangle.getPressure) {
-    Bangle.getPressure().then(onTemperature);
+    Bangle.getPressure().then(p =>{if (p) onTemperature(p);});
   } else {
     onTemperature({
       temperature : E.getTemperature()
@@ -34,8 +34,11 @@ function drawTemperature() {
 
 setInterval(function() {
   drawTemperature();
-}, 10000);
+}, 5000);
+Bangle.loadWidgets();
+Bangle.setUI({
+  mode : "custom",
+  back : function() {load();}
+});
 E.showMessage("Reading temperature...");
 drawTemperature();
-Bangle.loadWidgets();
-Bangle.drawWidgets();
diff --git a/apps/thermom/metadata.json b/apps/thermom/metadata.json
index 381f85e17..a215df624 100644
--- a/apps/thermom/metadata.json
+++ b/apps/thermom/metadata.json
@@ -1,7 +1,7 @@
 {
   "id": "thermom",
   "name": "Thermometer",
-  "version": "0.05",
+  "version": "0.07",
   "description": "Displays the current temperature in degree Celsius/Fahrenheit (depending on locale), updates every 10 seconds with average of last 5 readings.",
   "icon": "app.png",
   "tags": "tool",
diff --git a/apps/timerclk/ChangeLog b/apps/timerclk/ChangeLog
index e17baa27c..7a357b1aa 100644
--- a/apps/timerclk/ChangeLog
+++ b/apps/timerclk/ChangeLog
@@ -1,2 +1,3 @@
 0.01: New App!
 0.02: Add sunrise/sunset. Fix timer bugs.
+0.03: Use default Bangle formatter for booleans
diff --git a/apps/timerclk/metadata.json b/apps/timerclk/metadata.json
index 7c6c7c9b3..72f42d8d4 100644
--- a/apps/timerclk/metadata.json
+++ b/apps/timerclk/metadata.json
@@ -2,7 +2,7 @@
 	"id": "timerclk",
 	"name": "Timer Clock",
 	"shortName":"Timer Clock",
-	"version":"0.02",
+	"version":"0.03",
 	"description": "A clock with stopwatches, timers and alarms build in.",
 	"icon": "app-icon.png",
 	"type": "clock",
diff --git a/apps/timerclk/settings.js b/apps/timerclk/settings.js
index 992985f52..1a8500add 100644
--- a/apps/timerclk/settings.js
+++ b/apps/timerclk/settings.js
@@ -1,6 +1,5 @@
 (function(back) {
   const FILE = "timerclk.json";
-  const BOOL_FORMAT = v=>v?/*LANG*/"On":/*LANG*/"Off";
   // Load settings
   var settings = require('Storage').readJSON(FILE, true) || {}
   settings.clock = Object.assign({
@@ -130,7 +129,6 @@
     },
     "short date": {
       value: !!settings.clock.shortDate,
-      format: BOOL_FORMAT,
       onchange: v => {
         settings.clock.shortDate = v;
         writeSettings();
@@ -138,7 +136,6 @@
     },
     "stopwatches": {
       value: !!settings.clock.showStopwatches,
-      format: v=>v?/*LANG*/"Show":/*LANG*/"Hide",
       onchange: v => {
         settings.clock.showStopwatches = v;
         writeSettings();
@@ -146,7 +143,6 @@
     },
     "timers": {
       value: !!settings.clock.showTimers,
-      format: v=>v?/*LANG*/"Show":/*LANG*/"Hide",
       onchange: v => {
         settings.clock.showTimers = v;
         writeSettings();
@@ -154,7 +150,6 @@
     },
     "sun times": {
       value: !!settings.clock.showSrss,
-      format: v=>v?/*LANG*/"Show":/*LANG*/"Hide",
       onchange: v => {
         settings.clock.showSrss = v;
         writeSettings();
diff --git a/apps/tinyVario/ChangeLog b/apps/tinyVario/ChangeLog
new file mode 100644
index 000000000..a201ee465
--- /dev/null
+++ b/apps/tinyVario/ChangeLog
@@ -0,0 +1,5 @@
+0.01: Initial Version
+0.02: Touch data fields to configure them.
+0.03: Changed menu layout, fixed automatic flight time detection.
+0.04: flight time detection should work without GPS now. New vario display available.
+0.05: some bugs fiexed, no new features.
diff --git a/apps/tinyVario/README.md b/apps/tinyVario/README.md
new file mode 100644
index 000000000..c375585c2
--- /dev/null
+++ b/apps/tinyVario/README.md
@@ -0,0 +1,20 @@
+# Turn your Bangle.js2 into a flight computer!
+
+All settings can be accessed by touching the corresponding data fields. I made all interface objects as big as possible to make it easier to use with gloves. 
+
+## This is a work in progress. Working features so far:
+- Altimeter
+- Variometer
+- Average rate of climb
+- Ground speed
+- Flying time with automatic take-off detection
+
+
+## Planned features:
+- glide slope display
+- final glide computer
+- waypoint navigation
+- flight log (possibly IGC file export)
+
+Contact me for feedback and suggestions!
+tinyVario@dumke.org
diff --git a/apps/tinyVario/app-icon.js b/apps/tinyVario/app-icon.js
new file mode 100644
index 000000000..c94da4e62
--- /dev/null
+++ b/apps/tinyVario/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEw4P/AAMgFksD4/AAoUGnguDgkEAosQAocAAosYAoX8gEIAgMBwEAh4FCAQMJBAIFCgAFUO4Rraj4hBTwZwDgFgsAFDoAXEoYFEqUAg4fCj0AngfChgfEAoPAYomDD4kiAok2AYU8gEUAoXmgEcR4WBAogaEgKtBAAdMAolIAokoAojJDAAM4AokwRxIA=="))
diff --git a/apps/tinyVario/app.js b/apps/tinyVario/app.js
new file mode 100644
index 000000000..b9a87c821
--- /dev/null
+++ b/apps/tinyVario/app.js
@@ -0,0 +1,465 @@
+/*
+To do:
+  -flight log
+  -statistics page
+  -navigation
+*/
+
+getAltitude = (p,baseP) => (44330 * (1.0 - Math.pow(p/baseP, 0.1903)));
+getFL = () => (44330 * (1.0 - Math.pow(pressure/1013.25, 0.1903))).toFixed(0);
+getTimeString = () => (settings.localTime) ? (require("locale").time(Date(),1)):(Date().toUTCString().slice(Date().toUTCString().length-12,Date().toUTCString().length-7));
+
+var fg=g.getColor();
+var bg=g.getBgColor();
+var red="#F00",green="#0F0";
+
+const unitsRoc=[
+  {name:"m/s", factor:1, precision:1, layoutCode:{type:"v", halign:1, c: [
+            {type:"txt", font:"12%", halign:0, filly:0, label:"m"},
+            {type:"", height:1,width:"20", bgCol:fg},
+            {type:"txt", font:"12%", halign:0, filly:0, label:"s"}]}},
+  {name:"ft/m", factor:196.85039370078738, precision:0, layoutCode:{type:"v", halign:1, c: [
+            {type:"txt", font:"12%", halign:0, filly:0, label:"ft"},
+            {type:"", height:1,width:"30", bgCol:fg},
+            {type:"txt", font:"12%", halign:0, filly:0, label:"min"}]}},
+  {name:"kt", factor:1.9438444924406, precision:1, layoutCode:
+            {type:"txt", font:"12%", halign:0, filly:0, label:"kt"}}
+  ];
+const unitsGs=[
+  {name:"km/h", factor:1, precision:1, layoutCode:{type:"v", halign:1, c: [
+            {type:"txt", font:"12%", halign:0, filly:0, label:"km"},
+            {type:"", height:1,width:"30", bgCol:fg},
+            {type:"txt", font:"12%", halign:0, filly:0, label:"h"}]}},
+  {name:"kt", factor:0.5399568, precision:0, layoutCode:{type:"txt", font:"12%", halign:0, filly:0, label:"kt"}},
+  {name:"m/s", factor:0.2777777777777778, precision:1, layoutCode:{type:"v", halign:1, c: [
+            {type:"txt", font:"12%", halign:0, filly:0, label:"m"},
+            {type:"", height:1,width:"20", bgCol:fg},
+            {type:"txt", font:"12%", halign:0, filly:0, label:"s"}]}}
+  ];
+const unitsAlt=[
+  {name:"m", factor:1, precision:0, layoutCode:{type:"txt", font:"12%", halign:0, filly:0, label:"m"}},
+  {name:"ft", factor:3.280839895013123, precision:0, layoutCode:{type:"txt", font:"12%", halign:0, filly:0, label:"ft"}}
+  ];
+const unitROC={type:"v", halign:1, c: [
+            {type:"txt", font:"12%", halign:0, filly:0, label:"m"},
+            {type:"", height:1,width:"20", bgCol:fg},
+            {type:"txt", font:"12%", halign:0, filly:0, label:"s"}
+          ]};
+
+const ground=0, flying=1, landed=2, maybeFlying=3, maybeLanded=4;
+
+var settings = Object.assign({
+  rocU: 0,
+  altU: 0,
+  gsU:0,
+  intTime:10,
+  localTime:true,
+  autoDetect:true,
+  bargraph:false
+}, require('Storage').readJSON("tinyVario.json", true) || {});
+
+var qnh=Math.floor(Bangle.getOptions().seaLevelPressure) || 1013;
+var pfdHandle;
+var rawP=0, samples=0;
+var altH = [];
+var altRaw=-9999, altFast=0, altSlow=0;
+var fastGain=0.5, slowGain=0.3;
+var roc=0,rocAvg=0, gs;
+var lastPressure = Date.now();
+var pressure = 1000;
+var state=ground;
+var takeoffTime=0, landingTime=0, flyingTime;
+var Layout = require("Layout");
+var oldSettings;
+
+//var delta=0;//TESTING
+
+
+function updateText(t) {
+  g.reset();
+  g.clearRect(t.x,t.y,t.x+t.w-1,t.y+t.h-1);
+  if (t.col) g.setColor(t.col);
+  else g.setColor(fg);
+  if (t.halign==1)
+    g.setFont(t.font).setFontAlign(1,0,0).drawString(t.label, t.x+t.w, t.y+(t.h>>1));
+  else if (t.halign==-1)
+    g.setFont(t.font).setFontAlign(-1,0,0).drawString(t.label, t.x, t.y+(t.h>>1));
+  else
+    g.setFont(t.font).setFontAlign(0,0,0).drawString(t.label, t.x+(t.w>>1), t.y+(t.h>>1));
+}
+
+function initPFD() {
+  Bangle.setUI();
+  var pfd = new Layout(
+    {type:"v",c: [
+      /*{type:"h",c: [
+        {type:"", fillx:1, height:"1"}
+        ]},*/
+      {type:"h",filly:1, c: [
+        {type:"custom", width:"25", render:()=>{
+          var p = pfd.vario;
+          if (roc>0.1) g.setColor(0,1,0);
+          if (roc<-1) g.setColor(1,0,0);
+          var y=p.y+p.h/2-roc*(p.h/2)/5;
+          if (settings.bargraph==false) {
+            g.clearRect(p.x,p.y,p.x+p.w-1,p.y+p.h-1);
+            g.fillRect(p.x,p.y+(p.h/2),p.x+p.w-1,Math.clip(y,p.y,p.y+p.h-1));
+          } else {
+            g.setClipRect(p.x,p.y,p.x+p.w-1,p.y+p.h-1);
+            g.scroll(-1,0);
+            g.drawLine(p.x+p.w-1,p.y+(p.h/2),p.x+p.w-1,Math.clip(y,p.y,p.y+p.h-1));
+          }
+          g.reset();
+        }, id:"vario",filly:1, cb:()=>initVarioMenu()},
+        {type:"", filly:1, width:1, bgCol:fg},
+        {type:"v",fillx:1, c: [
+          {type:"h", halign:1, c:[
+            {type:"txt", font:"22%", halign:1, filly:1, fillx:1, label:"9999", id:"alt", cb:()=>initAltMenu()},
+            unitsAlt[settings.altU].layoutCode
+          ]},
+          {type:"", fillx:1, height:"1", bgCol:fg},
+          {type:"h", halign:1, c:[
+            {type:"txt", font:"25%", halign:1, filly:1, fillx:1, label:"-9.9", id:"avg", cb:()=>initAvgMenu()},
+            unitsRoc[settings.rocU].layoutCode
+          ]},
+          {type:"", fillx:1, height:"1", bgCol:fg},
+          {type:"h", halign:1, c:[
+            {type:"txt", font:"25%", halign:1, filly:1, fillx:1, label:"XXX", id:"gs", cb:()=>initGsMenu()},
+            unitsGs[settings.gsU].layoutCode
+          ]}
+        ]}
+      ]},
+      {type:"", fillx:1, height:"1", bgCol:fg},
+      {type:"h",c: [
+        {type:"txt",pad:0, halign:0, font:"15%",fillx:1, label:"99:99", id:"time", cb:()=>initTimeMenu()},
+        {type:"", width:1,height:g.getHeight()*0.15, bgCol:fg},
+        {type:"txt",pad:0, halign:0, font:"15%", fillx:1, label:"--:--", id:"flyingtime", cb:()=>initFlyingTimeMenu() }
+      ]}
+    ]},{lazy:true}
+  );
+  g.clear();
+  pfd.render();
+  //-------testing------
+  //rawP=1000;
+  //samples=1;
+  //--------------------
+  pfdHandle = setInterval(function() {
+    t1=Date().getTime();
+    //process pressure readings
+    if (samples) {
+      pressure=rawP/samples;
+      samples=0;
+      rawP=0;
+      if (altRaw==-9999) {//first measurement)
+        altRaw=getAltitude(pressure,qnh);
+        altFast=altRaw;
+        altSlow=altRaw;
+        for (let i = 0; i < settings.intTime*4+1; i++) altH.push(altRaw);
+      }
+    }
+    //altRaw=altRaw+delta;getAltitude(pressure,qnh);//TESTING
+    altRaw=getAltitude(pressure,qnh);
+    altFast=altFast+(altRaw-altFast)*fastGain;
+    altSlow=altSlow+(altRaw-altSlow)*slowGain;
+    altH.push(altFast);
+    while (altH.length>settings.intTime*4) {
+      rocAvg=(altH[altH.length-1]-altH[0])/settings.intTime;
+      altH.shift();
+    }
+    roc=(altFast-altSlow)/((0.25/slowGain)-(0.25/fastGain));
+
+    if (settings.autoDetect==true) switch (state) {
+      case ground:
+        if ((gs>=5) || (roc>=1) || (roc<=-1)) {
+          state=maybeFlying;
+          takeoffTime=Date().getTime();
+        }
+        break;
+      case maybeFlying:
+        if (!(gs>=5) && (roc<1) && (roc>-1)) state=ground;
+        else if (Date().getTime()-takeoffTime>60000) state=flying;
+        break;
+      case flying:
+        if (!(gs>=5) && (roc<1) && (roc>-1)) {
+          state=maybeLanded;
+          landingTime=Date().getTime();
+        }
+        break;
+      case maybeLanded:
+        if ((gs>=5) || (roc>=1) || (roc<=-1)) state=flying;
+        else if (Date().getTime()-landingTime>60000) state=landed;
+        break;
+    }
+    if ((state==flying) || (state==maybeLanded)) {
+      flyingTime=Date().getTime()-takeoffTime;
+      pfd.flyingtime.label=(flyingTime / 3600000).toFixed(0)+":"+(flyingTime / 60000 % 60).toFixed(0).padStart(2,'0');
+      pfd.flyingtime.col=fg;
+    } else if (state==landed) {
+      flyingTime=landingTime-takeoffTime;
+      pfd.flyingtime.label=(flyingTime / 3600000).toFixed(0)+":"+(flyingTime / 60000 % 60).toFixed(0).padStart(2,'0');
+      pfd.flyingtime.col=green;
+    }
+
+    pfd.alt.label=(altRaw*unitsAlt[settings.altU].factor).toFixed(unitsAlt[settings.altU].precision);
+    pfd.avg.col=(rocAvg<-1) ? (red):((rocAvg>0.1) ? (green):(fg));
+    pfd.avg.label=(rocAvg*unitsRoc[settings.rocU].factor).toFixed(unitsRoc[settings.rocU].precision);
+
+    var gps = Bangle.getGPSFix();
+    if (gps!=undefined) {
+      pfd.gs.label=(gps.speed*unitsGs[settings.gsU].factor).toFixed(unitsGs[settings.gsU].precision);
+      updateText(pfd.gs);
+      gs=gps.speed;
+    } //else gs=0;
+    
+    pfd.time.label=getTimeString();
+    updateText(pfd.alt);
+    updateText(pfd.avg);
+    updateText(pfd.time);
+    updateText(pfd.flyingtime);
+
+    pfd.vario.render();
+    //print(Date().getTime()-t1);
+  }, 250);
+
+}
+
+function initAltMenu() {
+  var oldQnh=qnh;
+  function updateAltMenu() {
+    altMenu.clear();
+    altMenu.alt.label=
+      (getAltitude(pressure,qnh)*unitsAlt[settings.altU].factor).toFixed(unitsAlt[settings.altU].precision)
+      +unitsAlt[settings.altU].name;
+    altMenu.qnh.label=qnh;
+    altMenu.render();
+  }
+  oldSettings=Object.assign({},settings);
+  clearInterval(pfdHandle);
+  var altMenu = new Layout ({
+    type:"v", c: [{
+      type:"v", width:180, c: [
+        {type:"h", c: [
+          {type:"btn", font:"15%", pad:1, fillx:1, filly:1, label:"-", cb:l=>{qnh--; updateAltMenu();}},
+          {type:"btn", font:"15%", pad:1, fillx:1, filly:1, label:"+", cb:l=>{qnh++; updateAltMenu();}}
+        ]},
+        {type:"v", c: [
+          {type:"h", c: [
+            {type:"txt", font:"13%", fillx:1, filly:1, label:"QNH: "},
+            {type:"txt", font:"13%", fillx:1, filly:1, id:"qnh", label:"    "},
+          ]},
+          {type:"h", c: [
+            {type:"txt", font:"13%", fillx:1, filly:1, label:"Alt: "},
+            {type:"txt", font:"13%", fillx:1, filly:1, id:"alt", label: "      "},
+          ]}
+        ]},
+        {type:"btn", font:"12%", id:"units", pad:2, fillx:1, filly:1, label:"Units: "+unitsAlt[settings.altU].name, cb:()=>{
+          settings.altU=(settings.altU+1)%unitsAlt.length;
+          altMenu.units.label="Units: "+unitsAlt[settings.altU].name;
+          altMenu.render();
+        }},
+      ]},
+      {type:"h", c: [
+        {type:"btn", font:"16%", pad:1, fillx:1, label:"BACK", cb: ()=>{
+          settings=Object.assign({},oldSettings);
+          print("old settings restored");
+          initPFD();
+        }},
+        {type:"btn", font:"16%", pad:1, fillx:1, label:"SAVE", cb: ()=>{
+          require('Storage').writeJSON("tinyVario.json", settings);
+          Bangle.setOptions({seaLevelPressure:qnh});
+          initPFD();
+        }}
+      ]}
+    ], lazy:true});
+  g.clear();
+  altMenu.render();
+  updateAltMenu();
+}
+
+function initAvgMenu() {
+  oldSettings=Object.assign({},settings);
+  clearInterval(pfdHandle);
+  var avgMenu = new Layout ({
+    type:"v", c: [{
+      type:"v", width:180, c: [
+        {type:"h", c: [
+          {type:"btn", font:"12%", pad:2, fillx:1, filly:1, label:"-", cb:l=>{
+            settings.intTime=Math.clip(settings.intTime-1,1,60);
+            avgMenu.clear();
+            avgMenu.interval.label="Interval: "+settings.intTime+"s";
+            avgMenu.render();
+          }},
+          {type:"btn", font:"12%", pad:1, fillx:1, filly:1, label:"+", cb:l=>{
+            settings.intTime=Math.clip(settings.intTime+1,1,60);
+            avgMenu.clear();
+            avgMenu.interval.label="Interval: "+settings.intTime+"s";
+            avgMenu.render();
+          }}
+        ]},
+        {type:"txt", id:"interval", font:"10%", pad:1, fillx:1, filly:1, label:"Interval: "+settings.intTime+"s"},
+        {type:"btn", font:"12%", id:"units", pad:1, fillx:1, filly:1, label:"Units: "+unitsRoc[settings.rocU].name, cb:()=>{
+          settings.rocU=(settings.rocU+1)%unitsRoc.length;
+          avgMenu.units.label="Units: "+unitsRoc[settings.rocU].name;
+          avgMenu.render();
+        }},
+      ]},
+      {type:"h", c: [
+        {type:"btn", font:"16%", pad:1, fillx:1, label:"BACK", cb: ()=>{
+          settings=Object.assign({},oldSettings);
+          initPFD();
+        }},
+        {type:"btn", font:"16%", pad:1, fillx:1, label:"SAVE", cb: ()=>{
+          require('Storage').writeJSON("tinyVario.json", settings);
+          initPFD();
+        }}
+      ]}
+    ], lazy:true});
+  g.clear();
+  avgMenu.render();
+}
+
+function initGsMenu() {
+  oldSettings=Object.assign({},settings);
+  clearInterval(pfdHandle);
+  var gsMenu = new Layout ({
+    type:"v", c: [
+      {type:"btn", font:"20%", id:"units", pad:1, fillx:1, filly:1, label:"Units:\n"+unitsGs[settings.gsU].name, cb:()=>{
+        settings.gsU=(settings.gsU+1)%unitsGs.length;
+        gsMenu.units.label="Units:\n"+unitsGs[settings.gsU].name;
+        gsMenu.render();
+      }},
+      {type:"h", c: [
+        {type:"btn", font:"16%", pad:1, fillx:1, label:"BACK", cb: ()=>{
+          settings=Object.assign({},oldSettings);
+          print("old settings restored");
+          initPFD();
+        }},
+        {type:"btn", font:"16%", pad:1, fillx:1, label:"SAVE", cb: ()=>{
+          require('Storage').writeJSON("tinyVario.json", settings);
+          initPFD();
+        }}
+      ]}
+    ], lazy:true});
+  g.clear();
+  gsMenu.render();
+}
+
+function initTimeMenu() {
+  oldSettings=Object.assign({},settings);
+  clearInterval(pfdHandle);
+  var timeMenu = new Layout ({
+    type:"v", c: [
+      {type:"btn", font:"20%", id:"format", pad:1, fillx:1, filly:1, label:"Time:\n"+((settings.localTime==true) ? ("LCL") : ("UTC")), cb:()=>{
+        settings.localTime=!settings.localTime;
+        timeMenu.format.label="Time:\n"+((settings.localTime==true) ? ("LCL") : ("UTC"));
+        timeMenu.render();
+      }},
+      {type:"h", c: [
+        {type:"btn", font:"16%", pad:1, fillx:1, label:"BACK", cb: ()=>{
+          settings=Object.assign({},oldSettings);
+          initPFD();
+        }},
+        {type:"btn", font:"16%", pad:1, fillx:1, label:"SAVE", cb: ()=>{
+          require('Storage').writeJSON("tinyVario.json", settings);
+          initPFD();
+        }}
+      ]}
+    ], lazy:true});
+  g.clear();
+  timeMenu.render();
+}
+
+function initVarioMenu() {
+  oldSettings=Object.assign({},settings);
+  clearInterval(pfdHandle);
+  var varioMenu = new Layout ({
+    type:"v", c: [
+      {type:"btn", font:"20%", id:"format", pad:1, fillx:1, filly:1, label:"Display:\n"+((settings.bargraph==true) ? ("graph") : ("simple")), cb:()=>{
+        settings.bargraph=!settings.bargraph;
+        varioMenu.format.label="Display:\n"+((settings.bargraph==true) ? ("graph") : ("simple"));
+        varioMenu.render();
+      }},
+      {type:"h", c: [
+        {type:"btn", font:"16%", pad:1, fillx:1, label:"BACK", cb: ()=>{
+          settings=Object.assign({},oldSettings);
+          initPFD();
+        }},
+        {type:"btn", font:"16%", pad:1, fillx:1, label:"SAVE", cb: ()=>{
+          require('Storage').writeJSON("tinyVario.json", settings);
+          initPFD();
+        }}
+      ]}
+    ], lazy:true});
+  g.clear();
+  varioMenu.render();
+}
+function initFlyingTimeMenu() {
+  oldSettings=Object.assign({},settings);
+  clearInterval(pfdHandle);
+  var ftMenu = new Layout (
+    {type:"v", c: [
+      {type:"v", c: [
+        {type:"h", c: [
+          {type:"btn", font:"12%", pad:1, fillx:1, filly:1, label:"Toggle\nAuto", cb:()=>{
+            settings.autoDetect=!settings.autoDetect;
+            ftMenu.manual.label= (settings.autoDetect==true)? 
+              ("AUTO"):((state==flying) ? ("LAND") : ("TAKE\nOFF"));
+            ftMenu.render();
+          }},
+          {type:"btn", font:"12%", id:"manual", pad:1, fillx:1, filly:1, label:(settings.autoDetect==true)? 
+            ("AUTO"):((state==flying) ? ("LAND") : ("TAKE\nOFF")), cb:()=>{
+              if (settings.autoDetect==false) {
+                if (state!=flying) {
+                  E.showPrompt("Take off now?").then((v)=> {
+                    if (v) {
+                      state=flying;
+                      takeoffTime=Date().getTime();
+                      initPFD();
+                    }
+                  });
+                } else {
+                  E.showPrompt("Land now?").then((v)=> {
+                    if (v) {
+                      state=landed;
+                      landingTime=Date().getTime();
+                      initPFD();
+                    }
+                  });
+                }
+              }
+            }
+          }
+        ]},
+        {type:"btn", font:"12%", pad:1, fillx:1, filly:1, label:"Reset", cb:()=>{
+          E.showPrompt("Reset Flight?").then((v)=> {
+            state=ground;
+            initPFD();
+          });
+        }}
+      ]},
+      {type:"h", c: [
+        {type:"btn", font:"16%", pad:1, fillx:1, label:"BACK", cb: ()=>{
+          settings=Object.assign({},oldSettings);
+          initPFD();
+        }},
+        {type:"btn", font:"16%", pad:1, fillx:1, label:"SAVE", cb: ()=>{
+          require('Storage').writeJSON("tinyVario.json", settings);
+          initPFD();
+        }}
+      ]}
+    ], lazy:true});
+  g.clear();
+  ftMenu.render();
+}
+
+Bangle.setGPSPower(true, "tinyVario");
+Bangle.setBarometerPower(true, "tinyVario");
+
+Bangle.on('pressure', function(e) {
+  if (samples<10) { //no need to gather more samples when stuck in a menu
+    rawP+=e.pressure;
+    samples++;
+  }
+});
+
+initPFD();
diff --git a/apps/tinyVario/app.png b/apps/tinyVario/app.png
new file mode 100644
index 000000000..1f6c09bc4
Binary files /dev/null and b/apps/tinyVario/app.png differ
diff --git a/apps/tinyVario/metadata.json b/apps/tinyVario/metadata.json
new file mode 100644
index 000000000..f038e7515
--- /dev/null
+++ b/apps/tinyVario/metadata.json
@@ -0,0 +1,15 @@
+{ "id": "tinyVario",
+  "name": "Tiny Vario",
+  "shortName" : "tinyVario",
+  "version":"0.05",
+  "icon": "app.png",
+  "readme": "README.md",
+  "description": "A very simple app for gliding / paragliding / hang gliding etc.",
+  "tags": "outdoors",
+  "supports" : ["BANGLEJS2"],
+  "storage": [
+    {"name":"tinyVario.app.js","url":"app.js"},
+    {"name":"tinyVario.img","url":"app-icon.js","evaluate":true}
+    ],
+  "data": [{"name":"tinyVario.json"}]
+}
diff --git a/apps/toucher/ChangeLog b/apps/toucher/ChangeLog
index 7b5c53de7..e15ffa29b 100644
--- a/apps/toucher/ChangeLog
+++ b/apps/toucher/ChangeLog
@@ -5,3 +5,4 @@
 0.05: Improve perf
 0.06: Complete rewrite in 80x80, better perf, add settings
 0.07: Added suppport for Bangle 2, added README file
+0.08: Use default Bangle formatter for booleans
diff --git a/apps/toucher/metadata.json b/apps/toucher/metadata.json
index 8b2715f0c..0c7a35773 100644
--- a/apps/toucher/metadata.json
+++ b/apps/toucher/metadata.json
@@ -2,7 +2,7 @@
   "id": "toucher",
   "name": "Touch Launcher",
   "shortName": "Toucher",
-  "version": "0.07",
+  "version": "0.08",
   "description": "Touch enable left to right launcher.",
   "icon": "app.png",
   "type": "launch",
diff --git a/apps/toucher/settings.js b/apps/toucher/settings.js
index 51275d846..f3004000a 100644
--- a/apps/toucher/settings.js
+++ b/apps/toucher/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/touchtimer/ChangeLog b/apps/touchtimer/ChangeLog
index f81907152..203360bd0 100644
--- a/apps/touchtimer/ChangeLog
+++ b/apps/touchtimer/ChangeLog
@@ -3,3 +3,4 @@
 0.03: Add ability to repeat last timer
 0.04: Add 5 second count down buzzer
 0.05: Fix 5 second count down buzzer to be only in the final 5 seconds
+0.06: Use default Bangle formatter for booleans
diff --git a/apps/touchtimer/metadata.json b/apps/touchtimer/metadata.json
index 9261f3619..8e09a7e34 100644
--- a/apps/touchtimer/metadata.json
+++ b/apps/touchtimer/metadata.json
@@ -2,7 +2,7 @@
   "id": "touchtimer",
   "name": "Touch Timer",
   "shortName": "Touch Timer",
-  "version": "0.05",
+  "version": "0.06",
   "description": "Quickly and easily create a timer with touch-only input. The time can be easily set with a number pad.",
   "icon": "app.png",
   "tags": "tools",
diff --git a/apps/touchtimer/settings.js b/apps/touchtimer/settings.js
index 79424f250..d3de4e6d3 100644
--- a/apps/touchtimer/settings.js
+++ b/apps/touchtimer/settings.js
@@ -33,7 +33,6 @@
       },
       "CountDown Buzz": {
         value: !!settings.countDownBuzz,
-        format: value => value?"On":"Off",
         onchange: (value) => {
           settings.countDownBuzz = value;
           writeSettings(settings);
diff --git a/apps/vectorclock/ChangeLog b/apps/vectorclock/ChangeLog
index 02831edde..6693f57ec 100644
--- a/apps/vectorclock/ChangeLog
+++ b/apps/vectorclock/ChangeLog
@@ -6,3 +6,4 @@
 0.06: Redraw widgets when time is updated
 0.07: Fix problem with "Bangle.CLOCK": github.com/espruino/BangleApps/issues/1437
 0.08: Redraw widgets only once per minute
+0.09: Workaround for issue in 2v14 firmware (fix #1959)
diff --git a/apps/vectorclock/app.js b/apps/vectorclock/app.js
index 663a4c84f..ee3a4ea53 100644
--- a/apps/vectorclock/app.js
+++ b/apps/vectorclock/app.js
@@ -16,7 +16,7 @@ var commands = [];
 var showSeconds = true;
 
 function pushCommand(command) {
-  let hash = E.CRC32(E.toJS(arguments));
+  var hash = E.CRC32(E.toJS(arguments));
   if (!delete rectsToClear[hash]) {
     commands.push({hash: hash, command: Function.apply.bind(command, null, arguments.slice(1))});
   }
diff --git a/apps/vectorclock/metadata.json b/apps/vectorclock/metadata.json
index 541766fa2..245aad044 100644
--- a/apps/vectorclock/metadata.json
+++ b/apps/vectorclock/metadata.json
@@ -1,7 +1,7 @@
 {
   "id": "vectorclock",
   "name": "Vector Clock",
-  "version": "0.08",
+  "version": "0.09",
   "description": "A digital clock that uses the built-in vector font.",
   "icon": "app.png",
   "type": "clock",
diff --git a/apps/verticalface/ChangeLog b/apps/verticalface/ChangeLog
index 99ab68ec4..dc6c11eab 100644
--- a/apps/verticalface/ChangeLog
+++ b/apps/verticalface/ChangeLog
@@ -4,3 +4,4 @@
 0.07: Added leading zero to hours and minutes
 0.08: Show step count by calling wpedom.getSteps() or activepedom.getSteps()
 0.09: Fix time when minutes<10 and hours>9 (fix #767)
+0.10: Tell clock widgets to hide.
diff --git a/apps/verticalface/app.js b/apps/verticalface/app.js
index 4fcae5642..178481047 100644
--- a/apps/verticalface/app.js
+++ b/apps/verticalface/app.js
@@ -97,6 +97,28 @@ function drawBattery() {
 // Clear the screen once, at startup
 g.clear();
 
+// Show launcher when button pressed
+Bangle.setUI("clockupdown", btn=>{
+  if (btn!=0) return;
+  //HRM Controller.
+  if(!HRMstate){
+    //console.log("Toggled HRM");
+    //Turn on.
+    Bangle.buzz();
+    Bangle.setHRMPower(1);
+    currentHRM = "CALC";
+    HRMstate = true;
+  } else if(HRMstate){
+    //console.log("Toggled HRM");
+    //Turn off.
+    Bangle.buzz();
+    Bangle.setHRMPower(0);
+    HRMstate = false;
+    currentHRM = [];
+  }
+  drawBPM(HRMstate);
+});
+
 // Load and draw widgets
 Bangle.loadWidgets();
 Bangle.drawWidgets();
@@ -128,28 +150,6 @@ Bangle.on('lcdPower',on=>{
   }
 });
 
-// Show launcher when button pressed
-Bangle.setUI("clockupdown", btn=>{
-  if (btn!=0) return;
-  //HRM Controller.
-  if(!HRMstate){
-    //console.log("Toggled HRM");
-    //Turn on.
-    Bangle.buzz();
-    Bangle.setHRMPower(1);
-    currentHRM = "CALC";
-    HRMstate = true;
-  } else if(HRMstate){
-    //console.log("Toggled HRM");
-    //Turn off.
-    Bangle.buzz();
-    Bangle.setHRMPower(0);
-    HRMstate = false;
-    currentHRM = [];
-  }
-  drawBPM(HRMstate);
-});
-
 Bangle.on('touch', function(button) {
   if(button == 1 || button == 2){
     Bangle.showLauncher();
diff --git a/apps/verticalface/metadata.json b/apps/verticalface/metadata.json
index da41b3f0d..273070022 100644
--- a/apps/verticalface/metadata.json
+++ b/apps/verticalface/metadata.json
@@ -2,7 +2,7 @@
   "id": "verticalface",
   "name": "Vertical watch face",
   "shortName": "Vertical Face",
-  "version": "0.09",
+  "version": "0.10",
   "description": "A simple vertical watch face with the date. Heart rate monitor is toggled with BTN1",
   "icon": "app.png",
   "type": "clock",
diff --git a/apps/waypointer/ChangeLog b/apps/waypointer/ChangeLog
index 7ccad08ea..292d77a99 100644
--- a/apps/waypointer/ChangeLog
+++ b/apps/waypointer/ChangeLog
@@ -1,3 +1,4 @@
 0.01: New app!
 0.02: Make Bangle.js 2 compatible
 0.03: Silently use built in heading when no magnav calibration file is present
+0.04: Move waypoints.json (and editor) to 'waypoints' app
diff --git a/apps/waypointer/README.md b/apps/waypointer/README.md
index c0b4c5125..241d70a65 100644
--- a/apps/waypointer/README.md
+++ b/apps/waypointer/README.md
@@ -61,58 +61,10 @@ The app indicates that WP2 is now marked by adding the prefix @ to
 it's name. The distance should be small as shown in the screen shot
 as you have just marked your current location.
 
-## Waypoint JSON file
-
-When the app is loaded from the app loader, a file named
-`waypoints.json` is loaded along with the javascript etc. The file
-has the following contents:
-
-
-```
-[
-  {
-  "name":"NONE"
-  },
-  {
-  "name":"No10",
-  "lat":51.5032,
-  "lon":-0.1269
-  },
-  {
-  "name":"Stone",
-  "lat":51.1788,
-  "lon":-1.8260
-  },
-  { "name":"WP0" },
-  { "name":"WP1" },
-  { "name":"WP2" },
-  { "name":"WP3" },
-  { "name":"WP4" }
-]
-```
-
-The file contains the initial NONE waypoint which is useful if you
-just want to display course and speed. The next two entries are
-waypoints to No 10 Downing Street and to Stone Henge - obtained from
-Google Maps. The last five entries are entries which can be *marked*.
-
-You add and delete entries using the Web IDE to load and then save
-the file from and to watch storage. The app itself does not limit the
-number of entries although it does load the entire file into RAM
-which will obviously limit this.
-
-
-## Waypoint Editor
-
-Clicking on the download icon of gpsnav in the app loader invokes the
-waypoint editor.  The editor downloads and displays the current
-`waypoints.json` file. Clicking the `Edit` button beside an entry
-causes the entry to be deleted from the list and displayed in the
-edit boxes. It can be restored - by clicking the `Add waypoint`
-button. A new markable entry is created by using the `Add name`
-button. The edited `waypoints.json` file is uploaded to the Bangle by
-clicking the `Upload` button.
+## Setting Waypoints
 
+Check out the documentation for the `Waypoints` app. This provides
+the ability to set waypoints from your browser.
 
 ## Calibration of the Compass
 
diff --git a/apps/waypointer/app.js b/apps/waypointer/app.js
index bdb6f6857..9fd288c9a 100644
--- a/apps/waypointer/app.js
+++ b/apps/waypointer/app.js
@@ -50,7 +50,7 @@ function drawCompass(course) {
   if(!candraw) return;
   if (Math.abs(previous.course - course) < 9) return; // reduce number of draws due to compass jitter
   previous.course = course;
-  
+
   buf1.setColor(1);
   buf1.fillCircle(buf1.getWidth()/2,buf1.getHeight()/2,79*scale);
   buf1.setColor(0);
@@ -63,10 +63,10 @@ function drawCompass(course) {
 /***** COMPASS CODE ***********/
 
 var heading = 0;
-function newHeading(m,h){ 
+function 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>=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;
@@ -147,9 +147,9 @@ function drawN(){
   var bs = wp_bearing.toString();
   bs = wp_bearing<10?"00"+bs : wp_bearing<100 ?"0"+bs : bs;
   var dst = loc.distance(dist);
-  
+
   // -1=left (default), 0=center, 1=right
-  
+
   // show distance on the left
   if (previous.dst !== dst) {
     previous.dst = dst;
@@ -159,7 +159,7 @@ function drawN(){
     buf2.drawString(dst,0,0);
     flip2_bw(0, g.getHeight()-40*scale);
   }
-  
+
   // bearing, place in middle at bottom of compass
   if (previous.bs !== bs) {
     previous.bs = bs;
@@ -192,7 +192,7 @@ function onGPS(fix) {
   if (fix!==undefined){
     satellites = fix.satellites;
   }
-  
+
   if (candraw) {
     if (fix!==undefined && fix.fix==1){
       dist = distance(fix,wp);
@@ -240,7 +240,7 @@ function setButtons(){
     else { doselect(); }
   });
 }
- 
+
 Bangle.on('lcdPower',function(on) {
   if (on) {
     clear_previous();
@@ -250,7 +250,7 @@ Bangle.on('lcdPower',function(on) {
   }
 });
 
-var waypoints = require("Storage").readJSON("waypoints.json")||[{name:"NONE"}];
+var waypoints = require("waypoints").load();
 wp=waypoints[0];
 
 function nextwp(inc){
@@ -266,7 +266,7 @@ function doselect(){
   if (selected && wpindex!=0 && waypoints[wpindex].lat===undefined && savedfix.fix) {
      waypoints[wpindex] ={name:"@"+wp.name, lat:savedfix.lat, lon:savedfix.lon};
      wp = waypoints[wpindex];
-     require("Storage").writeJSON("waypoints.json", waypoints);
+     require("waypoints").save(waypoints);
   }
   selected=!selected;
   drawN();
diff --git a/apps/waypointer/metadata.json b/apps/waypointer/metadata.json
index 707da94cf..8b923c604 100644
--- a/apps/waypointer/metadata.json
+++ b/apps/waypointer/metadata.json
@@ -1,16 +1,15 @@
 {
   "id": "waypointer",
   "name": "Way Pointer",
-  "version": "0.03",
+  "version": "0.04",
   "description": "Navigate to a waypoint using the GPS for bearing and compass to point way, uses the same waypoint interface as GPS Navigation",
   "icon": "waypointer.png",
   "tags": "tool,outdoors,gps",
   "supports": ["BANGLEJS", "BANGLEJS2"],
+  "dependencies" : { "waypoints":"type" },
   "readme": "README.md",
-  "interface": "waypoints.html",
   "storage": [
     {"name":"waypointer.app.js","url":"app.js"},
     {"name":"waypointer.img","url":"icon.js","evaluate":true}
-  ],
-  "data": [{"name":"waypoints.json","url":"waypoints.json"}]
+  ]
 }
diff --git a/apps/waypointer/waypoints.html b/apps/waypointer/waypoints.html
deleted file mode 100644
index 7a65821a2..000000000
--- a/apps/waypointer/waypoints.html
+++ /dev/null
@@ -1,170 +0,0 @@
-
-  
-    
-    
-  
-  
-
-    

List of waypoints

- - - - - - - - - - - - -
NameLat.Long.Actions
-
-

Add a new waypoint

-
-
-
- -
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- - - - - - - diff --git a/apps/waypointer/waypoints.json b/apps/waypointer/waypoints.json deleted file mode 100644 index 98a670c0d..000000000 --- a/apps/waypointer/waypoints.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "name":"NONE" - }, - { - "name":"No10", - "lat":51.5032, - "lon":-0.1269 - }, - { - "name":"Stone", - "lat":51.1788, - "lon":-1.8260 - }, - { "name":"WP0" }, - { "name":"WP1" }, - { "name":"WP2" }, - { "name":"WP3" }, - { "name":"WP4" } -] \ No newline at end of file diff --git a/apps/waypoints/ChangeLog b/apps/waypoints/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/waypoints/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/waypoints/README.md b/apps/waypoints/README.md new file mode 100644 index 000000000..e252054f2 --- /dev/null +++ b/apps/waypoints/README.md @@ -0,0 +1,56 @@ +# Waypoints + +This app provides a common way to set up the `waypoints.json` file, +which several other apps rely on for navigation. + +## Waypoint JSON file + +When the app is loaded from the app loader, a file named +`waypoints.json` is loaded along with the javascript etc. The file +has the following contents: + + +``` +[ + { + "name":"NONE" + }, + { + "name":"No10", + "lat":51.5032, + "lon":-0.1269 + }, + { + "name":"Stone", + "lat":51.1788, + "lon":-1.8260 + }, + { "name":"WP0" }, + { "name":"WP1" }, + { "name":"WP2" }, + { "name":"WP3" }, + { "name":"WP4" } +] +``` + +The file contains the initial NONE waypoint which is useful if you +just want to display course and speed. The next two entries are +waypoints to No 10 Downing Street and to Stone Henge - obtained from +Google Maps. The last five entries are entries which can be *marked*. + +You add and delete entries using the Web IDE to load and then save +the file from and to watch storage. The app itself does not limit the +number of entries although it does load the entire file into RAM +which will obviously limit this. + + +## Waypoint Editor + +Clicking on the download icon of `Waypoints` in the app loader invokes the +waypoint editor. The editor downloads and displays the current +`waypoints.json` file. Clicking the `Edit` button beside an entry +causes the entry to be deleted from the list and displayed in the +edit boxes. It can be restored - by clicking the `Add waypoint` +button. A new markable entry is created by using the `Add name` +button. The edited `waypoints.json` file is uploaded to the Bangle by +clicking the `Upload` button. diff --git a/apps/waypoints/app-icon.js b/apps/waypoints/app-icon.js new file mode 100644 index 000000000..49232b838 --- /dev/null +++ b/apps/waypoints/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwJC/AH4A/AH4AgA==")) diff --git a/apps/waypoints/app.js b/apps/waypoints/app.js new file mode 100644 index 000000000..06c254a36 --- /dev/null +++ b/apps/waypoints/app.js @@ -0,0 +1,34 @@ +// place your const, vars, functions or classes here + +// 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(); + } +}); + +// First draw... +draw(); + +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/waypoints/app.png b/apps/waypoints/app.png new file mode 100644 index 000000000..0b9344405 Binary files /dev/null and b/apps/waypoints/app.png differ diff --git a/apps/wpmoto/wpmoto.html b/apps/waypoints/interface.html similarity index 60% rename from apps/wpmoto/wpmoto.html rename to apps/waypoints/interface.html index 9966f51f6..48e47df57 100644 --- a/apps/wpmoto/wpmoto.html +++ b/apps/waypoints/interface.html @@ -4,6 +4,7 @@ + @@ -11,6 +12,8 @@ html, body { height: 100% } .flex-col { display:flex; flex-direction:column; height:100% } #map { width:100%; height:100% } + #tab-map { width:100%; height:100% } + #tab-list { width:100%; height:100% } /* https://stackoverflow.com/a/58686215 */ .arrow-icon { @@ -23,6 +26,7 @@ transform-origin: center center; font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif; } + @@ -32,8 +36,57 @@ +
+ +
+
+
+
@@ -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..49e23e1d6 100644 --- a/apps/weather/ChangeLog +++ b/apps/weather/ChangeLog @@ -12,3 +12,4 @@ 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 diff --git a/apps/weather/app.js b/apps/weather/app.js index efd9b0209..f63b226b9 100644 --- a/apps/weather/app.js +++ b/apps/weather/app.js @@ -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/metadata.json b/apps/weather/metadata.json index 1d0b6b469..25037de3d 100644 --- a/apps/weather/metadata.json +++ b/apps/weather/metadata.json @@ -1,7 +1,7 @@ { "id": "weather", "name": "Weather", - "version": "0.15", + "version": "0.16", "description": "Show Gadgetbridge weather report", "icon": "icon.png", "screenshots": [{"url":"screenshot.png"}], 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/metadata.json b/apps/widadjust/metadata.json index a308072f5..cef91369f 100644 --- a/apps/widadjust/metadata.json +++ b/apps/widadjust/metadata.json @@ -2,7 +2,7 @@ "id": "widadjust", "name": "Adjust Clock", "icon": "icon.png", - "version": "0.01", + "version": "0.02", "description": "Adjusts clock continually in the background to counter clock drift", "type": "widget", "tags": "widget", diff --git a/apps/widadjust/settings.js b/apps/widadjust/settings.js index 5791d763b..6743c7fc5 100644 --- a/apps/widadjust/settings.js +++ b/apps/widadjust/settings.js @@ -80,9 +80,7 @@ min: 0, max: intervalV.length - 1, format: v => intervalN[v], - onchange: v => { - settings.updateInterval = intervalV[v]; - }, + onchange: v => settings.updateInterval = intervalV[v] , }, 'Threshold': { @@ -97,10 +95,9 @@ 'Save State': { value: settings.saveState, - format: v => v ? 'On' : 'Off', - onchange: () => { - settings.saveState = !settings.saveState; - if (!settings.saveState && !stateFileErased) { + onchange: (v) => { + settings.saveState = v; + if (!v && !stateFileErased) { stateFileErased = true; require("Storage").erase(STATE_FILE); } @@ -109,10 +106,7 @@ 'Debug Log': { value: settings.debugLog, - format: v => v ? 'On' : 'Off', - onchange: () => { - settings.debugLog = !settings.debugLog; - }, + onchange: v => settings.debugLog = v, }, }; 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..8d3d37aab --- /dev/null +++ b/apps/widagps/metadata.json @@ -0,0 +1,14 @@ +{ "id": "widagps", + "name": "AGPS Widget", + "shortName":"AGPS Widget", + "icon": "widget.png", + "type": "widget", + "version":"0.01", + "description": "Load AGPS data in the background **using Gadgetbridge**", + "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/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/widbaroalarm/ChangeLog b/apps/widbaroalarm/ChangeLog index 86a902605..2dfe8336d 100644 --- a/apps/widbaroalarm/ChangeLog +++ b/apps/widbaroalarm/ChangeLog @@ -3,3 +3,7 @@ 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 index 59d91ff66..478b48a71 100644 --- a/apps/widbaroalarm/README.md +++ b/apps/widbaroalarm/README.md @@ -19,7 +19,9 @@ Get a notification when the pressure reaches defined thresholds. * Pause delay: Same as Dismiss delay but longer (useful for meetings and such). From 30 to 240 min ## Widget -The widget shows two rows: pressure value of last measurement and pressure average of the the last three hours. +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/metadata.json b/apps/widbaroalarm/metadata.json index 41b8d3e17..ba6c47b37 100644 --- a/apps/widbaroalarm/metadata.json +++ b/apps/widbaroalarm/metadata.json @@ -2,13 +2,14 @@ "id": "widbaroalarm", "name": "Barometer Alarm Widget", "shortName": "Barometer Alarm", - "version": "0.04", + "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"}, 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/widget.js b/apps/widbaroalarm/widget.js index e1516b6f1..d877c4384 100644 --- a/apps/widbaroalarm/widget.js +++ b/apps/widbaroalarm/widget.js @@ -1,141 +1,158 @@ (function() { - let medianPressure; - let threeHourAvrPressure; - let currentPressures = []; - let stop = false; // semaphore +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'); +const LOG_FILE = "widbaroalarm.log.json"; +const SETTINGS_FILE = "widbaroalarm.json"; +const storage = require('Storage'); - let settings; +let settings; - function loadSettings() { - settings = Object.assign( - storage.readJSON("widbaroalarm.default.json", true) || {}, - storage.readJSON(SETTINGS_FILE, true) || {} - ); - } +function loadSettings() { + settings = + Object.assign(storage.readJSON("widbaroalarm.default.json", true) || {}, + storage.readJSON(SETTINGS_FILE, true) || {}); +} - loadSettings(); +loadSettings(); +function setting(key) { return settings[key]; } - function setting(key) { - return settings[key]; - } +function saveSetting(key, value) { + settings[key] = value; + storage.write(SETTINGS_FILE, settings); +} - function saveSetting(key, value) { - settings[key] = value; - storage.write(SETTINGS_FILE, settings); - } +const interval = setting("interval"); - const interval = setting("interval"); +let history3 = + storage.readJSON(LOG_FILE, true) || []; // history of recent 3 hours - let history3 = storage.readJSON(LOG_FILE, true) || []; // history of recent 3 hours +function showAlarm(body, key, type) { + if (body == undefined) + return; + stop = true; - function showAlarm(body, key) { - if (body == undefined) return; - stop = true; - - E.showPrompt(body, { - title: "Pressure 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); - } - - - function doWeNeedToWarn(key) { + 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 - return setting(key) == 0 || setting(key) < tsNow; + + 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(); } - function checkForAlarms(pressure) { - if (pressure == undefined || pressure <= 0) return; + setTimeout(function() { + stop = false; + load(); + }, 20000); +} - let alreadyWarned = false; +/* + * 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); +} - const ts = Math.round(Date.now() / 1000); // seconds - const d = { - "ts": ts, - "p": pressure - }; +function isValidPressureValue(pressure) { + if (pressure == undefined || pressure <= 0) + return false; + return pressure > 800 && pressure < 1200; // very rough values +} - // delete entries older than 3h - for (let i = 0; i < history3.length; i++) { - if (history3[i]["ts"] < ts - (3 * 60 * 60)) { - history3.shift(); - } - } - // delete oldest entries until we have max 50 - while (history3.length > 50) { +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; } + } - if (setting("lowalarm")) { - // Is below the alarm threshold? - if (pressure <= setting("min")) { - if (!doWeNeedToWarn("lastLowWarningTs")) { - showAlarm("Pressure low: " + Math.round(pressure) + " hPa", "lastLowWarningTs"); - alreadyWarned = true; - } - } else { - saveSetting("lastLowWarningTs", 0); + // 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 (doWeNeedToWarn("lastHighWarningTs")) { - showAlarm("Pressure high: " + Math.round(pressure) + " hPa", "lastHighWarningTs"); - alreadyWarned = true; - } - } else { - saveSetting("lastHighWarningTs", 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 30min of data for reliable detection - const diffDateAge = Math.abs(history3[0]["ts"] - ts); - if (diffDateAge < 10 * 60) { // todo change to 1800 - return; - } - + 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) { @@ -143,66 +160,76 @@ // drop alarm if (drop3halarm > 0 && oldestPressure > pressure) { - if (diffPressure > drop3halarm) { - if (doWeNeedToWarn("lastDropWarningTs")) { - showAlarm((Math.round(diffPressure * 10) / 10) + " hPa/3h from " + - Math.round(oldestPressure) + " to " + Math.round(pressure) + " hPa", "lastDropWarningTs"); + 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 { - saveSetting("lastDropWarningTs", 0); + if (ts > setting("dropWarnTs")) + saveSetting("dropWarnTs", 0); } } else { - saveSetting("lastDropWarningTs", 0); + if (ts > setting("dropWarnTs")) + saveSetting("dropWarnTs", 0); } // raise alarm if (raise3halarm > 0 && oldestPressure < pressure) { - if (diffPressure > raise3halarm) { - if (doWeNeedToWarn("lastRaiseWarningTs")) { - showAlarm((Math.round(diffPressure * 10) / 10) + " hPa/3h from " + - Math.round(oldestPressure) + " to " + Math.round(pressure) + " hPa", "lastRaiseWarningTs"); + 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 { - saveSetting("lastRaiseWarningTs", 0); + if (ts > setting("raiseWarnTs")) + saveSetting("raiseWarnTs", 0); } } else { - saveSetting("lastRaiseWarningTs", 0); + if (ts > setting("raiseWarnTs")) + saveSetting("raiseWarnTs", 0); } } } } - - history3.push(d); - // write data to storage - storage.writeJSON(LOG_FILE, history3); - - // calculate 3h average for widget - if (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; - } } +} +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 check() { - if (stop) return; - const MEDIANLENGTH = 20; - Bangle.setBarometerPower(true, "widbaroalarm"); - Bangle.on('pressure', function(e) { - while (currentPressures.length > MEDIANLENGTH) currentPressures.pop(); - currentPressures.unshift(e.pressure); +/* + 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) { @@ -210,59 +237,68 @@ medianPressure = Math.round(E.sum(median.slice(mid - 4, mid + 5)) / 9); if (medianPressure > 0) { turnOff(); - checkForAlarms(medianPressure); + draw(); + handlePressureValue(medianPressure); } } - }); - - setTimeout(function() { - turnOff(); - }, 10000); - } - - function turnOff() { - if (Bangle.isBarometerOn()) - Bangle.setBarometerPower(false, "widbaroalarm"); - } - - function reload() { - check(); - } - - function draw() { - if (global.WIDGETS != undefined && typeof global.WIDGETS === "object") { - global.WIDGETS["baroalarm"] = { - width: setting("show") ? 24 : 0, - reload: reload, - area: "tr", - draw: draw - }; } - g.reset(); - if (setting("show")) { - g.setFont("6x8", 1).setFontAlign(1, 0); - if (medianPressure == undefined) { - check(); - const x = this.x, - y = this.y; - g.drawString("...", x + 24, y + 6); - setTimeout(function() { - g.setFont("6x8", 1).setFontAlign(1, 0); - g.drawString(Math.round(medianPressure), x + 24, y + 6); - }, 10000); - } else { + }); + + 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 && threeHourAvrPressure > 0) { - g.drawString(Math.round(threeHourAvrPressure), this.x + 24, this.y + 6 + 10); + 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(check, interval * 60000); - } - draw(); - +if (interval > 0) { + setInterval(getPressureValue, interval * 60000); +} +getPressureValue(); })(); 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_notify/ChangeLog b/apps/widbt_notify/ChangeLog index 3708089c1..8f1dab908 100644 --- a/apps/widbt_notify/ChangeLog +++ b/apps/widbt_notify/ChangeLog @@ -9,3 +9,6 @@ 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 \ No newline at end of file diff --git a/apps/widbt_notify/metadata.json b/apps/widbt_notify/metadata.json index 0a144ade1..6def70c64 100644 --- a/apps/widbt_notify/metadata.json +++ b/apps/widbt_notify/metadata.json @@ -1,13 +1,17 @@ { "id": "widbt_notify", "name": "Bluetooth Widget with Notification", - "version": "0.12", - "description": "Show the current Bluetooth connection status in the top right of the clock and vibrate when disconnected.", + "version": "0.15", + "description": "Show the current Bluetooth connection status in the top right of the watch. Optional buzz and/or and hide if disconnected", "icon": "widget.png", "type": "widget", "tags": "widget,bluetooth", "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ - {"name":"widbt_notify.wid.js","url":"widget.js"} - ] + {"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..1e0d5036b --- /dev/null +++ b/apps/widbt_notify/settings.js @@ -0,0 +1,69 @@ +(function(back) { + var FILE = "widbt_notify.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": "Bluetooth Widget WN" + }, + "< Back": () => back(), + "Show Widget": { + value: (settings.showWidget !== undefined ? settings.showWidget : true), + onchange: v => { + settings.showWidget = v; + writeSettings(); + } + }, + "Buzz on Connect": { + value: (settings.buzzOnConnect !== undefined ? settings.buzzOnConnect : true), + onchange: v => { + settings.buzzOnConnect = v; + writeSettings(); + } + }, + "Buzz on loss": { + value: (settings.buzzOnLoss !== undefined ? settings.buzzOnLoss : true), + onchange: v => { + settings.buzzOnLoss = v; + writeSettings(); + } + }, + "Hide connected": { + value: (settings.hideConnected !== undefined ? settings.hideConnected : false), + onchange: v => { + settings.hideConnected = v; + writeSettings(); + } + } + }; + + E.showMenu(mainmenu); + +}); diff --git a/apps/widbt_notify/widget.js b/apps/widbt_notify/widget.js index fd088c670..de2baa3cf 100644 --- a/apps/widbt_notify/widget.js +++ b/apps/widbt_notify/widget.js @@ -2,42 +2,106 @@ WIDGETS.bluetooth_notify = { area: "tr", width: 15, warningEnabled: 1, + + // ------------ Settings -------- very lame - need to improve + readshowWidget: function() { + var showWidget; + const SETTINGSFILE = "widbt_notify.json"; + function def (value, def) {return value !== undefined ? value : def;} + var settings = require('Storage').readJSON(SETTINGSFILE, true) || {}; + showWidget = def(settings.showWidget, true); + return showWidget; + }, + + readBuzzOnConnect: function() { + var buzzOnConnect; + const SETTINGSFILE = "widbt_notify.json"; + function def (value, def) {return value !== undefined ? value : def;} + var settings = require('Storage').readJSON(SETTINGSFILE, true) || {}; + buzzOnConnect = def(settings.buzzOnConnect, true); + return buzzOnConnect; + }, + + readBuzzOnLoss: function() { + var buzzOnLoss; + const SETTINGSFILE = "widbt_notify.json"; + function def (value, def) {return value !== undefined ? value : def;} + var settings = require('Storage').readJSON(SETTINGSFILE, true) || {}; + buzzOnLoss = def(settings.buzzOnLoss, true); + return buzzOnLoss; + }, + + readHideConnected: function() { + var hideConnected; + const SETTINGSFILE = "widbt_notify.json"; + function def (value, def) {return value !== undefined ? value : def;} + var settings = require('Storage').readJSON(SETTINGSFILE, true) || {}; + hideConnected = def(settings.hideConnected, true); + return hideConnected; + }, + + + // ------------ Settings -------- + draw: function() { - g.reset(); - if (NRF.getSecurityStatus().connected) { - g.setColor((g.getBPP() > 8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f")); - } else { - // g.setColor(g.theme.dark ? "#666" : "#999"); - g.setColor("#f00"); // red is easier to distinguish from blue + if (WIDGETS.bluetooth_notify.readshowWidget()){ + g.reset(); + if (NRF.getSecurityStatus().connected) { + if (!WIDGETS.bluetooth_notify.readHideConnected()) { + 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); + } } - g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="), 2 + this.x, 2 + this.y); }, redrawCurrentApp: function(){ if(typeof(draw)=='function'){ + g.clear(); draw(); + Bangle.loadWidgets(); + Bangle.drawWidgets(); }else{ load(); // fallback. This might reset some variables } }, connect: function() { - WIDGETS.bluetooth_notify.draw(); + + if(WIDGETS.bluetooth_notify.warningEnabled == 1){ + E.showMessage(/*LANG*/'Connection\nrestored.', 'Bluetooth'); + setTimeout(()=>{WIDGETS.bluetooth_notify.redrawCurrentApp();}, 3000); // clear message - this will reload the widget, resetting 'warningEnabled'. + + WIDGETS.bluetooth_notify.warningEnabled = 0; + setTimeout('WIDGETS.bluetooth_notify.warningEnabled = 1;', 30000); // don't buzz for the next 30 seconds. + + var quiet = (require('Storage').readJSON('setting.json',1)||{}).quiet; + if(!quiet && WIDGETS.bluetooth_notify.readBuzzOnConnect()){ + Bangle.buzz(700, 1); // buzz on connection resume + } + } + WIDGETS.bluetooth_notify.draw(); + }, disconnect: function() { - if(WIDGETS.bluetooth_notify.warningEnabled == 1){ - E.showMessage(/*LANG*/'Connection\nlost.', 'Bluetooth'); - setTimeout(()=>{WIDGETS.bluetooth_notify.redrawCurrentApp();}, 3000); // clear message - this will reload the widget, resetting 'warningEnabled'. - - WIDGETS.bluetooth_notify.warningEnabled = 0; - setTimeout('WIDGETS.bluetooth_notify.warningEnabled = 1;', 30000); // don't buzz for the next 30 seconds. - - var quiet = (require('Storage').readJSON('setting.json',1)||{}).quiet; - if(!quiet){ - Bangle.buzz(700, 1); // buzz on connection loss + if(WIDGETS.bluetooth_notify.warningEnabled == 1){ + E.showMessage(/*LANG*/ 'Connection\nlost.', 'Bluetooth'); + setTimeout(()=>{WIDGETS.bluetooth_notify.redrawCurrentApp();}, 3000); // clear message - this will reload the widget, resetting 'warningEnabled'. + + WIDGETS.bluetooth_notify.warningEnabled = 0; + setTimeout('WIDGETS.bluetooth_notify.warningEnabled = 1;', 30000); // don't buzz for the next 30 seconds. + + var quiet = (require('Storage').readJSON('setting.json',1)||{}).quiet; + if(!quiet && WIDGETS.bluetooth_notify.readBuzzOnLoss()){ + Bangle.buzz(700, 1); // buzz on connection loss + } } - } + WIDGETS.bluetooth_notify.draw(); } }; diff --git a/apps/widcw/ChangeLog b/apps/widcw/ChangeLog index a4bc24d1a..07b8f7424 100644 --- a/apps/widcw/ChangeLog +++ b/apps/widcw/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/widcw/metadata.json b/apps/widcw/metadata.json index 653b093ec..467ab1729 100644 --- a/apps/widcw/metadata.json +++ b/apps/widcw/metadata.json @@ -1,7 +1,7 @@ { "id": "widcw", "name": "Calendar Week Widget", - "version": "0.01", + "version": "0.02", "description": "Widget which shows the current calendar week", "icon": "widget.png", "type": "widget", diff --git a/apps/widcw/widget.js b/apps/widcw/widget.js index ef43a4551..e33ad0aad 100644 --- a/apps/widcw/widget.js +++ b/apps/widcw/widget.js @@ -34,8 +34,8 @@ } // redraw when date changes - setTimeout(()=>WIDGETS["widcw"].draw(), (86401 - Math.floor(date/1000) % 86400)*1000); - + 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 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..f280c96eb --- /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" : ["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 0eb9e5692..b530843e7 100644 --- a/apps/widgps/ChangeLog +++ b/apps/widgps/ChangeLog @@ -4,3 +4,6 @@ 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 b135c77bd..cfd35f5bb 100644 --- a/apps/widgps/metadata.json +++ b/apps/widgps/metadata.json @@ -1,14 +1,19 @@ { "id": "widgps", "name": "GPS Widget", - "version": "0.06", - "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 206096013..73351eaa6 100644 --- a/apps/widgps/widget.js +++ b/apps/widgps/widget.js @@ -1,36 +1,85 @@ -(function(){ - var interval; +(function() { - // 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.draw(); - return isGPSon; - } +let settings = Object.assign( + require('Storage').readJSON("widgps.default.json", true) || {}, + require('Storage').readJSON("widgps.json", true) || {} +); - WIDGETS.gps={area:"tr",width:24,draw:function() { +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 - } else { - g.setColor("#FD0"); // on but no fix = amber - } - } else { - g.setColor("#888"); // off = grey - } // 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 + 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; } - g.drawImage(atob("GBiBAAAAAAAAAAAAAA//8B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+A//8AAAAAAAAAAAAA=="), this.x, 2+this.y); - }}; + 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 { + 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); + } + } + } + } +}; })(); diff --git a/apps/widmeda/ChangeLog b/apps/widmeda/ChangeLog new file mode 100644 index 000000000..7415150e6 --- /dev/null +++ b/apps/widmeda/ChangeLog @@ -0,0 +1 @@ +0.01: Initial Medical Alert Widget! diff --git a/apps/widmeda/README.md b/apps/widmeda/README.md new file mode 100644 index 000000000..0bbfc4dc3 --- /dev/null +++ b/apps/widmeda/README.md @@ -0,0 +1,23 @@ +# Medical Alert Widget + +Shows a medical alert logo in the top right widget area, and a medical alert message in the bottom widget area. + +**Note:** this is not a replacement for a medical alert band but hopefully a useful addition. + +## Features + +Implemented: + +- Basic medical alert logo and message +- Only display bottom widget on clocks +- High contrast colours depending on theme + +Future: + +- Configure when to show bottom widget (always/never/clocks) +- Configure medical alert text +- Show details when touched + +## Creator + +James Taylor ([jt-nti](https://github.com/jt-nti)) diff --git a/apps/widmeda/metadata.json b/apps/widmeda/metadata.json new file mode 100644 index 000000000..346306b8e --- /dev/null +++ b/apps/widmeda/metadata.json @@ -0,0 +1,15 @@ +{ "id": "widmeda", + "name": "Medical Alert Widget", + "shortName":"Medical Alert", + "version":"0.01", + "description": "Display a medical alert in the bottom widget section.", + "icon": "widget.png", + "type": "widget", + "tags": "health,medical,tools,widget", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "screenshots": [{"url":"screenshot_light.png"}], + "storage": [ + {"name":"widmeda.wid.js","url":"widget.js"} + ] +} diff --git a/apps/widmeda/screenshot_light.png b/apps/widmeda/screenshot_light.png new file mode 100644 index 000000000..bf92d753d Binary files /dev/null and b/apps/widmeda/screenshot_light.png differ diff --git a/apps/widmeda/widget.js b/apps/widmeda/widget.js new file mode 100644 index 000000000..a2d109596 --- /dev/null +++ b/apps/widmeda/widget.js @@ -0,0 +1,30 @@ +(() => { + // 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 message + 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",18); + g.setFontAlign(0,0); + g.clearRect(this.x, this.y, this.x + this.width - 1, this.y + 23); + g.drawString("MEDICAL ALERT", this.width / 2, this.y + ( 23 / 2 )); + } + }; +})(); 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/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/widmp/ChangeLog b/apps/widmp/ChangeLog index f0cd6bd8a..a51ac080a 100644 --- a/apps/widmp/ChangeLog +++ b/apps/widmp/ChangeLog @@ -4,3 +4,5 @@ 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 b1e6a6c8c..654b5a383 100644 --- a/apps/widmp/metadata.json +++ b/apps/widmp/metadata.json @@ -1,7 +1,7 @@ { "id": "widmp", "name": "Moon Phase", - "version": "0.06", + "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", diff --git a/apps/widmp/settings.js b/apps/widmp/settings.js index 46c5d3609..a389f7918 100644 --- a/apps/widmp/settings.js +++ b/apps/widmp/settings.js @@ -25,7 +25,6 @@ "< Back": () => back(), "Default": { value: (settings.default_colour !== undefined ? settings.default_colour : true), - format: v => v ? "Yes" : "No", onchange: v => { settings.default_colour = v; writeSettings(); diff --git a/apps/widmp/widget.js b/apps/widmp/widget.js index 22a7d6572..e5aa7fef2 100644 --- a/apps/widmp/widget.js +++ b/apps/widmp/widget.js @@ -4,15 +4,14 @@ var phase = 0; // The last phase we calculated var southernHemisphere = false; // when in southern hemisphere -- use the "My Location" App - // https://deirdreobyrne.github.io/calculating_moon_phases/ - function moonPhase(millis) { - k = (millis - 946728000000) / 3155760000000; - mp = (8328.69142475915 * k) + 2.35555563685; - m = (628.30195516723 * k) + 6.24006012726; - d = (7771.37714483372 * k) + 5.19846652984; - t = d + (0.109764 * Math.sin (mp)) - (0.036652 * Math.sin(m)) + (0.022235 * Math.sin(d+d-mp)) + (0.011484 * Math.sin(d+d)) + (0.003735 * Math.sin(mp+mp)) + (0.00192 * Math.sin(d)); - k = (1 - Math.cos(t))/2; - if (Math.sin(t) < 0) { + // 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 @@ -71,7 +70,7 @@ millis = (new Date()).getTime(); if ((millis - lastCalculated) >= 7000000) { // if it's more than 7,000 sec since last calculation, re-calculate! - phase = moonPhase(millis); + phase = moonPhase(millis/1000); lastCalculated = millis; } 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/widtwenties/ChangeLog b/apps/widtwenties/ChangeLog new file mode 100644 index 000000000..87935d810 --- /dev/null +++ b/apps/widtwenties/ChangeLog @@ -0,0 +1,2 @@ +0.01: New Widget! +0.02: Fix calling null on draw \ No newline at end of file diff --git a/apps/widtwenties/README.md b/apps/widtwenties/README.md new file mode 100644 index 000000000..1dea18b8e --- /dev/null +++ b/apps/widtwenties/README.md @@ -0,0 +1,15 @@ +# Twenties + +Follow the [20-20-20 rule](https://www.aoa.org/AOA/Images/Patients/Eye%20Conditions/20-20-20-rule.pdf) with discrete reminders. Your BangleJS will buzz every 20 minutes for you to look away from your screen, and then buzz 20 seconds later to look back. Additionally, alternate between standing and sitting every 20 minutes to be standing for [more than 30 minutes](https://uwaterloo.ca/kinesiology-health-sciences/how-long-should-you-stand-rather-sit-your-work-station) per hour. + +## Usage + +Download this widget and, as long as your watch-face supports widgets, it will automatically run in the background. + +## Features + +Vibrate to remind you to stand up and look away for healthy living. + +## Creator + +[@splch](https://github.com/splch/) diff --git a/apps/widtwenties/metadata.json b/apps/widtwenties/metadata.json new file mode 100644 index 000000000..2e51457ac --- /dev/null +++ b/apps/widtwenties/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "widtwenties", + "name": "Twenties", + "shortName": "twenties", + "version": "0.02", + "description": "Buzzes every 20m to stand / sit and look 20ft away for 20s.", + "icon": "widget.png", + "type": "widget", + "tags": "widget,tools", + "supports": ["BANGLEJS", "BANGLEJS2"], + "readme": "README.md", + "storage": [{ "name": "widtwenties.wid.js", "url": "widget.js" }] +} diff --git a/apps/widtwenties/widget.js b/apps/widtwenties/widget.js new file mode 100644 index 000000000..58bc622eb --- /dev/null +++ b/apps/widtwenties/widget.js @@ -0,0 +1,24 @@ +// WIDGETS = {}; // <-- for development only + +/* run widgets in their own function scope so +they don't interfere with currently-running apps */ +(() => { + const move = 20 * 60 * 1000; // 20 minutes + const look = 20 * 1000; // 20 seconds + + buzz = _ => { + Bangle.buzz().then(_ => { + setTimeout(Bangle.buzz, look); + }); + }; + + // add widget + WIDGETS.twenties = { + buzz: buzz, + draw: _ => { return null; }, + }; + + setInterval(WIDGETS.twenties.buzz, move); // buzz to stand / sit +})(); + +// Bangle.drawWidgets(); // <-- for development only \ No newline at end of file diff --git a/apps/widtwenties/widget.png b/apps/widtwenties/widget.png new file mode 100644 index 000000000..7c6b02055 Binary files /dev/null and b/apps/widtwenties/widget.png differ 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..2f35c81fe --- /dev/null +++ b/apps/wpmoto/ChangeLog @@ -0,0 +1,3 @@ +... +0.02: First update with ChangeLog Added +0.03: Move waypoints.json (and editor) to 'waypoints' app diff --git a/apps/wpmoto/app.js b/apps/wpmoto/app.js index 7deacb6ca..c8e30a583 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; @@ -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..01ca4edd8 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.03", "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/backup.js b/backup.js index 8a894666e..06d98a366 100644 --- a/backup.js +++ b/backup.js @@ -101,6 +101,7 @@ function bangleUpload() { 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}); diff --git a/bin/apploader.js b/bin/apploader.js index 427a0ef99..76d518a27 100755 --- a/bin/apploader.js +++ b/bin/apploader.js @@ -1,4 +1,4 @@ -#!/usr/bin/nodejs +#!/usr/bin/env nodejs /* Simple Command-line app loader for Node.js =============================================== 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 fd8072e06..87065dab4 100755 --- a/bin/firmwaremaker_c.js +++ b/bin/firmwaremaker_c.js @@ -1,4 +1,4 @@ -#!/usr/bin/node +#!/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:"); 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 index 81c0f75ac..8bd23dd77 100755 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -1,4 +1,4 @@ -#!/usr/bin/node +#!/usr/bin/env node /* Checks for any obvious problems in apps.json */ @@ -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); } }); @@ -65,10 +81,19 @@ const APP_KEYS = [ const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite', 'supports']; 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","textinput","scheduler","notify","locale","settings"]; // values allowed for "type" field +const METADATA_TYPES = ["app","clock","widget","bootloader","RAM","launch","textinput","scheduler","notify","locale","settings","waypoints"]; // 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" +]; function globToRegex(pattern) { const ESCAPE = '.*+-?^${}()|[]\\'; @@ -87,87 +112,99 @@ 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 (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); - if (!Array.isArray(app.supports)) ERROR(`App ${app.id} has no 'supports' field or it's not an array`); + 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`); + ERROR(`App ${app.id} 'dependencies' must all be tagged 'type' or 'app' 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); + 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+")"); @@ -179,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")) { @@ -194,11 +231,18 @@ 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 { else { match = fileContents.match(/^\s*require\(\"heatshrink\"\)\.decompress\(\s*atob\(\s*\"([^"]*)\"\s*\)\s*\)\s*$/); if (match) icon = heatshrink.decompress(Buffer.from(match[1], 'base64')); - else ERROR(`JS icon ${file.name} does not match the pattern 'require("heatshrink").decompress(atob("..."))'`); + else ERROR(`JS icon ${file.name} does not match the pattern 'require("heatshrink").decompress(atob("..."))'`, {file:appDirRelative+file.url}); } if (match) { if (icon[0] > 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?) @@ -256,32 +300,41 @@ 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}); + }); } }); + + // 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' @@ -291,9 +344,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 cdab12fcf..6857957f5 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit cdab12fcf85bc140e1f5c3f7b15218108c3f7d32 +Subproject commit 6857957f5aedfd9d175ecbf8e49d08bb167b8128 diff --git a/loader.js b/loader.js index 0e0fdcba0..a79f06781 100644 --- a/loader.js +++ b/loader.js @@ -16,7 +16,7 @@ if (window.location.host=="banglejs.com") { 'This is not the official Bangle.js App Loader - you can try the Official Version here.'; } -var RECOMMENDED_VERSION = "2v14"; +var RECOMMENDED_VERSION = "2v15"; // could check http://www.espruino.com/json/BANGLEJS.json for this // We're only interested in Bangles @@ -195,10 +195,10 @@ window.addEventListener('load', (event) => { }); // BLE Compatibility - var selectLang = document.getElementById("settings-ble-compat"); - if (SETTINGS.bleCompat!==undefined) - Puck.increaseMTU = !SETTINGS.bleCompat; - selectLang.addEventListener("change",event=>{ + 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; diff --git a/modules/ClockFace.js b/modules/ClockFace.js index f8dc33287..a89281d2d 100644 --- a/modules/ClockFace.js +++ b/modules/ClockFace.js @@ -19,7 +19,7 @@ function ClockFace(options) { options.update.apply(this, [t, {d: true, h: true, m: true, s: true}]); }); this.update = options.update || (t => { - g.clear(); + 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"; @@ -49,6 +49,7 @@ function ClockFace(options) { } ClockFace.prototype.tick = function() { + "ram" const time = new Date(); const now = { d: `${time.getFullYear()}-${time.getMonth()}-${time.getDate()}`, @@ -108,7 +109,7 @@ ClockFace.prototype.resume = function() { delete this._last; this.paused = false; if (this._resume) this._resume.apply(this); - this.tick(true); + this.tick(); }; /** diff --git a/modules/ClockFace.md b/modules/ClockFace.md index b2332c805..85482213c 100644 --- a/modules/ClockFace.md +++ b/modules/ClockFace.md @@ -208,7 +208,7 @@ let menu = { /*LANG*/"< Back": back, }; require("ClockFace_menu").addSettingsFile(menu, ".settings.json", [ - "showDate", "loadWidgets" + "showDate", "loadWidgets", "powerSave", ]); E.showMenu(menu); diff --git a/modules/ClockFace_menu.js b/modules/ClockFace_menu.js index f2267d9ca..a1dd76fee 100644 --- a/modules/ClockFace_menu.js +++ b/modules/ClockFace_menu.js @@ -11,12 +11,16 @@ exports.addItems = function(menu, callback, items) { 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": - // boolean options, which default to true if (value===undefined) value = true; + // fall through + case "powerSave": + // same for all boolean options: menu[label] = { value: !!value, onchange: v => callback(key, v), diff --git a/modules/Layout.js b/modules/Layout.js index fd5809a93..fdcf0ceae 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -10,15 +10,14 @@ */ -function Layout(layout, options) { +function Layout(layout, options) { this._l = this.l = layout; // Do we have >1 physical buttons? this.physBtns = (process.env.HWVERSION==2) ? 1 : 3; this.options = options || {}; this.lazy = this.options.lazy || false; - - var btnList; + let btnList; if (process.env.HWVERSION!=2) { // no touchscreen, find any buttons in 'layout' btnList = []; @@ -37,9 +36,9 @@ function Layout(layout, options) { if (this.options.btns) { var buttons = this.options.btns; - this.b = buttons; if (this.physBtns >= buttons.length) { // enough physical buttons + this.b = buttons; let btnHeight = Math.floor(Bangle.appRect.h / this.physBtns); if (this.physBtns > 2 && buttons.length==1) buttons.unshift({label:""}); // pad so if we have a button in the middle diff --git a/modules/Layout.min.js b/modules/Layout.min.js index 4523c547c..b5a924358 100644 --- a/modules/Layout.min.js +++ b/modules/Layout.min.js @@ -1,4 +1,4 @@ -function p(b,k){function d(h){h.id&&(f[h.id]=h);h.type||(h.type="");h.c&&h.c.forEach(d)}this._l=this.l=b;this.physBtns=2==process.env.HWVERSION?1:3;this.options=k||{};this.lazy=this.options.lazy||!1;if(2!=process.env.HWVERSION){var a=[];function h(m){"btn"==m.type&&a.push(m);m.c&&m.c.forEach(h)}h(b);a.length&&(this.physBtns=0,this.buttons=a,this.selectedButton=-1)}if(this.options.btns)if(this.b=b=this.options.btns,this.physBtns>=b.length){let h=Math.floor(Bangle.appRect.h/ +function p(b,k){function d(h){h.id&&(f[h.id]=h);h.type||(h.type="");h.c&&h.c.forEach(d)}this._l=this.l=b;this.physBtns=2==process.env.HWVERSION?1:3;this.options=k||{};this.lazy=this.options.lazy||!1;let a;if(2!=process.env.HWVERSION){a=[];function h(m){"btn"==m.type&&a.push(m);m.c&&m.c.forEach(h)}h(b);a.length&&(this.physBtns=0,this.buttons=a,this.selectedButton=-1)}if(this.options.btns)if(b=this.options.btns,this.physBtns>=b.length){this.b=b;let h=Math.floor(Bangle.appRect.h/ this.physBtns);for(2b.length;)b.push({label:""});this._l.width=g.getWidth()-8;this._l={type:"h",filly:1,c:[this._l,{type:"v",pad:1,filly:1,c:b.map(m=>(m.type="txt",m.font="6x8",m.height=h,m.r=1,m))}]}}else this._l.width=g.getWidth()-32,this._l={type:"h",c:[this._l,{type:"v",c:b.map(h=>(h.type="btn",h.filly=1,h.width=32,h.r=1,h))}]},a&&a.push.apply(a,this._l.c[1].c);this.setUI();var f=this;d(this._l);this.updateNeeded=!0}function r(b, k,d,a,f){var h=null==b.bgCol?f:g.toColor(b.bgCol);if(h!=f||"txt"==b.type||"btn"==b.type||"img"==b.type||"custom"==b.type){var m=b.c;delete b.c;var c="H"+E.CRC32(E.toJS(b));m&&(b.c=m);delete k[c]||((a[c]=[b.x,b.y,b.x+b.w-1,b.y+b.h-1]).bg=null==f?g.theme.bg:f,d&&(d.push(b),d=null))}if(b.c)for(var l of b.c)r(l,k,d,a,h)}p.prototype.setUI=function(){Bangle.setUI();let b;this.buttons&&(Bangle.setUI({mode:"updown",back:this.options.back},k=>{var d=this.selectedButton,a=this.buttons.length;if(void 0===k&& this.buttons[d])return this.buttons[d].cb();this.buttons[d]&&(delete this.buttons[d].selected,this.render(this.buttons[d]));d=(d+a+k)%a;this.buttons[d]&&(this.buttons[d].selected=1,this.render(this.buttons[d]));this.selectedButton=d}),b=!0);this.options.back&&!b&&Bangle.setUI({mode:"custom",back:this.options.back});if(this.b){function k(d,a){.75e+(0|n.filly),0);c||(h b.h-b.pad);k++;b.c&&b.c.forEach(d=>this.debug(d,k))};p.prototype.update=function(){function b(a){"ram";k[a.type](a);if(a.r&1){var f=a._w;a._w=a._h;a._h=f}a._w=0|Math.max(a._w+(a.pad<<1),0|a.width);a._h=0|Math.max(a._h+(a.pad<<1),0|a.height)}delete this.updateNeeded;var k={txt:function(a){a.font.endsWith("%")&&(a.font="Vector"+Math.round(g.getHeight()*a.font.slice(0,-1)/100));if(a.wrap)a._h=a._w=0;else{var f=g.setFont(a.font).stringMetrics(a.label);a._w=f.width;a._h=f.height}},btn:function(a){a.font&& a.font.endsWith("%")&&(a.font="Vector"+Math.round(g.getHeight()*a.font.slice(0,-1)/100));var f=a.src?g.imageMetrics("function"==typeof a.src?a.src():a.src):g.setFont(a.font||"6x8:2").stringMetrics(a.label);a._h=16+f.height;a._w=20+f.width},img:function(a){var f=g.imageMetrics("function"==typeof a.src?a.src():a.src),h=a.scale||1;a._w=f.width*h;a._h=f.height*h},"":function(a){a._w=0;a._h=0},custom:function(a){a._w=0;a._h=0},h:function(a){a.c.forEach(b);a._h=a.c.reduce((f,h)=>Math.max(f,h._h),0);a._w= a.c.reduce((f,h)=>f+h._w,0);null==a.fillx&&a.c.some(f=>f.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(f=>f.filly)&&(a.filly=1)},v:function(a){a.c.forEach(b);a._h=a.c.reduce((f,h)=>f+h._h,0);a._w=a.c.reduce((f,h)=>Math.max(f,h._w),0);null==a.fillx&&a.c.some(f=>f.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(f=>f.filly)&&(a.filly=1)}},d=this._l;b(d);d.fillx||d.filly?(d.w=Bangle.appRect.w,d.h=Bangle.appRect.h,d.x=Bangle.appRect.x,d.y=Bangle.appRect.y):(d.w=d._w,d.h=d._h,d.x=Bangle.appRect.w-d.w>>1,d.y= -Bangle.appRect.y+(Bangle.appRect.h-d.h>>1));this.layout(d)};p.prototype.clear=function(b){b||(b=this._l);g.reset();void 0!==b.bgCol&&g.setBgColor(b.bgCol);g.clearRect(b.x,b.y,b.x+b.w-1,b.y+b.h-1)};exports=p +Bangle.appRect.y+(Bangle.appRect.h-d.h>>1));this.layout(d)};p.prototype.clear=function(b){b||(b=this._l);g.reset();void 0!==b.bgCol&&g.setBgColor(b.bgCol);g.clearRect(b.x,b.y,b.x+b.w-1,b.y+b.h-1)};exports=p \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..878b45cb2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5074 @@ +{ + "name": "BangleApps", + "version": "0.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "BangleApps", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "acorn": "^7.2.0" + }, + "devDependencies": { + "eslint": "^8.14.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-plugin-import": "^2.26.0", + "npm-watch": "^0.11.0" + } + }, + "node_modules/@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, + "dependencies": { + "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" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@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, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@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 + }, + "node_modules/@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "dependencies": { + "defer-to-connect": "^1.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@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 + }, + "node_modules/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 + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/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, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/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 + }, + "node_modules/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, + "dependencies": { + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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 + }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dev": true, + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.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" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/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, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + } + }, + "node_modules/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/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 + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dev": true, + "dependencies": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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 + }, + "node_modules/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, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/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 + }, + "node_modules/defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "dev": true + }, + "node_modules/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, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha512-CEj8FwwNA4cVH2uFCoHUrmojhYh1vmCdOaneKJXwkeY1i9jnlslVo9dx+hQ5Hl9GnH/Bwy/IjxAyOePyPKYnzA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/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, + "dependencies": { + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/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, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "dependencies": { + "@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" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/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, + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/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, + "dependencies": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/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, + "dependencies": { + "debug": "^3.2.7", + "find-up": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/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, + "dependencies": { + "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" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/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, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/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, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/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, + "engines": { + "node": ">=10" + } + }, + "node_modules/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, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", + "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "dev": true, + "dependencies": { + "acorn": "^8.7.1", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree/node_modules/acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/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, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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 + }, + "node_modules/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 + }, + "node_modules/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 + }, + "node_modules/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, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/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, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + }, + "node_modules/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 + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/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, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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 + }, + "node_modules/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, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/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, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/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, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/global-dirs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", + "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "dev": true, + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globals": { + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/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, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/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 + }, + "node_modules/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, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/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, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/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, + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "dependencies": { + "ci-info": "^2.0.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/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, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-npm": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", + "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/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, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "node_modules/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, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/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, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", + "dev": true + }, + "node_modules/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 + }, + "node_modules/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 + }, + "node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.0" + } + }, + "node_modules/latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "dev": true, + "dependencies": { + "package-json": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/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, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/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 + }, + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/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 + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/nodemon": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.16.tgz", + "integrity": "sha512-zsrcaOfTWRuUzBn3P44RDliLlp263Z/76FPoHFr3cFFkOz0lTPAcIw8dCzfdVIx/t3AtDYCZRCDkoCojJqaG3w==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5", + "update-notifier": "^5.1.0" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/nodemon/node_modules/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, + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/nodemon/node_modules/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, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "dependencies": { + "nodemon": "^2.0.7", + "through2": "^4.0.2" + }, + "bin": { + "npm-watch": "cli.js" + } + }, + "node_modules/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, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/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, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/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, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/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, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/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, + "engines": { + "node": ">=4" + } + }, + "node_modules/package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "dev": true, + "dependencies": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/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, + "engines": { + "node": ">=4" + } + }, + "node_modules/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/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 + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/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, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/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 + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/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, + "engines": { + "node": ">=6" + } + }, + "node_modules/pupa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "dev": true, + "dependencies": { + "escape-goat": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/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, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/registry-auth-token": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", + "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", + "dev": true, + "dependencies": { + "rc": "^1.2.8" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "dev": true, + "dependencies": { + "rc": "^1.2.8" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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, + "engines": { + "node": ">=4" + } + }, + "node_modules/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", + "dev": true, + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/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, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dev": true, + "dependencies": { + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/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, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "engines": { + "node": ">=4" + } + }, + "node_modules/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, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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 + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/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, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/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, + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/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, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/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, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/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, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", + "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", + "dev": true, + "dependencies": { + "boxen": "^5.0.0", + "chalk": "^4.1.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.4.0", + "is-npm": "^5.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.1.0", + "pupa": "^2.1.1", + "semver": "^7.3.4", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", + "dev": true, + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/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 + }, + "node_modules/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 + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/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, + "dependencies": { + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/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 + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": 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 + }, + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "requires": { + "defer-to-connect": "^1.0.1" + } + }, + "@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, + "requires": {} + }, + "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-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "requires": { + "string-width": "^4.1.0" + } + }, + "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 + }, + "boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dev": true, + "requires": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + } + }, + "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" + } + }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + } + } + }, + "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 + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "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" + } + } + } + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "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 + }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dev": true, + "requires": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + } + }, + "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" + } + }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true + }, + "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" + } + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "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 + }, + "defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "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" + } + }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha512-CEj8FwwNA4cVH2uFCoHUrmojhYh1vmCdOaneKJXwkeY1i9jnlslVo9dx+hQ5Hl9GnH/Bwy/IjxAyOePyPKYnzA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "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-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "dev": true + }, + "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-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "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" + } + }, + "global-dirs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", + "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "dev": true, + "requires": { + "ini": "2.0.0" + } + }, + "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" + } + }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "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" + } + }, + "has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "dev": true + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "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" + } + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==", + "dev": true + }, + "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 + }, + "ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "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-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "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-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "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-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "requires": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + } + }, + "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-npm": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", + "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", + "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-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "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-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "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" + } + }, + "is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", + "dev": true + }, + "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-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", + "dev": true + }, + "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" + } + }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "requires": { + "json-buffer": "3.0.0" + } + }, + "latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "dev": true, + "requires": { + "package-json": "^6.3.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 + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "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.16", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.16.tgz", + "integrity": "sha512-zsrcaOfTWRuUzBn3P44RDliLlp263Z/76FPoHFr3cFFkOz0lTPAcIw8dCzfdVIx/t3AtDYCZRCDkoCojJqaG3w==", + "dev": true, + "requires": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5", + "update-notifier": "^5.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" + } + }, + "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 + }, + "normalize-url": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "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-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true + }, + "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 + }, + "package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "dev": true, + "requires": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + } + }, + "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 + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", + "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 + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "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 + }, + "pupa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "dev": true, + "requires": { + "escape-goat": "^2.0.0" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "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 + }, + "registry-auth-token": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", + "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", + "dev": true, + "requires": { + "rc": "^1.2.8" + } + }, + "registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "dev": true, + "requires": { + "rc": "^1.2.8" + } + }, + "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 + }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", + "dev": true, + "requires": { + "lowercase-keys": "^1.0.0" + } + }, + "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 + }, + "semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dev": true, + "requires": { + "semver": "^6.3.0" + } + }, + "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" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "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" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "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" + } + }, + "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-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true + }, + "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 + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "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 + }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "requires": { + "crypto-random-string": "^2.0.0" + } + }, + "update-notifier": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", + "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", + "dev": true, + "requires": { + "boxen": "^5.0.0", + "chalk": "^4.1.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.4.0", + "is-npm": "^5.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.1.0", + "pupa": "^2.1.1", + "semver": "^7.3.4", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "dependencies": { + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "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" + } + }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", + "dev": true, + "requires": { + "prepend-http": "^2.0.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" + } + }, + "widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "requires": { + "string-width": "^4.0.0" + } + }, + "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 + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "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 + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } +} 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 9bac72283..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 diff --git a/typescript/tsconfig.json b/tsconfig.json similarity index 77% rename from typescript/tsconfig.json rename to tsconfig.json index d36465a01..db3db1fc3 100644 --- a/typescript/tsconfig.json +++ b/tsconfig.json @@ -8,10 +8,12 @@ "noImplicitOverride": true, "noImplicitReturns": true, "noImplicitThis": true, + "noLib": true, "noUncheckedIndexedAccess": true, "noUnusedLocals": true, "noUnusedParameters": true, - "strict": true + "strict": true, + "typeRoots": ["./typescript/types"] }, - "include": ["../apps/**/*", "./**/*"], + "include": ["./**/*"] } diff --git a/typescript/README.md b/typescript/README.md index 13800aeec..7c1e21abd 100644 --- a/typescript/README.md +++ b/typescript/README.md @@ -1,13 +1,10 @@ -# BangleTS +# Bangle.ts -A generic project setup for compiling apps from Typescript to Bangle.js ready, readable Javascript. -It includes types for _some_ of the modules and globals that are exposed for apps to use. -The goal is to have types for everything, but that will take some time. Feel free to help out by contributing! +A generic project setup for compiling apps from Typescript to +Bangle.js-ready, readable JavaScript. -## Using the types - -All currently typed modules can be found in `/typescript/types.globals.d.ts`. -The typing is an ongoing process. If anything is still missing, you can add it! It will automatically be available in your TS files. +The types are now automatically generated by a script (see +[here](https://github.com/espruino/Espruino/blob/master/TYPESCRIPT.md). ## Compilation @@ -26,4 +23,4 @@ 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 BangleJS. +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 index 52be5f98a..8e498e884 100644 --- a/typescript/package-lock.json +++ b/typescript/package-lock.json @@ -7,10 +7,89 @@ "": { "name": "Bangle.ts", "version": "0.0.1", + "dependencies": { + "node-fetch": "^3.2.9" + }, "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.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.9.tgz", + "integrity": "sha512-/2lI+DBecVvVm9tDhjziTVjo2wmTsSxSk58saUYP0P/fRJ3xxtfMDY24+CKTkfm0Dlhyn3CSXNL0SoRiCZ8Rzg==", + "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", @@ -23,14 +102,64 @@ "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.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.9.tgz", + "integrity": "sha512-/2lI+DBecVvVm9tDhjziTVjo2wmTsSxSk58saUYP0P/fRJ3xxtfMDY24+CKTkfm0Dlhyn3CSXNL0SoRiCZ8Rzg==", + "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 index 8cd38ce63..070257cc0 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -1,13 +1,15 @@ { "name": "Bangle.ts", "description": "Bangle.js Typescript Project Setup and Types", - "author": "Sebastian Di Luzio (https://diluz.io)", + "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", - "build:types": "tsc ./types/globals.d.ts" + "build": "tsc" + }, + "dependencies": { + "node-fetch": "^3.2.9" } } diff --git a/typescript/types/globals.d.ts b/typescript/types/globals.d.ts deleted file mode 100644 index e82c3da3d..000000000 --- a/typescript/types/globals.d.ts +++ /dev/null @@ -1,184 +0,0 @@ -// TODO all of these globals (copied from eslintrc) need to be typed at some point -/* The typing status is listed on the left of the attribute, e.g.: -status "Attribute" - - // Methods and Fields at https://banglejs.com/reference - "Array": "readonly", - "ArrayBuffer": "readonly", - "ArrayBufferView": "readonly", -started "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", -started "Graphics": "readonly", -done "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", -started "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 -done "g": "readonly", -done "WIDGETS": "readonly" - */ - -// ambient JS definitions - -declare const require: ((module: 'heatshrink') => { - decompress: (compressedString: string) => string; -}) & // TODO add more - ((module: 'otherString') => {}); - -// ambient bangle.js definitions - -declare const Bangle: { - // functions - buzz: (duration?: number, intensity?: number) => Promise; - drawWidgets: () => void; - isCharging: () => boolean; - // events - on(event: 'charging', listener: (charging: boolean) => void): void; - // TODO add more -}; - -declare type Image = { - width: number; - height: number; - bpp?: number; - buffer: ArrayBuffer | string; - transparent?: number; - palette?: Uint16Array; -}; - -declare type GraphicsApi = { - reset: () => GraphicsApi; - flip: () => void; - setColor: (color: string) => GraphicsApi; // TODO we can most likely type color more usefully than this - drawImage: ( - image: string | Image | ArrayBuffer, - xOffset: number, - yOffset: number, - options?: { - rotate?: number; - scale?: number; - } - ) => GraphicsApi; - // TODO add more -}; - -declare const Graphics: GraphicsApi; -declare const g: GraphicsApi; - -declare type Widget = { - area: 'tr' | 'tl'; - width: number; - draw: (this: { x: number; y: number }) => void; -}; -declare const WIDGETS: { [key: string]: Widget }; diff --git a/typescript/types/main.d.ts b/typescript/types/main.d.ts new file mode 100644 index 000000000..3e14741ee --- /dev/null +++ b/typescript/types/main.d.ts @@ -0,0 +1,13021 @@ +// 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; + + /** + * Feature flag - If true, this Bangle.js firmware reads `setting.json` and + * modifies beep & buzz behaviour accordingly (the bootloader doesn't need to do + * it). + * @returns {boolean} + * @url http://www.espruino.com/Reference#l_Bangle_F_BEEPSET + */ + static F_BEEPSET: boolean; + + /** + * 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" +}