diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index 0d837fe43..59cb23a46 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -5,3 +5,4 @@ 0.04: Android icon now goes to settings page with 'find phone' 0.05: Fix handling of message actions 0.06: Option to keep messages after a disconnect (default false) (fix #1186) +0.07: Include charging state in battery updates to phone diff --git a/apps/android/boot.js b/apps/android/boot.js index fff9ad444..eb3d26c6e 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -50,8 +50,9 @@ }; // Battery monitor - function sendBattery() { gbSend({ t: "status", bat: E.getBattery() }); } + function sendBattery() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); } NRF.on("connect", () => setTimeout(sendBattery, 2000)); + Bangle.on("charging", sendBattery); if (!settings.keep) NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect setInterval(sendBattery, 10*60*1000); diff --git a/apps/android/metadata.json b/apps/android/metadata.json index 6b780ff55..d126b869a 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -2,7 +2,7 @@ "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.06", + "version": "0.07", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", "icon": "app.png", "tags": "tool,system,messages,notifications,gadgetbridge", diff --git a/apps/gbridge/ChangeLog b/apps/gbridge/ChangeLog index 67d421f33..059767ece 100644 --- a/apps/gbridge/ChangeLog +++ b/apps/gbridge/ChangeLog @@ -26,3 +26,4 @@ 0.24: tag HRM power requests to allow this to work alongside other widgets/apps (fix #799) 0.25: workaround call notification Fix inflated step number +0.26: Include charging status in battery updates to phone diff --git a/apps/gbridge/PROTOCOL.md b/apps/gbridge/PROTOCOL.md index 1fd0ddb4a..7191ca0b1 100644 --- a/apps/gbridge/PROTOCOL.md +++ b/apps/gbridge/PROTOCOL.md @@ -11,11 +11,12 @@ t can be one of "info", "warn", "error" ## report battery level ``` -{ "t": "status", "bat": 30, "volt": 30 } +{ "t": "status", "bat": 30, "volt": 30, "chg": 0 } ``` * bat is in range 0 to 100 * volt is optional and should be greater than 0 +* chg is optional and should be either 0 or 1 to indicate the watch is charging ## find phone diff --git a/apps/gbridge/metadata.json b/apps/gbridge/metadata.json index cdbc95c11..0c46aa852 100644 --- a/apps/gbridge/metadata.json +++ b/apps/gbridge/metadata.json @@ -1,7 +1,7 @@ { "id": "gbridge", "name": "Gadgetbridge", - "version": "0.25", + "version": "0.26", "description": "(NOT RECOMMENDED) Displays Gadgetbridge notifications from Android. Please use the 'Android' Bangle.js app instead.", "icon": "app.png", "type": "widget", diff --git a/apps/gbridge/widget.js b/apps/gbridge/widget.js index 7cb7147ec..3b5f2c780 100644 --- a/apps/gbridge/widget.js +++ b/apps/gbridge/widget.js @@ -256,7 +256,7 @@ } function sendBattery() { - gbSend({ t: "status", bat: E.getBattery() }); + gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); } // Send a summary of activity to Gadgetbridge @@ -268,6 +268,7 @@ // Battery monitor NRF.on("connect", () => setTimeout(sendBattery, 2000)); + Bangle.on("charging", sendBattery); setInterval(sendBattery, 10*60*1000); sendBattery(); // Activity monitor diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog index a693b2a83..1e4864af8 100644 --- a/apps/health/ChangeLog +++ b/apps/health/ChangeLog @@ -9,3 +9,4 @@ 0.08: Suppress bleed through of E.showMenu's when displaying bar charts 0.09: Fix file naming so months are 1-based (not 0) (fix #1119) 0.10: Adds additional 3 minute setting for HRM +0.11: Pre-minified boot&lib - folds constants and saves RAM diff --git a/apps/health/README.md b/apps/health/README.md index c69e2e45b..f44854e3e 100644 --- a/apps/health/README.md +++ b/apps/health/README.md @@ -34,6 +34,11 @@ writes data to a binary file (one per month). A library (that can be used with `require("health").readXYZ` can then be used to grab historical health info. +`boot.js` and `lib.js` include some constants that don't get inlined by the simple +minifier used in the App Loader, so we use the closure compiler to pre-minify them. +The easiest way to use it is to install `https://github.com/espruino/EspruinoDocs` +and run `EspruinoDocs/bin/minify.js lib.js lib.min.js` + ## TODO * `interface` page for desktop to allow data to be viewed and exported in common formats diff --git a/apps/health/boot.min.js b/apps/health/boot.min.js new file mode 100644 index 000000000..00313a1f5 --- /dev/null +++ b/apps/health/boot.min.js @@ -0,0 +1,4 @@ +(function(){var a=0|(require("Storage").readJSON("health.json",1)||{}).hrm;if(1==a||2==a){function f(){Bangle.setHRMPower(1,"health");setTimeout(()=>Bangle.setHRMPower(0,"health"),6E4*a);if(1==a)for(var b=1;2>=b;b++)setTimeout(()=>{Bangle.setHRMPower(1,"health");setTimeout(()=>{Bangle.setHRMPower(0,"health")},2E5*b+6E4)},2E5*b)}Bangle.on("health",f);Bangle.on("HRM",b=>{80{function f(c){return String.fromCharCode(c.steps>>8,c.steps&255,c.bpm,Math.min(c.movement/8,255))}var b=new Date(Date.now()-59E4),e=function(c){return 145*(c.getDate()-1)+6*c.getHours()+(0|6*c.getMinutes()/60)}(b);b=function(c){return"health-"+c.getFullYear()+"-"+(c.getMonth()+1)+".raw"}(b);var g=require("Storage").read(b);if(g){var d=g.substr(8+4*e,4);if("\u00ff\u00ff\u00ff\u00ff"!=d){print("HEALTH ERR: Already written!");return}}else require("Storage").write(b, +"HEALTH1\x00",0,17988);var h=8+4*e;require("Storage").write(b,f(a),h,17988);if(143==e%145)if(e=h+4,"\u00ff\u00ff\u00ff\u00ff"!=g.substr(e,4))print("HEALTH ERR: Daily summary already written!");else{a={steps:0,bpm:0,movement:0,movCnt:0,bpmCnt:0};for(var k=0;144>k;k++)d=g.substr(h,4),"\u00ff\u00ff\u00ff\u00ff"!=d&&(a.steps+=(d.charCodeAt(0)<<8)+d.charCodeAt(1),a.movement+=d.charCodeAt(2),a.movCnt++,d=d.charCodeAt(2),a.bpm+=d,d&&a.bpmCnt++),h-=4;a.bpmCnt&&(a.bpm/=a.bpmCnt);a.movCnt&&(a.movement/=a.movCnt); +require("Storage").write(b,f(a),e,17988)}}) \ No newline at end of file diff --git a/apps/health/lib.min.js b/apps/health/lib.min.js new file mode 100644 index 000000000..4bdc4c0fb --- /dev/null +++ b/apps/health/lib.min.js @@ -0,0 +1,3 @@ +function h(a){return"health-"+a.getFullYear()+"-"+(a.getMonth()+1)+".raw"}function k(a){return 145*(a.getDate()-1)+6*a.getHours()+(0|6*a.getMinutes()/60)}exports.readAllRecords=function(a,f){a=h(a);a=require("Storage").read(a);if(void 0!==a)for(var c=8,d=0;31>d;d++){for(var b=0;24>b;b++)for(var e=0;6>e;e++){var g=a.substr(c,4);"\u00ff\u00ff\u00ff\u00ff"!=g&&f({day:d+1,hr:b,min:10*e,steps:g.charCodeAt(0)<<8|g.charCodeAt(1),bpm:g.charCodeAt(2),movement:g.charCodeAt(3)});c+= +4}c+=4}};exports.readDailySummaries=function(a,f){k(a);a=h(a);a=require("Storage").read(a);if(void 0!==a)for(var c=584,d=0;31>d;d++){var b=a.substr(c,4);"\u00ff\u00ff\u00ff\u00ff"!=b&&f({day:d+1,steps:b.charCodeAt(0)<<8|b.charCodeAt(1),bpm:b.charCodeAt(2),movement:b.charCodeAt(3)});c+=580}};exports.readDay=function(a,f){k(a);var c=h(a);c=require("Storage").read(c);if(void 0!==c){a=8+580*(a.getDate()-1);for(var d=0;24>d;d++)for(var b=0;6>b;b++){var e=c.substr(a,4);"\u00ff\u00ff\u00ff\u00ff"!=e&&f({hr:d, +min:10*b,steps:e.charCodeAt(0)<<8|e.charCodeAt(1),bpm:e.charCodeAt(2),movement:e.charCodeAt(3)});a+=4}}} \ No newline at end of file diff --git a/apps/health/metadata.json b/apps/health/metadata.json index da9d764ea..eafc2657c 100644 --- a/apps/health/metadata.json +++ b/apps/health/metadata.json @@ -1,7 +1,7 @@ { "id": "health", "name": "Health Tracking", - "version": "0.10", + "version": "0.11", "description": "Logs health data and provides an app to view it (requires firmware 2v10.100 or later)", "icon": "app.png", "tags": "tool,system,health", @@ -11,7 +11,7 @@ "storage": [ {"name":"health.app.js","url":"app.js"}, {"name":"health.img","url":"app-icon.js","evaluate":true}, - {"name":"health.boot.js","url":"boot.js"}, - {"name":"health","url":"lib.js"} + {"name":"health.boot.js","url":"boot.min.js"}, + {"name":"health","url":"lib.min.js"} ] } diff --git a/apps/ios/ChangeLog b/apps/ios/ChangeLog index 1873649f9..b6a386bcb 100644 --- a/apps/ios/ChangeLog +++ b/apps/ios/ChangeLog @@ -6,3 +6,4 @@ 0.06: Fix (not) popupping up old messages 0.07: Added more details from music (instead of Undefined), added more app identifiers 0.08: Added more app identifiers, added 'cannot display' in case a message goes empty because of replacements +0.09: Enable 'ams' on new firmwares (ams/ancs can now be enabled individually) (fix #1365) diff --git a/apps/ios/boot.js b/apps/ios/boot.js index d317c23b0..50286c4a6 100644 --- a/apps/ios/boot.js +++ b/apps/ios/boot.js @@ -1,4 +1,5 @@ bleServiceOptions.ancs = true; +if (NRF.amsIsActive) bleServiceOptions.ams = true; // amsIsActive was added at the same time as the "am" option Bangle.ancsMessageQueue = []; /* Handle ANCS events coming in, and fire off 'notify' events diff --git a/apps/ios/metadata.json b/apps/ios/metadata.json index 26e474f89..0083c66b0 100644 --- a/apps/ios/metadata.json +++ b/apps/ios/metadata.json @@ -1,7 +1,7 @@ { "id": "ios", "name": "iOS Integration", - "version": "0.08", + "version": "0.09", "description": "Display notifications/music/etc from iOS devices", "icon": "app.png", "tags": "tool,system,ios,apple,messages,notifications", diff --git a/apps/lazybones/ChangeLog b/apps/lazybones/ChangeLog new file mode 100644 index 000000000..9db0e26c5 --- /dev/null +++ b/apps/lazybones/ChangeLog @@ -0,0 +1 @@ +0.01: first release diff --git a/apps/lazybones/README.md b/apps/lazybones/README.md new file mode 100644 index 000000000..4c90f54de --- /dev/null +++ b/apps/lazybones/README.md @@ -0,0 +1,45 @@ +# Lazybones + +'Warns when you have been sat still for too long.' + +![](screenshot_lazybones2.png) +![](screenshot_lazybones.png) + +- Spending too much time sat down is destructive to our health and fitness. +- Lazy bones checks the step count every minute and if this + has not changed after 26 minutes the buzzer will sound and a pop up + screen will show. +- It gives 3 warnings before giving up +- Standing up and walking around until the step counting threshold is reached will automatically dismiss the warning. +- When the warning is not displaying the apps is a simple clock +- The timer only goes off between the hours 9am to 9pm. +- The app is a basic proof of concept that can be used in other clocks +- A settings menu may be added in a future release + + +## Dedication + +This is app is dedicated in memory of my friend, Huw Evans 1960-2019, +poet, writer, actor, archiologist, technical author, husband, father, +friend. + +In May 2017, Huw Evans received a terminal cancer diagnosis. [Not +Long Now](https://www.youtube.com/watch?v=HD_Xysb6ZEA) is his +response to that diagnosis and the drastic shortening of his life +expectancy. Not Long Now is a one-man show, which uses poetry, +story-telling, puppetry and video to address one basic question: +given we are all going to die, how then shall we live? + +## Why the Skull ? + +![](lazybones.png) + +The Skull is a [Memento +mori](https://en.wikipedia.org/wiki/Memento_mori) (Latin for +'remember that you [have to] die') is an artistic or symbolic trope +acting as a reminder of the inevitability of death. + +Let us choose not to die from sitting too long. + + +Written by: [Hugh Barney](https://github.com/hughbarney) For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/) diff --git a/apps/lazybones/lazybones.app.js b/apps/lazybones/lazybones.app.js new file mode 100644 index 000000000..6af26d6bc --- /dev/null +++ b/apps/lazybones/lazybones.app.js @@ -0,0 +1,204 @@ +Graphics.prototype.setFontRoboto = function(scale) { + // Actual height 21 (20 - 0) + this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAH/zA/+YAAAAAAAHwAAwAAHwAA+AAAAAAAAAAAQACDAAYbADP4B/8A/zAGYZADH4A/+A/7AHYYADCAAAAAAAQAeHgH4eBzgwMMHnhw88GGBw4wHj+AcPgAAAAAAAAAAB4AA/gAGMAAwhwGMcAfuABzgABzgAc+AOMYBhBAAMYAB/AAHwAAAAAHwD5+A/8YGPDAw8YGPzA/HYD4fAADwAB/AAOYAABAAAAHwAA4AAAAAAAAAAH/gD//B8A+cAA7AADAAAAAAAYAAbwAHHgHwf/4A/8AAAAEAABiAAGwAA8AA/AAH+AAGwAByAAEAAAAAAAMAABgAAMAABgAH/wA/+AAMAABgAAMAABgAAAAAAAIAAfAADwAAAABgAAMAABgAAMAABgAAAAAAAAAAAAADAAAYAAAAAAAAADgAB8AB+AA+AA+AA/AAHAAAgAAAAAAB8AB/8Af/wHAHAwAYGADAwAYHAHAf/wB/8AAAAAAAAAAABgAAcAADAAAYAAH//A//4AAAAAAAAAAAAAAAAAAAAABwDAeA4HAPAwHYGBzAwcYHHDAfwYB8DAAAYAAAAAAABgOAcBwHADAwwYGGDAwwYHPHAf/wB58AAAAAAAAADAAB4AAfAAPYAHjAB4YA8DAH//A//4AAYAADAAAAAAAAAEMA/xwH+HAxgYGMDAxgYGODAw/4GD+AAHAAAAAAAAAf8AP/wD2HA5wYGMDAxgYGOHAA/wAD8AAAAAAAAAAAGAAAwAAGADAwB4GB+Aw+AGfAA/gAHwAAwAAAAAAADAB5+Af/wHPDAwwYGGDAwwYHPHAfvwB58AAAAAAAAAAAB+AAf4AHDjAwMYGBjAwM4HDOAf/gB/4AAAAAAAAAAAAYDADAYAAAAAAAAAAYDAfAYHwAAAABAAAcAADgAA+AAGwAB3AAMYABjgAYMAAAAAAAAAAAAAAABmAAMwABmAAMwABmAAMwABmAAMwAAiAAAAAAAAAYMADjgAMYAB3AAGwAA2AADgAAcAABAAAAAAAAAMAADgAA4AAGBzAweYGHAA/wAD8AAEAAAAwAB/4A/PwOAGDgAYYPxmH/Mw4ZmMDMxgZmM+Mx/5mHDAYAIDgDAPBwAf8AAMAAAAAAAYAAfAAPwAP4AH+AH4wA8GAH4wAP2AAPwAAfwAAfAAAYAAAAAAAAAAA//4H//AwwYGGDAwwYGGDAwwYH/HAf/wB58AAAAADAAH/AD/+AcBwHADAwAYGADAwAYGADA4A4DweAODgAAAAAAAAAAAAAAH//A//4GADAwAYGADAwAYGADAYAwD4+AP/gAfwAAAAAAAAAAAH//A//4GDDAwYYGDDAwYYGDDAwYYGCDAgAYAAAAAAAH//A//4GDAAwYAGDAAwYAGDAAwYAGAAAAAAAAAAH/AD/8AcBwHAHAwAYGADAwYYGDDA4YYDz/AOfwAAAAAAAAAAA//4H//A//4ADAAAYAADAAAYAADAAAYAADAA//4H//AAAAAAAAAAAAAAA//4H//AAAAAAAAABAAAeAAB4AADAAAYAADAAAYAAHA//wH/8AAAAAAAAAAAAAAA//4H//AAcAAPAAD4AA/wAOPADg8A4B4GAHAgAYAAAAAAAH//A//4AADAAAYAADAAAYAADAAAYAADAAAAAAAA//4H//A+AAB+AAD8AAD8AAH4AAPAAH4AH4AD8AD8AA+AAH//A//4AAAAAAAH//A//4H//AeAAB8AADwAAPgAAeAAA8AADwH//A//4AAAAAAAAAAAH/AB/8AeDwHAHAwAYGADAwAYGADA4A4DweAP/gA/4AAAAAAAAAAAH//A//4GBgAwMAGBgAwMAGBgAwcAH/AAfwAA8AAAAAA/4AP/gDgOA4A4GADAwAYGADAwAYHAHgeD+B/8wD+GAAAAAAAAAAA//4H//AwYAGDAAwYAGDgAweAHH8Afz4B8HAAAIAAYAPDwD8OA5w4GGDAwwYGHDAwYYHDnAePwBw8AAAAGAAAwAAGAAAwAAGAAA//4H//AwAAGAAAwAAGAAAwAAAAAAAAAH/4A//wAAPAAAYAADAAAYAADAAAYAAPA//wH/8AAAAAAAAgAAHAAA/AAB/AAD+AAD+AAD4AAfAAfwAfwAfwAH4AA4AAEAAA+AAH/AAH/gAD/AAD4AD+AH+AH8AA+AAH+AAD+AAD/AAD4AH/AP/AH+AA8AAAAAAAAAGADA4A4HweAPPgA/wAB8AAfwAPvgDweA8B4GADAAAIGAAA4AAHwAAPgAAfAAA/4AH/AD4AB8AA+AAHgAAwAAAAAAAAAGADAwB4GAfAwPYGDzAx4YGeDA/AYHwDA4AYGADAAAAAAAA///3//+wAA2AAGAAAGAAA+AAD8AAD8AAD4AAH4AAHgAAMAAAAwAA2AAG///3//+AAAAAAAAAAAOAAHwAD4AA8AAD8AADwAAGAAAAAAABgAAMAABgAAMAABgAAMAABgAAMAABgAAAEAAAwAADAAAIAAAAAAAAAAEeABn4Ad3ADMYAZjADMYAZmAB/4AP/AAAAAAAA//4H//ABgwAYDADAYAYDADg4AP+AA/gABwAAAAAAAAA/gAP+ADg4AYDADAYAYDADAYAOOABxwAAAAAEAAH8AB/wAcHADAYAYDADAYAcDA//4H//AAAAAAAAAAAAH8AB/wAdnADMYAZjADMYAZjAB84AHmAAMAAMAABgAB//gf/8HMAAxgAGIAAAAAAH8IB/zAcHMDAZgYDMDAZgcHcD//Af/wAAAAAAAAAAH//A//4AMAADAAAYAADAAAcAAD/4AP/AAAAAAAAAAAGf/Az/4AAAAAAAAAAMz//mf/4AAAAAAAAAAH//A//4ABwAAeAAH4ABzwAcPACAYAABAAAAAAAA//4H//AAAAAAAAAAAAf/AD/4AMAADAAAYAADAAAcAAD/4AP/ABgAAYAADAAAYAADgAAP/AA/4AAAAAAAAf/AD/4AMAADAAAYAADAAAcAAD/4AP/AAAAAAAAAAAAH8AB/wAcHADAYAYDADAYAYDADx4AP+AA/gAAAAAAAAf/8D//gYDADAYAYDADAYAcHAB/wAH8AAEAAAAAAEAAH8AB/wAcHADAYAYDADAYAYDAD//gf/8AAAAAAAAAAAf/AD/4AcAADAAAYAACAAAAEAB5wAfnADMYAZjADGYAYzADn4AOeAAAAAAAADAAAYAAf/wD//ADAYAYDAAAAAAAAD/gAf/AAA4AADAAAYAADAAAwAf/AD/4AAAAAAAAYAAD4AAP4AAP4AAPAAH4AH4AD8AAcAAAAAAQAADwAAf4AAf4AAPAAP4AP4ADwAAfgAA/gAA/AAD4AH+AD+AAeAAAAAAAAACAYAcHADzwAH8AAfAAH8ADx4AcHACAIAcAMD4BgP4MAP/AAPwAP4AP4AD4AAcAAAAAAAAADAYAYHADD4AY7ADOYAfjADwYAcDADAYAAAAADAAA4AH//B/v8cABzAACAAAH//w//+AAAAAAACAACcAAx/n+H//AA4AAHAAAAAAAAAAAAAOAADgAAYAADAAAcAABgAAGAAAwAAGAADwAAcAAAAA"), 32, atob("BQUHDQwPDQQHBwkMBAYGCQwMDAwMDAwMDAwFBAsMCwoTDg0ODgwMDg8GDA0LEg8ODQ4NDA0ODRMNDQ0GCQYJCQYLDAsMCwcMDAUFCwUSDAwMDAcLBwwKEAoKCgcFBw4A"), 21+(scale<<8)+(1<<16)); + return this; +} + +function setSmallFont() { + g.setFontRoboto(); +} + +const h = g.getHeight(); +const w = g.getWidth(); + +function draw() { + if (!idle) + drawClock(); + else + drawIdle(); + queueDraw(); +} + +function drawClock() { + var date = new Date(); + var timeStr = require("locale").time(date,1); + + g.reset(); + g.setColor(g.theme.bg); + g.fillRect(Bangle.appRect); + + g.setFont('Vector', w/3); + g.setFontAlign(0, 0); + g.setColor(g.theme.fg); + g.drawString(timeStr, w/2, h/2); + setSmallFont(); + g.drawString('Last Step ' + lastStepTime, w/2, 3*h/4); +} + +///////////////// IDLE TIMER ///////////////////////////////////// + +function log_debug(o) { + //print(o); +} + +// variable for controlling idle alert +let lastStep = getTime(); +let lastStepTime = '??'; +let warned = 0; +let idle = false; +let IDLE_MINUTES = 26; + +function drawIdle() { + let mins = Math.round((getTime() - lastStep) / 60); + g.reset(); + g.setColor(g.theme.bg); + g.fillRect(Bangle.appRect); + g.setColor(g.theme.fg); + setSmallFont(); + g.setFontAlign(0, 0); + g.drawString('Last step was', w/2, (h/3)); + g.drawString(mins + ' minutes ago', w/2, 20+(h/3)); + dismissBtn.draw(); +} + +/////////////// BUTTON CLASS /////////////////////////////////////////// + +// simple on screen button class +function BUTTON(name,x,y,w,h,c,f,tx) { + this.name = name; + this.x = x; + this.y = y; + this.w = w; + this.h = h; + this.color = c; + this.callback = f; + this.text = tx; +} + +// if pressed the callback +BUTTON.prototype.check = function(x,y) { + //console.log(this.name + ":check() x=" + x + " y=" + y +"\n"); + + if (x>= this.x && x<= (this.x + this.w) && y>= this.y && y<= (this.y + this.h)) { + log_debug(this.name + ":callback\n"); + this.callback(); + return true; + } + return false; +}; + +BUTTON.prototype.draw = function() { + g.setColor(this.color); + g.fillRect(this.x, this.y, this.x + this.w, this.y + this.h); + g.setColor("#000"); // the icons and boxes are drawn black + setSmallFont(); + g.setFontAlign(0, 0); + g.drawString(this.text, (this.x + this.w/2), (this.y + this.h/2)); + g.drawRect(this.x, this.y, (this.x + this.w), (this.y + this.h)); +}; + +function dismissPrompt() { + idle = false; + warned = false; + lastStep = getTime(); + Bangle.buzz(100); + draw(); +} + +var dismissBtn = new BUTTON("big",0, 3*h/4 ,w, h/4, "#0ff", dismissPrompt, "Dismiss"); + +Bangle.on('touch', function(button, xy) { + if (idle && dismissBtn.check(xy.x, xy.y)) return; +}); + +// if we get a step then we are not idle +Bangle.on('step', s => { + setLastStepTime(); + lastStep = getTime(); + // redraw if we had been idle + if (idle == true) { + dismissPrompt(); + } + idle = false; + warned = 0; +}); + +function setLastStepTime() { + var date = new Date(); + lastStepTime = require("locale").time(date,1); +} + +function checkIdle() { + let hour = (new Date()).getHours(); + let active = (hour >= 9 && hour < 21); + //let active = true; + let dur = getTime() - lastStep; + + if (active && dur > IDLE_MINUTES * 60) { + drawIdle(); + if (warned++ < 3) { + buzzer(warned); + log_debug("checkIdle: warned=" + warned); + Bangle.setLocked(false); + } + idle = true; + } else { + idle = false; + warned = 0; + } +} + +setLastStepTime(); + +// timeout for multi-buzzer +var buzzTimeout; + +// n buzzes +function buzzer(n) { + log_debug("buzzer n=" + n); + + if (n-- < 1) return; + Bangle.buzz(250); + + if (buzzTimeout) clearTimeout(buzzTimeout); + buzzTimeout = setTimeout(function() { + buzzTimeout = undefined; + buzzer(n); + }, 500); +} + + +/////////////////////////////////////////////////////////////////////////////// + + +// timeout used to update every minute +var drawTimeout; +const DRAW_PERIOD = 60000; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + checkIdle(); + draw(); + }, DRAW_PERIOD - (Date.now() % DRAW_PERIOD)); +} + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +g.clear(); + +// Show launcher when middle button pressed +Bangle.setUI("clock"); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +draw(); diff --git a/apps/lazybones/lazybones.icon.js b/apps/lazybones/lazybones.icon.js new file mode 100644 index 000000000..6a783cdaf --- /dev/null +++ b/apps/lazybones/lazybones.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgIEBn/8BIUD///4AFBh/AgfwAoM8AQlwAQMOgEB8AFBg+AgeAAoMBDQIxD4EGAodghg9EAosYAocYApcwAocMAshgBLwIFBg/4j/ghkxh0cnFwxkZxgOBmOYjcYg1gjHYAosPAofwhwFDuEGAodgAoIjBAoYvBAoUP/Of+AFBg/j4/gAoLGBYgNgU4IjBU4MBwEOuADBgHwhuwh5nBvEP+EeAoMcg/gnBzCWQQCBggCBkACBZgQqBAo0HAQLsCIAJCCAAP//6eDn/8AYI")) diff --git a/apps/lazybones/lazybones.png b/apps/lazybones/lazybones.png new file mode 100644 index 000000000..ecca084c6 Binary files /dev/null and b/apps/lazybones/lazybones.png differ diff --git a/apps/lazybones/metadata.json b/apps/lazybones/metadata.json new file mode 100644 index 000000000..961da8a14 --- /dev/null +++ b/apps/lazybones/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "lazybones", + "name": "Lazybones", + "version": "0.01", + "description": "Idleness timer, warns when you have been sat too long", + "icon": "lazybones.png", + "screenshots": [{"url":"screenshot_lazybones.png"}], + "readme": "README.md", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"lazybones.app.js","url":"lazybones.app.js"}, + {"name":"lazybones.img","url":"lazybones.icon.js","evaluate":true} + ] +} diff --git a/apps/lazybones/screenshot_lazybones.png b/apps/lazybones/screenshot_lazybones.png new file mode 100644 index 000000000..b01d28760 Binary files /dev/null and b/apps/lazybones/screenshot_lazybones.png differ diff --git a/apps/lazybones/screenshot_lazybones2.png b/apps/lazybones/screenshot_lazybones2.png new file mode 100644 index 000000000..f3a4b308e Binary files /dev/null and b/apps/lazybones/screenshot_lazybones2.png differ diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog index e2ae0111b..890d97389 100644 --- a/apps/recorder/ChangeLog +++ b/apps/recorder/ChangeLog @@ -11,3 +11,5 @@ 0.07: Add recording for Barometer Record all HRM events Move recording for CoreTemp to its own app +0.08: Memory usage improvements for recorder app itself +0.09: Show correct number for log in overwrite prompt diff --git a/apps/recorder/app.js b/apps/recorder/app.js index 5b1c63aef..7075563aa 100644 --- a/apps/recorder/app.js +++ b/apps/recorder/app.js @@ -95,6 +95,7 @@ function showMainMenu() { Object.keys(recorders).forEach(id=>{ mainmenu["Log "+recorders[id]().name] = menuRecord(id); }); + delete recorders; return E.showMenu(mainmenu); } @@ -217,202 +218,201 @@ function viewTrack(filename, info) { }); }; menu['< Back'] = () => { viewTracks(); }; - return E.showMenu(menu); -} -function plotTrack(info) { - "ram" - - function distance(lat1,long1,lat2,long2) { "ram" - var x = (long1-long2) * Math.cos((lat1+lat2)*Math.PI/360); - var y = lat2 - lat1; - return Math.sqrt(x*x + y*y) * 6371000 * Math.PI / 180; - } - - // Function to convert lat/lon to XY - var getMapXY; - if (info.qOSTM) { - getMapXY = osm.latLonToXY.bind(osm); - } else { - getMapXY = function(lat, lon) { "ram" - return {x:cx + Math.round((long - info.lon)*info.lfactor*info.scale), - y:cy + Math.round((info.lat - lat)*info.scale)}; - }; - } - - E.showMenu(); // remove menu - E.showMessage("Drawing...","Track "+info.fn); - g.flip(); // on buffered screens, draw a not saying we're busy - g.clear(1); - var s = require("Storage"); - var W = g.getWidth(); - var H = g.getHeight(); - var cx = W/2; - var cy = 24 + (H-24)/2; - if (!info.qOSTM) { - g.setColor("#f00").fillRect(9,80,11,120).fillPoly([9,60,19,80,0,80]); - g.setColor(g.theme.fg).setFont("6x8").setFontAlign(0,0).drawString("N",10,50); - } else { - osm.lat = info.lat; - osm.lon = info.lon; - osm.draw(); - g.setColor("#000"); - } - var latIdx = info.fields.indexOf("Latitude"); - var lonIdx = info.fields.indexOf("Longitude"); - g.drawString(asTime(info.duration),10,220); - var f = require("Storage").open(info.filename,"r"); - if (f===undefined) return; - var l = f.readLine(f); - l = f.readLine(f); // skip headers - var ox=0; - var oy=0; - var olat,olong,dist=0; - var i=0, c = l.split(","); - // skip until we find our first data - while(l!==undefined && c[latIdx]=="") { - c = l.split(","); - l = f.readLine(f); - } - // now start plotting - var lat = +c[latIdx]; - var long = +c[lonIdx]; - var mp = getMapXY(lat, long); - g.moveTo(mp.x,mp.y); - g.setColor("#0f0"); - g.fillCircle(mp.x,mp.y,5); - if (info.qOSTM) g.setColor("#f09"); - else g.setColor(g.theme.fg); - l = f.readLine(f); - g.flip(); // force update - while(l!==undefined) { - c = l.split(",");l = f.readLine(f); - if (c[latIdx]=="")continue; - lat = +c[latIdx]; - long = +c[lonIdx]; - mp = getMapXY(lat, long); - g.lineTo(mp.x,mp.y); - if (info.qOSTM) g.fillCircle(mp.x,mp.y,2); // make the track more visible - var d = distance(olat,olong,lat,long); - if (!isNaN(d)) dist+=d; - olat = lat; - olong = long; - ox = mp.x; - oy = mp.y; - if (++i > 100) { g.flip();i=0; } - } - g.setColor("#f00"); - g.fillCircle(ox,oy,5); - if (info.qOSTM) g.setColor("#000"); - else g.setColor(g.theme.fg); - g.drawString(require("locale").distance(dist),g.getWidth() / 2, g.getHeight() - 20); - g.setFont("6x8",2); - g.setFontAlign(0,0,3); - g.drawString("Back",g.getWidth() - 10, g.getHeight() - 40); - setWatch(function() { - viewTrack(info.fn, info); - }, global.BTN3||BTN1); - Bangle.drawWidgets(); - g.flip(); -} - -function plotGraph(info, style) { - "ram" - E.showMenu(); // remove menu - E.showMessage("Calculating...","Track "+info.fn); - var filename = info.filename; - var infn = new Float32Array(80); - var infc = new Uint16Array(80); - var title; - var lt = 0; // last time - var tn = 0; // count for each time period - var strt, dur = info.duration; - var f = require("Storage").open(filename,"r"); - if (f===undefined) return; - var l = f.readLine(f); - l = f.readLine(f); // skip headers - var nl = 0, c, i; - var timeIdx = info.fields.indexOf("Time"); - if (l!==undefined) { - c = l.split(","); - strt = c[timeIdx]; - } - if (style=="Altitude") { - title = "Altitude (m)"; - var altIdx = info.fields.indexOf("Altitude"); - while(l!==undefined) { - ++nl;c=l.split(",");l = f.readLine(f); - if (c[altIdx]=="") continue; - i = Math.round(80*(c[timeIdx] - strt)/dur); - infn[i]+=+c[altIdx]; - infc[i]++; + function plotTrack(info) { "ram" + function distance(lat1,long1,lat2,long2) { "ram" + var x = (long1-long2) * Math.cos((lat1+lat2)*Math.PI/360); + var y = lat2 - lat1; + return Math.sqrt(x*x + y*y) * 6371000 * Math.PI / 180; + } + + // Function to convert lat/lon to XY + var getMapXY; + if (info.qOSTM) { + getMapXY = osm.latLonToXY.bind(osm); + } else { + getMapXY = function(lat, lon) { "ram" + return {x:cx + Math.round((long - info.lon)*info.lfactor*info.scale), + y:cy + Math.round((info.lat - lat)*info.scale)}; + }; + } + + E.showMenu(); // remove menu + E.showMessage("Drawing...","Track "+info.fn); + g.flip(); // on buffered screens, draw a not saying we're busy + g.clear(1); + var s = require("Storage"); + var W = g.getWidth(); + var H = g.getHeight(); + var cx = W/2; + var cy = 24 + (H-24)/2; + if (!info.qOSTM) { + g.setColor("#f00").fillRect(9,80,11,120).fillPoly([9,60,19,80,0,80]); + g.setColor(g.theme.fg).setFont("6x8").setFontAlign(0,0).drawString("N",10,50); + } else { + osm.lat = info.lat; + osm.lon = info.lon; + osm.draw(); + g.setColor("#000"); } - } else if (style=="Speed") { - title = "Speed (m/s)"; var latIdx = info.fields.indexOf("Latitude"); var lonIdx = info.fields.indexOf("Longitude"); + g.drawString(asTime(info.duration),10,220); + var f = require("Storage").open(info.filename,"r"); + if (f===undefined) return; + var l = f.readLine(f); + l = f.readLine(f); // skip headers + var ox=0; + var oy=0; + var olat,olong,dist=0; + var i=0, c = l.split(","); // skip until we find our first data while(l!==undefined && c[latIdx]=="") { c = l.split(","); l = f.readLine(f); } - // now iterate - var p,lp = Bangle.project({lat:c[1],lon:c[2]}); - var t,dx,dy,d,lt = c[timeIdx]; + // now start plotting + var lat = +c[latIdx]; + var long = +c[lonIdx]; + var mp = getMapXY(lat, long); + g.moveTo(mp.x,mp.y); + g.setColor("#0f0"); + g.fillCircle(mp.x,mp.y,5); + if (info.qOSTM) g.setColor("#f09"); + else g.setColor(g.theme.fg); + l = f.readLine(f); + g.flip(); // force update while(l!==undefined) { - ++nl;c=l.split(","); - l = f.readLine(f); - if (c[latIdx] == "") { - continue; - } - t = c[timeIdx]; - i = Math.round(80*(t - strt)/dur); - p = Bangle.project({lat:c[latIdx],lon:c[lonIdx]}); - dx = p.x-lp.x; - dy = p.y-lp.y; - d = Math.sqrt(dx*dx+dy*dy); - if (t!=lt) { - infn[i]+=d / (t-lt); // speed + c = l.split(",");l = f.readLine(f); + if (c[latIdx]=="")continue; + lat = +c[latIdx]; + long = +c[lonIdx]; + mp = getMapXY(lat, long); + g.lineTo(mp.x,mp.y); + if (info.qOSTM) g.fillCircle(mp.x,mp.y,2); // make the track more visible + var d = distance(olat,olong,lat,long); + if (!isNaN(d)) dist+=d; + olat = lat; + olong = long; + ox = mp.x; + oy = mp.y; + if (++i > 100) { g.flip();i=0; } + } + g.setColor("#f00"); + g.fillCircle(ox,oy,5); + if (info.qOSTM) g.setColor("#000"); + else g.setColor(g.theme.fg); + g.drawString(require("locale").distance(dist),g.getWidth() / 2, g.getHeight() - 20); + g.setFont("6x8",2); + g.setFontAlign(0,0,3); + g.drawString("Back",g.getWidth() - 10, g.getHeight() - 40); + setWatch(function() { + viewTrack(info.fn, info); + }, global.BTN3||BTN1); + Bangle.drawWidgets(); + g.flip(); + } + + function plotGraph(info, style) { "ram" + E.showMenu(); // remove menu + E.showMessage("Calculating...","Track "+info.fn); + var filename = info.filename; + var infn = new Float32Array(80); + var infc = new Uint16Array(80); + var title; + var lt = 0; // last time + var tn = 0; // count for each time period + var strt, dur = info.duration; + var f = require("Storage").open(filename,"r"); + if (f===undefined) return; + var l = f.readLine(f); + l = f.readLine(f); // skip headers + var nl = 0, c, i; + var timeIdx = info.fields.indexOf("Time"); + if (l!==undefined) { + c = l.split(","); + strt = c[timeIdx]; + } + if (style=="Altitude") { + title = "Altitude (m)"; + var altIdx = info.fields.indexOf("Altitude"); + while(l!==undefined) { + ++nl;c=l.split(",");l = f.readLine(f); + if (c[altIdx]=="") continue; + i = Math.round(80*(c[timeIdx] - strt)/dur); + infn[i]+=+c[altIdx]; infc[i]++; } - lp = p; - lt = t; + } else if (style=="Speed") { + title = "Speed (m/s)"; + var latIdx = info.fields.indexOf("Latitude"); + var lonIdx = info.fields.indexOf("Longitude"); + // skip until we find our first data + while(l!==undefined && c[latIdx]=="") { + c = l.split(","); + l = f.readLine(f); + } + // now iterate + var p,lp = Bangle.project({lat:c[1],lon:c[2]}); + var t,dx,dy,d,lt = c[timeIdx]; + while(l!==undefined) { + ++nl;c=l.split(","); + l = f.readLine(f); + if (c[latIdx] == "") { + continue; + } + t = c[timeIdx]; + i = Math.round(80*(t - strt)/dur); + p = Bangle.project({lat:c[latIdx],lon:c[lonIdx]}); + dx = p.x-lp.x; + dy = p.y-lp.y; + d = Math.sqrt(dx*dx+dy*dy); + if (t!=lt) { + infn[i]+=d / (t-lt); // speed + infc[i]++; + } + lp = p; + lt = t; + } + } else throw new Error("Unknown type "+style); + var min=100000,max=-100000; + for (var i=0;i0) infn[i]/=infc[i]; + var n = infn[i]; + if (n>max) max=n; + if (n0) infn[i]/=infc[i]; - var n = infn[i]; - if (n>max) max=n; - if (n 8) { + grid*=2; + } + // draw + g.clear(1).setFont("6x8",1); + var r = require("graph").drawLine(g, infn, { + x:4,y:24, + width: g.getWidth()-24, + height: g.getHeight()-(24+8), + axes : true, + gridy : grid, + gridx : infn.length / 3, + title: title, + miny: min, + maxy: max, + xlabel : x=>Math.round(x*dur/(60*infn.length))+" min" // minutes + }); + g.setFont("6x8",2); + g.setFontAlign(0,0,3); + g.drawString("Back",g.getWidth() - 10, g.getHeight() - 40); + setWatch(function() { + viewTrack(info.filename, info); + }, global.BTN3||BTN1); + g.flip(); } - // work out a nice grid value - var heightDiff = max-min; - var grid = 1; - while (heightDiff/grid > 8) { - grid*=2; - } - // draw - g.clear(1).setFont("6x8",1); - var r = require("graph").drawLine(g, infn, { - x:4,y:24, - width: g.getWidth()-24, - height: g.getHeight()-(24+8), - axes : true, - gridy : grid, - gridx : infn.length / 3, - title: title, - miny: min, - maxy: max, - xlabel : x=>Math.round(x*dur/(60*infn.length))+" min" // minutes - }); - g.setFont("6x8",2); - g.setFontAlign(0,0,3); - g.drawString("Back",g.getWidth() - 10, g.getHeight() - 40); - setWatch(function() { - viewTrack(info.filename, info); - }, global.BTN3||BTN1); - g.flip(); + + return E.showMenu(menu); } + showMainMenu(); diff --git a/apps/recorder/metadata.json b/apps/recorder/metadata.json index 815a7db40..d23562837 100644 --- a/apps/recorder/metadata.json +++ b/apps/recorder/metadata.json @@ -1,8 +1,8 @@ { "id": "recorder", - "name": "Recorder (BETA)", + "name": "Recorder", "shortName": "Recorder", - "version": "0.07", + "version": "0.09", "description": "Record GPS position, heart rate and more in the background, then download to your PC.", "icon": "app.png", "tags": "tool,outdoors,gps,widget", diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js index de465b7c1..742d373a4 100644 --- a/apps/recorder/widget.js +++ b/apps/recorder/widget.js @@ -228,7 +228,7 @@ },setRecording:function(isOn) { var settings = loadSettings(); if (isOn && !settings.recording && require("Storage").list(settings.file).length) - return E.showPrompt("Overwrite\nLog 0?",{title:"Recorder",buttons:{Yes:"yes",No:"no"}}).then(selection=>{ + return E.showPrompt("Overwrite\nLog " + settings.file.match(/\d+/)[0] + "?",{title:"Recorder",buttons:{Yes:"yes",No:"no"}}).then(selection=>{ if (selection=="no") return false; // just cancel if (selection=="yes") require("Storage").open(settings.file,"r").erase(); // TODO: Add 'new file' option diff --git a/apps/simplest/README.md b/apps/simplest/README.md index 2fe597234..cc7b52c79 100644 --- a/apps/simplest/README.md +++ b/apps/simplest/README.md @@ -3,3 +3,5 @@ The simplest working clock, acts as a tutorial piece ![](screenshot.jpg) + +Written by: [Hugh Barney](https://github.com/hughbarney) For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/) diff --git a/apps/torch/ChangeLog b/apps/torch/ChangeLog index 8e76b717a..de1bcd265 100644 --- a/apps/torch/ChangeLog +++ b/apps/torch/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Change start sequence to BTN1/3/1/3 to avoid accidental turning on (fix #342) +0.03: Add Color Changing Settings diff --git a/apps/torch/app.js b/apps/torch/app.js index 28aa00bd6..9504f3ac0 100644 --- a/apps/torch/app.js +++ b/apps/torch/app.js @@ -1,6 +1,16 @@ +const SETTINGS_FILE = "torch.json"; +let settings; + +function loadSettings() { + settings = require("Storage").readJSON(SETTINGS_FILE,1)|| {'bg': '#FFFFFF', 'color': 'White'}; +} + +loadSettings(); + Bangle.setLCDPower(1); Bangle.setLCDTimeout(0); g.reset(); +g.setColor(settings.bg); g.fillRect(0,0,g.getWidth(),g.getHeight()); // Any button turns off setWatch(()=>load(), BTN1); diff --git a/apps/torch/metadata.json b/apps/torch/metadata.json index 39655dbba..1f64e1e82 100644 --- a/apps/torch/metadata.json +++ b/apps/torch/metadata.json @@ -2,14 +2,15 @@ "id": "torch", "name": "Torch", "shortName": "Torch", - "version": "0.02", - "description": "Turns screen white to help you see in the dark. Select from the launcher or press BTN1,BTN3,BTN1,BTN3 quickly to start when in any app that shows widgets", + "version": "0.03", + "description": "Turns screen white to help you see in the dark. Select from the launcher or press BTN1,BTN3,BTN1,BTN3 quickly to start when in any app that shows widgets. You can also set the color through the apps settings menu.", "icon": "app.png", "tags": "tool,torch", "supports": ["BANGLEJS"], "storage": [ {"name":"torch.app.js","url":"app.js"}, {"name":"torch.wid.js","url":"widget.js"}, - {"name":"torch.img","url":"app-icon.js","evaluate":true} + {"name":"torch.img","url":"app-icon.js","evaluate":true}, + {"name":"torch.settings.js","url":"settings.js"} ] } diff --git a/apps/torch/settings.js b/apps/torch/settings.js new file mode 100644 index 000000000..8dd6d1854 --- /dev/null +++ b/apps/torch/settings.js @@ -0,0 +1,38 @@ +(function(back) { + const SETTINGS_FILE = "torch.json"; + + // initialize with default settings... + let s = {'bg': '#FFFFFF', 'color': 'White'} + + // ...and overwrite them with any saved values + // This way saved values are preserved if a new version adds more settings + const storage = require('Storage') + let settings = storage.readJSON(SETTINGS_FILE, 1) || s; + const saved = settings || {} + for (const key in saved) { + s[key] = saved[key] + } + + function save() { + settings = s + storage.write(SETTINGS_FILE, settings) + } + + var color_options = ['Green','Orange','Cyan','Purple','Red','Blue','Yellow','White']; + var bg_code = ['#0f0','#FFA500','#0ff','#f0f','#f00','#00f','#ffef00','#FFFFFF']; + + E.showMenu({ + '': { 'title': 'Torch' }, + '< Back': back, + 'Colour': { + value: 0 | color_options.indexOf(s.color), + min: 0, max: 7, + format: v => color_options[v], + onchange: v => { + s.color = color_options[v]; + s.bg = bg_code[v]; + save(); + }, + } + }); +})