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)
================================
-[](https://app.travis-ci.com/github/espruino/BangleApps)
+[](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.
+
+
+
+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.

## 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.
+
+
+
+## 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 Upload
+ 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.
+
+
+
+
+ Title
+ Command
+
+
+
+
+
+
+
+ Save to Bangle.js
+
+
+
+
+
+
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
-
-
-
- Name
- Lat.
- Long.
- Actions
-
-
-
-
-
-
-
- Add a new waypoint
-
-
- Reload Upload
-
-
-
-
-
-
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.
+
+
+
+
+# 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:
+
+
+
+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
+
+ Upload
+
+
+
+
+
+
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 @@
+
+ Start
+ Stop
+ Reset
+ Save CSV
+ Download CSV
+
+
+
+
+
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
@@ -12,13 +13,51 @@