diff --git a/apps/.eslintrc.js b/.eslintrc.js similarity index 79% rename from apps/.eslintrc.js rename to .eslintrc.js index 4c86b8174..5d15ec385 100644 --- a/apps/.eslintrc.js +++ b/.eslintrc.js @@ -1,4 +1,26 @@ -const lintExemptions = require("./lint_exemptions.js"); +const lintExemptions = require("./apps/lint_exemptions.js"); +const fs = require("fs"); +const path = require("path"); + +function findGeneratedJS(roots) { + function* listFiles(dir, allow) { + for (const f of fs.readdirSync(dir)) { + const filepath = path.join(dir, f); + const stat = fs.statSync(filepath); + + if (stat.isDirectory()) { + yield* listFiles(filepath, allow); + } else if(allow(filepath)) { + yield filepath; + } + } + } + + return roots.flatMap(root => + [...listFiles(root, f => f.endsWith(".ts"))] + .map(f => f.replace(/\.ts$/, ".js")) + ); +} module.exports = { "env": { @@ -195,9 +217,32 @@ module.exports = { "no-control-regex" : "off" }, overrides: [ + { + files: ["*.ts"], + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + ], + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + rules: { + "no-delete-var": "off", + "no-empty": ["error", { "allowEmptyCatch": true }], + "no-prototype-builtins": "off", + "prefer-const": "off", + "prefer-rest-params": "off", + "no-control-regex" : "off", + "@typescript-eslint/no-delete-var": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-this-alias": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-var-requires": "off", + } + }, ...Object.entries(lintExemptions).map(([filePath, {rules}]) => ({ files: [filePath], rules: Object.fromEntries(rules.map(rule => [rule, "off"])), })), ], + ignorePatterns: findGeneratedJS(["apps/", "modules/"]), } diff --git a/README.md b/README.md index ee1c22061..9b0458043 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ Bangle.js App Loader (and Apps) * 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/) +The release version is manually refreshed with regular intervals while the development version is continuously updated as new code is committed to this repository. + **All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By submitting code to this repository you confirm that you are happy with it being MIT licensed, and that it is not licensed in another way that would make this impossible. diff --git a/apps/_example_clock/app.js b/apps/_example_clock/app.js index 0df138d11..d1f997136 100644 --- a/apps/_example_clock/app.js +++ b/apps/_example_clock/app.js @@ -1,44 +1,48 @@ -// timeout used to update every minute -let drawTimeout; +{ + // timeout used to update every minute + let drawTimeout; -// schedule a draw for the next minute -function queueDraw() { - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = setTimeout(function() { - drawTimeout = undefined; - draw(); - }, 60000 - (Date.now() % 60000)); -} + // schedule a draw for the next minute + let queueDraw = function() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); + }; -function draw() { - // queue next draw in one minute - queueDraw(); - // Work out where to draw... - var x = g.getWidth()/2; - var y = g.getHeight()/2; - g.reset(); - // work out locale-friendly date/time - var date = new Date(); - var timeStr = require("locale").time(date,1); - var dateStr = require("locale").date(date); - // draw time - g.setFontAlign(0,0).setFont("Vector",48); - g.clearRect(0,y-15,g.getWidth(),y+25); // clear the background - g.drawString(timeStr,x,y); - // draw date - y += 35; - g.setFontAlign(0,0).setFont("6x8"); - g.clearRect(0,y-4,g.getWidth(),y+4); // clear the background - g.drawString(dateStr,x,y); -} + let draw = function() { + // queue next draw in one minute + queueDraw(); + // Work out where to draw... + var x = g.getWidth()/2; + var y = g.getHeight()/2; + g.reset(); + // work out locale-friendly date/time + var date = new Date(); + var timeStr = require("locale").time(date,1); + var dateStr = require("locale").date(date); + // draw time + g.setFontAlign(0,0).setFont("Vector",48); + g.clearRect(0,y-15,g.getWidth(),y+25); // clear the background + g.drawString(timeStr,x,y); + // draw date + y += 35; + g.setFontAlign(0,0).setFont("6x8"); + g.clearRect(0,y-4,g.getWidth(),y+4); // clear the background + g.drawString(dateStr,x,y); + }; -// Clear the screen once, at startup -g.clear(); -// draw immediately at first, queue update -draw(); + // Clear the screen once, at startup + g.clear(); + // draw immediately at first, queue update + draw(); -// Show launcher when middle button pressed -Bangle.setUI("clock"); -// Load widgets -Bangle.loadWidgets(); -Bangle.drawWidgets(); \ No newline at end of file + // Show launcher when middle button pressed + Bangle.setUI({mode:"clock", remove:function() { + // free any memory we allocated to allow fast loading + }}); + // Load widgets + Bangle.loadWidgets(); + Bangle.drawWidgets(); +} \ No newline at end of file diff --git a/apps/_example_clock/metadata.json b/apps/_example_clock/metadata.json index c6e1256d3..d9150af3c 100644 --- a/apps/_example_clock/metadata.json +++ b/apps/_example_clock/metadata.json @@ -4,6 +4,7 @@ "version":"0.01", "description": "A detailed description of my clock", "icon": "icon.png", + "screenshots": [{"url":"screenshot.png"}], "type": "clock", "tags": "clock", "supports" : ["BANGLEJS2"], diff --git a/apps/_example_clock/screenshot.png b/apps/_example_clock/screenshot.png new file mode 100644 index 000000000..32d910ef5 Binary files /dev/null and b/apps/_example_clock/screenshot.png differ diff --git a/apps/accelsender/ChangeLog b/apps/accelsender/ChangeLog new file mode 100644 index 000000000..2a37193a3 --- /dev/null +++ b/apps/accelsender/ChangeLog @@ -0,0 +1 @@ +0.01: Initial release. diff --git a/apps/accelsender/README.md b/apps/accelsender/README.md new file mode 100644 index 000000000..eb18bb88a --- /dev/null +++ b/apps/accelsender/README.md @@ -0,0 +1,19 @@ +# Accerleration Data Provider + +This app provides acceleration data via Bluetooth, which can be used in Gadgetbridge. + +## Usage + +This boot code runs in the background and has no user interface. +Currently this app is used to enable Sleep as Android tracking for your Banglejs using Gadgetbridge. + +**Please Note**: This app only listens to "accel" events and sends them to your phone using Bluetooth. + +## Creator + +[Another Stranger](https://github.com/anotherstranger) + +## Aknowledgements + +Special thanks to [José Rebelo](https://github.com/joserebelo) and [Rob Pilling](https://github.com/bobrippling) +for their Code Reviews and guidance. diff --git a/apps/accelsender/bluetooth.png b/apps/accelsender/bluetooth.png new file mode 100644 index 000000000..1a884a62c Binary files /dev/null and b/apps/accelsender/bluetooth.png differ diff --git a/apps/accelsender/boot.js b/apps/accelsender/boot.js new file mode 100644 index 000000000..b1a076e2b --- /dev/null +++ b/apps/accelsender/boot.js @@ -0,0 +1,55 @@ +(() => { + /** + * Sends a message to the gadgetbridge via Bluetooth. + * @param {Object} message - The message to be sent. + */ + function gbSend(message) { + try { + Bluetooth.println(""); + Bluetooth.println(JSON.stringify(message)); + } catch (error) { + console.error("Failed to send message via Bluetooth:", error); + } + } + + var max_acceleration = { x: 0, y: 0, z: 0, diff: 0, td: 0, mag: 0 }; + var hasData = false; + + /** + * Updates the maximum acceleration if the current acceleration is greater. + * @param {Object} accel - The current acceleration object with x, y, z, and mag properties. + */ + function updateAcceleration(accel) { + hasData = true; + var current_max_raw = accel.mag; + var max_raw = max_acceleration.mag; + + if (current_max_raw > max_raw) { + max_acceleration = accel; + } + } + + /** + * Updates the acceleration data and sends it to gadgetbridge. + * Resets the maximum acceleration. + * Note: If your interval setting is too short, the last value gets sent again. + */ + function sendAccelerationData() { + var accel = hasData ? max_acceleration : Bangle.getAccel(); + + var update_data = { + t: "accel", accel: accel + }; + gbSend(update_data); + + max_acceleration = { x: 0, y: 0, z: 0, mag: 0, diff: 0, td: 0 }; + hasData = false; + } + + var config = require("Storage").readJSON("accelsender.json") || {}; + if (config.enabled) { // Gadgetbridge needs to enable and disable tracking by writing {enabled: true} to "accelsender.json" and reloading + setInterval(sendAccelerationData, config.interval); + Bangle.on("accel", updateAcceleration); // Log all acceleration events + } + +})(); diff --git a/apps/accelsender/boot.min.js b/apps/accelsender/boot.min.js new file mode 100644 index 000000000..72a336083 --- /dev/null +++ b/apps/accelsender/boot.min.js @@ -0,0 +1 @@ +(()=>{function e(a){c=!0;a.mag>b.mag&&(b=a)}function f(){var a={t:"accel",accel:c?b:Bangle.getAccel()};try{Bluetooth.println(""),Bluetooth.println(JSON.stringify(a))}catch(g){console.error("Failed to send message via Bluetooth:",g)}b={x:0,y:0,z:0,mag:0,diff:0,td:0};c=!1}var b={x:0,y:0,z:0,diff:0,td:0,mag:0},c=!1,d=require("Storage").readJSON("accelsender.json")||{};d.enabled&&(setInterval(f,d.interval),Bangle.on("accel",e))})(); diff --git a/apps/accelsender/config.json b/apps/accelsender/config.json new file mode 100644 index 000000000..70590f2f2 --- /dev/null +++ b/apps/accelsender/config.json @@ -0,0 +1,4 @@ +{ + "enabled": false, + "interval": 10000 +} \ No newline at end of file diff --git a/apps/accelsender/metadata.json b/apps/accelsender/metadata.json new file mode 100644 index 000000000..b63f7485e --- /dev/null +++ b/apps/accelsender/metadata.json @@ -0,0 +1,27 @@ +{ + "id": "accelsender", + "name": "Acceleration Data Provider", + "shortName": "Accel Data Provider", + "version": "0.01", + "description": "This app sends accelerometer and heart rate data from your Bangle.js via Bluetooth.", + "icon": "bluetooth.png", + "type": "bootloader", + "tags": "accel", + "supports": [ + "BANGLEJS", + "BANGLEJS2" + ], + "readme": "README.md", + "storage": [ + { + "name": "accelsender.boot.js", + "url": "boot.min.js" + } + ], + "data": [ + { + "name": "accelsender.json", + "url": "config.json" + } + ] +} diff --git a/apps/activityreminder/metadata.json b/apps/activityreminder/metadata.json index 7be6fe911..1cbe4683e 100644 --- a/apps/activityreminder/metadata.json +++ b/apps/activityreminder/metadata.json @@ -6,7 +6,7 @@ "version":"0.12", "icon": "app.png", "type": "app", - "tags": "tool,activity", + "tags": "tool,activity,health", "supports": ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", "storage": [ diff --git a/apps/analogquadclk/ChangeLog b/apps/analogquadclk/ChangeLog index 841543254..d1bd8762f 100644 --- a/apps/analogquadclk/ChangeLog +++ b/apps/analogquadclk/ChangeLog @@ -1,2 +1,3 @@ 0.01: New Clock! -0.02: Fix fastloading memory leak and clockinfo overwritten by hands \ No newline at end of file +0.02: Fix fastloading memory leak and clockinfo overwritten by hands +0.03: Use new clockinfo lib with function to render images wirh borders \ No newline at end of file diff --git a/apps/analogquadclk/app.js b/apps/analogquadclk/app.js index 9fd81a6f4..dbaa49825 100644 --- a/apps/analogquadclk/app.js +++ b/apps/analogquadclk/app.js @@ -102,6 +102,7 @@ // Show launcher when middle button pressed Bangle.setUI({ mode: "clock", + redraw : draw, remove: function() { if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = undefined; @@ -116,9 +117,6 @@ Bangle.loadWidgets(); require("widget_utils").hide(); - // used for clockinfo image rendering - let clockInfoG = Graphics.createArrayBuffer(28, 28, 2, {msb:true}); - clockInfoG.transparent = 3; // render clockinfos let clockInfoDraw = function(itm, info, options) { // itm: the item containing name/hasRange/etc @@ -139,18 +137,8 @@ fg = g.toColor("#f00"); } - if (info.img) { - //g.drawImage(info.img, left ? 2 : W - 27, top ? 18 : H - 41); // draw the image - // fiddle around colouring the border and inside of the image - clockInfoG.clear(1); - // do a border - images need to be transparent for this - clockInfoG.setColor(2).drawImage(info.img, 1,1).drawImage(info.img, 3,1). - drawImage(info.img, 1,3).drawImage(info.img, 3,3); - clockInfoG.setColor(1).drawImage(info.img, 2,2); // main image - clockInfoG.floodFill(27,27,3); // flood fill edge to transparent - clockInfoG.palette = new Uint16Array([bg,fg,bg/*border*/, g.toColor("#888")]); - g.drawImage(clockInfoG, imgx-1, imgy-1); - } + if (info.img) + require("clock_info").drawBorderedImage(info.img,imgx,imgy); g.setFont("6x8:2").setFontAlign(left ? -1 : 1, -1); g.setColor(bg).drawString(info.text, textx-2, texty). // draw the text background diff --git a/apps/analogquadclk/metadata.json b/apps/analogquadclk/metadata.json index 00d46e160..d60255717 100644 --- a/apps/analogquadclk/metadata.json +++ b/apps/analogquadclk/metadata.json @@ -1,12 +1,12 @@ { "id": "analogquadclk", "name": "Analog Quad Clock", "shortName":"Quad Clock", - "version":"0.02", + "version":"0.03", "description": "An analog clock with clockinfos in each of the 4 corners, allowing 4 different data types to be rendered at once", "icon": "icon.png", "screenshots" : [ { "url":"screenshot.png" }, { "url":"screenshot2.png" } ], "type": "clock", - "tags": "clock,clkinfo,analog", + "tags": "clock,clkinfo,analog,clockbg", "supports" : ["BANGLEJS2"], "dependencies" : { "clock_info":"module", "clockbg":"module" }, "storage": [ diff --git a/apps/andark/ChangeLog b/apps/andark/ChangeLog index 341868930..7d7f9567b 100644 --- a/apps/andark/ChangeLog +++ b/apps/andark/ChangeLog @@ -1,4 +1,7 @@ 0.01: Release 0.02: Rename app 0.03: Add type "clock" -0.04: changed update cylce, when locked +0.04: Changed update cylce, when locked +0.05: Fix support for dark theme + support widgets + + add settings for widgets, order of drawing and hour hand length +0.06: Fix issue showing widgets when app is fast-loaded into from launcher with widgets disabled diff --git a/apps/andark/README.md b/apps/andark/README.md index 3770c1017..9034677c2 100644 --- a/apps/andark/README.md +++ b/apps/andark/README.md @@ -1,10 +1,16 @@ -# Analog Clock +# Dark Analog Clock ## Features -* second hand +* second hand (only on unlocked screen) * date -* battery percantage -* no widgets +* battery percentage (showing charge status with color) +* turned off or swipeable widgets (choose in settings) ![logo](andark_screen.png) + +## Settings + +* whether to load widgets, or not; if widgets are loaded, they are swipeable from the top; if not, NO ACTIONS of widgets are available +* date and battery can be printed both below hands (as if hands were physical) and above (more readable) +* hour hand can be made slighly shorter to improve readability when minute hand is behind a number diff --git a/apps/andark/andark_screen.png b/apps/andark/andark_screen.png index 2ac54c1cd..1f0e5b089 100644 Binary files a/apps/andark/andark_screen.png and b/apps/andark/andark_screen.png differ diff --git a/apps/andark/app.js b/apps/andark/app.js index 865e3e708..e6b5204f0 100644 --- a/apps/andark/app.js +++ b/apps/andark/app.js @@ -1,22 +1,43 @@ +const defaultSettings = { + loadWidgets : false, + textAboveHands : false, + shortHrHand : false +}; +const settings = Object.assign(defaultSettings, require('Storage').readJSON('andark.json',1)||{}); + const c={"x":g.getWidth()/2,"y":g.getHeight()/2}; -let zahlpos=[]; + +const zahlpos=(function() { + let z=[]; + let sk=1; + for(let i=-10;i<50;i+=5){ + let win=i*2*Math.PI/60; + let xsk =c.x+2+Math.cos(win)*(c.x-10), + ysk =c.y+2+Math.sin(win)*(c.x-10); + if(sk==3){xsk-=10;} + if(sk==6){ysk-=10;} + if(sk==9){xsk+=10;} + if(sk==12){ysk+=10;} + if(sk==10){xsk+=3;} + z.push([sk,xsk,ysk]); + sk+=1; + } + return z; +})(); + let unlock = false; function zeiger(len,dia,tim){ - const x =c.x+ Math.cos(tim)*len/2, - y =c.y + Math.sin(tim)*len/2, + const x=c.x+ Math.cos(tim)*len/2, + y=c.y + Math.sin(tim)*len/2, d={"d":3,"x":dia/2*Math.cos(tim+Math.PI/2),"y":dia/2*Math.sin(tim+Math.PI/2)}, pol=[c.x-d.x,c.y-d.y,c.x+d.x,c.y+d.y,x+d.x,y+d.y,x-d.x,y-d.y]; return pol; } -function draw(){ - const d=new Date(); +function drawHands(d) { let m=d.getMinutes(), h=d.getHours(), s=d.getSeconds(); - //draw black rectangle in the middle to clear screen from scale and hands - g.setColor(0,0,0); - g.fillRect(10,10,2*c.x-10,2*c.x-10); g.setColor(1,1,1); if(h>12){ @@ -29,30 +50,64 @@ function draw(){ m=2*Math.PI/60*(m)-Math.PI/2; s=2*Math.PI/60*s-Math.PI/2; - g.setFontAlign(0,0); - g.setFont("Vector",10); - let dateStr = " "+require("locale").date(d)+" "; - g.drawString(dateStr, c.x, c.y+20, true); - // g.drawString(d.getDate(),1.4*c.x,c.y,true); - g.drawString(Math.round(E.getBattery()/5)*5+"%",c.x,c.y+40,true); - drawlet(); //g.setColor(1,0,0); - const hz = zeiger(100,5,h); + const hz = zeiger(settings.shortHrHand?88:100,5,h); g.fillPoly(hz,true); - // g.setColor(1,1,1); + //g.setColor(1,1,1); const minz = zeiger(150,5,m); g.fillPoly(minz,true); if (unlock){ - const sekz = zeiger(150,2,s); - g.fillPoly(sekz,true); + const sekz = zeiger(150,2,s); + g.fillPoly(sekz,true); } g.fillCircle(c.x,c.y,4); - - - } + +function drawText(d) { + g.setFont("Vector",10); + g.setBgColor(0,0,0); + g.setColor(1,1,1); + let dateStr = require("locale").date(d); + g.drawString(dateStr, c.x, c.y+20, true); + let batStr = Math.round(E.getBattery()/5)*5+"%"; + if (Bangle.isCharging()) { + g.setBgColor(1,0,0); + } + g.drawString(batStr, c.x, c.y+40, true); +} + +function drawNumbers() { + //draws the numbers on the screen + g.setFont("Vector",20); + g.setColor(1,1,1); + g.setBgColor(0,0,0); + for(let i = 0;i<12;i++){ + g.drawString(zahlpos[i][0],zahlpos[i][1],zahlpos[i][2],true); + } +} + +function draw(){ + // draw black rectangle in the middle to clear screen from scale and hands + g.setColor(0,0,0); + g.fillRect(10,10,2*c.x-10,2*c.x-10); + // prepare for drawing the text + g.setFontAlign(0,0); + // do drawing + drawNumbers(); + const d=new Date(); + if (settings.textAboveHands) { + drawHands(d); drawText(d); + } else { + drawText(d); drawHands(d); + } +} + //draws the scale once the app is startet function drawScale(){ + // clear the screen + g.setBgColor(0,0,0); + g.clear(); + // draw the ticks of the scale for(let i=-14;i<47;i++){ const win=i*2*Math.PI/60; let d=2; @@ -64,61 +119,34 @@ function drawScale(){ } } -//draws the numbers on the screen +//// main running sequence //// -function drawlet(){ - g.setFont("Vector",20); - for(let i = 0;i<12;i++){ - g.drawString(zahlpos[i][0],zahlpos[i][1],zahlpos[i][2]); - } -} -//calcultes the Position of the numbers when app starts and saves them in an array -function setlet(){ - let sk=1; - for(let i=-10;i<50;i+=5){ - let win=i*2*Math.PI/60; - let xsk =c.x+2+Math.cos(win)*(c.x-10), - ysk =c.y+2+Math.sin(win)*(c.x-10); - if(sk==3){xsk-=10;} - if(sk==6){ysk-=10;} - if(sk==9){xsk+=10;} - if(sk==12){ysk+=10;} - if(sk==10){xsk+=3;} - zahlpos.push([sk,xsk,ysk]); - sk+=1; - } -} -setlet(); +// Show launcher when middle button pressed, and widgets that we're clock +Bangle.setUI("clock"); +// Load widgets if needed, and make them show swipeable +if (settings.loadWidgets) { + Bangle.loadWidgets(); + require("widget_utils").swipeOn(); +} else if (global.WIDGETS) require("widget_utils").hide(); // Clear the screen once, at startup -g.setBgColor(0,0,0); -g.clear(); drawScale(); draw(); -let secondInterval= setInterval(draw, 1000); -// Stop updates when LCD is off, restart when on +let secondInterval = setInterval(draw, 1000); +// Stop updates when LCD is off, restart when on Bangle.on('lcdPower',on=>{ if (secondInterval) clearInterval(secondInterval); secondInterval = undefined; if (on) { - secondInterval = setInterval(draw, 1000); - draw(); // draw immediately + secondInterval = setInterval(draw, 1000); + draw(); // draw immediately } }); Bangle.on('lock',on=>{ + unlock = !on; if (secondInterval) clearInterval(secondInterval); - secondInterval = undefined; - if (!on) { - secondInterval = setInterval(draw, 1000); - unlock = true; - draw(); // draw immediately - }else{ - secondInterval = setInterval(draw, 60000); - unlock = false; - draw(); - } - }); - -// Show launcher when middle button pressed -Bangle.setUI("clock"); + secondInterval = setInterval(draw, unlock ? 1000 : 60000); + draw(); // draw immediately +}); +Bangle.on('charging',on=>{draw();}); diff --git a/apps/andark/metadata.json b/apps/andark/metadata.json index 3e2b3116e..fc8872f4b 100644 --- a/apps/andark/metadata.json +++ b/apps/andark/metadata.json @@ -1,15 +1,18 @@ { "id": "andark", "name": "Analog Dark", "shortName":"AnDark", - "version":"0.04", + "version":"0.06", "description": "analog clock face without disturbing widgets", "icon": "andark_icon.png", "type": "clock", "tags": "clock", "supports" : ["BANGLEJS2"], + "screenshots": [{"url":"andark_screen.png"}], "readme": "README.md", "storage": [ {"name":"andark.app.js","url":"app.js"}, + {"name":"andark.settings.js","url":"settings.js"}, {"name":"andark.img","url":"app_icon.js","evaluate":true} - ] + ], + "data": [{"name":"andark.json"}] } diff --git a/apps/andark/settings.js b/apps/andark/settings.js new file mode 100644 index 000000000..708913705 --- /dev/null +++ b/apps/andark/settings.js @@ -0,0 +1,28 @@ +(function(back) { + const defaultSettings = { + loadWidgets : false, + textAboveHands : false, + shortHrHand : false + } + let settings = Object.assign(defaultSettings, require('Storage').readJSON('andark.json',1)||{}); + + const save = () => require('Storage').write('andark.json', settings); + + const appMenu = { + '': {title: 'andark'}, '< Back': back, + /*LANG*/'Load widgets': { + value : !!settings.loadWidgets, + onchange : v => { settings.loadWidgets=v; save();} + }, + /*LANG*/'Text above hands': { + value : !!settings.textAboveHands, + onchange : v => { settings.textAboveHands=v; save();} + }, + /*LANG*/'Short hour hand': { + value : !!settings.shortHrHand, + onchange : v => { settings.shortHrHand=v; save();} + }, + }; + + E.showMenu(appMenu); +}); diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index de5130a91..11c78588a 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -34,3 +34,6 @@ 0.32: Added support for loyalty cards from gadgetbridge 0.33: Fix alarms created in Gadgetbridge not repeating 0.34: Implement API for activity tracks fetching (Recorder app logs). +0.35: Implement API to enable/disable acceleration data tracking. +0.36: Move from wrapper function to {} and let - faster execution at boot + Allow `calendar-` to take an array of items to remove \ No newline at end of file diff --git a/apps/android/boot.js b/apps/android/boot.js index 666cac6d3..729ed2b47 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -1,21 +1,22 @@ -(function() { - function gbSend(message) { +/* global GB */ +{ + let gbSend = function(message) { Bluetooth.println(""); Bluetooth.println(JSON.stringify(message)); } - var lastMsg; // for music messages - may not be needed now... - var actInterval; // Realtime activity reporting interval when `act` is true - var actHRMHandler; // For Realtime activity reporting - var gpsState = {}; // keep information on GPS via Gadgetbridge + let lastMsg; // for music messages - may not be needed now... + let actInterval; // Realtime activity reporting interval when `act` is true + let actHRMHandler; // For Realtime activity reporting + let gpsState = {}; // keep information on GPS via Gadgetbridge // this settings var is deleted after this executes to save memory - var settings = require("Storage").readJSON("android.settings.json",1)||{}; + let settings = require("Storage").readJSON("android.settings.json",1)||{}; //default alarm settings if (settings.rp == undefined) settings.rp = true; if (settings.as == undefined) settings.as = true; if (settings.vibrate == undefined) settings.vibrate = ".."; require('Storage').writeJSON("android.settings.json", settings); - var _GB = global.GB; + let _GB = global.GB; let fetchRecInterval; global.GB = (event) => { // feed a copy to other handlers if there were any @@ -121,7 +122,10 @@ var cal = require("Storage").readJSON("android.calendar.json",true); //if any of those happen we are out of sync! if (!cal || !Array.isArray(cal)) cal = []; - cal = cal.filter(e=>e.id!=event.id); + if (Array.isArray(event.id)) + cal = cal.filter(e=>!event.id.includes(e.id)); + else + cal = cal.filter(e=>e.id!=event.id); require("Storage").writeJSON("android.calendar.json", cal); }, //triggered by GB, send all ids @@ -292,6 +296,10 @@ // we receive all, just override what we have if (Array.isArray(event.d)) require("Storage").writeJSON("android.cards.json", event.d); + }, + "accelsender": function () { + require("Storage").writeJSON("accelsender.json", {enabled: event.enable, interval: event.interval}); + load(); } }; var h = HANDLERS[event.t]; @@ -332,7 +340,7 @@ }; // Battery monitor - function sendBattery() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); } + let sendBattery = function() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); } Bangle.on("charging", sendBattery); NRF.on("connect", () => setTimeout(function() { sendBattery(); @@ -427,4 +435,4 @@ // remove settings object so it's not taking up RAM delete settings; -})(); +} diff --git a/apps/android/metadata.json b/apps/android/metadata.json index 72055f557..7768efb6c 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -2,7 +2,7 @@ "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.34", + "version": "0.36", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", "icon": "app.png", "tags": "tool,system,messages,notifications,gadgetbridge", diff --git a/apps/android/test.js b/apps/android/test.js deleted file mode 100644 index 007b06fc7..000000000 --- a/apps/android/test.js +++ /dev/null @@ -1,153 +0,0 @@ -let result = true; - -function assertTrue(condition, text) { - if (!condition) { - result = false; - print("FAILURE: " + text); - } else print("OK: " + text); -} - -function assertFalse(condition, text) { - assertTrue(!condition, text); -} - -function assertUndefinedOrEmpty(array, text) { - assertTrue(!array || array.length == 0, text); -} - -function assertNotEmpty(array, text) { - assertTrue(array && array.length > 0, text); -} - -let internalOn = () => { - return getPinMode((process.env.HWVERSION==2)?D30:D26) == "input"; -}; - -let sec = { - connected: false -}; - -NRF.getSecurityStatus = () => sec; -// add an empty starting point to make the asserts work -Bangle._PWR={}; - -let teststeps = []; - -teststeps.push(()=>{ - print("Not connected, should use internal GPS"); - assertTrue(!NRF.getSecurityStatus().connected, "Not connected"); - - assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS"); - assertFalse(Bangle.isGPSOn(), "isGPSOn"); - - assertTrue(Bangle.setGPSPower(1, "test"), "Switch GPS on"); - - assertNotEmpty(Bangle._PWR.GPS, "GPS"); - assertTrue(Bangle.isGPSOn(), "isGPSOn"); - assertTrue(internalOn(), "Internal GPS on"); - - assertFalse(Bangle.setGPSPower(0, "test"), "Switch GPS off"); - - assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS"); - assertFalse(Bangle.isGPSOn(), "isGPSOn"); - assertFalse(internalOn(), "Internal GPS off"); - -}); - -teststeps.push(()=>{ - print("Connected, should use GB GPS"); - sec.connected = true; - - assertTrue(NRF.getSecurityStatus().connected, "Connected"); - - assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS"); - assertFalse(Bangle.isGPSOn(), "isGPSOn"); - assertFalse(internalOn(), "Internal GPS off"); - - - print("Internal GPS stays on until the first GadgetBridge event arrives"); - assertTrue(Bangle.setGPSPower(1, "test"), "Switch GPS on"); - - assertNotEmpty(Bangle._PWR.GPS, "GPS"); - assertTrue(Bangle.isGPSOn(), "isGPSOn"); - assertTrue(internalOn(), "Internal GPS on"); - - print("Send minimal GadgetBridge GPS event to trigger switch"); - GB({t:"gps"}); -}); - -teststeps.push(()=>{ - print("GPS should be on, internal off"); - - assertNotEmpty(Bangle._PWR.GPS, "GPS"); - assertTrue(Bangle.isGPSOn(), "isGPSOn"); - assertFalse(internalOn(), "Internal GPS off"); -}); - -teststeps.push(()=>{ - print("Switching GPS off turns both GadgetBridge as well as internal off"); - assertFalse(Bangle.setGPSPower(0, "test"), "Switch GPS off"); - - assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS"); - assertFalse(Bangle.isGPSOn(), "isGPSOn"); - assertFalse(internalOn(), "Internal GPS off"); -}); - -teststeps.push(()=>{ - print("Wait for all timeouts to run out"); - return 12000; -}); - -teststeps.push(()=>{ - print("Check auto switch when no GPS event arrives"); - - assertTrue(Bangle.setGPSPower(1, "test"), "Switch GPS on"); - - assertNotEmpty(Bangle._PWR.GPS, "GPS"); - assertTrue(Bangle.isGPSOn(), "isGPSOn"); - assertTrue(internalOn(), "Internal GPS on"); - - print("Send minimal GadgetBridge GPS event to trigger switch"); - GB({t:"gps"}); - - print("Internal should be switched off now"); - - assertNotEmpty(Bangle._PWR.GPS, "GPS"); - assertTrue(Bangle.isGPSOn(), "isGPSOn"); - assertFalse(internalOn(), "Internal GPS off"); - - //wait on next test - return 12000; -}); - -teststeps.push(()=>{ - print("Check state and disable GPS, internal should be on"); - - assertNotEmpty(Bangle._PWR.GPS, "GPS"); - assertTrue(Bangle.isGPSOn(), "isGPSOn"); - assertTrue(internalOn(), "Internal GPS on"); - - assertFalse(Bangle.setGPSPower(0, "test"), "Switch GPS off"); -}); - -teststeps.push(()=>{ - print("Result Overall is " + (result ? "OK" : "FAIL")); -}); - -let wrap = (functions) => { - if (functions.length > 0) { - setTimeout(()=>{ - let waitingTime = functions.shift()(); - if (waitingTime){ - print("WAITING: ", waitingTime); - setTimeout(()=>{wrap(functions);}, waitingTime); - } else - wrap(functions); - },0); - } -}; - -setTimeout(()=>{ - wrap(teststeps); -}, 5000); - diff --git a/apps/android/test.json b/apps/android/test.json new file mode 100644 index 000000000..429fd70fe --- /dev/null +++ b/apps/android/test.json @@ -0,0 +1,99 @@ +{ + "app" : "android", + "setup" : [{ + "id": "default", + "steps" : [ + {"t":"cmd", "js": "Bangle.setGPSPower=(isOn, appID)=>{if (!appID) appID='?';if (!Bangle._PWR) Bangle._PWR={};if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[];if (isOn && !Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.push(appID);if (!isOn && Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.splice(Bangle._PWR.GPS.indexOf(appID),1);return Bangle._PWR.GPS.length>0;};", "text": "Fake the setGPSPower"}, + {"t":"wrap", "fn": "Bangle.setGPSPower", "id": "gpspower"}, + {"t":"cmd", "js": "Serial1.println = () => { }", "text": "Fake the serial port println"}, + {"t":"cmd", "js": "Bluetooth.println = () => { }", "text": "Fake the Bluetooth println"}, + {"t":"cmd", "js": "Bangle._PWR={}", "text": "Prepare an empty _PWR for following asserts"}, + {"t":"cmd", "js": "require('Storage').writeJSON('android.settings.json', {overwriteGps: true})", "text": "Enable GPS overwrite"}, + {"t":"cmd", "js": "eval(require('Storage').read('android.boot.js'))", "text": "Load the boot code"} + ] + },{ + "id": "connected", + "steps" : [ + {"t":"cmd", "js": "NRF.getSecurityStatus = () => { return { connected: true };}", "text": "Control the security status to be connected"} + ] + },{ + "id": "disconnected", + "steps" : [ + {"t":"cmd", "js": "NRF.getSecurityStatus = () => { return { connected: false };}", "text": "Control the security status to be disconnected"} + ] + }], + "tests" : [{ + "description": "Check setGPSPower is replaced", + "steps" : [ + {"t":"cmd", "js": "Serial1.println = () => { }", "text": "Fake the serial port"}, + {"t":"cmd", "js": "Bluetooth.println = () => { }", "text": "Fake the Bluetooth println"}, + {"t":"cmd", "js": "require('Storage').writeJSON('android.settings.json', {overwriteGps: true})", "text": "Enable GPS overwrite"}, + {"t":"cmd", "js": "eval(require('Storage').read('android.boot.js'))", "text": "Load the boot code"}, + {"t":"assert", "js": "Bangle.setGPSPower.toString().includes('native')", "is":"false", "text": "setGPSPower has been replaced"} + ] + },{ + "description": "Test switching hardware GPS on and off", + "steps" : [ + {"t":"setup", "id": "default"}, + {"t":"setup", "id": "connected"}, + {"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"undefinedOrEmpty", "text": "No GPS clients"}, + {"t":"assert", "js": "Bangle.isGPSOn()", "is":"falsy", "text": "isGPSOn shows GPS as off"}, + {"t":"assert", "js": "Bangle.setGPSPower(1, 'test')", "is":"truthy", "text": "setGPSPower returns truthy when switching on"}, + {"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"notEmpty", "text": "GPS clients"}, + {"t":"assert", "js": "Bangle.isGPSOn()", "is":"truthy", "text": "isGPSOn shows GPS as on"}, + {"t":"assertCall", "id": "gpspower", "count": 1, "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 1 } ] , "text": "internal GPS switched on"}, + {"t":"assert", "js": "Bangle.setGPSPower(0, 'test')", "is":"falsy", "text": "setGPSPower returns falsy when switching off"}, + {"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"undefinedOrEmpty", "text": "No GPS clients"}, + {"t":"assert", "js": "Bangle.isGPSOn()", "is":"falsy", "text": "isGPSOn shows GPS as off"}, + {"t":"assertCall", "id": "gpspower", "count": 2, "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 0 } ] , "text": "internal GPS switched off"} + ] + },{ + "description": "Test switching when GB GPS is available, internal GPS active until GB GPS event arrives", + "steps" : [ + {"t":"setup", "id": "default"}, + {"t":"setup", "id": "connected"}, + {"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"undefinedOrEmpty", "text": "No GPS clients"}, + {"t":"assert", "js": "Bangle.isGPSOn()", "is":"falsy", "text": "isGPSOn shows GPS as off"}, + + {"t":"assert", "js": "Bangle.setGPSPower(1, 'test')", "is":"truthy", "text": "setGPSPower returns truthy when switching on"}, + {"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"notEmpty", "text": "GPS clients"}, + {"t":"assert", "js": "Bangle.isGPSOn()", "is":"truthy", "text": "isGPSOn shows GPS as on"}, + {"t":"assertCall", "id": "gpspower", "count": 1, "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 1 } ], "text": "internal GPS switched on"}, + + {"t":"gb", "obj":{"t":"gps"}}, + {"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"notEmpty", "text": "GPS clients still there"}, + {"t":"assert", "js": "Bangle.isGPSOn()", "is":"truthy", "text": "isGPSOn still shows GPS as on"}, + {"t":"assertCall", "id": "gpspower", "count": 2, "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 0 } ], "text": "internal GPS switched off"} + ] + },{ + "description": "Test switching when GB GPS is available, internal stays off", + "steps" : [ + {"t":"setup", "id": "default"}, + {"t":"setup", "id": "connected"}, + + {"t":"assert", "js": "Bangle.setGPSPower(1, 'test')", "is":"truthy", "text": "setGPSPower returns truthy when switching on"}, + + {"t":"gb", "obj":{"t":"gps"}}, + {"t":"assertCall", "id": "gpspower", "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 0 } ], "text": "internal GPS switched off"}, + + {"t":"assert", "js": "Bangle.setGPSPower(0, 'test')", "is":"falsy", "text": "setGPSPower returns truthy when switching on"}, + {"t":"assertCall", "id": "gpspower", "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 0 } ], "text": "internal GPS still switched off"} + ] + },{ + "description": "Test switching when GB GPS is available, but no event arrives", + "steps" : [ + {"t":"setup", "id": "default"}, + {"t":"setup", "id": "connected"}, + + {"t":"assert", "js": "Bangle.setGPSPower(1, 'test')", "is":"truthy", "text": "setGPSPower returns truthy when switching on"}, + + {"t":"resetCall", "id": "gpspower"}, + {"t":"gb", "obj":{"t":"gps"}, "text": "trigger switch"}, + {"t":"assertCall", "id": "gpspower", "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 0 } ], "text": "internal GPS switched off"}, + + {"t":"resetCall", "id": "gpspower"}, + {"t":"advanceTimers", "ms":"12000", "text": "wait for fallback"}, + {"t":"assertCall", "id": "gpspower", "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 1 } ], "text": "internal GPS switched on caused by missing GB event"} + ] + }] +} diff --git a/apps/antonclk/test.json b/apps/antonclk/test.json new file mode 100644 index 000000000..a719e0a14 --- /dev/null +++ b/apps/antonclk/test.json @@ -0,0 +1,15 @@ +{ + "app" : "antonclk", + "tests" : [{ + "description": "Check memory usage after setUI", + "steps" : [ + {"t":"cmd", "js": "Bangle.loadWidgets()"}, + {"t":"cmd", "js": "eval(require('Storage').read('antonclk.app.js'))"}, + {"t":"cmd", "js": "Bangle.setUI()"}, + {"t":"saveMemoryUsage"}, + {"t":"cmd", "js": "eval(require('Storage').read('antonclk.app.js'))"}, + {"t":"cmd", "js":"Bangle.setUI()"}, + {"t":"checkMemoryUsage"} + ] + }] +} diff --git a/apps/astrocalc/ChangeLog b/apps/astrocalc/ChangeLog index ee9c7bbe4..df039ba0e 100644 --- a/apps/astrocalc/ChangeLog +++ b/apps/astrocalc/ChangeLog @@ -6,3 +6,4 @@ 0.06: Fix azimuth (bug #2651), only show degrees 0.07: Minor code improvements 0.08: Minor code improvements +0.09: Fix: Handle when the moon rise/set do not occur on the current day diff --git a/apps/astrocalc/astrocalc-app.js b/apps/astrocalc/astrocalc-app.js index cd1965290..283c5aab7 100644 --- a/apps/astrocalc/astrocalc-app.js +++ b/apps/astrocalc/astrocalc-app.js @@ -180,8 +180,8 @@ function drawMoonTimesPage(gps, title) { const moonColor = g.theme.dark ? {r: 1, g: 1, b: 1} : {r: 0, g: 0, b: 0}; const pageData = { - Rise: dateToTimeString(times.rise), - Set: dateToTimeString(times.set), + Rise: times.rise ? dateToTimeString(times.rise) : "Not today", + Set: times.set ? dateToTimeString(times.set) : "Not today", }; drawData(title, pageData, null, g.getHeight()/2 - Object.keys(pageData).length/2*20 + 5); @@ -240,7 +240,7 @@ function sunIndexPageMenu(gps) { "title": "-- Sun --", }, "Current Pos": () => { - m = E.showMenu(); + E.showMenu(); drawSunShowPage(gps, "Current Pos", new Date()); }, }; @@ -248,13 +248,13 @@ function sunIndexPageMenu(gps) { Object.keys(sunTimes).sort().reduce((menu, key) => { const title = titlizeKey(key); menu[title] = () => { - m = E.showMenu(); + E.showMenu(); drawSunShowPage(gps, key, sunTimes[key]); }; return menu; }, sunMenu); - sunMenu["< Back"] = () => m = indexPageMenu(gps); + sunMenu["< Back"] = () => indexPageMenu(gps); return E.showMenu(sunMenu); } @@ -266,18 +266,18 @@ function moonIndexPageMenu(gps) { "title": "-- Moon --", }, "Times": () => { - m = E.showMenu(); + E.showMenu(); drawMoonTimesPage(gps, /*LANG*/"Times"); }, "Position": () => { - m = E.showMenu(); + E.showMenu(); drawMoonPositionPage(gps, /*LANG*/"Position"); }, "Illumination": () => { - m = E.showMenu(); + E.showMenu(); drawMoonIlluminationPage(gps, /*LANG*/"Illumination"); }, - "< Back": () => m = indexPageMenu(gps), + "< Back": () => indexPageMenu(gps), }; return E.showMenu(moonMenu); @@ -289,10 +289,10 @@ function indexPageMenu(gps) { "title": /*LANG*/"Select", }, /*LANG*/"Sun": () => { - m = sunIndexPageMenu(gps); + sunIndexPageMenu(gps); }, /*LANG*/"Moon": () => { - m = moonIndexPageMenu(gps); + moonIndexPageMenu(gps); }, "< Back": () => { load(); } }; @@ -300,9 +300,9 @@ function indexPageMenu(gps) { return E.showMenu(menu); } -function getCenterStringX(str) { - return (g.getWidth() - g.stringWidth(str)) / 2; -} +//function getCenterStringX(str) { +// return (g.getWidth() - g.stringWidth(str)) / 2; +//} function init() { let location = require("Storage").readJSON("mylocation.json",1)||{"lat":51.5072,"lon":0.1276,"location":"London"}; @@ -311,5 +311,4 @@ function init() { Bangle.drawWidgets(); } -let m; init(); diff --git a/apps/astrocalc/metadata.json b/apps/astrocalc/metadata.json index a43534325..d88037d09 100644 --- a/apps/astrocalc/metadata.json +++ b/apps/astrocalc/metadata.json @@ -1,7 +1,7 @@ { "id": "astrocalc", "name": "Astrocalc", - "version": "0.08", + "version": "0.09", "description": "Calculates interesting information on the sun like sunset and sunrise and moon cycles for the current day based on your location from MyLocation app", "icon": "astrocalc.png", "tags": "app,sun,moon,cycles,tool,outdoors", diff --git a/apps/autoreset/ChangeLog b/apps/autoreset/ChangeLog index 5560f00bc..cef136817 100644 --- a/apps/autoreset/ChangeLog +++ b/apps/autoreset/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Add black- and whitelist for apps. Configure the timout time. diff --git a/apps/autoreset/README.md b/apps/autoreset/README.md index 965b2e291..3d086ee0e 100644 --- a/apps/autoreset/README.md +++ b/apps/autoreset/README.md @@ -6,11 +6,11 @@ Sets a timeout to load the clock face. The timeout is stopped and started again Install with app loader and Auto Reset will run in background. If you don't interact with the watch it will time out to the clock face after 10 minutes. +Through the settings apps can be black-/whitelisted and the timeout length can be configured. + ## TODO -- Add settings page - - set how many minutes the timeout should count down. - - whitelist/blacklist for apps. +- per app specific timeout lengths? ## Requests diff --git a/apps/autoreset/boot.js b/apps/autoreset/boot.js index 3302eca08..fc8d9ec0a 100644 --- a/apps/autoreset/boot.js +++ b/apps/autoreset/boot.js @@ -1,12 +1,29 @@ { +const DEFAULTS = { + mode: 0, + apps: [], + timeout: 10 +}; +const settings = require("Storage").readJSON("autoreset.json", 1) || DEFAULTS; + +// Check if the back button should be enabled for the current app. +// app is the src file of the app. +// Derivative of the backswipe app's logic. +function enabledForApp(app) { + if (Bangle.CLOCK==1) return false; + if (!settings) return true; + let isListed = settings.apps.filter((a) => a.files.includes(app)).length > 0; + return settings.mode===0?!isListed:isListed; +} + let timeoutAutoreset; -let resetTimeoutAutoreset = (force)=>{ +const resetTimeoutAutoreset = (force)=>{ if (timeoutAutoreset) clearTimeout(timeoutAutoreset); setTimeout(()=>{ // Short outer timeout to make sure we have time to leave clock face before checking `Bangle.CLOCK!=1`. - if (Bangle.CLOCK!=1) { // Only add timeout if not already on clock face. + if (enabledForApp(global.__FILE__)) { timeoutAutoreset = setTimeout(()=>{ if (Bangle.CLOCK!=1) Bangle.showClock(); - }, 10*60*1000); + }, settings.timeout*60*1000); } },200); }; diff --git a/apps/autoreset/metadata.json b/apps/autoreset/metadata.json index ca35b4d7a..c8866924b 100644 --- a/apps/autoreset/metadata.json +++ b/apps/autoreset/metadata.json @@ -1,6 +1,6 @@ { "id": "autoreset", "name": "Auto Reset", - "version":"0.01", + "version":"0.02", "description": "Sets a timeout to load the clock face. The timeout is stopped and started again upon user input.", "icon": "app.png", "type": "bootloader", @@ -8,6 +8,10 @@ "supports" : ["BANGLEJS2"], "readme": "README.md", "storage": [ - {"name":"autoreset.boot.js","url":"boot.js"} + {"name":"autoreset.boot.js","url":"boot.js"}, + {"name":"autoreset.settings.js","url":"settings.js"} + ], + "data":[ + {"name":"autoreset.json"} ] } diff --git a/apps/autoreset/settings.js b/apps/autoreset/settings.js new file mode 100644 index 000000000..8cbccd6f0 --- /dev/null +++ b/apps/autoreset/settings.js @@ -0,0 +1,115 @@ +(function(back) { + var FILE = 'autoreset.json'; + // Mode can be 'blacklist' or 'whitelist' + // Apps is an array of app info objects, where all the apps that are there are either blocked or allowed, depending on the mode + var DEFAULTS = { + 'mode': 0, + 'apps': [], + 'timeout': 10 + }; + + var settings = {}; + + var loadSettings = function() { + settings = require('Storage').readJSON(FILE, 1) || DEFAULTS; + }; + + var saveSettings = function(settings) { + require('Storage').write(FILE, settings); + }; + + // Get all app info files + var getApps = function() { + var apps = require('Storage').list(/\.info$/).map(appInfoFileName => { + var appInfo = require('Storage').readJSON(appInfoFileName, 1); + return appInfo && { + 'name': appInfo.name, + 'sortorder': appInfo.sortorder, + 'src': appInfo.src, + 'files': appInfo.files + }; + }).filter(app => app && !!app.src); + apps.sort((a, b) => { + var n = (0 | a.sortorder) - (0 | b.sortorder); + if (n) return n; // do sortorder first + if (a.name < b.name) return -1; + if (a.name > b.name) return 1; + return 0; + }); + return apps; + }; + + var showMenu = function() { + var menu = { + '': { 'title': 'Auto Reset' }, + /*LANG*/'< Back': () => { + back(); + }, + /*LANG*/'Mode': { + value: settings.mode, + min: 0, + max: 1, + format: v => ["Blacklist", "Whitelist"][v], + onchange: v => { + settings.mode = v; + saveSettings(settings); + }, + }, + /*LANG*/'App List': () => { + showAppSubMenu(); + }, + /*LANG*/'Timeout [min]': { + value: settings.timeout, + min: 0.25, max: 30, step : 0.25, + format: v => v, + onchange: v => { + settings.timeout = v; + saveSettings(settings); + }, + }, + }; + + E.showMenu(menu); + }; + + var showAppSubMenu = function() { + var menu = { + '': { 'title': 'Auto Reset' }, + '< Back': () => { + showMenu(); + }, + 'Add App': () => { + showAppList(); + } + }; + settings.apps.forEach(app => { + menu[app.name] = () => { + settings.apps.splice(settings.apps.indexOf(app), 1); + saveSettings(settings); + showAppSubMenu(); + } + }); + E.showMenu(menu); + } + + var showAppList = function() { + var apps = getApps(); + var menu = { + '': { 'title': 'Auto Reset' }, + /*LANG*/'< Back': () => { + showMenu(); + } + }; + apps.forEach(app => { + menu[app.name] = () => { + settings.apps.push(app); + saveSettings(settings); + showAppSubMenu(); + } + }); + E.showMenu(menu); + } + + loadSettings(); + showMenu(); +}) diff --git a/apps/backswipe/ChangeLog b/apps/backswipe/ChangeLog index 54ffebfc7..4e81269fe 100644 --- a/apps/backswipe/ChangeLog +++ b/apps/backswipe/ChangeLog @@ -1,3 +1,6 @@ 0.01: New App! 0.02: Don't fire if the app uses swipes already. 0.03: Only count defined handlers in the handler array. +0.04: Fix messages auto opened by `messagegui` could not be blacklisted. Needs + a refresh by deselecting and reselecting the "Messages" app throught Back Swipe + settings. diff --git a/apps/backswipe/boot.js b/apps/backswipe/boot.js index d5d54cd59..8ff277634 100644 --- a/apps/backswipe/boot.js +++ b/apps/backswipe/boot.js @@ -47,9 +47,9 @@ function enabledForApp(app) { if (!settings) return true; if (settings.mode === 0) { - return !(settings.apps.filter((a) => a.src === app).length > 0); + return !(settings.apps.filter((a) => (a.src===app)||(a.files&&a.files.includes(app))).length > 0); // The `a.src===app` and `a.files&&...` checks are for backwards compatibility. Otherwise only `a.files.includes(app)` is needed. } else if (settings.mode === 1) { - return settings.apps.filter((a) => a.src === app).length > 0; + return settings.apps.filter((a) => (a.src===app)||(a.files&&a.files.includes(app))).length > 0; } else { return settings.mode === 2 ? true : false; } diff --git a/apps/backswipe/metadata.json b/apps/backswipe/metadata.json index add609a0f..4324286b5 100644 --- a/apps/backswipe/metadata.json +++ b/apps/backswipe/metadata.json @@ -1,7 +1,7 @@ { "id": "backswipe", "name": "Back Swipe", "shortName":"BackSwipe", - "version":"0.03", + "version":"0.04", "description": "Service that allows you to use an app's back button using left to right swipe gesture", "icon": "app.png", "tags": "back,gesture,swipe", diff --git a/apps/backswipe/settings.js b/apps/backswipe/settings.js index 42ca7ae7d..c98f706eb 100644 --- a/apps/backswipe/settings.js +++ b/apps/backswipe/settings.js @@ -26,7 +26,8 @@ return appInfo && { 'name': appInfo.name, 'sortorder': appInfo.sortorder, - 'src': appInfo.src + 'src': appInfo.src, + 'files': appInfo.files }; }).filter(app => app && !!app.src); apps.sort((a, b) => { diff --git a/apps/blecsc/ChangeLog b/apps/blecsc/ChangeLog new file mode 100644 index 000000000..1500000b5 --- /dev/null +++ b/apps/blecsc/ChangeLog @@ -0,0 +1,5 @@ +0.01: Initial version +0.02: Minor code improvements +0.03: Moved from cycling app, fixed connection issues and cadence +0.04: Added support for <1 wheel/crank event/second (using idle counters) (ref #3434) +0.05: Fix <1 event/second issue \ No newline at end of file diff --git a/apps/blecsc/README.md b/apps/blecsc/README.md new file mode 100644 index 000000000..5cde87168 --- /dev/null +++ b/apps/blecsc/README.md @@ -0,0 +1,32 @@ +# BLE Cycling Speed Sencor (CSC) + +Displays data from a BLE Cycling Speed and Cadence sensor. + +Other than in the original version of the app, total distance is not stored on the Bangle, but instead is calculated from the CWR (cumulative wheel revolutions) reported by the sensor. This metric is, according to the BLE spec, an absolute value that persists throughout the lifetime of the sensor and never rolls over. + +## Settings + +Accessible from `Settings -> Apps -> BLE CSC` + +Here you can set the wheel diameter + +## Development + +``` +var csc = require("blecsc").getInstance(); +csc.on("status", txt => { + print("##", txt); + E.showMessage(txt); +}); +csc.on("data", e => print(e)); +csc.start(); +``` + +The `data` event contains: + + * cwr/ccr => wheel/crank cumulative revs + * lwet/lcet => wheel/crank last event time in 1/1024s + * wrps/crps => calculated wheel/crank revs per second + * wdt/cdt => time period in seconds between events + * wr => wheel revs + * kph => kilometers per hour \ No newline at end of file diff --git a/apps/blecsc/blecsc.js b/apps/blecsc/blecsc.js new file mode 100644 index 000000000..9b7d8b751 --- /dev/null +++ b/apps/blecsc/blecsc.js @@ -0,0 +1,230 @@ +/** + * This library communicates with a Bluetooth CSC peripherial using the Espruino NRF library. + * + * ## Usage: + * 1. Register event handlers using the \`on(eventName, handlerFunction)\` method + * You can subscribe to the \`wheelEvent\` and \`crankEvent\` events or you can + * have raw characteristic values passed through using the \`value\` event. + * 2. Search and connect to a BLE CSC peripherial by calling the \`connect()\` method + * 3. To tear down the connection, call the \`disconnect()\` method + * + * ## Events + * - \`status\` - string containing connection status + * - \`data\` - the peripheral sends a notification containing wheel/crank event data + * - \`disconnect\` - the peripheral ends the connection or the connection is lost + * + * cwr/ccr => wheel/crank cumulative revs + * lwet/lcet => wheel/crank last event time in 1/1024s + * wrps/crps => calculated wheel/crank revs per second + * wdt/cdt => time period in seconds between events + * wr => wheel revs + * kph => kilometers per hour + */ +class BLECSC { + constructor() { + this.reconnect = false; // set when start called + this.device = undefined; // set when device found + this.gatt = undefined; // set when connected + // .on("status", => string + // .on("data" + // .on("disconnect" + this.resetStats(); + // Set default values and merge with stored values + this.settings = Object.assign({ + circum: 2068 // circumference in mm + }, (require('Storage').readJSON('blecsc.json', true) || {})); + } + + resetStats() { + this.cwr = undefined; + this.ccr = undefined; + this.lwet = undefined; + this.lcet = undefined; + this.lastCwr = undefined; + this.lastCcr = undefined; + this.lastLwet = undefined; + this.lastLcet = undefined; + this.kph = undefined; + this.wrps = 0; // wheel revs per second + this.crps = 0; // crank revs per second + this.widle = 0; // wheel idle counter + this.cidle = 0; // crank idle counter + //this.batteryLevel = undefined; + } + + getDeviceAddress() { + if (!this.device || !this.device.id) + return '00:00:00:00:00:00'; + return this.device.id.split(" ")[0]; + } + + status(txt) { + this.emit("status", txt); + } + + /** + * Find and connect to a device which exposes the CSC service. + * + * @return {Promise} + */ + connect() { + this.status("Scanning"); + // Find a device, then get the CSC Service and subscribe to + // notifications on the CSC Measurement characteristic. + // NRF.setLowPowerConnection(true); + var reconnect = this.reconnect; // auto-reconnect + return NRF.requestDevice({ + timeout: 5000, + filters: [{ + services: ["1816"] + }], + }).then(device => { + this.status("Connecting"); + this.device = device; + this.device.on('gattserverdisconnected', event => { + this.device = undefined; + this.gatt = undefined; + this.resetStats(); + this.status("Disconnected"); + this.emit("disconnect", event); + if (reconnect) {// auto-reconnect + reconnect = false; + setTimeout(() => { + if (this.reconnect) this.connect().then(() => {}, () => {}); + }, 500); + } + }); + + return new Promise(resolve => setTimeout(resolve, 150)); // On CooSpo we get a 'Connection Timeout' if we try and connect too soon + }).then(() => { + return this.device.gatt.connect(); + }).then(gatt => { + this.status("Connected"); + this.gatt = gatt; + return gatt.getPrimaryService("1816"); + }).then(service => { + return service.getCharacteristic("2a5b"); // UUID of the CSC measurement characteristic + }).then(characteristic => { + // register for changes on 2a5b + characteristic.on('characteristicvaluechanged', event => { + const flags = event.target.value.getUint8(0); + var offs = 0; + var data = {}; + if (flags & 1) { // FLAGS_WREV_BM + this.lastCwr = this.cwr; + this.lastLwet = this.lwet; + this.cwr = event.target.value.getUint32(1, true); + this.lwet = event.target.value.getUint16(5, true); + if (this.lastCwr === undefined) this.lastCwr = this.cwr; + if (this.lastLwet === undefined) this.lastLwet = this.lwet; + if (this.lwet < this.lastLwet) this.lastLwet -= 65536; + let secs = (this.lwet - this.lastLwet) / 1024; + if (secs) { + this.wrps = (this.cwr - this.lastCwr) / secs; + this.widle = 0; + } else { + if (this.widle<5) this.widle++; + else this.wrps = 0; + } + this.kph = this.wrps * this.settings.circum / 3600; + Object.assign(data, { // Notify the 'wheelEvent' handler + cwr: this.cwr, // cumulative wheel revolutions + lwet: this.lwet, // last wheel event time + wrps: this.wrps, // wheel revs per second + wr: this.cwr - this.lastCwr, // wheel revs + wdt : secs, // time period + kph : this.kph + }); + offs += 6; + } + if (flags & 2) { // FLAGS_CREV_BM + this.lastCcr = this.ccr; + this.lastLcet = this.lcet; + this.ccr = event.target.value.getUint16(offs + 1, true); + this.lcet = event.target.value.getUint16(offs + 3, true); + if (this.lastCcr === undefined) this.lastCcr = this.ccr; + if (this.lastLcet === undefined) this.lastLcet = this.lcet; + if (this.lcet < this.lastLcet) this.lastLcet -= 65536; + let secs = (this.lcet - this.lastLcet) / 1024; + if (secs) { + this.crps = (this.ccr - this.lastCcr) / secs; + this.cidle = 0; + } else { + if (this.cidle<5) this.cidle++; + else this.crps = 0; + } + Object.assign(data, { // Notify the 'crankEvent' handler + ccr: this.ccr, // cumulative crank revolutions + lcet: this.lcet, // last crank event time + crps: this.crps, // crank revs per second + cdt : secs, // time period + }); + } + this.emit("data",data); + }); + return characteristic.startNotifications(); +/* }).then(() => { + return this.gatt.getPrimaryService("180f"); + }).then(service => { + return service.getCharacteristic("2a19"); + }).then(characteristic => { + characteristic.on('characteristicvaluechanged', (event)=>{ + this.batteryLevel = event.target.value.getUint8(0); + }); + return characteristic.startNotifications();*/ + }).then(() => { + this.status("Ready"); + }, err => { + this.status("Error: " + err); + if (reconnect) { // auto-reconnect + reconnect = false; + setTimeout(() => { + if (this.reconnect) this.connect().then(() => {}, () => {}); + }, 500); + } + throw err; + }); + } + + /** + * Disconnect the device. + */ + disconnect() { + if (!this.gatt) return; + this.gatt.disconnect(); + this.gatt = undefined; + } + + /* Start trying to connect - will keep searching and attempting to connect*/ + start() { + this.reconnect = true; + if (!this.device) + this.connect().then(() => {}, () => {}); + } + + /* Stop trying to connect, and disconnect */ + stop() { + this.reconnect = false; + this.disconnect(); + } +} + +// Get an instance of BLECSC or create one if it doesn't exist +BLECSC.getInstance = function() { + if (!BLECSC.instance) { + BLECSC.instance = new BLECSC(); + } + return BLECSC.instance; +}; + +exports = BLECSC; + +/* +var csc = require("blecsc").getInstance(); +csc.on("status", txt => { + print("##", txt); + E.showMessage(txt); +}); +csc.on("data", e => print(e)); +csc.start(); +*/ diff --git a/apps/blecsc/clkinfo.js b/apps/blecsc/clkinfo.js new file mode 100644 index 000000000..9a9515c3a --- /dev/null +++ b/apps/blecsc/clkinfo.js @@ -0,0 +1,74 @@ +(function() { + var csc = require("blecsc").getInstance(); + //csc.on("status", txt => { print("CSC",txt); }); + csc.on("data", e => { + ci.items.forEach(it => { if (it._visible) it.emit('redraw'); }); + }); + csc.on("disconnect", e => { + // redraw all with no info + ci.items.forEach(it => { if (it._visible) it.emit('redraw'); }); + }); + var uses = 0; + var ci = { + name: "CSC", + items: [ + { name : "Speed", + get : () => { + return { + text : (csc.kph === undefined) ? "--" : require("locale").speed(csc.kph), + img : atob("GBiBAAAAAAAAAAAAAAABwAABwAeBgAMBgAH/gAH/wAPDwA/DcD9m/Ge35sW9o8//M8/7E8CBA2GBhn8A/h4AeAAAAAAAAAAAAAAAAA==") + }; + }, + show : function() { + uses++; + if (uses==1) csc.start(); + this._visible = true; + }, + hide : function() { + this._visible = false; + uses--; + if (uses==0) csc.stop(); + } + }, + { name : "Distance", + get : () => { + return { + text : (csc.kph === undefined) ? "--" : require("locale").distance(csc.cwr * csc.settings.circum / 1000), + img : atob("GBiBAAAAAB8AADuAAGDAAGTAAGRAAEBAAGBAAGDAADCAADGAIB8B+A/BjAfjBgAyJgAyIgAyAj/jBnADBmABjGAA2HAA8D//4AAAAA==") + }; + }, + show : function() { + uses++; + if (uses==1) csc.start(); + this._visible = true; + }, + hide : function() { + this._visible = false; + uses--; + if (uses==0) csc.stop(); + } + }, + { name : "Cadence", + get : () => { + return { + text : (csc.crps === undefined) ? "--" : Math.round(csc.crps*60), + img : atob("GBiBAAAAAAAAAAB+EAH/sAeB8A4A8AwB8BgAABgAADAAADAAADAAADAADDAADDAAABgAABgAGAwAEA4AAAeAwAH8gAB8AAAAAAAAAA==") + }; + }, + show : function() { + uses++; + if (uses==1) csc.start(); + this._visible = true; + }, + hide : function() { + this._visible = false; + uses--; + if (uses==0) csc.stop(); + } + } + ] + }; + return ci; +}) + + diff --git a/apps/blecsc/icons8-cycling-48.png b/apps/blecsc/icons8-cycling-48.png new file mode 100644 index 000000000..0bc83859f Binary files /dev/null and b/apps/blecsc/icons8-cycling-48.png differ diff --git a/apps/blecsc/metadata.json b/apps/blecsc/metadata.json new file mode 100644 index 000000000..0daa01fc8 --- /dev/null +++ b/apps/blecsc/metadata.json @@ -0,0 +1,22 @@ +{ + "id": "blecsc", + "name": "BLE Cycling Speed Sensor Library", + "shortName": "BLE CSC", + "version": "0.05", + "description": "Module to get live values from a BLE Cycle Speed (CSC) sensor. Includes recorder and clockinfo plugins", + "icon": "icons8-cycling-48.png", + "tags": "outdoors,exercise,ble,bluetooth,clkinfo", + "type":"module", + "provides_modules" : ["blecsc"], + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"blecsc","url":"blecsc.js"}, + {"name":"blecsc.settings.js","url":"settings.js"}, + {"name":"blecsc.recorder.js","url":"recorder.js"}, + {"name":"blecsc.clkinfo.js","url":"clkinfo.js"} + ], + "data": [ + {"name":"blecsc.json"} + ] +} diff --git a/apps/blecsc/recorder.js b/apps/blecsc/recorder.js new file mode 100644 index 000000000..510f50c3f --- /dev/null +++ b/apps/blecsc/recorder.js @@ -0,0 +1,28 @@ +(function(recorders) { + recorders.blecsc = function() { + var csc = require("blecsc").getInstance(); + var speed, cadence; + csc.on("data", e => { + speed = e.kph; // speed in KPH + cadence = (e.crps===undefined)?"":Math.round(e.crps*60); // crank rotations per minute + }); + return { + name : "CSC", + fields : ["Speed (kph)","Cadence (rpm)"], + getValues : () => { + var r = [speed,cadence]; + speed = ""; + cadence = ""; + return r; + }, + start : () => { + csc.start(); + }, + stop : () => { + csc.stop(); + }, + draw : (x,y) => g.setColor(csc.device?"#0f0":"#8f8").drawImage(atob("Dw+BAAAAAAABgOIA5gHcBxw9fpfTPqYRC8HgAAAAAAAA"),x,y) + }; + } +}) + diff --git a/apps/blecsc/settings.js b/apps/blecsc/settings.js new file mode 100644 index 000000000..b445b2541 --- /dev/null +++ b/apps/blecsc/settings.js @@ -0,0 +1,85 @@ +(function(back) { + const storage = require('Storage') + const SETTINGS_FILE = 'blecsc.json' + + // Set default values and merge with stored values + let settings = Object.assign({ + circum: 2068 // circumference in mm + }, (storage.readJSON(SETTINGS_FILE, true) || {})); + + function saveSettings() { + storage.writeJSON(SETTINGS_FILE, settings); + } + + function circumMenu() { + var v = 0|settings.circum; + var cm = 0|(v/10); + var mm = v-(cm*10); + E.showMenu({ + '': { title: /*LANG*/"Circumference", back: mainMenu }, + 'cm': { + value: cm, + min: 80, max: 240, step: 1, + onchange: (v) => { + cm = v; + settings.circum = (cm*10)+mm; + saveSettings(); + }, + }, + '+ mm': { + value: mm, + min: 0, max: 9, step: 1, + onchange: (v) => { + mm = v; + settings.circum = (cm*10)+mm; + saveSettings(); + }, + }, + /*LANG*/'Std Wheels': function() { + // https://support.wahoofitness.com/hc/en-us/articles/115000738484-Tire-Size-Wheel-Circumference-Chart + E.showMenu({ + '': { title: /*LANG*/'Std Wheels', back: circumMenu }, + '650x38 wheel' : function() { + settings.circum = 1995; + saveSettings(); + mainMenu(); + }, + '700x32c wheel' : function() { + settings.circum = 2152; + saveSettings(); + mainMenu(); + }, + '24"x1.75 wheel' : function() { + settings.circum = 1890; + saveSettings(); + mainMenu(); + }, + '26"x1.5 wheel' : function() { + settings.circum = 2010; + saveSettings(); + mainMenu(); + }, + '27.5"x1.5 wheel' : function() { + settings.circum = 2079; + saveSettings(); + mainMenu(); + } + }); + } + + }); + } + + function mainMenu() { + E.showMenu({ + '': { 'title': 'BLE CSC' }, + '< Back': back, + /*LANG*/'Circumference': { + value: settings.circum+"mm", + onchange: circumMenu + }, + }); + } + + mainMenu(); +}) \ No newline at end of file diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 95a9ff2c6..8948002e5 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -69,3 +69,4 @@ 0.58: "Make Connectable" temporarily bypasses the whitelist 0.59: Whitelist: Try to resolve peer addresses using NRF.resolveAddress() - for 2v19 or 2v18 cutting edge builds 0.60: Minor code improvements +0.61: Instead of breaking execution with an Exception when updating boot, just use if..else (fix 'Uncaught undefined') \ No newline at end of file diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index 315b60723..f8ba22752 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -14,15 +14,15 @@ if (DEBUG) { } if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed let CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT); - boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`; + boot += `if(E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`; } else { let CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT); - boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`; + boot += `if(E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`; } -boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`; +boot += `{eval(require('Storage').read('bootupdate.js'));print("Storage Updated!")}else{\n`; boot += `E.setFlags({pretokenise:1});\n`; boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`; -bootPost += `NRF.setServices(bleServices, bleServiceOptions);delete bleServices,bleServiceOptions;\n`; // executed after other boot code +bootPost += `NRF.setServices(bleServices,bleServiceOptions);delete bleServices,bleServiceOptions;\n`; // executed after other boot code if (s.ble!==false) { if (s.HID) { // Human interface device if (s.HID=="joy") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA="));`; @@ -105,7 +105,7 @@ if (!date.toLocalISOString) boot += `Date.prototype.toLocalISOString = function( // show timings if (DEBUG) boot += `print(".boot0",0|(Date.now()-_tm),"ms");_tm=Date.now();\n` // ================================================== BOOT.JS -// Append *.boot.js files. +// Append *.boot.js files. // Name files with a number - eg 'foo.5.boot.js' to enforce order (lowest first). Numbered files get placed before non-numbered // These could change bleServices/bleServiceOptions if needed let bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{ @@ -122,6 +122,7 @@ let bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{ return a==b ? 0 : (a>b ? 1 : -1); }); // precalculate file size +bootPost += "}"; let fileSize = boot.length + bootPost.length; bootFiles.forEach(bootFile=>{ // match the size of data we're adding below in bootFiles.forEach diff --git a/apps/boot/metadata.json b/apps/boot/metadata.json index 07e999797..60c4e1814 100644 --- a/apps/boot/metadata.json +++ b/apps/boot/metadata.json @@ -1,7 +1,7 @@ { "id": "boot", "name": "Bootloader", - "version": "0.60", + "version": "0.61", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "icon": "bootloader.png", "type": "bootloader", diff --git a/apps/bootgattbat/ChangeLog b/apps/bootgattbat/ChangeLog index df07f6ad0..3df6422e5 100644 --- a/apps/bootgattbat/ChangeLog +++ b/apps/bootgattbat/ChangeLog @@ -1,2 +1,3 @@ 0.01: Initial release. 0.02: Handle the case where other apps have set bleAdvert to an array +0.03: Use the bleAdvert module diff --git a/apps/bootgattbat/boot.js b/apps/bootgattbat/boot.js index 34d9f8d93..f9b5969e2 100644 --- a/apps/bootgattbat/boot.js +++ b/apps/bootgattbat/boot.js @@ -1,26 +1,8 @@ (() => { function advertiseBattery() { - if(Array.isArray(Bangle.bleAdvert)){ - // ensure we're in the cycle - var found = false; - for(var ad in Bangle.bleAdvert){ - if(ad[0x180F]){ - ad[0x180F] = [E.getBattery()]; - found = true; - break; - } - } - if(!found) - Bangle.bleAdvert.push({ 0x180F: [E.getBattery()] }); - }else{ - // simple object - Bangle.bleAdvert[0x180F] = [E.getBattery()]; - } - - NRF.setAdvertising(Bangle.bleAdvert); + require("ble_advert").set(0x180F, [E.getBattery()]); } - if (!Bangle.bleAdvert) Bangle.bleAdvert = {}; setInterval(advertiseBattery, 60 * 1000); advertiseBattery(); })(); diff --git a/apps/bootgattbat/metadata.json b/apps/bootgattbat/metadata.json index f67b4507d..b53708c2e 100644 --- a/apps/bootgattbat/metadata.json +++ b/apps/bootgattbat/metadata.json @@ -2,7 +2,7 @@ "id": "bootgattbat", "name": "BLE GATT Battery Service", "shortName": "BLE Battery Service", - "version": "0.02", + "version": "0.03", "description": "Adds the GATT Battery Service to advertise the percentage of battery currently remaining over Bluetooth.\n", "icon": "bluetooth.png", "type": "bootloader", diff --git a/apps/bootgatthrm/ChangeLog b/apps/bootgatthrm/ChangeLog index 2512ed877..205f036b5 100644 --- a/apps/bootgatthrm/ChangeLog +++ b/apps/bootgatthrm/ChangeLog @@ -1,3 +1,4 @@ 0.01: Initial release. 0.02: Added compatibility to OpenTracks and added HRM Location 0.03: Allow setting to keep BLE connected +0.04: Use the bleAdvert module diff --git a/apps/bootgatthrm/boot.js b/apps/bootgatthrm/boot.js index 0fceb86d9..efba1f453 100644 --- a/apps/bootgatthrm/boot.js +++ b/apps/bootgatthrm/boot.js @@ -4,18 +4,13 @@ * This function prepares BLE heart rate Advertisement. */ - NRF.setAdvertising( - { - 0x180d: undefined - }, - { - // We need custom Advertisement settings for Apps like OpenTracks - connectable: true, - discoverable: true, - scannable: true, - whenConnected: true, - } - ); + require("ble_advert").set(0x180d, undefined, { + // We need custom Advertisement settings for Apps like OpenTracks + connectable: true, + discoverable: true, + scannable: true, + whenConnected: true, + }); NRF.setServices({ 0x180D: { // heart_rate @@ -28,7 +23,6 @@ } } }); - } const keepConnected = (require("Storage").readJSON("gatthrm.settings.json", 1) || {}).keepConnected; diff --git a/apps/bootgatthrm/metadata.json b/apps/bootgatthrm/metadata.json index dc831ab7d..d32b51601 100644 --- a/apps/bootgatthrm/metadata.json +++ b/apps/bootgatthrm/metadata.json @@ -2,7 +2,7 @@ "id": "bootgatthrm", "name": "BLE GATT HRM Service", "shortName": "BLE HRM Service", - "version": "0.03", + "version": "0.04", "description": "Adds the GATT HRM Service to advertise the measured HRM over Bluetooth.\n", "icon": "bluetooth.png", "type": "bootloader", diff --git a/apps/btadv/ChangeLog b/apps/btadv/ChangeLog index 07e67157c..245f4fca6 100644 --- a/apps/btadv/ChangeLog +++ b/apps/btadv/ChangeLog @@ -1,2 +1,3 @@ 0.01: New app! 0.02: Advertise accelerometer data and sensor location +0.03: Use the bleAdvert module diff --git a/apps/btadv/app.ts b/apps/btadv/app.ts index 1d9501175..b56546ba1 100644 --- a/apps/btadv/app.ts +++ b/apps/btadv/app.ts @@ -1,5 +1,6 @@ { -// @ts-ignore helper +// @ts-expect-error helper +// eslint-disable-next-line @typescript-eslint/no-unused-vars const __assign = Object.assign; const Layout = require("Layout"); @@ -666,6 +667,8 @@ const getBleAdvert = (map: (s: BleServ) => T, all = false) => { // done via advertise in setServices() //const updateBleAdvert = () => { +// require("ble_advert").set(...) +// // let bleAdvert: ReturnType>; // // if (!(bleAdvert = (Bangle as any).bleAdvert)) { diff --git a/apps/btadv/metadata.json b/apps/btadv/metadata.json index efe024a2f..060c2b498 100644 --- a/apps/btadv/metadata.json +++ b/apps/btadv/metadata.json @@ -2,7 +2,7 @@ "id": "btadv", "name": "btadv", "shortName": "btadv", - "version": "0.02", + "version": "0.03", "description": "Advertise & export live heart rate, accel, pressure, GPS & mag data over bluetooth", "icon": "icon.png", "tags": "health,tool,sensors,bluetooth", diff --git a/apps/bthome/ChangeLog b/apps/bthome/ChangeLog index ca91e0f34..82c93ee1f 100644 --- a/apps/bthome/ChangeLog +++ b/apps/bthome/ChangeLog @@ -2,4 +2,5 @@ 0.02: Fix double-button press if you press the next button within 30s (#3243) 0.03: Cope with identical duplicate buttons (fix #3260) Set 'n' for buttons in Bangle.btHomeData correctly (avoids adding extra buttons on end of advertising) -0.04: Fix duplicate button on edit->save \ No newline at end of file +0.04: Fix duplicate button on edit->save +0.05: Use the bleAdvert module diff --git a/apps/bthome/boot.js b/apps/bthome/boot.js index 7c9e37f77..00e08df90 100644 --- a/apps/bthome/boot.js +++ b/apps/bthome/boot.js @@ -1,5 +1,3 @@ -// Ensure we have the bleAdvert global (to play well with other stuff) -if (!Bangle.bleAdvert) Bangle.bleAdvert = {}; Bangle.btHomeData = []; { require("BTHome").packetId = 0|(Math.random()*256); // random packet id so new packets show up @@ -39,20 +37,6 @@ Bangle.btHome = function(extras, options) { if (bat) bat.v = E.getBattery(); var advert = require("BTHome").getAdvertisement(Bangle.btHomeData)[0xFCD2]; // Add to the list of available advertising - if(Array.isArray(Bangle.bleAdvert)){ - var found = false; - for(var ad in Bangle.bleAdvert){ - if(ad[0xFCD2]){ - ad[0xFCD2] = advert; - found = true; - break; - } - } - if(!found) - Bangle.bleAdvert.push({ 0xFCD2: advert }); - } else { - Bangle.bleAdvert[0xFCD2] = advert; - } var advOptions = {}; var updateTimeout = 10*60*1000; // update every 10 minutes if (options.event) { // if it's an event... @@ -60,7 +44,7 @@ Bangle.btHome = function(extras, options) { advOptions.whenConnected = true; updateTimeout = 30000; // slow down in 30 seconds } - NRF.setAdvertising(Bangle.bleAdvert, advOptions); + require("ble_advert").set(0xFCD2, advert, advOptions); if (Bangle.btHomeTimeout) clearTimeout(Bangle.btHomeTimeout); Bangle.btHomeTimeout = setTimeout(function() { delete Bangle.btHomeTimeout; diff --git a/apps/bthome/metadata.json b/apps/bthome/metadata.json index 1ccc19316..0156a5d32 100644 --- a/apps/bthome/metadata.json +++ b/apps/bthome/metadata.json @@ -1,7 +1,7 @@ { "id": "bthome", "name": "BTHome", "shortName":"BTHome", - "version":"0.04", + "version":"0.05", "description": "Allow your Bangle to advertise with BTHome and send events to Home Assistant via Bluetooth", "icon": "icon.png", "type": "app", diff --git a/apps/burn/.gitignore b/apps/burn/.gitignore new file mode 100644 index 000000000..656b79624 --- /dev/null +++ b/apps/burn/.gitignore @@ -0,0 +1 @@ +.prettierignore \ No newline at end of file diff --git a/apps/burn/ChangeLog b/apps/burn/ChangeLog new file mode 100644 index 000000000..66c4f98bd --- /dev/null +++ b/apps/burn/ChangeLog @@ -0,0 +1,7 @@ +0.01: New App! +0.02: Added README.md +0.03: Icon update +0.04: Icon Fix +0.05: Misc cleanup for PR +0.06: Implementing fixes from PR comments +0.07: Bug fix diff --git a/apps/burn/README.md b/apps/burn/README.md new file mode 100644 index 000000000..44f1e260f --- /dev/null +++ b/apps/burn/README.md @@ -0,0 +1,30 @@ +# Burn: Calorie Counter + +Burn is a simple calorie counter application. It is based on the original Counter app and has been enhanced with additional features (I recommend using +it with the "Digital Clock Widget", if you intend to keep it running). + +## Features + +- **Persistent counter**: The counter value is saved to flash, so it persists even when the app is closed or the device is restarted. +- **Daily reset**: The counter resets each day, allowing you to track your calorie intake on a daily basis. +- **Adjustable increment value**: You can adjust the increment value to suit your needs. + +## Controls + +### Bangle.js 1 + +- **BTN1**: Increase (or tap right) +- **BTN3**: Decrease (or tap left) +- **Press BTN2**: Change increment + +### Bangle.js 2 + +- **Swipe up**: Increase +- **Swipe down**: Decrease +- **Press BTN**: Change increment + +## How it Works + +The counter value and the date are stored in a file named "kcal.txt". The counter value is read from the file when the app starts and written to the file whenever the counter value is updated. + +The app uses the current date to determine whether to reset the counter. If the date has changed since the last time the counter was updated, the counter is reset to 0. diff --git a/apps/burn/app-icon.js b/apps/burn/app-icon.js new file mode 100644 index 000000000..8cd3b7ca1 --- /dev/null +++ b/apps/burn/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4f/gUA///j32o8h9v6glA+P9+/3tu27YCLvICBgEGCJlFmwRBgALFxQRIgIdF28JmIIEknG7cMyVJk4nBC4PJk4dBC4OJkmSrYRCkuACQM26/88wRGgQHB2iGCm33//8GoXtonbraYGgwRB/+bNY4AEg9/CIPbth3BxYRJn4RB/YRBgEUTwIRGne275IBCIQABjYRGrpCB+VK1gJDgYQFgXN23YQwIjEAA0WMwPQ0mSqgRK2QRBy6cBCJUFGIO12wjBpgRMlsAqmSqTOCAA0sCINogEIyVKCJdLoEAhQRNpQFCyVII5IRGqARQNZUECIcGyRLBPpPSCIQWBsCzK3VJoEB0mTCBUAitplEA0WYCJb7B1EBCYIAsjJjCknDpMkyUAmlKNwuEyEAgMSwwQBpNhAQM4CAcDkgRBe4ODogOB4MT0MldgcxCIXEyWAi3axNgykECIcBxIRBEwIRBYoK3BykGkw1DxPEyEZksSCIMEpcDjAIBGocbhMQiMzCIUyqALB5KmFhMghMk0VLQANYPoLeBCI0SRgOKJYOAgOSpihFCIMaCIOTgMk4ACBqVICIyTBKIMZkkAhpyBo4RHgOk4EZPAIjByVFNYYIBAoU2AoOwAQO5kmMf7QALpMg2VJ23Mn////8OIVThmUCIs27FvCIP+eQOSpUIyXAgFNEYfQCIX8P4OSp0MCIVZgEWFoNgrMktuwgdt23YOIQ")) \ No newline at end of file diff --git a/apps/burn/app-icon.png b/apps/burn/app-icon.png new file mode 100644 index 000000000..23d4a13e6 Binary files /dev/null and b/apps/burn/app-icon.png differ diff --git a/apps/burn/app-screenshot.png b/apps/burn/app-screenshot.png new file mode 100644 index 000000000..fef0e701e Binary files /dev/null and b/apps/burn/app-screenshot.png differ diff --git a/apps/burn/app.js b/apps/burn/app.js new file mode 100644 index 000000000..348a19153 --- /dev/null +++ b/apps/burn/app.js @@ -0,0 +1,243 @@ +/* + * Burn: Calories Counter for Bangle.js (Espruino). Based on the original Counter app. + * Features: + * - Persistent counter: saved to a file. + * - Daily reset: counter resets each day. + * - Adjustable increment value. + * + * Bangle.js 1 Controls: + * - BTN1: Increase (or tap right) + * - BTN3: Decrease (or tap left) + * - Press BTN2: Change increment + * + * Bangle.js 2 Controls: + * - Swipe up: Increase + * - Swipe down: Decrease + * - Press BTN: Change increment + */ + +// File variable to handle file operations +let file; + +// Check if the hardware version is Bangle.js 2 +const BANGLEJS2 = process.env.HWVERSION == 2; + +// Importing the Storage module for file operations +const Storage = require("Storage"); + +// File path for the counter data +const PATH = "kcal.txt"; + +// Function to get the current date as a string +function dayString() { + const date = new Date(); + // Month is 0-indexed, so we add 1 to get the correct month number + return `${date.getMonth() + 1}-${date.getDate()}-${date.getFullYear()}`; +} + +// Counter object to keep track of the count and the date +let counter = { count: 0, date: dayString() }; + +// Function to read the counter from the file +function readCounterFromFile() { + try { + // Open the file in read mode + file = Storage.open(PATH, "r"); + let line = file.readLine(); + + // If the file has content, parse it and update the counter + if (line) { + let splitLine = line.trim().split(","); + counter = { count: parseInt(splitLine[0]), date: splitLine[1] }; + } + } catch (err) { + // If the file does not exist, the counter will remain 0 + } +} + +// Function to write the counter to the file +function writeCounterToFile() { + // Open the file in write mode + file = Storage.open(PATH, "w"); + // Write the counter and date to the file + file.write(counter.count.toString() + "," + counter.date + "\n"); +} + +// Function to reset the counter +function resetCounter() { + // Reset the counter to 0 and update the date + counter = { count: 0, date: dayString() }; +} + +// Function to update the counter value +function updateCounterValue(value) { + // Update the counter with the new value, ensuring it's not less than 0 + counter = { count: Math.max(0, value), date: dayString() }; +} + +// Function to update the counter +function updateCounter(value) { + // If the date has changed, reset the counter + if (counter.date != dayString()) { + resetCounter(); + } else { + // Otherwise, update the counter value + updateCounterValue(value); + } + + // Write the updated counter to the file + writeCounterToFile(); + // Update the screen with the new counter value + updateScreen(); +} + +// Function to set a watch on a button to update the counter when pressed +function counterButtonWatch(button, increment) { + setWatch( + () => { + // If the button is for incrementing, or the counter is greater than 0, update the counter + if (increment || counter.count > 0) { + updateCounter( + counter.count + (increment ? getInterval() : -getInterval()) + ); + // Update the screen with the new counter value + updateScreen(); + } + }, + button, + { repeat: true } + ); +} + +// Function to create interval functions +const createIntervalFunctions = function () { + // Array of intervals + const intervals = [50, 100, 200, 10]; + // Current location in the intervals array + let location = 0; + + // Function to get the current interval + const getInterval = function () { + return intervals[location]; + }; + + // Function to rotate the increment + const rotateIncrement = function () { + // Update the location to the next index in the intervals array, wrapping around if necessary + location = (location + 1) % intervals.length; + // Update the screen with the new increment + updateScreen(); + }; + + // Return the getInterval and rotateIncrement functions + return { getInterval, rotateIncrement }; +}; + +// Create the interval functions +const intervalFunctions = createIntervalFunctions(); +const getInterval = intervalFunctions.getInterval; +const rotateIncrement = intervalFunctions.rotateIncrement; + +// Function to update the screen +function updateScreen() { + // Clear the screen area for the counter + g.clearRect(0, 50, 250, BANGLEJS2 ? 130 : 150) + .setBgColor(g.theme.bg) + .setColor(g.theme.fg) + .setFont("Vector", 40) + .setFontAlign(0, 0) + // Draw the counter value + .drawString(Math.floor(counter.count), g.getWidth() / 2, 100) + .setFont("6x8") + // Clear the screen area for the increment + .clearRect(g.getWidth() / 2 - 50, 140, g.getWidth() / 2 + 50, 160) + // Draw the increment value + .drawString("Increment: " + getInterval(), g.getWidth() / 2, 150); + + // If the hardware version is Bangle.js 1, draw the increment and decrement buttons + if (!BANGLEJS2) { + g.drawString("-", 45, 100).drawString("+", 185, 100); + } +} + +// If the hardware version is Bangle.js 2, set up the drag handling and button watch + +let drag; + +if (BANGLEJS2) { + // Set up drag handling + Bangle.on("drag", (e) => { + // If this is the start of a drag, record the initial coordinates + if (!drag) { + drag = { x: e.x, y: e.y }; + return; + } + + // If the button is still being pressed, ignore this event + if (e.b) return; + + // Calculate the change in x and y from the start of the drag + const dx = e.x - drag.x; + const dy = e.y - drag.y; + // Reset the drag start coordinates + drag = null; + + // Determine if the drag is primarily horizontal or vertical + const isHorizontalDrag = Math.abs(dx) > Math.abs(dy) + 10; + const isVerticalDrag = Math.abs(dy) > Math.abs(dx) + 10; + + // If the drag is primarily horizontal, ignore it + if (isHorizontalDrag) { + return; + } + + // If the drag is primarily vertical, update the counter + if (isVerticalDrag) { + // If the drag is downwards and the counter is greater than 0, decrease the counter + if (dy > 0 && counter.count > 0) { + updateCounter(counter.count - getInterval()); + } else if (dy < 0) { + // If the drag is upwards, increase the counter + updateCounter(counter.count + getInterval()); + } + // Update the screen with the new counter value + updateScreen(); + } + }); + + // Set a watch on the button to rotate the increment when pressed + setWatch(rotateIncrement, BTN1, { repeat: true }); +} else { + // If the hardware version is Bangle.js 1, set up the button watches + + // Set watch on button to increase the counter + counterButtonWatch(BTN1, true); + counterButtonWatch(BTN5, true); // screen tap + // Set watch on button to decrease the counter + counterButtonWatch(BTN3, false); + counterButtonWatch(BTN4, false); // screen tap + + // Set a watch on button to rotate the increment when pressed + setWatch( + () => { + rotateIncrement(); + }, + BTN2, + { repeat: true } + ); +} + +// clear the screen +g.clear(); + +// Set the background and foreground colors +g.setBgColor(g.theme.bg).setColor(g.theme.fg); + +// Load and draw the widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +// Read the counter from the file +readCounterFromFile(); +// Update the screen with the counter value +updateScreen(); diff --git a/apps/burn/metadata.json b/apps/burn/metadata.json new file mode 100644 index 000000000..c032058c8 --- /dev/null +++ b/apps/burn/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "burn", + "name": "Burn", + "version": "0.07", + "description": "Simple Calorie Counter -- saves to flash and resets at midnight. I often keep mine running while the digital clock widget is at the top", + "icon": "app-icon.png", + "tags": "tool", + "readme":"README.md", + "supports": ["BANGLEJS", "BANGLEJS2"], + "screenshots": [{"url":"app-screenshot.png"}], + "allow_emulator": true, + "storage": [ + {"name":"burn.app.js","url":"app.js"}, + {"name":"burn.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/chargent/ChangeLog b/apps/chargent/ChangeLog index fb3eaa780..349618315 100644 --- a/apps/chargent/ChangeLog +++ b/apps/chargent/ChangeLog @@ -4,3 +4,4 @@ 0.04: Added notification 0.05: Fixed boot 0.06: Allow tap to silence notification/buzzing +0.07: Fix notification-tap silencing and notification length diff --git a/apps/chargent/boot.js b/apps/chargent/boot.js index 198e36f18..42c384711 100644 --- a/apps/chargent/boot.js +++ b/apps/chargent/boot.js @@ -24,8 +24,8 @@ lim = sum / cnt; require('Storage').writeJSON('chargent.json', {limit: lim}); } - const onHide = () => { id = clearInterval(id) }; - require('notify').show({id: 'chargent', title: 'Fully charged', onHide }); + const onHide = () => { if(id) id = clearInterval(id) }; + require('notify').show({id: 'chargent', title: 'Charged', onHide }); // TODO ? customizable Bangle.buzz(500); setTimeout(() => Bangle.buzz(500), 1000); diff --git a/apps/chargent/metadata.json b/apps/chargent/metadata.json index bae35b361..75366ff59 100644 --- a/apps/chargent/metadata.json +++ b/apps/chargent/metadata.json @@ -1,6 +1,6 @@ { "id": "chargent", "name": "Charge Gently", - "version": "0.06", + "version": "0.07", "description": "When charging, reminds you to disconnect the watch to prolong battery life.", "icon": "icon.png", "type": "bootloader", diff --git a/apps/clkinfocal/ChangeLog b/apps/clkinfocal/ChangeLog index 50da8d080..ccb73b648 100644 --- a/apps/clkinfocal/ChangeLog +++ b/apps/clkinfocal/ChangeLog @@ -1,3 +1,5 @@ 0.01: New App! 0.02: added settings options to change date format -0.03: Remove un-needed font requirement, now outputs transparent image \ No newline at end of file +0.03: Remove un-needed font requirement, now outputs transparent image +0.04: Fix image after 0.03 regression +0.05: Remove duplicated day in calendar when format setting is 'dd MMM' \ No newline at end of file diff --git a/apps/clkinfocal/clkinfo.js b/apps/clkinfocal/clkinfo.js index da9607bb4..dc93ddd0e 100644 --- a/apps/clkinfocal/clkinfo.js +++ b/apps/clkinfocal/clkinfo.js @@ -5,7 +5,7 @@ var getDateString = function(dt) { switch(settings.fmt) { case "dd MMM": - return '' + dt.getDate() + ' ' + require("locale").month(dt,1).toUpperCase(); + return require("locale").month(dt,1).toUpperCase(); case "DDD dd": return require("locale").dow(dt,1).toUpperCase() + ' ' + dt.getDate(); default: // DDD @@ -20,7 +20,7 @@ get : () => { let d = new Date(); let g = Graphics.createArrayBuffer(24,24,1,{msb:true}); - g.transparent = 1; + g.transparent = 0; g.drawImage(atob("FhgBDADAMAMP/////////////////////8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAP///////"),1,0); g.setFont("6x15").setFontAlign(0,0).drawString(d.getDate(),11,17); return { diff --git a/apps/clkinfocal/metadata.json b/apps/clkinfocal/metadata.json index 2f1182883..71dc811e9 100644 --- a/apps/clkinfocal/metadata.json +++ b/apps/clkinfocal/metadata.json @@ -1,6 +1,6 @@ { "id": "clkinfocal", "name": "Calendar Clockinfo", - "version":"0.03", + "version":"0.05", "description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays the day of the month in the icon, and the weekday. There is also a settings menu to select the format of the text", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], diff --git a/apps/clkinfogps/clkinfo.js b/apps/clkinfogps/clkinfo.js index ed2fc721a..7db9bbdae 100644 --- a/apps/clkinfogps/clkinfo.js +++ b/apps/clkinfogps/clkinfo.js @@ -91,7 +91,7 @@ }; var info = { - name: "Gps", + name: "GPS", items: [ { name: "gridref", diff --git a/apps/clkinfogpsspeed/ChangeLog b/apps/clkinfogpsspeed/ChangeLog new file mode 100644 index 000000000..78ba28f3b --- /dev/null +++ b/apps/clkinfogpsspeed/ChangeLog @@ -0,0 +1 @@ +0.01: New Clock Info! diff --git a/apps/clkinfogpsspeed/clkinfo.js b/apps/clkinfogpsspeed/clkinfo.js new file mode 100644 index 000000000..a2c1f51c1 --- /dev/null +++ b/apps/clkinfogpsspeed/clkinfo.js @@ -0,0 +1,27 @@ +(function() { + var speed; + function gpsHandler(e) { + speed = e.speed; + ci.items[0].emit('redraw'); + } + var ci = { + name: "GPS", + items: [ + { name : "Speed", + get : function() { return { text : isFinite(speed) ? require("locale").speed(speed) : "--", + v : 0, min : isFinite(speed) ? speed : 0, max : 150, + img : atob("GBiBAAAAAAAAAAAAAAAAAAD/AAHDgAMYwAbDYAwAMAoA0BgDmBgfGB4ceBgYGBgAGBoAWAwAMAwAMAf/4AP/wAAAAAAAAAAAAAAAAA==") }}, + show : function() { + Bangle.setGPSPower(1, "clkinfogpsspeed"); + Bangle.on("GPS", gpsHandler); + }, + hide : function() { + Bangle.removeListener("GPS", gpsHandler); + Bangle.setGPSPower(0, "clkinfogpsspeed"); + } + // run : function() {} optional (called when tapped) + } + ] + }; + return ci; +}) // must not have a semi-colon! \ No newline at end of file diff --git a/apps/clkinfogpsspeed/icon.png b/apps/clkinfogpsspeed/icon.png new file mode 100644 index 000000000..10186f13f Binary files /dev/null and b/apps/clkinfogpsspeed/icon.png differ diff --git a/apps/clkinfogpsspeed/metadata.json b/apps/clkinfogpsspeed/metadata.json new file mode 100644 index 000000000..7fceeb7b8 --- /dev/null +++ b/apps/clkinfogpsspeed/metadata.json @@ -0,0 +1,13 @@ +{ "id": "clkinfogpsspeed", + "name": "GPS Speed Clockinfo", + "shortName":"GPS Speed", + "version":"0.01", + "description": "A Clockinfo that displays your current speed according to the GPS", + "icon": "icon.png", + "type": "clkinfo", + "tags": "clkinfo", + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"clkinfogpsspeed.clkinfo.js","url":"clkinfo.js"} + ] +} diff --git a/apps/clock_info/ChangeLog b/apps/clock_info/ChangeLog index 81c879311..8276321ac 100644 --- a/apps/clock_info/ChangeLog +++ b/apps/clock_info/ChangeLog @@ -10,3 +10,4 @@ 0.09: Save clkinfo settings on kill and remove 0.10: Fix focus bug when changing focus between two clock infos 0.11: Prepend swipe listener if possible +0.12: Add drawFilledImage to allow drawing icons with a separately coloured middle \ No newline at end of file diff --git a/apps/clock_info/lib.js b/apps/clock_info/lib.js index b44df9b18..a9ca7de31 100644 --- a/apps/clock_info/lib.js +++ b/apps/clock_info/lib.js @@ -371,6 +371,46 @@ exports.addInteractive = function(menu, options) { return options; }; +/* clockinfos usually return a 24x24 image. This draws that image but +recolors it such that it is transparent, with the middle of the image as background +and the image itself as foreground. options is passed to g.drawImage */ +exports.drawFilledImage = function(img,x,y,options) { + if (!img) return; + if (!g.floodFill/*2v18+*/) return g.drawImage(img,x,y,options); + let gfx = exports.imgGfx; + if (!gfx) { + gfx = exports.imgGfx = Graphics.createArrayBuffer(26, 26, 2, {msb:true}); + gfx.transparent = 3; + gfx.palette = new Uint16Array([g.theme.bg, g.theme.fg, g.toColor("#888"), g.toColor("#888")]); + } + /* img is (usually) a black and white transparent image. But we really would like the bits in + the middle of it to be white. So what we do is we draw a slightly bigger rectangle in white, + draw the image, and then flood-fill the rectangle back to the background color. floodFill + was only added in 2v18 so we have to check for it and fallback if not. */ + gfx.clear(1).setColor(1).drawImage(img, 1,1).floodFill(0,0,3); + var scale = (options && options.scale) || 1; + return g.drawImage(gfx, x-scale,y-scale,options); +}; + +/* clockinfos usually return a 24x24 image. This creates a 26x26 gfx of the image but +recolors it such that it is transparent, with the middle and border of the image as background +and the image itself as foreground. options is passed to g.drawImage */ +exports.drawBorderedImage = function(img,x,y,options) { + if (!img) return; + if (!g.floodFill/*2v18+*/) return g.drawImage(img,x,y,options); + let gfx = exports.imgGfxB; + if (!gfx) { + gfx = exports.imgGfxB = Graphics.createArrayBuffer(28, 28, 2, {msb:true}); + gfx.transparent = 3; + gfx.palette = new Uint16Array([g.theme.bg, g.theme.fg, g.theme.bg/*border*/, g.toColor("#888")]); + } + gfx.clear(1).setColor(2).drawImage(img, 1,1).drawImage(img, 3,1).drawImage(img, 1,3).drawImage(img, 3,3); // border + gfx.setColor(1).drawImage(img, 2,2); // main image + gfx.floodFill(27,27,3); // flood fill edge to transparent + var o = ((options && options.scale) || 1)*2; + return g.drawImage(gfx, x-o,y-o,options); +}; + // Code for testing (plots all elements from first list) /* g.clear(); diff --git a/apps/clock_info/metadata.json b/apps/clock_info/metadata.json index 921a6fc4e..3d47c5062 100644 --- a/apps/clock_info/metadata.json +++ b/apps/clock_info/metadata.json @@ -1,11 +1,11 @@ { "id": "clock_info", "name": "Clock Info Module", "shortName": "Clock Info", - "version":"0.11", + "version":"0.12", "description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)", "icon": "app.png", "type": "module", - "tags": "clkinfo", + "tags": "clkinfo,clockinfo", "supports" : ["BANGLEJS2"], "provides_modules" : ["clock_info"], "readme": "README.md", diff --git a/apps/clockbg/ChangeLog b/apps/clockbg/ChangeLog index f7c47d4da..026dc1aa0 100644 --- a/apps/clockbg/ChangeLog +++ b/apps/clockbg/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! -0.02: Moved settings into 'Settings->Apps' \ No newline at end of file +0.02: Moved settings into 'Settings->Apps' +0.03: Add 'Squares' option for random squares background \ No newline at end of file diff --git a/apps/clockbg/README.md b/apps/clockbg/README.md index 8f2fe3e9d..14bbeb7a7 100644 --- a/apps/clockbg/README.md +++ b/apps/clockbg/README.md @@ -9,8 +9,12 @@ By default the app provides just a red/green/blue background but it can easily b You can either: -* Go to [the Clock Backgrounds app](https://banglejs.com/apps/?id=clockbg) in the App Loader and upload backgrounds -* Go to the `Backgrounds` app on the Bangle itself, and choose between solid color, random colors, or any uploaded images. +* Go to [the Clock Backgrounds app](https://banglejs.com/apps/?id=clockbg) in the App Loader and use pre-made image backgrounds (or upload your own) +* Go to the `Backgrounds` app on the Bangle itself, and choose between: + * `Solid Color` - one color that never changes + * `Random Color` - a new color every time the clock starts + * `Image` - choose from a previously uploaded image + * `Squares` - a randomly generated pattern of squares in the selected color palette ## Usage in code @@ -33,8 +37,10 @@ ensure that the clock background library is automatically loaded. ## Features to be added -This library/app is still pretty basic right now, but a few features could be added that would really improve functionality: +A few features could be added that would really improve functionality: -* Support for >1 image, and choosing randomly between them +* When 'fast loading', 'random' backgrounds don't update at the moment +* Support for >1 image to be uploaded (requires some image management in `interface.html`), and choose randomly between them * Support for gradients (random colors) +* More types of auto-generated pattern (as long as they can be generated quickly or in the background) * Storing 'clear' areas of uploaded images so clocks can easily position themselves \ No newline at end of file diff --git a/apps/clockbg/img/ai_flow.jpeg b/apps/clockbg/img/ai_flow.jpeg new file mode 100644 index 000000000..f30f89843 Binary files /dev/null and b/apps/clockbg/img/ai_flow.jpeg differ diff --git a/apps/clockbg/img/ai_horse.jpeg b/apps/clockbg/img/ai_horse.jpeg new file mode 100644 index 000000000..3b7da61f1 Binary files /dev/null and b/apps/clockbg/img/ai_horse.jpeg differ diff --git a/apps/clockbg/img/icons8-australia-480.png b/apps/clockbg/img/icons8-australia-480.png index edf34cdee..3bb1ac09b 100644 Binary files a/apps/clockbg/img/icons8-australia-480.png and b/apps/clockbg/img/icons8-australia-480.png differ diff --git a/apps/clockbg/img/icons8-austria-480.png b/apps/clockbg/img/icons8-austria-480.png index 8450a8840..5431bbd7a 100644 Binary files a/apps/clockbg/img/icons8-austria-480.png and b/apps/clockbg/img/icons8-austria-480.png differ diff --git a/apps/clockbg/img/icons8-belgium-480.png b/apps/clockbg/img/icons8-belgium-480.png index 395fef8d1..f0c6485f9 100644 Binary files a/apps/clockbg/img/icons8-belgium-480.png and b/apps/clockbg/img/icons8-belgium-480.png differ diff --git a/apps/clockbg/img/icons8-brazil-480.png b/apps/clockbg/img/icons8-brazil-480.png index 570ee39f9..505af26ca 100644 Binary files a/apps/clockbg/img/icons8-brazil-480.png and b/apps/clockbg/img/icons8-brazil-480.png differ diff --git a/apps/clockbg/img/icons8-canada-480.png b/apps/clockbg/img/icons8-canada-480.png index 3d3080768..62181468f 100644 Binary files a/apps/clockbg/img/icons8-canada-480.png and b/apps/clockbg/img/icons8-canada-480.png differ diff --git a/apps/clockbg/img/icons8-china-480.png b/apps/clockbg/img/icons8-china-480.png index c3c3faba7..ba56ccc1e 100644 Binary files a/apps/clockbg/img/icons8-china-480.png and b/apps/clockbg/img/icons8-china-480.png differ diff --git a/apps/clockbg/img/icons8-denmark-480.png b/apps/clockbg/img/icons8-denmark-480.png index 2add9dbce..716e15f98 100644 Binary files a/apps/clockbg/img/icons8-denmark-480.png and b/apps/clockbg/img/icons8-denmark-480.png differ diff --git a/apps/clockbg/img/icons8-england-480.png b/apps/clockbg/img/icons8-england-480.png index 5c9a836a6..2343521f3 100644 Binary files a/apps/clockbg/img/icons8-england-480.png and b/apps/clockbg/img/icons8-england-480.png differ diff --git a/apps/clockbg/img/icons8-flag-of-europe-480.png b/apps/clockbg/img/icons8-flag-of-europe-480.png index 654da87ba..616d5fe7a 100644 Binary files a/apps/clockbg/img/icons8-flag-of-europe-480.png and b/apps/clockbg/img/icons8-flag-of-europe-480.png differ diff --git a/apps/clockbg/img/icons8-france-480.png b/apps/clockbg/img/icons8-france-480.png index e3cb56348..09a24c8b1 100644 Binary files a/apps/clockbg/img/icons8-france-480.png and b/apps/clockbg/img/icons8-france-480.png differ diff --git a/apps/clockbg/img/icons8-germany-480.png b/apps/clockbg/img/icons8-germany-480.png index 0f86aa568..dd2b317bb 100644 Binary files a/apps/clockbg/img/icons8-germany-480.png and b/apps/clockbg/img/icons8-germany-480.png differ diff --git a/apps/clockbg/img/icons8-great-britain-480.png b/apps/clockbg/img/icons8-great-britain-480.png index 0acdcc191..f89add414 100644 Binary files a/apps/clockbg/img/icons8-great-britain-480.png and b/apps/clockbg/img/icons8-great-britain-480.png differ diff --git a/apps/clockbg/img/icons8-greece-480.png b/apps/clockbg/img/icons8-greece-480.png index 319cf93cb..eb823b4bc 100644 Binary files a/apps/clockbg/img/icons8-greece-480.png and b/apps/clockbg/img/icons8-greece-480.png differ diff --git a/apps/clockbg/img/icons8-hungary-480.png b/apps/clockbg/img/icons8-hungary-480.png index ab838afea..4097e88d3 100644 Binary files a/apps/clockbg/img/icons8-hungary-480.png and b/apps/clockbg/img/icons8-hungary-480.png differ diff --git a/apps/clockbg/img/icons8-italy-480.png b/apps/clockbg/img/icons8-italy-480.png index f917cd85a..82b1b710e 100644 Binary files a/apps/clockbg/img/icons8-italy-480.png and b/apps/clockbg/img/icons8-italy-480.png differ diff --git a/apps/clockbg/img/icons8-lgbt-flag-480.png b/apps/clockbg/img/icons8-lgbt-flag-480.png index 10f869a0a..2e8bbebb1 100644 Binary files a/apps/clockbg/img/icons8-lgbt-flag-480.png and b/apps/clockbg/img/icons8-lgbt-flag-480.png differ diff --git a/apps/clockbg/img/icons8-netherlands-480.png b/apps/clockbg/img/icons8-netherlands-480.png index b271c5448..4ea397e27 100644 Binary files a/apps/clockbg/img/icons8-netherlands-480.png and b/apps/clockbg/img/icons8-netherlands-480.png differ diff --git a/apps/clockbg/img/icons8-new-zealand-480.png b/apps/clockbg/img/icons8-new-zealand-480.png index fc1ca7ec1..e21fdc574 100644 Binary files a/apps/clockbg/img/icons8-new-zealand-480.png and b/apps/clockbg/img/icons8-new-zealand-480.png differ diff --git a/apps/clockbg/img/icons8-norway-480.png b/apps/clockbg/img/icons8-norway-480.png index 270f5af8b..a57c0f7fb 100644 Binary files a/apps/clockbg/img/icons8-norway-480.png and b/apps/clockbg/img/icons8-norway-480.png differ diff --git a/apps/clockbg/img/icons8-scotland-480.png b/apps/clockbg/img/icons8-scotland-480.png index 991652211..20f08cfbb 100644 Binary files a/apps/clockbg/img/icons8-scotland-480.png and b/apps/clockbg/img/icons8-scotland-480.png differ diff --git a/apps/clockbg/img/icons8-spain-480.png b/apps/clockbg/img/icons8-spain-480.png index 19c8dac04..17fed3360 100644 Binary files a/apps/clockbg/img/icons8-spain-480.png and b/apps/clockbg/img/icons8-spain-480.png differ diff --git a/apps/clockbg/img/icons8-sweden-480.png b/apps/clockbg/img/icons8-sweden-480.png index 60f412840..99299e93a 100644 Binary files a/apps/clockbg/img/icons8-sweden-480.png and b/apps/clockbg/img/icons8-sweden-480.png differ diff --git a/apps/clockbg/img/icons8-switzerland-480.png b/apps/clockbg/img/icons8-switzerland-480.png index 801ba4776..cde8c6ff0 100644 Binary files a/apps/clockbg/img/icons8-switzerland-480.png and b/apps/clockbg/img/icons8-switzerland-480.png differ diff --git a/apps/clockbg/img/icons8-ukraine-480.png b/apps/clockbg/img/icons8-ukraine-480.png index 3762c1112..718695b38 100644 Binary files a/apps/clockbg/img/icons8-ukraine-480.png and b/apps/clockbg/img/icons8-ukraine-480.png differ diff --git a/apps/clockbg/img/icons8-usa-480.png b/apps/clockbg/img/icons8-usa-480.png index d6288404e..a7867f070 100644 Binary files a/apps/clockbg/img/icons8-usa-480.png and b/apps/clockbg/img/icons8-usa-480.png differ diff --git a/apps/clockbg/img/icons8-wales-480.png b/apps/clockbg/img/icons8-wales-480.png index 6c2941342..58233db34 100644 Binary files a/apps/clockbg/img/icons8-wales-480.png and b/apps/clockbg/img/icons8-wales-480.png differ diff --git a/apps/clockbg/interface.html b/apps/clockbg/interface.html index 7fba8ea7b..c92600b1b 100644 --- a/apps/clockbg/interface.html +++ b/apps/clockbg/interface.html @@ -2,27 +2,29 @@ -

Upload an image:

-
+

Upload an image:

+

If you'd like to contribute images you can add them on GitHub!

Preview:
- +