From b711b50f888ecfb96f5a5cf4e2340507a6087c87 Mon Sep 17 00:00:00 2001 From: Marco Heiming Date: Mon, 10 Jan 2022 16:10:42 +0100 Subject: [PATCH 01/24] Initial version of BanglExercise --- apps.json | 19 ++- apps/banglexercise/ChangeLog | 1 + apps/banglexercise/README.md | 35 ++++ apps/banglexercise/app-icon.js | 1 + apps/banglexercise/app.js | 259 ++++++++++++++++++++++++++++++ apps/banglexercise/app.png | Bin 0 -> 690 bytes apps/banglexercise/screenshot.png | Bin 0 -> 2043 bytes 7 files changed, 314 insertions(+), 1 deletion(-) create mode 100644 apps/banglexercise/ChangeLog create mode 100644 apps/banglexercise/README.md create mode 100644 apps/banglexercise/app-icon.js create mode 100644 apps/banglexercise/app.js create mode 100644 apps/banglexercise/app.png create mode 100644 apps/banglexercise/screenshot.png diff --git a/apps.json b/apps.json index 389603249..0adb4abc5 100644 --- a/apps.json +++ b/apps.json @@ -5149,7 +5149,7 @@ {"name":"mmind.app.js","url":"mmind.app.js"}, {"name":"mmind.img","url":"mmind.icon.js","evaluate":true} ] - }, + }, { "id": "presentor", "name": "Presentor", @@ -5503,5 +5503,22 @@ {"name":"limelight.settings.js","url":"limelight.settings.js"}, {"name":"limelight.img","url":"limelight.icon.js","evaluate":true} ] + }, + { "id": "banglexercise", + "name": "BanglExercise", + "shortName":"BanglExercise", + "version":"0.01", + "description": "Can automatically track exercises while wearing the Bangle.js watch.", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "app", + "tags": "sport", + "supports" : ["BANGLEJS2"], + "allow_emulator":true, + "readme": "README.md", + "storage": [ + {"name":"banglexercise.app.js","url":"app.js"}, + {"name":"banglexercise.img","url":"app-icon.js","evaluate":true} + ] } ] diff --git a/apps/banglexercise/ChangeLog b/apps/banglexercise/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/banglexercise/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/banglexercise/README.md b/apps/banglexercise/README.md new file mode 100644 index 000000000..e7ac9573d --- /dev/null +++ b/apps/banglexercise/README.md @@ -0,0 +1,35 @@ +# Banglexercise + +Can automatically track exercises while wearing the Bangle.js watch. + +Currently only push ups are supported. + + +## Usage + +Select the exercise type you want to practive and go for it! +Press stop to end your exercise. + + +## Screenshots +![](screenshot.png) + +## TODO +* Add other exercise types: + * Rope jumps + * Curls + * Sit ups + * ... +* Save exercises to file system +* Add settings (vibration, beep, ...) +* Find a nicer icon + + +## Contribute +Feel free to send in improvements and remarks. + +## Creator +Marco ([myxor](https://github.com/myxor)) + +## Icons +Icons taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0 diff --git a/apps/banglexercise/app-icon.js b/apps/banglexercise/app-icon.js new file mode 100644 index 000000000..e1923bf54 --- /dev/null +++ b/apps/banglexercise/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwIbYh/8AYM/+EP/wFBv4FB/4FB/4FHAwIEBAv4FPAgIGCAosHAofggYFD4EABgXgOgIFLDAQWBAo0BAoOAVIV/UYQABj/4AocDCwQFTg46CEY4vFAopBBApIAVA==")) diff --git a/apps/banglexercise/app.js b/apps/banglexercise/app.js new file mode 100644 index 000000000..feb3a1127 --- /dev/null +++ b/apps/banglexercise/app.js @@ -0,0 +1,259 @@ +const Layout = require("Layout"); + +let historyY = []; +let historyZ = []; +const avgSize = 10; + +const thresholdY = 2500; +const thresholdPushUpTime = 1400; // mininmal time between two push ups +let tStart; + +let avgY; +let avgZ; +let historyAvgY = []; +let historyAvgZ = []; +let historySlopeY = []; +let historySlopeZ = []; + +let lastZeroPassType; +let lastZeroPassTime = 0; +let lastPushUpCmpltTime = 0; + +let exerciseType = ""; +let pushUpCounter = 0; + +let layout; + +let recordActive = false; + + +function showMenu() { + let menu; + if (pushUpCounter == 0) { + menu = { + "": { + title: "Banglexercise" + }, + "Start push ups": function() { + exerciseType = "push ups"; + E.showMenu(); + startRecording(); + } + }; + } else { + menu = { + "": { + title: "Banglexercise" + }, + "Last:": { + value: pushUpCounter + " push ups" + }, + "Start push ups": function() { + exerciseType = "push ups"; + E.showMenu(); + startRecording(); + } + }; + } + E.showMenu(menu); +} + +function accelHandler(accel) { + const t = Math.round(new Date().getTime()); // time in ms + const y = accel.y * 8192; + const z = accel.z * 8192; + //console.log(t, y, z); + + while (historyY.length > avgSize) + historyY.shift(); + historyY.push(y); + + if (historyY.length > avgSize / 2) + avgY = E.sum(historyY) / historyY.length; + + while (historyZ.length > avgSize) + historyZ.shift(); + historyZ.push(z); + + if (historyZ.length > avgSize / 2) + avgZ = E.sum(historyZ) / historyZ.length; + + if (avgY) { + //console.log(avgY, avgZ); + historyAvgY.push([t, avgY]); + historyAvgZ.push([t, avgZ]); + } + + let mY; + let mZ; + // slope for Y + let l = historyAvgY.length; + if (l > 1) { + const p1 = historyAvgY[l - 2]; + const p2 = historyAvgY[l - 1]; + mY = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000); + if (Math.abs(mY) >= thresholdY) { + historyAvgY.shift(); + historySlopeY.push([t, mY]); + //console.log(t, Math.abs(mY)); + + const lMY = historySlopeY.length; + if (lMY > 1) { + const pMY1 = historySlopeY[lMY - 2][1]; + const pMY2 = historySlopeY[lMY - 1][1]; + isValidPushUp(pMY1, pMY2, t); + } + } + } + + // slope for Z + l = historyAvgZ.length; + if (l > 1) { + const p1 = historyAvgZ[l - 2]; + const p2 = historyAvgZ[l - 1]; + mZ = (p2[1] - p1[1]) / (p2[0] - p1[0]); + historyAvgZ.shift(); + historySlopeZ.push([p2[0] - p1[0], mZ]); + } +} + +function isValidPushUp(p1, p2, t) { + if (p1 > 0 && p2 < 0) { + + if (lastZeroPassType == "-+") { + console.log(t, "Push up half complete..."); + + layout.progress.label = "..."; + layout.render(layout.progress); + } + + lastZeroPassType = "+-"; + lastZeroPassTime = t; + } + if (p2 > 0 && p1 < 0) { + + if (lastZeroPassType == "+-") { + // potential complete push up. Let's check the time difference... + const tDiffLastPushUp = t - lastPushUpCmpltTime; + const tDiffStart = t - tStart; + console.log(t, "Push up maybe complete?", Math.round(tDiffLastPushUp), Math.round(tDiffStart)); + + if ((lastPushUpCmpltTime <= 0 && tDiffStart >= thresholdPushUpTime) || tDiffLastPushUp >= thresholdPushUpTime) { + console.log(t, "Push up complete!!!"); + + lastPushUpCmpltTime = t; + pushUpCounter++; + + layout.count.label = pushUpCounter; + layout.render(layout.count); + layout.progress.label = ""; + layout.render(layout.progress); + + Bangle.buzz(100, 0.3); // TODO make configurable + } else { + console.log(t, "Push up to quick for threshold!"); + } + } + + lastZeroPassType = "-+"; + lastZeroPassTime = t; + } +} + +/* + +function calcPushUps() { + const l = historySlopeY.length; + for (let i = 1; i < l; i++) { + const p1 = historySlopeY[i - 1][1]; + const p2 = historySlopeY[i][1]; + const t = historySlopeY[i][0]; + isValidPushUp(p1, p2, t); + } +} +*/ + +function reset() { + historyY = []; + historyZ = []; + historyAvgY = []; + historyAvgZ = []; + historySlopeY = []; + historySlopeZ = []; + + lastZeroPassType = ""; + lastZeroPassTime = 0; + lastPushUpCmpltTime = 0; + pushUpCounter = 0; +} + + +function startRecording() { + if (recordActive) return; + g.clear(1); + reset(); + layout = new Layout({ + type: "v", + c: [{ + type: "txt", + id: "type", + font: "6x8:2", + label: exerciseType, + pad: 5 + }, + { + type: "txt", + id: "count", + font: "6x8:9", + label: pushUpCounter, + pad: 5, + bgCol: g.theme.bg + }, + { + type: "txt", + id: "progress", + font: "6x8:2", + label: "", + pad: 5 + }, + { + type: "txt", + id: "recording", + font: "6x8:2", + label: "RECORDING", + bgCol: "#f00", + pad: 5, + fillx: 1 + }, + ] + }, { + btns: [ + { + label: "STOP", + cb: () => { + stopRecording(); + } + } + ] + }); + layout.render(); + + Bangle.setPollInterval(80); // 12.5 Hz + Bangle.on('accel', accelHandler); + Bangle.buzz(200, 1); + tStart = new Date().getTime(); + recordActive = true; +} + +function stopRecording() { + if (!recordActive) return; + g.clear(1); + Bangle.removeListener('accel', accelHandler); + showMenu(); + console.log("Found " + pushUpCounter + " push ups!"); + recordActive = false; +} + +g.clear(1); +Bangle.drawWidgets(); +showMenu(); diff --git a/apps/banglexercise/app.png b/apps/banglexercise/app.png new file mode 100644 index 0000000000000000000000000000000000000000..ee733206310029c15e866e4fc6bef39305d70668 GIT binary patch literal 690 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTCmUKs7M+SzC{oH>NS%G|oWRD45dJguM!v-tY$DUh!@P+6=(yLU`z6LcLCBs@Y8vBJ&@uo z@Q5r1(g|SvA=~LZ0|Vn0PZ!6KjC*fqZuC0rAk)5ISX|cS=uz>4qEPnOCH@cIG_P3P zFGDWi zNGnrbNL|R7G5egyoLkxdBkfxbtgvmLFk!~jikVyTJQzLaunJ`CzH(+>eiw_(tM5ID zCxaKT>-gQeKjZ#PZ`Muf7F<@rVfw%ShRjWzrNDVa4Hc#BdJrKWAyK=c`uohKOXBRS z7i6&M&E|RIp%;De^8uL)tYJyZ&wgNABYF7x`R1LH8)UCF6mu=pVXB?2D!xxh=y%`w zvwFGbP5q~azSwr`;=Yw1ZzZe9Wl8+~?RF@-QMWVkzz65fv&UH090_!2JnVKjno+s% zjis=&wZJj^z|x7+T(&#?Q18sW|I6{rTaII}*{P}%1L#X5Imj9~9Jam|MPl%6IS=VU&;E=?<Hxi+V}Sty2#I1U-4_X;bU=*PM1S-^ErIH(Bc9_KZH+1;!FW?Xq@{?d#wat2 zqSPaenYJE9vqHzCE!82k>QzeX6>8#H&He5_^T(`p*S-JUv(`C#?X~wgd+)PNP;eAqEM1m z_SP0|asJCCgE3v@PMo1! z*f|v3#LSzFzS{I!!rV21lYAG;c$8rSY` zrtv;c`F-*otRXP}(DjxKjPZJTgeC>x=>7yoFGVWJH|o)xX+XB^NKHex&wPl`a~=~X za(P8aF({q6xR8)IKj56l)xcxv656}p+2QGn?xOha*4FfUv5E=1o8>8@ke5^X-Alra z{?c-FSdtCn#41uNW-umNx0VFrlWLa)#(@Ij$rUqQ#GSv%Zi@XPBg}gPCyKWp&p8}M z6HV+)2y3H)YaQ^Mom$c5vW71elfNdWFPDfG{GjOe_VwTz(o~)T+9W+c*{D*%tK9*e zoJ~|W+Yzo%+X)vnc0{$qEqCJk=RJ4*O9-72(B~Uzi@Es|RQEyjmgu4b^j7ydTi6Z= zTKjLeV4AP--+63`7du)}g~nr62CXHl@SB==)Cgk4oJWBwo1uoLt@$Jzoe zC8XG-fYql4>_h}+N$)&!8#8LB2x!D1B(J^4SIpD$WT!%~SBvItV#w}Bu^X$Pk*we;?6cI0@-D(Q=rGO_|R9q%_clHFEW>WP@3PJ2lX6b9WH_gJ#2oO{P>u;wA8BEkN?TPq z%^@Y+O-FLEsH-w~$0{or?;P%C_!V_W>sRIRoIl=dlz?e=`(Rq?PWbD0J3{ciJg-L; zxvZ9qEgR}wlM84&H3=%LDFm|q3jH11uV|iGtX+s^Gmi0ynq5i*$3#_ zg+J!w${3g@L`bGr#cC#`$MNK5&VPC>2@A?DFMX?x#pRoMyH9t@B^7G+?s{Oh_8X0G zdxIWdXd?sP7~p>UV*XhE!$E*z;+B&8<4Q^M4d>$>b`fhPoA)+AQJd17ewQEr$U-(Z zi?LMG(ytl$U}&R@dAxoWMG>q1h-<}t6VKQVm+sCMa7sP7#%G{990ok zZaza$U~;u;dV?Im+ikXvp0`kdvfQSWgVmHyrEgp=ZF(@0w^`k28dzQ*VFU;ceDdf(o`Cg>Bmg3o{v-Hn zd2^(E?-x3$-1VX;Tbvj|TjlUy#vZ*@U9b6V39?(t1J}IJ@v@K-((+>Q%!fY+P7m~> z)!xYJRn$*Dw4B`OQ+_C%Wykju9`})?N8H^aB$=bl;ZJN}EjlY>dhI#z)la1C1rXhwz1fgbiQh50C;I)_p2 z20-=scl5QzK*vM-t?q$nFKrt?G64N@O6e*^3d{M literal 0 HcmV?d00001 From bfe2725483e55f657f0592e519e60c077abe1897 Mon Sep 17 00:00:00 2001 From: Marco Heiming Date: Mon, 10 Jan 2022 19:02:39 +0100 Subject: [PATCH 02/24] Show heart rate during training Alot of code cleanup and improvements --- apps/banglexercise/README.md | 2 +- apps/banglexercise/app.js | 269 +++++++++++++++++++---------------- 2 files changed, 151 insertions(+), 120 deletions(-) diff --git a/apps/banglexercise/README.md b/apps/banglexercise/README.md index e7ac9573d..efc87424d 100644 --- a/apps/banglexercise/README.md +++ b/apps/banglexercise/README.md @@ -1,4 +1,4 @@ -# Banglexercise +# BanglExercise Can automatically track exercises while wearing the Bangle.js watch. diff --git a/apps/banglexercise/app.js b/apps/banglexercise/app.js index feb3a1127..e18faf554 100644 --- a/apps/banglexercise/app.js +++ b/apps/banglexercise/app.js @@ -1,15 +1,9 @@ const Layout = require("Layout"); +const heatshrink = require('heatshrink'); +let tStart; let historyY = []; let historyZ = []; -const avgSize = 10; - -const thresholdY = 2500; -const thresholdPushUpTime = 1400; // mininmal time between two push ups -let tStart; - -let avgY; -let avgZ; let historyAvgY = []; let historyAvgZ = []; let historySlopeY = []; @@ -17,103 +11,122 @@ let historySlopeZ = []; let lastZeroPassType; let lastZeroPassTime = 0; -let lastPushUpCmpltTime = 0; -let exerciseType = ""; -let pushUpCounter = 0; +let lastExerciseCmpltTime = 0; + +let exerciseType = { + "name": "" +}; +const exerciseTypes = [{ + "id": "pushup", + "name": "Push ups", + "useYaxe": true, + "useZaxe": false + } // add other exercises here +]; +let exerciseCounter = 0; let layout; - let recordActive = false; +/** + * Thresholds + */ +const avgSize = 6; +const pushUpThresholdY = 2500; +const pushUpThresholdTime = 1400; // mininmal time between two push ups -function showMenu() { +let hrtValue; + +function showMainMenu() { let menu; - if (pushUpCounter == 0) { - menu = { - "": { - title: "Banglexercise" - }, - "Start push ups": function() { - exerciseType = "push ups"; - E.showMenu(); - startRecording(); - } + menu = { + "": { + title: "BanglExercise" + } + }; + + exerciseTypes.forEach(function(et) { + menu["Do " + et.name] = function() { + exerciseType = et; + E.showMenu(); + startRecording(); }; - } else { - menu = { - "": { - title: "Banglexercise" - }, - "Last:": { - value: pushUpCounter + " push ups" - }, - "Start push ups": function() { - exerciseType = "push ups"; - E.showMenu(); - startRecording(); - } + }); + + if (exerciseCounter > 0) { + menu["----"] = {}; + menu["Last:"] = { + value: exerciseCounter + " " + exerciseType.name }; } + E.showMenu(menu); } function accelHandler(accel) { + if (!exerciseType) return; const t = Math.round(new Date().getTime()); // time in ms - const y = accel.y * 8192; - const z = accel.z * 8192; + const y = exerciseType.useYaxe ? accel.y * 8192 : 0; + const z = exerciseType.useZaxe ? accel.z * 8192 : 0; //console.log(t, y, z); - while (historyY.length > avgSize) - historyY.shift(); - historyY.push(y); + if (exerciseType.useYaxe) { + while (historyY.length > avgSize) + historyY.shift(); - if (historyY.length > avgSize / 2) - avgY = E.sum(historyY) / historyY.length; + historyY.push(y); - while (historyZ.length > avgSize) - historyZ.shift(); - historyZ.push(z); - - if (historyZ.length > avgSize / 2) - avgZ = E.sum(historyZ) / historyZ.length; - - if (avgY) { - //console.log(avgY, avgZ); - historyAvgY.push([t, avgY]); - historyAvgZ.push([t, avgZ]); + if (historyY.length > avgSize / 2) { + const avgY = E.sum(historyY) / historyY.length; + historyAvgY.push([t, avgY]); + } } - let mY; - let mZ; - // slope for Y - let l = historyAvgY.length; - if (l > 1) { - const p1 = historyAvgY[l - 2]; - const p2 = historyAvgY[l - 1]; - mY = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000); - if (Math.abs(mY) >= thresholdY) { - historyAvgY.shift(); - historySlopeY.push([t, mY]); - //console.log(t, Math.abs(mY)); + if (exerciseType.useYaxe) { + while (historyZ.length > avgSize) + historyZ.shift(); - const lMY = historySlopeY.length; - if (lMY > 1) { - const pMY1 = historySlopeY[lMY - 2][1]; - const pMY2 = historySlopeY[lMY - 1][1]; - isValidPushUp(pMY1, pMY2, t); + historyZ.push(z); + + if (historyZ.length > avgSize / 2) { + const avgZ = E.sum(historyZ) / historyZ.length; + historyAvgZ.push([t, avgZ]); + } + } + + // slope for Y + if (exerciseType.useYaxe) { + let l = historyAvgY.length; + if (l > 1) { + const p1 = historyAvgY[l - 2]; + const p2 = historyAvgY[l - 1]; + const mY = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000); + if (Math.abs(mY) >= pushUpThresholdY) { + historyAvgY.shift(); + historySlopeY.push([t, mY]); + //console.log(t, Math.abs(mY)); + + const lMY = historySlopeY.length; + if (lMY > 1) { + const pMY1 = historySlopeY[lMY - 2][1]; + const pMY2 = historySlopeY[lMY - 1][1]; + isValidPushUp(pMY1, pMY2, t); + } } } } // slope for Z - l = historyAvgZ.length; - if (l > 1) { - const p1 = historyAvgZ[l - 2]; - const p2 = historyAvgZ[l - 1]; - mZ = (p2[1] - p1[1]) / (p2[0] - p1[0]); - historyAvgZ.shift(); - historySlopeZ.push([p2[0] - p1[0], mZ]); + if (exerciseType.useZaxe) { + l = historyAvgZ.length; + if (l > 1) { + const p1 = historyAvgZ[l - 2]; + const p2 = historyAvgZ[l - 1]; + const mZ = (p2[1] - p1[1]) / (p2[0] - p1[0]); + historyAvgZ.shift(); + historySlopeZ.push([p2[0] - p1[0], mZ]); + } } } @@ -123,8 +136,8 @@ function isValidPushUp(p1, p2, t) { if (lastZeroPassType == "-+") { console.log(t, "Push up half complete..."); - layout.progress.label = "..."; - layout.render(layout.progress); + layout.progress.label = "*"; + layout.render(); } lastZeroPassType = "+-"; @@ -134,20 +147,19 @@ function isValidPushUp(p1, p2, t) { if (lastZeroPassType == "+-") { // potential complete push up. Let's check the time difference... - const tDiffLastPushUp = t - lastPushUpCmpltTime; + const tDiffLastPushUp = t - lastExerciseCmpltTime; const tDiffStart = t - tStart; console.log(t, "Push up maybe complete?", Math.round(tDiffLastPushUp), Math.round(tDiffStart)); - if ((lastPushUpCmpltTime <= 0 && tDiffStart >= thresholdPushUpTime) || tDiffLastPushUp >= thresholdPushUpTime) { + if ((lastExerciseCmpltTime <= 0 && tDiffStart >= pushUpThresholdTime) || tDiffLastPushUp >= pushUpThresholdTime) { console.log(t, "Push up complete!!!"); - lastPushUpCmpltTime = t; - pushUpCounter++; + lastExerciseCmpltTime = t; + exerciseCounter++; - layout.count.label = pushUpCounter; - layout.render(layout.count); + layout.count.label = exerciseCounter; layout.progress.label = ""; - layout.render(layout.progress); + layout.render(); Bangle.buzz(100, 0.3); // TODO make configurable } else { @@ -160,19 +172,6 @@ function isValidPushUp(p1, p2, t) { } } -/* - -function calcPushUps() { - const l = historySlopeY.length; - for (let i = 1; i < l; i++) { - const p1 = historySlopeY[i - 1][1]; - const p2 = historySlopeY[i][1]; - const t = historySlopeY[i][0]; - isValidPushUp(p1, p2, t); - } -} -*/ - function reset() { historyY = []; historyZ = []; @@ -183,8 +182,9 @@ function reset() { lastZeroPassType = ""; lastZeroPassTime = 0; - lastPushUpCmpltTime = 0; - pushUpCounter = 0; + lastExerciseCmpltTime = 0; + exerciseCounter = 0; + tStart = 0; } @@ -192,29 +192,54 @@ function startRecording() { if (recordActive) return; g.clear(1); reset(); + Bangle.setHRMPower(1, "banglexercise"); + if (!hrtValue) hrtValue = "..."; + layout = new Layout({ type: "v", c: [{ type: "txt", id: "type", font: "6x8:2", - label: exerciseType, + label: exerciseType.name, pad: 5 }, { - type: "txt", - id: "count", - font: "6x8:9", - label: pushUpCounter, - pad: 5, - bgCol: g.theme.bg + type: "h", + c: [{ + type: "txt", + id: "count", + font: "6x8:10", + label: exerciseCounter, + pad: 5, + bgCol: g.theme.bg + }, + { + type: "txt", + id: "progress", + font: "6x8:2", + label: "", + pad: 5 + }, + ] }, { - type: "txt", - id: "progress", - font: "6x8:2", - label: "", - pad: 5 + type: "h", + c: [{ + type: "img", + pad: 4, + src: function() { + return heatshrink.decompress(atob("h0OwYOLkmQhMkgACByVJgESpIFBpEEBAIFBCgIFCCgsABwcAgQOCAAMSpAwDyBNM")); + } + }, + { + type: "txt", + id: "hrtRate", + font: "6x8:2", + label: hrtValue, + pad: 5 + }, + ] }, { type: "txt", @@ -234,7 +259,8 @@ function startRecording() { stopRecording(); } } - ] + ], + lazy: true }); layout.render(); @@ -248,12 +274,17 @@ function startRecording() { function stopRecording() { if (!recordActive) return; g.clear(1); + + Bangle.setHRMPower(0, "banglexercise"); + Bangle.removeListener('accel', accelHandler); - showMenu(); - console.log("Found " + pushUpCounter + " push ups!"); + showMainMenu(); recordActive = false; } +Bangle.on('HRM', function(hrm) { + hrtValue = hrm.bpm; +}); + g.clear(1); -Bangle.drawWidgets(); -showMenu(); +showMainMenu(); From c9fa1b8f65db32d3dae0063630228332f0679c3f Mon Sep 17 00:00:00 2001 From: Marco Heiming Date: Tue, 11 Jan 2022 11:44:07 +0100 Subject: [PATCH 03/24] Update screenshot --- apps/banglexercise/screenshot.png | Bin 2043 -> 2181 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/banglexercise/screenshot.png b/apps/banglexercise/screenshot.png index 9a325d87378dd297bab31da0521462e06d5c1ab9..417be685b08802657be55b02498c4c292128b70d 100644 GIT binary patch literal 2181 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 From 83aa32ee840a6aec7fcaf0995de8f3a1278e4a89 Mon Sep 17 00:00:00 2001 From: Marco Heiming Date: Tue, 11 Jan 2022 12:38:19 +0100 Subject: [PATCH 04/24] Moved exercise threshold values to the exercise type configuration & initial support for curls --- apps/banglexercise/README.md | 9 ++- apps/banglexercise/app.js | 148 +++++++++++++++++++++-------------- 2 files changed, 98 insertions(+), 59 deletions(-) diff --git a/apps/banglexercise/README.md b/apps/banglexercise/README.md index efc87424d..9d8ee7be4 100644 --- a/apps/banglexercise/README.md +++ b/apps/banglexercise/README.md @@ -2,7 +2,13 @@ Can automatically track exercises while wearing the Bangle.js watch. -Currently only push ups are supported. +Currently only push ups and curls are supported. + +## Disclaimer + +This app is very experimental. +It could be and is likely that the threshold values for detecting exercises do not work for everyone. +Therefore it would be great if we could improve this app together :-) ## Usage @@ -17,7 +23,6 @@ Press stop to end your exercise. ## TODO * Add other exercise types: * Rope jumps - * Curls * Sit ups * ... * Save exercises to file system diff --git a/apps/banglexercise/app.js b/apps/banglexercise/app.js index e18faf554..3f710cf1e 100644 --- a/apps/banglexercise/app.js +++ b/apps/banglexercise/app.js @@ -15,26 +15,35 @@ let lastZeroPassTime = 0; let lastExerciseCmpltTime = 0; let exerciseType = { + "id": "", "name": "" }; + +// add new exercises here: const exerciseTypes = [{ "id": "pushup", "name": "Push ups", "useYaxe": true, - "useZaxe": false - } // add other exercises here + "useZaxe": false, + "thresholdY": 2500, + "thresholdTime": 1400 // mininmal time between two push ups + }, + { + "id": "curl", + "name": "Curls", + "useYaxe": true, + "useZaxe": false, + "thresholdY": 2500, + "thresholdTime": 1000 // mininmal time between two curls + } ]; let exerciseCounter = 0; let layout; let recordActive = false; -/** - * Thresholds - */ +// Size of average window for data analysis const avgSize = 6; -const pushUpThresholdY = 2500; -const pushUpThresholdTime = 1400; // mininmal time between two push ups let hrtValue; @@ -55,7 +64,9 @@ function showMainMenu() { }); if (exerciseCounter > 0) { - menu["----"] = {}; + menu["----"] = { + value: "" + }; menu["Last:"] = { value: exerciseCounter + " " + exerciseType.name }; @@ -101,19 +112,18 @@ function accelHandler(accel) { if (l > 1) { const p1 = historyAvgY[l - 2]; const p2 = historyAvgY[l - 1]; - const mY = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000); - if (Math.abs(mY) >= pushUpThresholdY) { - historyAvgY.shift(); - historySlopeY.push([t, mY]); - //console.log(t, Math.abs(mY)); + const slopeY = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000); - const lMY = historySlopeY.length; - if (lMY > 1) { - const pMY1 = historySlopeY[lMY - 2][1]; - const pMY2 = historySlopeY[lMY - 1][1]; - isValidPushUp(pMY1, pMY2, t); - } + // we use this data for exercises which can be detected by using Y axis data + switch (exerciseType.id) { + case "pushup": + isValidYAxisExercise(slopeY, t); + break; + case "curl": + isValidYAxisExercise(slopeY, t); + break; } + } } @@ -123,55 +133,81 @@ function accelHandler(accel) { if (l > 1) { const p1 = historyAvgZ[l - 2]; const p2 = historyAvgZ[l - 1]; - const mZ = (p2[1] - p1[1]) / (p2[0] - p1[0]); + const slopeZ = (p2[1] - p1[1]) / (p2[0] - p1[0]); historyAvgZ.shift(); - historySlopeZ.push([p2[0] - p1[0], mZ]); + historySlopeZ.push([p2[0] - p1[0], slopeZ]); + + // TODO: we can use this data for some exercises which can be detected by using Z axis data } } } -function isValidPushUp(p1, p2, t) { - if (p1 > 0 && p2 < 0) { +/* + * Check if slope value of Y-axis data looks like an exercise + * + * In detail we look for slop values which are bigger than the configured Y threshold for the current exercise + * Then we look for two consecutive slope values of which one is above 0 and the other is below zero. + * If we find one pair of these values this could be part of one exercise. + * Then we look for a pair of values which cross the zero from the otherwise direction + */ +function isValidYAxisExercise(slopeY, t) { + if (!exerciseType) return; - if (lastZeroPassType == "-+") { - console.log(t, "Push up half complete..."); + const thresholdY = exerciseType.thresholdY; + const thresholdTime = exerciseType.thresholdTime; + const exerciseName = exerciseType.name; - layout.progress.label = "*"; - layout.render(); - } + if (Math.abs(slopeY) >= thresholdY) { + historyAvgY.shift(); + historySlopeY.push([t, slopeY]); + //console.log(t, Math.abs(slopeY)); - lastZeroPassType = "+-"; - lastZeroPassTime = t; - } - if (p2 > 0 && p1 < 0) { + const lSlopeY = historySlopeY.length; + if (lSlopeY > 1) { + const p1 = historySlopeY[lSlopeY - 2][1]; + const p2 = historySlopeY[lSlopeY - 1][1]; + if (p1 > 0 && p2 < 0) { + if (lastZeroPassType == "-+") { + console.log(t, exerciseName + " half complete..."); - if (lastZeroPassType == "+-") { - // potential complete push up. Let's check the time difference... - const tDiffLastPushUp = t - lastExerciseCmpltTime; - const tDiffStart = t - tStart; - console.log(t, "Push up maybe complete?", Math.round(tDiffLastPushUp), Math.round(tDiffStart)); + layout.progress.label = "*"; + layout.render(); + } - if ((lastExerciseCmpltTime <= 0 && tDiffStart >= pushUpThresholdTime) || tDiffLastPushUp >= pushUpThresholdTime) { - console.log(t, "Push up complete!!!"); + lastZeroPassType = "+-"; + lastZeroPassTime = t; + } + if (p2 > 0 && p1 < 0) { + if (lastZeroPassType == "+-") { + // potential complete exercise. Let's check the time difference... + const tDiffLastPushUp = t - lastExerciseCmpltTime; + const tDiffStart = t - tStart; + console.log(t, exerciseName + " maybe complete?", Math.round(tDiffLastPushUp), Math.round(tDiffStart)); - lastExerciseCmpltTime = t; - exerciseCounter++; + if ((lastExerciseCmpltTime <= 0 && tDiffStart >= thresholdTime) || tDiffLastPushUp >= thresholdTime) { + console.log(t, exerciseName + " complete!!!"); - layout.count.label = exerciseCounter; - layout.progress.label = ""; - layout.render(); + lastExerciseCmpltTime = t; + exerciseCounter++; - Bangle.buzz(100, 0.3); // TODO make configurable - } else { - console.log(t, "Push up to quick for threshold!"); + layout.count.label = exerciseCounter; + layout.progress.label = ""; + layout.render(); + + Bangle.buzz(100, 0.4); // TODO make configurable + } else { + console.log(t, exerciseName + " to quick for time threshold!"); + } + } + + lastZeroPassType = "-+"; + lastZeroPassTime = t; } } - - lastZeroPassType = "-+"; - lastZeroPassTime = t; } } + function reset() { historyY = []; historyZ = []; @@ -252,14 +288,12 @@ function startRecording() { }, ] }, { - btns: [ - { - label: "STOP", - cb: () => { - stopRecording(); - } + btns: [{ + label: "STOP", + cb: () => { + stopRecording(); } - ], + }], lazy: true }); layout.render(); From a3ada12a928931646e078a083bc394012f3c646e Mon Sep 17 00:00:00 2001 From: Marco Heiming Date: Tue, 11 Jan 2022 16:04:54 +0100 Subject: [PATCH 05/24] Add settings for buzz; more timeouts to better detect activities; reduce memory usage --- apps.json | 6 ++- apps/banglexercise/app.js | 99 ++++++++++++++++++++++------------ apps/banglexercise/settings.js | 21 ++++++++ 3 files changed, 92 insertions(+), 34 deletions(-) create mode 100644 apps/banglexercise/settings.js diff --git a/apps.json b/apps.json index 0adb4abc5..230a37b67 100644 --- a/apps.json +++ b/apps.json @@ -5518,7 +5518,11 @@ "readme": "README.md", "storage": [ {"name":"banglexercise.app.js","url":"app.js"}, - {"name":"banglexercise.img","url":"app-icon.js","evaluate":true} + {"name":"banglexercise.img","url":"app-icon.js","evaluate":true}, + {"name":"banglexercise.settings.js","url":"settings.js"} + ], + "data": [ + {"name":"banglexercise.json"} ] } ] diff --git a/apps/banglexercise/app.js b/apps/banglexercise/app.js index 3f710cf1e..ac4153239 100644 --- a/apps/banglexercise/app.js +++ b/apps/banglexercise/app.js @@ -9,10 +9,11 @@ let historyAvgZ = []; let historySlopeY = []; let historySlopeZ = []; -let lastZeroPassType; +let lastZeroPassCameFromPositive; let lastZeroPassTime = 0; -let lastExerciseCmpltTime = 0; +let lastExerciseCompletionTime = 0; +let lastExerciseHalfCompletionTime = 0; let exerciseType = { "id": "", @@ -22,19 +23,23 @@ let exerciseType = { // add new exercises here: const exerciseTypes = [{ "id": "pushup", - "name": "Push ups", + "name": "push ups", "useYaxe": true, "useZaxe": false, "thresholdY": 2500, - "thresholdTime": 1400 // mininmal time between two push ups + "thresholdMinTime": 1400, // mininmal time between two push ups in ms + "thresholdMaxTime": 5000, // maximal time between two push ups in ms + "thresholdMinDurationTime": 700, // mininmal duration of half a push ups in ms }, { "id": "curl", - "name": "Curls", + "name": "curls", "useYaxe": true, "useZaxe": false, "thresholdY": 2500, - "thresholdTime": 1000 // mininmal time between two curls + "thresholdMinTime": 1000, // mininmal time between two curls in ms + "thresholdMaxTime": 5000, // maximal time between two curls in ms + "thresholdMinDurationTime": 500, // mininmal duration of half a push ups in ms } ]; let exerciseCounter = 0; @@ -47,6 +52,10 @@ const avgSize = 6; let hrtValue; +let settings = storage.readJSON("banglexercise.json", 1) || { + 'buzz': true +}; + function showMainMenu() { let menu; menu = { @@ -64,7 +73,7 @@ function showMainMenu() { }); if (exerciseCounter > 0) { - menu["----"] = { + menu["--------"] = { value: "" }; menu["Last:"] = { @@ -91,6 +100,8 @@ function accelHandler(accel) { if (historyY.length > avgSize / 2) { const avgY = E.sum(historyY) / historyY.length; historyAvgY.push([t, avgY]); + while (historyAvgY.length > avgSize) + historyAvgY.shift(); } } @@ -103,6 +114,8 @@ function accelHandler(accel) { if (historyZ.length > avgSize / 2) { const avgZ = E.sum(historyZ) / historyZ.length; historyAvgZ.push([t, avgZ]); + while (historyAvgZ.length > avgSize) + historyAvgZ.shift(); } } @@ -113,7 +126,6 @@ function accelHandler(accel) { const p1 = historyAvgY[l - 2]; const p2 = historyAvgY[l - 1]; const slopeY = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000); - // we use this data for exercises which can be detected by using Y axis data switch (exerciseType.id) { case "pushup": @@ -154,7 +166,9 @@ function isValidYAxisExercise(slopeY, t) { if (!exerciseType) return; const thresholdY = exerciseType.thresholdY; - const thresholdTime = exerciseType.thresholdTime; + const thresholdMinTime = exerciseType.thresholdMinTime; + const thresholdMaxTime = exerciseType.thresholdMaxTime; + const thresholdMinDurationTime = exerciseType.thresholdMinDurationTime; const exerciseName = exerciseType.name; if (Math.abs(slopeY) >= thresholdY) { @@ -164,43 +178,61 @@ function isValidYAxisExercise(slopeY, t) { const lSlopeY = historySlopeY.length; if (lSlopeY > 1) { - const p1 = historySlopeY[lSlopeY - 2][1]; - const p2 = historySlopeY[lSlopeY - 1][1]; + const p1 = historySlopeY[lSlopeY - 1][1]; + const p2 = historySlopeY[lSlopeY - 2][1]; if (p1 > 0 && p2 < 0) { - if (lastZeroPassType == "-+") { + if (lastZeroPassCameFromPositive == false) { + lastExerciseHalfCompletionTime = t; console.log(t, exerciseName + " half complete..."); - layout.progress.label = "*"; + layout.progress.label = "½"; layout.render(); } - lastZeroPassType = "+-"; + lastZeroPassCameFromPositive = true; lastZeroPassTime = t; } if (p2 > 0 && p1 < 0) { - if (lastZeroPassType == "+-") { - // potential complete exercise. Let's check the time difference... - const tDiffLastPushUp = t - lastExerciseCmpltTime; + if (lastZeroPassCameFromPositive == true) { + const tDiffLastExercise = t - lastExerciseCompletionTime; const tDiffStart = t - tStart; - console.log(t, exerciseName + " maybe complete?", Math.round(tDiffLastPushUp), Math.round(tDiffStart)); + console.log(t, exerciseName + " maybe complete?", Math.round(tDiffLastExercise), Math.round(tDiffStart)); - if ((lastExerciseCmpltTime <= 0 && tDiffStart >= thresholdTime) || tDiffLastPushUp >= thresholdTime) { - console.log(t, exerciseName + " complete!!!"); + // check minimal time between exercises: + if ((lastExerciseCompletionTime <= 0 && tDiffStart >= thresholdMinTime) || tDiffLastExercise >= thresholdMinTime) { - lastExerciseCmpltTime = t; - exerciseCounter++; + // check maximal time between exercises: + if (lastExerciseCompletionTime <= 0 || tDiffLastExercise <= thresholdMaxTime) { - layout.count.label = exerciseCounter; - layout.progress.label = ""; - layout.render(); + // check minimal duration of exercise: + const tDiffExerciseHalfCompletion = t - lastExerciseHalfCompletionTime; + if (tDiffExerciseHalfCompletion > thresholdMinDurationTime) { + console.log(t, exerciseName + " complete!!!"); - Bangle.buzz(100, 0.4); // TODO make configurable + lastExerciseCompletionTime = t; + exerciseCounter++; + + layout.count.label = exerciseCounter; + layout.progress.label = ""; + layout.render(); + + if (settings.buzz) + Bangle.buzz(100, 0.4); + } else { + console.log(t, exerciseName + " to quick for duration time threshold!"); + lastExerciseCompletionTime = t; + } + } else { + console.log(t, exerciseName + " to slow for time threshold!"); + lastExerciseCompletionTime = t; + } } else { console.log(t, exerciseName + " to quick for time threshold!"); + lastExerciseCompletionTime = t; } } - lastZeroPassType = "-+"; + lastZeroPassCameFromPositive = false; lastZeroPassTime = t; } } @@ -216,9 +248,10 @@ function reset() { historySlopeY = []; historySlopeZ = []; - lastZeroPassType = ""; + lastZeroPassCameFromPositive = undefined; lastZeroPassTime = 0; - lastExerciseCmpltTime = 0; + lastExerciseHalfCompletionTime = 0; + lastExerciseCompletionTime = 0; exerciseCounter = 0; tStart = 0; } @@ -300,18 +333,18 @@ function startRecording() { Bangle.setPollInterval(80); // 12.5 Hz Bangle.on('accel', accelHandler); - Bangle.buzz(200, 1); tStart = new Date().getTime(); recordActive = true; + if (settings.buzz) + Bangle.buzz(200, 1); } function stopRecording() { if (!recordActive) return; + g.clear(1); - - Bangle.setHRMPower(0, "banglexercise"); - Bangle.removeListener('accel', accelHandler); + Bangle.setHRMPower(0, "banglexercise"); showMainMenu(); recordActive = false; } diff --git a/apps/banglexercise/settings.js b/apps/banglexercise/settings.js new file mode 100644 index 000000000..3208c6eca --- /dev/null +++ b/apps/banglexercise/settings.js @@ -0,0 +1,21 @@ +(function(back) { + const SETTINGS_FILE = "banglexercise.json"; + const storage = require('Storage'); + let settings = storage.readJSON(SETTINGS_FILE, 1) || {}; + function save(key, value) { + settings[key] = value; + storage.write(SETTINGS_FILE, settings); + } + E.showMenu({ + '': { 'title': 'BanglExercise' }, + '< Back': back, + 'Buzz': { + value: "buzz" in settings ? settings.buzz : false, + format: () => (settings.buzz ? 'Yes' : 'No'), + onchange: () => { + settings.buzz = !settings.buzz; + save('buzz', settings.buzz); + } + } + }); +}); From cc5a1f5d5a3756bc99d9b8cb66783a7cc34f0890 Mon Sep 17 00:00:00 2001 From: Marco Heiming Date: Tue, 11 Jan 2022 16:09:02 +0100 Subject: [PATCH 06/24] Fix missing module requirement --- apps/banglexercise/app.js | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/banglexercise/app.js b/apps/banglexercise/app.js index ac4153239..3e53cff61 100644 --- a/apps/banglexercise/app.js +++ b/apps/banglexercise/app.js @@ -1,5 +1,6 @@ const Layout = require("Layout"); const heatshrink = require('heatshrink'); +const storage = require('storage'); let tStart; let historyY = []; From 800624f2d405270deb9f7ed5cab41946d494b41e Mon Sep 17 00:00:00 2001 From: Marco Heiming Date: Tue, 11 Jan 2022 16:18:10 +0100 Subject: [PATCH 07/24] Fix missing module requirement --- apps/banglexercise/app.js | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/banglexercise/app.js b/apps/banglexercise/app.js index ac4153239..f5a532408 100644 --- a/apps/banglexercise/app.js +++ b/apps/banglexercise/app.js @@ -1,5 +1,6 @@ const Layout = require("Layout"); const heatshrink = require('heatshrink'); +const storage = require('Storage'); let tStart; let historyY = []; From 1cdcedc997db4286270eaa5c3b7782b57310f073 Mon Sep 17 00:00:00 2001 From: David Peer Date: Tue, 11 Jan 2022 17:41:02 +0100 Subject: [PATCH 08/24] Bugfix: If the weather module does not exist, an exception was thrown. Now its hidden in a function. --- apps/lcars/lcars.app.js | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 09998ccf5..39d01a687 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -1,10 +1,5 @@ const SETTINGS_FILE = "lcars.setting.json"; -const Storage = require("Storage"); -const weather = require('weather'); - - -// ...and overwrite them with any saved values -// This way saved values are preserved if a new version adds more settings +const locale = require('locale'); const storage = require('Storage') let settings = { alarm: -1, @@ -39,7 +34,7 @@ var disableInfoUpdate = true; // When gadgetbridge connects, step infos cannot b /* * Requirements and globals */ -const locale = require('locale'); + var bgLeft = { width : 27, height : 176, bpp : 3, @@ -124,7 +119,7 @@ function queueDraw() { function printData(key, y, c){ g.setFontAlign(-1,-1,0); var text = "ERR"; - var value = "NOT FOUND"; + var value = "ERR"; if(key == "Battery"){ text = "BAT"; @@ -136,7 +131,7 @@ function printData(key, y, c){ } else if(key == "Temp."){ text = "TEMP"; - value = Math.floor(E.getTemperature()) + "C"; + value = locale.temp(parseInt(E.getTemperature())); } else if(key == "HRM"){ text = "HRM"; @@ -148,12 +143,8 @@ function printData(key, y, c){ } else if (key == "Weather"){ text = "TEMP"; - const w = weather.get(); - if (!w) { - value = "ERR"; - } else { - value = require('locale').temp(w.temp-273.15); // applies conversion - } + var weather = getWeather(); + value = locale.temp(parseInt(weather.temp-273.15)); } g.setColor(c); @@ -429,6 +420,21 @@ function getSteps() { } +function getWeather(){ + let weather; + + try { + weather = require('weather'); + } catch(ex) { + return { + temp: 0.0 + }; + } + + return weather.get(); +} + + /* * Handle alarm */ From 5de8ca8184909f17b65900e1883204810b82c3e5 Mon Sep 17 00:00:00 2001 From: David Peer Date: Tue, 11 Jan 2022 18:10:05 +0100 Subject: [PATCH 09/24] Updated changelog, readme, screenshots etc. More consistent naming of datapoints. --- apps/lcars/ChangeLog | 4 ++-- apps/lcars/README.md | 36 +++++++++++++++------------- apps/lcars/lcars.app.js | 45 ++++++++++++++++------------------- apps/lcars/lcars.settings.js | 2 +- apps/lcars/screenshot.png | Bin 5259 -> 5547 bytes 5 files changed, 44 insertions(+), 43 deletions(-) diff --git a/apps/lcars/ChangeLog b/apps/lcars/ChangeLog index 45d7a8dd7..bcaddeb56 100644 --- a/apps/lcars/ChangeLog +++ b/apps/lcars/ChangeLog @@ -7,5 +7,5 @@ 0.07: Added settings to adjust data that is shown for each row. 0.08: Support for multiple screens. 24h graph for steps + HRM. Fullscreen Mode. 0.09: Tab anywhere to open the launcher. -0.10: Fix - Clock is unresponsive, if gadgetbridge connects. -0.11: Added getting the gadgetbridge weather \ No newline at end of file +0.10: Removed swipes to be compatible with the Pattern Launcher. Stability improvements. +0.11: Show the gadgetbridge weather temperature (settings). \ No newline at end of file diff --git a/apps/lcars/README.md b/apps/lcars/README.md index 4bf5218f6..510e5637c 100644 --- a/apps/lcars/README.md +++ b/apps/lcars/README.md @@ -4,20 +4,27 @@ A simple LCARS inspired clock. Note: To display the steps, the health app is required. If this app is not installed, the data will not be shown. To contribute you can open a PR at this [GitHub Repo]( https://github.com/peerdavid/BangleApps) +## Control + * Tap left / right to change between screens. + * Tap top / bottom to control the current screen. + ## Features * LCARS Style watch face. - * Full screen mode - widgets are still loaded. - * Supports multiple screens with different data. - * Tab anywhere to open the launcher. - * [Screen 1] Date + Time + Lock status. - * [Screen 1] Shows randomly images of real planets. - * [Screen 1] Shows different states such as (charging, out of battery, GPS on etc.) - * [Screen 1] Swipe up/down to activate an alarm. - * [Screen 1] Shows 3 customizable datapoints on the first screen. - * [Screen 1] The lower orange line indicates the battery level. - * [Screen 2] Display graphs for steps + hrm on the second screen. - * [Screen 2] Switch between day/month via swipe up/down. + * Full screen mode - widgets are still loaded but not shown. + * Tab on left/right to switch between different screens. + * Cusomizable data that is shown on screen 1 (steps, weather etc.) + * Shows random images of real planets. + * Tap on top/bottom of screen 1 to activate an alarm. + * The lower orange line indicates the battery level. + * Display graphs for steps + hrm on the second screen. +## Data that can be configured + * Steps - Steps loaded via the health module + * Battery - Current battery level in % + * VREF - Voltage of battery + * HRM - Last measured HRM + * Temp - Weather temperature loaded via the weather module + gadgetbridge + * CoreT - Temperature of device ## Multiple screens support Access different screens via swipe left/ right @@ -26,10 +33,7 @@ Access different screens via swipe left/ right ![](screenshot_2.png) -## Icons -
Icons made by Smashicons, Freepik from www.flaticon.com
- - ## Contributors -- Creator: [David Peer](https://github.com/peerdavid). +- Initial creation and improvements: [David Peer](https://github.com/peerdavid). - Improvements: [Adam Schmalhofer](https://github.com/adamschmalhofer). +- Improvements: [Jon Warrington](https://github.com/BartokW). diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 39d01a687..521f661e7 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -28,7 +28,7 @@ let cGrey = "#9E9E9E"; let lcarsViewPos = 0; let drag; let hrmValue = 0; -var plotWeek = false; +var plotMonth = false; var disableInfoUpdate = true; // When gadgetbridge connects, step infos cannot be loaded /* @@ -118,33 +118,30 @@ function queueDraw() { function printData(key, y, c){ g.setFontAlign(-1,-1,0); - var text = "ERR"; + key = key.toUpperCase() + var text = key; var value = "ERR"; - if(key == "Battery"){ - text = "BAT"; - value = E.getBattery() + "%"; - - } else if(key == "Steps"){ + if(key == "STEPS"){ text = "STEP"; value = getSteps(); - } else if(key == "Temp."){ - text = "TEMP"; - value = locale.temp(parseInt(E.getTemperature())); - - } else if(key == "HRM"){ - text = "HRM"; - value = hrmValue; + } else if(key == "BATTERY"){ + text = "BAT"; + value = E.getBattery() + "%"; } else if (key == "VREF"){ - text = "VREF"; value = E.getAnalogVRef().toFixed(2) + "V"; - } else if (key == "Weather"){ - text = "TEMP"; + } else if(key == "HRM"){ + value = hrmValue; + + } else if (key == "TEMP"){ var weather = getWeather(); value = locale.temp(parseInt(weather.temp-273.15)); + + } else if(key == "CORET"){ + value = locale.temp(parseInt(E.getTemperature())); } g.setColor(c); @@ -300,7 +297,7 @@ function drawPosition1(){ } // Plot HRM graph - if(plotWeek){ + if(plotMonth){ var data = new Uint16Array(32); var cnt = new Uint8Array(32); health.readDailySummaries(new Date(), h=>{ @@ -337,8 +334,8 @@ function drawPosition1(){ g.setFontAlign(1, 1, 0); g.setFontAntonioMedium(); g.setColor(cWhite); - g.drawString("WEEK HRM", 154, 27); - g.drawString("WEEK STEPS [K]", 154, 115); + g.drawString("M-HRM", 154, 27); + g.drawString("M-STEPS [K]", 154, 115); // Plot day } else { @@ -378,8 +375,8 @@ function drawPosition1(){ g.setFontAlign(1, 1, 0); g.setFontAntonioMedium(); g.setColor(cWhite); - g.drawString("DAY HRM", 154, 27); - g.drawString("DAY STEPS", 154, 115); + g.drawString("D-HRM", 154, 27); + g.drawString("D-STEPS", 154, 115); } } @@ -568,9 +565,9 @@ Bangle.on('touch', function(btn, e){ drawState(); return; } - } else if (lcarsViewPos == 1 && (is_upper || is_lower) && plotWeek != is_lower){ + } else if (lcarsViewPos == 1 && (is_upper || is_lower) && plotMonth != is_lower){ feedback(); - plotWeek = is_lower; + plotMonth = is_lower; draw(); return; } diff --git a/apps/lcars/lcars.settings.js b/apps/lcars/lcars.settings.js index a0e54f9b4..1dd6e8d73 100644 --- a/apps/lcars/lcars.settings.js +++ b/apps/lcars/lcars.settings.js @@ -18,7 +18,7 @@ storage.write(SETTINGS_FILE, settings) } - var data_options = ["Battery", "Steps", "Temp.", "HRM", "VREF", "Weather"]; + var data_options = ["Steps", "Battery", "VREF", "HRM", "Temp", "CoreT"]; E.showMenu({ '': { 'title': 'LCARS Clock' }, diff --git a/apps/lcars/screenshot.png b/apps/lcars/screenshot.png index 5d7603b45e6c7aadcc01aa0afdf13ff4d579eb93..5381492b242865bae924e11daf9a5cd40313188f 100644 GIT binary patch literal 5547 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 literal 5259 zcmV;66m;u}P)Px}MM*?KRCr$PUE8*#Dh!>~UZBTkD`W%$ zBCr5B!5a|D%;Jg&{QdiUK5?`5WZo+p@y*Zt`!~@qG!X!N;MBNp#?mEqPX@ju?D^2B z2qXhXMKbfbBm&96mxLXA=rlc%{{8*sd}Cdr8B+t(#l{R#D2Q1}O^iI@UumaF6C|Y4|7rNL(qE z$qV2*eM{%IxLjjk%B&L8Qt(L(UH;DHL(5@}3`1yO6nMQXQyTFI07L;8EgPE70`3-* zW%$<^xG-wXq;-0ffoo=_%y0c>N6xEP;+_iNmeB+SCYlZgu%dw_P*EA-W#ILZJMEne z478YcatLI}Cb<=W*9llMaDz9=0V2ERys*T5mKR3}6QX^KQ$gp;3)2S#Hw(QeB2}A&vx^x6!@n>j2*lzzz+} z0=T*#bt%dQGRwNNnD{Y1u*EiBKzh?l0G`+u)!pbb(R5Z!T3iT=foCi;j4n;kEce4& z%t!_nTDb=#^}V+Mu#Fa>dIxilandm*HiIBkzZ4dP_r29NnjSA(C-?Dy}sFfhl$1;moOVaXwjg(DbP zT2`jM?*R-9oQ-BhAozMD%S7g7RZ|+;y+gib!|gO+N5`fvME!0g!nofv8JMk@$g;4e zd9_86y#_`O_p@3q_TEIiVTb^&_Wpz5_y0uXU$>XRr{Na8_Jq^q^k~Jgq(kDW^{`E4jrgM+dz+kfs zne5Rn#983AKe>ck` zmKuurWMDK>N;)s-MqP=cCp2jJV;uECVNik>+30AX^;~Et?Fy z#ykRiy?LPbTk^qjD6|RObd6(cv}`DZdzqAD?y`^9Vqh&DQvPIMAxbDnJc|z8qgu{< zK+A>z;3x(z@<3zm`n96ez*TES82%_o9sywGow%=#Uy+WS>%dJ2x%;eiNC$i*11Ery zrr*+NyV16ZsO{%!TPw9gP|%G#>)fb;0?(QClY; zwGxN+zDySvfnGm0t|Y#k;uzTco{Xt@mUe%PY37-x`G-h5!33TQWla{WUld zPXMq6e56xYX{_rZjd#;b{yy_NebrJ-y}Q`?ck6>3T#PgT&$G;R#|KTv-S;)-*FMP| zZ;E{8)w@Wbl?^GkmBzY$Q|yHldl&}hz?uMFC?NqXV|^F~E?||ko~IE}Swh3ox>Ye5 zID|}_GP#_ADZ_98uKy-Uin{iZ0Io*?DZ&ZhChUdWbU6c$2w?hYEK3t7fJ3m@l*z3a z7(GLwmw^$Mx7e6UA~a8KwBbefJD&rYv7Ry%1DDWn{w-}(1PBS>vA}jjX@+9ps{!n>>NE=u zSpugchsCbe^&`M>hdh&kt5%8tumntN5@+dc>-rtQc0?J6VBp$HtEGaps6nkai4DCH zq7}dzJ0f{n>}p*<0vLD6a|i}L8o($35U_C#n*hEJ?n5)M#~`|IabS5fSw>(Nf$wYekt{sVz#HCB&bG-2cq6bo7h(Z%91};q?R-@}$XnKn z_{z9lg`1y)O`@6D+oMKGkBlN2uWMUOJeen%1%dDSq3mSf%SLYVXsI_d3PC{p2{vHrfF_*J6xhVBeaE70*{P@R1<07$X_jXQx{6 zd>sQvf)@#HPC+sPy%DH3^Z14@ZGx2G?QNF4AC17eoif3T1}xK!6@dl7K@+xQ-mx0@ z4W;>;U&jdAL5a+YpA=MW&T!|>961+iweZmRE$_r712^?6ZREOhXO0??BXDKKF&VfC zy30+G3|xbTYc%OP1si29>pHR`O3PE~ErF}>A0w|CWYTpH152G_G5}Ce7kS`KPw(4}vS+#w7H~Ll zZ49+Py8<~0Qp(RN2YMQ0lm@2xNSd>_MG}MEm4T&Bz`0{|WJ?AH7#U$I8ft4pST!8OrSJkWTOx6UeRRiCSf$1b{ z&qjNFTFU_HF#OmwMB92LgjOz&g(Ea@4Jyj01xE$c65~?qDbj_g!Bv;19&t2H%Z)Ue zCbt$*E@fZ}#ugpxInEyiFj6iSzySq7Ku z*xvia$tk0OHTlx(1AF%3T*|rv18h2_~K3X&}|)g|z`_NCwsbEG_eLLp%b0 zj^&#HtY|jG2evdcq6s+*s;q2FD__T`Cu_A=L?^ufjLqbVkHg%u(Wvxov@W>a4yDG3`{}94bwY-uZ-Wi@@D0~ zO|UmNK--Om>}^2GZ^;I3ai`>a2?NuH>-oC72f)&EN7#*4GoJ+-OGBd95f?&Q8LUCq z>H~Wv~vwimlJmF8Rv7C8B{N;@Y|qNR<$PAB$*K zgjub8tT>(y;D|;WB@fF*7^R%va-hs;fv@MfHPQS^i1KEPx00DYBQQb)l7UBPg)BpJ z1QNi_q01Xbh(H2(gjUEhG)Lf6AGo=cgWjkesDXSSX3%QI9Td`GL!j|H)dwE6!CK3u zeW3_#fYxUpdEZE1Ub5o&2nL=!;c^B9;@?I!ek%jh`%ueByKi}qWixO~!`FZRSq7d@ z75ja9c@CGp{)jM_u8wV8Ui3NTeyQBF?QYA!-=snQa1zG-{aw=S{~rzJ z76vZNLK)Hq@WQa%eUu5U--WB|{4D0=7_sF!bfn-IUHbO)<-5@$03fhQm?q2C{9Q)C7PHCjFBP`9ujC%$bf3A}c&A_ruD4N%Phl)F|p)ffD zQZ3D085q^3665cIzQKyiR~m8ZuiJlj*8#2t1@Ub(@Lm!1LT|ahmyKH(n3i1Ah`+-+ z&wgG8=9rRumd3W)sDQFG)whWI@eAMq&jW4TKG~b~aSlou%!NAy-zBVTAVPMqfl!3AA0@wijt3@T>*TcZnC9wSEUn#Pclb27t z|H8QyoNF?i2ChN;+xym2(LH0nWe8Uf^s0#-2B!B~HX7;>)S;1lUk?LQJ?n8LES>x~ z4XkP3#sBqdV3dI;gV`%3dKh>EB&LCB`u#FP>Dc=Koz1}MLY%p~Ug!)S5ce=JWmKg5 zSejDHQ}bUXXM{2&>QubWg+Q413-Ik{3>^$PrWvw=l5e7}TugozYt^M6E?NR^@ayS1w7vb0dUS zU#|upb|sEuU`)G4Y2wP}aA@H(8CY{I5C*OpT-pP%2k^$!G`+;oM{D4r_d=ZJ`wI5{xi=<8v~Ujt*Lwl1JqMD8r9Lp- z#YGv5V@O%99K*oB0Ot2naC;@VvR>{3kEDIMWAiml-NL|LJu2x^OFts6hEzALUho_v zD4|u4OLwD1?sbC{lZwEf}J8}GQwT0l&R@tb`j`{o% z#8XO>59y3RGVosNt>DHOxHI;7dq4y(KNsR~4NUh!NWV!zl7R!zbwU=G0{HG4_=NV! zM;H;f6u`wEy1Wp3Z)cVfm<0Yv?;i%d?{Ot|m3o;zdeGt$*aDYg$Bf0{`+z&o_nlv^ z+a1SH{l>b$l`s{bt=3Db?@@o(<9I26Emxw(Mr|S--RL;G?b^Ux|}fgZ85{wQ(aDS?axbQ@1iO1(o!>8kTq9w(hT@Dlxd1 zf!TZ%re?ueB8xIFl_kF|bEywZV+b=aI!|CSEc=S<32T);PDl=TnD|O7;_Gv*=z1MelEr z4?Ed0Lu9G!5DZ+EXKjLKGcd~3t)|ye47}?|tqg3*5=AJn8h8K(W|)3h2JT^8jbTN? zbhB?gL1HW<7XvE~SDBA$N@k*0W@Bu{feD%=b6EL6Wk`unuLe#ARyVD=G2F|*E~`lb zCo{ZL21PaVR~b27h#klkw}f_vCYIWFx<@l`X%EWC_21o~Wrz1x2DWF}y7rV9*wVl7H6~3sdwt-+_d=k|XMd&Wr0>Q> zGcY?GTb7sNRo4|tsja$TnFlmmXH=!6!^M?53~$-ob3$hTHwF`|OB%Rnmbt?l``dVif% z;47by@Jb0*(5+biN&cj|Q Date: Tue, 11 Jan 2022 18:11:49 +0100 Subject: [PATCH 10/24] Updated screenshot. --- apps/lcars/screenshot.png | Bin 5547 -> 5721 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/lcars/screenshot.png b/apps/lcars/screenshot.png index 5381492b242865bae924e11daf9a5cd40313188f..319062dcce36640c38e24378d97b7724ab3a9f74 100644 GIT binary patch literal 5721 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 From 755f44cf3f3896a3a646f96bea49ce48aa94b90d Mon Sep 17 00:00:00 2001 From: David Peer Date: Tue, 11 Jan 2022 18:20:53 +0100 Subject: [PATCH 11/24] Bugfix: Alarm not working. --- apps/lcars/ChangeLog | 3 ++- apps/lcars/lcars.app.js | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/lcars/ChangeLog b/apps/lcars/ChangeLog index bcaddeb56..bb5270b70 100644 --- a/apps/lcars/ChangeLog +++ b/apps/lcars/ChangeLog @@ -8,4 +8,5 @@ 0.08: Support for multiple screens. 24h graph for steps + HRM. Fullscreen Mode. 0.09: Tab anywhere to open the launcher. 0.10: Removed swipes to be compatible with the Pattern Launcher. Stability improvements. -0.11: Show the gadgetbridge weather temperature (settings). \ No newline at end of file +0.11: Show the gadgetbridge weather temperature (settings). +0.12: Refactoring and stability improvements. \ No newline at end of file diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 521f661e7..c4e675873 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -470,7 +470,7 @@ function handleAlarm(){ .then(() => { // Update alarm state to disabled settings.alarm = -1; - Storage.writeJSON(SETTINGS_FILE, settings); + storage.writeJSON(SETTINGS_FILE, settings); }); } @@ -510,7 +510,7 @@ function increaseAlarm(){ settings.alarm = getCurrentTimeInMinutes() + 5; } - Storage.writeJSON(SETTINGS_FILE, settings); + storage.writeJSON(SETTINGS_FILE, settings); } @@ -521,7 +521,7 @@ function decreaseAlarm(){ settings.alarm = -1; } - Storage.writeJSON(SETTINGS_FILE, settings); + storage.writeJSON(SETTINGS_FILE, settings); } function feedback(){ From 1f06e6cfc578473c86214f76c5c43d9715b905a4 Mon Sep 17 00:00:00 2001 From: David Peer Date: Tue, 11 Jan 2022 21:24:23 +0100 Subject: [PATCH 12/24] Added humidity --- apps/lcars/README.md | 1 + apps/lcars/lcars.app.js | 28 +++++++++++++++++++--------- apps/lcars/lcars.settings.js | 10 +++++----- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/apps/lcars/README.md b/apps/lcars/README.md index 510e5637c..46e134f78 100644 --- a/apps/lcars/README.md +++ b/apps/lcars/README.md @@ -24,6 +24,7 @@ To contribute you can open a PR at this [GitHub Repo]( https://github.com/peerda * VREF - Voltage of battery * HRM - Last measured HRM * Temp - Weather temperature loaded via the weather module + gadgetbridge + * Humidity - Humidity loaded via the weather module + gadgetbridge * CoreT - Temperature of device ## Multiple screens support diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index c4e675873..0005e9016 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -3,9 +3,9 @@ const locale = require('locale'); const storage = require('Storage') let settings = { alarm: -1, - dataRow1: "Battery", - dataRow2: "Steps", - dataRow3: "Temp." + dataRow1: "Steps", + dataRow2: "Temp", + dataRow3: "Battery" }; let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; for (const key in saved_settings) { @@ -140,6 +140,11 @@ function printData(key, y, c){ var weather = getWeather(); value = locale.temp(parseInt(weather.temp-273.15)); + } else if (key == "HUMIDITY"){ + text = "HUM"; + var weather = getWeather(); + value = parseInt(weather.hum) + "%"; + } else if(key == "CORET"){ value = locale.temp(parseInt(E.getTemperature())); } @@ -418,17 +423,22 @@ function getSteps() { function getWeather(){ - let weather; + var weather = { + temp: 0, + hum: 0, + txt: "", + wind: 0, + wdir: 0, + wrose: "" + }; try { - weather = require('weather'); + weather = require('weather').get(); } catch(ex) { - return { - temp: 0.0 - }; + // Return default } - return weather.get(); + return weather; } diff --git a/apps/lcars/lcars.settings.js b/apps/lcars/lcars.settings.js index 1dd6e8d73..ba630799a 100644 --- a/apps/lcars/lcars.settings.js +++ b/apps/lcars/lcars.settings.js @@ -7,7 +7,7 @@ alarm: -1, dataRow1: "Battery", dataRow2: "Steps", - dataRow3: "Temp." + dataRow3: "Temp" }; let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; for (const key in saved_settings) { @@ -18,14 +18,14 @@ storage.write(SETTINGS_FILE, settings) } - var data_options = ["Steps", "Battery", "VREF", "HRM", "Temp", "CoreT"]; + var data_options = ["Steps", "Battery", "VREF", "HRM", "Temp", "Humidity", "CoreT"]; E.showMenu({ '': { 'title': 'LCARS Clock' }, '< Back': back, 'Row 1': { value: 0 | data_options.indexOf(settings.dataRow1), - min: 0, max: 5, + min: 0, max: 6, format: v => data_options[v], onchange: v => { settings.dataRow1 = data_options[v]; @@ -34,7 +34,7 @@ }, 'Row 2': { value: 0 | data_options.indexOf(settings.dataRow2), - min: 0, max: 5, + min: 0, max: 6, format: v => data_options[v], onchange: v => { settings.dataRow2 = data_options[v]; @@ -43,7 +43,7 @@ }, 'Row 3': { value: 0 | data_options.indexOf(settings.dataRow3), - min: 0, max: 5, + min: 0, max: 6, format: v => data_options[v], onchange: v => { settings.dataRow3 = data_options[v]; From dfd04df0f3d6237559e8319957eca9c0c5021f39 Mon Sep 17 00:00:00 2001 From: David Peer Date: Wed, 12 Jan 2022 07:20:08 +0100 Subject: [PATCH 13/24] Fixed weather --- apps/lcars/lcars.app.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 0005e9016..2674d323f 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -138,7 +138,7 @@ function printData(key, y, c){ } else if (key == "TEMP"){ var weather = getWeather(); - value = locale.temp(parseInt(weather.temp-273.15)); + value = weather.temp; } else if (key == "HUMIDITY"){ text = "HUM"; @@ -423,14 +423,7 @@ function getSteps() { function getWeather(){ - var weather = { - temp: 0, - hum: 0, - txt: "", - wind: 0, - wdir: 0, - wrose: "" - }; + var weather; try { weather = require('weather').get(); @@ -438,6 +431,19 @@ function getWeather(){ // Return default } + if (weather === undefined){ + weather = { + temp: "-", + hum: "-", + txt: "-", + wind: "-", + wdir: "-", + wrose: "-" + }; + } else { + weather.temp = locale.temp(parseInt(weather.temp-273.15)) + } + return weather; } From 2e564e06913e10ff6f06b67477dc9d3104e92440 Mon Sep 17 00:00:00 2001 From: David Peer Date: Wed, 12 Jan 2022 07:20:52 +0100 Subject: [PATCH 14/24] Created V0.12 --- apps.json | 6 +++--- apps/lcars/ChangeLog | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps.json b/apps.json index b5929a56c..0a4a80a72 100644 --- a/apps.json +++ b/apps.json @@ -1357,7 +1357,7 @@ "shortName":"HR Alarm", "version":"0.01", "description": "This invisible widget vibrates whenever the heart rate gets close to the upper limit or goes over or under the configured limits", - "icon": "widget.png", + "icon": "widget.png", "type": "widget", "tags": "widget", "supports" : ["BANGLEJS2"], @@ -4514,7 +4514,7 @@ "name": "LCARS Clock", "shortName":"LCARS", "icon": "lcars.png", - "version":"0.11", + "version":"0.12", "readme": "README.md", "supports": ["BANGLEJS2"], "description": "Library Computer Access Retrieval System (LCARS) clock.", @@ -5184,7 +5184,7 @@ {"name":"mmind.app.js","url":"mmind.app.js"}, {"name":"mmind.img","url":"mmind.icon.js","evaluate":true} ] - }, + }, { "id": "presentor", "name": "Presentor", diff --git a/apps/lcars/ChangeLog b/apps/lcars/ChangeLog index bb5270b70..702ef58b9 100644 --- a/apps/lcars/ChangeLog +++ b/apps/lcars/ChangeLog @@ -9,4 +9,4 @@ 0.09: Tab anywhere to open the launcher. 0.10: Removed swipes to be compatible with the Pattern Launcher. Stability improvements. 0.11: Show the gadgetbridge weather temperature (settings). -0.12: Refactoring and stability improvements. \ No newline at end of file +0.12: Added humidity to data. \ No newline at end of file From 5e353b6755d97021a349a980db632aef75486bff Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 12 Jan 2022 09:14:33 +0000 Subject: [PATCH 15/24] messages : If showMessage called with no message (eg all messages deleted) now return to the clock (fix #1267) --- apps/messages/ChangeLog | 1 + apps/messages/app.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index 0c02a76e1..522534af0 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -27,3 +27,4 @@ 0.18: Use app-specific icon colors Spread message action buttons out Back button now goes back to list of messages + If showMessage called with no message (eg all messages deleted) now return to the clock (fix #1267) diff --git a/apps/messages/app.js b/apps/messages/app.js index 4704c422f..80e4a3244 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -225,7 +225,7 @@ function showMessageSettings(msg) { function showMessage(msgid) { var msg = MESSAGES.find(m=>m.id==msgid); - if (!msg) return checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0}); // go home if no message found + if (!msg) return checkMessages({clockIfNoMsg:1,clockIfAllRead:0,showMsgIfUnread:0}); // go home if no message found if (msg.src=="Maps") { cancelReloadTimeout(); // don't auto-reload to clock now return showMapMessage(msg); From 3e6cfc95a9c4aaa6991dbe30c9b82fca681c87a8 Mon Sep 17 00:00:00 2001 From: Marco Heiming Date: Wed, 12 Jan 2022 10:27:39 +0100 Subject: [PATCH 16/24] Improve layout and add exit menu item --- apps/banglexercise/README.md | 6 +++--- apps/banglexercise/app.js | 14 +++++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/apps/banglexercise/README.md b/apps/banglexercise/README.md index 9d8ee7be4..729fb60e3 100644 --- a/apps/banglexercise/README.md +++ b/apps/banglexercise/README.md @@ -6,7 +6,7 @@ Currently only push ups and curls are supported. ## Disclaimer -This app is very experimental. +This app is experimental but it seems to work quiet reliable for me. It could be and is likely that the threshold values for detecting exercises do not work for everyone. Therefore it would be great if we could improve this app together :-) @@ -25,8 +25,8 @@ Press stop to end your exercise. * Rope jumps * Sit ups * ... -* Save exercises to file system -* Add settings (vibration, beep, ...) +* Save exercise summaries to file system +* Configure daily goal for exercises * Find a nicer icon diff --git a/apps/banglexercise/app.js b/apps/banglexercise/app.js index f5a532408..778207d26 100644 --- a/apps/banglexercise/app.js +++ b/apps/banglexercise/app.js @@ -81,6 +81,9 @@ function showMainMenu() { value: exerciseCounter + " " + exerciseType.name }; } + menu.Exit = function() { + load(); + }; E.showMenu(menu); } @@ -187,6 +190,7 @@ function isValidYAxisExercise(slopeY, t) { console.log(t, exerciseName + " half complete..."); layout.progress.label = "½"; + g.clear(); layout.render(); } @@ -215,6 +219,7 @@ function isValidYAxisExercise(slopeY, t) { layout.count.label = exerciseCounter; layout.progress.label = ""; + g.clear(); layout.render(); if (settings.buzz) @@ -279,10 +284,9 @@ function startRecording() { c: [{ type: "txt", id: "count", - font: "6x8:10", - label: exerciseCounter, - pad: 5, - bgCol: g.theme.bg + font: exerciseCounter < 100 ? "6x8:9" : "6x8:8", + label: 10, + pad: 5 }, { type: "txt", @@ -328,7 +332,7 @@ function startRecording() { stopRecording(); } }], - lazy: true + lazy: false }); layout.render(); From 4b8490645d11ca3c69ff99a4b78c6fbd8b4dc60f Mon Sep 17 00:00:00 2001 From: Marco Heiming Date: Wed, 12 Jan 2022 10:31:05 +0100 Subject: [PATCH 17/24] Fix typo and disable debug logging --- apps/banglexercise/README.md | 2 +- apps/banglexercise/app.js | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/banglexercise/README.md b/apps/banglexercise/README.md index 729fb60e3..28b276a59 100644 --- a/apps/banglexercise/README.md +++ b/apps/banglexercise/README.md @@ -13,7 +13,7 @@ Therefore it would be great if we could improve this app together :-) ## Usage -Select the exercise type you want to practive and go for it! +Select the exercise type you want to practice and go for it! Press stop to end your exercise. diff --git a/apps/banglexercise/app.js b/apps/banglexercise/app.js index 778207d26..99ebd121e 100644 --- a/apps/banglexercise/app.js +++ b/apps/banglexercise/app.js @@ -187,7 +187,7 @@ function isValidYAxisExercise(slopeY, t) { if (p1 > 0 && p2 < 0) { if (lastZeroPassCameFromPositive == false) { lastExerciseHalfCompletionTime = t; - console.log(t, exerciseName + " half complete..."); + //console.log(t, exerciseName + " half complete..."); layout.progress.label = "½"; g.clear(); @@ -201,7 +201,7 @@ function isValidYAxisExercise(slopeY, t) { if (lastZeroPassCameFromPositive == true) { const tDiffLastExercise = t - lastExerciseCompletionTime; const tDiffStart = t - tStart; - console.log(t, exerciseName + " maybe complete?", Math.round(tDiffLastExercise), Math.round(tDiffStart)); + //console.log(t, exerciseName + " maybe complete?", Math.round(tDiffLastExercise), Math.round(tDiffStart)); // check minimal time between exercises: if ((lastExerciseCompletionTime <= 0 && tDiffStart >= thresholdMinTime) || tDiffLastExercise >= thresholdMinTime) { @@ -212,7 +212,7 @@ function isValidYAxisExercise(slopeY, t) { // check minimal duration of exercise: const tDiffExerciseHalfCompletion = t - lastExerciseHalfCompletionTime; if (tDiffExerciseHalfCompletion > thresholdMinDurationTime) { - console.log(t, exerciseName + " complete!!!"); + //console.log(t, exerciseName + " complete!!!"); lastExerciseCompletionTime = t; exerciseCounter++; @@ -225,15 +225,15 @@ function isValidYAxisExercise(slopeY, t) { if (settings.buzz) Bangle.buzz(100, 0.4); } else { - console.log(t, exerciseName + " to quick for duration time threshold!"); + //console.log(t, exerciseName + " to quick for duration time threshold!"); lastExerciseCompletionTime = t; } } else { - console.log(t, exerciseName + " to slow for time threshold!"); + //console.log(t, exerciseName + " to slow for time threshold!"); lastExerciseCompletionTime = t; } } else { - console.log(t, exerciseName + " to quick for time threshold!"); + //console.log(t, exerciseName + " to quick for time threshold!"); lastExerciseCompletionTime = t; } } From ffcdcbce6bf15fc19bc605130235dbd608f3d913 Mon Sep 17 00:00:00 2001 From: Marco Heiming Date: Wed, 12 Jan 2022 11:24:04 +0100 Subject: [PATCH 18/24] Rename "recording" to "training" (because there is no recording for now) --- apps/banglexercise/app.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/banglexercise/app.js b/apps/banglexercise/app.js index 99ebd121e..0d5c814bf 100644 --- a/apps/banglexercise/app.js +++ b/apps/banglexercise/app.js @@ -69,7 +69,7 @@ function showMainMenu() { menu["Do " + et.name] = function() { exerciseType = et; E.showMenu(); - startRecording(); + startTraining(); }; }); @@ -263,7 +263,7 @@ function reset() { } -function startRecording() { +function startTraining() { if (recordActive) return; g.clear(1); reset(); @@ -319,7 +319,7 @@ function startRecording() { type: "txt", id: "recording", font: "6x8:2", - label: "RECORDING", + label: "TRAINING", bgCol: "#f00", pad: 5, fillx: 1 @@ -329,7 +329,7 @@ function startRecording() { btns: [{ label: "STOP", cb: () => { - stopRecording(); + stopTraining(); } }], lazy: false @@ -344,7 +344,7 @@ function startRecording() { Bangle.buzz(200, 1); } -function stopRecording() { +function stopTraining() { if (!recordActive) return; g.clear(1); From ea44abb791634b60c454dbfb746c493ffac20614 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 12 Jan 2022 10:58:47 +0000 Subject: [PATCH 19/24] android 0.06: Option to keep messages after a disconnect (default false) (fix #1186) --- apps.json | 8 ++++--- apps/android/ChangeLog | 1 + apps/android/README.md | 48 ++++++++++++++++++++++++++++++++++++++++ apps/android/boot.js | 6 ++++- apps/android/settings.js | 20 +++++++++++++---- 5 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 apps/android/README.md diff --git a/apps.json b/apps.json index 882816204..bb55dcc17 100644 --- a/apps.json +++ b/apps.json @@ -99,18 +99,20 @@ "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.05", + "version": "0.06", "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", "dependencies": {"messages":"app"}, "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "storage": [ {"name":"android.app.js","url":"app.js"}, {"name":"android.settings.js","url":"settings.js"}, {"name":"android.img","url":"app-icon.js","evaluate":true}, {"name":"android.boot.js","url":"boot.js"} ], + "data": [{"name":"android.settings.json"}], "sortorder": -8 }, { @@ -1357,7 +1359,7 @@ "shortName":"HR Alarm", "version":"0.01", "description": "This invisible widget vibrates whenever the heart rate gets close to the upper limit or goes over or under the configured limits", - "icon": "widget.png", + "icon": "widget.png", "type": "widget", "tags": "widget", "supports" : ["BANGLEJS2"], @@ -5185,7 +5187,7 @@ {"name":"mmind.app.js","url":"mmind.app.js"}, {"name":"mmind.img","url":"mmind.icon.js","evaluate":true} ] - }, + }, { "id": "presentor", "name": "Presentor", diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index c2c4ea6be..0d837fe43 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -4,3 +4,4 @@ 0.03: Handling of message actions (ok/clear) 0.04: Android icon now goes to settings page with 'find phone' 0.05: Fix handling of message actions +0.06: Option to keep messages after a disconnect (default false) (fix #1186) diff --git a/apps/android/README.md b/apps/android/README.md new file mode 100644 index 000000000..c10718aac --- /dev/null +++ b/apps/android/README.md @@ -0,0 +1,48 @@ +# Android Integration + +This app allows your Bangle.js to receive notifications [from the Gadgetbridge app on Android](http://www.espruino.com/Gadgetbridge) + +See [this link](http://www.espruino.com/Gadgetbridge) for notes on how to install +the Android app (and how it works). + +It requires the `Messages` app on Bangle.js (which should be automatically installed) to +display any notifications that are received. + +## Settings + +You can access the settings menu either from the `Android` icon in the launcher, +or from `App Settings` in the `Settings` menu. + +It contains: + +* `Connected` - shows whether there is an active Bluetooth connection or not +* `Find Phone` - opens a submenu where you can activate the `Find Phone` functionality +of Gadgetbridge - making your phone make noise so you can find it. +* `Keep Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js +keep any messages it has received, or should it delete them? +* `Messages` - launches the messages app, showing a list of messages + +## How it works + +Gadgetbridge on Android connects to Bangle.js, and sends commands over the +BLE UART connection. These take the form of `GB({ ... JSON ... })\n` - so they +call a global function called `GB` which then interprets the JSON. + +Responses are sent back to Gadgetbridge simply as one line of JSON. + +More info on message formats on http://www.espruino.com/Gadgetbridge + +## Testing + +Bangle.js can only hold one connection open at a time, so it's hard to see +if there are any errors when handling Gadgetbridge messages. + +However you can: + +* Use the `Gadgetbridge Debug` app on Bangle.js to display/log the messages received from Gadgetbridge +* Connect with the Web IDE and manually enter the Gadgetbridge messages on the left-hand side to +execute them as if they came from Gadgetbridge, for instance: + +``` +GB({"t":"notify","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"}) +``` diff --git a/apps/android/boot.js b/apps/android/boot.js index 59ffe006d..fff9ad444 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -4,6 +4,7 @@ Bluetooth.println(JSON.stringify(message)); } + var settings = require("Storage").readJSON("android.settings.json",1)||{}; var _GB = global.GB; global.GB = (event) => { // feed a copy to other handlers if there were any @@ -51,7 +52,8 @@ // Battery monitor function sendBattery() { gbSend({ t: "status", bat: E.getBattery() }); } NRF.on("connect", () => setTimeout(sendBattery, 2000)); - NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect + if (!settings.keep) + NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect setInterval(sendBattery, 10*60*1000); // Health tracking Bangle.on('health', health=>{ @@ -68,4 +70,6 @@ if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id }); // error/warn here? }; + // remove settings object so it's not taking up RAM + delete settings; })(); diff --git a/apps/android/settings.js b/apps/android/settings.js index d241397a4..7c46a1fc0 100644 --- a/apps/android/settings.js +++ b/apps/android/settings.js @@ -2,17 +2,29 @@ function gb(j) { Bluetooth.println(JSON.stringify(j)); } + var settings = require("Storage").readJSON("android.settings.json",1)||{}; + function updateSettings() { + require("Storage").writeJSON("android.settings.json", settings); + } var mainmenu = { "" : { "title" : "Android" }, "< Back" : back, - "Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" }, + /*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" }, "Find Phone" : () => E.showMenu({ "" : { "title" : "Find Phone" }, "< Back" : ()=>E.showMenu(mainmenu), - "On" : _=>gb({t:"findPhone",n:true}), - "Off" : _=>gb({t:"findPhone",n:false}), + /*LANG*/"On" : _=>gb({t:"findPhone",n:true}), + /*LANG*/"Off" : _=>gb({t:"findPhone",n:false}), }), - "Messages" : ()=>load("messages.app.js") + /*LANG*/"Keep Msgs" : { + value : !!settings.keep, + format : v=>v?/*LANG*/"Yes":/*LANG*/"No", + onchange: v => { + settings.keep = v; + updateSettings(); + } + }, + /*LANG*/"Messages" : ()=>load("messages.app.js") }; E.showMenu(mainmenu); }) From 569c5f9e2b5ef2d59770e244db4adffed8a1d586 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 12 Jan 2022 10:59:02 +0000 Subject: [PATCH 20/24] Add LANG tags to settings for translations --- apps/setting/settings.js | 122 +++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 27ce24e50..514adf1dd 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -63,7 +63,7 @@ const boolFormat = v => v ? /*LANG*/"On" : /*LANG*/"Off"; function showMainMenu() { const mainmenu = { - '': { 'title': 'Settings' }, + '': { 'title': /*LANG*/'Settings' }, '< Back': ()=>load(), /*LANG*/'Apps': ()=>showAppSettingsMenu(), /*LANG*/'System': ()=>showSystemMenu(), @@ -78,7 +78,7 @@ function showMainMenu() { function showSystemMenu() { const mainmenu = { - '': { 'title': 'System' }, + '': { 'title': /*LANG*/'System' }, '< Back': ()=>showMainMenu(), /*LANG*/'Theme': ()=>showThemeMenu(), /*LANG*/'LCD': ()=>showLCDMenu(), @@ -122,7 +122,7 @@ function showAlertsMenu() { } const mainmenu = { - '': { 'title': 'Alerts' }, + '': { 'title': /*LANG*/'Alerts' }, '< Back': ()=>showMainMenu(), /*LANG*/'Beep': beepMenuItem, /*LANG*/'Vibration': { @@ -159,8 +159,8 @@ function showBLEMenu() { E.showMenu({ '': { 'title': 'Bluetooth' }, '< Back': ()=>showMainMenu(), - 'Make Connectable': ()=>makeConnectable(), - 'BLE': { + /*LANG*/'Make Connectable': ()=>makeConnectable(), + /*LANG*/'BLE': { value: settings.ble, format: boolFormat, onchange: () => { @@ -168,7 +168,7 @@ function showBLEMenu() { updateSettings(); } }, - 'Programmable': { + /*LANG*/'Programmable': { value: settings.blerepl, format: boolFormat, onchange: () => { @@ -176,7 +176,7 @@ function showBLEMenu() { updateSettings(); } }, - 'HID': { + /*LANG*/'HID': { value: Math.max(0,0 | hidV.indexOf(settings.HID)), min: 0, max: 3, format: v => hidN[v], @@ -185,11 +185,11 @@ function showBLEMenu() { updateSettings(); } }, - 'Passkey BETA': { + /*LANG*/'Passkey BETA': { value: settings.passkey?settings.passkey:"none", onchange: () => setTimeout(showPasskeyMenu) // graphical_menu redraws after the call }, - 'Whitelist': { + /*LANG*/'Whitelist': { value: settings.whitelist?(settings.whitelist.length+" devs"):"off", onchange: () => setTimeout(showWhitelistMenu) // graphical_menu redraws after the call } @@ -213,7 +213,7 @@ function showThemeMenu() { var m = E.showMenu({ '':{title:'Theme'}, '< Back': ()=>showSystemMenu(), - 'Dark BW': ()=>{ + /*LANG*/'Dark BW': ()=>{ upd({ fg:cl("#fff"), bg:cl("#000"), fg2:cl("#0ff"), bg2:cl("#000"), @@ -221,7 +221,7 @@ function showThemeMenu() { dark:true }); }, - 'Light BW': ()=>{ + /*LANG*/'Light BW': ()=>{ upd({ fg:cl("#000"), bg:cl("#fff"), fg2:cl("#000"), bg2:cl("#cff"), @@ -229,7 +229,7 @@ function showThemeMenu() { dark:false }); }, - 'Customize': ()=>showCustomThemeMenu(), + /*LANG*/'Customize': ()=>showCustomThemeMenu(), }); function showCustomThemeMenu() { @@ -261,9 +261,9 @@ function showThemeMenu() { "< Back": () => showThemeMenu() }; const labels = { - fg: 'Foreground', bg: 'Background', - fg2: 'Foreground 2', bg2: 'Background 2', - fgH: 'Highlight FG', bgH: 'Highlight BG', + fg: /*LANG*/'Foreground', bg: /*LANG*/'Background', + fg2: /*LANG*/'Foreground 2', bg2: /*LANG*/'Background 2', + fgH: /*LANG*/'Highlight FG', bgH: /*LANG*/'Highlight BG', }; ["fg", "bg", "fg2", "bg2", "fgH", "bgH"].forEach(t => { menu[labels[t]] = { @@ -292,7 +292,7 @@ function showThemeMenu() { function showPasskeyMenu() { var menu = { "< Back" : ()=>showBLEMenu(), - "Disable" : () => { + /*LANG*/"Disable" : () => { settings.passkey = undefined; updateSettings(); showBLEMenu(); @@ -320,7 +320,7 @@ function showPasskeyMenu() { function showWhitelistMenu() { var menu = { "< Back" : ()=>showBLEMenu(), - "Disable" : () => { + /*LANG*/"Disable" : () => { settings.whitelist = undefined; updateSettings(); showBLEMenu(); @@ -328,7 +328,7 @@ function showWhitelistMenu() { }; if (settings.whitelist) settings.whitelist.forEach(function(d){ menu[d.substr(0,17)] = function() { - E.showPrompt('Remove\n'+d).then((v) => { + E.showPrompt(/*LANG*/'Remove\n'+d).then((v) => { if (v) { settings.whitelist.splice(settings.whitelist.indexOf(d),1); updateSettings(); @@ -337,8 +337,8 @@ function showWhitelistMenu() { }); } }); - menu['Add Device']=function() { - E.showAlert("Connect device\nto add to\nwhitelist","Whitelist").then(function() { + menu[/*LANG*/'Add Device']=function() { + E.showAlert(/*LANG*/"Connect device\nto add to\nwhitelist",/*LANG*/"Whitelist").then(function() { NRF.removeAllListeners('connect'); showWhitelistMenu(); }); @@ -358,7 +358,7 @@ function showLCDMenu() { const lcdMenu = { '': { 'title': 'LCD' }, '< Back': ()=>showSystemMenu(), - 'LCD Brightness': { + /*LANG*/'LCD Brightness': { value: settings.brightness, min: 0.1, max: 1, @@ -369,7 +369,7 @@ function showLCDMenu() { Bangle.setLCDBrightness(settings.brightness); } }, - 'LCD Timeout': { + /*LANG*/'LCD Timeout': { value: settings.timeout, min: 0, max: 60, @@ -380,7 +380,7 @@ function showLCDMenu() { Bangle.setLCDTimeout(settings.timeout); } }, - 'Wake on BTN1': { + /*LANG*/'Wake on BTN1': { value: settings.options.wakeOnBTN1, format: boolFormat, onchange: () => { @@ -391,7 +391,7 @@ function showLCDMenu() { }; if (!BANGLEJS2) Object.assign(lcdMenu, { - 'Wake on BTN2': { + /*LANG*/'Wake on BTN2': { value: settings.options.wakeOnBTN2, format: boolFormat, onchange: () => { @@ -399,7 +399,7 @@ function showLCDMenu() { updateOptions(); } }, - 'Wake on BTN3': { + /*LANG*/'Wake on BTN3': { value: settings.options.wakeOnBTN3, format: boolFormat, onchange: () => { @@ -408,7 +408,7 @@ function showLCDMenu() { } }}); Object.assign(lcdMenu, { - 'Wake on FaceUp': { + /*LANG*/'Wake on FaceUp': { value: settings.options.wakeOnFaceUp, format: boolFormat, onchange: () => { @@ -416,7 +416,7 @@ function showLCDMenu() { updateOptions(); } }, - 'Wake on Touch': { + /*LANG*/'Wake on Touch': { value: settings.options.wakeOnTouch, format: boolFormat, onchange: () => { @@ -424,7 +424,7 @@ function showLCDMenu() { updateOptions(); } }, - 'Wake on Twist': { + /*LANG*/'Wake on Twist': { value: settings.options.wakeOnTwist, format: boolFormat, onchange: () => { @@ -432,7 +432,7 @@ function showLCDMenu() { updateOptions(); } }, - 'Twist Threshold': { + /*LANG*/'Twist Threshold': { value: internalToG(settings.options.twistThreshold), min: -0.5, max: 0.5, @@ -442,7 +442,7 @@ function showLCDMenu() { updateOptions(); } }, - 'Twist Max Y': { + /*LANG*/'Twist Max Y': { value: settings.options.twistMaxY, min: -1500, max: 1500, @@ -452,7 +452,7 @@ function showLCDMenu() { updateOptions(); } }, - 'Twist Timeout': { + /*LANG*/'Twist Timeout': { value: settings.options.twistTimeout, min: 0, max: 2000, @@ -468,9 +468,9 @@ function showLCDMenu() { function showLocaleMenu() { const localemenu = { - '': { 'title': 'Locale' }, + '': { 'title': /*LANG*/'Locale' }, '< Back': ()=>showSystemMenu(), - 'Time Zone': { + /*LANG*/'Time Zone': { value: settings.timezone, min: -11, max: 13, @@ -480,7 +480,7 @@ function showLocaleMenu() { updateSettings(); } }, - 'Clock Style': { + /*LANG*/'Clock Style': { value: !!settings["12hour"], format: v => v ? "12hr" : "24hr", onchange: v => { @@ -494,29 +494,29 @@ function showLocaleMenu() { function showUtilMenu() { var menu = { - '': { 'title': 'Utilities' }, + '': { 'title': /*LANG*/'Utilities' }, '< Back': ()=>showMainMenu(), - 'Debug Info': { + /*LANG*/'Debug Info': { value: E.clip(0|settings.log,0,2), min: 0, max: 2, - format: v => ["Hide","Show","Log"][E.clip(0|v,0,2)], + format: v => [/*LANG*/"Hide",/*LANG*/"Show",/*LANG*/"Log"][E.clip(0|v,0,2)], onchange: v => { settings.log = v; updateSettings(); } }, - 'Compact Storage': () => { - E.showMessage("Compacting...\nTakes approx\n1 minute",{title:"Storage"}); + /*LANG*/'Compact Storage': () => { + E.showMessage(/*LANG*/"Compacting...\nTakes approx\n1 minute",{title:/*LANG*/"Storage"}); require("Storage").compact(); showUtilMenu(); }, - 'Rewrite Settings': () => { + /*LANG*/'Rewrite Settings': () => { require("Storage").write(".boot0","eval(require('Storage').read('bootupdate.js'));"); load("setting.app.js"); }, - 'Flatten Battery': () => { - E.showMessage('Flattening battery - this can take hours.\nLong-press button to cancel.'); + /*LANG*/'Flatten Battery': () => { + E.showMessage(/*LANG*/'Flattening battery - this can take hours.\nLong-press button to cancel.'); Bangle.setLCDTimeout(0); Bangle.setLCDPower(1); if (Bangle.setGPSPower) Bangle.setGPSPower(1,"flat"); @@ -528,8 +528,8 @@ function showUtilMenu() { var i=1000;while (i--); }, 1); }, - 'Reset Settings': () => { - E.showPrompt('Reset to Defaults?',{title:"Settings"}).then((v) => { + /*LANG*/'Reset Settings': () => { + E.showPrompt(/*LANG*/'Reset to Defaults?',{title:/*LANG*/"Settings"}).then((v) => { if (v) { E.showMessage('Resetting'); resetSettings(); @@ -540,8 +540,8 @@ function showUtilMenu() { /*LANG*/'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() } }; if (Bangle.factoryReset) { - menu['Factory Reset'] = ()=>{ - E.showPrompt('This will remove everything!',{title:"Factory Reset"}).then((v) => { + menu[/*LANG*/'Factory Reset'] = ()=>{ + E.showPrompt(/*LANG*/'This will remove everything!',{title:/*LANG*/"Factory Reset"}).then((v) => { if (v) { E.showMessage(); Terminal.setConsole(); @@ -558,7 +558,7 @@ function makeConnectable() { try { NRF.wake(); } catch (e) { } Bluetooth.setConsole(1); var name = "Bangle.js " + NRF.getAddress().substr(-5).replace(":", ""); - E.showPrompt(name + "\nStay Connectable?", { title: "Connectable" }).then(r => { + E.showPrompt(name + /*LANG*/"\nStay Connectable?", { title: /*LANG*/"Connectable" }).then(r => { if (settings.ble != r) { settings.ble = r; updateSettings(); @@ -574,7 +574,7 @@ function showClockMenu() { .sort((a, b) => a.sortorder - b.sortorder); const clockMenu = { '': { - 'title': 'Select Clock', + 'title': /*LANG*/'Select Clock', }, '< Back': ()=>showSystemMenu(), }; @@ -592,7 +592,7 @@ function showClockMenu() { }; }); if (clockApps.length === 0) { - clockMenu["No Clocks Found"] = () => { }; + clockMenu[/*LANG*/"No Clocks Found"] = () => { }; } return E.showMenu(clockMenu); } @@ -600,47 +600,47 @@ function showClockMenu() { function showSetTimeMenu() { d = new Date(); const timemenu = { - '': { 'title': 'Set Time' }, + '': { 'title': /*LANG*/'Set Time' }, '< Back': function () { setTime(d.getTime() / 1000); showSystemMenu(); }, - 'Hour': { + /*LANG*/'Hour': { value: d.getHours(), onchange: function (v) { this.value = (v+24)%24; d.setHours(this.value); } }, - 'Minute': { + /*LANG*/'Minute': { value: d.getMinutes(), onchange: function (v) { this.value = (v+60)%60; d.setMinutes(this.value); } }, - 'Second': { + /*LANG*/'Second': { value: d.getSeconds(), onchange: function (v) { this.value = (v+60)%60; d.setSeconds(this.value); } }, - 'Date': { + /*LANG*/'Date': { value: d.getDate(), onchange: function (v) { this.value = ((v+30)%31)+1; d.setDate(this.value); } }, - 'Month': { + /*LANG*/'Month': { value: d.getMonth() + 1, onchange: function (v) { this.value = ((v+11)%12)+1; d.setMonth(this.value - 1); } }, - 'Year': { + /*LANG*/'Year': { value: d.getFullYear(), min: 2019, max: 2100, @@ -654,7 +654,7 @@ function showSetTimeMenu() { function showAppSettingsMenu() { let appmenu = { - '': { 'title': 'App Settings' }, + '': { 'title': /*LANG*/'App Settings' }, '< Back': ()=>showMainMenu(), } const apps = storage.list(/\.settings\.js$/) @@ -671,7 +671,7 @@ function showAppSettingsMenu() { return 0; }) if (apps.length === 0) { - appmenu['No app has settings'] = () => { }; + appmenu[/*LANG*/'No app has settings'] = () => { }; } apps.forEach(function (app) { appmenu[app.name] = () => { showAppSettings(app) }; @@ -688,17 +688,17 @@ function showAppSettings(app) { appSettings = eval(appSettings); } catch (e) { console.log(`${app.name} settings error:`, e) - return showError('Error in settings'); + return showError(/*LANG*/'Error in settings'); } if (typeof appSettings !== "function") { - return showError('Invalid settings'); + return showError(/*LANG*/'Invalid settings'); } try { // pass showAppSettingsMenu as "back" argument appSettings(()=>showAppSettingsMenu()); } catch (e) { console.log(`${app.name} settings error:`, e) - return showError('Error in settings'); + return showError(/*LANG*/'Error in settings'); } } From fc8e089fbe43621862b00df5b2a014d61f0bfca9 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 12 Jan 2022 11:05:27 +0000 Subject: [PATCH 21/24] improve sanity checking - no false warnings for apps files with `supports` field --- bin/sanitycheck.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js index e50256fb6..98fd1275f 100755 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -58,6 +58,7 @@ const APP_KEYS = [ ]; const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite', 'supports']; const DATA_KEYS = ['name', 'wildcard', 'storageFile', 'url', 'content', 'evaluate']; +const SUPPORTS_DEVICES = ["BANGLEJS","BANGLEJS2"]; // device IDs allowed for 'supports' const FORBIDDEN_FILE_NAME_CHARS = /[,;]/; // used as separators in appid.info const VALID_DUPLICATES = [ '.tfmodel', '.tfnames' ]; const GRANDFATHERED_ICONS = ["s7clk", "snek", "astral", "alpinenav", "slomoclock", "arrow", "pebble", "rebble"]; @@ -90,7 +91,7 @@ apps.forEach((app,appIdx) => { if (!Array.isArray(app.supports)) ERROR(`App ${app.id} has no 'supports' field or it's not an array`); else { app.supports.forEach(dev => { - if (!["BANGLEJS","BANGLEJS2"].includes(dev)) + if (!SUPPORTS_DEVICES.includes(dev)) ERROR(`App ${app.id} has unknown device in 'supports' field - ${dev}`); }); } @@ -140,6 +141,13 @@ apps.forEach((app,appIdx) => { if (char) ERROR(`App ${app.id} storage file ${file.name} contains invalid character "${char[0]}"`) if (fileNames.includes(file.name) && !file.supports) // assume that there aren't duplicates if 'supports' is set ERROR(`App ${app.id} file ${file.name} is a duplicate`); + if (file.supports && !Array.isArray(file.supports)) + ERROR(`App ${app.id} file ${file.name} supports field must be an array`); + if (file.supports) + file.supports.forEach(dev => { + if (!SUPPORTS_DEVICES.includes(dev)) + ERROR(`App ${app.id} file ${file.name} has unknown device in 'supports' field - ${dev}`); + }); fileNames.push(file.name); allFiles.push({app: app.id, file: file.name}); if (file.url) if (!fs.existsSync(appDir+file.url)) ERROR(`App ${app.id} file ${file.url} doesn't exist`); @@ -271,7 +279,8 @@ while(fileA=allFiles.pop()) { if (globA.test(nameB)||globB.test(nameA)) { if (isGlob(nameA)||isGlob(nameB)) ERROR(`App ${fileB.app} ${typeB} file ${nameB} matches app ${fileA.app} ${typeB} file ${nameA}`) - else WARN(`App ${fileB.app} ${typeB} file ${nameB} is also listed as ${typeA} file for app ${fileA.app}`) + else if (fileA.app != fileB.app) + WARN(`App ${fileB.app} ${typeB} file ${nameB} is also listed as ${typeA} file for app ${fileA.app}`) } }) } From 0b742e4ff18c6d650ec1a6ef375e6d53a32f9b98 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 12 Jan 2022 12:50:44 +0000 Subject: [PATCH 22/24] assistedgps 0.02: Update to work with Bangle.js 2 --- apps.json | 7 +-- apps/assistedgps/ChangeLog | 1 + apps/assistedgps/custom.html | 90 ++++++++++++++++++++++++++---------- 3 files changed, 71 insertions(+), 27 deletions(-) diff --git a/apps.json b/apps.json index 5225f9256..27efe3663 100644 --- a/apps.json +++ b/apps.json @@ -1536,13 +1536,14 @@ { "id": "assistedgps", "name": "Assisted GPS Update (AGPS)", - "version": "0.01", - "description": "Downloads assisted GPS (AGPS) data to Bangle.js 1 for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.", + "version": "0.02", + "description": "Downloads assisted GPS (AGPS) data to Bangle.js 1 or 2 for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.", "icon": "app.png", "type": "RAM", "tags": "tool,outdoors,agps", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "custom": "custom.html", + "customConnect": true, "storage": [] }, { diff --git a/apps/assistedgps/ChangeLog b/apps/assistedgps/ChangeLog index 5560f00bc..4ec2c8f71 100644 --- a/apps/assistedgps/ChangeLog +++ b/apps/assistedgps/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Update to work with Bangle.js 2 diff --git a/apps/assistedgps/custom.html b/apps/assistedgps/custom.html index 139c232af..e67ac93af 100644 --- a/apps/assistedgps/custom.html +++ b/apps/assistedgps/custom.html @@ -8,34 +8,47 @@

GPS can take a long time (~5 minutes) to get an accurate position the first time it is used. AGPS uploads a few hints to the GPS receiver about satellite positions that allow it to get a faster, more accurate fix - however they are only valid for a short period of time.

-

You can upload data that covers a longer period of time, but the upload will take longer.

-
- - - - - + -

Click

+ + From 5ea2cf8b5601133672bf55ea132477049fcd1b11 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 12 Jan 2022 13:37:39 +0000 Subject: [PATCH 23/24] settings 0.41: Stop users disabling all wake-up methods and locking themselves out (fix #1272) --- apps.json | 2 +- apps/setting/ChangeLog | 1 + apps/setting/README.md | 3 +++ apps/setting/settings.js | 12 +++++++++++- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index 43c7a04a1..93b1cf44e 100644 --- a/apps.json +++ b/apps.json @@ -169,7 +169,7 @@ { "id": "setting", "name": "Settings", - "version": "0.40", + "version": "0.41", "description": "A menu for setting up Bangle.js", "icon": "settings.png", "tags": "tool,system", diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index 4d9881613..77c7b2040 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -43,3 +43,4 @@ 0.38: Restructed menus as per forum discussion 0.39: Fix misbehaving debug info option 0.40: Moved off into Utils, put System after Apps +0.41: Stop users disabling all wake-up methods and locking themselves out (fix #1272) diff --git a/apps/setting/README.md b/apps/setting/README.md index 305c0b610..42e3939fb 100644 --- a/apps/setting/README.md +++ b/apps/setting/README.md @@ -31,9 +31,12 @@ This is Bangle.js's settings menu * **LCD Brightness** set how bright the LCD is. Due to hardware limitations in the LCD backlight, you may notice flicker if the LCD is not at 100% brightness. * **LCD Timeout** how long should the LCD stay on for if no activity is detected. 0=stay on forever * **Wake on X** should the given activity wake up the Bangle.js LCD? + * On Bangle.js 2 when locked the touchscreen is turned off to save power. Because of this, + `Wake on Touch` actually uses the accelerometer, and you need to actually tap the display to wake Bangle.js. * **Twist X** these options adjust the sensitivity of `Wake on Twist` to ensure Bangle.js wakes up with just the right amount of wrist movement. + ## Quiet Mode Quiet Mode is a hint to apps and widgets that you do not want to be disturbed. diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 514adf1dd..a32b83d3c 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -11,8 +11,18 @@ function updateSettings() { } function updateOptions() { + var o = settings.options; + // Check to make sure nobody disabled all wakeups and locked themselves out! + if (BANGLEJS2) { + if (!(o.wakeOnBTN1||o.wakeOnFaceUp||o.wakeOnTouch||o.wakeOnTwist)) { + o.wakeOnBTN1 = true; + } + } else { + if (!(o.wakeOnBTN1||o.wakeOnBTN2||o.wakeOnBTN3||o.wakeOnFaceUp||o.wakeOnTouch||o.wakeOnTwist)) + o.wakeOnBTN2 = true; + } updateSettings(); - Bangle.setOptions(settings.options) + Bangle.setOptions(o) } function gToInternal(g) { From 55c0a37fc323fb7725d9ccdc5b558c5916f0227a Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 12 Jan 2022 13:37:45 +0000 Subject: [PATCH 24/24] comments --- apps/assistedgps/custom.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/assistedgps/custom.html b/apps/assistedgps/custom.html index e67ac93af..fa11b696c 100644 --- a/apps/assistedgps/custom.html +++ b/apps/assistedgps/custom.html @@ -118,6 +118,10 @@ // Disable BDS, use just GPS (supposedly improve lock time) js += `\x10Serial1.println("${CASIC_CHECKSUM("$PCAS04,1")}")\n`; // set GPS-only mode + // What about: + // NAV-TIMEUTC (0x01 0x10) + // NAV-PV (0x01 0x03) + // or AGPS.zip uses AID-INI (0x0B 0x01) } for (var i=0;i