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 f5801f54a..f4588ac6f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ .htaccess node_modules -package-lock.json .DS_Store *.js.bak appdates.csv 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/2ofthemclk/ChangeLog b/apps/2ofthemclk/ChangeLog new file mode 100644 index 000000000..2286a7f70 --- /dev/null +++ b/apps/2ofthemclk/ChangeLog @@ -0,0 +1 @@ +0.01: New App! \ No newline at end of file diff --git a/apps/2ofthemclk/README.md b/apps/2ofthemclk/README.md new file mode 100644 index 000000000..7ac2cf779 --- /dev/null +++ b/apps/2ofthemclk/README.md @@ -0,0 +1,11 @@ +# two of them clock + +You can now wear teh memez on your wrist. + +![](screenshot.png) + +Also serves as an example of displaying seconds only when unlocked or charging and only refreshing on the minute otherwise. +Widgets not supported + +## Creator +- [Kilrah](https://github.com/kilrah) diff --git a/apps/2ofthemclk/app-icon.js b/apps/2ofthemclk/app-icon.js new file mode 100644 index 000000000..9bfb8a550 --- /dev/null +++ b/apps/2ofthemclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgZC/AH4ADkAPOgVJkgEBAQQAJiQRByEJgmQCJWSpMEAQMkyQJCpASHhAOBpAmBJJgjBCIUJCRg4CCIJxFMQ2SoARCkmACI0EBAJHCCIMLj4RFiUBskAgIXBEAU5A4P34CtCiEJsEJ/AHBCgOBAoQAEi0H////HciQsBwywICIXWzkG4A+BEY0gif46dt6/cgnIgkWnHfLIP/MoUWwHbpvC/kAjEEj0HNYQCCkEfGgP/64RB2EAifHLwMAjg1CCIMD/0H/0B8EAh+HgeAkARCE4IjC/4jBYIMPLIcIAYUPB4OBCIQABhu/AoShCHYIRBx6QBDgUw2//8OHPwcJ39//ILBCIU9LgMBSQgsBJAYRBkE/CIIABgRHD3wRFkk/2zBDAYU//3b/oRB8ARBj6ABgEE7YREEYf+oMkSwINCyClCn//z//+4RBgMkgU3EgUcwFJgEeboOXCIP2EYJCDAAVJkkGWoIuBgf2EYQPDkECCIOGd4ffyEJkgFBAAcSoEkwQCBhw+BwQaByVAkGAKwIFBBANLkEQgAyBCIVIkBpBgmSBYOQoApBgcgiQRCAQIyCCgsSjIFBCIcgRgJNCCgQyBpAgDAQT2BCgIOBBAQUCCIpfBCIwCKP4QRNpCSDCLyJBCIbjBTwYRLboJ0BCI4QD")) diff --git a/apps/2ofthemclk/app.js b/apps/2ofthemclk/app.js new file mode 100644 index 000000000..78415fba7 --- /dev/null +++ b/apps/2ofthemclk/app.js @@ -0,0 +1,90 @@ +const img = require("heatshrink").decompress(atob("2GwgZC/ADEIAQMBgEQAgMChEEAoQA4wACEAHKDBAQYA9X/YACgQFGkBH1HAQFCwEIgkAAQVAH2GAAwtBQwcQgMAwQ/vGQI4CAQRHCwFAkACCQYTRFAEqzBAopHCgMEAQiSHAEsQAYT1ChBEEPoMQBAMQAoQRCX9BEDOgR6BHwkgwVBQw4aEIlKGCPoWCI4REBBAMEQYUCH856CNwJ9CO4YCBHwOSgCGBRIREBJQICBAAOAH0UQGQJ0CHYTCDAQOQpKABZAICCZAREBQccgIgJHBoJrBXIQFBIISDFwALBKwTEkd4RrBPobIBHAMkIgICBQYoODSoIfBRIYmEACjjBMoIgBwAsEF4YFDkmSAoUBBATgDDQIjEADSqBEwI4BgJxCPoUEiVIkmCpI7BoLCBpBTEC4MChAjEfy6hCL4J3EAQMJHwLCDBwJEBQYMkiQODI4JcBQAICDEwKMCRK4dBYoJrCHYMgyB3BHYWShKGCBweAJoLXDQAJiBZAQAVC4ZZBU4KADPQR3BQYLCByA+BBAWCJoabCUIR6BcwQIFaIQAPC4KABwC8CoC/DPQSDDoICBHwREBJQabBHwRHBNYSqDZYQAMCIQCBQYT7DAQKADQwVJAQY7BAoJEBHAJZCQwYmBEANANATXBI4IvBPpJQFiEAPoYCBWwg7CBYIFCAQIFGcAYCBX4ImBIgQaBBwLDPwCDCoMAEYS/BfwJECoJEDAQuCSQLOBBwJ9DQYY7BHwaDDABYRBQwIgBDQMSVoMSHwK5BIgKDDIIyACKwMEyDLChKGEZwckIgWCIhwXBYQY7BEwJHBHZICHSoSeCMogCCQYYOBYho+BTwQmBQYQvCQAUSIJyACZAKDCMobsDEANAH5hTDTYYmDF4ICBQaICCLgSeBEAKACJohKCRgJBIwCeBC4IdBEALsBQAYCCHx4XBLIRlBkkQRgRBBdgMEJoSDMgJEBKwRcCU4J9SAQYaCGoI4BAoUQVobFBJQL4BGoICBAA4UDL4QsCiRBVAQjdBiS8CAQMShLRDfAUQH44+BC4sgyAjBHzC/CGoKnCySqCagJuCiEIQYNAQZIRCyAXBQALFXQxBHEaIQIBQYZNBH4sSQYVAKwIgBIgQ+ZMQYFBHwMBVQiGCGoILBIAsEgBNBTwWCZASAdMQkSVQaDBKAOCXIVBZALLBIIY+BwBWBQYbCfQYbCBEwRHBRIRxBQwMCQgiYDCIOShBcCQbxEBEYJHChIIBAQMQQYK5CwFAH4JHBiCYCQYg+eUgaDBAQYLCeoR6BAQUBQYcCQYJNCCgRBgQAZrBBAZ0BYoUQAQMAQYaJBKwLaDDQgCdQA6MCBYI4BiEJIgKDCYoVAHwIOBCgRBggTvChLCDRIMEGQTFBAQKDCgAOBhI+kyVIj4mBgkCRgzFGgRHBQYRKBKAJcCIkP/+UIJpB3BJoMQAoOQoBBBgCYDQclJgF+YQZEIkkQKAKDBgGAQAKSBQYNBIMfgh0BHY2SXgI1BQANIgjLCQYKPBLJZBcgPBggIDoI+BHYL4CgUBBAI/BwDIBKYUSIMsAnApFfYQABHwOQoEEiACCZYKPBQctIGwSDEfYIAEX4L7BiQLCQYTFmpEDwAuBgSAByVAIIoABhMAYoKDBH0xBHGoJ6BABMkYoJTBAoTFqgf+H5QABkGQYQOAQdNPGQUcvxBMgGAoMEiVBkmQExYOBILkfwP4jhAKgmShEEYRxNBCIJEBiVJkBKSo4zDv//8CDLFgTdBkmCExWCC4gFBIIM5IK8fwDHMEx0EDBEB/5BQoED+EAgbFBQZIjBAYK2CVozRBAoMSLRUPDoJBPvEAvw1FEZJNBgI4DAQa2CAoKeKgf+g/kIJ84//+IIX4AYMHgfx4Ech0HjgqFiUJDol/8AFBkANBUJDsC8ECIJuT/H/MQ0H8f+n//x/AFQwmFyE4j4FBL4PHKwwAB/gmBJoNBTAYCIyP4CgJBG//wn/j+CvIgLFFgFwIIMAnAUHHwU4AQMIQZmf/5BIIgjyJII0OAQMPTA8B/4CBBYjILv//cZAIBMQRBNyAHCn41B/AXEMQMP/AcHhJBIk//KwIAJQwRBRKwN/4BBC8Fx4Hj/5BBJoJBNpEDCgQAWII8AMgP4j+PAgIADYQMPOIsBQZEfwE4IMEfHooAD/EDwE/dIqDI/g4QEYJBLg//+ARBIJKDBAA8EII1HcAZBN/kBEwxBFMQSDKDQSzEuACBIIySDIJsHgEOQZkcIJeOn4XCGQhHBiRBGOgZALuAaBIJeSg4vCSoJBHwP48F/45xDvxBKDoKCMTwQAHEAeRaIccIIuP/5rBg///x0DgeB44EBIIpKBv//8EPfAwAGQZeTDQN/DoU///x44+Bn6tCj/+cwJBCwAFCIImRQAMPIIIaBH5UHgf+IJf4jggDg59C8EOAoKhBj47DEwYCBhIgE/4RBn4gEIJKnBIJkfXwL4C/kBJQJBB8fwCwSJDAAK8BIIrmBIIIdBBgP/DQYAGBZDmIF4XgIIMD+E4XgIKBSQJuFHAMBIIn4/4UBAQf/45oCAAb1BVQRBJyCtCNwQCBuE48eABAQgCFYJHCAAMcgBBEyE/OIUfWwIVBLIxiBWwOOYpZZBhwJBgPAgF+JQU/IIopDMoK8CEAeAagUHgf/4BBCGw0B/5BNDAIODHwN+AgNxLgI1B8CDJUgoLDMoZ6BGwxoBj6VBIJORLYXAfwMHHAPAjgjBHYMHB4RBFEAwLFh6kBgJBDZAhBSNwMH/l/HwP/QYKtEIIk/KwIgEiE4BYM4YoP48DXBEwM/8Y7Dn//IJ74BhxBBh4IDAAPHHwIgBIIUB47RCgiDDHwIvCUIIvCL4P/YoqJEIJn/+I+FAAPwZwMBQwK5EIIxxCx0AU4Q0HQYjmLyAOBIJXgZwILBZwIgEBYJBDkGAg68BgDFBIIMB/BBIQZhBEbod/IIZfDAARBEgYFBkCDDg6/EHwMBYQhHDeQJBLwYwC4ED/wMBh4ICEwYOCIIg4DQYYvCa4JfB/CGBUIQmCIIU4IJmBDAP+O4SkCIIhHDIIs4IJH/xyDCMoP/46YD+IpBQZ2AHwPxX5H4AQofHIIcEIIQgBHwKGCHYQABuBZCn6MDII4gBIIahC//wC4KhBJYZBBMQomBgIgCQYMAUgQPDgLIBTYbgGQZJBCx40BRIQmCZAYAEFIyDDEALCC/8cgLmCQYhBQBwRBDHwLIHAAakGh0BIIQHCIIX/wLaDI4IFD8FwSQhBGpBBC8ZBB/kOHYSkCIIccv4CBDokcuADBQYjjDMQP8QY4IBI4ZBHSQZiEfwaDD+BfCFgIgHkCDEgEPIIUAbRDOKIIOQAofjHoWOAwJ6BYov8uLFHAALFCQYL1Bh/4L4Uf+A7Hv5BPL4UAv0HfwcBAgQADgEONgxBCAgI7BUIYADLIrjFIIyYDnEfQALpDPoStCNYTODcxCDBeohBFboKPBAQLpBIJRnD/ED/wUB+IgDjkOn7RDQYTwHIIQgC8FwIIOP45BEgCGBFgJ3DIJQoDDYPwC4OPHAIICJoStHIIsgCgPgagMfGoV/Rgf8v4eBIKK8Cx/AHAUD+E/JQM4BAXjU4yDFC4LXCQwIQEcYRfIB4cgA4pWCEYImCIIKtBwBEBJQaDKgE4GoPHGQ6hDIBIABIIk/gfxfYl+BQNx4DIDQZNJII6hBwLCDUghALQY3jGgZZCL4OPEwKPEjggGhJBEn4gBHwaDFDQ4gGIIcOYooAB+ChD/gLEj+AIJVwa4SbBACpfCIIkPEQLIEAA/HVQ7FDkkOQYP+n/gEwRBZcYI+LR4ofEgRBDkBBB8EB//gHR1wAomCIIgODX4YAJR4TFGQYihB/hBBYipiCA4hfBnBALF4SDCOgcSQYhBCAQPj+P4HRcfMQkEL4QOEOgPAgF/IJMDAYWAnDFIQYRQBg/jx4jBABUDIJAIEGIRxC8YmBIAnAAgaDBAAYgCAQSSEgeOXxn8AohBDyQHCHAYRFI4ILBPoMfx4PBwPAYpBlBn6kDABscAogdDyVIA4IgBMQICBn7sB444B45KBIgPxYoU/EIZBHa4JBEuA/HBAV+AwUBIIlJgg4C/yGD8F/YgjLFwP4IJTCB/4OEIJFxEALUDII0gh4wC/ACCEwIABX4MfIwgOCageCIImQnEcLgMcHxDCCv4vBWwIABhJBFkkBF4SGBGQI7Dd4JAGAAR6BgAgFyUHgBlCGQYAHBwMcAwYdFpIIBWwaADAAWOn4FD+ILFgEEIK8AnAEDgRBGQYLCD8AjC/w+BYoIAC/jXDAAXAQY9ADoZBMAAhBHgDjBAQSzBRIYAFeoQADGQKDHgH8gBcCMoI9KgbjCwRBGkBfBUgX8uEHj+BX4f8v0AJAxBJpBBBL4JBBQZ8SII0EgIsDAQUDX4fHRgMAj5BF/0AcwyDBAAQUBIJ5fHQYKhBO4j1BXgXwnEcNwRBHkAjGo62DIIPjMoLFJ+EBL45BD/+BHwfgZYLpCjkP/DFIEY8/GYYXBfYYAJIJMkBgLmBEAQ0DIITCGIIcBU42TX4kBMoIAMhJBLEAg0DJQM4j/wYoXHB4IFBQZGBIIgAOiA+HAQSbBj6eEIQfx/8AuEHRIUAhwOBIJGRIKcCIJWQg7FDX4wsBv5EBJouAU5E/KYYAOHxBBDn5BFgb7DgEcPoJNGIJMAIIsggFAHw0B4EEIJeSQYx9C/+OnAIE/BNCCgIjJHYL7FpCMGgP4HxSkD//+QY/x4AJDh7IBJoPgiQjKiAVCBAVIYhCDOgeAII4IFwPH/+P/DpMHYRQDIJDgIAQtATYx6BjgHEIIP4SoRlNI45BVpDyBAAf8boLgBX4IADg8f/0BHyBrDIA0CLJ5BEg4FDh/AEQrPB5JBbgBBPYocOgEfwAFDII3+HyICCDgsHgESCJFBBApBEg8cAgMgQY0H//yIKl4IQvgIJICGHYUAgPH/gXBglwQY0cdJ4CFo5fF8C/KIIy8Dg/kIIMn+CnHHyQCCC4IdFwTFPn+OC4U/GoUjII0AghBbnAdKoAIGxwaBv/4JQZBHHyYCCp/gd4fACJOQYo0kh//gfwgIICng/FgPBIK1AIIcAhIRKNY+R/EfwAXDn+AEIccuPJIK1INAIeBuBBJkGQYo4aBkECBAZBBnCEEHygmDgFx4BrBBxFBkkAIJACBiQFDkaDCh/AjgLEASsCgEgHAQLFyCDBYpCVIP4RBdyDCJOgNBgiDLAQkncwQABjg+XaJpNBfYMQQaH+H4MB4HgIMgsBiUJgGAQZ8kxxBBh0AghBjQAICBoDFBQZ8kx/Aj/+gKAjkmCoKABAQMAhAXPyZBB+DCkAQMJkkCgEgwSDQyVIkA+kpMEiEIgACBkCDRAVB9BgMEYYMAQwJB4QYNAQYUEgTLBQfGAQIQABwA+2AQNIkiDCgEIZYMIIO8JQIkBgjC3AQNBgkQIISDByBB3HAKDEoKA3AQOCgEgIAWChJB6hCDEhBB5iFAQf6DDgMAgSD7YYcEiVJkBB3kGAIIaDBYvMgH4cIkGCaIpBypBBEAAI7zAQtBH4kBgkSYocgIOdIQQrFBoIOEI4YCuoBAEoKDBpMEBwTLzQY5BBHeICFIAqDByAODkBBywRAEgUBgi/zAQmQoBBDiFAPosEIOaDFhI7zAQpAEgkSH26DIBw4JBhEEiFIkGQAQRBmpA/EAAVAkGAhEAwUAA==")); + +var battery = E.getBattery(); + +// Positions on screen +const timeX = 88, timeY = 52; +const dateX = 5, dateY = 180; +const battX = 172, battY = 175; + +// Draw on every second if unlocked or charging, minute otherwise, start at with seconds on load +var drawTimeout; +var drawInterval = 1000; + +// schedule a draw for the next interval +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, drawInterval - (Date.now() % drawInterval)); +} + +// Update display and timeout on lock/unlock and charge state change +Bangle.on('lock',on=>{ + draw(); +}); + +Bangle.on('charging',charging=>{ + draw(); +}); + +function draw() { + // work out how to display the current time + var d = new Date(); + var h = d.getHours(), m = d.getMinutes(); + var time = (" "+h).substr(-2) + ":" + ("0"+m).substr(-2); + var seconds = ("0"+d.getSeconds()).substr(-2); + + // g.clear(); // Unneeded if background image takes the whole screen + + // Draw background + g.drawImage(img); + g.setColor(1, 1, 1); + + // draw the current time + g.setFontAlign(1,1); // align right bottom + g.setFont("6x15",3); + g.drawString(time, timeX, timeY, false); + + // Draw battery % + g.setFont("6x15",1); + var battStr = ""; + if(Bangle.isCharging()) { + battStr = "+"; + } + g.drawString(battStr + battery + "%", battX, battY, false); + + // Draw date + g.setFontAlign(-1,1); // align left bottom + g.setFont("6x15",2); + var dateStr = require("locale").date(d)+" "; + g.drawString(dateStr, dateX, dateY, false); + + // draw the seconds only if unlocked, set next timeout + if(!Bangle.isLocked() || Bangle.isCharging()) { + drawInterval = 1000; + g.setFont("6x15",2); + g.drawString(seconds, timeX+2, timeY-4, false); + } + else + drawInterval = 60000; + + // Schedule next draw + queueDraw(); + // console.log("Draw " + time + ":" + seconds); +} + +function refreshBattery() { + battery = E.getBattery(); +} + +// Only update displayed battery level every minute as it fluctuates a lot +var batteryInterval = setInterval(refreshBattery, 60000); + +Bangle.setUI("clock"); +Bangle.setLocked(false); +// Clear the screen once, at startup +g.clear(); +// draw immediately at first +draw(); diff --git a/apps/2ofthemclk/app.png b/apps/2ofthemclk/app.png new file mode 100644 index 000000000..d304f27d9 Binary files /dev/null and b/apps/2ofthemclk/app.png differ diff --git a/apps/2ofthemclk/bg.png b/apps/2ofthemclk/bg.png new file mode 100644 index 000000000..5f65ca3c7 Binary files /dev/null and b/apps/2ofthemclk/bg.png differ diff --git a/apps/2ofthemclk/metadata.json b/apps/2ofthemclk/metadata.json new file mode 100644 index 000000000..fa02b3e2f --- /dev/null +++ b/apps/2ofthemclk/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "2ofthemclk", + "name": "two of them clock", + "version": "0.01", + "description": "You can now wear teh memez on your wrist.", + "readme": "README.md", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"2ofthemclk.app.js","url":"app.js"}, + {"name":"2ofthemclk.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/2ofthemclk/screenshot.png b/apps/2ofthemclk/screenshot.png new file mode 100644 index 000000000..b9a80a2c5 Binary files /dev/null and b/apps/2ofthemclk/screenshot.png differ diff --git a/apps/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 4edb72aa5..37820dce6 100644 --- a/apps/activityreminder/ChangeLog +++ b/apps/activityreminder/ChangeLog @@ -4,3 +4,5 @@ 0.04: Obey system quiet mode 0.05: Battery optimisation, add the pause option, bug fixes 0.06: Add a temperature threshold to detect (and not alert) if the BJS isn't worn. Better support for the peoples using the app at night +0.07: Fix bug on the cutting edge firmware +0.08: Use default Bangle formatter for booleans diff --git a/apps/activityreminder/app.js b/apps/activityreminder/app.js index f3d72976e..c2b626fb3 100644 --- a/apps/activityreminder/app.js +++ b/apps/activityreminder/app.js @@ -1,42 +1,46 @@ -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(); +(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(); + + 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); } - if (v == 2) { - activityreminder_data.dismissDate = new Date(); + + function run() { + if (activityreminder.mustAlert(activityreminder_data, activityreminder_settings)) { + drawAlert(); + } else { + eval(storage.read("activityreminder.settings.js"))(() => load()); + } } - 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()); - } -} - - -const activityreminder = require("activityreminder"); -const storage = require("Storage"); -g.clear(); -Bangle.loadWidgets(); -Bangle.drawWidgets(); -const activityreminder_settings = activityreminder.loadSettings(); -const activityreminder_data = activityreminder.loadData(); -run(); + g.clear(); + Bangle.loadWidgets(); + Bangle.drawWidgets(); + run(); + +})(); \ No newline at end of file diff --git a/apps/activityreminder/boot.js b/apps/activityreminder/boot.js index 4ae9548c2..f97cf274d 100644 --- a/apps/activityreminder/boot.js +++ b/apps/activityreminder/boot.js @@ -1,65 +1,70 @@ -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); - } -}); - -const activityreminder = require("activityreminder"); -const activityreminder_settings = activityreminder.loadSettings(); -if (activityreminder_settings.enabled) { - const activityreminder_data = activityreminder.loadData(); - if(activityreminder_data.firstLoad){ +(function () { + // load variable before defining functions cause it can trigger a ReferenceError + const activityreminder = require("activityreminder"); + const activityreminder_settings = activityreminder.loadSettings(); + let activityreminder_data = activityreminder.loadData(); + + if (activityreminder_data.firstLoad) { activityreminder_data.firstLoad = false; activityreminder.saveData(activityreminder_data); } - setInterval(run, 60000); - /* todo in a futur release - increase setInterval time to something that is still sensible (5 mins ?) - when we added a settimer - */ -} + 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 + */ + } +})(); diff --git a/apps/activityreminder/lib.js b/apps/activityreminder/lib.js index 08fffd5f4..704d35641 100644 --- a/apps/activityreminder/lib.js +++ b/apps/activityreminder/lib.js @@ -1,5 +1,3 @@ -const storage = require("Storage"); - exports.loadSettings = function () { return Object.assign({ enabled: true, @@ -10,20 +8,20 @@ exports.loadSettings = function () { pauseDelayMin: 120, minSteps: 50, tempThreshold: 27 - }, storage.readJSON("activityreminder.s.json", true) || {}); + }, require("Storage").readJSON("activityreminder.s.json", true) || {}); }; exports.writeSettings = function (settings) { - storage.writeJSON("activityreminder.s.json", settings); + require("Storage").writeJSON("activityreminder.s.json", settings); }; exports.saveData = function (data) { - storage.writeJSON("activityreminder.data.json", data); + require("Storage").writeJSON("activityreminder.data.json", data); }; exports.loadData = function () { let health = Bangle.getHealthStatus("day"); - const data = Object.assign({ + let data = Object.assign({ firstLoad: true, stepsDate: new Date(), stepsOnDate: health.steps, @@ -31,7 +29,7 @@ exports.loadData = function () { dismissDate: new Date(1970), pauseDate: new Date(1970), }, - storage.readJSON("activityreminder.data.json") || {}); + require("Storage").readJSON("activityreminder.data.json") || {}); if(typeof(data.stepsDate) == "string") data.stepsDate = new Date(data.stepsDate); diff --git a/apps/activityreminder/metadata.json b/apps/activityreminder/metadata.json index 752c6c101..75ebf80b2 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.06", + "version":"0.08", "icon": "app.png", "type": "app", "tags": "tool,activity", diff --git a/apps/activityreminder/settings.js b/apps/activityreminder/settings.js index f25697de0..de490b796 100644 --- a/apps/activityreminder/settings.js +++ b/apps/activityreminder/settings.js @@ -1,85 +1,80 @@ (function (back) { - // Load settings - const activityreminder = require("activityreminder"); - const 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); - } - } - }); + // Show the menu + E.showMenu({ + "": { "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); + } + } + }); }) diff --git a/apps/advcasio/ChangeLog b/apps/advcasio/ChangeLog new file mode 100644 index 000000000..7189e4650 --- /dev/null +++ b/apps/advcasio/ChangeLog @@ -0,0 +1,2 @@ +0.01: AdvCasio first version +0.02: Remove un-needed fonts to improve memory usage 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..f134ce23f --- /dev/null +++ b/apps/advcasio/app.js @@ -0,0 +1,303 @@ +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; + } +}); + + +// Load widgets, but don't show them +Bangle.loadWidgets(); +Bangle.setUI("clock"); + +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..bcc47fc79 --- /dev/null +++ b/apps/advcasio/metadata.json @@ -0,0 +1,25 @@ +{ "id": "advcasio", + "name": "Advanced Casio Clock", + "shortName":"advcasio", + "version":"0.02", + "description": "An over-engineered clock inspired by Casio watches. It has a 4 days weather, a timer using swipe and a scratchpad. Can be updated using a dedicated webapp.", + "icon": "app.png", + "tags": "clock", + "type": "clock", + "screenshots": [ + { "url": "screenshot-clock-1.jpg" }, + { "url": "screenshot-clock-2.jpg" }, + { "url": "screenshot-clock-3.jpg" }, + { "url": "screenshot-webapp.jpg" } + ], + "supports" : ["BANGLEJS", "BANGLEJS2"], + "readme": "README.md", + "allow_emulator":true, + "storage": [ + {"name":"advcasio.app.js","url":"app.js"}, + {"name":"advcasio.img","url":"app-icon.js","evaluate":true} + ], + "data": [ + { "name": "advcasio.data.json", "url": "data.json", "storageFile": true } + ] +} diff --git a/apps/advcasio/screenshot-clock-1.jpg b/apps/advcasio/screenshot-clock-1.jpg new file mode 100644 index 000000000..7f6f042c9 Binary files /dev/null and b/apps/advcasio/screenshot-clock-1.jpg differ diff --git a/apps/advcasio/screenshot-clock-2.jpg b/apps/advcasio/screenshot-clock-2.jpg new file mode 100644 index 000000000..b5f1e38af Binary files /dev/null and b/apps/advcasio/screenshot-clock-2.jpg differ diff --git a/apps/advcasio/screenshot-clock-3.jpg b/apps/advcasio/screenshot-clock-3.jpg new file mode 100644 index 000000000..59389eb31 Binary files /dev/null and b/apps/advcasio/screenshot-clock-3.jpg differ diff --git a/apps/advcasio/screenshot-webapp.jpg b/apps/advcasio/screenshot-webapp.jpg new file mode 100644 index 000000000..d67bdba91 Binary files /dev/null and b/apps/advcasio/screenshot-webapp.jpg differ diff --git a/apps/agenda/ChangeLog b/apps/agenda/ChangeLog new file mode 100644 index 000000000..048595af1 --- /dev/null +++ b/apps/agenda/ChangeLog @@ -0,0 +1,3 @@ +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 diff --git a/apps/agenda/README.md b/apps/agenda/README.md new file mode 100644 index 000000000..a546e0a89 --- /dev/null +++ b/apps/agenda/README.md @@ -0,0 +1,3 @@ +# Agenda + +Basic agenda reading the events synchronised from GadgetBridge diff --git a/apps/agenda/agenda-icon.js b/apps/agenda/agenda-icon.js new file mode 100644 index 000000000..891543955 --- /dev/null +++ b/apps/agenda/agenda-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwg1yhGIxAPMBwIPFhH//GAC5n/C4oHBC5/IGwoXBHQQAKC4OIFAWOxHv9GO9wAKI4XoC4foEIIWLC4IABC4gIBFxnuE4IqBC4gARC4ZzNAAwXaxe7ACO4C625C4m4xIJBzAeCxGbCAOIFgQOBC4pOBxe4AYIPBAYQKCAYYXE3GL/ADBx/oxb3BC4X+xG4xwOBC4uP/YDB54MBf4Po3eM/4XBx/+C4pTBGIIkBLgOYAYIvB9GJBwI6BL45zCL4aCCL4h3GU64ALdYS1CI55bBAAgXFO4mMO4QDBDIO/////YxBU53IxIVB/GfDAWYa5wtC/GPAYWIL4wXBL4oSBC4jcBC4m4QIWYSwWIIQIAG/CnMMAIAC/JLCMIIvMIwZHFJAJfLC5yPHAYIRDAoy/KCIi7BMon4d4+Od4IXBxAZBEQLtB/+YxIXDL4SLCL4WPzAXCNgRFBLIKnKLIrcEI4gXNAAp3CxGZAAzCBC5KnCKAIAICxBlBC4IAJxG/C4/4wAXLhBgD/IcD3AXMGAIqDDgRGNGAoXDFxxhEI4W4FxwwCaoYWBFx4YDAAQWRAEQ")) diff --git a/apps/agenda/agenda.js b/apps/agenda/agenda.js new file mode 100644 index 000000000..c600ef7f6 --- /dev/null +++ b/apps/agenda/agenda.js @@ -0,0 +1,132 @@ +/* CALENDAR is a list of: + {id:int, + type, + timestamp, + durationInSeconds, + title, + description, + location, + allDay: bool, + } +*/ + +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +var FILE = "android.calendar.json"; + +var Locale = require("locale"); + +var fontSmall = "6x8"; +var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2"; +var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2"; +var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4"; + +//FIXME maybe write the end from GB already? Not durationInSeconds here (or do while receiving?) +var CALENDAR = require("Storage").readJSON("android.calendar.json",true)||[]; +var settings = require("Storage").readJSON("agenda.settings.json",true)||{}; + +CALENDAR=CALENDAR.sort((a,b)=>a.timestamp - b.timestamp) + +function getDate(timestamp) { + return new Date(timestamp*1000); +} +function formatDateLong(date, includeDay) { + if(includeDay) + return Locale.date(date)+" "+Locale.time(date,1); + return Locale.time(date,1); +} +function formatDateShort(date) { + return Locale.date(date).replace(/\d\d\d\d/,"")+Locale.time(date,1); +} + +var lines = []; +function showEvent(ev) { + var bodyFont = fontBig; + if(!ev) return; + g.setFont(bodyFont); + //var lines = []; + if (ev.title) lines = g.wrapString(ev.title, g.getWidth()-10) + var titleCnt = lines.length; + var start = getDate(ev.timestamp); + var end = getDate((+ev.timestamp) + (+ev.durationInSeconds)); + var includeDay = true; + if (titleCnt) lines.push(""); // add blank line after title + if(start.getDay() == end.getDay() && start.getMonth() == end.getMonth()) + includeDay = false; + if(includeDay) { + lines = lines.concat( + /*LANG*/"Start:", + g.wrapString(formatDateLong(start, includeDay), g.getWidth()-10), + /*LANG*/"End:", + g.wrapString(formatDateLong(end, includeDay), 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)); + } + if(ev.location) + lines = lines.concat(/*LANG*/"Location"+": ", g.wrapString(ev.location, g.getWidth()-10)); + if(ev.description) + lines = lines.concat("",g.wrapString(ev.description, g.getWidth()-10)); + lines = lines.concat(["",/*LANG*/"< Back"]); + E.showScroller({ + h : g.getFontHeight(), // height of each menu item in pixels + c : lines.length, // number of menu items + // a function to draw a menu item + draw : function(idx, r) { + // FIXME: in 2v13 onwards, clearRect(r) will work fine. There's a bug in 2v12 + g.setBgColor(idx=lines.length-2) + showList(); + }, + back : () => showList() + }); +} + +function showList() { + //it might take time for GB to delete old events, decide whether to show them grayed out or hide entirely + if(!settings.pastEvents) { + let now = new Date(); + //TODO add threshold here? + CALENDAR = CALENDAR.filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000); + } + if(CALENDAR.length == 0) { + E.showMessage("No events"); + return; + } + E.showScroller({ + h : 52, + c : Math.max(CALENDAR.length,3), // workaround for 2v10.219 firmware (min 3 not needed for 2v11) + draw : function(idx, r) {"ram" + var ev = CALENDAR[idx]; + g.setColor(g.theme.fg); + g.clearRect(r.x,r.y,r.x+r.w, r.y+r.h); + if (!ev) return; + var isPast = false; + var x = r.x+2, title = ev.title; + var body = formatDateShort(getDate(ev.timestamp))+"\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) { + g.setFontAlign(-1,-1).setFont(fontMedium).setColor(isPast ? "#888" : g.theme.fg); + var l = g.wrapString(body, r.w-(x+14)); + if (l.length>3) { + l = l.slice(0,3); + l[l.length-1]+="..."; + } + g.drawString(l.join("\n"), x+10,r.y+20); + } + g.setColor("#888").fillRect(r.x,r.y+r.h-1,r.x+r.w-1,r.y+r.h-1); // dividing line between items + }, + select : idx => showEvent(CALENDAR[idx]), + back : () => load() + }); +} +showList(); diff --git a/apps/agenda/agenda.png b/apps/agenda/agenda.png new file mode 100644 index 000000000..c850b0e5d Binary files /dev/null and b/apps/agenda/agenda.png differ diff --git a/apps/agenda/metadata.json b/apps/agenda/metadata.json new file mode 100644 index 000000000..b057d37e0 --- /dev/null +++ b/apps/agenda/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "agenda", + "name": "Agenda", + "version": "0.03", + "description": "Simple agenda", + "icon": "agenda.png", + "screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}], + "tags": "agenda", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"agenda.app.js","url":"agenda.js"}, + {"name":"agenda.settings.js","url":"settings.js"}, + {"name":"agenda.img","url":"agenda-icon.js","evaluate":true} + ], + "data": [{"name":"agenda.settings.json"}] +} diff --git a/apps/agenda/screenshot_agenda_event1.png b/apps/agenda/screenshot_agenda_event1.png new file mode 100644 index 000000000..581da286b Binary files /dev/null and b/apps/agenda/screenshot_agenda_event1.png differ diff --git a/apps/agenda/screenshot_agenda_event2.png b/apps/agenda/screenshot_agenda_event2.png new file mode 100644 index 000000000..f5edcaae8 Binary files /dev/null and b/apps/agenda/screenshot_agenda_event2.png differ diff --git a/apps/agenda/screenshot_agenda_overview.png b/apps/agenda/screenshot_agenda_overview.png new file mode 100644 index 000000000..a2030d05f Binary files /dev/null and b/apps/agenda/screenshot_agenda_overview.png differ diff --git a/apps/agenda/settings.js b/apps/agenda/settings.js new file mode 100644 index 000000000..4220fcb63 --- /dev/null +++ b/apps/agenda/settings.js @@ -0,0 +1,48 @@ +(function(back) { + function gbSend(message) { + Bluetooth.println(""); + Bluetooth.println(JSON.stringify(message)); + } + var settings = require("Storage").readJSON("agenda.settings.json",1)||{}; + function updateSettings() { + require("Storage").writeJSON("agenda.settings.json", settings); + } + var CALENDAR = require("Storage").readJSON("android.calendar.json",true)||[]; + var mainmenu = { + "" : { "title" : "Agenda" }, + "< Back" : back, + /*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?/*LANG*/"Yes":/*LANG*/"No" }, + /*LANG*/"Force calendar sync" : () => { + if(NRF.getSecurityStatus().connected) { + E.showPrompt(/*LANG*/"Do you want to also clear the internal database first?", { + buttons: {/*LANG*/"Yes": 1, /*LANG*/"No": 2, /*LANG*/"Cancel": 3} + }).then((v)=>{ + switch(v) { + case 1: + require("Storage").writeJSON("android.calendar.json",[]); + CALENDAR = []; + /* falls through */ + case 2: + gbSend({t:"force_calendar_sync", ids: CALENDAR.map(e=>e.id)}); + E.showAlert(/*LANG*/"Request sent to the phone").then(()=>E.showMenu(mainmenu)); + break; + case 3: + default: + E.showMenu(mainmenu); + return; + } + }); + } else { + E.showAlert(/*LANG*/"You are not connected").then(()=>E.showMenu(mainmenu)); + } + }, + /*LANG*/"Show past events" : { + value : !!settings.pastEvents, + onchange: v => { + settings.pastEvents = v; + updateSettings(); + } + }, + }; + E.showMenu(mainmenu); +}) diff --git a/apps/agpsdata/ChangeLog b/apps/agpsdata/ChangeLog new file mode 100644 index 000000000..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 00187fa7c..52ee8bf9c 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -30,3 +30,8 @@ 0.28: Fix bug with alarms not firing when configured to fire only once 0.29: Fix wrong 'dow' handling in new timer if first day of week is Monday 0.30: Fix "Enable All" +0.31: Add seconds to timers +0.32: Fix wrong hidden filter + Add option for auto-delete a timer after it expires +0.33: Allow hiding timers&alarms + diff --git a/apps/alarm/app.js b/apps/alarm/app.js index 2aff43de2..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() }; @@ -268,11 +272,28 @@ function showEditTimerMenu(selectedTimer, timerIndex) { wrap: true, onchange: v => time.m = v }, + /*LANG*/"Seconds": { + value: time.s, + min: 0, + max: 59, + step: 1, + wrap: true, + onchange: v => time.s = v + }, /*LANG*/"Enabled": { value: timer.on, onchange: v => timer.on = v }, + /*LANG*/"Delete After Expiration": { + value: timer.del, + onchange: v => timer.del = v + }, + /*LANG*/"Hidden": { + value: timer.hidden || false, + onchange: v => timer.hidden = v + }, /*LANG*/"Vibrate": require("buzz_menu").pattern(timer.vibrate, v => timer.vibrate = v), + /*LANG*/"Cancel": () => showMainMenu() }; if (!isNew) { diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json index 3c17ee177..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.30", + "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 efd7e7e46..bc8e3032d 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -90,10 +90,81 @@ sched.setAlarms(alarms); sched.reload(); }, + //TODO perhaps move those in a library (like messages), used also for viewing events? + //simple package with events all together + "calendarevents" : function() { + require("Storage").writeJSON("android.calendar.json", event.events); + }, + //add and remove events based on activity on phone (pebble-like) + "calendar" : function() { + var cal = require("Storage").readJSON("android.calendar.json",true); + if (!cal || !Array.isArray(cal)) cal = []; + var i = cal.findIndex(e=>e.id==event.id); + if(i<0) + cal.push(event); + else + cal[i] = event; + require("Storage").writeJSON("android.calendar.json", cal); + }, + "calendar-" : function() { + var cal = require("Storage").readJSON("android.calendar.json",true); + //if any of those happen we are out of sync! + if (!cal || !Array.isArray(cal)) return; + cal = cal.filter(e=>e.id!=event.id); + require("Storage").writeJSON("android.calendar.json", cal); + }, + //triggered by GB, send all ids + "force_calendar_sync_start" : function() { + var cal = require("Storage").readJSON("android.calendar.json",true); + if (!cal || !Array.isArray(cal)) cal = []; + gbSend({t:"force_calendar_sync", ids: cal.map(e=>e.id)}); + }, + "http":function() { + //get the promise and call the promise resolve + if (Bangle.httpRequest === undefined) return; + var request=Bangle.httpRequest[event.id]; + if (request === undefined) return; //already timedout or wrong id + delete Bangle.httpRequest[event.id]; + clearTimeout(request.t); //t = timeout variable + if(event.err!==undefined) //if is error + request.j(event.err); //r = reJect function + else + request.r(event); //r = resolve function + } }; 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 bf37b8407..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.10", + "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", @@ -15,6 +15,6 @@ {"name":"android.img","url":"app-icon.js","evaluate":true}, {"name":"android.boot.js","url":"boot.js"} ], - "data": [{"name":"android.settings.json"}], + "data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}], "sortorder": -8 } diff --git a/apps/android/settings.js b/apps/android/settings.js index 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/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/barclock/ChangeLog b/apps/barclock/ChangeLog index 197a24738..a00ae9325 100644 --- a/apps/barclock/ChangeLog +++ b/apps/barclock/ChangeLog @@ -11,3 +11,5 @@ 0.11: Use ClockFace.is12Hour 0.12: Add settings to hide date,widgets 0.13: Add font setting +0.14: Use ClockFace_menu.addItems +0.15: Add Power saving option \ 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 0a0b73778..5b783dbda 100644 --- a/apps/barclock/metadata.json +++ b/apps/barclock/metadata.json @@ -1,7 +1,7 @@ { "id": "barclock", "name": "Bar Clock", - "version": "0.13", + "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 3e97688a1..7b88b7021 100644 --- a/apps/barclock/settings.js +++ b/apps/barclock/settings.js @@ -1,26 +1,30 @@ (function(back) { - let s = require('Storage').readJSON("barclock.settings.json", true) || {}; + let s = require("Storage").readJSON("barclock.settings.json", true) || {}; - function saver(key) { - return value => { - s[key] = value; - require('Storage').writeJSON("barclock.settings.json", s); - } + function save(key, value) { + s[key] = value; + require("Storage").writeJSON("barclock.settings.json", s); } const fonts = [/*LANG*/"Bitmap",/*LANG*/"Vector"]; - const menu = { + let menu = { "": {"title": /*LANG*/"Bar Clock"}, /*LANG*/"< Back": back, - /*LANG*/"Show date": require("ClockFace_menu").showDate(s.showDate, saver('showDate')), - /*LANG*/"Load widgets": require("ClockFace_menu").loadWidgets(s.loadWidgets, saver('loadWidgets')), /*LANG*/"Font": { value: s.font|0, - min:0,max:1,wrap:true, - format:v=>fonts[v], - onchange:saver('font'), + min: 0, max: 1, wrap: true, + format: v => fonts[v], + onchange: v => save("font", v), }, }; - + let items = { + showDate: s.showDate, + loadWidgets: s.loadWidgets, + }; + // Power saving for Bangle.js 1 doesn't make sense (no updates while screen is off anyway) + if (process.env.HWVERSION>1) { + items.powerSave = s.powerSave; + } + require("ClockFace_menu").addItems(menu, save, items); E.showMenu(menu); }); diff --git a/apps/bigdclock/ChangeLog b/apps/bigdclock/ChangeLog index 2b6fcc7cb..09cc978fb 100644 --- a/apps/bigdclock/ChangeLog +++ b/apps/bigdclock/ChangeLog @@ -1,3 +1,5 @@ 0.01: Initial version 0.02: setTimeout bug fix; no leading zero on date; lightmode; 12 hour format; cleanup 0.03: Internationalisation; bug fix - battery icon responds promptly to charging state +0.04: bug fix +0.05: proper fix for the race condition in queueDraw() diff --git a/apps/bigdclock/bigdclock.app.js b/apps/bigdclock/bigdclock.app.js index 7b26d4f17..c013c6188 100644 --- a/apps/bigdclock/bigdclock.app.js +++ b/apps/bigdclock/bigdclock.app.js @@ -12,12 +12,12 @@ Graphics.prototype.setFontOpenSans = function(scale) { var drawTimeout; -// schedule a draw for the next minute -function queueDraw() { +function queueDraw(millis_now) { if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = setTimeout(function () { + drawTimeout = undefined; draw(); - }, 60300 - (Date.now() % 60000)); // We aim for 300ms into the next minute to ensure we make it! + }, 60000 - (millis_now % 60000)); } function draw() { @@ -69,7 +69,7 @@ function draw() { // widget redraw Bangle.drawWidgets(); - queueDraw(); + queueDraw(date.getTime()); } Bangle.on('lcdPower', on => { diff --git a/apps/bigdclock/metadata.json b/apps/bigdclock/metadata.json index e80dd9a04..7359bcf20 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.03", + "version":"0.05", "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/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..1a204ec34 100644 --- a/apps/bwclk/ChangeLog +++ b/apps/bwclk/ChangeLog @@ -6,4 +6,10 @@ 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. \ No newline at end of file diff --git a/apps/bwclk/README.md b/apps/bwclk/README.md index f6a1c6522..8da9b928c 100644 --- a/apps/bwclk/README.md +++ b/apps/bwclk/README.md @@ -1,17 +1,45 @@ # 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* +- 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] -- Weather[Optional] -- HomeAssistant [Optional] + | | | | + Bpm -5min 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..f81e90e7c 100644 --- a/apps/bwclk/app.js +++ b/apps/bwclk/app.js @@ -1,10 +1,10 @@ -/* +/************ * Includes */ const locale = require('locale'); const storage = require('Storage'); -/* +/************ * Statics */ const SETTINGS_FILE = "bwclk.setting.json"; @@ -12,14 +12,15 @@ const TIMER_IDX = "bwclk"; 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,22 +29,10 @@ for (const key in saved_settings) { } -/* +/************ * Assets */ - // Manrope font -Graphics.prototype.setLargeFont = function(scale) { - // Actual height 48 (49 - 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='))), - 46, - atob("EhooGyUkJiUnISYnFQ=="), - 63+(scale<<8)+(1<<16) - ); - return this; -}; - Graphics.prototype.setXLargeFont = function(scale) { // Actual height 53 (55 - 3) this.setFontCustom( @@ -54,102 +43,225 @@ Graphics.prototype.setXLargeFont = function(scale) { ); }; + +Graphics.prototype.setLargeFont = function(scale) { + // Actual height 47 (48 - 2) + this.setFontCustom( + atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAD/AAAAAAAAA/wAAAAAAAAP8AAAAAAAAD/AAAAAAAAA/wAAAAAAAAP8AAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAD/AAAAAAAAP/wAAAAAAAf/8AAAAAAB///AAAAAAH///wAAAAAf///8AAAAB/////AAAAH////8AAAAP////wAAAA/////AAAAB////+AAAAA////4AAAAAP///gAAAAAD//+AAAAAAA//4AAAAAAAP/gAAAAAAAD/AAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///+AAAAAB////8AAAAB/////wAAAA/////+AAAA//////wAAAf/////+AAAH//////wAAD//////+AAB/+AAAf/gAAf+AAAA/8AAH/AAAAH/AAD/gAAAA/4AA/wAAAAH+AAP8AAAAB/gAD+AAAAAf4AA/gAAAAH+AAP4AAAAA/gAD+AAAAAf4AA/wAAAAH+AAP8AAAAB/gAD/AAAAA/4AA/4AAAAP+AAH/AAAAH/AAB/4AAAH/wAAP/wAAP/4AAD//////+AAAf//////AAAD//////gAAAf/////wAAAD/////4AAAAf////4AAAAB////4AAAAAB///gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAH/AAAAAAAAD/gAAAAAAAA/4AAAAAAAAf8AAAAAAAAH+AAAAAAAAD/gAAAAAAAB/wAAAAAAAAf8AAAAAAAAP///////AAD///////wAA///////8AAP///////AAD///////wAA///////8AAP///////AAD///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAB/AAAA/gAAA/wAAA/4AAAf8AAAf+AAAP/AAAP/gAAH/wAAH/4AAD/8AAD/+AAB//AAA//gAA//wAAf/AAAP/8AAH/AAAH//AAD/gAAD//wAA/wAAB//8AAP8AAA///AAD/AAAf+fwAA/gAAP/n8AAP4AAH/x/AAD+AAD/4fwAA/gAB/8H8AAP8AAf+B/AAD/AAP/AfwAA/4AH/gH8AAH/AH/wB/AAB/8H/4AfwAAP///8AH8AAD////AB/AAAf///gAfwAAD///wAH8AAAf//4AB/AAAD//4AAfwAAAP/8AAH8AAAAf4AAB/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAADgAAAfwAAAB+AAAH8AAAAfwAAB/AAAAH+AAAfwAAAB/wAAH8AAAA/+AAB/AAAAP/gAAfwA4AA/8AAH8AfgAH/AAB/AP8AA/4AAfwD/gAH+AAH8B/4AB/gAB/A/8AAf4AAfwf/AAD+AAH8P/wAA/gAB/H/8AAf4AAfz//gAH+AAH8//4AB/gAB/f//AA/4AAf/+/4Af8AAH//P/AP/AAB//j////gAAf/wf///4AAH/4H///8AAB/8A///+AAAf+AH///AAAH/AA///gAAB/gAD//wAAAfwAAP/wAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAH/wAAAAAAAH/8AAAAAAAH//AAAAAAAH//wAAAAAAH//8AAAAAAH///AAAAAAH///wAAAAAH///8AAAAAP//9/AAAAAP//8fwAAAAP//4H8AAAAP//4B/AAAAP//4AfwAAAP//4AH8AAAD//4AB/AAAA//4AAfwAAAP/4AAH8AAAD/wAAB/AAAA/wAAAfwAAAPwAH////AADwAB////wAAwAAf///8AAAAAH////AAAAAB////wAAAAAf///8AAAAAH////AAAAAA////wAAAAAAAfwAAAAAAAAH8AAAAAAAAB/AAAAAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAGAHwAAAB///gB+AAAH///8AfwAAB////AP+AAAf///wD/wAAH///+A/+AAB////gP/gAAf///4A/8AAH/8P8AH/AAB/AD+AA/4AAfwA/gAH+AAH8AfwAB/gAB/AH8AAf4AAfwB/AAH+AAH8AfwAB/gAB/AH8AAf4AAfwB/gAH+AAH8Af4AB/gAB/AH/AA/wAAfwB/4Af8AAH8AP/AP/AAB/AD////gAAfwAf///wAAH8AD///8AAB/AA///+AAAfwAH///AAAAAAA///gAAAAAAD//gAAAAAAAP/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///4AAAAAH////wAAAAH/////AAAAD/////4AAAB//////AAAA//////4AAAf//////AAAP//////4AAD/8D/w/+AAB/4B/wD/wAAf8A/wAf8AAP+AP4AD/gAD/AD+AAf4AA/wB/AAH+AAP4AfwAB/gAD+AH8AAf4AA/gB/AAH+AAP4AfwAB/gAD+AH+AAf4AA/wB/gAH+AAP8Af8AD/gAD/gH/gB/wAAf8A/8A/8AAH/AP///+AAB/gB////gAAPwAP///wAAB4AD///4AAAMAAf//8AAAAAAD//+AAAAAAAP/+AAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAAAH8AAAAAAAAB/AAAAAAAAAfwAAAAAAAAH8AAAAAAAAB/AAAAABwAAfwAAAAB8AAH8AAAAD/AAB/AAAAD/wAAfwAAAH/8AAH8AAAH//AAB/AAAP//wAAfwAAP//8AAH8AAf//+AAB/AAf//8AAAfwA///8AAAH8A///4AAAB/A///4AAAAfx///wAAAAH9///wAAAAB////gAAAAAf///gAAAAAH///AAAAAAB///AAAAAAAf/+AAAAAAAH/+AAAAAAAB/8AAAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAf/AAAAAP+Af/8AAAAP/4P//wAAAP//P//+AAAH//////wAAB//////8AAA///////gAAf//////8AAH////gP/AAD/wf/wA/wAA/4D/4AP+AAP8Af8AB/gAD/AH/AAf4AA/gA/wAH+AAP4AP4AA/gAD+AD/AAP4AA/gA/wAH+AAP8Af8AB/gAD/AH/AAf4AA/4D/4AP+AAP/B//AH/AAB////4D/wAAf//////8AAD//////+AAAf//////AAAH//////wAAA//8///4AAAD/+D//8AAAAP+Af/8AAAAAAAB/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/gAAAAAAAB//AAAAAAAB//8AAAAAAB///gAAgAAA///8AAcAAAf///gAPAAAH///8AH4AAD////AD/AAB/+H/4B/wAAf+Af+Af8AAP+AB/wD/gAD/gAf8Af4AA/wAD/AH+AAP8AA/wB/gAD+AAH8AP4AA/gAB/AD+AAP4AAfwB/gAD+AAH8Af4AA/wAD/AH+AAP8AA/gD/gAD/gAf4A/wAAf8AP8A/8AAH/gH/Af/AAA///////gAAP//////wAAB//////8AAAP/////+AAAB//////AAAAP/////AAAAA/////gAAAAD////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wA/wAAAAAP8AP8AAAAAD/AD/AAAAAA/wA/wAAAAAP8AP8AAAAAD/AD/AAAAAA/wA/wAAAAAP8AP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='), + 46, + atob("ExspGyUkJiQnISYnFQ=="), + 62+(scale<<8)+(1<<16) + ); + return this; +}; + + Graphics.prototype.setMediumFont = function(scale) { // Actual height 41 (42 - 2) this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAB/AAAAAAAP/AAAAAAD//AAAAAA///AAAAAP///AAAAB///8AAAAf///AAAAH///wAAAB///+AAAAH///gAAAAH//4AAAAAH/+AAAAAAH/wAAAAAAH8AAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAH////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gAAH+AAD+AAAD/AAH8AAAB/AAH4AAAA/gAH4AAAAfgAH4AAAAfgAPwAAAAfgAPwAAAAfgAPwAAAAfgAHwAAAAfgAH4AAAAfgAH4AAAA/gAH8AAAA/AAD+AAAD/AAD/gAAH/AAB/////+AAB/////8AAA/////4AAAf////wAAAH////gAAAB///+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAAfwAAAAAAA/gAAAAAAA/AAAAAAAB/AAAAAAAD+AAAAAAAD8AAAAAAAH8AAAAAAAH//////AAH//////AAH//////AAH//////AAH//////AAH//////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4AAA/AAAP4AAB/AAAf4AAD/AAA/4AAD/AAB/4AAH/AAD/4AAP/AAH/AAAf/AAH8AAA//AAH4AAB//AAP4AAD//AAPwAAH+/AAPwAAP8/AAPwAAf4/AAPwAA/4/AAPwAA/w/AAPwAB/g/AAPwAD/A/AAP4AH+A/AAH8AP8A/AAH/A/4A/AAD///wA/AAD///gA/AAB///AA/AAA//+AA/AAAP/8AA/AAAD/wAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAH4AAAHwAAH4AAAH4AAH4AAAH8AAH4AAAP+AAH4AAAH+AAH4A4AB/AAH4A+AA/AAH4B/AA/gAH4D/AAfgAH4H+AAfgAH4P+AAfgAH4f+AAfgAH4/+AAfgAH5/+AAfgAH5//AAfgAH7+/AA/gAH/8/gB/AAH/4f4H/AAH/wf//+AAH/gP//8AAH/AH//8AAH+AD//wAAH8AB//gAAD4AAf+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAAAAD/AAAAAAAP/AAAAAAB//AAAAAAH//AAAAAAf//AAAAAB///AAAAAH///AAAAAf/8/AAAAB//w/AAAAH/+A/AAAA//4A/AAAD//gA/AAAH/+AA/AAAH/4AA/AAAH/gAA/AAAH+AAA/AAAHwAAA/AAAHAAf///AAEAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAP/AHgAAH///AP4AAH///gP8AAH///gP8AAH///gP+AAH///gD/AAH/A/AB/AAH4A/AA/gAH4A+AAfgAH4B+AAfgAH4B+AAfgAH4B8AAfgAH4B8AAfgAH4B+AAfgAH4B+AAfgAH4B+AA/gAH4B/AA/AAH4A/gD/AAH4A/4H+AAH4Af//+AAH4AP//8AAH4AP//4AAHwAD//wAAAAAB//AAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAD////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gP4H+AAD/AfgD/AAH8A/AB/AAH8A/AA/gAH4B+AAfgAH4B+AAfgAPwB8AAfgAPwB8AAfgAPwB+AAfgAPwB+AAfgAH4B+AAfgAH4B/AA/gAH8B/AB/AAH+A/wD/AAD+A/8P+AAB8Af//+AAB4AP//8AAAwAH//4AAAAAD//gAAAAAA//AAAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAHAAPwAAAA/AAPwAAAD/AAPwAAAf/AAPwAAB//AAPwAAP//AAPwAA//8AAPwAH//wAAPwAf/+AAAPwB//4AAAPwP//AAAAPw//8AAAAP3//gAAAAP//+AAAAAP//wAAAAAP//AAAAAAP/4AAAAAAP/gAAAAAAP+AAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAH+A//gAAAf/h//4AAA//z//8AAB/////+AAD/////+AAD///+H/AAH+H/4B/AAH8B/wA/gAH4A/gAfgAH4A/gAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAH4A/gAfgAH4A/gAfgAH8B/wA/gAH/H/4B/AAD///+H/AAD/////+AAB/////+AAA//z//8AAAf/h//4AAAH+A//gAAAAAAH+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAD/8AAAAAAP/+AAAAAAf//AAcAAA///gA8AAB///wB+AAD/x/4B/AAD+AP4B/AAH8AH8A/gAH4AH8A/gAH4AD8AfgAP4AD8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAH4AD8AfgAH4AD4A/gAH8AH4B/AAD+APwD/AAD/g/wP+AAB/////+AAA/////8AAAf////4AAAP////wAAAH////AAAAA///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DxcjFyAfISAiHCAiEg=="), 54+(scale<<8)+(1<<16)); return this; }; + Graphics.prototype.setSmallFont = function(scale) { // Actual height 28 (27 - 0) - this.setFontCustom(atob("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=")) -}; -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==")) -}; +function imgLock(){ + return { + width : 16, height : 16, bpp : 1, + transparent : 0, + buffer : E.toArrayBuffer(atob("A8AH4A5wDDAYGBgYP/w//D/8Pnw+fD58Pnw//D/8P/w=")) + } +} -var imgBattery = { - width : 24, height : 24, bpp : 1, - transparent : 1, - buffer : require("heatshrink").decompress(atob("/4AN4EAg4TBgd///9oEAAQv8ARQRDDQQgCEwQ4OA")) -}; +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 imgBpm = { - width : 24, height : 24, bpp : 1, - transparent : 1, - buffer : require("heatshrink").decompress(atob("/4AOn4CD/wCCjgCCv/8jF/wGYgOA5MB//BC4PDAQnjAQPnAQgANA")) -}; +function imgBattery(){ + return { + width : 24, height : 24, bpp : 1, + transparent : 1, + buffer : require("heatshrink").decompress(atob("/4AN4EAg4TBgd///9oEAAQv8ARQRDDQQgCEwQ4OA")) + } +} -var imgTemperature = { - width : 24, height : 24, bpp : 1, - transparent : 1, - buffer : require("heatshrink").decompress(atob("//D///wICBjACBngCNkgCP/0kv/+s1//nDn/8wICEBAIOC/08v//IYJECA==")) -}; +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 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 imgBpm() { + return { + width : 24, height : 24, bpp : 1, + transparent : 1, + buffer : require("heatshrink").decompress(atob("/4AOn4CD/wCCjgCCv/8jF/wGYgOA5MB//BC4PDAQnjAQPnAQgANA")) + } +} -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 imgTemperature() { + return { + width : 24, height : 24, bpp : 1, + transparent : 1, + buffer : require("heatshrink").decompress(atob("//D///wICBjACBngCNkgCP/0kv/+s1//nDn/8wICEBAIOC/08v//IYJECA==")) + } +} -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 imgWeather(){ + return { + width : 24, height : 24, bpp : 1, + transparent : 0, + buffer : require("heatshrink").decompress(atob("AAcYAQ0MgEwAQUAngLB/8AgP/wACCgf/4Fz//OAQQICCIoaCEAQpGHA4ACA=")) + } +} -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 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")) + } +} -/* - * INFO ENTRIES - */ -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 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 getInfoEntry(){ - if(isAlarmEnabled()){ - return [getAlarmMinutes() + " min.", imgTimer, "left"] - } else if(Bangle.isCharging()){ - return [E.getBattery() + "%", imgCharging, "left"] - } else{ - return infoArray[settings.showInfo](); +function imgHomeAssistant() { + 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==")) } } +/************ + * 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()] }, + ] +] + /* + * Timer Menu + */ +try{ + require('sched'); + menu.push([ + function(){ + var text = isAlarmEnabled() ? getAlarmMinutes() + " min." : "Timer"; + return [text, imgTimer(), () => decreaseAlarm(), () => increaseAlarm(), null ] + }, + ]); +} catch(ex) { + // If sched is not installed, we hide this menu item +} + +/* + * 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; + return menu[settings.menuPosX][settings.menuPosY](); +} + + +/************ * 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 +276,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 +295,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,15 +304,16 @@ function getWeather(){ } return { - temp: "? °C", - hum: "-", - txt: "-", - wind: "? km/h", - wdir: "-", - wrose: "-" + temp: " ? ", + hum: " ? ", + txt: " ? ", + wind: " ? ", + wdir: " ? ", + wrose: " ? " }; } + function isAlarmEnabled(){ try{ var alarm = require('sched'); @@ -216,6 +328,7 @@ function isAlarmEnabled(){ return false; } + function getAlarmMinutes(){ if(!isAlarmEnabled()){ return -1; @@ -226,6 +339,7 @@ function getAlarmMinutes(){ return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000)); } + function increaseAlarm(){ try{ var minutes = isAlarmEnabled() ? getAlarmMinutes() : 0; @@ -237,6 +351,7 @@ function increaseAlarm(){ } catch(ex){ } } + function decreaseAlarm(){ try{ var minutes = getAlarmMinutes(); @@ -256,10 +371,9 @@ function decreaseAlarm(){ } -/* - * DRAW functions +/************ + * DRAW */ - function draw() { // Queue draw again queueDraw(); @@ -278,8 +392,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 +407,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,13 +436,13 @@ 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 menuName = menuEntry[0]; + var menuImg = menuEntry[1]; + var printImgLeft = settings.menuPosY != 0; // Show large or small time depending on info entry - if(infoStr == null){ + if(menuName == null){ if(settings.hideColon){ g.setXLargeFont(); } else { @@ -341,8 +454,8 @@ function drawTime(){ } g.drawString(timeStr, W/2, y); - // Draw info if set - if(infoStr == null){ + // Draw menu if set + if(menuName == null){ return; } @@ -350,29 +463,31 @@ function drawTime(){ g.setFontAlign(0,0); g.setSmallFont(); var imgWidth = 0; - if(infoImg !== undefined){ - imgWidth = infoImg.width; - var strWidth = g.stringWidth(infoStr); + if(menuImg !== undefined){ + imgWidth = 24.0; + var strWidth = g.stringWidth(menuName); + var scale = imgWidth / menuImg.width; g.drawImage( - infoImg, - W/2 + (printImgLeft ? -strWidth/2-2 : strWidth/2+2) - infoImg.width/2, - y - infoImg.height/2 + menuImg, + W/2 + (printImgLeft ? -strWidth/2-2 : strWidth/2+2) - parseInt(imgWidth/2), + y - parseInt(imgWidth/2), + { scale: scale } ); } - g.drawString(infoStr, printImgLeft ? W/2 + imgWidth/2 + 2 : W/2 - imgWidth/2 - 2, y+3); + g.drawString(menuName, 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 +525,110 @@ 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){ 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){ 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){ + Bangle.buzz(80, 0.6).then(()=>{ + try{ + menuEntry[4](); + setTimeout(()=>{ + Bangle.buzz(80, 0.6); + }, 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... + } }); @@ -472,6 +642,12 @@ 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(); + +// 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(); // Show launcher when middle button pressed diff --git a/apps/bwclk/metadata.json b/apps/bwclk/metadata.json index eba1449a6..c65317e5b 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.15", + "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..f9a9a7d3f 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/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 f00b3fa0a..419810021 100644 --- a/apps/cassioWatch/ChangeLog +++ b/apps/cassioWatch/ChangeLog @@ -7,4 +7,5 @@ 0.6: Add Settings Page 0.7: Update Rocket Sequences Scope to not use memory all time 0.8: Update Some Variable Scopes to not use memory until need -0.9: Remove ESLint spaces \ No newline at end of file +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 diff --git a/apps/cassioWatch/README.md b/apps/cassioWatch/README.md index 1342af8e6..aaeb3f122 100644 --- a/apps/cassioWatch/README.md +++ b/apps/cassioWatch/README.md @@ -6,5 +6,6 @@ Clock with Space Cassio Watch Style. It displays current temperature,day,steps,battery.heartbeat and weather. + **To-do**: -Integrate heartbeat and Weather, Align and change size of some elements. +Align and change size of some elements. diff --git a/apps/cassioWatch/app.js b/apps/cassioWatch/app.js index 93538ec50..6bbb9e823 100644 --- a/apps/cassioWatch/app.js +++ b/apps/cassioWatch/app.js @@ -1,11 +1,9 @@ +const storage = require('Storage'); + require("Font6x12").add(Graphics); require("Font8x12").add(Graphics); require("Font7x11Numeric7Seg").add(Graphics); -let ClockInterval; -let RocketInterval; -let BatteryInterval; - function bigThenSmall(big, small, x, y) { g.setFont("7x11Numeric7Seg", 2); g.drawString(big, x, y); @@ -14,16 +12,6 @@ function bigThenSmall(big, small, x, y) { g.drawString(small, x, y); } -function ClearIntervals(inoreclock) { - if (RocketInterval) clearInterval(RocketInterval); - if (BatteryInterval) clearInterval(BatteryInterval); - RocketInterval = undefined; - BatteryInterval = undefined; - if (inoreclock) return; - if (ClockInterval) clearInterval(ClockInterval); - ClockInterval = undefined; -} - function getBackgroundImage() { return require("heatshrink").decompress(atob("2GwwkGIf4AfgMRkUiiIHCiMRiAMDAwYCCBAYVDAHMv/4ACkBIBAgPxBgM/BYXyAoICBCowA5gRADKQUDKAYMCmYCBiBXBCo4A5J4MxiMSKQUf+YBBBgSiBgc/kBXBBAMyCoK2CK/btCiUhfAJLCkBkDiMQgBXDCoUvNAJX+AAU/+MB/8wAQIAC+cQK5hoDgIEBBIQFEAYIPHBIgBBAQQIDBwZXSKIMxgJaBgEjmZYCmBXLgLBBkkAgUhiMxBIM0iMSCoMRkZECkQJEichBINDiETAgISBiQTDK6MvJAXzVIQrBBYMCK5E/K4kwGIJXFgdAMgQQBiYiCDgU0HQSlCgMikIEBEAMTDYJXQ+UikYDBj6nCAAMTWoJ6BK4oVEK4c0oQ+BK4MjAgMDJoJXHNYJXHBwa0BohcDY4QAKgJQE+LzBNwJVBkQMEkBXBCoyvFJAVAKISaBiMiHQRIDkVBoSyCK5CvBAgavNDAJAC+cQn5DCgSpBl4MDgBXBgCsBCoYoMLAKREgIKDBJIdKK5oA/AH4A/AH4A/ADUBIH4APiAFEi1mAGUADrkRKwUGK2ZXes1gK2xXfD8A3/K/4AWgxX/ACtga2AwIHLkAgCwvJw6RcDgIABK+w4cK/I4dsEGP5BXtSAQ6BV/5XSG4RX/K6Y3fK+42CK/5XTGwcGK/5XSVwY5cK+o1DAAayYsAhDsCv4K7BTBK4YeYK7CyFVzJXFFIpXtVwYiYK/rmZKYYDDELJXXG4YiaK/Y0aKgQAEK+gkdKt5XGKzqv5GTpX6ETlgK4xWrKTyxKVthXmAGRX/K/5X/AH5X/K/4gBAH4A/AFz/uAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHNggEGHfEAgAEHKyQXVK0qTCAggbUK+6SDAApzXK/5BRDYZX3KxBBSYqxXngyvaV25XEd4ZCSsAcBAoRZ2dQZXBLwgaQCIYeCAGirCS4YGCDSJXCC6ZaodYICBZzSw4S4I+XDgSv4K4rzCK/47RAQTMaWHI9YV3TscV3aVagByBK3SwCSqyt8AAQ+XK/4A/AH4A/AH4A3gAA/AH4AuZbdggwc3ADpX/K/5XxsEAgA+XK/o8BgBX/K64/WK/4/XK/5X/K/5XvgBX/K64cYHrw4CSTFggCuXK4oDCEQJXYDS6ScDgg4CPKyRCAAZX0HAgBDK+LlYK4oeBAwZ9aK+lgAoQGBgyvzDIIDBK66sCG4JXYCwIBDK7ADCK+xZCHwJXzGoQ8BK7DpBAAaSXSgRXZO4okCK+IaXV4oABEILSWSYjRCHSo3BDSxXEAAIcBAISvyKawcIAYIGCK/4cUH4YlaHS0AHgI1XOg5YBPrY6WHgRXfAGRXDHzBX8VoJX/K68ADjRX6sBX/K/5X/K8wdcK/UAG7B0iKzZYbK/BWDAH4A/hWpzWhIf4ASgOpzIAB0EAhhH/AB8ZzGJ1WazMA4pH/AB+pxOZxOpzVMqA2ugUzmcgD7cKVYOqzGqpnRFw8ykchK8kviEBmQFBgMiFocSCAcSkUQAgMikRsHhWqxOq0Ut4mqBw0DC4IxBD4wpBHAQMCA4cCGJIAFj8hDIQuBkMTCwU/AYQJBiUxFoPxiIVDK4kyxUz4cxl+KK5MfDQXyD4UCmMSmAEBAQQHDgMTmIxHAAqpBmaqCFwMDEYZRBgEjCQQBB+USK5E/ns/0Uzwc6K48ykYkCK4IfCc4I4CK4QHEBAYAMiICBmYuDmQEBh8iAgRXCLISvJO4MqwcklEiK5CADV4oaBV4oHEK6Eve4JNCbwRfCiMTFoMDkMRSAJXCD49azWp0UqzWayJXIQwcAO4cCkMCFIJOCA4XxK6KPBkR6DTwYyBAwYPEAggfFzORpWK1OZyAOHJ4QfERAUSEgQxIIIgAr1URWIOZzOgGtwAhgMZzWq1OaIv4ASKgOqzTkvAEmq1WgFtQA==")); } @@ -41,15 +29,31 @@ function getRocketSequences() { }; } -let rocket_sequence = 1; - -let settings = require('Storage').readJSON("cassioWatch.settings.json", true) || {}; +let rocketSequence = 1; +let settings = storage.readJSON("cassioWatch.settings.json", true) || {}; let rocketSpeed = settings.rocketSpeed || 700; delete settings; -g.clear(); +// 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 DrawClock() { + +function clearIntervals() { + if (rocketInterval) clearInterval(rocketInterval); + rocketInterval = undefined; + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; +} + +function drawClock() { g.setFont("7x11Numeric7Seg", 3); g.clearRect(80, 57, 170, 96); g.setColor(0, 255, 255); @@ -66,23 +70,57 @@ function DrawClock() { g.drawString(time < 10 ? "0" + time : time, 78, 137); } -function DrawBattery() { +function drawBattery() { bigThenSmall(E.getBattery(), "%", 135, 21); } -function DrawRocket() { +function drawRocket() { let Rocket = getRocketSequences(); g.clearRect(5, 62, 63, 115); g.setColor(0, 255, 255); g.drawRect(5, 62, 63, 115); g.fillRect(5, 62, 63, 115); - g.drawImage(Rocket[rocket_sequence], 5, 65, { scale: 0.7 }); + g.drawImage(Rocket[rocketSequence], 5, 65, { scale: 0.7 }); g.setColor(0, 0, 0); - rocket_sequence = rocket_sequence + 1; - if (rocket_sequence > 8) rocket_sequence = 1; + rocketSequence = rocketSequence + 1; + if(rocketSequence > 8) rocketSequence = 1; } -function DrawScene() { +function getTemperature(){ + try { + var weatherJson = storage.readJSON('weather.json'); + var weather = weatherJson.weather; + return Math.round(weather.temp-273.15); + + } catch(ex) { + print(ex) + return "?" + } +} + +function getSteps() { + var steps = 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(0, 255, 255); @@ -94,40 +132,44 @@ function DrawScene() { g.drawString("Launching Process", 30, 20); g.setFont("8x12"); g.drawString("ACTIVATE", 40, 35); + + g.setFontAlign(0,-1); g.setFont("8x12", 2); - g.drawString("30", 142, 132); - g.drawString("55", 95, 98); - g.setFont("8x12", 1); - g.drawString(Bangle.getStepCount(), 143, 104); - ClockInterval = setInterval(DrawClock, 30000); - DrawClock(); - RocketInterval = setInterval(DrawRocket, rocketSpeed); - DrawRocket(); - BatteryInterval = setInterval(DrawBattery, 5 * 60000); - DrawBattery(); + g.drawString(getTemperature(), 155, 132); + g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), 109, 98); + g.drawString(getSteps(), 158, 98); + + g.setFontAlign(-1,-1); + drawClock(); + drawRocket(); + drawBattery(); + + // Hide widgets + for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} } Bangle.on("lcdPower", (on) => { - if (!on) { - g.clear(); - ClearIntervals(true); + if (on) { + draw(); + } else { + clearIntervals(); } }); + Bangle.on("lock", (locked) => { - if (locked) { - ClearIntervals(true); - } else { - ClearIntervals(); - DrawScene(); + clearIntervals(); + draw(); + if (!locked) { + rocketInterval = setInterval(drawRocket, rocketSpeed); } }); + +// Load widgets, but don't show them +Bangle.loadWidgets(); +Bangle.setUI("clock"); + g.reset(); g.clear(); -Bangle.setUI("clock"); -DrawScene(); - -if (Bangle.isLocked()) { - ClearIntervals(true); -} \ No newline at end of file +draw(); \ No newline at end of file diff --git a/apps/cassioWatch/metadata.json b/apps/cassioWatch/metadata.json index 70cd9c242..dabdc2c93 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.9", + "version": "0.10", "type": "clock", "tags": "clock, weather, cassio, retro", "supports": ["BANGLEJS2"], 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..f18cdb048 100644 --- a/apps/clockcal/ChangeLog +++ b/apps/clockcal/ChangeLog @@ -1,3 +1,5 @@ 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) diff --git a/apps/clockcal/app.js b/apps/clockcal/app.js index 5e8c7f796..6d97f22ce 100644 --- a/apps/clockcal/app.js +++ b/apps/clockcal/app.js @@ -123,7 +123,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) { diff --git a/apps/clockcal/metadata.json b/apps/clockcal/metadata.json index 3998215d7..e3b92f81f 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.05", "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/cogclock/ChangeLog b/apps/cogclock/ChangeLog index 3158b6116..f4bfe77a5 100644 --- a/apps/cogclock/ChangeLog +++ b/apps/cogclock/ChangeLog @@ -1,2 +1,3 @@ 0.01: New clock 0.02: Use ClockFace library, add settings +0.03: Use ClockFace_menu.addSettingsFile diff --git a/apps/cogclock/metadata.json b/apps/cogclock/metadata.json index 40733bcd1..29000b589 100644 --- a/apps/cogclock/metadata.json +++ b/apps/cogclock/metadata.json @@ -1,7 +1,7 @@ { "id": "cogclock", "name": "Cog Clock", - "version": "0.02", + "version": "0.03", "description": "A cross-shaped clock inside a cog", "icon": "icon.png", "screenshots": [{"url":"screenshot.png"}], diff --git a/apps/cogclock/settings.js b/apps/cogclock/settings.js index 4eadc32c2..a91b033d0 100644 --- a/apps/cogclock/settings.js +++ b/apps/cogclock/settings.js @@ -1,19 +1,10 @@ (function(back) { - let s = require('Storage').readJSON("cogclock.settings.json", true) || {}; - - function saver(key) { - return value => { - s[key] = value; - require('Storage').writeJSON("cogclock.settings.json", s); - } - } - - const menu = { + let menu = { "": {"title": /*LANG*/"Cog Clock"}, /*LANG*/"< Back": back, - /*LANG*/"Show date": require("ClockFace_menu").showDate(s.showDate, saver('showDate')), - /*LANG*/"Load widgets": require("ClockFace_menu").loadWidgets(s.loadWidgets, saver('loadWidgets')), }; - + require("ClockFace_menu").addSettingsFile(menu, "cogclock.settings.json", [ + "showDate", "loadWidgets" + ]); E.showMenu(menu); }); diff --git a/apps/color_catalog/Changelog b/apps/color_catalog/ChangeLog similarity index 100% rename from apps/color_catalog/Changelog rename to apps/color_catalog/ChangeLog diff --git a/apps/colorful_clock/ChangeLog b/apps/colorful_clock/ChangeLog new file mode 100644 index 000000000..c72634017 --- /dev/null +++ b/apps/colorful_clock/ChangeLog @@ -0,0 +1,2 @@ +... +0.03: First update with ChangeLog Added diff --git a/apps/configurable_clock/ChangeLog b/apps/configurable_clock/ChangeLog new file mode 100644 index 000000000..84e7affed --- /dev/null +++ b/apps/configurable_clock/ChangeLog @@ -0,0 +1,2 @@ +... +0.02: First update with ChangeLog Added 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/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/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 5798c7842..5107bc6ae 100644 --- a/apps/espruinoctrl/metadata.json +++ b/apps/espruinoctrl/metadata.json @@ -5,8 +5,8 @@ "version": "0.01", "description": "Send commands to other Espruino devices via the Bluetooth UART interface. Customisable commands!", "icon": "app.png", - "tags": "", - "supports": ["BANGLEJS"], + "tags": "tool,bluetooth", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "custom": "custom.html", "storage": [ diff --git a/apps/espruinoprog/ChangeLog b/apps/espruinoprog/ChangeLog new file mode 100644 index 000000000..6fdcad1d6 --- /dev/null +++ b/apps/espruinoprog/ChangeLog @@ -0,0 +1,4 @@ +0.01: New App! +0.02: Add 'pre' code that can erase the device + Wait more between sending code snippets + Now force use of 'Storage' (assume 2v00 or later) diff --git a/apps/espruinoprog/README.md b/apps/espruinoprog/README.md new file mode 100644 index 000000000..aef4cccad --- /dev/null +++ b/apps/espruinoprog/README.md @@ -0,0 +1,43 @@ +# Espruino Programmer + +Finds Bluetooth devices with a specific name (eg `Puck.js`), connects and uploads code. Great for programming many devices at once! + +**WARNING:** This will reprogram **any matching Espruino device within range** while +the app is running. Unless you are careful to remove other devices from the area or +turn them off, you could find some of your devices unexpectedly get programmed! + +## Customising + +Click on the Customise button in the app loader to set up the programmer. + +* First you need to choose the kind of devices you want to upload to. This is +the text that should match the Bluetooth advertising name. So `Puck.js` for Puck.js +devices, or `Bangle.js` for Bangles. +* In the next box, you have code to run before the upload of the main code. By default +the code `require("Storage").list().forEach(f=>require("Storage").erase(f));reset();` will +erase all files on the device and reset it. +* Now paste in the code you want to write to the device. This is automatically +written to flash (`.bootcde`). See https://www.espruino.com/Saving#save-on-send-to-flash- +for more information. +* Now enter the code that should be sent **after** programming. This code +should make the device so it doesn't advertise on Bluetooth with the Bluetooth +name you entered for the first item. It may also help if it indicates to you that +the device is programmed properly. + * You could turn advertising off with `NRF.sleep()` + * You could change the advertising name with `NRF.setAdvertising({},{name:"Ok"});` + * On a Bangle, you could turn it off with `Bangle.off()` +* Finally scroll down and click `Upload` +* Now you can run the new `Programmer` app on the Bangle. + +## Usage + +Just run the app, and as soon as it starts it'll start scanning for +devices to upload to! + +To stop scanning, long-press the button to return to the clock. + +## Notes + +* This assumes the device being written to is at least version 2v00 of Espruino +* Currently, code is not minified before upload (so you need to supply pre-minified + code if you want that) diff --git a/apps/espruinoprog/app-icon.js b/apps/espruinoprog/app-icon.js new file mode 100644 index 000000000..532c60eea --- /dev/null +++ b/apps/espruinoprog/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4cA/4AB7wJB8/5uX+7uUgH41lSKf4AKpMkyQCCggEDAQVtCAMCCNWUx9JufSrmkCJeKqsiytICJtFkWRCJWAEaARCI5BkEoAGBymJ9eSvXkCJZ9JCLI1DyM9uQRLNYWRpRZMR5ARWAwSPCuWR9MuCJZZIgARGPouTCIcSA4OQAoMW7dt2wCEEZECCI1oCJAADrZyBAAcDuQRByOABQkKDAvbtwRBxu24AMFAAcGCIY/B7AQIhpOC3MjKIVsCJe3jYRCwiPEkARBQg227ieDAQO0CJPhCKHJCK1N0ARI28JCIjUDEY4OBzWRfAoRG3ARBygRH3oPBswRB4QjFfAYgCt9pYoJoEkmbCJONCI1ACJGSiQRE7TXDCIuQEYkmCIhpDEYSCFCIj2DCIOTrYRE6ARDAH4AHA")) diff --git a/apps/espruinoprog/app.js b/apps/espruinoprog/app.js new file mode 100644 index 000000000..58fac4a0b --- /dev/null +++ b/apps/espruinoprog/app.js @@ -0,0 +1,100 @@ +var uart; // require("ble_uart") +var device; // BluetoothDevice +var uploadTimeout; // a timeout used during upload - if we disconnect, kill this +Bangle.loadWidgets(); + +var json = require("Storage").readJSON("espruinoprog.json",1); +/*var json = { // for example + namePrefix : "Puck.js ", + code : "E.setBootCode('digitalPulse(LED2,1,100);')", + post : "LED.set();NRF.sleep()", +};*/ + +if ("object" != typeof json) { + E.showAlert("JSON not found","Programmer").then(() => load()); + throw new Error("JSON not found"); + // stops execution +} + +// Set up terminal +var R = Bangle.appRect; +var termg = Graphics.createArrayBuffer(R.w, R.h, 1, {msb:true}); +termg.setFont("6x8"); +var term; + +function showTerminal() { + E.showMenu(); // clear anything that was drawn + if (term) term.print(""); // redraw terminal +} + +function scanAndConnect() { + termg.clear(); + term = require("VT100").connect(termg, { + charWidth : 6, + charHeight : 8 + }); + term.print = str => { + for (var i of str) term.char(i); + g.reset().drawImage(termg,R.x,R.y); + }; + term.print(`\r\nScanning...\r\n`); + NRF.requestDevice({ filters: [{ namePrefix: json.namePrefix }] }).then(function(dev) { + term.print(`Found ${dev.name||dev.id.substr(0,17)}\r\n`); + device = dev; + + term.print(`Connect to ${dev.name||dev.id.substr(0,17)}...\r\n`); + device.removeAllListeners(); + device.on('gattserverdisconnected', function(reason) { + if (!uart) return; + term.print(`\r\nDISCONNECTED (${reason})\r\n`); + uart = undefined; + device = undefined; + if (uploadTimeout) clearTimeout(uploadTimeout); + uploadTimeout = undefined; + setTimeout(scanAndConnect, 1000); + }); + require("ble_uart").connect(device).then(function(u) { + uart = u; + term.print("Connected...\r\n"); + uart.removeAllListeners(); + uart.on('data', function(d) { term.print(d); }); + term.print("Upload initial...\r\n"); + uart.write((json.pre||"")+"\n").then(() => { + term.print("\r\Done.\r\n"); + uploadTimeout = setTimeout(function() { + uploadTimeout = undefined; + term.print("\r\nUpload Code...\r\n"); + uart.write((json.code||"")+"\n").then(() => { + term.print("\r\Done.\r\n"); + // main upload completed - wait a bit + uploadTimeout = setTimeout(function() { + uploadTimeout = undefined; + term.print("\r\Upload final...\r\n"); + // now upload the code to run after... + uart.write((json.post||"")+"\n").then(() => { + term.print("\r\nDone.\r\n"); + // now wait and disconnect (if not already done!) + uploadTimeout = setTimeout(function() { + uploadTimeout = undefined; + term.print("\r\nDisconnecting...\r\n"); + if (uart) uart.disconnect(); + }, 500); + }); + }, 2000); + }); + }, 2000); + }); + }); + }).catch(err => { + if (err.toString().startsWith("No device found")) { + // expected - try again + scanAndConnect(); + } else + term.print(`\r\ERROR ${err.toString()}\r\n`); + }); +} + +// now start +Bangle.drawWidgets(); +showTerminal(); +scanAndConnect(); diff --git a/apps/espruinoprog/app.png b/apps/espruinoprog/app.png new file mode 100644 index 000000000..b2b435f04 Binary files /dev/null and b/apps/espruinoprog/app.png differ diff --git a/apps/espruinoprog/custom.html b/apps/espruinoprog/custom.html new file mode 100644 index 000000000..a12189707 --- /dev/null +++ b/apps/espruinoprog/custom.html @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + +

Upload code to devices with names starting with:

+

+

Enter the code to send before upload here:

+

+

Enter your program to upload here:

+

+

Enter the code to send after upload here:

+

+

Then click  

+

Click here to reset to defaults.

+ + + + diff --git a/apps/espruinoprog/metadata.json b/apps/espruinoprog/metadata.json new file mode 100644 index 000000000..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/ffcniftya/ChangeLog b/apps/ffcniftya/ChangeLog index cb520193b..6d2f50119 100644 --- a/apps/ffcniftya/ChangeLog +++ b/apps/ffcniftya/ChangeLog @@ -2,3 +2,5 @@ 0.02: Shows the current week number (ISO8601), can be disabled via settings 0.03: Call setUI before loading widgets Improve settings page +0.04: Use ClockFace library + diff --git a/apps/ffcniftya/app.js b/apps/ffcniftya/app.js index 4000a1578..2c1a54f6e 100644 --- a/apps/ffcniftya/app.js +++ b/apps/ffcniftya/app.js @@ -1,22 +1,3 @@ -const locale = require("locale"); -const is12Hour = Object.assign({ "12hour": false }, require("Storage").readJSON("setting.json", true))["12hour"]; -const showWeekNum = Object.assign({ showWeekNum: true }, require('Storage').readJSON("ffcniftya.json", true))["showWeekNum"]; - -/* Clock *********************************************/ -const scale = g.getWidth() / 176; - -const widget = 24; - -const viewport = { - width: g.getWidth(), - height: g.getHeight(), -} - -const center = { - x: viewport.width / 2, - y: Math.round(((viewport.height - widget) / 2) + widget), -} - // copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480 function ISO8601_week_no(date) { var tdt = new Date(date.valueOf()); @@ -30,77 +11,49 @@ function ISO8601_week_no(date) { return 1 + Math.ceil((firstThursday - tdt) / 604800000); } -function d02(value) { - return ('0' + value).substr(-2); +function format(value) { + return ("0" + value).substr(-2); } -function draw() { - g.reset(); - g.clearRect(0, widget, viewport.width, viewport.height); - const now = new Date(); +const ClockFace = require("ClockFace"); +const clock = new ClockFace({ + init: function () { + const appRect = Bangle.appRect; - const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0)); - const minutes = d02(now.getMinutes()); - const day = d02(now.getDate()); - const month = d02(now.getMonth() + 1); - const year = now.getFullYear(now); - const weekNum = d02(ISO8601_week_no(now)); - const monthName = locale.month(now, 3); - const dayName = locale.dow(now, 3); + this.viewport = { + width: appRect.w, + height: appRect.h + }; - const centerTimeScaleX = center.x + 32 * scale; - g.setFontAlign(1, 0).setFont("Vector", 90 * scale); - g.drawString(hour, centerTimeScaleX, center.y - 31 * scale); - g.drawString(minutes, centerTimeScaleX, center.y + 46 * scale); + this.center = { + x: this.viewport.width / 2, + y: Math.round((this.viewport.height / 2) + appRect.y) + }; - g.fillRect(center.x + 30 * scale, center.y - 72 * scale, center.x + 32 * scale, center.y + 74 * scale); + this.scale = g.getWidth() / this.viewport.width; + this.centerTimeScaleX = this.center.x + 32 * this.scale; + this.centerDatesScaleX = this.center.x + 40 * this.scale; + }, + draw: function (date) { + const hour = date.getHours() - (this.is12Hour && date.getHours() > 12 ? 12 : 0); + const month = date.getMonth() + 1; + const monthName = require("date_utils").month(month, 1); + const dayName = require("date_utils").dow(date.getDay(), 1); - const centerDatesScaleX = center.x + 40 * scale; - g.setFontAlign(-1, 0).setFont("Vector", 16 * scale); - g.drawString(year, centerDatesScaleX, center.y - 62 * scale); - g.drawString(month, centerDatesScaleX, center.y - 44 * scale); - g.drawString(day, centerDatesScaleX, center.y - 26 * scale); - if (showWeekNum) g.drawString(weekNum, centerDatesScaleX, center.y + 15 * scale); - g.drawString(monthName, centerDatesScaleX, center.y + 48 * scale); - g.drawString(dayName, centerDatesScaleX, center.y + 66 * scale); -} + g.setFontAlign(1, 0).setFont("Vector", 90 * this.scale); + g.drawString(format(hour), this.centerTimeScaleX, this.center.y - 31 * this.scale); + g.drawString(format(date.getMinutes()), this.centerTimeScaleX, this.center.y + 46 * this.scale); + g.fillRect(this.center.x + 30 * this.scale, this.center.y - 72 * this.scale, this.center.x + 32 * this.scale, this.center.y + 74 * this.scale); -/* Minute Ticker *************************************/ - -let tickTimer; - -function clearTickTimer() { - if (tickTimer) { - clearTimeout(tickTimer); - tickTimer = undefined; - } -} - -function queueNextTick() { - clearTickTimer(); - tickTimer = setTimeout(tick, 60000 - (Date.now() % 60000)); -} - -function tick() { - draw(); - queueNextTick(); -} - -/* Init **********************************************/ - -// Clear the screen once, at startup -g.clear(); -tick(); - -Bangle.on('lcdPower', (on) => { - if (on) { - tick(); - } else { - clearTickTimer(); - } + g.setFontAlign(-1, 0).setFont("Vector", 16 * this.scale); + g.drawString(date.getFullYear(date), this.centerDatesScaleX, this.center.y - 62 * this.scale); + g.drawString(format(month), this.centerDatesScaleX, this.center.y - 44 * this.scale); + g.drawString(format(date.getDate()), this.centerDatesScaleX, this.center.y - 26 * this.scale); + if (this.showWeekNum) g.drawString(format(ISO8601_week_no(date)), this.centerDatesScaleX, this.center.y + 15 * this.scale); + g.drawString(monthName, this.centerDatesScaleX, this.center.y + 48 * this.scale); + g.drawString(dayName, this.centerDatesScaleX, this.center.y + 66 * this.scale); + }, + settingsFile: "ffcniftya.json" }); - -Bangle.setUI("clock"); -Bangle.loadWidgets(); -Bangle.drawWidgets(); +clock.start(); \ No newline at end of file diff --git a/apps/ffcniftya/metadata.json b/apps/ffcniftya/metadata.json index 91b426cd0..015c56119 100644 --- a/apps/ffcniftya/metadata.json +++ b/apps/ffcniftya/metadata.json @@ -1,7 +1,7 @@ { "id": "ffcniftya", "name": "Nifty-A Clock", - "version": "0.03", + "version": "0.04", "description": "A nifty clock with time and date", "icon": "app.png", "screenshots": [{"url":"screenshot_nifty.png"}], diff --git a/apps/ffcniftyb/ChangeLog b/apps/ffcniftyb/ChangeLog index 9fc7e3c5c..83b11eb78 100644 --- a/apps/ffcniftyb/ChangeLog +++ b/apps/ffcniftyb/ChangeLog @@ -3,3 +3,4 @@ 0.03: Call setUI before loading widgets Fix bug with black being unselectable Improve settings page +0.04: Use ClockFace library diff --git a/apps/ffcniftyb/app.js b/apps/ffcniftyb/app.js index 65c74dbd7..540924fa5 100644 --- a/apps/ffcniftyb/app.js +++ b/apps/ffcniftyb/app.js @@ -1,20 +1,10 @@ -const is12Hour = Object.assign({ "12hour": false }, require("Storage").readJSON("setting.json", true))["12hour"]; -const color = Object.assign({ color: 63488 }, require("Storage").readJSON("ffcniftyb.json", true)).color; // Default to RED +var scale; +var screen; +var center; +var buf; +var img; -/* Clock *********************************************/ -const scale = g.getWidth() / 176; - -const screen = { - width: g.getWidth(), - height: g.getHeight() - 24, -}; - -const center = { - x: screen.width / 2, - y: screen.height / 2, -}; - -function d02(value) { +function format(value) { return ("0" + value).substr(-2); } @@ -22,91 +12,69 @@ function renderEllipse(g) { g.fillEllipse(center.x - 5 * scale, center.y - 70 * scale, center.x + 160 * scale, center.y + 90 * scale); } -function renderText(g) { - const now = new Date(); +function renderText(g, date) { + const hour = date.getHours() - (this.is12Hour && date.getHours() > 12 ? 12 : 0); + const month = date.getMonth() + 1; - const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0)); - const minutes = d02(now.getMinutes()); - const day = d02(now.getDate()); - const month = d02(now.getMonth() + 1); - const year = now.getFullYear(); - - const month2 = require("locale").month(now, 3); - const day2 = require("locale").dow(now, 3); + const monthName = require("date_utils").month(month, 1); + const dayName = require("date_utils").dow(date.getDay(), 1); g.setFontAlign(1, 0).setFont("Vector", 90 * scale); - g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale); - g.drawString(minutes, center.x + 32 * scale, center.y + 46 * scale); + g.drawString(format(hour), center.x + 32 * scale, center.y - 31 * scale); + g.drawString(format(date.getMinutes()), center.x + 32 * scale, center.y + 46 * scale); g.setFontAlign(1, 0).setFont("Vector", 16 * scale); - g.drawString(year, center.x + 80 * scale, center.y - 42 * scale); - g.drawString(month, center.x + 80 * scale, center.y - 26 * scale); - g.drawString(day, center.x + 80 * scale, center.y - 10 * scale); - g.drawString(month2, center.x + 80 * scale, center.y + 44 * scale); - g.drawString(day2, center.x + 80 * scale, center.y + 60 * scale); + g.drawString(date.getFullYear(), center.x + 80 * scale, center.y - 42 * scale); + g.drawString(format(month), center.x + 80 * scale, center.y - 26 * scale); + g.drawString(format(date.getDate()), center.x + 80 * scale, center.y - 10 * scale); + g.drawString(monthName, center.x + 80 * scale, center.y + 44 * scale); + g.drawString(dayName, center.x + 80 * scale, center.y + 60 * scale); } -const buf = Graphics.createArrayBuffer(screen.width, screen.height, 1, { - msb: true +const ClockFace = require("ClockFace"); +const clock = new ClockFace({ + init: function () { + const appRect = Bangle.appRect; + + screen = { + width: appRect.w, + height: appRect.h + }; + + center = { + x: screen.width / 2, + y: screen.height / 2 + }; + + buf = Graphics.createArrayBuffer(screen.width, screen.height, 1, { msb: true }); + + scale = g.getWidth() / screen.width; + + img = { + width: screen.width, + height: screen.height, + transparent: 0, + bpp: 1, + buffer: buf.buffer + }; + + // default to RED (see settings.js) + // don't use || to default because 0 is a valid color + this.color = this.color === undefined ? 63488 : this.color; + }, + draw: function (date) { + // render outside text with ellipse + buf.clear(); + renderText(buf.setColor(1), date); + renderEllipse(buf.setColor(0)); + g.setColor(this.color).drawImage(img, 0, 24); + + // render ellipse with inside text + buf.clear(); + renderEllipse(buf.setColor(1)); + renderText(buf.setColor(0), date); + g.setColor(this.color).drawImage(img, 0, 24); + }, + settingsFile: "ffcniftyb.json" }); - -function draw() { - - const img = { - width: screen.width, - height: screen.height, - transparent: 0, - bpp: 1, - buffer: buf.buffer - }; - - // cleat screen area - g.clearRect(0, 24, g.getWidth(), g.getHeight()); - - // render outside text with ellipse - buf.clear(); - renderText(buf.setColor(1)); - renderEllipse(buf.setColor(0)); - g.setColor(color).drawImage(img, 0, 24); - - // render ellipse with inside text - buf.clear(); - renderEllipse(buf.setColor(1)); - renderText(buf.setColor(0)); - g.setColor(color).drawImage(img, 0, 24); -} - - -/* Minute Ticker *************************************/ - -let ticker; - -function stopTick() { - if (ticker) { - clearTimeout(ticker); - ticker = undefined; - } -} - -function startTick(run) { - stopTick(); - run(); - ticker = setTimeout(() => startTick(run), 60000 - (Date.now() % 60000)); -} - -/* Init **********************************************/ - -g.clear(); -startTick(draw); - -Bangle.on("lcdPower", (on) => { - if (on) { - startTick(draw); - } else { - stopTick(); - } -}); - -Bangle.setUI("clock"); -Bangle.loadWidgets(); -Bangle.drawWidgets(); +clock.start(); \ No newline at end of file diff --git a/apps/ffcniftyb/metadata.json b/apps/ffcniftyb/metadata.json index 3d26c27ea..019ae6eb3 100644 --- a/apps/ffcniftyb/metadata.json +++ b/apps/ffcniftyb/metadata.json @@ -1,7 +1,7 @@ { "id": "ffcniftyb", "name": "Nifty-B Clock", - "version": "0.03", + "version": "0.04", "description": "A nifty clock (series B) with time, date and colour configuration", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], 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/ChangeLog b/apps/football/ChangeLog index 9b91672a5..66b9882cc 100644 --- a/apps/football/ChangeLog +++ b/apps/football/ChangeLog @@ -1 +1,2 @@ 1.00: Initial implementation +1.01: Bug fixes and performance and visual improvements diff --git a/apps/football/app.js b/apps/football/app.js index 8350bea88..d12f07e2b 100644 --- a/apps/football/app.js +++ b/apps/football/app.js @@ -1,4 +1,19 @@ +// globals. TODO: move into an object const digit = []; +let part = 0; +let endInc = 0; +var endGame = false; +let goalFrame = 0; +var stopped = true; +let score0 = 0; +let score1 = 0; +let inc = 0; +let msinc = 0; +let seq0 = 0; +let seq1 = 0; +let goaler = -1; +const w = g.getWidth(); +let owner = -1; const dash = { width: 75, @@ -6,6 +21,7 @@ const dash = { bpp: 1, buffer: require('heatshrink').decompress(atob('AH4A/AH4A/AH4A/AH4A/AB0D/4AB+AJEBAX/BAk/CQ8PCQ4kDCQoIDCQgkDCQgkECQgIE4ASHFxH8JRgSEEgYSEPJAkEAH4A/AH4A/AH4A/AH4A/ACg=')) }; + function loadDigits () { digit[0] = { width: 80, @@ -58,8 +74,8 @@ function loadDigits () { digit[7] = { width: 80, height: 128, - bpp: 1, - buffer: require('heatshrink').decompress(atob('AGUH/4AE/wJBgYJF/gJBgIJF+AeCBJN/BIngsAJBn4JE4HgBIMfBImBBIUPBIkDBIRQE/0HBIRQE/kPBIRQE/EfBIRQE+E/BIZQD8AJEKAfABYIJCKAYsBBIYADIAIJHKgIJHNAIJ/BP4J/BP4Jzg//4AJGgf/wAJGgP/BAwAB/wJIvgJInAJIiAJIAH5PPMZJ3JRZCfJWZLHJfM4J/BP4J/BP4JNg4JIgYJIgIJIgAJJv4JIn4JIj4JIh4JIeg4JIgYJIgIJIgAJJsAJIkAJIAH4AQA=')) + bpp: 4, + buffer : require("heatshrink").decompress(atob("AH4A/AEtVADdQE5Nf/4AayAnJgoma/J4LKDR2KKDZODUMadChKhiJwefE5RQXJwbxLKCxOEE5hQVJwgnNKCZOFE5pQTJwonOKCJOGE5xQRD4Q8EE5xQPJw4nPgFZzIAMqCdFE6IARJwgnhJwonhJwonhe5In/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4nlr4mE/NQE78FE4n1Ez5QGE0JQEJ0RQETsBQFJ0gABrJOkAH4A/AH4A/ADNZqAmkgv/yAnkr///JQjJwIABypOkAAP5J0oABUMJODKAShgEwhQiE/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/AA+fE80JE8xQGE8JQFE8JQFE8RQEE8RQEE8ZQDE8ZQDE8hQCE8hQCE8pQBE8pQBE80JE80AE84A/AH4A/AH4A/AAQA==")) }; digit[8] = { @@ -170,9 +186,6 @@ const gol11 = { loadDigits(); -let goalFrame = 0; -let score0 = 0; -let score1 = 0; function printNumber (n, x, y, options) { if (n > 9 || n < -1) { @@ -197,13 +210,7 @@ function printNumber (n, x, y, options) { g.drawImage(img, x, y, options); } } -let inc = 0; -let msinc = 0; -let seq0 = 0; -let seq1 = 0; -let goaler = -1; -const w = g.getWidth(); -let owner = -1; + g.setBgColor(0, 0, 0); g.clear(); g.setColor(1, 1, 1); @@ -247,43 +254,63 @@ function onStop () { refresh(); refresh_ms(); } -var stopped = true; -Bangle.on('tap', function (pos) { - console.log('touch', pos); + +function onButtonPress() { + console.log('on.tap'); + setWatch(() => { + onButtonPress(); +}, BTN1); + Bangle.beep(); if (endGame) { - Bangle.beep(); score0 = 0; score1 = 0; seq0 = 0; seq1 = 0; + part = 0; inc = 0; msinc = 0; stopped = true; endGame = false; } else { if (inc == 0) { - autogame(); + // autogame(); + stopped = false; } else { onStop(); } } +} + +setWatch(() => { + onButtonPress(); +}, BTN1); +/*Bangle.on('tap', function () { + onButtonPress(); }); +*/ g.setFont12x20(3); -let part = 0; -let endInc = 0; -var endGame = false; + function refresh () { g.clear(); if (inc > 59) { inc = 0; part++; } + if (part >= 2 && inc > 30) { + part = 2; + Bangle.buzz(); + endGame = true; + endInc = inc; + inc = 0; + } if (inc > 44) { + inc = 0; if (part < 2) { part++; } if (part >= 2) { if (score0 != score1) { + Bangle.buzz(); endGame = true; endInc = inc; inc = 0; @@ -342,6 +369,12 @@ function refresh_pixels () { let bx = (owner == 0) ? w / 3 : w / 2; bx += 2; g.drawImage(frame4 ? ball0 : ball1, bx, 10, { scale: 5 }); + const liney = 60; + if (owner) { + g.drawLine(w-8, liney, 2*(w/3), liney); + } else { + g.drawLine(8, liney, w/3, liney); + } } let dots = 0; function refresh_dots () { @@ -437,4 +470,5 @@ function autogame () { } Bangle.setOptions({ lockTimeout: 0, backlightTimeout: 0 }); -autogame(); +// autogame(); + 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/fwupdate/custom.html b/apps/fwupdate/custom.html index 0321e46bc..3f8f50b3f 100644 --- a/apps/fwupdate/custom.html +++ b/apps/fwupdate/custom.html @@ -82,6 +82,7 @@ function onInit(device) { if (crc==46757280) version = "2v11.58"; if (crc==3508163280 || crc==1418074094) version = "2v12"; if (crc==4056371285) version = "2v13"; + if (crc==1038322422) version = "2v14"; if (!ok) { version += `(⚠ update required)`; } 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/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..304e94690 100644 --- a/apps/gpsnav/ChangeLog +++ b/apps/gpsnav/ChangeLog @@ -3,4 +3,4 @@ 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 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/metadata.json b/apps/gpsnav/metadata.json index 5c1830318..f1e0c92c0 100644 --- a/apps/gpsnav/metadata.json +++ b/apps/gpsnav/metadata.json @@ -1,16 +1,15 @@ { "id": "gpsnav", "name": "GPS Navigation", - "version": "0.05", + "version": "0.06", "description": "Displays GPS Course and Speed, + Directions to waypoint and waypoint recording, now with waypoint editor", "icon": "icon.png", "tags": "tool,outdoors,gps", "supports": ["BANGLEJS"], "readme": "README.md", - "interface": "waypoints.html", + "dependencies" : { "waypoints":"type" }, "storage": [ {"name":"gpsnav.app.js","url":"app.min.js"}, {"name":"gpsnav.img","url":"app-icon.js","evaluate":true} - ], - "data": [{"name":"waypoints.json","url":"waypoints.json"}] + ] } 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/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/hworldclock/hsuncalc.js b/apps/hworldclock/hsuncalc.js new file mode 100644 index 000000000..b1af0a0d9 --- /dev/null +++ b/apps/hworldclock/hsuncalc.js @@ -0,0 +1,298 @@ +/* Module suncalc.js + (c) 2011-2015, Vladimir Agafonkin + SunCalc is a JavaScript library for calculating sun/moon position and light phases. + https://github.com/mourner/suncalc + +PB: Usage: +E.setTimeZone(2); // 1 = MEZ, 2 = MESZ +SunCalc = require("suncalc.js"); +pos = SunCalc.getPosition(Date.now(), 53.3, 10.1); +times = SunCalc.getTimes(Date.now(), 53.3, 10.1); +rise = times.sunrise; // Date object +rise_str = rise.getHours() + ':' + rise.getMinutes(); //hh:mm +*/ +var exports={}; + +// shortcuts for easier to read formulas + +var PI = Math.PI, + sin = Math.sin, + cos = Math.cos, + tan = Math.tan, + asin = Math.asin, + atan = Math.atan2, + acos = Math.acos, + rad = PI / 180; + +// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas + +// date/time constants and conversions + +var dayMs = 1000 * 60 * 60 * 24, + J1970 = 2440588, + J2000 = 2451545; + +function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; } +function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); } // PB: onece removed + 0.5; included it again 4 Jan 2021 +function toDays(date) { return toJulian(date) - J2000; } + + +// general calculations for position + +var e = rad * 23.4397; // obliquity of the Earth + +function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); } +function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); } + +function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); } +function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); } + +function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; } + +function astroRefraction(h) { + if (h < 0) // the following formula works for positive altitudes only. + h = 0; // if h = -0.08901179 a div/0 would occur. + + // formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. + // 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad: + return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179)); +} + +// general sun calculations + +function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); } + +function eclipticLongitude(M) { + + var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center + P = rad * 102.9372; // perihelion of the Earth + + return M + C + P + PI; +} + +function sunCoords(d) { + + var M = solarMeanAnomaly(d), + L = eclipticLongitude(M); + + return { + dec: declination(L, 0), + ra: rightAscension(L, 0) + }; +} + +// calculates sun position for a given date and latitude/longitude + +exports.getPosition = function (date, lat, lng) { + + var lw = rad * -lng, + phi = rad * lat, + d = toDays(date), + + c = sunCoords(d), + H = siderealTime(d, lw) - c.ra; + + return { + azimuth: Math.round((azimuth(H, phi, c.dec) / rad + 180) % 360), // PB: converted to deg + altitude: Math.round( altitude(H, phi, c.dec) / rad) // PB: converted to deg + }; +}; + + +// sun times configuration (angle, morning name, evening name) + +var times = [ + [-0.833, 'sunrise', 'sunset' ] +]; + +// calculations for sun times +var J0 = 0.0009; + +function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); } + +function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; } +function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); } + +function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); } +function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; } + +// returns set time for the given sun altitude +function getSetJ(h, lw, phi, dec, n, M, L) { + + var w = hourAngle(h, phi, dec), + a = approxTransit(w, lw, n); + return solarTransitJ(a, M, L); +} + + +// calculates sun times for a given date, latitude/longitude, and, optionally, +// the observer height (in meters) relative to the horizon + +exports.getTimes = function (date, lat, lng, height) { + + height = height || 0; + + var lw = rad * -lng, + phi = rad * lat, + + dh = observerAngle(height), + + d = toDays(date), + n = julianCycle(d, lw), + ds = approxTransit(0, lw, n), + + M = solarMeanAnomaly(ds), + L = eclipticLongitude(M), + dec = declination(L, 0), + + Jnoon = solarTransitJ(ds, M, L), + + i, len, time, h0, Jset, Jrise; + + + var result = { + solarNoon: fromJulian(Jnoon), + nadir: fromJulian(Jnoon - 0.5) + }; + + for (i = 0, len = times.length; i < len; i += 1) { + time = times[i]; + h0 = (time[0] + dh) * rad; + + Jset = getSetJ(h0, lw, phi, dec, n, M, L); + Jrise = Jnoon - (Jset - Jnoon); + + result[time[1]] = fromJulian(Jrise); + result[time[2]] = fromJulian(Jset); + } + + return result; +}; + + +// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas + +function moonCoords(d) { // geocentric ecliptic coordinates of the moon + + var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude + M = rad * (134.963 + 13.064993 * d), // mean anomaly + F = rad * (93.272 + 13.229350 * d), // mean distance + + l = L + rad * 6.289 * sin(M), // longitude + b = rad * 5.128 * sin(F), // latitude + dt = 385001 - 20905 * cos(M); // distance to the moon in km + + return { + ra: rightAscension(l, b), + dec: declination(l, b), + dist: dt + }; +} + +getMoonPosition = function (date, lat, lng) { + + var lw = rad * -lng, + phi = rad * lat, + d = toDays(date), + + c = moonCoords(d), + H = siderealTime(d, lw) - c.ra, + h = altitude(H, phi, c.dec), + // formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. + pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H)); + + h = h + astroRefraction(h); // altitude correction for refraction + + return { + azimuth: azimuth(H, phi, c.dec), + altitude: h, + distance: c.dist, + parallacticAngle: pa + }; +}; + + +// calculations for illumination parameters of the moon, +// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and +// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. + +getMoonIllumination = function (date) { + + var d = toDays(date || new Date()), + s = sunCoords(d), + m = moonCoords(d), + + sdist = 149598000, // distance from Earth to Sun in km + + phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)), + inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi)), + angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) - + cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra)); + + return { + fraction: (1 + cos(inc)) / 2, + phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI, + angle: angle + }; +}; + + +function hoursLater(date, h) { + return new Date(date.valueOf() + h * dayMs / 24); +} + +// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article + +getMoonTimes = function (date, lat, lng, inUTC) { + var t = new Date(date); + if (inUTC) t.setUTCHours(0, 0, 0, 0); + else t.setHours(0, 0, 0, 0); + + var hc = 0.133 * rad, + h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc, + h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx; + + // go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set) + for (var i = 1; i <= 24; i += 2) { + h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc; + h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc; + + a = (h0 + h2) / 2 - h1; + b = (h2 - h0) / 2; + xe = -b / (2 * a); + ye = (a * xe + b) * xe + h1; + d = b * b - 4 * a * h1; + roots = 0; + + if (d >= 0) { + dx = Math.sqrt(d) / (Math.abs(a) * 2); + x1 = xe - dx; + x2 = xe + dx; + if (Math.abs(x1) <= 1) roots++; + if (Math.abs(x2) <= 1) roots++; + if (x1 < -1) x1 = x2; + } + + if (roots === 1) { + if (h0 < 0) rise = i + x1; + else set = i + x1; + + } else if (roots === 2) { + rise = i + (ye < 0 ? x2 : x1); + set = i + (ye < 0 ? x1 : x2); + } + + if (rise && set) break; + + h0 = h2; + } + + var result = {}; + + if (rise) result.rise = hoursLater(t, rise); + if (set) result.set = hoursLater(t, set); + + if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true; + + return result; +}; \ No newline at end of file diff --git a/apps/hworldclock/hworldclock-icon.js b/apps/hworldclock/hworldclock-icon.js new file mode 100644 index 000000000..6e05d254c --- /dev/null +++ b/apps/hworldclock/hworldclock-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgJC/ABEE+EA4EAj9E8HF//gn/gwP///wt/MgF//8gh/8gYLBwEP+EHAofghgFD4EOj//gEPA4ILBGgIxB/wFBgwFB/lsgCKBj/4oxHBvAFBJoV8gP4TQX+gJUBAAN/Aok+AoVgAoXogAfBjkA8AfBAoXAAoUYY4cAiCDEAooA/ABg")) diff --git a/apps/hworldclock/hworldclock.png b/apps/hworldclock/hworldclock.png new file mode 100644 index 000000000..565e0dc6b Binary files /dev/null and b/apps/hworldclock/hworldclock.png differ diff --git a/apps/hworldclock/metadata.json b/apps/hworldclock/metadata.json new file mode 100644 index 000000000..653cfc59c --- /dev/null +++ b/apps/hworldclock/metadata.json @@ -0,0 +1,25 @@ +{ + "id": "hworldclock", + "name": "Hanks World Clock", + "shortName": "Hanks World Clock", + "version": "0.23", + "description": "Current time zone plus up to three others", + "allow_emulator":true, + "icon": "app.png", + "screenshots": [{"url":"screenshot_hworld.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "custom": "custom.html", + "storage": [ + {"name":"hworldclock.app.js","url":"app.js"}, + {"name":"hworldclock.img","url":"hworldclock-icon.js","evaluate":true}, + {"name":"hworldclock.settings.js","url":"settings.js"}, + {"name":"hsuncalc.js","url":"hsuncalc.js"} + ], + "data": [ + {"name":"hworldclock.settings.json"}, + {"name":"hworldclock.json"} + ] +} \ No newline at end of file diff --git a/apps/hworldclock/screenshot_hworld.png b/apps/hworldclock/screenshot_hworld.png new file mode 100644 index 000000000..565e0dc6b Binary files /dev/null and b/apps/hworldclock/screenshot_hworld.png differ diff --git a/apps/hworldclock/settings.js b/apps/hworldclock/settings.js new file mode 100644 index 000000000..26c946b5f --- /dev/null +++ b/apps/hworldclock/settings.js @@ -0,0 +1,50 @@ +(function(back) { + var FILE = "hworldclock.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": "Hanks World Clock" + }, + "< Back": () => back(), + "Seconds": stringInSettings("secondsMode", ["always", "when unlocked", "none"]), + "Color w. dark": stringInSettings("colorWhenDark", ["green", "default"]), + "Show SunInfo": { + value: (settings.showSunInfo !== undefined ? settings.showSunInfo : true), + onchange: v => { + settings.showSunInfo = v; + writeSettings(); + } + } + }; + + E.showMenu(mainmenu); + +}); diff --git a/apps/iconlaunch/ChangeLog b/apps/iconlaunch/ChangeLog index 4a72a9f28..991f15abb 100644 --- a/apps/iconlaunch/ChangeLog +++ b/apps/iconlaunch/ChangeLog @@ -1,2 +1,3 @@ 0.01: Initial release -0.02: implemented "direct launch" and "one click exit" settings \ No newline at end of file +0.02: implemented "direct launch" and "one click exit" settings +0.03: Use default Bangle formatter for booleans diff --git a/apps/iconlaunch/metadata.json b/apps/iconlaunch/metadata.json index 01e447672..c7acc534f 100644 --- a/apps/iconlaunch/metadata.json +++ b/apps/iconlaunch/metadata.json @@ -2,7 +2,7 @@ "id": "iconlaunch", "name": "Icon Launcher", "shortName" : "Icon launcher", - "version": "0.02", + "version": "0.03", "icon": "app.png", "description": "A launcher inspired by smartphones, with an icon-only scrollable menu.", "tags": "tool,system,launcher", @@ -13,6 +13,5 @@ { "name": "iconlaunch.settings.js", "url": "settings.js" } ], "screenshots": [{ "url": "screenshot1.png" }, { "url": "screenshot2.png" }], - "readme": "README.md", - "sortorder": -10 + "readme": "README.md" } diff --git a/apps/iconlaunch/settings.js b/apps/iconlaunch/settings.js index e9667047c..bd1a4a597 100644 --- a/apps/iconlaunch/settings.js +++ b/apps/iconlaunch/settings.js @@ -15,22 +15,18 @@ /*LANG*/"< Back": back, /*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) } }, /*LANG*/"Direct launch": { value: settings.direct == true, - format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", onchange: (m) => { save("direct", m) } }, /*LANG*/"One click exit": { value: settings.oneClickExit == true, - format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", onchange: (m) => { save("oneClickExit", m) } } }; diff --git a/apps/imageclock/ChangeLog b/apps/imageclock/ChangeLog index 5b99b5848..af1b97b3d 100644 --- a/apps/imageclock/ChangeLog +++ b/apps/imageclock/ChangeLog @@ -7,3 +7,4 @@ 0.06: Watchfaces can be refreshed partly 0.07: Allow wrapping drawing in timeouts to get faster reactions Show/Hide widgets with swipe up or down +0.08: Use default Bangle formatter for booleans diff --git a/apps/imageclock/metadata.json b/apps/imageclock/metadata.json index a2594653e..c3ece0184 100644 --- a/apps/imageclock/metadata.json +++ b/apps/imageclock/metadata.json @@ -2,7 +2,7 @@ "id": "imageclock", "name": "Imageclock", "shortName": "Imageclock", - "version": "0.07", + "version": "0.08", "type": "clock", "description": "BETA!!! File formats still subject to change --- This app is a highly customizable watchface. To use it, you need to select a watchface. You can build the watchfaces yourself without programming anything. All you need to do is write some json and create image files.", "icon": "app.png", diff --git a/apps/imageclock/settings.js b/apps/imageclock/settings.js index a86901b9e..a0c1ee9d2 100644 --- a/apps/imageclock/settings.js +++ b/apps/imageclock/settings.js @@ -25,7 +25,6 @@ }, 'Performance log': { value: !!settings.perflog, - format: v => settings.perflog ? "On" : "Off", onchange: v => { settings.perflog = v; writeSettings(); diff --git a/apps/imgclock/ChangeLog b/apps/imgclock/ChangeLog index 01a6a4248..29eec7606 100644 --- a/apps/imgclock/ChangeLog +++ b/apps/imgclock/ChangeLog @@ -7,3 +7,4 @@ 0.06: Support 12 hour time 0.07: Don't cut off wide date formats 0.08: Use Bangle.setUI for button/launcher handling +0.09: Bangle.js 2 compatibility diff --git a/apps/imgclock/app.js b/apps/imgclock/app.js index 0e4435638..aff47061b 100644 --- a/apps/imgclock/app.js +++ b/apps/imgclock/app.js @@ -10,8 +10,8 @@ var IX = inf.x, IY = inf.y, IBPP = inf.bpp; var IW = 174, IH = 45, OY = 24; var bgwidth = img.charCodeAt(0); var bgoptions; -if (bgwidth<240) - bgoptions = { scale : 240/bgwidth }; +if (bgwidth
+ Please wait...
@@ -12,13 +13,51 @@ diff --git a/apps/imgclock/metadata.json b/apps/imgclock/metadata.json index 799d11acc..9fa1d64c7 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.09", "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/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/kanawatch/ChangeLog b/apps/kanawatch/ChangeLog new file mode 100644 index 000000000..86a98398e --- /dev/null +++ b/apps/kanawatch/ChangeLog @@ -0,0 +1,4 @@ +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 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..1e9c0f922 --- /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); +Bangle.loadWidgets(); +tickWatch(); +setInterval(tickWatch, 1000 * 60); + +// show launcher when button pressed +Bangle.setUI('clock'); + 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..7183d6cad --- /dev/null +++ b/apps/kanawatch/metadata.json @@ -0,0 +1,31 @@ +{ + "id": "kanawatch", + "name": "Kanawatch", + "shortName": "Kanawatch", + "version": "0.04", + "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/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..a60193ba7 --- /dev/null +++ b/apps/macwatch2/ChangeLog @@ -0,0 +1,3 @@ +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 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..3b78d5baf --- /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 da = d.toString().split(" "); + hh = da[4].substr(0,2); + mi = da[4].substr(3,2); + dd = ("0"+(new Date()).getDate()).substr(-2); + mo = ("0"+((new Date()).getMonth()+1)).substr(-2); + yy = ("0"+((new Date()).getFullYear())).substr(-2); + g.drawString(hh, 52, 65, true); + g.drawString(mi, 132, 65, true); + g.drawString(':', 93,65); + 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; + } +}); + + +// Load widgets but hide them +Bangle.loadWidgets(); +draw(); +Bangle.setUI("clock"); 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..09ec01e06 --- /dev/null +++ b/apps/macwatch2/metadata.json @@ -0,0 +1,17 @@ +{ "id": "macwatch2", + "name": "MacWatch2", + "shortName":"MacWatch2", + "icon": "app.png", + "version":"0.03", + "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..ed59e37b0 100644 --- a/apps/matrixclock/ChangeLog +++ b/apps/matrixclock/ChangeLog @@ -1,4 +1,6 @@ 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 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..c7f63dfdc 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 { 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..eeefb6de4 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.06", "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/messages/ChangeLog b/apps/messages/ChangeLog index 4b577e191..ed38fa79a 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -52,4 +52,15 @@ 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: 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: Option to disable auto-open of messages 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..0956b1573 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -54,7 +54,7 @@ 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 3f801e101..05d525f3b 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,30 +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; - } - // first, buzz - if (!quiet && loadMessages && global.WIDGETS && WIDGETS.messages){ - WIDGETS.messages.buzz(); - if(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; // if we're in a clock or it's important, go straight to messages app if (loadMessages){ - return load("messages.app.js"); + if(!quiet && unlockWatch){ + Bangle.setLocked(false); + Bangle.setLCDPower(1); // turn screen on + } + // 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 to let someone know - WIDGETS.messages.show(); + 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 @@ -102,53 +97,65 @@ 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); } 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=="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=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA="); 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) { + const s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase(); return { // generic colors, using B2-safe colors + "airbnb": "#f00", "alarm": "#fff", "mail": "#ff0", "music": "#f0f", @@ -157,22 +164,33 @@ 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 "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", @@ -183,5 +201,5 @@ exports.getMessageImageCol = function(msg,def) { "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 adea36f12..7d90f6aea 100644 --- a/apps/messages/settings.js +++ b/apps/messages/settings.js @@ -1,9 +1,12 @@ (function(back) { function settings() { let settings = require('Storage').readJSON("messages.settings.json", true) || {}; - if (settings.vibrate===undefined) settings.vibrate="."; + 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; settings.unlockWatch=!!settings.unlockWatch; settings.openMusic=!!settings.openMusic; settings.maxUnreadTimeout=240; @@ -20,12 +23,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 +50,29 @@ }, /*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) + } }; E.showMenu(mainmenu); -}) +}); diff --git a/apps/messages/widget.js b/apps/messages/widget.js index ca02d4f1f..835626f5d 100644 --- a/apps/messages/widget.js +++ b/apps/messages/widget.js @@ -1,5 +1,17 @@ -WIDGETS["messages"]={area:"tl", width:0, iconwidth:24, -draw:function(recall) { +(() => { + +function getMessages() { + if ("undefined"!=typeof MESSAGES) return MESSAGES; + return require("Storage").readJSON("messages.json",1)||[]; +} + +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 +20,68 @@ 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 && (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() { +},buzz:function(msgSrc) { if ((require('Storage').readJSON('setting.json',1)||{}).quiet) return; // never buzz during Quiet Mode - require("buzz").pattern((require('Storage').readJSON("messages.settings.json", true) || {}).vibrate || "."); + 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 + 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(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/minimal_clock/ChangeLog b/apps/minimal_clock/ChangeLog new file mode 100644 index 000000000..c72634017 --- /dev/null +++ b/apps/minimal_clock/ChangeLog @@ -0,0 +1,2 @@ +... +0.03: First update with ChangeLog Added 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/alarm.js b/apps/multitimer/alarm.js index 97cbaa5fa..eb1b3b259 100644 --- a/apps/multitimer/alarm.js +++ b/apps/multitimer/alarm.js @@ -120,7 +120,7 @@ function showAlarm(alarm) { Bangle.setLocked(false); } - require("buzz").pattern(alarm.vibrate === undefined ? ".." : alarm.vibrate).then(() => { + require("buzz").pattern(alarm.vibrate === undefined ? "::" : alarm.vibrate).then(() => { if (buzzCount--) { setTimeout(buzz, settings.buzzIntervalMillis); } else if (alarm.as) { // auto-snooze 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/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/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 c19694ea4..02d43c065 100644 --- a/apps/noteify/app.js +++ b/apps/noteify/app.js @@ -5,7 +5,7 @@ var notes = require("Storage").readJSON("noteify.json", true) || []; var alarms = require("sched").getAlarms(); msg = ""; -function startNote(idx) { +function startNote(idx) { idx == undefined ? note = "" : note = notes[idx].note; require("textinput").input({text:note}).then(result => { if (result != "") { @@ -23,20 +23,20 @@ function viewNote(idx) { textY += e.dy; g.setClipRect(0, 30, g.getWidth(), g.getHeight()); if (textY > 30) textY = 30; - if (textY < textBound) textY = textBound; + if (textY < textBound) textY = textBound; g.clearRect(0, 30, g.getWidth(), g.getHeight()).setColor(g.theme.fg).setFont("6x8:2").setFontAlign(-1, -1).drawString(g.wrapString(notes[idx].note, g.getWidth()).join("\n"), 0, textY); },back:()=>{ Bangle.setUI(); showEditMenu(idx); }}); - + } function showMainMenu() { var mainMenu = { "" : { "title" : "Noteify" }, "< Back" : function() { load(); }, - "New note" : function() { + "New note" : function() { E.showMenu(); startNote(); }, @@ -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< 12) ? a.msg.replace(/\n/g, " ").substring(0, 12)+"..." : msg.replace(/\n/g, " ").substring(0, 12)+"..."; - + const menu = { '': { 'title': alarmTitle }, '< Back' : () => showAlarmMenu(), @@ -213,18 +212,15 @@ function editAlarm(alarmIndex, alarm) { }, 'Enabled': { value: a.on, - format: v=>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 } }; @@ -264,7 +260,7 @@ function editTimer(alarmIndex, alarm) { var t = decodeTime(a.timer); var timerTitle = (a.msg == undefined) ? 'Timer' : (a.msg.length > 12) ? a.msg.replace(/\n/g, " ").substring(0, 12)+"..." : msg.replace(/\n/g, " ").substring(0, 12)+"..."; - + const menu = { '': { 'title': timerTitle }, '< Back' : () => showMainMenu(), @@ -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..b9a5425d1 --- /dev/null +++ b/apps/novaclock/ChangeLog @@ -0,0 +1,2 @@ +... +0.10: First update with ChangeLog Added diff --git a/apps/novaclock/README.md b/apps/novaclock/README.md new file mode 100644 index 000000000..b54f44241 --- /dev/null +++ b/apps/novaclock/README.md @@ -0,0 +1,9 @@ +# Nova Clock +A simple clock app that uses a clockwork star, from Kirby. + +*Note: This clock draws slightly into the widget area, but since it's in the middle, it shouln't matter that much (nobody has that many widgets... right?)* + +## Credits +Pixel art by me, [dronesflier](https://github.com/dronesflier) + +The Kirby series belongs to Nintendo/HAL Labs diff --git a/apps/novaclock/app-icon.js b/apps/novaclock/app-icon.js new file mode 100644 index 000000000..cf159ba1f --- /dev/null +++ b/apps/novaclock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AE3N5ouuGFovuFwYwrF9wuFGFIqC5nMF9guBF9ReD43GGFJeDF9ReFGFImCFwYvBGAReqMEAdCAAwvKAA4pWFxYwNGhQoJAAouHYQYAEGBwsIFBIANGRBgLFzIwDYhoweFx4xGGC4uSGDYuFFpqTIF1BhXFzBhVXSZhNF6QuWGAgvSFzAvS4wved6KOsSDovJ5gACF9IsCBIQxFF8ItEAAYxEF7qHDFowxGBwZeaFxgPEAwYvYABAONF74PVF64RbF6IThDZYVnDIoXtAH4A/AH4AkA==")) diff --git a/apps/novaclock/app.js b/apps/novaclock/app.js new file mode 100644 index 000000000..e5bd37b06 --- /dev/null +++ b/apps/novaclock/app.js @@ -0,0 +1,274 @@ +function nova() { + var nova = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHsAABZM/K5fM5kAv2igGi1EAvFUK/6v/K8ipC0SvCAQhM/K5l+AQ5XG5nM1BrBBgJX/V4WoWQRXHKoIYGBBBX2vxOBKIQFCLoRMD1BXHLPpOBK4gFCK4RIDK5IDFK+4AKIwpYFBYuiKfLtBABAPEK5i9IKtg8BAAUPABHMlYADgBXMAwyqrKQOiKA0OAIRWDqwACLAOoWgZQILFg4B0SoHKIYADiEr5moKwgABW4R0BKowUBK1f+VIkOU4QCBToQACJoRTBIgJWDK4YDBWQRVCWFZEBVZBQEAAxNDlbGBBQgABAoguEWE6IBKoxUCIAr8EUIQIDCQUALgOiAAJnCWQavoVgb8BKoeoKoRBBfQZYEBAa9E5mo1FUK4IOEV9JWBVYyUBSoZCBTAJXCLAfMAohgD0V+WITIDLAivkKw6qFTIV+AQKZCKIagDAAl+qgUBV4pYEK8ZWFVgJQBVISvDLIQCCIQigELwQHB0V4vCuCC4IPEK8ZWGKoSvDAAhdC5pYEq3MWgQOCOgJbD1AGBPoZrCgGoK075CHoQAELQi6C0RXCLAKhEDIwADYgYqCK0xGBKoStGBAKaCLAbxDTgRVC5nNCIN+DIQRDCogSBK8ZWBKopPDAIJZDBgZYFUIS8EvwPCAoKvENgZXeKwpJDABxlFK4ZMBK4ZmCAQXNWoJYDYghWk64AEh0Oh8OhAKFLAmiHwVO0RVBvFUBoQAEBgKyBKwVWLDxXGKwpSBKoQOBLIxYE5oFD0V+VQKyCAIIFBV4IABLIZXCqxXaKwcOVw5VF1gIBA4JXGIoUAACitCWDhXCEgJWGJoIABGYYKELApXBDIpuCh4GB1gACBYIICDIJXFlaubLAJXFHgJWFLASyDWAwZFhCmGaghXKWC5XDVw8OHwI9HK4xYCDIoYHDIZ8BDIhXjdgi4DAAxWDdwrIFK5KwOAoJWYgCvHJAMOHpJZCSwZXGJIIZIB4MHa4JXELIxLJVyBXFFwKVJLIavKhBxJDASvFKwpXJgGoK6GiK4pYBK5QMBK5UHDJgNBK4tWK5cA0SvXdoQuDK8ANCDIZSBK4YOBJI9+K5RWHHoiwBF4OsKxo9B1BXEBoRWJP4hXDAApOGV5gMBK4eiK4I+EK4RYHBQMOhyuE1ByBAA6cDAQQNJK5pCBV547BWBBZBSYsIMYZXDDIQfB1B5BAYIlDAAXN5wECNoSuHK45YCK56vDLApOBAIYCBdIJWILAYABEQRTD5uoqmi5otCTYJWIYJKvUK4vXBwIACKoRWEK5RZHWoRXBAgRXHqxXCZwQADAwKvRFAKwGLIwKFKAZzDWAt+BYJbC5oDBWoIRBK5eivF+AAl4K540BQIQ+CAB5VCIQL7CeoKrFAQS1EB4YeCLQwvJK5kOAYItBbomiJIRgEforfBN4IYBUoIEC5pjDL4K9FBgQsBb4ZYGJw4vBK54uDFQRMFLwIADVgj+DUAV4IoqxEBAmiQgRgFLQZXGgF+V5sPUghKCeALiEAAr3GKoQCBVAKxBBYQZKFYLCBQ4xXJRYJXPIII4BLgRYEABCRDDIZEBUoYECDZd45qGFgA9BAAJLH1BXOS4gBBdwTZBHwwGEHAKuCAoQMELAZgDJQLeDCgKEFK4MPAYJMIK5JYDh0AdAZCCFILlE0QABGgI1CCQiXBJYYRBVw5YDEgYsCKx4AMK4UIUgeoE4StBJYRBDRYiSCKIacCKY5MFCoUAbQKuSK56vBRIpWCKQYADBAZRCKYRKDJILPCOIQABvwDCBYSxBZQRtDK7cQDYKwCFAOiFoL5DAQQAIIgIDEKIZoDAAJxDAYJeBgAHCCQRWCK66wDK4KVDK4JSDGoKYCSwShGfggcBBYahCMAYdCvALBGIOiAoZWZK4qvBJwIhBTIRfCTwb4EdAQOCYoLAKa4QkBvx2BvCDEK8CwCIQQEBGAKbBAARBEewRaCL4JXDXQmoUALGDPAgrCKwpXZLAcIEwZYDKopZEWYgBBTANUgF4vwYEDYqzCNgR7EgEOKzSwELAjyDRIIxBHoiwCWoZWCegRpFCwgUDFIQoCKwZXbLAYhBLAeip0Ac4Y3CK4YGB0V+NIKqB5gCBLoJQFVgQACVoIVB0QRBgBVBKziwEK4ifDWYXNfoKYDUwIACqheDlUrCwZ4DOYQKC1EqvxYBFQKueLBd+qkqqhODAAl4p1OSwXNLAVUAIMqCo4SBp1UB4N4WgKufLBg1BAAIzBAolO1F4TwSkDLAb9BJoN+DIJSCB4ICCVoJWiK4pYF5oyBKgQDDJ4RTCAoRcECQV+BId+CAQjBKIMPhxXiLBWoAASrBAAIEBMgZIBLohICJ4JWBAgN4VwRwDgBVCh+iK0CxMKIQCCVIhbEAAYFCUoQYCCYmoVsxYIh0ALIg+DWoKhBUwRHC0QKBW4IKEAAqmBKwMPiBWmWI6yFAAq1BV4YKGNYYAFKoQACK05YJLJCnBJZBVKKwMOK1hYDLJ2iUwJPH5pVIAAJXBBARWpLAX+LApZCLQqxCLIvNK4YVCDQMIVoXMFIQAsJwZZFLQZbDLAKqFBwYVChEIKoQMCK1qyLLY4AGfwZUBVYSsxWRJaKABy5EK2ZZELRKiCUYZUKKu5aG/y0OKYXMCwYA/WgoAE0QEDB4RV/LRQAJJn4A/AH4A/AFo")); + return nova; +} + +function novaEyesStage1() { + var novaEyesStage1 = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4ArgAAFDNhVj5gAFH58rDIXN5movwZRKs+oAIg/OgEr5mi1Go0V+AYIFCLN7oGABIZiK0fXAAkOh0Ph0IBQo+HDI0PhEHhEPDJpWoKQJVCh5ZHHwoZFCgcPg50BLIpYpHohVF1hFChxXODAQACL4hXsHgwABgAACBQg+HDIhuChAZEOYJYtJYkIKwo+EBoJXKJYIABDIyxGK8yUFSYpXKHwTIFKw4NFWFKUGHg0AKwaWGDIrHGWGA9QLIUIK5LJBDJAaDK9o8BhA9JHwSvLK5QQBhEOK9qVLgAMBK5bJJAAMHK9yuBJIRXWh78BDJBWBDIpXnWAIvB1hWNK4xYCOJK7BDIxYoGAZYGBQMOdg6wFBgZWFg7IGK9JYCGQI8FdYpXILAMIDI7IILFZOBAIYCBIoI8KOQ4VDYwRWtHorwCAAQ/DBohXKLAZZCLYQZKLFRZFBQo8HDJLLBDJpYlAB4ZiLE3M5moAQIAFHhgZD5oaCDofNK14/E1Go0QADHaBZDKwQBBKuTyKDNgA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AD4A==")); + return novaEyesStage1; +} + +function novaEyesStage0() { + var novaEyesStage0 = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4ArgAAFDNhVj64AFH58rDLBVrH6EAlYZXK0Y7KH4gZiK1MOh0Ph0IHxoZGh8Ig8Ih5YwHgpSBKoUPLI4+FDIoUDh8HOgJZFLFI9EKousIoUOK5wYCAARfEK9g8GAAMAAAQKEHw4ZENwUIDIhzBLFpLEhBWFHwgNBK5RLBAAIZGWIxXmSgqTFK5Q+CZApWHBoqwpSgw8GgBWDSwwZFY4ywwHqBZChBXJZIIZIDQZXtHgMIHpI+CV5ZXKCAMIhxXtSpcABgJXLZJIABg5XuVwJJCK60PfgIZIKwIZFK86wBF4OsKxpXGLARxJXYIZGLFAwDLAwKBhzsHWAoMDKwsHZAxXpLAQyBHgrrFK5BYBhAZHZBBYrJwIBDAQJFBHhRyHCobGCK1o9FeAQACH4YNEK5RYDLIRbCDJRYqLIoKFHg4ZJZYIZNLFYAIHhIZZLEo/LBgIZkLNw7QDLJZnAAgZsAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AfA")); + return novaEyesStage0; +} + +function novaEyesStage2() { + var novaEyesStage2 = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4ArgAAFDNhVj5nM1ABC5g/PlYQB1AYCvwZRKsxWE0RZPBgOiv2iAQNUAYIACLN6RBHYQAETQKcDHxMAvwACLIYaEOYRWtGQYAD1BAELBKsBBgRRCWIQBCDwZYqKwKoGLowFDHwpWCDIRRBAQPMAwd+vANBWNRXBJASoDeRKWGDIoEBOoQDDDAQeBK9CUBJQiTCAAq2CB4Q+DDITGFCAZ2DOohYngAAVDLhWj64ACh0PhA0G6+sBoMPCQY+BgAKCBYMOhwZHBoYZFK88PAAKMHKwYABK4oZEKw5YEDIxXpHpRZChBXJZIIZIDQZXtHgMIHpI+CV5ZXKCAMIhxXtSpcABgJXLZJIABg5XuVwJJCK60PfgIZIKwIZFK86wBF4OsgErKxZXGLARxJXYIZGLEoAVDLhXk1AAC5mo0QGDABJXEBIgaBDYfMAA4ZELEouEHgIALHgsABAN+5hUB0R1CDJywlRIvNAQYECMgZXGY4ZXBCAIDCKQTIILFAAISYo8IOQYUDAoQDCBQRWrHwQABcoxdFHhJyCC4i3DDoWiK1hYDRgSQCAAgMBDJ3NDQJWE5oZMLM6ZGHaBZDOgQBBKuQ/FAAgZsAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AfA")); + return novaEyesStage2; +} + +function novaEyesStage3() { + var novaEyesStage3 = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4ArgAAFDNhVj5nM1ABC5g/PlYQB1AYCvwZRKsxWE0RZPBgOiv2iAQNUAYIACLN6RBHYQAETQKcDHxMAvwACLIYaEOYRWtGQYAD1BAELBKsBBgRRCWIQBCBQV+LFRWBVAxdGAoY+FKwQZCJgICB5hZCvF+qgFBWNRXBJASoDeROiK4ytDCIOoC4R5DAYQIBK9A8BGIY2EABQ+DZARRBUoJVCDYzTELE49CVgo7KHosABoocFZYgBDK8yuGABJfGHwIZQLYQcDLEsAAC4ZaK8nXAAMPh8OGhHX1gPBhATCK4QZDh0PJ5IaCh4ZEK848BhCOKHwI9FOIpXKCAMIhxXpdrAZZK8mo1Gi0V+AIYABBAOiBoIAEBAJXCAwQYDDIwAFDYRXoJAozCAYRDCAAN4I4RXCJAoFELBIdBK8o+DGoyzDKQKcDHgpyBYwquECoYMCAQJWmWAgvCUYadHdgzKF5jMCAITTD5gIDK84+B5gABG4SZDd5A8FZQZLDLQIWGDJCwlLAQAC5oCDAgQABVwoZEKIQeCAYTGFDI5YmcogEEAAg8IOQYUDAoQDCBQRWrHwQABJoV+SQ48KOQRpEW4YdC0RWsLAaMCAIQAEBgIZO5oaCKwfNDJhZnTIw7QLIZ0EKuQ/FAAgZsAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AfA==")); + return novaEyesStage3; +} + +function novaEyesStage4() { + var novaEyesStage4 = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4ArgAAFDNhVj5nM1ABC5g/PlYQB1AYCvwZRKsxWE0RZPBgOiv2iAQNUAYIACLN6RBHYQAETQKcDHxMAvwACLIYaEOYRWtGQYAD1BAELBKsBBgRRCWIQBCBQV+LFRWBVAxdGAoY+FKwQZCJgICB5hZCvF+qgFBWNRXBJASoDeROiK4ytDCIOoC4R5DAYQIBK9EAJQg2EBAoFEHwauBUIN4p1UKoQbGaYhYnK4KvCKoQFBHYi6EHosrBQQaCYwRbBDQd4XYSwoKwIuB5oACIAJBBAYpMDAAI+BKwIKDDAJKBvC2BEgIUDEwawmdgIvDGIhVDLYYAEHoIZBVIQPBKwJeBZoLJEXgYHBK8wzBHISMF5xdFK45GCMwQDBNYonBBAN+XAV+K8wAPlYHGDKIAHV8ztCqiHBWo6gEdgOoK4SpEAgQODDwIjBAAIjBCIRXm5g1CHQQCDHgJWCAgIAD0RXDDIPM5oRC5oECDYR+CK9aVES4ZQFHQShEK4yvEAAbJGAARXlHwI9BJYIAEAwLsEql4vwBBHgZYCCQRrEEYgMDAQJWmWAgvCJQJaDUQQ8DVwYZFVAS9CAITED5gIDK84+BcIQ3CTIa5HKwjKFJYZaBCwwZIWEpYCAAXNAQYECAAJkBK4ysCYYR2DAQIICAAZXpd4zqFAAg8IOQYUDAoQDCBQRWrHwQABJoV+SQ48KOQRpEW4YdC0RWsLAaMCAIQAEBgIZO5oaCKwfNDJhZnTIw7QLIZ0EKuQ/FAAgZsAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AfA==")); + return novaEyesStage4; + +} + +function novaEyesWhiteStage0() { + var novaEyesWhiteStage0 = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4ArgAAFDNhVj64AFH58rDLBVrH6EAlYZXK0Y7KH4gZiK2Q+JDLJW0Hw4ZZK/5X6HiY+FDLJX/K/Q8VHwYZZK/5X/K/5X/K/5X/K/5X/K+Y+WHgYZZK/5X7Hyg8FDLJX/K/Y+SHg4ZZLGg8JDLJYlH5YMBDMhZuHaAZZLM4AEDNgA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AD4A=")); + return novaEyesWhiteStage0; +} + +function novaEyesTransStage1() { + var novaEyesTransStage1 = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4ArgAAFDNhVj5gAFH58rDIXN5movwZRKs+oAIg/OgEr5mi1Go0V+AYIFCLN7oGABIZiK0YRYDLJW0CY4ZZK/5X6FCoVDDLJX/K/QmXC4IZZK/5X/K/5X/K/5X/K/5X/K+YmWCoYZZK/5X7FCgTFDLJX/K/YqSCI4ZZLEoAPDMRYm5nM1ACBAAo8MDIfNDQQdD5pWvH4mo1GiAAY7QLIZWCAIJVyeRQZsAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AfA")); + return novaEyesTransStage1; +} + +function novaEyesTransStage2() { + var novaEyesTransStage2 = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4ArgAAFDNhVj5nM1ABC5g/PlYQB1AYCvwZRKsxWE0RZPBgOiv2iAQNUAYIACLN6RBHYQAETQKcDHxMAvwACLIYaEOYRWtGQYAD1BAELBKsBBgRRCWIQBCDwZYqKwKoGLowFDHwpWCDIRRBAQPMAwd+vANBWNRXBJASoDeRKWGDIoEBOoQDDDAQeBK9CUBJQiTCAAq2CB4Q+DDITGFCAZ2DOohYngAAVDLhWjC7AZZK/5X/K/5X/K/5X/K/5X/K+YmBACoZcK8moAAXM1GiAwYAJK4gJEDQIbD5gAHDIhYlFwg8BABY8FgAIBv3MKgOiOoQZOWEqJF5oCDAgRkDK4zHDK4IQBAYRSCZBBYoABCTFHhByDCgYFCAYQKCK1Y+CAALlGLoo8JOQQXEW4YdC0RWsLAaMCSAQAEBgIZO5oaBKwnNDJhZnTIw7QLIZ0CAIJVyH4oAEDNgA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AD4A==")); + return novaEyesTransStage2; +} + +function novaEyesTransStage3() { + var novaEyesTransStage3 = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4ArgAAFDNhVj5nM1ABC5g/PlYQB1AYCvwZRKsxWE0RZPBgOiv2iAQNUAYIACLN6RBHYQAETQKcDHxMAvwACLIYaEOYRWtGQYAD1BAELBKsBBgRRCWIQBCBQV+LFRWBVAxdGAoY+FKwQZCJgICB5hZCvF+qgFBWNRXBJASoDeROiK4ytDCIOoC4R5DAYQIBK9A8BGIY2EABQ+DZARRBUoJVCDYzTELE49CVgo7KHosABoocFZYgBDK8yuGABJfGHwIZQLYQcDLEsAAC4ZaK8gXYDLJX/K/jtYDLJXk1Go0WivwBDAAIIB0QNBAAgIBK4QGCDAYZGAAobCK9BIFGYQDCIYQABvBHCK4RIFAohYJDoJXlHwY1GWYZSBTgY8FOQLGFVwgVDBgQCBK0ywEF4SjDTo7sGZQvMZgQBCaYfMBAZXnHwPMAAI3CTIbvIHgrKDJYZaBCwwZIWEpYCAAXNAQYECAAKuFDIhRCDwQDCYwoZHLEzlEAggAEHhByDCgYFCAYQKCK1Y+CAAJNCvySHHhRyCNIi3DDoWiK1hYDRgQBCAAgMBDJ3NDQRWD5oZMLM6ZGHaBZDOghVyH4oAEDNgA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AD4=")); + return novaEyesTransStage3; +} + +function novaTopRedraw() { + var novaTopRedraw = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHsAABZM/K5fM5kAv2igGi1EAvFUK/6v/K8isDAQV+BAK4BJn5XL1BOB1GoLod4LQIRE0XM5vM1HN5pX/5hXBIwJdGKwgYGA45X3TgJRDLQZXFAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AB8AIH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AP")); + return novaTopRedraw; +} + +function star() { + var backgroundstar = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A9qxA/K/5W/AH5XGLH6w/K34A/AA2BwJB/K62sIP5XWIH4AW1iv/K/5X/AHFWqwCCq2BJ4ICB1mBAAJZBAQIQBlYUCAYMrAgJZ8KQICBU4QCBLIJVBK4QOCNAJSCOQQA/XpQA/K6q3CAH5XUwJB/ACqu/V7BA/AC2BwJB/LC5A/V/4AtqxX/AC2slZB/ACuBqxB/LC5A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4Ao")); + return backgroundstar; +} + +function novaOpenEyes(speed, white, animation) { + if (!white) { + g.drawImage(novaEyesStage4(), -10, -10, { + scale: 2.2 + }); + setTimeout(function() { + g.drawImage(novaEyesStage3(), -10, -10, { + scale: 2.2 + }); + }, speed * 2); + setTimeout(function() { + g.drawImage(novaEyesStage2(), -10, -10, { + scale: 2.2 + }); + }, speed * 3); + setTimeout(function() { + g.drawImage(novaEyesStage1(), -10, -10, { + scale: 2.2 + }); + }, speed * 4); + if (animation) { + setTimeout(function() { + g.drawImage(novaEyesStage0(), -10, -10, { + scale: 2.2 + }); + }, speed * 5); + } else {} + } else { + + g.drawImage(novaEyesStage4(), -10, -10, { + scale: 2.2 + }); + setTimeout(function() { + g.drawImage(novaEyesWhiteStage0(), -10, -10, { + scale: 2.2 + }); + timedraw(true); + g.drawImage(novaEyesTransStage3(), -10, -10, { + scale: 2.2 + }); + }, speed * 2); + setTimeout(function() { + g.drawImage(novaEyesWhiteStage0(), -10, -10, { + scale: 2.2 + }); + timedraw(true); + g.drawImage(novaEyesTransStage2(), -10, -10, { + scale: 2.2 + }); + }, speed * 3); + setTimeout(function() { + g.drawImage(novaEyesWhiteStage0(), -10, -10, { + scale: 2.2 + }); + timedraw(true); + g.drawImage(novaEyesTransStage1(), -10, -10, { + scale: 2.2 + }); + open = true; + }, speed * 4); + if (animation) { + setTimeout(function() { + g.drawImage(novaEyesWhiteStage0(), -10, -10, { + scale: 2.2 + }); + open = true; + }, speed * 5); + } else {} + } +} + +function novaCloseEyes(speed, white, animation) { + if (!white) { // for other + if (animation) { + g.drawImage(novaEyesStage0(), -10, -10, { + scale: 2.2 + }); + } else {} + setTimeout(function() { + g.drawImage(novaEyesStage1(), -10, -10, { + scale: 2.2 + }); + }, speed * 2); + setTimeout(function() { + g.drawImage(novaEyesStage2(), -10, -10, { + scale: 2.2 + }); + }, speed * 3); + setTimeout(function() { + g.drawImage(novaEyesStage3(), -10, -10, { + scale: 2.2 + }); + }, speed * 4); + setTimeout(function() { + g.drawImage(novaEyesStage4(), -10, -10, { + scale: 2.2 + }); + }, speed * 5); + } else { // for time + + if (animation) { + timedraw(true); + g.drawImage(novaEyesWhiteStage0(), -10, -10, { + scale: 2.2 + }); + } else {} + setTimeout(function() { + timedraw(true); + g.drawImage(novaEyesTransStage1(), -10, -10, { + scale: 2.2 + }); + }, speed * 2); + setTimeout(function() { + timedraw(true); + g.drawImage(novaEyesTransStage2(), -10, -10, { + scale: 2.2 + }); + }, speed * 3); + setTimeout(function() { + timedraw(true); + g.drawImage(novaEyesTransStage3(), -10, -10, { + scale: 2.2 + }); + }, speed * 4); + setTimeout(function() { + g.drawImage(novaEyesStage4(), -10, -10, { + scale: 2.2 + }); + }, speed * 5); + open = false; + } +} + +function timedraw(animation) { + if (open && timemode || animation) { + g.setFont("6x8", 4); + g.setColor("#00F"); + var d = new Date(); + var h = d.getHours(), + m = d.getMinutes(); + g.drawImage(novaEyesWhiteStage0(), -10, -10, { + scale: 2.2 + }); + g.drawImage(novaEyesTransStage1(), -10, -10, { + scale: 2.2 + }); + // Check if single digit + var hourDigits = h.toString(); + if (hourDigits.length === 1) { // if hour digits only one, render in middle + g.drawString(h, 50, 66); + } else { + g.drawString(h, 38, 66); + } + var minutes = m.toString(); + if (minutes.length === 1) { // same for mins + g.drawString(m, 107, 66); + } else { + g.drawString(m, 94, 66); + } + } +} + +function main() { + Bangle.on("lock", function(lock) { + g.drawImage(novaTopRedraw(), -10, novaYPos, { + scale: 2.2 + }); + if (lock) { + novaCloseEyes(200, true, false); + setTimeout(function() { + novaOpenEyes(100, false, false); + timemode = false; + }, 1200); + }else{ + novaCloseEyes(100, false, false); + setTimeout(function() { + timemode = true; + novaOpenEyes(200, true, false); + }, 600); + } + }); +} + + +g.setFont("6x8", 4); +g.setColor("#FFF"); +var open = false; +var timemode = true; +var clockmode; +var novaYPos = -7; +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}); +g.drawImage(star(), 120, -5, {scale:0.8}); + + + +var secondInterval = setInterval(function() { + timedraw(); + g.drawImage(novaTopRedraw(), -10, novaYPos, { + scale: 2.2 + }); +}, 1000); + +novaOpenEyes(300, true, false); +main(); diff --git a/apps/novaclock/app.png b/apps/novaclock/app.png new file mode 100644 index 000000000..6270be5d3 Binary files /dev/null and b/apps/novaclock/app.png differ diff --git a/apps/novaclock/metadata.json b/apps/novaclock/metadata.json new file mode 100644 index 000000000..a8a2c0af1 --- /dev/null +++ b/apps/novaclock/metadata.json @@ -0,0 +1,15 @@ +{ "id": "novaclock", + "name": "Nova Clock", + "shortName":"Nova Clock", + "icon": "app.png", + "type": "clock", + "version":"0.10", + "description": "A clock inspired by the Kirby series", + "tags": "clock", + "supports": ["BANGLEJS2"], + "readme":"README.md", + "storage": [ + {"name":"novaclock.app.js","url":"app.js"}, + {"name":"novaclock.img","url":"app-icon.js","evaluate":true} + ] +} 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/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 7b83706bf..93217e76f 100644 --- a/apps/pongclock/ChangeLog +++ b/apps/pongclock/ChangeLog @@ -1 +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 dbd044517..3447893ab 100644 --- a/apps/pongclock/metadata.json +++ b/apps/pongclock/metadata.json @@ -2,8 +2,9 @@ "name": "Pong Clock", "shortName":"Pong Clock", "icon": "pongclock.png", - "version":"0.01", + "version":"0.03", "description": "A Pong playing clock", + "type": "clock", "tags": "", "allow_emulator":true, "supports": ["BANGLEJS", "BANGLEJS2"], 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/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/rebble/ChangeLog b/apps/rebble/ChangeLog index 4b415c1c5..4e2e76484 100644 --- a/apps/rebble/ChangeLog +++ b/apps/rebble/ChangeLog @@ -6,3 +6,4 @@ 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 diff --git a/apps/rebble/metadata.json b/apps/rebble/metadata.json index e28c67784..ec7650f53 100644 --- a/apps/rebble/metadata.json +++ b/apps/rebble/metadata.json @@ -2,7 +2,7 @@ "id": "rebble", "name": "Rebble Clock", "shortName": "Rebble", - "version": "0.08", + "version": "0.09", "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 8ba61f818..fc91fe0ac 100644 --- a/apps/rebble/rebble.app.js +++ b/apps/rebble/rebble.app.js @@ -236,7 +236,7 @@ function drawBattery(x,y,wi,hi) { g.clearRect(x+2,y+2+2,x+wi-4-2,y+2+hi-2); // centre g.setColor(g.theme.fg); g.fillRect(x+wi-3,y+2+(((hi - 1)/2)-1),x+wi-2,y+2+(((hi - 1)/2)-1)+4); // contact - g.fillRect(x+3, y+5, x +4 + E.getBattery()*(wi-12)/100, y+hi-1); // the level + g.fillRect(x+3, y+5, x +3 + E.getBattery()*(wi-10)/100, y+hi-1); // the level if( Bangle.isCharging() ) { 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 @@ - - - - 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/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 5786741c7..2dfe8336d 100644 --- a/apps/widbaroalarm/ChangeLog +++ b/apps/widbaroalarm/ChangeLog @@ -1,3 +1,9 @@ 0.01: Initial version -0.02: Do not warn multiple times for the same exceedance +0.02: Do not warn multiple times for the same exceed 0.03: Fix crash +0.04: Use Prompt with dismiss and pause + Improve barometer value median calculation +0.05: Fix warning calculation + Show difference of last measurement to pressure average of the the last three hours in the widget + Only use valid pressure values +0.06: Fix exception diff --git a/apps/widbaroalarm/README.md b/apps/widbaroalarm/README.md index fdc239170..478b48a71 100644 --- a/apps/widbaroalarm/README.md +++ b/apps/widbaroalarm/README.md @@ -15,10 +15,13 @@ Get a notification when the pressure reaches defined thresholds. 0 to disable this alarm. * Show widget: Enable/disable widget visibility * Buzz on alarm: Enable/disable buzzer on alarm - +* Dismiss delay: Delay added before the next alert if the alert is dismissed. From 5 to 60 min +* Pause delay: Same as Dismiss delay but longer (useful for meetings and such). From 30 to 240 min ## Widget -The widget shows two rows: 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/default.json b/apps/widbaroalarm/default.json index 3d81baa81..696c70819 100644 --- a/apps/widbaroalarm/default.json +++ b/apps/widbaroalarm/default.json @@ -7,5 +7,7 @@ "drop3halarm": 2, "raise3halarm": 0, "show": true, - "interval": 15 + "interval": 15, + "dismissDelayMin": 15, + "pauseDelayMin": 60 } diff --git a/apps/widbaroalarm/metadata.json b/apps/widbaroalarm/metadata.json index 134f03623..ba6c47b37 100644 --- a/apps/widbaroalarm/metadata.json +++ b/apps/widbaroalarm/metadata.json @@ -2,14 +2,14 @@ "id": "widbaroalarm", "name": "Barometer Alarm Widget", "shortName": "Barometer Alarm", - "version": "0.03", + "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"], - "dependencies": {"notify":"type"}, "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/settings.js b/apps/widbaroalarm/settings.js index bea6319d1..ee8ce82c2 100644 --- a/apps/widbaroalarm/settings.js +++ b/apps/widbaroalarm/settings.js @@ -87,6 +87,26 @@ }, onchange: x => save('buzz', x) }, + 'Dismiss delay': { + value: settings.dismissDelayMin, + min: 5, max: 60, + onchange: v => { + save('dismissDelayMin', v) + }, + format: x => { + return x + " min"; + } + }, + 'Pause delay': { + value: settings.pauseDelayMin, + min: 30, max: 240, + onchange: v => { + save('pauseDelayMin', v) + }, + format: x => { + return x + " min"; + } + }, }; E.showMenu(menu); } diff --git a/apps/widbaroalarm/widget.js b/apps/widbaroalarm/widget.js index 2745db8ad..d877c4384 100644 --- a/apps/widbaroalarm/widget.js +++ b/apps/widbaroalarm/widget.js @@ -1,239 +1,304 @@ (function() { - let medianPressure; - let threeHourAvrPressure; - let currentPressures = []; +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, title) { - if (body == undefined) return; + 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 - require("notify").show({ - title: title || "Pressure", - body: body, - icon: require("heatshrink").decompress(atob("jEY4cA///gH4/++mkK30kiWC4H8x3BGDmSGgYDCgmSoEAg3bsAIDpAIFkmSpMAm3btgIFDQwIGNQpTYkAIJwAHEgMoCA0JgMEyBnBCAW3KoQQDhu3oAIH5JnDBAW24IIBEYm2EYwACBCIACA")) - }); - - if (setting("buzz") && - !(storage.readJSON('setting.json', 1) || {}).quiet) { - Bangle.buzz(); + if (v == 1) { + saveSetting(key, tsNow); } - } - - - function didWeAlreadyWarn(key) { - return setting(key) == undefined || setting(key) > 0; - } - - function checkForAlarms(pressure) { - if (pressure == undefined || pressure <= 0) return; - - let alreadyWarned = false; - - const ts = Math.round(Date.now() / 1000); // seconds - const d = { - "ts": ts, - "p": pressure - }; - - // delete entries older than 3h - for (let i = 0; i < history3.length; i++) { - if (history3[i]["ts"] < ts - (3 * 60 * 60)) { - history3.shift(); - } + 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')); } - // delete oldest entries until we have max 50 - while (history3.length > 50) { + if (v == 3) { + // save timestamp of the future so that we do not warn again for the same + // event until then + saveSetting(key, tsNow + 60 * setting('pauseDelayMin')); + } + stop = false; + load(); + }); + + if (setting("buzz") && !(storage.readJSON('setting.json', 1) || {}).quiet) { + Bangle.buzz(); + } + + setTimeout(function() { + stop = false; + load(); + }, 20000); +} + +/* + * returns true if an alarm should be triggered + */ +function doWeNeedToAlarm(key) { + const tsNow = Math.round(Date.now() / 1000); // seconds + return setting(key) == undefined || setting(key) == 0 || tsNow > setting(key); +} + +function isValidPressureValue(pressure) { + if (pressure == undefined || pressure <= 0) + return false; + return pressure > 800 && pressure < 1200; // very rough values +} + +function handlePressureValue(pressure) { + if (pressure == undefined || pressure <= 0) + return; + + const ts = Math.round(Date.now() / 1000); // seconds + const d = {"ts" : ts, "p" : pressure}; + + history3.push(d); + + // delete oldest entries until we have max 50 + while (history3.length > 50) { + history3.shift(); + } + + // delete entries older than 3h + for (let i = 0; i < history3.length; i++) { + if (history3[i]["ts"] < ts - (3 * 60 * 60)) { history3.shift(); + } else { + break; } + } - if (setting("lowalarm")) { - // Is below the alarm threshold? - if (pressure <= setting("min")) { - if (!didWeAlreadyWarn("lastLowWarningTs")) { - showAlarm("Pressure low: " + Math.round(pressure) + " hPa"); - saveSetting("lastLowWarningTs", ts); - 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("lastLowWarningTs", 0); + saveSetting("lowWarnTs", 0); } + } - if (setting("highalarm")) { - // Is above the alarm threshold? - if (pressure >= setting("max")) { - if (!didWeAlreadyWarn("lastHighWarningTs")) { - showAlarm("Pressure high: " + Math.round(pressure) + " hPa"); - saveSetting("lastHighWarningTs", ts); - 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("lastHighWarningTs", 0); + 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 - if (history3[0]["ts"] > ts - (30 * 60)) { - 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) { - const diff = oldestPressure - pressure; + const diffPressure = Math.abs(oldestPressure - pressure); // drop alarm if (drop3halarm > 0 && oldestPressure > pressure) { - if (Math.abs(diff) > drop3halarm) { - if (!didWeAlreadyWarn("lastDropWarningTs")) { - showAlarm((Math.round(Math.abs(diff) * 10) / 10) + " hPa/3h from " + - Math.round(oldestPressure) + " to " + Math.round(pressure) + " hPa", "Pressure drop"); - saveSetting("lastDropWarningTs", ts); + 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 (Math.abs(diff) > raise3halarm) { - if (!didWeAlreadyWarn("lastRaiseWarningTs")) { - showAlarm((Math.round(Math.abs(diff) * 10) / 10) + " hPa/3h from " + - Math.round(oldestPressure) + " to " + Math.round(pressure) + " hPa", "Pressure raise"); - saveSetting("lastRaiseWarningTs", ts); + 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 +function calculcate3hAveragePressure() { + if (history3 != undefined && history3.length > 0) { let sum = 0; for (let i = 0; i < history3.length; i++) { sum += history3[i]["p"]; } threeHourAvrPressure = sum / history3.length; + } else { + threeHourAvrPressure = undefined; } +} +/* + turn on barometer power + take multiple measurements + sort the results + take the middle one (median) + turn off barometer power +*/ +function getPressureValue() { + if (stop) + return; + const MEDIANLENGTH = 20; + Bangle.setBarometerPower(true, "widbaroalarm"); + Bangle.on('pressure', function(e) { + while (currentPressures.length > MEDIANLENGTH) + currentPressures.pop(); + const pressure = e.pressure; + if (isValidPressureValue(pressure)) { + currentPressures.unshift(pressure); + median = currentPressures.slice().sort(); - function baroHandler(data) { - if (data) { - const pressure = Math.round(data.pressure); - if (pressure == undefined || pressure <= 0) return; - currentPressures.push(pressure); + if (median.length > 10) { + var mid = median.length >> 1; + medianPressure = Math.round(E.sum(median.slice(mid - 4, mid + 5)) / 9); + if (medianPressure > 0) { + turnOff(); + draw(); + handlePressureValue(medianPressure); + } + } } + }); + + setTimeout(function() { turnOff(); }, 30000); +} + +function turnOff() { + if (Bangle.isBarometerOn()) + Bangle.setBarometerPower(false, "widbaroalarm"); +} + +function draw() { + if (global.WIDGETS != undefined && typeof global.WIDGETS === "object") { + global.WIDGETS["baroalarm"] = { + width : setting("show") ? 24 : 0, + area : "tr", + draw : draw + }; } + g.reset(); - /* - turn on barometer power - take 5 measurements - sort the results - take the middle one (median) - turn off barometer power - */ - function check() { - Bangle.setBarometerPower(true, "widbaroalarm"); - setTimeout(function() { - currentPressures = []; + if (this.x == undefined || this.y != 0) + return; // widget not yet there - Bangle.getPressure().then(baroHandler); - Bangle.getPressure().then(baroHandler); - Bangle.getPressure().then(baroHandler); - Bangle.getPressure().then(baroHandler); - Bangle.getPressure().then(baroHandler); + g.clearRect(this.x, this.y, this.x + this.width - 1, this.y + 23); - setTimeout(function() { - Bangle.setBarometerPower(false, "widbaroalarm"); + if (setting("show")) { + g.setFont("6x8", 1).setFontAlign(1, 0); + if (medianPressure == undefined) { + // trigger a new check + getPressureValue(); - currentPressures.sort(); - - // take median value - medianPressure = currentPressures[3]; - checkForAlarms(medianPressure); - }, 1000); - }, 500); - } - - function reload() { - check(); - } - - function draw() { - if (global.WIDGETS != undefined && typeof WIDGETS === "object") { - WIDGETS["baroalarm"] = { - width: setting("show") ? 24 : 0, - reload: reload, - area: "tr", - draw: draw - }; - } - - g.reset(); - if (setting("show") && medianPressure != undefined) { - g.setFont("6x8", 1).setFontAlign(1, 0); + // 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); } } } +} - // Let's delay the first check a bit - setTimeout(function() { - check(); - if (interval > 0) { - setInterval(check, interval * 60000); - } - }, 1000); - +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..096f85787 100644 --- a/apps/widgps/ChangeLog +++ b/apps/widgps/ChangeLog @@ -4,3 +4,5 @@ 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. \ No newline at end of file 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..14cdb81d4 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.08", + "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..ee34d174c 100644 --- a/apps/widgps/widget.js +++ b/apps/widgps/widget.js @@ -1,36 +1,84 @@ -(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.draw(); + return isGPSon; +}; + +WIDGETS.gps = { + area : "tr", + width : 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/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/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/apps/wristlight/Icon.png b/apps/wristlight/Icon.png new file mode 100644 index 000000000..0bbcd3e36 Binary files /dev/null and b/apps/wristlight/Icon.png differ diff --git a/apps/wristlight/README.md b/apps/wristlight/README.md new file mode 100644 index 000000000..e69d61b76 --- /dev/null +++ b/apps/wristlight/README.md @@ -0,0 +1,9 @@ +# Wrist Light + +A flash light on your wrist with different colors + +![](screenshot.png) +![](screenshot_red.png) + +## Creator +[@pidajo](https://github.com/pidajo) diff --git a/apps/wristlight/app.js b/apps/wristlight/app.js new file mode 100644 index 000000000..24bc1eab2 --- /dev/null +++ b/apps/wristlight/app.js @@ -0,0 +1,52 @@ +function draw(color) { + if (color == undefined) { + color = -1; + } + g.clear(); + g.setColor(color); + g.fillRect(0, 0, g.getWidth(), g.getHeight()); +} + +function draw2Pattern() { + colors = ["ff0000", "8080ff", "00ff00", + "ffffff"]; + drawPattern(2, colors); +} + +function draw3Pattern() { + colors = ["ff0000", "00ff00", "0000ff", + "ff00ff", "ffffff", "00ffff", + "ffff00", "ff8000", "ff0080"]; + drawPattern(3, colors); +} + +function drawPattern(size, colors) { + g.clear(); + var w = g.getWidth() / size; + var h = g.getHeight() / size; + for (var i = 0; i < size; i++) { + for (var j = 0; j < size; j++) { + var color = colors[i*size + j]; + g.setColor("#" + color); + g.fillRect(j * w, i * h, j * w + w, i * h + h); + } + } + Bangle.on("touch", function(btn, xy) { + var x = parseInt((xy.x) / w); + var y = parseInt((xy.y) / h); + draw("#" + colors[y * size + x]); + }); +} + +// Clear the screen once, at startup +// draw immediately at first +draw3Pattern(); + +/* +require("Storage").write("wristlight.info",{ + "id":"wristlight", + "name":"Wrist Light", + "src":"wristlight.app.js", + "icon":"wristlight.img" +}); +*/ diff --git a/apps/wristlight/metadata.json b/apps/wristlight/metadata.json new file mode 100644 index 000000000..af1d700df --- /dev/null +++ b/apps/wristlight/metadata.json @@ -0,0 +1,16 @@ +{ "id": "wristlight", + "name": "Wrist Light", + "shortName":"Wrist Light", + "icon": "wristlight48.png", + "version":"0.01", + "description": "A flash light with different colors on your wrist", + "tags": "flash,light", + "allow_emulator":true, + "supports": ["BANGLEJS", "BANGLEJS2"], + "readme":"README.md", + "screenshots" : [ { "url":"screenshot.png" }, { "url":"screenshot_red.png" } ], + "storage": [ + {"name":"wristlight.app.js","url":"app.js"}, + {"name":"wristlight.img","url":"wristlight-icon.js","evaluate":true} + ] +} diff --git a/apps/wristlight/screenshot.png b/apps/wristlight/screenshot.png new file mode 100644 index 000000000..31850d1ca Binary files /dev/null and b/apps/wristlight/screenshot.png differ diff --git a/apps/wristlight/screenshot_red.png b/apps/wristlight/screenshot_red.png new file mode 100644 index 000000000..06ac7a190 Binary files /dev/null and b/apps/wristlight/screenshot_red.png differ diff --git a/apps/wristlight/wristlight-icon.js b/apps/wristlight/wristlight-icon.js new file mode 100644 index 000000000..689372499 --- /dev/null +++ b/apps/wristlight/wristlight-icon.js @@ -0,0 +1 @@ +atob("MDCEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMzMzMzMAN3d3d3d3QDu7u7u7gAAAAAADMzMzMzMDd3d3d3d3dDu7u7u7uAAAAAAzMzMzMzMDd3d3d3d3dDu7u7u7u4AAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu7u7u7uwAAAA//8AAAAHd3d3d3cAAAALu7u7u7u7AAD/////AAB3d3d3d3dwAAALu7u7u7u7AA//////8AB3d3d3d3dwAAALu7u7u7u7AP///////wB3d3d3d3dwAAALu7u7u7u7AP///////wB3d3d3d3dwAAALu7u7u7u7D/////////B3d3d3d3dwAAALu7u7u7u7D/////////B3d3d3d3dwAAALu7u7u7u7D/////////B3d3d3d3dwAAALu7u7u7u7A////////zB3d3d3d3dwAAALu7u7u7u7AP///////wB3d3d3d3dwAAALu7u7u7u7AP///////wB3d3d3d3dwAAALu7u7u7u7AA//////8AB3d3d3d3dwAAALu7u7u7u7AAD/////AAB3d3d3d3dwAAAMu7u7u7u8AAACMzMgAAB3d3d3d3dwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALu7u7u7u7AIiIiIiIiACIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAAAu7u7u7u7CYiIiIiIiJCIiIiIiIgAAAAAq7u7u7u7CYiIiIiIiJCIiIiIiIAAAAAAALu7u7u7CoiIiIiIiKCIiIiIiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") diff --git a/apps/wristlight/wristlight48.png b/apps/wristlight/wristlight48.png new file mode 100644 index 000000000..8f0ff59f7 Binary files /dev/null and b/apps/wristlight/wristlight48.png differ diff --git a/backup.js b/backup.js 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/create_apps_json.sh b/bin/create_apps_json.sh index 665079fc6..30c58bc93 100755 --- a/bin/create_apps_json.sh +++ b/bin/create_apps_json.sh @@ -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_c.js b/bin/firmwaremaker_c.js index 7940e551d..4aa8e087f 100755 --- a/bin/firmwaremaker_c.js +++ b/bin/firmwaremaker_c.js @@ -1,4 +1,4 @@ -#!/usr/bin/nodejs +#!/usr/bin/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/sanitycheck.js b/bin/sanitycheck.js index 81c0f75ac..9939b061b 100755 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -17,13 +17,21 @@ 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||{}; + console.log(`::warning${Object.keys(opt).length?" ":""}${Object.keys(opt).map(k=>k+"="+opt[k]).join(",")}::${msg}`); + warningCount++; } var apps = []; @@ -43,16 +51,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 +77,15 @@ 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? +}; function globToRegex(pattern) { const ESCAPE = '.*+-?^${}()|[]\\'; @@ -87,87 +104,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 +208,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 +223,11 @@ 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}); } } for (const key in file) { - if (!STORAGE_KEYS.includes(key)) ERROR(`App ${app.id} file ${file.name} has unknown key ${key}`); + if (!STORAGE_KEYS.includes(key)) ERROR(`App ${app.id} file ${file.name} has unknown key ${key}`, {file:appDirRelative+file.url}); } // warn if JS icon is the wrong size if (file.name == app.id+".img") { @@ -209,44 +238,44 @@ apps.forEach((app,appIdx) => { 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 +285,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 +329,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..278def4e4 100755 --- a/bin/thumbnailer.js +++ b/bin/thumbnailer.js @@ -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 e9887f3d4..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 = "2v13"; +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 6b59e68e7..85482213c 100644 --- a/modules/ClockFace.md +++ b/modules/ClockFace.md @@ -174,4 +174,42 @@ Bangle.on('step', function(steps) { if (clock.paused === false) // draw step count }); +``` + + +ClockFace_menu +============== +If your clock comes with a settings menu, you can use this library to easily add +some common options: + +```js + +let settings = require("Storage").readJSON(".settings.json", true)||{}; +function save(key, value) { + settings[key] = value; + require("Storage").writeJSON(".settings.json", settings); +} + +let menu = { + "": {"title": /*LANG*/" Settings"}, +}; +require("ClockFace_menu").addItems(menu, save, { + showDate: settings.showDate, + loadWidgets: settings.loadWidgets, +}); +E.showMenu(menu); + +``` + +Or even simpler, if you just want to use a basic settings file: +```js +let menu = { + "": {"title": /*LANG*/" Settings"}, + /*LANG*/"< Back": back, +}; +require("ClockFace_menu").addSettingsFile(menu, ".settings.json", [ + "showDate", "loadWidgets", "powerSave", +]); +E.showMenu(menu); + ``` \ No newline at end of file diff --git a/modules/ClockFace_menu.js b/modules/ClockFace_menu.js index cd99ea39f..a1dd76fee 100644 --- a/modules/ClockFace_menu.js +++ b/modules/ClockFace_menu.js @@ -1,10 +1,52 @@ -// boolean options, which default to true -exports.showDate = -exports.loadWidgets = - function(value, callback) { - if (value === undefined) value = true; - return { - value: !!value, - onchange: v=>callback(v), - }; - }; +/** + * Add setting items to a menu + * + * @param {object} menu Menu to add items to + * @param {function} callback Callback when value changes + * @param {object} items Menu items to add, with their current value + */ +exports.addItems = function(menu, callback, items) { + Object.keys(items).forEach(key => { + let value = items[key]; + const label = { + showDate:/*LANG*/"Show date", + loadWidgets:/*LANG*/"Load widgets", + powerSave:/*LANG*/"Power saving", + }[key]; + switch(key) { + // boolean options which default to true + case "showDate": + case "loadWidgets": + if (value===undefined) value = true; + // fall through + case "powerSave": + // same for all boolean options: + menu[label] = { + value: !!value, + onchange: v => callback(key, v), + }; + } + }); +}; + +/** + * Create a basic settings menu for app, reading/writing to settings file + * + * @param {object} menu Menu to add settings to + * @param {string} settingsFile File to read/write settings to/from + * @param {string[]} items List of settings to add + */ +exports.addSettingsFile = function(menu, settingsFile, items) { + let s = require("Storage").readJSON(settingsFile, true) || {}; + + function save(key, value) { + s[key] = value; + require("Storage").writeJSON(settingsFile, s); + } + + let toAdd = {}; + items.forEach(function(key) { + toAdd[key] = s[key]; + }); + exports.addItems(menu, save, toAdd); +}; \ No newline at end of file diff --git a/modules/Layout.js b/modules/Layout.js index 19cfabe11..fdcf0ceae 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -2,15 +2,22 @@ // See Layout.md for documentation -function Layout(layout, options) { +/* Minify to 'Layout.min.js' by: + + * checking out: https://github.com/espruino/EspruinoDocs + * run: ../EspruinoDocs/bin/minify.js modules/Layout.js modules/Layout.min.js + +*/ + + +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 = []; @@ -29,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 @@ -71,7 +78,7 @@ function Layout(layout, options) { Layout.prototype.setUI = function() { Bangle.setUI(); // remove all existing input handlers - var uiSet; + let uiSet; if (this.buttons) { // multiple buttons so we'll jus use back/next/select Bangle.setUI({mode:"updown", back:this.options.back}, dir=>{ diff --git a/modules/Layout.min.js b/modules/Layout.min.js new file mode 100644 index 000000000..b5a924358 --- /dev/null +++ b/modules/Layout.min.js @@ -0,0 +1,14 @@ +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){.75=d.x&&a.y>=d.y&&a.x<=d.x+d.w&&a.y<=d.y+d.h&&(2==a.type&&d.cbl?d.cbl(a):d.cb&&d.cb(a));d.c&&d.c.forEach(f=>k(f,a))}Bangle.touchHandler=(d,a)=>k(this._l,a);Bangle.on("touch",Bangle.touchHandler)}}; +p.prototype.render=function(b){function k(c){"ram";g.reset();void 0!==c.col&&g.setColor(c.col);void 0!==c.bgCol&&g.setBgColor(c.bgCol).clearRect(c.x,c.y,c.x+c.w-1,c.y+c.h-1);d[c.type](c)}b||(b=this._l);this.updateNeeded&&this.update();var d={"":function(){},txt:function(c){if(c.wrap){g.setFont(c.font).setFontAlign(0,-1);var l=g.wrapString(c.label,c.w),e=c.y+(c.h-g.getFontHeight()*l.length>>1);l.forEach((n,q)=>g.drawString(n,c.x+(c.w>>1),e+g.getFontHeight()*q))}else g.setFont(c.font).setFontAlign(0, +0,c.r).drawString(c.label,c.x+(c.w>>1),c.y+(c.h>>1))},btn:function(c){var l=c.x+(0|c.pad),e=c.y+(0|c.pad),n=c.w-(c.pad<<1),q=c.h-(c.pad<<1);l=[l,e+4,l+4,e,l+n-5,e,l+n-1,e+4,l+n-1,e+q-5,l+n-5,e+q-1,l+4,e+q-1,l,e+q-5,l,e+4];e=c.selected?g.theme.bgH:g.theme.bg2;g.setColor(e).fillPoly(l).setColor(c.selected?g.theme.fgH:g.theme.fg2).drawPoly(l);void 0!==c.col&&g.setColor(c.col);c.src?g.setBgColor(e).drawImage("function"==typeof c.src?c.src():c.src,c.x+c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5*Math.PI* +(c.r||0)}):g.setFont(c.font||"6x8:2").setFontAlign(0,0,c.r).drawString(c.label,c.x+c.w/2,c.y+c.h/2)},img:function(c){g.drawImage("function"==typeof c.src?c.src():c.src,c.x+c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5*Math.PI*(c.r||0)})},custom:function(c){c.render(c)},h:function(c){c.c.forEach(k)},v:function(c){c.c.forEach(k)}};if(this.lazy){this.rects||(this.rects={});var a=this.rects.clone(),f=[];r(b,a,f,this.rects,null);for(var h in a)delete this.rects[h];b=Object.keys(a).map(c=>a[c]).reverse(); +for(var m of b)g.setBgColor(m.bg).clearRect.apply(g,m);f.forEach(k)}else k(b)};p.prototype.forgetLazyState=function(){this.rects={}};p.prototype.layout=function(b){switch(b.type){case "h":var k=b.x+(0|b.pad),d=0,a=b.c&&b.c.reduce((e,n)=>e+(0|n.fillx),0);a||(k+=b.w-b._w>>1,a=1);var f=k;b.c.forEach(e=>{e.x=0|f;k+=e._w;d+=0|e.fillx;f=k+Math.floor(d*(b.w-b._w)/a);e.w=0|f-e.x;e.h=0|(e.filly?b.h-(b.pad<<1):e._h);e.y=0|b.y+(0|b.pad)+((1+(0|e.valign))*(b.h-(b.pad<<1)-e.h)>>1);e.c&&this.layout(e)});break; +case "v":var h=b.y+(0|b.pad),m=0,c=b.c&&b.c.reduce((e,n)=>e+(0|n.filly),0);c||(h+=b.h-b._h>>1,c=1);var l=h;b.c.forEach(e=>{e.y=0|l;h+=e._h;m+=0|e.filly;l=h+Math.floor(m*(b.h-b._h)/c);e.h=0|l-e.y;e.w=0|(e.fillx?b.w-(b.pad<<1):e._w);e.x=0|b.x+(0|b.pad)+((1+(0|e.halign))*(b.w-(b.pad<<1)-e.w)>>1);e.c&&this.layout(e)})}};p.prototype.debug=function(b,k){b||(b=this._l);k=k||1;g.setColor(k&1,k&2,k&4).drawRect(b.x+k-1,b.y+k-1,b.x+b.w-k,b.y+b.h-k);b.pad&&g.drawRect(b.x+b.pad-1,b.y+b.pad-1,b.x+b.w-b.pad,b.y+ +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 \ 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/runtest.sh b/tests/Layout/bin/runtest.sh index e06dec86b..9bac72283 100755 --- a/tests/Layout/bin/runtest.sh +++ b/tests/Layout/bin/runtest.sh @@ -18,24 +18,35 @@ SRCJS=$1 SRCBMP=$SRCDIR/`basename $SRCJS .js`.bmp echo "TEST $SRCJS ($SRCBMP)" -cat ../../modules/Layout.js > $TESTJS -echo 'Bangle = { setUI : function(){}, appRect:{x:0,y:0,w:176,h:176,x2:175,y2:175} };BTN1=0;process.env = process.env;process.env.HWVERSION=2;' >> $TESTJS -echo 'g = Graphics.createArrayBuffer(176,176,4);' >> $TESTJS -cat $SRCJS >> $TESTJS || exit 1 -echo 'layout.render()' >> $TESTJS -#echo 'layout.debug()' >> $TESTJS -echo 'require("fs").writeFileSync("'$TESTBMP'",g.asBMP())' >> $TESTJS +run_test () { + LAYOUTFILE=$1 + echo 'exports = {};' > $TESTJS + cat $LAYOUTFILE >> $TESTJS + echo ';' >> $TESTJS + echo 'Layout = exports;' >> $TESTJS + echo 'Bangle = { setUI : function(){}, appRect:{x:0,y:0,w:176,h:176,x2:175,y2:175} };BTN1=0;process.env = process.env;process.env.HWVERSION=2;' >> $TESTJS + echo 'g = Graphics.createArrayBuffer(176,176,4);' >> $TESTJS + cat $SRCJS >> $TESTJS || exit 1 + echo 'layout.render()' >> $TESTJS + #echo 'layout.debug()' >> $TESTJS + echo 'require("fs").writeFileSync("'$TESTBMP'",g.asBMP())' >> $TESTJS + echo ============================================= + echo TESTING $LAYOUTFILE $SRCJS + bin/espruino $TESTJS || exit 1 + if ! cmp $TESTBMP $SRCBMP >/dev/null 2>&1 + then + echo ============================================= + echo $LAYOUTFILE + echo $TESTBMP $SRCBMP differ + echo ============================================== + convert "+append" $TESTBMP $SRCBMP testresult.bmp + display testresult.bmp + exit 1 + else + echo Files are the same + exit 0 + fi +} -bin/espruino $TESTJS || exit 1 -if ! cmp $TESTBMP $SRCBMP >/dev/null 2>&1 -then - echo ============================================= - echo $TESTBMP $SRCBMP differ - echo ============================================== - convert "+append" $TESTBMP $SRCBMP testresult.bmp - display testresult.bmp - exit 1 -else - echo Files are the same - exit 0 -fi +run_test ../../modules/Layout.js +run_test ../../modules/Layout.min.js diff --git a/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" +}