From 501c7e79cfe6916154a48a8b086ef589e682c893 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Wed, 2 Mar 2022 16:55:38 +0800 Subject: [PATCH 01/64] Update app.js Support some invalid base32 characters. --- apps/authentiwatch/app.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index 73b8bdeea..69890c90c 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -32,9 +32,12 @@ if (settings.tokens) tokens = settings.tokens; /* v0.03+ settings */ function b32decode(seedstr) { // RFC4648 - var i, buf = 0, bitcount = 0, retstr = ""; - for (i in seedstr) { - var c = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".indexOf(seedstr.charAt(i).toUpperCase(), 0); + var buf = 0, bitcount = 0, retstr = ""; + for (var c of seedstr.toUpperCase()) { + if (c=='0')c='O'; + if (c=='1')c='I'; + if (c=='8')c='B'; + c = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".indexOf(c); if (c != -1) { buf <<= 5; buf |= c; @@ -47,7 +50,7 @@ function b32decode(seedstr) { } } var retbuf = new Uint8Array(retstr.length); - for (i in retstr) { + for (var i in retstr) { retbuf[i] = retstr.charCodeAt(i); } return retbuf; From cb0aec47a43919235df08c4e2472c5dd64df5183 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Wed, 2 Mar 2022 16:57:48 +0800 Subject: [PATCH 02/64] Update app.js Add language translation tags --- apps/authentiwatch/app.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index 69890c90c..e9d00a265 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -8,9 +8,9 @@ const algos = { "SHA256":{sha:crypto.SHA256,retsz:32,blksz:64 }, "SHA1" :{sha:crypto.SHA1 ,retsz:20,blksz:64 }, }; -const calculating = "Calculating"; -const notokens = "No tokens"; -const notsupported = "Not supported"; +const calculating = /*LANG*/"Calculating"; +const notokens = /*LANG*/"No tokens"; +const notsupported = /*LANG*/"Not supported"; // sample settings: // {tokens:[{"algorithm":"SHA1","digits":6,"period":30,"issuer":"","account":"","secret":"Bbb","label":"Aaa"}],misc:{}} From fe8345ed15e5a1202dd4f2a027ede2705969f849 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Wed, 2 Mar 2022 22:44:13 +0800 Subject: [PATCH 03/64] Update interface.html Refactoring --- apps/authentiwatch/interface.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/authentiwatch/interface.html b/apps/authentiwatch/interface.html index 5ee32fd8e..bfc0e55d1 100644 --- a/apps/authentiwatch/interface.html +++ b/apps/authentiwatch/interface.html @@ -54,9 +54,9 @@ var tokens = settings.tokens; */ function base32clean(val, nows) { var ret = val.replaceAll(/\s+/g, ' '); - ret = ret.replaceAll(/0/g, 'O'); - ret = ret.replaceAll(/1/g, 'I'); - ret = ret.replaceAll(/8/g, 'B'); + ret = ret.replaceAll('0', 'O'); + ret = ret.replaceAll('1', 'I'); + ret = ret.replaceAll('8', 'B'); ret = ret.replaceAll(/[^A-Za-z2-7 ]/g, ''); if (nows) { ret = ret.replaceAll(/\s+/g, ''); @@ -81,9 +81,9 @@ function b32encode(str) { function b32decode(seedstr) { // RFC4648 - var i, buf = 0, bitcount = 0, ret = ''; - for (i in seedstr) { - var c = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'.indexOf(seedstr.charAt(i).toUpperCase(), 0); + var buf = 0, bitcount = 0, ret = ''; + for (var c of seedstr.toUpperCase()) { + c = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'.indexOf(c); if (c != -1) { buf <<= 5; buf |= c; @@ -487,7 +487,7 @@ function startScan(handler,cancel) { document.body.className = 'scanning'; navigator.mediaDevices .getUserMedia({video:{facingMode:'environment'}}) - .then(function(stream){ + .then(stream => { scanning=true; video.setAttribute('playsinline',true); video.srcObject = stream; From 7810dd2cfc01145b58998886b13ce78893219852 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Mon, 7 Mar 2022 23:33:23 +0800 Subject: [PATCH 04/64] Update app.js Bangle 2: Improve drag responsiveness and exit on button press --- apps/authentiwatch/app.js | 48 +++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index e9d00a265..b8181e4a2 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -34,9 +34,9 @@ function b32decode(seedstr) { // RFC4648 var buf = 0, bitcount = 0, retstr = ""; for (var c of seedstr.toUpperCase()) { - if (c=='0')c='O'; - if (c=='1')c='I'; - if (c=='8')c='B'; + if (c == '0') c = 'O'; + if (c == '1') c = 'I'; + if (c == '8') c = 'B'; c = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".indexOf(c); if (c != -1) { buf <<= 5; @@ -166,11 +166,7 @@ function drawToken(id, r) { } while (g.stringWidth(state.otp) > (r.w - adj)); g.drawString(state.otp, (x1 + adj + x2) / 2, y1 + tokenextraheight, false); } - // shaded lines top and bottom - g.setColor(0.5, 0.5, 0.5) - .drawLine(x1, y1, x2, y1) - .drawLine(x1, y2, x2, y2) - .setClipRect(0, 0, g.getWidth(), g.getHeight()); + g.setClipRect(0, 0, g.getWidth(), g.getHeight()); } function draw() { @@ -212,7 +208,7 @@ function draw() { if (id == state.curtoken && (tokens[id].period <= 0 || state.nextTime != 0)) { drewcur = true; } - id += 1; + id++; y += tokenheight; } if (drewcur) { @@ -273,11 +269,33 @@ function onTouch(zone, e) { } function onDrag(e) { - if (e.x > g.getWidth() || e.y > g.getHeight()) return; - if (e.dx == 0 && e.dy == 0) return; - var newy = Math.min(state.listy - e.dy, tokens.length * tokenheight - Bangle.appRect.h); - state.listy = Math.max(0, newy); - draw(); + if (e.b != 0 && e.x < g.getWidth() && e.y < g.getHeight() && e.dy != 0) { + var y = Math.max(0, Math.min(state.listy - e.dy, tokens.length * tokenheight - Bangle.appRect.h)); + if (state.listy != y) { + var id, dy = state.listy - y; + state.listy = y; + g.setClipRect(Bangle.appRect.x,Bangle.appRect.y,Bangle.appRect.x2,Bangle.appRect.y2) + .scroll(0, dy); + if (dy > 0) { + id = Math.floor((state.listy + dy) / tokenheight); + y = id * tokenheight + Bangle.appRect.y - state.listy; + do { + drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:tokenheight}); + id--; + y -= tokenheight; + } while (y > 0); + } + if (dy < 0) { + id = Math.floor((state.listy + dy + Bangle.appRect.h) / tokenheight); + y = id * tokenheight + Bangle.appRect.y - state.listy; + while (y < Bangle.appRect.y2) { + drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:tokenheight}); + id++; + y += tokenheight; + } + } + } + } } function onSwipe(e) { @@ -332,6 +350,8 @@ if (typeof BTN2 == 'number') { setWatch(function(){bangle1Btn(-1);}, BTN1, {edge:"rising" , debounce:50, repeat:true}); setWatch(function(){exitApp(); }, BTN2, {edge:"falling", debounce:50}); setWatch(function(){bangle1Btn( 1);}, BTN3, {edge:"rising" , debounce:50, repeat:true}); +} else { + setWatch(function(){exitApp(); }, BTN1, {edge:"falling", debounce:50}); } Bangle.loadWidgets(); From 929920fcb6708a715b0961e830c03cca7ef8a358 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Mon, 7 Mar 2022 23:33:53 +0800 Subject: [PATCH 05/64] Update ChangeLog 0.07 --- apps/authentiwatch/ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/authentiwatch/ChangeLog b/apps/authentiwatch/ChangeLog index bb2945db4..655916170 100644 --- a/apps/authentiwatch/ChangeLog +++ b/apps/authentiwatch/ChangeLog @@ -4,3 +4,4 @@ 0.04: Fix tapping at very bottom of list, exit on inactivity 0.05: Add support for bulk importing and exporting tokens 0.06: Add spaces to codes for improved readability (thanks @BartS23) +0.07: Bangle 2: Improve drag responsiveness and exit on button press From 51744f842df0484bab0b72bc3bcbe8f089e8c8e3 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Mon, 7 Mar 2022 23:34:18 +0800 Subject: [PATCH 06/64] Update metadata.json 0.07 --- apps/authentiwatch/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/authentiwatch/metadata.json b/apps/authentiwatch/metadata.json index b4ed34a12..36e1ea34d 100644 --- a/apps/authentiwatch/metadata.json +++ b/apps/authentiwatch/metadata.json @@ -4,7 +4,7 @@ "shortName": "AuthWatch", "icon": "app.png", "screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot4.png"}], - "version": "0.06", + "version": "0.07", "description": "Google Authenticator compatible tool.", "tags": "tool", "interface": "interface.html", From 915ad6394795ea25e28d05480095ad23247836ec Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Tue, 8 Mar 2022 15:58:46 +0800 Subject: [PATCH 07/64] Update interface.html Use push instead of concat. --- apps/authentiwatch/interface.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/authentiwatch/interface.html b/apps/authentiwatch/interface.html index bfc0e55d1..7d567d34f 100644 --- a/apps/authentiwatch/interface.html +++ b/apps/authentiwatch/interface.html @@ -405,7 +405,7 @@ class proto3decoder { constructor(str) { this.buf = []; for (let i in str) { - this.buf = this.buf.concat(str.charCodeAt(i)); + this.buf.push(str.charCodeAt(i)); } } getVarint() { From d7def5d5e1c95fcd456a406ef8bda3da183e9f34 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Tue, 8 Mar 2022 21:52:57 +0800 Subject: [PATCH 08/64] Update app.js Cache font size calculations --- apps/authentiwatch/app.js | 45 +++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index b8181e4a2..8a8aeb962 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -18,18 +18,6 @@ var settings = require("Storage").readJSON("authentiwatch.json", true) || {token if (settings.data ) tokens = settings.data ; /* v0.02 settings */ if (settings.tokens) tokens = settings.tokens; /* v0.03+ settings */ -// QR Code Text -// -// Example: -// -// otpauth://totp/${url}:AA_${algorithm}_${digits}dig_${period}s@${url}?algorithm=${algorithm}&digits=${digits}&issuer=${url}&period=${period}&secret=${secret} -// -// ${algorithm} : one of SHA1 / SHA256 / SHA512 -// ${digits} : one of 6 / 8 -// ${period} : one of 30 / 60 -// ${url} : a domain name "example.com" -// ${secret} : the seed code - function b32decode(seedstr) { // RFC4648 var buf = 0, bitcount = 0, retstr = ""; @@ -75,6 +63,10 @@ function do_hmac(key, message, algo) { var v = new DataView(ret, ret[ret.length - 1] & 0x0F, 4); return v.getUint32(0) & 0x7FFFFFFF; } +function formatOtp(otp, digits) { + var re = (digits % 3 == 0 || (digits % 3 >= digits % 4 && digits % 4 != 0)) ? "" : "."; + return otp.replace(new RegExp("(..." + re + ")", "g"), "$1 ").trim(); +} function hotp(d, token, dohmac) { var tick; if (token.period > 0) { @@ -98,8 +90,7 @@ function hotp(d, token, dohmac) { ret = "0" + ret; } // add a space after every 3rd or 4th digit - var re = (token.digits % 3 == 0 || (token.digits % 3 >= token.digits % 4 && token.digits % 4 != 0)) ? "" : "."; - ret = ret.replace(new RegExp("(..." + re + ")", "g"), "$1 ").trim(); + ret = formatOtp(ret, token.digits); } catch(err) { ret = notsupported; } @@ -107,6 +98,7 @@ function hotp(d, token, dohmac) { return {hotp:ret, next:((token.period > 0) ? ((tick + 1) * token.period * 1000) : d.getTime() + 30000)}; } +var fontsz_cache = {}; var state = { listy: 0, prevcur:0, @@ -117,12 +109,25 @@ var state = { hide:0 }; +function size_font(id, txt, w) { + var sz = fontsz_cache[id]; + if (sz) { + g.setFont("Vector", sz); + } else { + sz = tokendigitsheight; + do { + g.setFont("Vector", sz--); + } while (g.stringWidth(txt) > w); + fontsz_cache[id] = sz + 1; + } +} + function drawToken(id, r) { var x1 = r.x; var y1 = r.y; var x2 = r.x + r.w - 1; var y2 = r.y + r.h - 1; - var adj, lbl, sz; + var adj, lbl; g.setClipRect(Math.max(x1, Bangle.appRect.x ), Math.max(y1, Bangle.appRect.y ), Math.min(x2, Bangle.appRect.x2), Math.min(y2, Bangle.appRect.y2)); lbl = tokens[id].label.substr(0, 10); @@ -137,10 +142,7 @@ function drawToken(id, r) { } else { g.setColor(g.theme.fg) .setBgColor(g.theme.bg); - sz = tokendigitsheight; - do { - g.setFont("Vector", sz--); - } while (g.stringWidth(lbl) > r.w); + size_font("l" + id, lbl, r.w); // center in box g.setFontAlign(0, 0, 0); adj = (y1 + y2) / 2; @@ -160,10 +162,7 @@ function drawToken(id, r) { adj = 12; } // digits just below label - sz = tokendigitsheight; - do { - g.setFont("Vector", sz--); - } while (g.stringWidth(state.otp) > (r.w - adj)); + size_font("d" + id, state.otp, r.w - adj); g.drawString(state.otp, (x1 + adj + x2) / 2, y1 + tokenextraheight, false); } g.setClipRect(0, 0, g.getWidth(), g.getHeight()); From ebdea05bff9df19ea85ed7a636967b27f740a21f Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Wed, 9 Mar 2022 21:53:24 +0800 Subject: [PATCH 09/64] Update app.js Improve code style --- apps/authentiwatch/app.js | 93 ++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index 8a8aeb962..fb130969f 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -1,6 +1,6 @@ -const tokenextraheight = 16; -var tokendigitsheight = 30; -var tokenheight = tokendigitsheight + tokenextraheight; +const TOKEN_EXTRA_HEIGHT = 16; +var TOKEN_DIGITS_HEIGHT = 30; +var TOKEN_HEIGHT = TOKEN_DIGITS_HEIGHT + TOKEN_EXTRA_HEIGHT; // Hash functions const crypto = require("crypto"); const algos = { @@ -8,9 +8,9 @@ const algos = { "SHA256":{sha:crypto.SHA256,retsz:32,blksz:64 }, "SHA1" :{sha:crypto.SHA1 ,retsz:20,blksz:64 }, }; -const calculating = /*LANG*/"Calculating"; -const notokens = /*LANG*/"No tokens"; -const notsupported = /*LANG*/"Not supported"; +const CALCULATING = /*LANG*/"Calculating"; +const NO_TOKENS = /*LANG*/"No tokens"; +const NOT_SUPPORTED = /*LANG*/"Not supported"; // sample settings: // {tokens:[{"algorithm":"SHA1","digits":6,"period":30,"issuer":"","account":"","secret":"Bbb","label":"Aaa"}],misc:{}} @@ -43,7 +43,8 @@ function b32decode(seedstr) { } return retbuf; } -function do_hmac(key, message, algo) { + +function doHmac(key, message, algo) { var a = algos[algo]; // RFC2104 if (key.length > a.blksz) { @@ -63,11 +64,13 @@ function do_hmac(key, message, algo) { var v = new DataView(ret, ret[ret.length - 1] & 0x0F, 4); return v.getUint32(0) & 0x7FFFFFFF; } + function formatOtp(otp, digits) { var re = (digits % 3 == 0 || (digits % 3 >= digits % 4 && digits % 4 != 0)) ? "" : "."; return otp.replace(new RegExp("(..." + re + ")", "g"), "$1 ").trim(); } -function hotp(d, token, dohmac) { + +function hotp(d, token, calcHmac) { var tick; if (token.period > 0) { // RFC6238 - timed @@ -81,10 +84,10 @@ function hotp(d, token, dohmac) { var v = new DataView(msg.buffer); v.setUint32(0, tick >> 16 >> 16); v.setUint32(4, tick & 0xFFFFFFFF); - var ret = calculating; - if (dohmac) { + var ret = CALCULATING; + if (calcHmac) { try { - var hash = do_hmac(b32decode(token.secret), msg, token.algorithm.toUpperCase()); + var hash = doHmac(b32decode(token.secret), msg, token.algorithm.toUpperCase()); ret = "" + hash % Math.pow(10, token.digits); while (ret.length < token.digits) { ret = "0" + ret; @@ -92,13 +95,13 @@ function hotp(d, token, dohmac) { // add a space after every 3rd or 4th digit ret = formatOtp(ret, token.digits); } catch(err) { - ret = notsupported; + ret = NOT_SUPPORTED; } } return {hotp:ret, next:((token.period > 0) ? ((tick + 1) * token.period * 1000) : d.getTime() + 30000)}; } -var fontsz_cache = {}; +var fontszCache = {}; var state = { listy: 0, prevcur:0, @@ -109,16 +112,16 @@ var state = { hide:0 }; -function size_font(id, txt, w) { - var sz = fontsz_cache[id]; +function sizeFont(id, txt, w) { + var sz = fontszCache[id]; if (sz) { g.setFont("Vector", sz); } else { - sz = tokendigitsheight; + sz = TOKEN_DIGITS_HEIGHT; do { g.setFont("Vector", sz--); } while (g.stringWidth(txt) > w); - fontsz_cache[id] = sz + 1; + fontszCache[id] = sz + 1; } } @@ -135,14 +138,14 @@ function drawToken(id, r) { // current token g.setColor(g.theme.fgH) .setBgColor(g.theme.bgH) - .setFont("Vector", tokenextraheight) + .setFont("Vector", TOKEN_EXTRA_HEIGHT) // center just below top line .setFontAlign(0, -1, 0); adj = y1; } else { g.setColor(g.theme.fg) .setBgColor(g.theme.bg); - size_font("l" + id, lbl, r.w); + sizeFont("l" + id, lbl, r.w); // center in box g.setFontAlign(0, 0, 0); adj = (y1 + y2) / 2; @@ -162,8 +165,8 @@ function drawToken(id, r) { adj = 12; } // digits just below label - size_font("d" + id, state.otp, r.w - adj); - g.drawString(state.otp, (x1 + adj + x2) / 2, y1 + tokenextraheight, false); + sizeFont("d" + id, state.otp, r.w - adj); + g.drawString(state.otp, (x1 + adj + x2) / 2, y1 + TOKEN_EXTRA_HEIGHT, false); } g.setClipRect(0, 0, g.getWidth(), g.getHeight()); } @@ -174,7 +177,7 @@ function draw() { var d = new Date(); if (state.curtoken != -1) { var t = tokens[state.curtoken]; - if (state.otp == calculating) { + if (state.otp == CALCULATING) { state.otp = hotp(d, t, true).hotp; } if (d.getTime() > state.nextTime) { @@ -200,20 +203,20 @@ function draw() { } if (tokens.length > 0) { var drewcur = false; - var id = Math.floor(state.listy / tokenheight); - var y = id * tokenheight + Bangle.appRect.y - state.listy; + var id = Math.floor(state.listy / TOKEN_HEIGHT); + var y = id * TOKEN_HEIGHT + Bangle.appRect.y - state.listy; while (id < tokens.length && y < Bangle.appRect.y2) { - drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:tokenheight}); + drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:TOKEN_HEIGHT}); if (id == state.curtoken && (tokens[id].period <= 0 || state.nextTime != 0)) { drewcur = true; } id++; - y += tokenheight; + y += TOKEN_HEIGHT; } if (drewcur) { // the current token has been drawn - schedule a redraw if (tokens[state.curtoken].period > 0) { - timerdly = (state.otp == calculating) ? 1 : 1000; // timed + timerdly = (state.otp == CALCULATING) ? 1 : 1000; // timed } else { timerdly = state.nexttime - d.getTime(); // counter } @@ -230,9 +233,9 @@ function draw() { state.nexttime = 0; } } else { - g.setFont("Vector", tokendigitsheight) + g.setFont("Vector", TOKEN_DIGITS_HEIGHT) .setFontAlign(0, 0, 0) - .drawString(notokens, Bangle.appRect.x + Bangle.appRect.w / 2, Bangle.appRect.y + Bangle.appRect.h / 2, false); + .drawString(NO_TOKENS, Bangle.appRect.x + Bangle.appRect.w / 2, Bangle.appRect.y + Bangle.appRect.h / 2, false); } if (state.drawtimer) { clearTimeout(state.drawtimer); @@ -242,18 +245,18 @@ function draw() { function onTouch(zone, e) { if (e) { - var id = Math.floor((state.listy + (e.y - Bangle.appRect.y)) / tokenheight); + var id = Math.floor((state.listy + (e.y - Bangle.appRect.y)) / TOKEN_HEIGHT); if (id == state.curtoken || tokens.length == 0 || id >= tokens.length) { id = -1; } if (state.curtoken != id) { if (id != -1) { - var y = id * tokenheight - state.listy; + var y = id * TOKEN_HEIGHT - state.listy; if (y < 0) { state.listy += y; y = 0; } - y += tokenheight; + y += TOKEN_HEIGHT; if (y > Bangle.appRect.h) { state.listy += (y - Bangle.appRect.h); } @@ -269,28 +272,28 @@ function onTouch(zone, e) { function onDrag(e) { if (e.b != 0 && e.x < g.getWidth() && e.y < g.getHeight() && e.dy != 0) { - var y = Math.max(0, Math.min(state.listy - e.dy, tokens.length * tokenheight - Bangle.appRect.h)); + var y = Math.max(0, Math.min(state.listy - e.dy, tokens.length * TOKEN_HEIGHT - Bangle.appRect.h)); if (state.listy != y) { var id, dy = state.listy - y; state.listy = y; g.setClipRect(Bangle.appRect.x,Bangle.appRect.y,Bangle.appRect.x2,Bangle.appRect.y2) .scroll(0, dy); if (dy > 0) { - id = Math.floor((state.listy + dy) / tokenheight); - y = id * tokenheight + Bangle.appRect.y - state.listy; + id = Math.floor((state.listy + dy) / TOKEN_HEIGHT); + y = id * TOKEN_HEIGHT + Bangle.appRect.y - state.listy; do { - drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:tokenheight}); + drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:TOKEN_HEIGHT}); id--; - y -= tokenheight; + y -= TOKEN_HEIGHT; } while (y > 0); } if (dy < 0) { - id = Math.floor((state.listy + dy + Bangle.appRect.h) / tokenheight); - y = id * tokenheight + Bangle.appRect.y - state.listy; + id = Math.floor((state.listy + dy + Bangle.appRect.h) / TOKEN_HEIGHT); + y = id * TOKEN_HEIGHT + Bangle.appRect.y - state.listy; while (y < Bangle.appRect.y2) { - drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:tokenheight}); + drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:TOKEN_HEIGHT}); id++; - y += tokenheight; + y += TOKEN_HEIGHT; } } } @@ -324,12 +327,12 @@ function bangle1Btn(e) { } state.curtoken = Math.max(state.curtoken, 0); state.curtoken = Math.min(state.curtoken, tokens.length - 1); - state.listy = state.curtoken * tokenheight; - state.listy -= (Bangle.appRect.h - tokenheight) / 2; - state.listy = Math.min(state.listy, tokens.length * tokenheight - Bangle.appRect.h); + state.listy = state.curtoken * TOKEN_HEIGHT; + state.listy -= (Bangle.appRect.h - TOKEN_HEIGHT) / 2; + state.listy = Math.min(state.listy, tokens.length * TOKEN_HEIGHT - Bangle.appRect.h); state.listy = Math.max(state.listy, 0); var fakee = {}; - fakee.y = state.curtoken * tokenheight - state.listy + Bangle.appRect.y; + fakee.y = state.curtoken * TOKEN_HEIGHT - state.listy + Bangle.appRect.y; state.curtoken = -1; state.nextTime = 0; onTouch(0, fakee); From f96ba8d6a5df56459ebbcf2249c925c3deae72a3 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Wed, 9 Mar 2022 21:56:52 +0800 Subject: [PATCH 10/64] Add files via upload Update screenshots --- apps/authentiwatch/screenshot1.png | Bin 2708 -> 1595 bytes apps/authentiwatch/screenshot2.png | Bin 2914 -> 1835 bytes apps/authentiwatch/screenshot3.png | Bin 2656 -> 1425 bytes apps/authentiwatch/screenshot4.png | Bin 2951 -> 1721 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/authentiwatch/screenshot1.png b/apps/authentiwatch/screenshot1.png index c7ca744b497c2098743be85b15d16878f7196a54..c3ac0e3b7e98b4db240c3928901179496a6ff69c 100644 GIT binary patch literal 1595 zcmaKsdpOez7{_NO4E6JzM09N9q@u)fEt)YWlWQiE#7K_he#>EwOIy>CYY5@NVXe$8 zA(q<`!Xmjdw~mxZiOpr~=v@9ePfve*-}iYw|GdxhdEV!nXlHXq2C4`Jfj}}?3(UEl zUHLT<;GGE{2$9@b;-Oe;bMd#LlF~{FBm{@EgCqkjoI^k$8~E3V>?7t(>_CV&+8PZ4 zF;k=j1ThdutP_htUiCUT^3bf;ePvt>O!RJe{)X0WQTjuM8@ zV_UiLF5Z(mI>!@&)T|=*8)RZrzG4a&QG2 zRP9mAW^TP<(+5{QYipjb)h|8FIqL;cBG;NdA=XuI5<@-@*AjY5$TTTo#~W{l}5^z;N31HlrE47D=RQ7G|7_@n_U~ z8}da@Y4msd_Tw_!>z(mQhu064@^^qdC@v zWH2FPoknSn%tp&l)a=A_W_vMZZ+jmW5e0u>H#SFEwQM&PNO9f3FJ|qLdDyI~T@JFp zt_)Fv?*uvhO+Gd{jStIX^WY{DPeiUS$D!($yGSS|t~gg(Y`6A)t$^jL{i= z9U5$6X#4T|;Z)@<&r$}!N?YfqAWa!;JApDjGWcAI`w3yX zfCR+^Ly;qs^Agz#s%K+kN%!WLbG3X zP<@yNtDT@M)NXw+O^)uK8bn{&R8=~id|0kHgJ>%#;5y%^;5!LKU#%Az@LFq~FL>7V z;5K-z`uGb`iK4Pahqn7m!={_M48r(U;T8U?vh@>gFD@XiDUPSipPw9YnLO7E{p^m` z6J(alfiu>eD6grLkn&O#n`(OF%ml}59Nk5L_ zs%^0D`cME`HT&j48+c#j{J=#-GE(JKLpTDL27B6QYsnAPRoW)N;M9t<$~8=1JH&(j zi>lkwkQs8vkm+uhc5#N1q89m|2Klb#|GMT0aevISEeUFd(P|@^36)>8-T<-GF&xMF zJ&h!SIwNLj0jUx!aO+p19Od%tUM!wJ-6rD{y|il^)}AfU__I2+V_&jG#xnNZA=}74LRqH5R3_^nq3n{K98|K5veg+h zB1<{SE{Z9GETcIk%W=}{e1AW@*Y$q5@BfG2eO>pL`~Sc0w6pe>0tg8N0008kR%T8I z+VpS1c@8?Qo#k^NAlb>%1ZW(VTmb-ty|tOK3&n?Bc8Of2cIalwuU}PUQx9EyNQV;k z#lYOΞ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 diff --git a/apps/authentiwatch/screenshot2.png b/apps/authentiwatch/screenshot2.png index 8156dd3e8a8070e3b57b93163f8cd44ca5e90e85..26bafdbb2db394b8652c4197ce828c4463281351 100644 GIT binary patch literal 1835 zcmb7FdpHvc8y}X2aTX=!wsmw?a*0|lCzlyQ>>@)#R?cyV$)!<3)@)Dh z005xs=IZ1n<3Iihki0CzMq-s@v^~zv(|P->f)W_Ie{e?9B$JfRyPk{%05GtBLQXR& z|D8+(hN3)C06<-aiiEfg08ngkb3%D1j4c*jOTTXeb|wx>rTxfS7f04!Y8N>|Sfp*+ zw%;g(`_@VYnPOM0)XGYcO2qkgycCy-_aEdPjFVka5feOLVG)=mU3AX7`XR?21r(GBw#e+P!MI^JFrqq zV;Bgj>FYYDx=d3-?W=$67!>8_00}p|z|aFZ-lCwrKL$F#nL1PEl{&rEEzXJzjmp#9 z!w6QP;9#r9R|icz`*xsv668^FcrwMwu8sopz^y3G!4y!e+kfe3f{b$T*5;4i^|ec6 zQ(kN%%F!9m9SD;U*i*P7B=SOxikh4}@W0`8TIC|i^9xeSu^50`-2G`oQOm+%0s%6H zZ~IYC{u+=9R{&xt!O6_Djybq80um4PmtNfXCJ1jEZi?rspA#^Ao;L=}(!y;fOc+g< zNT(uSucaO5a0&*dSo{Fe!d9cHW&&87=i?li!BRzTcKJrH64vh>>~S&47k_Q-f&jUHgS6Q{3ragojiD_`&{+# z!RBrVvY#opegx@Ju7>B2k`@zpHSSg(y+ewx#PZ;r=fv`y1qXcV?_7!D{ggGZI=ex_pak;dm7N9Y&oc59K)o&;7!$}RRY^L zV^+K}gdwMS=l6;r9NkAl7w3iN_{WD2ww;7jpE6Ein?~C2KkaYzZt*a|Kl2A|&d#2+ z&vSg@Q6V-bbw&;U+s62EL37qoS-0RpicYXTq5^3d#>d472J>lc{3g4xr#?VIlwFH= zP)ci{v!~-{c`f|C$*)wTVI*L{Cg1Z^oL zhF9iyEEaeq1#wQ@5L){W_uCUKFQI8u%7KE~%ddciv0`jRY7}_#G{I>ZQl?cRTFP*L z|LYI7*8Ve7#W*E)PG29WE}6Ujg|=!QmQrW?DZ=-@J&R0%4SiX|Y|5=M56s!+M@KWD zd56f30{@!gmEwHH;UEuv**ZhSBRJE`%SI;z-}KDMpr12L3^HQ%Nd*n(NRS3z&j4Kb zw`-D3Fn!bTrGXCJ$`)I0XC0>>kUqL@j@vvDJ!%(uJ}a$QCn@ zc$lzKH5(w&Fd|Mveo68h4tc!S>G&J>+3^=p;_3~y^=>H@>NK_(F(E|0=nc+qh0BET zB_jh+rtz}vwj55TH4UKf-DHpvYU!$OunJo7->(11{_jOe5uIh0u9#GBa-Vb{^WoA7 zAZnod#F_q)n6G2U;TsYG;7*r$&)nrJ%OrH2PmdGPHP zNTPixL?lX0&p_{_e+VJ?wKdsft$;&SPjbzJAa`!nA1W*etMXAV9ii)!qU3gTA3sO5y1bFQ>UQRmfl9qi7r}$`P1^aC&FZRXECmT6 zWKP9A9&?d+{YOSz>*`@&#z~hD%%7&RODWiR;x;ZEj+695ZklG8e}@$G9bZtq~GV)1NPy<|+^+d@RJo1z`)d7>&YDCs|9F;0mG%f1@G%^BnL I&@q_uFAa7*R{#J2 literal 2914 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 Z(A4{6$wZuM;{MYNIM|-BskI8c@jt}ERJ#BG diff --git a/apps/authentiwatch/screenshot3.png b/apps/authentiwatch/screenshot3.png index 6d14e0b965efa1b522d034bc0fee2f8415a950d5..80f2fb172b55fdfc19ac61f2c7878d718ebdc1fb 100644 GIT binary patch delta 1418 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 KMNUMnLSTZ>#1y{( literal 2951 zcmaJ@X*iS(7k*~UjG2*agCtuhyA)n!8%t)8B}+-gpk%GF%aTVW%S#4>63UirV=y9` zF};oODm7y4nk<#28oNY3-~aFWew=fk>)ij&bzk>=QXTDag6M;2000E7@s>_|Nd0di z5qrJ9WyWIu z!aU*3d@(N0Lu~Rk+65y#*C!OzdBoH%2XMV@cSMA{l=^uepSV1eud^aB2ik07RKf2n z;PtN*{{`{kTIi>%2KKmUv{#a1^!SC4M|2&?{yx#lpUxn#G1tYO#(-)(9lFF+ug*yQ zbPZSbfhNnKcsZKEH(@ZO%)HrO;9F7coZ{q?3i=?BAA&+|g|7dVQUu%}v5qEV!N-L=g5F1&(n?D`3L5;p9d+Zf4!0jY#49QB zH$fp0R0x9X&B22{;a_X*(Hd~#cj;Fb5{nw?C66#bQ^qjKsFH-F=8(e0%9zK2e0w>{ zZkPUH1ZnV^9pABjtJO;|dIx_<$8h;HwpSZakZf2l{mBl3x8}n(&yZoSE@Lri>PWgY zuD7tOjU6&{;3CkneW9x)40rn_jH&6UC*BE7QexniZKH&G-2(&7;?~lQ^~`?&*~OFf zl~A_;MUrIzd= z%Li|*7zuDSI7EOs(lS15RlidHmF1zm*6KG!=bqNm4KKpKlpP$H@;M1+$$uC-dG25|oom5K9hSc-?PUQ9n99c|`yiO^(J0==tT37y$)~WEgRj5_`SJxN<^x9& ztZ>td6j-qisnU0H zwTve(lFIZ|b;rp-`{4Du zA|SJE;9EW#gF#j?-@?RsP9PAgYHg~37Ul|%oUF=+!iH_Fw@_z6Ai2BGnvRiYdf4R@ij@M8{aHmhF{)UQPA-2e3`Y`E77GV# z6Ni59+>*OdVmK+<-BD$szu!dMV660ohs2}D-m$y|*S_;>&VN%^bA>B|XBQ6V?bDeq zpx=!%mf~8l44+APb+`ILYaO4Exq+^IF2S~5Vg&d;DZ4<_B-|TXm}S#ha1q7m;P4rS zn$d!6=rP}aJZW&n=k~3hH9pqppEH_q;&t=nFB_19m>x(}M}!r}N*T}J{Xh1$G)gbLTUUQ1Bs0!7AtFG)j%A#Xw} znqz|Cl;Ef35~aG7N@KrfDLmXt|GvA*@exxr*U7t_U+UXE)Jbtt5Xt5i3#6!urEr=} z4iaWurZ5s!;dFf!rd~>uDX9UwjRZCSA)eX4cc}YDA-Upkz znTJz{_wlRVf8Zn2D6*frhn0bKjM+ksZ~fYxl;CDF&fy8OA7QaH%kH*Xow8|jAdkgK z8Jn)j1-ob5ytChs!4KawCPXxjny8d(w$B2bw9&9ok_h2l*P;lP79(eJjdrAa)Kt=r zb%erys$6t?(J`}Lt9Cm}t# zEX&|mhn0<_nH3D@EsmTJcs@w#$4$>ts3NN0p}jX!9JAy^`n zPJa7wVcJ#{0XJV(q-(t^OYP~+_qA7LuAuqFMG?46aFS( zP?}wR<-{TGsPB_evOt&1!H`&1A)J@C<)_Q|+JFlaU^xTV*V;JhQL7^F&w%GYCIc6> zl<${75pa`^0sjMC3U@FM;ss5cDA zN?+;+JAS<$F*PmRiIM5p{*ycRX}fvru5~YBAH|3OgS5hyqo$qQcmn|a*v0-sW$`q77sm`EjvqT}^#P7Az3@7IFgh=D)E^C{b{)js@mt$<1GCC) zaKxtgxMHJ;;^Q_zY%Lo-lpZn$Xeis{M5EG9=xDZ!m>2Z zYGjrhUw>Z~kNRhE-yJnGZxl?DZJBkr_+5`)5&n51-e1eorRA+-CFFqJ zJu+`$0cqqEdhOS<%jtQM_^?EO^y$07YG77gscQsV@bT=qu`us`F-a(~w=?u0z!rz4Nr)(y zzS15a{(q=F4PNQ!yJhsvp!n_5&l@r(;t#xq(`b^N^PWWdjxh-msmhdXwKds){~QS5 zu7N92d%A0%ndbWKxpFwdAYm^H|2N$RS>K$zJ$WNLU1ABfq9_-!OfajdFl2T?b>+;- z4E7OoOkIlMnC-6@Z)CI7p)XIw7d0q&y0$Ab{lu+cX|2_UD~xwUB~w@{>AIajqoC&B i@KS%7z6u=Xk?DX?!=o=H8z=WZYry)ny(Pt*aQi=;H)1vb From 31d835dad59e7d5c9c6d404c95c85ee5332a40c2 Mon Sep 17 00:00:00 2001 From: Joseph Paul Date: Sun, 20 Mar 2022 09:25:43 +0100 Subject: [PATCH 11/64] Cycling: Initial commit --- apps/cycling/ChangeLog | 1 + apps/cycling/README.md | 26 ++ apps/cycling/blecsc-emu.js | 111 ++++++++ apps/cycling/blecsc.js | 150 +++++++++++ apps/cycling/cscsensor.app.js | 420 +++++++++++++++++++++++++++++ apps/cycling/icons8-cycling-48.png | Bin 0 -> 1487 bytes apps/cycling/metadata.json | 16 ++ apps/cycling/settings.js | 37 +++ 8 files changed, 761 insertions(+) create mode 100644 apps/cycling/ChangeLog create mode 100644 apps/cycling/README.md create mode 100644 apps/cycling/blecsc-emu.js create mode 100644 apps/cycling/blecsc.js create mode 100644 apps/cycling/cscsensor.app.js create mode 100644 apps/cycling/icons8-cycling-48.png create mode 100644 apps/cycling/metadata.json create mode 100644 apps/cycling/settings.js diff --git a/apps/cycling/ChangeLog b/apps/cycling/ChangeLog new file mode 100644 index 000000000..ec66c5568 --- /dev/null +++ b/apps/cycling/ChangeLog @@ -0,0 +1 @@ +0.01: Initial version diff --git a/apps/cycling/README.md b/apps/cycling/README.md new file mode 100644 index 000000000..61ba1d455 --- /dev/null +++ b/apps/cycling/README.md @@ -0,0 +1,26 @@ +# Cycling +> Displays data from a BLE Cycling Speed and Cadence sensor. + +*Fork of the CSCSensor app using the layout library and separate module for CSC functionality* + +The following data are displayed: +- curent speed +- moving time +- average speed +- maximum speed +- trip distance +- total distance + +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, a absolute value that persists throughout the lifetime of the sensor and never rolls over. + +**Cadence / Crank features are currently not implemented** + +# TODO +* Settings: imperial/metric +* Store circumference per device address +* Sensor battery status +* Implement crank events / show cadence +* Bangle.js 1 compatibility + +# Development +There is a "mock" version of the `blecsc` module, which can be used to test features in the emulator. Check `blecsc-emu.js` for usage. diff --git a/apps/cycling/blecsc-emu.js b/apps/cycling/blecsc-emu.js new file mode 100644 index 000000000..ca5058545 --- /dev/null +++ b/apps/cycling/blecsc-emu.js @@ -0,0 +1,111 @@ +// UUID of the Bluetooth CSC Service +const SERVICE_UUID = "1816"; +// UUID of the CSC measurement characteristic +const MEASUREMENT_UUID = "2a5b"; + +// Wheel revolution present bit mask +const FLAGS_WREV_BM = 0x01; +// Crank revolution present bit mask +const FLAGS_CREV_BM = 0x02; + +/** + * Fake BLECSC implementation for the emulator, where it's hard to test + * with actual hardware. Generates "random" wheel events (no crank). + * + * To upload as a module, paste the entire file in the console using this + * command: require("Storage").write("blecsc-emu",``); + */ +class BLECSCEmulator { + constructor() { + this.timeout = undefined; + this.interval = 500; + this.ccr = 0; + this.lwt = 0; + this.handlers = { + // value + // disconnect + // wheelEvent + // crankEvent + }; + } + + getDeviceAddress() { + return 'fa:ke:00:de:vi:ce'; + } + + /** + * Callback for the GATT characteristicvaluechanged event. + * Consumers must not call this method! + */ + onValue(event) { + // Not interested in non-CSC characteristics + if (event.target.uuid != "0x" + MEASUREMENT_UUID) return; + + // Notify the generic 'value' handler + if (this.handlers.value) this.handlers.value(event); + + const flags = event.target.value.getUint8(0, true); + // Notify the 'wheelEvent' handler + if ((flags & FLAGS_WREV_BM) && this.handlers.wheelEvent) this.handlers.wheelEvent({ + cwr: event.target.value.getUint32(1, true), // cumulative wheel revolutions + lwet: event.target.value.getUint16(5, true), // last wheel event time + }); + + // Notify the 'crankEvent' handler + if ((flags & FLAGS_CREV_BM) && this.handlers.crankEvent) this.handlers.crankEvent({ + ccr: event.target.value.getUint16(7, true), // cumulative crank revolutions + lcet: event.target.value.getUint16(9, true), // last crank event time + }); + } + + /** + * Register an event handler. + * + * @param {string} event value|disconnect + * @param {function} handler handler function that receives the event as its first argument + */ + on(event, handler) { + this.handlers[event] = handler; + } + + fakeEvent() { + this.interval = Math.max(50, Math.min(1000, this.interval + Math.random()*40-20)); + this.lwt = (this.lwt + this.interval) % 0x10000; + this.ccr++; + + var buffer = new ArrayBuffer(8); + var view = new DataView(buffer); + view.setUint8(0, 0x01); // Wheel revolution data present bit + view.setUint32(1, this.ccr, true); // Cumulative crank revolutions + view.setUint16(5, this.lwt, true); // Last wheel event time + + this.onValue({ + target: { + uuid: "0x2a5b", + value: view, + }, + }); + + this.timeout = setTimeout(this.fakeEvent.bind(this), this.interval); + } + + /** + * Find and connect to a device which exposes the CSC service. + * + * @return {Promise} + */ + connect() { + this.timeout = setTimeout(this.fakeEvent.bind(this), this.interval); + return Promise.resolve(true); + } + + /** + * Disconnect the device. + */ + disconnect() { + if (!this.timeout) return; + clearTimeout(this.timeout); + } +} + +exports = BLECSCEmulator; diff --git a/apps/cycling/blecsc.js b/apps/cycling/blecsc.js new file mode 100644 index 000000000..7a47108e5 --- /dev/null +++ b/apps/cycling/blecsc.js @@ -0,0 +1,150 @@ +const SERVICE_UUID = "1816"; +// UUID of the CSC measurement characteristic +const MEASUREMENT_UUID = "2a5b"; + +// Wheel revolution present bit mask +const FLAGS_WREV_BM = 0x01; +// Crank revolution present bit mask +const FLAGS_CREV_BM = 0x02; + +/** + * This class 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 + * - \`wheelEvent\` - the peripharial sends a notification containing wheel event data + * - \`crankEvent\` - the peripharial sends a notification containing crank event data + * - \`value\` - the peripharial sends any CSC characteristic notification (including wheel & crank event) + * - \`disconnect\` - the peripherial ends the connection or the connection is lost + * + * Each event can only have one handler. Any call to \`on()\` will + * replace a previously registered handler for the same event. + */ +class BLECSC { + constructor() { + this.device = undefined; + this.ccInterval = undefined; + this.gatt = undefined; + this.handlers = { + // wheelEvent + // crankEvent + // value + // disconnect + }; + } + + getDeviceAddress() { + if (!this.device || !this.device.id) + return '00:00:00:00:00:00'; + return this.device.id.split(" ")[0]; + } + + checkConnection() { + if (!this.device) + console.log("no device"); + // else + // console.log("rssi: " + this.device.rssi); + } + + /** + * Callback for the GATT characteristicvaluechanged event. + * Consumers must not call this method! + */ + onValue(event) { + // Not interested in non-CSC characteristics + if (event.target.uuid != "0x" + MEASUREMENT_UUID) return; + + // Notify the generic 'value' handler + if (this.handlers.value) this.handlers.value(event); + + const flags = event.target.value.getUint8(0, true); + // Notify the 'wheelEvent' handler + if ((flags & FLAGS_WREV_BM) && this.handlers.wheelEvent) this.handlers.wheelEvent({ + cwr: event.target.value.getUint32(1, true), // cumulative wheel revolutions + lwet: event.target.value.getUint16(5, true), // last wheel event time + }); + + // Notify the 'crankEvent' handler + if ((flags & FLAGS_CREV_BM) && this.handlers.crankEvent) this.handlers.crankEvent({ + ccr: event.target.value.getUint16(7, true), // cumulative crank revolutions + lcet: event.target.value.getUint16(9, true), // last crank event time + }); + } + + /** + * Callback for the NRF disconnect event. + * Consumers must not call this method! + */ + onDisconnect(event) { + console.log("disconnected"); + if (this.ccInterval) + clearInterval(this.ccInterval); + + if (!this.handlers.disconnect) return; + this.handlers.disconnect(event); + } + + /** + * Register an event handler. + * + * @param {string} event wheelEvent|crankEvent|value|disconnect + * @param {function} handler function that will receive the event as its first argument + */ + on(event, handler) { + this.handlers[event] = handler; + } + + /** + * Find and connect to a device which exposes the CSC service. + * + * @return {Promise} + */ + connect() { + // Register handler for the disconnect event to be passed throug + NRF.on('disconnect', this.onDisconnect.bind(this)); + + // Find a device, then get the CSC Service and subscribe to + // notifications on the CSC Measurement characteristic. + // NRF.setLowPowerConnection(true); + return NRF.requestDevice({ + timeout: 5000, + filters: [{ services: [SERVICE_UUID] }], + }).then(device => { + this.device = device; + this.device.on('gattserverdisconnected', this.onDisconnect.bind(this)); + this.ccInterval = setInterval(this.checkConnection.bind(this), 2000); + return device.gatt.connect(); + }).then(gatt => { + this.gatt = gatt; + return gatt.getPrimaryService(SERVICE_UUID); + }).then(service => { + return service.getCharacteristic(MEASUREMENT_UUID); + }).then(characteristic => { + characteristic.on('characteristicvaluechanged', this.onValue.bind(this)); + return characteristic.startNotifications(); + }); + } + + /** + * Disconnect the device. + */ + disconnect() { + if (this.ccInterval) + clearInterval(this.ccInterval); + + if (!this.gatt) return; + try { + this.gatt.disconnect(); + } catch { + // + } + } +} + +exports = BLECSC; diff --git a/apps/cycling/cscsensor.app.js b/apps/cycling/cscsensor.app.js new file mode 100644 index 000000000..9de3f5a3e --- /dev/null +++ b/apps/cycling/cscsensor.app.js @@ -0,0 +1,420 @@ +const Layout = require('Layout'); + +const SETTINGS_FILE = 'cscsensor.json'; +const storage = require('Storage'); + +const RECONNECT_TIMEOUT = 4000; +const MAX_CONN_ATTEMPTS = 2; + +class CSCSensor { + constructor(blecsc, display) { + // Dependency injection + this.blecsc = blecsc; + this.display = display; + + // Load settings + this.settings = storage.readJSON(SETTINGS_FILE, 1) || {}; + this.wheelCirc = (this.settings.wheelcirc || 2230) / 1000; // unit: m + + // CSC runtime variables + this.movingTime = 0; // unit: s + this.lastBangleTime = Date.now(); // unit: ms + this.lwet = 0; // last wheel event time (unit: s/1024) + this.cwr = -1; // cumulative wheel revolutions + this.cwrTrip = 0; // wheel revolutions since trip start + this.speed = 0; // unit: m/s + this.maxSpeed = 0; // unit: m/s + this.speedFailed = 0; + + // Other runtime variables + this.connected = false; + this.failedAttempts = 0; + this.failed = false; + + // Layout configuration + this.layout = 0; + this.display.useMetricUnits(true); + // this.display.useMetricUnits(!require("locale").speed(1).toString().endsWith("mph")); + } + + onDisconnect(event) { + console.log("disconnected ", event); + + this.connected = false; + this.setLayout(0); + this.display.setDeviceAddress("unknown"); + + if (this.failedAttempts >= MAX_CONN_ATTEMPTS) { + this.failed = true; + this.display.setStatus("Connection failed after " + MAX_CONN_ATTEMPTS + " attempts."); + } else { + this.display.setStatus("Disconnected"); + setTimeout(this.connect.bind(this), RECONNECT_TIMEOUT); + } + + } + + connect() { + this.connected = false; + this.setLayout(0); + this.display.setStatus("Connecting..."); + console.log("Trying to connect to BLE CSC"); + + // Hook up events + this.blecsc.on('wheelEvent', this.onWheelEvent.bind(this)); + this.blecsc.on('disconnect', this.onDisconnect.bind(this)); + + // Scan for BLE device and connect + this.blecsc.connect() + .then(function() { + this.failedAttempts = 0; + this.failed = false; + this.connected = true; + var addr = this.blecsc.getDeviceAddress(); + console.log("Connected to " + addr); + + this.display.setDeviceAddress(addr); + this.display.setStatus("Connected"); + + // Switch to speed screen in 2s + setTimeout(function() { + this.setLayout(1); + this.updateScreen(); + }.bind(this), 2000); + }.bind(this)) + .catch(function(e) { + this.failedAttempts++; + this.onDisconnect(e); + }.bind(this)); + } + + disconnect() { + this.blecsc.disconnect(); + this.connected = false; + this.setLayout(0); + this.display.setStatus("Disconnected") + } + + setLayout(num) { + this.layout = num; + if (this.layout == 0) { + this.display.updateLayout("status"); + } else if (this.layout == 1) { + this.display.updateLayout("speed"); + } else if (this.layout == 2) { + this.display.updateLayout("distance"); + } + } + + reset() { + this.connected = false; + this.failed = false; + this.failedAttempts = 0; + } + + interact(d) { + // Only interested in tap / center button + if (d) return; + + // Reconnect in failed state + if (this.failed) { + this.reset(); + this.connect(); + } else if (this.connected) { + this.setLayout((this.layout + 1) % 3); + } + } + + updateScreen() { + var tripDist = this.cwrTrip * this.wheelCirc; + var avgSpeed = this.movingTime > 3 ? tripDist / this.movingTime : 0 + + this.display.setTotalDistance(this.cwr * this.wheelCirc); + this.display.setTripDistance(tripDist); + this.display.setSpeed(this.speed); + this.display.setAvg(avgSpeed); + this.display.setMax(this.maxSpeed); + this.display.setTime(Math.floor(this.movingTime)); + } + + onWheelEvent(event) { + // Calculate number of revolutions since last wheel event + var dRevs = (this.cwr > 0 ? event.cwr - this.cwr : 0); + this.cwr = event.cwr; + + // Increment the trip revolutions counter + this.cwrTrip += dRevs; + + // Calculate time delta since last wheel event + var dT = (event.lwet - this.lwet)/1024; + var now = Date.now(); + var dBT = (now-this.lastBangleTime)/1000; + this.lastBangleTime = now; + if (dT<0) dT+=64; // wheel event time wraps every 64s + if (Math.abs(dT-dBT)>3) dT = dBT; // not sure about the reason for this + this.lwet = event.lwet; + + // Recalculate current speed + if (dRevs>0 && dT>0) { + this.speed = dRevs * this.wheelCirc / dT; + this.speedFailed = 0; + this.movingTime += dT; + } else { + this.speedFailed++; + if (this.speedFailed>3) { + this.speed = 0; + } + } + + // Update max speed + if (this.speed>this.maxSpeed + && (this.movingTime>3 || this.speed<20) + && this.speed<50 + ) this.maxSpeed = this.speed; + + this.updateScreen(); + } +} + +class CSCDisplay { + constructor() { + this.metric = true; + this.fontLabel = "6x8"; + this.fontMed = "15%"; + this.fontLarge = "32%"; + this.currentLayout = "status"; + this.layouts = {}; + this.layouts.speed = new Layout({ + type: "v", + c: [ + { + type: "h", + id: "speed_g", + fillx: 1, + filly: 1, + pad: 4, + bgCol: "#fff", + c: [ + {type: undefined, width: 32, halign: -1}, + {type: "txt", id: "speed", label: "00.0", font: this.fontLarge, bgCol: "#fff", col: "#000", width: 122}, + {type: "txt", id: "speed_u", label: " km/h", font: this.fontLabel, col: "#000", width: 22, r: 90}, + ] + }, + { + type: "h", + id: "time_g", + fillx: 1, + pad: 4, + bgCol: "#000", + height: 32, + c: [ + {type: undefined, width: 32, halign: -1}, + {type: "txt", id: "time", label: "00:00", font: this.fontMed, bgCol: "#000", col: "#fff", width: 122}, + {type: "txt", id: "time_u", label: "mins", font: this.fontLabel, bgCol: "#000", col: "#fff", width: 22, r: 90}, + ] + }, + { + type: "h", + id: "stats_g", + fillx: 1, + bgCol: "#fff", + height: 32, + c: [ + { + type: "v", + pad: 4, + bgCol: "#fff", + c: [ + {type: "txt", id: "max_l", label: "MAX", font: this.fontLabel, col: "#000"}, + {type: "txt", id: "max", label: "00.0", font: this.fontMed, bgCol: "#fff", col: "#000", width: 69}, + ], + }, + { + type: "v", + pad: 4, + bgCol: "#fff", + c: [ + {type: "txt", id: "avg_l", label: "AVG", font: this.fontLabel, col: "#000"}, + {type: "txt", id: "avg", label: "00.0", font: this.fontMed, bgCol: "#fff", col: "#000", width: 69}, + ], + }, + {type: "txt", id: "stats_u", label: " km/h", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 22, r: 90}, + ] + }, + ], + }); + this.layouts.distance = new Layout({ + type: "v", + c: [ + { + type: "h", + id: "tripd_g", + fillx: 1, + pad: 4, + bgCol: "#fff", + height: 32, + c: [ + {type: "txt", id: "tripd_l", label: "TRP", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 36}, + {type: "txt", id: "tripd", label: "0", font: this.fontMed, bgCol: "#fff", col: "#000", width: 118}, + {type: "txt", id: "tripd_u", label: "km", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 22, r: 90}, + ] + }, + { + type: "h", + id: "totald_g", + fillx: 1, + pad: 4, + bgCol: "#000", + height: 32, + c: [ + {type: "txt", id: "totald_l", label: "TTL", font: this.fontLabel, bgCol: "#000", col: "#fff", width: 36}, + {type: "txt", id: "totald", label: "0", font: this.fontMed, bgCol: "#000", col: "#fff", width: 118}, + {type: "txt", id: "totald_u", label: "km", font: this.fontLabel, bgCol: "#000", col: "#fff", width: 22, r: 90}, + ] + }, + ], + }); + this.layouts.status = new Layout({ + type: "v", + c: [ + { + type: "h", + id: "status_g", + fillx: 1, + bgCol: "#fff", + height: 100, + c: [ + {type: "txt", id: "status", label: "Bangle Cycling", font: this.fontMed, bgCol: "#fff", col: "#000", width: 176, wrap: 1}, + ] + }, + { + type: "h", + id: "addr_g", + fillx: 1, + pad: 4, + bgCol: "#fff", + height: 32, + c: [ + { type: "txt", id: "addr_l", label: "MAC", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 36 }, + { type: "txt", id: "addr", label: "unknown", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 140 }, + ] + }, + ], + }); + } + + updateLayout(layout) { + this.currentLayout = layout; + + g.clear(); + this.layouts[layout].update(); + this.layouts[layout].render(); + } + + renderIfLayoutActive(layout, node) { + if (layout != this.currentLayout) return; + this.layouts[layout].render(node) + } + + useMetricUnits(metric) { + this.metric = metric; + + console.log("using " + (metric ? "metric" : "imperial") + " units"); + + var speedUnit = metric ? "km/h" : "mph"; + this.layouts.speed.speed_u.label = speedUnit; + this.layouts.speed.stats_u.label = speedUnit; + + var distanceUnit = metric ? "km" : "mi"; + this.layouts.distance.tripd_u.label = distanceUnit; + this.layouts.distance.totald_u.label = distanceUnit; + + this.updateLayout(this.currentLayout); + } + + convertDistance(meters) { + if (this.metric) return meters / 1000; + return meters / 1609.344; + } + + convertSpeed(mps) { + if (this.metric) return mps * 3.6; + return mps * 2.23694; + } + + setSpeed(speed) { + this.layouts.speed.speed.label = this.convertSpeed(speed).toFixed(1); + this.renderIfLayoutActive("speed", this.layouts.speed.speed_g); + } + + setAvg(speed) { + this.layouts.speed.avg.label = this.convertSpeed(speed).toFixed(1); + this.renderIfLayoutActive("speed", this.layouts.speed.stats_g); + } + + setMax(speed) { + this.layouts.speed.max.label = this.convertSpeed(speed).toFixed(1); + this.renderIfLayoutActive("speed", this.layouts.speed.stats_g); + } + + setTime(seconds) { + var time = ''; + var hours = Math.floor(seconds/3600); + if (hours) { + time += hours + ":"; + this.layouts.speed.time_u.label = " hrs"; + } else { + this.layouts.speed.time_u.label = "mins"; + } + + time += String(Math.floor((seconds%3600)/60)).padStart(2, '0') + ":"; + time += String(seconds % 60).padStart(2, '0'); + + this.layouts.speed.time.label = time; + this.renderIfLayoutActive("speed", this.layouts.speed.time_g); + } + + setTripDistance(distance) { + this.layouts.distance.tripd.label = this.convertDistance(distance).toFixed(1) + this.renderIfLayoutActive("distance", this.layouts.distance.tripd_g); + } + + setTotalDistance(distance) { + this.layouts.distance.totald.label = this.convertDistance(distance).toFixed(1) + this.renderIfLayoutActive("distance", this.layouts.distance.totald_g); + } + + setDeviceAddress(address) { + this.layouts.status.addr.label = address + this.renderIfLayoutActive("status", this.layouts.status.addr_g); + } + + setStatus(status) { + this.layouts.status.status.label = status + this.renderIfLayoutActive("status", this.layouts.status.status_g); + } +} + +var BLECSC; +if (process.env.BOARD === "EMSCRIPTEN" || process.env.BOARD === "EMSCRIPTEN2") { + // Emulator + BLECSC = require("blecsc-emu"); +} else { + // Actual hardware + BLECSC = require("blecsc"); +} +var blecsc = new BLECSC(); +var display = new CSCDisplay(); +var sensor = new CSCSensor(blecsc, display); + +E.on('kill',()=>{ + sensor.disconnect(); +}); + +Bangle.setUI("updown", d => { + sensor.interact(d); +}); + +sensor.connect(); +// Bangle.loadWidgets(); +// Bangle.drawWidgets(); diff --git a/apps/cycling/icons8-cycling-48.png b/apps/cycling/icons8-cycling-48.png new file mode 100644 index 0000000000000000000000000000000000000000..0bc83859f1ac8d5b1d40aa787ab3c8bf339913fd GIT binary patch literal 1487 zcmV;=1u*)FP)BH^< zyW9n~y*qr_|Cil8&-47wInUcYcLAS#^2w*s6pf~HE?fR2B+da?2cQbX+sIIO#vR6} zC14F+`r0W52Ju37k`R;+ZG@6I2ZJY&z?9<-X7myuo-S{P&K=C?CBSq=-i|7FFn)>z zGlWX>KF@8r7q(gy1B1o}w0CMluJ4M^%-R8xwu0#!IG+Jvn>&239pdkH7KGgqBYtRX z)lMB~7s!Iy(Le8ue>L-ME?t%irW_D-wwd2XDaI0a{CFjS^1Q>z4XxT!ZQUV(a_xcY zE{>a0k)&U{slroVil2d!T}O7;Hcu~g6+8fRnFFc%UtKZp$@2HqpJHdAXn#>i&uvRZ zZKHNXw@71aWbpT6W)}f4c;Z))6)4X;oZQ+NP~X@#vf%BiZqhIKW&a1Sc;egR1P<-k z{(fzXNHrJz#96RAa7J5=dQ<-qCQE z#sWaFzaLh`#6j$w3wFY4@B61VVsb-5+>0Ap0N~oE+XF5)&8&G~VolTxt;^u;5muAI z_>fl;n3a~n52Q6S+yfz- z48{XqNg(<8jIQPm1UTEr`Kz%iCf2MA719#3*1J_6yDN_lu|C{ll(V;sW_)P6sQ&EA z`GY5RFQ4d%kKyF$lbAMStthU%DJ&Fw5yKGPCguXz*q;DLIxetq zQ&GO1>TEb!UZJ-wtLOsWr^WLUFR55a1JFZ+%wuOS_P?kzO>*eW))i2U?0003g14xmMV5(D= zZ)+Reo$m(#vmg$-zE*C@{b}Wv+)8JAH_U(7zD^lnc3c6*O@;Zc5&+BCtRi9%4{8tU z3bbC@QYuHZ@;KzXthu_VPlk?lHvFL^Vv3A`CVBba!c>K#2 zh4}zvSAa?YfJv1h2a}T^AOir1(O)_<)&blI;0B0S5!q)?A-+9YXT$Nbb2jB0BV5p zhYQ{teA!y4z+_T~w~NKZFaYQQ_@E@wkzQ5L?*O2Y{AYqV9l*<0I7)5>`!8};uXvO1 zvY0C{nbhHJVlELf;rgAm7ohxG!J2nQ8obi{Vaw^`W#@D(s8-t^BGESUbz+>c_z9mZ pnU^5{H~9$VZN5)F`Q(##^&bl8rz8LGDIEX+002ovPDHLkV1j9?%0U1C literal 0 HcmV?d00001 diff --git a/apps/cycling/metadata.json b/apps/cycling/metadata.json new file mode 100644 index 000000000..917658fad --- /dev/null +++ b/apps/cycling/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "cycling", + "name": "Bangle Cycling", + "shortName": "Cycling", + "version": "0.01", + "description": "Display live values from a BLE CSC sensor", + "icon": "icons8-cycling-48.png", + "tags": "outdoors,exercise,ble,bluetooth", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"cycling.app.js","url":"cycling.app.js"}, + {"name":"cycling.settings.js","url":"settings.js"}, + {"name":"blecsc","url":"blecsc.js"} + ] +} diff --git a/apps/cycling/settings.js b/apps/cycling/settings.js new file mode 100644 index 000000000..810d8afc0 --- /dev/null +++ b/apps/cycling/settings.js @@ -0,0 +1,37 @@ +// This file should contain exactly one function, which shows the app's settings +/** + * @param {function} back Use back() to return to settings menu + */ +(function(back) { + const SETTINGS_FILE = 'cscsensor.json' + // initialize with default settings... + let s = { + 'wheelcirc': 2230, + } + // ...and overwrite them with any saved values + // This way saved values are preserved if a new version adds more settings + const storage = require('Storage') + const saved = storage.readJSON(SETTINGS_FILE, 1) || {} + for (const key in saved) { + s[key] = saved[key]; + } + // creates a function to safe a specific setting, e.g. save('color')(1) + function save(key) { + return function (value) { + s[key] = value; + storage.write(SETTINGS_FILE, s); + } + } + const menu = { + '': { 'title': 'Cycle speed sensor' }, + '< Back': back, + 'Wheel circ.(mm)': { + value: s.wheelcirc, + min: 800, + max: 2400, + step: 5, + onchange: save('wheelcirc'), + }, + } + E.showMenu(menu); +}) From 1523a15b8e9c271ee85da212fe66e5f3768874ce Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Sun, 20 Mar 2022 17:36:34 +0800 Subject: [PATCH 12/64] Update app.js Rewrite UI code and drawing --- apps/authentiwatch/app.js | 440 +++++++++++++++++++++----------------- 1 file changed, 248 insertions(+), 192 deletions(-) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index fb130969f..2734a064a 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -1,3 +1,4 @@ +const COUNTER_TRIANGLE_SIZE = 10; const TOKEN_EXTRA_HEIGHT = 16; var TOKEN_DIGITS_HEIGHT = 30; var TOKEN_HEIGHT = TOKEN_DIGITS_HEIGHT + TOKEN_EXTRA_HEIGHT; @@ -44,8 +45,8 @@ function b32decode(seedstr) { return retbuf; } -function doHmac(key, message, algo) { - var a = algos[algo]; +function hmac(key, message, algo) { + var a = algos[algo.toUpperCase()]; // RFC2104 if (key.length > a.blksz) { key = a.sha(key); @@ -66,50 +67,52 @@ function doHmac(key, message, algo) { } function formatOtp(otp, digits) { + // add 0 padding + var ret = "" + otp % Math.pow(10, digits); + while (ret.length < digits) { + ret = "0" + ret; + } + // add a space after every 3rd or 4th digit var re = (digits % 3 == 0 || (digits % 3 >= digits % 4 && digits % 4 != 0)) ? "" : "."; - return otp.replace(new RegExp("(..." + re + ")", "g"), "$1 ").trim(); + return ret.replace(new RegExp("(..." + re + ")", "g"), "$1 ").trim(); } -function hotp(d, token, calcHmac) { - var tick; +function hotp(token) { + var d = Date.now(); + var tick, next; if (token.period > 0) { // RFC6238 - timed - var seconds = Math.floor(d.getTime() / 1000); + var seconds = Math.floor(d / 1000); tick = Math.floor(seconds / token.period); + next = (tick + 1) * token.period * 1000; } else { // RFC4226 - counter tick = -token.period; + next = d + 30000; } var msg = new Uint8Array(8); var v = new DataView(msg.buffer); v.setUint32(0, tick >> 16 >> 16); v.setUint32(4, tick & 0xFFFFFFFF); - var ret = CALCULATING; - if (calcHmac) { - try { - var hash = doHmac(b32decode(token.secret), msg, token.algorithm.toUpperCase()); - ret = "" + hash % Math.pow(10, token.digits); - while (ret.length < token.digits) { - ret = "0" + ret; - } - // add a space after every 3rd or 4th digit - ret = formatOtp(ret, token.digits); - } catch(err) { - ret = NOT_SUPPORTED; - } + var ret; + try { + ret = hmac(b32decode(token.secret), msg, token.algorithm); + ret = formatOtp(ret, token.digits); + } catch(err) { + ret = NOT_SUPPORTED; } - return {hotp:ret, next:((token.period > 0) ? ((tick + 1) * token.period * 1000) : d.getTime() + 30000)}; + return {hotp:ret, next:next}; } +// Tokens are displayed in three states: +// 1. Unselected (state.id==-1) +// 2. Selected, inactive (no code) (state.id!=-1,state.hotp.hotp=="") +// 3. Selected, active (code showing) (state.id!=-1,state.hotp.hotp!="") var fontszCache = {}; var state = { - listy: 0, - prevcur:0, - curtoken:-1, - nextTime:0, - otp:"", - rem:0, - hide:0 + listy:0, // list scroll position + id:-1, // current token ID + hotp:{hotp:"",next:0} }; function sizeFont(id, txt, w) { @@ -125,117 +128,42 @@ function sizeFont(id, txt, w) { } } -function drawToken(id, r) { - var x1 = r.x; - var y1 = r.y; - var x2 = r.x + r.w - 1; - var y2 = r.y + r.h - 1; - var adj, lbl; - g.setClipRect(Math.max(x1, Bangle.appRect.x ), Math.max(y1, Bangle.appRect.y ), - Math.min(x2, Bangle.appRect.x2), Math.min(y2, Bangle.appRect.y2)); - lbl = tokens[id].label.substr(0, 10); - if (id == state.curtoken) { - // current token - g.setColor(g.theme.fgH) - .setBgColor(g.theme.bgH) - .setFont("Vector", TOKEN_EXTRA_HEIGHT) - // center just below top line - .setFontAlign(0, -1, 0); - adj = y1; - } else { - g.setColor(g.theme.fg) - .setBgColor(g.theme.bg); - sizeFont("l" + id, lbl, r.w); - // center in box - g.setFontAlign(0, 0, 0); - adj = (y1 + y2) / 2; - } - g.clearRect(x1, y1, x2, y2) - .drawString(lbl, (x1 + x2) / 2, adj, false); - if (id == state.curtoken) { - if (tokens[id].period > 0) { - // timed - draw progress bar - let xr = Math.floor(Bangle.appRect.w * state.rem / tokens[id].period); - g.fillRect(x1, y2 - 4, xr, y2 - 1); - adj = 0; - } else { - // counter - draw triangle as swipe hint - let yc = (y1 + y2) / 2; - g.fillPoly([0, yc, 10, yc - 10, 10, yc + 10, 0, yc]); - adj = 12; - } - // digits just below label - sizeFont("d" + id, state.otp, r.w - adj); - g.drawString(state.otp, (x1 + adj + x2) / 2, y1 + TOKEN_EXTRA_HEIGHT, false); - } - g.setClipRect(0, 0, g.getWidth(), g.getHeight()); -} +tokenY = id => id * TOKEN_HEIGHT + AR.y - state.listy; +half = n => Math.floor(n / 2); -function draw() { - var timerfn = exitApp; - var timerdly = 10000; - var d = new Date(); - if (state.curtoken != -1) { - var t = tokens[state.curtoken]; - if (state.otp == CALCULATING) { - state.otp = hotp(d, t, true).hotp; - } - if (d.getTime() > state.nextTime) { - if (state.hide == 0) { - // auto-hide the current token - if (state.curtoken != -1) { - state.prevcur = state.curtoken; - state.curtoken = -1; +function timerCalc() { + let timerfn = exitApp; + let timerdly = 10000; + let id = state.id; + if (id != -1) { + if (state.hotp.hotp != "") { + if (tokens[id].period > 0) { + // timed HOTP + if (state.hotp.next < Date.now()) { + if (state.cnt > 0) { + --state.cnt; + state.hotp = hotp(tokens[id]); + } else { + state.hotp.hotp = ""; + } + timerdly = 1; + timerfn = updateCurrentToken; + } else { + timerdly = 1000; + timerfn = updateProgressBar; } - state.nextTime = 0; } else { - // time to generate a new token - var r = hotp(d, t, state.otp != ""); - state.nextTime = r.next; - state.otp = r.hotp; - if (t.period <= 0) { - state.hide = 1; + // counter HOTP + if (state.cnt > 0) { + --state.cnt; + timerdly = 30000; + } else { + state.hotp.hotp = ""; + timerdly = 1; } - state.hide--; + timerfn = updateCurrentToken; } } - state.rem = Math.max(0, Math.floor((state.nextTime - d.getTime()) / 1000)); - } - if (tokens.length > 0) { - var drewcur = false; - var id = Math.floor(state.listy / TOKEN_HEIGHT); - var y = id * TOKEN_HEIGHT + Bangle.appRect.y - state.listy; - while (id < tokens.length && y < Bangle.appRect.y2) { - drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:TOKEN_HEIGHT}); - if (id == state.curtoken && (tokens[id].period <= 0 || state.nextTime != 0)) { - drewcur = true; - } - id++; - y += TOKEN_HEIGHT; - } - if (drewcur) { - // the current token has been drawn - schedule a redraw - if (tokens[state.curtoken].period > 0) { - timerdly = (state.otp == CALCULATING) ? 1 : 1000; // timed - } else { - timerdly = state.nexttime - d.getTime(); // counter - } - timerfn = draw; - if (tokens[state.curtoken].period <= 0) { - state.hide = 0; - } - } else { - // de-select the current token if it is scrolled out of view - if (state.curtoken != -1) { - state.prevcur = state.curtoken; - state.curtoken = -1; - } - state.nexttime = 0; - } - } else { - g.setFont("Vector", TOKEN_DIGITS_HEIGHT) - .setFontAlign(0, 0, 0) - .drawString(NO_TOKENS, Bangle.appRect.x + Bangle.appRect.w / 2, Bangle.appRect.y + Bangle.appRect.h / 2, false); } if (state.drawtimer) { clearTimeout(state.drawtimer); @@ -243,101 +171,230 @@ function draw() { state.drawtimer = setTimeout(timerfn, timerdly); } -function onTouch(zone, e) { - if (e) { - var id = Math.floor((state.listy + (e.y - Bangle.appRect.y)) / TOKEN_HEIGHT); - if (id == state.curtoken || tokens.length == 0 || id >= tokens.length) { - id = -1; - } - if (state.curtoken != id) { - if (id != -1) { - var y = id * TOKEN_HEIGHT - state.listy; - if (y < 0) { - state.listy += y; - y = 0; +function updateCurrentToken() { + drawToken(state.id); + timerCalc(); +} + +function updateProgressBar() { + drawProgressBar(); + timerCalc(); +} + +function drawProgressBar() { + let id = state.id; + if (id != -1) { + if (tokens[id].period > 0) { + let rem = Math.floor((state.hotp.next - Date.now()) / 1000); + if (rem >= 0) { + let y1 = tokenY(id); + let y2 = y1 + TOKEN_HEIGHT - 1; + if (y2 >= AR.y && y1 <= AR.y2) { + // token visible + if ((y2 - 3) <= AR.y2) + { + // progress bar visible + y2 = Math.min(y2, AR.y2); + rem = Math.min(rem, tokens[id].period); + let xr = Math.floor(AR.w * rem / tokens[id].period) + AR.x; + g.setColor(g.theme.fgH) + .setBgColor(g.theme.bgH) + .fillRect(AR.x, y2 - 3, xr, y2) + .clearRect(xr + 1, y2 - 3, AR.x2, y2); + } + } else { + // token not visible + state.id = -1; } - y += TOKEN_HEIGHT; - if (y > Bangle.appRect.h) { - state.listy += (y - Bangle.appRect.h); - } - state.otp = ""; } - state.nextTime = 0; - state.curtoken = id; - state.hide = 2; } } - draw(); +} + +// id = token ID number (0...) +function drawToken(id) { + var x1 = AR.x; + var y1 = tokenY(id); + var x2 = AR.x2; + var y2 = y1 + TOKEN_HEIGHT - 1; + var adj, lbl; + g.setClipRect(x1, Math.max(y1, AR.y), x2, Math.min(y2, AR.y2)); + lbl = tokens[id].label.substr(0, 10); + if (id === state.id) { + g.setColor(g.theme.fgH) + .setBgColor(g.theme.bgH); + } else { + g.setColor(g.theme.fg) + .setBgColor(g.theme.bg); + } + if (id == state.id && state.hotp.hotp != "") { + // small label centered just below top line + g.setFont("Vector", TOKEN_EXTRA_HEIGHT) + .setFontAlign(0, -1, 0); + adj = y1; + } else { + // large label centered in box + sizeFont("l" + id, lbl, AR.w); + g.setFontAlign(0, 0, 0); + adj = half(y1 + y2); + } + g.clearRect(x1, y1, x2, y2) + .drawString(lbl, half(x1 + x2), adj, false); + if (id == state.id && state.hotp.hotp != "") { + adj = 0; + if (tokens[id].period <= 0) { + // counter - draw triangle as swipe hint + let yc = half(y1 + y2); + adj = COUNTER_TRIANGLE_SIZE; + g.fillPoly([AR.x, yc, AR.x + adj, yc - adj, AR.x + adj, yc + adj]); + adj += 2; + } + // digits just below label + x1 = half(x1 + adj + x2); + y1 += TOKEN_EXTRA_HEIGHT; + if (state.hotp.hotp == CALCULATING) { + sizeFont("c", CALCULATING, AR.w - adj); + g.drawString(CALCULATING, x1, y1, false) + .flip(); + state.hotp = hotp(tokens[id]); + g.clearRect(AR.x + adj, y1, AR.x2, y2); + } + sizeFont("d" + id, state.hotp.hotp, AR.w - adj); + g.drawString(state.hotp.hotp, x1, y1, false); + if (tokens[id].period > 0) { + drawProgressBar(); + } + } + g.setClipRect(0, 0, g.getWidth(), g.getHeight()); +} + +function startupDraw() { + if (tokens.length > 0) { + let id = 0; + let y = tokenY(id); + while (id < tokens.length && y < AR.y2) { + if ((y + TOKEN_HEIGHT) > AR.y) { + drawToken(id); + } + id++; + y += TOKEN_HEIGHT; + } + } else { + let x = AR.x + half(AR.w); + let y = AR.y + half(AR.h); + g.setFont("Vector", TOKEN_DIGITS_HEIGHT) + .setFontAlign(0, 0, 0) + .drawString(NO_TOKENS, x, y, false); + } + timerCalc(); } function onDrag(e) { - if (e.b != 0 && e.x < g.getWidth() && e.y < g.getHeight() && e.dy != 0) { - var y = Math.max(0, Math.min(state.listy - e.dy, tokens.length * TOKEN_HEIGHT - Bangle.appRect.h)); + state.cnt = 1; + if (e.b != 0 && e.dy != 0) { + var y = E.clip(state.listy - e.dy, 0, tokens.length * TOKEN_HEIGHT - AR.h); if (state.listy != y) { var id, dy = state.listy - y; state.listy = y; - g.setClipRect(Bangle.appRect.x,Bangle.appRect.y,Bangle.appRect.x2,Bangle.appRect.y2) + g.setClipRect(AR.x, AR.y, AR.x2, AR.y2) .scroll(0, dy); if (dy > 0) { id = Math.floor((state.listy + dy) / TOKEN_HEIGHT); - y = id * TOKEN_HEIGHT + Bangle.appRect.y - state.listy; + y = tokenY(id + 1); do { - drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:TOKEN_HEIGHT}); + drawToken(id); id--; y -= TOKEN_HEIGHT; - } while (y > 0); + } while (y > AR.y); } if (dy < 0) { - id = Math.floor((state.listy + dy + Bangle.appRect.h) / TOKEN_HEIGHT); - y = id * TOKEN_HEIGHT + Bangle.appRect.y - state.listy; - while (y < Bangle.appRect.y2) { - drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:TOKEN_HEIGHT}); + id = Math.floor((state.listy + dy + AR.h) / TOKEN_HEIGHT); + y = tokenY(id); + while (y < AR.y2) { + drawToken(id); id++; y += TOKEN_HEIGHT; } } } } + if (e.b == 0) { + timerCalc(); + } +} + +function changeId(id) { + if (id != state.id) { + state.hotp.hotp = CALCULATING; + let pid = state.id; + state.id = id; + if (pid != -1) { + drawToken(pid); + } + if (id != -1) { + drawToken( id); + } + timerCalc(); + } +} + +function onTouch(zone, e) { + state.cnt = 1; + if (e) { + var id = Math.floor((state.listy + e.y - AR.y) / TOKEN_HEIGHT); + if (id == state.id || tokens.length == 0 || id >= tokens.length) { + id = -1; + } + if (state.id != id) { + if (id != -1) { + // scroll token into view if necessary + var fakee = {b:1,x:0,y:0,dx:0,dy:0}; + var y = id * TOKEN_HEIGHT - state.listy; + if (y < 0) { + fakee.dy -= y; + y = 0; + } + y += TOKEN_HEIGHT; + if (y > AR.h) { + fakee.dy -= (y - AR.h); + } + onDrag(fakee); + } + changeId(id); + } + } } function onSwipe(e) { + state.cnt = 1; + let id = state.id; if (e == 1) { exitApp(); } - if (e == -1 && state.curtoken != -1 && tokens[state.curtoken].period <= 0) { - tokens[state.curtoken].period--; + if (e == -1 && id != -1 && tokens[id].period <= 0) { + tokens[id].period--; let newsettings={tokens:tokens,misc:settings.misc}; require("Storage").writeJSON("authentiwatch.json", newsettings); - state.nextTime = 0; - state.otp = ""; - state.hide = 2; + state.hotp.hotp = CALCULATING; + drawToken(id); } - draw(); } function bangle1Btn(e) { + state.cnt = 1; if (tokens.length > 0) { - if (state.curtoken == -1) { - state.curtoken = state.prevcur; - } else { - switch (e) { - case -1: state.curtoken--; break; - case 1: state.curtoken++; break; - } + var id = state.id; + switch (e) { + case -1: id--; break; + case 1: id++; break; } - state.curtoken = Math.max(state.curtoken, 0); - state.curtoken = Math.min(state.curtoken, tokens.length - 1); - state.listy = state.curtoken * TOKEN_HEIGHT; - state.listy -= (Bangle.appRect.h - TOKEN_HEIGHT) / 2; - state.listy = Math.min(state.listy, tokens.length * TOKEN_HEIGHT - Bangle.appRect.h); - state.listy = Math.max(state.listy, 0); - var fakee = {}; - fakee.y = state.curtoken * TOKEN_HEIGHT - state.listy + Bangle.appRect.y; - state.curtoken = -1; - state.nextTime = 0; - onTouch(0, fakee); + id = E.clip(id, 0, tokens.length - 1); + var fakee = {b:1,x:0,y:0,dx:0}; + fakee.dy = state.listy - E.clip(id * TOKEN_HEIGHT - half(AR.h - TOKEN_HEIGHT), 0, tokens.length * TOKEN_HEIGHT - AR.h); + onDrag(fakee); + changeId(id); } else { - draw(); // resets idle timer + timerCalc(); } } @@ -356,8 +413,7 @@ if (typeof BTN2 == 'number') { setWatch(function(){exitApp(); }, BTN1, {edge:"falling", debounce:50}); } Bangle.loadWidgets(); - -// Clear the screen once, at startup -g.clear(); -draw(); +const AR = Bangle.appRect; +g.clear(); // Clear the screen once, at startup +startupDraw(); Bangle.drawWidgets(); From 14aca8db0fc687e162ec59b735399f09c9c6bf68 Mon Sep 17 00:00:00 2001 From: Joseph Paul Date: Sun, 20 Mar 2022 09:55:00 +0100 Subject: [PATCH 13/64] Cycling: Re-enable widgets; optimise font sizes --- .../{cscsensor.app.js => cycling.app.js} | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) rename apps/cycling/{cscsensor.app.js => cycling.app.js} (93%) diff --git a/apps/cycling/cscsensor.app.js b/apps/cycling/cycling.app.js similarity index 93% rename from apps/cycling/cscsensor.app.js rename to apps/cycling/cycling.app.js index 9de3f5a3e..4b53046d9 100644 --- a/apps/cycling/cscsensor.app.js +++ b/apps/cycling/cycling.app.js @@ -1,6 +1,6 @@ const Layout = require('Layout'); -const SETTINGS_FILE = 'cscsensor.json'; +const SETTINGS_FILE = 'cycling.json'; const storage = require('Storage'); const RECONNECT_TIMEOUT = 4000; @@ -57,7 +57,7 @@ class CSCSensor { connect() { this.connected = false; this.setLayout(0); - this.display.setStatus("Connecting..."); + this.display.setStatus("Connecting"); console.log("Trying to connect to BLE CSC"); // Hook up events @@ -180,7 +180,8 @@ class CSCDisplay { constructor() { this.metric = true; this.fontLabel = "6x8"; - this.fontMed = "15%"; + this.fontSmall = "15%"; + this.fontMed = "18%"; this.fontLarge = "32%"; this.currentLayout = "status"; this.layouts = {}; @@ -206,7 +207,7 @@ class CSCDisplay { fillx: 1, pad: 4, bgCol: "#000", - height: 32, + height: 36, c: [ {type: undefined, width: 32, halign: -1}, {type: "txt", id: "time", label: "00:00", font: this.fontMed, bgCol: "#000", col: "#fff", width: 122}, @@ -218,7 +219,7 @@ class CSCDisplay { id: "stats_g", fillx: 1, bgCol: "#fff", - height: 32, + height: 36, c: [ { type: "v", @@ -226,7 +227,7 @@ class CSCDisplay { bgCol: "#fff", c: [ {type: "txt", id: "max_l", label: "MAX", font: this.fontLabel, col: "#000"}, - {type: "txt", id: "max", label: "00.0", font: this.fontMed, bgCol: "#fff", col: "#000", width: 69}, + {type: "txt", id: "max", label: "00.0", font: this.fontSmall, bgCol: "#fff", col: "#000", width: 69}, ], }, { @@ -235,7 +236,7 @@ class CSCDisplay { bgCol: "#fff", c: [ {type: "txt", id: "avg_l", label: "AVG", font: this.fontLabel, col: "#000"}, - {type: "txt", id: "avg", label: "00.0", font: this.fontMed, bgCol: "#fff", col: "#000", width: 69}, + {type: "txt", id: "avg", label: "00.0", font: this.fontSmall, bgCol: "#fff", col: "#000", width: 69}, ], }, {type: "txt", id: "stats_u", label: " km/h", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 22, r: 90}, @@ -245,6 +246,7 @@ class CSCDisplay { }); this.layouts.distance = new Layout({ type: "v", + bgCol: "#fff", c: [ { type: "h", @@ -264,12 +266,12 @@ class CSCDisplay { id: "totald_g", fillx: 1, pad: 4, - bgCol: "#000", + bgCol: "#fff", height: 32, c: [ - {type: "txt", id: "totald_l", label: "TTL", font: this.fontLabel, bgCol: "#000", col: "#fff", width: 36}, - {type: "txt", id: "totald", label: "0", font: this.fontMed, bgCol: "#000", col: "#fff", width: 118}, - {type: "txt", id: "totald_u", label: "km", font: this.fontLabel, bgCol: "#000", col: "#fff", width: 22, r: 90}, + {type: "txt", id: "totald_l", label: "TTL", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 36}, + {type: "txt", id: "totald", label: "0", font: this.fontMed, bgCol: "#fff", col: "#000", width: 118}, + {type: "txt", id: "totald_u", label: "km", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 22, r: 90}, ] }, ], @@ -284,7 +286,7 @@ class CSCDisplay { bgCol: "#fff", height: 100, c: [ - {type: "txt", id: "status", label: "Bangle Cycling", font: this.fontMed, bgCol: "#fff", col: "#000", width: 176, wrap: 1}, + {type: "txt", id: "status", label: "Bangle Cycling", font: this.fontSmall, bgCol: "#fff", col: "#000", width: 176, wrap: 1}, ] }, { @@ -309,6 +311,7 @@ class CSCDisplay { g.clear(); this.layouts[layout].update(); this.layouts[layout].render(); + Bangle.drawWidgets(); } renderIfLayoutActive(layout, node) { @@ -380,7 +383,12 @@ class CSCDisplay { } setTotalDistance(distance) { - this.layouts.distance.totald.label = this.convertDistance(distance).toFixed(1) + const distance = this.convertDistance(distance); + if (distance >= 1000) { + this.layouts.distance.totald.label = String(Math.round(distance)); + } else { + this.layouts.distance.totald.label = distance.toFixed(1); + } this.renderIfLayoutActive("distance", this.layouts.distance.totald_g); } @@ -415,6 +423,5 @@ Bangle.setUI("updown", d => { sensor.interact(d); }); +Bangle.loadWidgets(); sensor.connect(); -// Bangle.loadWidgets(); -// Bangle.drawWidgets(); From a44c62630a0149abd9324c1a00b5ac13319abeaa Mon Sep 17 00:00:00 2001 From: Joseph Paul Date: Sun, 20 Mar 2022 10:51:00 +0100 Subject: [PATCH 14/64] Cycling: Implement settings --- apps/cycling/README.md | 20 +++++++---- apps/cycling/cycling.app.js | 60 ++++++++++++++++++++++--------- apps/cycling/settings.js | 72 +++++++++++++++++++++++-------------- 3 files changed, 103 insertions(+), 49 deletions(-) diff --git a/apps/cycling/README.md b/apps/cycling/README.md index 61ba1d455..7ba8ee224 100644 --- a/apps/cycling/README.md +++ b/apps/cycling/README.md @@ -1,7 +1,7 @@ # Cycling > Displays data from a BLE Cycling Speed and Cadence sensor. -*Fork of the CSCSensor app using the layout library and separate module for CSC functionality* +*This is a fork of the CSCSensor app using the layout library and separate module for CSC functionality. It also drops persistence of total distance on the Bangle, as this information is also persisted on the sensor itself. Further, it allows configuration of display units (metric/imperial) independent of chosen locale. Finally, multiple sensors can be used and wheel circumference can be configured for each sensor individually.* The following data are displayed: - curent speed @@ -11,16 +11,24 @@ The following data are displayed: - trip distance - total distance -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, a absolute value that persists throughout the lifetime of the sensor and never rolls over. +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. **Cadence / Crank features are currently not implemented** -# TODO -* Settings: imperial/metric -* Store circumference per device address +## Usage +Open the app and connect to a CSC sensor. + +Upon first connection, close the app afain and enter the settings app to configure the wheel circumference. The total circumference is (cm + mm) - it is split up into two values for ease of configuration. Check the status screen inside the Cycling app while connected to see the address of the currently connected sensor (if you need to differentiate between multiple sensors). + +Inside the Cycling app, use button / tap screen to: +- cycle through screens (if connected) +- reconnect (if connection aborted) + +## TODO * Sensor battery status * Implement crank events / show cadence * Bangle.js 1 compatibility +* Allow setting CWR on the sensor (this is a feature intended by the BLE CSC spec, in case the sensor is replaced or transferred to a different bike) -# Development +## Development There is a "mock" version of the `blecsc` module, which can be used to test features in the emulator. Check `blecsc-emu.js` for usage. diff --git a/apps/cycling/cycling.app.js b/apps/cycling/cycling.app.js index 4b53046d9..268284a29 100644 --- a/apps/cycling/cycling.app.js +++ b/apps/cycling/cycling.app.js @@ -1,7 +1,11 @@ const Layout = require('Layout'); +const storage = require('Storage'); const SETTINGS_FILE = 'cycling.json'; -const storage = require('Storage'); +const SETTINGS_DEFAULT = { + sensors: {}, + metric: true, +}; const RECONNECT_TIMEOUT = 4000; const MAX_CONN_ATTEMPTS = 2; @@ -13,8 +17,8 @@ class CSCSensor { this.display = display; // Load settings - this.settings = storage.readJSON(SETTINGS_FILE, 1) || {}; - this.wheelCirc = (this.settings.wheelcirc || 2230) / 1000; // unit: m + this.settings = storage.readJSON(SETTINGS_FILE, true) || SETTINGS_DEFAULT; + this.wheelCirc = undefined; // CSC runtime variables this.movingTime = 0; // unit: s @@ -34,13 +38,16 @@ class CSCSensor { // Layout configuration this.layout = 0; this.display.useMetricUnits(true); - // this.display.useMetricUnits(!require("locale").speed(1).toString().endsWith("mph")); + this.deviceAddress = undefined; + this.display.useMetricUnits((this.settings.metric)); } onDisconnect(event) { console.log("disconnected ", event); this.connected = false; + this.wheelCirc = undefined; + this.setLayout(0); this.display.setDeviceAddress("unknown"); @@ -51,7 +58,23 @@ class CSCSensor { this.display.setStatus("Disconnected"); setTimeout(this.connect.bind(this), RECONNECT_TIMEOUT); } + } + loadCircumference() { + if (!this.deviceAddress) return; + + // Add sensor to settings if not present + if (!this.settings.sensors[this.deviceAddress]) { + this.settings.sensors[this.deviceAddress] = { + cm: 223, + mm: 0, + }; + storage.writeJSON(SETTINGS_FILE, this.settings); + } + + const high = this.settings.sensors[this.deviceAddress].cm || 223; + const low = this.settings.sensors[this.deviceAddress].mm || 0; + this.wheelCirc = (10*high + low) / 1000; } connect() { @@ -70,12 +93,14 @@ class CSCSensor { this.failedAttempts = 0; this.failed = false; this.connected = true; - var addr = this.blecsc.getDeviceAddress(); - console.log("Connected to " + addr); + this.deviceAddress = this.blecsc.getDeviceAddress(); + console.log("Connected to " + this.deviceAddress); - this.display.setDeviceAddress(addr); + this.display.setDeviceAddress(this.deviceAddress); this.display.setStatus("Connected"); + this.loadCircumference(); + // Switch to speed screen in 2s setTimeout(function() { this.setLayout(1); @@ -90,9 +115,9 @@ class CSCSensor { disconnect() { this.blecsc.disconnect(); - this.connected = false; + this.reset(); this.setLayout(0); - this.display.setStatus("Disconnected") + this.display.setStatus("Disconnected"); } setLayout(num) { @@ -110,6 +135,7 @@ class CSCSensor { this.connected = false; this.failed = false; this.failedAttempts = 0; + this.wheelCirc = undefined; } interact(d) { @@ -127,7 +153,7 @@ class CSCSensor { updateScreen() { var tripDist = this.cwrTrip * this.wheelCirc; - var avgSpeed = this.movingTime > 3 ? tripDist / this.movingTime : 0 + var avgSpeed = this.movingTime > 3 ? tripDist / this.movingTime : 0; this.display.setTotalDistance(this.cwr * this.wheelCirc); this.display.setTripDistance(tripDist); @@ -297,7 +323,7 @@ class CSCDisplay { bgCol: "#fff", height: 32, c: [ - { type: "txt", id: "addr_l", label: "MAC", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 36 }, + { type: "txt", id: "addr_l", label: "ADDR", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 36 }, { type: "txt", id: "addr", label: "unknown", font: this.fontLabel, bgCol: "#fff", col: "#000", width: 140 }, ] }, @@ -316,13 +342,13 @@ class CSCDisplay { renderIfLayoutActive(layout, node) { if (layout != this.currentLayout) return; - this.layouts[layout].render(node) + this.layouts[layout].render(node); } useMetricUnits(metric) { this.metric = metric; - console.log("using " + (metric ? "metric" : "imperial") + " units"); + // console.log("using " + (metric ? "metric" : "imperial") + " units"); var speedUnit = metric ? "km/h" : "mph"; this.layouts.speed.speed_u.label = speedUnit; @@ -378,12 +404,12 @@ class CSCDisplay { } setTripDistance(distance) { - this.layouts.distance.tripd.label = this.convertDistance(distance).toFixed(1) + this.layouts.distance.tripd.label = this.convertDistance(distance).toFixed(1); this.renderIfLayoutActive("distance", this.layouts.distance.tripd_g); } setTotalDistance(distance) { - const distance = this.convertDistance(distance); + distance = this.convertDistance(distance); if (distance >= 1000) { this.layouts.distance.totald.label = String(Math.round(distance)); } else { @@ -393,12 +419,12 @@ class CSCDisplay { } setDeviceAddress(address) { - this.layouts.status.addr.label = address + this.layouts.status.addr.label = address; this.renderIfLayoutActive("status", this.layouts.status.addr_g); } setStatus(status) { - this.layouts.status.status.label = status + this.layouts.status.status.label = status; this.renderIfLayoutActive("status", this.layouts.status.status_g); } } diff --git a/apps/cycling/settings.js b/apps/cycling/settings.js index 810d8afc0..445c4ca33 100644 --- a/apps/cycling/settings.js +++ b/apps/cycling/settings.js @@ -3,35 +3,55 @@ * @param {function} back Use back() to return to settings menu */ (function(back) { - const SETTINGS_FILE = 'cscsensor.json' - // initialize with default settings... - let s = { - 'wheelcirc': 2230, - } - // ...and overwrite them with any saved values - // This way saved values are preserved if a new version adds more settings const storage = require('Storage') - const saved = storage.readJSON(SETTINGS_FILE, 1) || {} - for (const key in saved) { - s[key] = saved[key]; - } - // creates a function to safe a specific setting, e.g. save('color')(1) - function save(key) { - return function (value) { - s[key] = value; - storage.write(SETTINGS_FILE, s); - } - } + const SETTINGS_FILE = 'cycling.json' + + // Set default values and merge with stored values + let settings = Object.assign({ + metric: true, + sensors: {}, + }, (storage.readJSON(SETTINGS_FILE, true) || {})); + const menu = { - '': { 'title': 'Cycle speed sensor' }, + '': { 'title': 'Cycling' }, '< Back': back, - 'Wheel circ.(mm)': { - value: s.wheelcirc, - min: 800, - max: 2400, - step: 5, - onchange: save('wheelcirc'), + 'Units': { + value: settings.metric, + format: v => v ? 'metric' : 'imperial', + onchange: (metric) => { + settings.metric = metric; + storage.writeJSON(SETTINGS_FILE, settings); + }, }, } + + const sensorMenus = {}; + for (var addr of Object.keys(settings.sensors)) { + // Define sub menu + sensorMenus[addr] = { + '': { title: addr }, + '< Back': () => E.showMenu(menu), + 'cm': { + value: settings.sensors[addr].cm, + min: 80, max: 240, step: 1, + onchange: (v) => { + settings.sensors[addr].cm = v; + storage.writeJSON(SETTINGS_FILE, settings); + }, + }, + '+ mm': { + value: settings.sensors[addr].mm, + min: 0, max: 9, step: 1, + onchange: (v) => { + settings.sensors[addr].mm = v; + storage.writeJSON(SETTINGS_FILE, settings); + }, + }, + }; + + // Add entry to main menu + menu[addr] = () => E.showMenu(sensorMenus[addr]); + } + E.showMenu(menu); -}) +})(load) From 433e3a4967579e056f067675011ee3205491e00b Mon Sep 17 00:00:00 2001 From: Joseph Paul Date: Sun, 20 Mar 2022 10:59:24 +0100 Subject: [PATCH 15/64] Cycling: Add JS icon --- apps/cycling/cycling.icon.js | 1 + apps/cycling/metadata.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 apps/cycling/cycling.icon.js diff --git a/apps/cycling/cycling.icon.js b/apps/cycling/cycling.icon.js new file mode 100644 index 000000000..12c597956 --- /dev/null +++ b/apps/cycling/cycling.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH/OAAIuuGFYuEGFQv/ADOlwV8wK/qwN8AAelGAguiFogACWsulFw6SERcwAFSISLnSMuAFZWCGENWllWLRSZC0vOAAovWmUslkyvbqJwIuHGC4uBAARiDdAwueL4YACMQLmfX5IAFqwwoMIowpMQ4wpGIcywDiYAA2IAAgwGq2kFwIvGC5YtPDJIuCF4gXPFxQHLF44XQFxAKOF4oXRBg4LOFwYvEEag7OBgReQNZzLNF5IXPBJlXq4vVC5Qv8R9TXQFwbvYJBgLlNbYXRBoYOEA44XfCAgAFCxgXYDI4VPC7IA/AH4A/AH4AWA")) diff --git a/apps/cycling/metadata.json b/apps/cycling/metadata.json index 917658fad..cb4260bb2 100644 --- a/apps/cycling/metadata.json +++ b/apps/cycling/metadata.json @@ -11,6 +11,7 @@ "storage": [ {"name":"cycling.app.js","url":"cycling.app.js"}, {"name":"cycling.settings.js","url":"settings.js"}, - {"name":"blecsc","url":"blecsc.js"} + {"name":"blecsc","url":"blecsc.js"}, + {"name":"cycling.img","url":"cycling.icon.js","evaluate": true} ] } From bda630ed0869bb74ce72c517773ee2caea216e55 Mon Sep 17 00:00:00 2001 From: Joseph Paul Date: Sun, 20 Mar 2022 11:05:54 +0100 Subject: [PATCH 16/64] Cycling: don't call settings function --- apps/cycling/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/cycling/settings.js b/apps/cycling/settings.js index 445c4ca33..76303379d 100644 --- a/apps/cycling/settings.js +++ b/apps/cycling/settings.js @@ -54,4 +54,4 @@ } E.showMenu(menu); -})(load) +}) From ed36b7286e2b8ff5505a504df0e429c898233f99 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Sun, 20 Mar 2022 23:11:18 +0800 Subject: [PATCH 17/64] Update app.js Workaround Bangle1 issues --- apps/authentiwatch/app.js | 97 ++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index 2734a064a..8e25e5ef2 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -135,34 +135,32 @@ function timerCalc() { let timerfn = exitApp; let timerdly = 10000; let id = state.id; - if (id != -1) { - if (state.hotp.hotp != "") { - if (tokens[id].period > 0) { - // timed HOTP - if (state.hotp.next < Date.now()) { - if (state.cnt > 0) { - --state.cnt; - state.hotp = hotp(tokens[id]); - } else { - state.hotp.hotp = ""; - } - timerdly = 1; - timerfn = updateCurrentToken; - } else { - timerdly = 1000; - timerfn = updateProgressBar; - } - } else { - // counter HOTP + if (id != -1 && state.hotp.hotp != "") { + if (tokens[id].period > 0) { + // timed HOTP + if (state.hotp.next < Date.now()) { if (state.cnt > 0) { --state.cnt; - timerdly = 30000; + state.hotp = hotp(tokens[id]); } else { state.hotp.hotp = ""; - timerdly = 1; } + timerdly = 1; timerfn = updateCurrentToken; + } else { + timerdly = 1000; + timerfn = updateProgressBar; } + } else { + // counter HOTP + if (state.cnt > 0) { + --state.cnt; + timerdly = 30000; + } else { + state.hotp.hotp = ""; + timerdly = 1; + } + timerfn = updateCurrentToken; } } if (state.drawtimer) { @@ -183,29 +181,27 @@ function updateProgressBar() { function drawProgressBar() { let id = state.id; - if (id != -1) { - if (tokens[id].period > 0) { - let rem = Math.floor((state.hotp.next - Date.now()) / 1000); - if (rem >= 0) { - let y1 = tokenY(id); - let y2 = y1 + TOKEN_HEIGHT - 1; - if (y2 >= AR.y && y1 <= AR.y2) { - // token visible - if ((y2 - 3) <= AR.y2) - { - // progress bar visible - y2 = Math.min(y2, AR.y2); - rem = Math.min(rem, tokens[id].period); - let xr = Math.floor(AR.w * rem / tokens[id].period) + AR.x; - g.setColor(g.theme.fgH) - .setBgColor(g.theme.bgH) - .fillRect(AR.x, y2 - 3, xr, y2) - .clearRect(xr + 1, y2 - 3, AR.x2, y2); - } - } else { - // token not visible - state.id = -1; + if (id != -1 && tokens[id].period > 0) { + let rem = Math.floor((state.hotp.next - Date.now()) / 1000); + if (rem >= 0) { + let y1 = tokenY(id); + let y2 = y1 + TOKEN_HEIGHT - 1; + if (y2 >= AR.y && y1 <= AR.y2) { + // token visible + if ((y2 - 3) <= AR.y2) + { + // progress bar visible + y2 = Math.min(y2, AR.y2); + rem = Math.min(rem, tokens[id].period); + let xr = Math.floor(AR.w * rem / tokens[id].period) + AR.x; + g.setColor(g.theme.fgH) + .setBgColor(g.theme.bgH) + .fillRect(AR.x, y2 - 3, xr, y2) + .clearRect(xr + 1, y2 - 3, AR.x2, y2); } + } else { + // token not visible + state.id = -1; } } } @@ -265,10 +261,10 @@ function drawToken(id) { drawProgressBar(); } } - g.setClipRect(0, 0, g.getWidth(), g.getHeight()); + g.setClipRect(0, 0, g.getWidth() - 1, g.getHeight() - 1); } -function startupDraw() { +function drawAll() { if (tokens.length > 0) { let id = 0; let y = tokenY(id); @@ -391,8 +387,13 @@ function bangle1Btn(e) { id = E.clip(id, 0, tokens.length - 1); var fakee = {b:1,x:0,y:0,dx:0}; fakee.dy = state.listy - E.clip(id * TOKEN_HEIGHT - half(AR.h - TOKEN_HEIGHT), 0, tokens.length * TOKEN_HEIGHT - AR.h); - onDrag(fakee); - changeId(id); + //onDrag(fakee); + //changeId(id); + // onDrag() (specifically g.scroll()) doesn't appear to work with the Bangle1 + state.id = id; + state.hotp.hotp = CALCULATING; + state.listy -= fakee.dy; + drawAll(); } else { timerCalc(); } @@ -415,5 +416,5 @@ if (typeof BTN2 == 'number') { Bangle.loadWidgets(); const AR = Bangle.appRect; g.clear(); // Clear the screen once, at startup -startupDraw(); +drawAll(); Bangle.drawWidgets(); From 5e92ec24dfa354523a5973f1c8923943aaca11a5 Mon Sep 17 00:00:00 2001 From: Brendan Hubble Date: Tue, 22 Mar 2022 09:05:53 +1100 Subject: [PATCH 18/64] touchtimer: ADD ability to repeat the timer - By adding the REPEAT value at the end of buzzCount loop it allows for finshed to be shown for the full buzzCount before it changes the text to REPEAT --- apps/touchtimer/ChangeLog | 3 ++- apps/touchtimer/app.js | 7 +++++++ apps/touchtimer/metadata.json | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/touchtimer/ChangeLog b/apps/touchtimer/ChangeLog index 01904c6ea..fbabe6cb7 100644 --- a/apps/touchtimer/ChangeLog +++ b/apps/touchtimer/ChangeLog @@ -1,2 +1,3 @@ 0.01: Initial creation of the touch timer app -0.02: Add settings menu \ No newline at end of file +0.02: Add settings menu +0.03: Add ability to repeat last timer diff --git a/apps/touchtimer/app.js b/apps/touchtimer/app.js index ffa1af80a..61d3a08fd 100644 --- a/apps/touchtimer/app.js +++ b/apps/touchtimer/app.js @@ -141,6 +141,13 @@ var main = () => { if (buzzCount >= settings.buzzCount) { clearInterval(buzzIntervalId); buzzIntervalId = undefined; + + buttonStartPause.value = "REPEAT"; + buttonStartPause.draw(); + buttonStartPause.value = "START"; + timerCountDown = undefined; + timerEdit.draw(); + return; } else { Bangle.buzz(settings.buzzDuration * 1000, 1); diff --git a/apps/touchtimer/metadata.json b/apps/touchtimer/metadata.json index 645a0ce18..50c5f03dd 100644 --- a/apps/touchtimer/metadata.json +++ b/apps/touchtimer/metadata.json @@ -2,7 +2,7 @@ "id": "touchtimer", "name": "Touch Timer", "shortName": "Touch Timer", - "version": "0.02", + "version": "0.03", "description": "Quickly and easily create a timer with touch-only input. The time can be easily set with a number pad.", "icon": "app.png", "tags": "tools", From 2ba72f8954dbef544555972c8f6dc47b8cd69646 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 22 Mar 2022 09:48:45 +0000 Subject: [PATCH 19/64] lightswitch 0.03: Settings page now uses built-in min/max/wrap (fix #1607) --- apps/lightswitch/ChangeLog | 1 + apps/lightswitch/metadata.json | 2 +- apps/lightswitch/settings.js | 16 ++++++++++------ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/lightswitch/ChangeLog b/apps/lightswitch/ChangeLog index 7a7ecd027..4210ccf03 100644 --- a/apps/lightswitch/ChangeLog +++ b/apps/lightswitch/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Add the option to enable touching the widget only on clock and settings. +0.03: Settings page now uses built-in min/max/wrap (fix #1607) diff --git a/apps/lightswitch/metadata.json b/apps/lightswitch/metadata.json index 902b1536b..9ac388eda 100644 --- a/apps/lightswitch/metadata.json +++ b/apps/lightswitch/metadata.json @@ -2,7 +2,7 @@ "id": "lightswitch", "name": "Light Switch Widget", "shortName": "Light Switch", - "version": "0.02", + "version": "0.03", "description": "A fast way to switch LCD backlight on/off, change the brightness and show the lock status. All in one widget.", "icon": "images/app.png", "screenshots": [ diff --git a/apps/lightswitch/settings.js b/apps/lightswitch/settings.js index aac159148..bebb16d15 100644 --- a/apps/lightswitch/settings.js +++ b/apps/lightswitch/settings.js @@ -44,9 +44,11 @@ // return entry for string value return { value: entry.value.indexOf(settings[key]), + min : 0, + max : entry.value.length-1, + wrap : true, format: v => entry.title ? entry.title[v] : entry.value[v], onchange: function(v) { - this.value = v = v >= entry.value.length ? 0 : v < 0 ? entry.value.length - 1 : v; writeSetting(key, entry.value[v], entry.drawWidgets); if (entry.exec) entry.exec(entry.value[v]); } @@ -57,8 +59,10 @@ value: settings[key] * entry.factor, step: entry.step, format: v => v > 0 ? v + entry.unit : "off", + min : entry.min, + max : entry.max, + wrap : true, onchange: function(v) { - this.value = v = v > entry.max ? entry.min : v < entry.min ? entry.max : v; writeSetting(key, v / entry.factor, entry.drawWidgets); }, }; @@ -133,16 +137,16 @@ title: "Light Switch" }, "< Back": () => back(), - "-- Widget --------": 0, + "-- Widget": 0, "Bulb col": getEntry("colors"), "Image": getEntry("image"), - "-- Control -------": 0, + "-- Control": 0, "Touch": getEntry("touchOn"), "Drag Delay": getEntry("dragDelay"), "Min Value": getEntry("minValue"), - "-- Unlock --------": 0, + "-- Unlock": 0, "TapSide": getEntry("unlockSide"), - "-- Flash ---------": 0, + "-- Flash": 0, "TapSide ": getEntry("tapSide"), "Tap": getEntry("tapOn"), "Timeout": getEntry("tOut"), From 9886125fbd796feabfea5beef484487c67324e63 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Tue, 22 Mar 2022 23:16:17 +0800 Subject: [PATCH 20/64] Update app.js Refactoring and extra range checks. --- apps/authentiwatch/app.js | 117 ++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 67 deletions(-) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index 8e25e5ef2..b2608a5dc 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -2,6 +2,7 @@ const COUNTER_TRIANGLE_SIZE = 10; const TOKEN_EXTRA_HEIGHT = 16; var TOKEN_DIGITS_HEIGHT = 30; var TOKEN_HEIGHT = TOKEN_DIGITS_HEIGHT + TOKEN_EXTRA_HEIGHT; +const SETTINGS = "authentiwatch.json"; // Hash functions const crypto = require("crypto"); const algos = { @@ -15,7 +16,7 @@ const NOT_SUPPORTED = /*LANG*/"Not supported"; // sample settings: // {tokens:[{"algorithm":"SHA1","digits":6,"period":30,"issuer":"","account":"","secret":"Bbb","label":"Aaa"}],misc:{}} -var settings = require("Storage").readJSON("authentiwatch.json", true) || {tokens:[],misc:{}}; +var settings = require("Storage").readJSON(SETTINGS, true) || {tokens:[],misc:{}}; if (settings.data ) tokens = settings.data ; /* v0.02 settings */ if (settings.tokens) tokens = settings.tokens; /* v0.03+ settings */ @@ -134,14 +135,13 @@ half = n => Math.floor(n / 2); function timerCalc() { let timerfn = exitApp; let timerdly = 10000; - let id = state.id; - if (id != -1 && state.hotp.hotp != "") { - if (tokens[id].period > 0) { + if (state.id != -1 && state.hotp.hotp != "") { + if (tokens[state.id].period > 0) { // timed HOTP if (state.hotp.next < Date.now()) { if (state.cnt > 0) { - --state.cnt; - state.hotp = hotp(tokens[id]); + state.cnt--; + state.hotp = hotp(tokens[state.id]); } else { state.hotp.hotp = ""; } @@ -154,7 +154,7 @@ function timerCalc() { } else { // counter HOTP if (state.cnt > 0) { - --state.cnt; + state.cnt--; timerdly = 30000; } else { state.hotp.hotp = ""; @@ -215,7 +215,7 @@ function drawToken(id) { var y2 = y1 + TOKEN_HEIGHT - 1; var adj, lbl; g.setClipRect(x1, Math.max(y1, AR.y), x2, Math.min(y2, AR.y2)); - lbl = tokens[id].label.substr(0, 10); + lbl = (id >= 0 && id < tokens.length) ? tokens[id].label.substr(0, 10) : ""; if (id === state.id) { g.setColor(g.theme.fgH) .setBgColor(g.theme.bgH); @@ -264,31 +264,24 @@ function drawToken(id) { g.setClipRect(0, 0, g.getWidth() - 1, g.getHeight() - 1); } -function drawAll() { - if (tokens.length > 0) { - let id = 0; - let y = tokenY(id); - while (id < tokens.length && y < AR.y2) { - if ((y + TOKEN_HEIGHT) > AR.y) { - drawToken(id); - } - id++; - y += TOKEN_HEIGHT; +function changeId(id) { + if (id != state.id) { + state.hotp.hotp = CALCULATING; + let pid = state.id; + state.id = id; + if (pid != -1) { + drawToken(pid); + } + if (id != -1) { + drawToken( id); } - } else { - let x = AR.x + half(AR.w); - let y = AR.y + half(AR.h); - g.setFont("Vector", TOKEN_DIGITS_HEIGHT) - .setFontAlign(0, 0, 0) - .drawString(NO_TOKENS, x, y, false); } - timerCalc(); } function onDrag(e) { state.cnt = 1; if (e.b != 0 && e.dy != 0) { - var y = E.clip(state.listy - e.dy, 0, tokens.length * TOKEN_HEIGHT - AR.h); + var y = E.clip(state.listy - E.clip(e.dy, -AR.h, AR.h), 0, Math.max(0, tokens.length * TOKEN_HEIGHT - AR.h)); if (state.listy != y) { var id, dy = state.listy - y; state.listy = y; @@ -319,21 +312,6 @@ function onDrag(e) { } } -function changeId(id) { - if (id != state.id) { - state.hotp.hotp = CALCULATING; - let pid = state.id; - state.id = id; - if (pid != -1) { - drawToken(pid); - } - if (id != -1) { - drawToken( id); - } - timerCalc(); - } -} - function onTouch(zone, e) { state.cnt = 1; if (e) { @@ -344,36 +322,39 @@ function onTouch(zone, e) { if (state.id != id) { if (id != -1) { // scroll token into view if necessary - var fakee = {b:1,x:0,y:0,dx:0,dy:0}; + var dy = 0; var y = id * TOKEN_HEIGHT - state.listy; if (y < 0) { - fakee.dy -= y; + dy -= y; y = 0; } y += TOKEN_HEIGHT; if (y > AR.h) { - fakee.dy -= (y - AR.h); + dy -= (y - AR.h); } - onDrag(fakee); + onDrag({b:1, dy:dy}); } changeId(id); } } + timerCalc(); } function onSwipe(e) { state.cnt = 1; - let id = state.id; - if (e == 1) { + switch (e) { + case 1: exitApp(); + break; + case -1: + if (state.id != -1 && tokens[state.id].period <= 0) { + tokens[state.id].period--; + require("Storage").writeJSON(SETTINGS, {tokens:tokens, misc:settings.misc}); + state.hotp.hotp = CALCULATING; + drawToken(state.id); + } } - if (e == -1 && id != -1 && tokens[id].period <= 0) { - tokens[id].period--; - let newsettings={tokens:tokens,misc:settings.misc}; - require("Storage").writeJSON("authentiwatch.json", newsettings); - state.hotp.hotp = CALCULATING; - drawToken(id); - } + timerCalc(); } function bangle1Btn(e) { @@ -385,18 +366,11 @@ function bangle1Btn(e) { case 1: id++; break; } id = E.clip(id, 0, tokens.length - 1); - var fakee = {b:1,x:0,y:0,dx:0}; - fakee.dy = state.listy - E.clip(id * TOKEN_HEIGHT - half(AR.h - TOKEN_HEIGHT), 0, tokens.length * TOKEN_HEIGHT - AR.h); - //onDrag(fakee); - //changeId(id); - // onDrag() (specifically g.scroll()) doesn't appear to work with the Bangle1 - state.id = id; - state.hotp.hotp = CALCULATING; - state.listy -= fakee.dy; - drawAll(); - } else { - timerCalc(); + var dy = state.listy - E.clip(id * TOKEN_HEIGHT - half(AR.h - TOKEN_HEIGHT), 0, Math.max(0, tokens.length * TOKEN_HEIGHT - AR.h)); + onDrag({b:1, dy:dy}); + changeId(id); } + timerCalc(); } function exitApp() { @@ -415,6 +389,15 @@ if (typeof BTN2 == 'number') { } Bangle.loadWidgets(); const AR = Bangle.appRect; -g.clear(); // Clear the screen once, at startup -drawAll(); +// draw the initial display +g.clear(); +if (tokens.length > 0) { + state.listy = AR.h; + onDrag({b:1, dy:AR.h}); +} else { + g.setFont("Vector", TOKEN_DIGITS_HEIGHT) + .setFontAlign(0, 0, 0) + .drawString(NO_TOKENS, AR.x + half(AR.w), AR.y + half(AR.h), false); +} +timerCalc(); Bangle.drawWidgets(); From 9abc9296df0be1144faec8a6b49109b8d9ef9b04 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 22 Mar 2022 15:22:05 +0000 Subject: [PATCH 21/64] hrm 0.09: Grey out BPM until confidence is over 50% --- apps/hrm/ChangeLog | 1 + apps/hrm/heartrate.js | 5 ++--- apps/hrm/metadata.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/hrm/ChangeLog b/apps/hrm/ChangeLog index e559adfb6..f05a9dc13 100644 --- a/apps/hrm/ChangeLog +++ b/apps/hrm/ChangeLog @@ -6,3 +6,4 @@ 0.06: Add widgets 0.07: Update scaling for new firmware 0.08: Don't force backlight on/watch unlocked on Bangle 2 +0.09: Grey out BPM until confidence is over 50% diff --git a/apps/hrm/heartrate.js b/apps/hrm/heartrate.js index 305f0e1bc..703b60c01 100644 --- a/apps/hrm/heartrate.js +++ b/apps/hrm/heartrate.js @@ -35,9 +35,9 @@ function onHRM(h) { g.clearRect(0,24,g.getWidth(),80); g.setFont("6x8").drawString("Confidence "+hrmInfo.confidence+"%", px, 75); var str = hrmInfo.bpm; - g.setFontVector(40).drawString(str,px,45); + g.setFontVector(40).setColor(hrmInfo.confidence > 50 ? g.theme.fg : "#888").drawString(str,px,45); px += g.stringWidth(str)/2; - g.setFont("6x8"); + g.setFont("6x8").setColor(g.theme.fg); g.drawString("BPM",px+15,45); } Bangle.on('HRM', onHRM); @@ -101,4 +101,3 @@ function readHRM() { lastHrmPt = [hrmOffset, y]; } } - diff --git a/apps/hrm/metadata.json b/apps/hrm/metadata.json index 3e94c163c..10821d094 100644 --- a/apps/hrm/metadata.json +++ b/apps/hrm/metadata.json @@ -1,7 +1,7 @@ { "id": "hrm", "name": "Heart Rate Monitor", - "version": "0.08", + "version": "0.09", "description": "Measure your heart rate and see live sensor data", "icon": "heartrate.png", "tags": "health", From 1d7c090c21a10aad2dbc64ebb9c95de05d432bdb Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Tue, 22 Mar 2022 23:26:50 +0800 Subject: [PATCH 22/64] Update app.js Update progress bar on Bangle1 button presses. Clean up timer before exiting. --- apps/authentiwatch/app.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index b2608a5dc..58d712451 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -369,11 +369,15 @@ function bangle1Btn(e) { var dy = state.listy - E.clip(id * TOKEN_HEIGHT - half(AR.h - TOKEN_HEIGHT), 0, Math.max(0, tokens.length * TOKEN_HEIGHT - AR.h)); onDrag({b:1, dy:dy}); changeId(id); + drawProgressBar(); } timerCalc(); } function exitApp() { + if (state.drawtimer) { + clearTimeout(state.drawtimer); + } Bangle.showLauncher(); } From 94207739c08a368b7f76c38eb9f3879757a77f94 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Wed, 23 Mar 2022 14:22:17 +0800 Subject: [PATCH 23/64] Update app.js Refactoring --- apps/authentiwatch/app.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index 58d712451..aae570e13 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -16,7 +16,7 @@ const NOT_SUPPORTED = /*LANG*/"Not supported"; // sample settings: // {tokens:[{"algorithm":"SHA1","digits":6,"period":30,"issuer":"","account":"","secret":"Bbb","label":"Aaa"}],misc:{}} -var settings = require("Storage").readJSON(SETTINGS, true) || {tokens:[],misc:{}}; +var settings = require("Storage").readJSON(SETTINGS, true) || {tokens:[], misc:{}}; if (settings.data ) tokens = settings.data ; /* v0.02 settings */ if (settings.tokens) tokens = settings.tokens; /* v0.03+ settings */ @@ -106,9 +106,9 @@ function hotp(token) { } // Tokens are displayed in three states: -// 1. Unselected (state.id==-1) -// 2. Selected, inactive (no code) (state.id!=-1,state.hotp.hotp=="") -// 3. Selected, active (code showing) (state.id!=-1,state.hotp.hotp!="") +// 1. Unselected (state.id<0) +// 2. Selected, inactive (no code) (state.id>=0,state.hotp.hotp=="") +// 3. Selected, active (code showing) (state.id>=0,state.hotp.hotp!="") var fontszCache = {}; var state = { listy:0, // list scroll position @@ -135,7 +135,7 @@ half = n => Math.floor(n / 2); function timerCalc() { let timerfn = exitApp; let timerdly = 10000; - if (state.id != -1 && state.hotp.hotp != "") { + if (state.id >= 0 && state.hotp.hotp != "") { if (tokens[state.id].period > 0) { // timed HOTP if (state.hotp.next < Date.now()) { @@ -181,7 +181,7 @@ function updateProgressBar() { function drawProgressBar() { let id = state.id; - if (id != -1 && tokens[id].period > 0) { + if (id >= 0 && tokens[id].period > 0) { let rem = Math.floor((state.hotp.next - Date.now()) / 1000); if (rem >= 0) { let y1 = tokenY(id); @@ -269,10 +269,10 @@ function changeId(id) { state.hotp.hotp = CALCULATING; let pid = state.id; state.id = id; - if (pid != -1) { + if (pid >= 0) { drawToken(pid); } - if (id != -1) { + if (id >= 0) { drawToken( id); } } @@ -320,7 +320,7 @@ function onTouch(zone, e) { id = -1; } if (state.id != id) { - if (id != -1) { + if (id >= 0) { // scroll token into view if necessary var dy = 0; var y = id * TOKEN_HEIGHT - state.listy; @@ -347,7 +347,7 @@ function onSwipe(e) { exitApp(); break; case -1: - if (state.id != -1 && tokens[state.id].period <= 0) { + if (state.id >= 0 && tokens[state.id].period <= 0) { tokens[state.id].period--; require("Storage").writeJSON(SETTINGS, {tokens:tokens, misc:settings.misc}); state.hotp.hotp = CALCULATING; From e76535fece1bad8660c986c228f663f1faff0817 Mon Sep 17 00:00:00 2001 From: Brendan Hubble Date: Wed, 23 Mar 2022 18:44:01 +1100 Subject: [PATCH 24/64] touchtimer: ADD 5 second count down buzzer - This is a configurable option (Default is off) for the timer to buzz each second when there are 5 seconds left. --- apps/touchtimer/ChangeLog | 1 + apps/touchtimer/app.js | 8 ++++++++ apps/touchtimer/metadata.json | 2 +- apps/touchtimer/settings.js | 8 ++++++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/touchtimer/ChangeLog b/apps/touchtimer/ChangeLog index fbabe6cb7..0969a3da4 100644 --- a/apps/touchtimer/ChangeLog +++ b/apps/touchtimer/ChangeLog @@ -1,3 +1,4 @@ 0.01: Initial creation of the touch timer app 0.02: Add settings menu 0.03: Add ability to repeat last timer +0.04: Add 5 second count down buzzer diff --git a/apps/touchtimer/app.js b/apps/touchtimer/app.js index 61d3a08fd..c2f2fb5e9 100644 --- a/apps/touchtimer/app.js +++ b/apps/touchtimer/app.js @@ -126,6 +126,14 @@ var main = () => { timerIntervalId = setInterval(() => { timerCountDown.draw(); + // Buzz lightly when there are less then 5 seconds left + if (settings.countDownBuzz) { + var remainingSeconds = timerCountDown.getAdjustedTime().seconds; + if (remainingSeconds <= 5 && remainingSeconds > 0) { + Bangle.buzz(); + } + } + if (timerCountDown.isFinished()) { buttonStartPause.value = "FINISHED!"; buttonStartPause.draw(); diff --git a/apps/touchtimer/metadata.json b/apps/touchtimer/metadata.json index 50c5f03dd..0f2b9f491 100644 --- a/apps/touchtimer/metadata.json +++ b/apps/touchtimer/metadata.json @@ -2,7 +2,7 @@ "id": "touchtimer", "name": "Touch Timer", "shortName": "Touch Timer", - "version": "0.03", + "version": "0.04", "description": "Quickly and easily create a timer with touch-only input. The time can be easily set with a number pad.", "icon": "app.png", "tags": "tools", diff --git a/apps/touchtimer/settings.js b/apps/touchtimer/settings.js index 885670f57..79424f250 100644 --- a/apps/touchtimer/settings.js +++ b/apps/touchtimer/settings.js @@ -31,6 +31,14 @@ writeSettings(settings); }, }, + "CountDown Buzz": { + value: !!settings.countDownBuzz, + format: value => value?"On":"Off", + onchange: (value) => { + settings.countDownBuzz = value; + writeSettings(settings); + }, + }, "Pause Between": { value: settings.pauseBetween, min: 1, From b26d3416092f8ad12fa790b012a3336a93e8a18a Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Wed, 23 Mar 2022 16:13:57 +0800 Subject: [PATCH 25/64] Update app.js Refactoring --- apps/authentiwatch/app.js | 46 +++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index aae570e13..5ea8dd476 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -182,7 +182,7 @@ function updateProgressBar() { function drawProgressBar() { let id = state.id; if (id >= 0 && tokens[id].period > 0) { - let rem = Math.floor((state.hotp.next - Date.now()) / 1000); + let rem = Math.min(tokens[id].period, Math.floor((state.hotp.next - Date.now()) / 1000)); if (rem >= 0) { let y1 = tokenY(id); let y2 = y1 + TOKEN_HEIGHT - 1; @@ -192,7 +192,6 @@ function drawProgressBar() { { // progress bar visible y2 = Math.min(y2, AR.y2); - rem = Math.min(rem, tokens[id].period); let xr = Math.floor(AR.w * rem / tokens[id].period) + AR.x; g.setColor(g.theme.fgH) .setBgColor(g.theme.bgH) @@ -209,13 +208,13 @@ function drawProgressBar() { // id = token ID number (0...) function drawToken(id) { - var x1 = AR.x; - var y1 = tokenY(id); - var x2 = AR.x2; - var y2 = y1 + TOKEN_HEIGHT - 1; - var adj, lbl; + let x1 = AR.x; + let y1 = tokenY(id); + let x2 = AR.x2; + let y2 = y1 + TOKEN_HEIGHT - 1; + let lbl = (id >= 0 && id < tokens.length) ? tokens[id].label.substr(0, 10) : ""; + let adj; g.setClipRect(x1, Math.max(y1, AR.y), x2, Math.min(y2, AR.y2)); - lbl = (id >= 0 && id < tokens.length) ? tokens[id].label.substr(0, 10) : ""; if (id === state.id) { g.setColor(g.theme.fgH) .setBgColor(g.theme.bgH); @@ -281,9 +280,9 @@ function changeId(id) { function onDrag(e) { state.cnt = 1; if (e.b != 0 && e.dy != 0) { - var y = E.clip(state.listy - E.clip(e.dy, -AR.h, AR.h), 0, Math.max(0, tokens.length * TOKEN_HEIGHT - AR.h)); + let y = E.clip(state.listy - E.clip(e.dy, -AR.h, AR.h), 0, Math.max(0, tokens.length * TOKEN_HEIGHT - AR.h)); if (state.listy != y) { - var id, dy = state.listy - y; + let id, dy = state.listy - y; state.listy = y; g.setClipRect(AR.x, AR.y, AR.x2, AR.y2) .scroll(0, dy); @@ -315,15 +314,15 @@ function onDrag(e) { function onTouch(zone, e) { state.cnt = 1; if (e) { - var id = Math.floor((state.listy + e.y - AR.y) / TOKEN_HEIGHT); + let id = Math.floor((state.listy + e.y - AR.y) / TOKEN_HEIGHT); if (id == state.id || tokens.length == 0 || id >= tokens.length) { id = -1; } if (state.id != id) { if (id >= 0) { // scroll token into view if necessary - var dy = 0; - var y = id * TOKEN_HEIGHT - state.listy; + let dy = 0; + let y = id * TOKEN_HEIGHT - state.listy; if (y < 0) { dy -= y; y = 0; @@ -357,17 +356,16 @@ function onSwipe(e) { timerCalc(); } -function bangle1Btn(e) { +function bangleBtn(e) { state.cnt = 1; if (tokens.length > 0) { - var id = state.id; + let id = state.id; switch (e) { case -1: id--; break; case 1: id++; break; } id = E.clip(id, 0, tokens.length - 1); - var dy = state.listy - E.clip(id * TOKEN_HEIGHT - half(AR.h - TOKEN_HEIGHT), 0, Math.max(0, tokens.length * TOKEN_HEIGHT - AR.h)); - onDrag({b:1, dy:dy}); + onDrag({b:1, dy:state.listy - E.clip(id * TOKEN_HEIGHT - half(AR.h - TOKEN_HEIGHT), 0, Math.max(0, tokens.length * TOKEN_HEIGHT - AR.h))}); changeId(id); drawProgressBar(); } @@ -384,12 +382,14 @@ function exitApp() { Bangle.on('touch', onTouch); Bangle.on('drag' , onDrag ); Bangle.on('swipe', onSwipe); -if (typeof BTN2 == 'number') { - setWatch(function(){bangle1Btn(-1);}, BTN1, {edge:"rising" , debounce:50, repeat:true}); - setWatch(function(){exitApp(); }, BTN2, {edge:"falling", debounce:50}); - setWatch(function(){bangle1Btn( 1);}, BTN3, {edge:"rising" , debounce:50, repeat:true}); -} else { - setWatch(function(){exitApp(); }, BTN1, {edge:"falling", debounce:50}); +if (typeof BTN1 == 'number') { + if (typeof BTN2 == 'number' && typeof BTN3 == 'number') { + setWatch(()=>bangleBtn(-1), BTN1, {edge:"rising" , debounce:50, repeat:true}); + setWatch(()=>exitApp() , BTN2, {edge:"falling", debounce:50}); + setWatch(()=>bangleBtn( 1), BTN3, {edge:"rising" , debounce:50, repeat:true}); + } else { + setWatch(()=>exitApp() , BTN1, {edge:"falling", debounce:50}); + } } Bangle.loadWidgets(); const AR = Bangle.appRect; From bf30b35ac07fc3bde3299e0dd5dc6dc2886c9d82 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Wed, 23 Mar 2022 16:18:21 +0800 Subject: [PATCH 26/64] Update app.js Fix progress bar drawing. --- apps/authentiwatch/app.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index 5ea8dd476..69e4a1ae1 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -188,15 +188,16 @@ function drawProgressBar() { let y2 = y1 + TOKEN_HEIGHT - 1; if (y2 >= AR.y && y1 <= AR.y2) { // token visible - if ((y2 - 3) <= AR.y2) + y1 = y2 - 3; + if (y1 <= AR.y2) { // progress bar visible y2 = Math.min(y2, AR.y2); let xr = Math.floor(AR.w * rem / tokens[id].period) + AR.x; g.setColor(g.theme.fgH) .setBgColor(g.theme.bgH) - .fillRect(AR.x, y2 - 3, xr, y2) - .clearRect(xr + 1, y2 - 3, AR.x2, y2); + .fillRect(AR.x, y1, xr, y2) + .clearRect(xr + 1, y1, AR.x2, y2); } } else { // token not visible From 47332eba50f743ccfb7e85439cc41f5321bf910f Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Wed, 23 Mar 2022 16:26:48 +0800 Subject: [PATCH 27/64] Update app.js Simplify. --- apps/authentiwatch/app.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index 69e4a1ae1..3920ebddd 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -360,12 +360,7 @@ function onSwipe(e) { function bangleBtn(e) { state.cnt = 1; if (tokens.length > 0) { - let id = state.id; - switch (e) { - case -1: id--; break; - case 1: id++; break; - } - id = E.clip(id, 0, tokens.length - 1); + let id = E.clip(state.id + e, 0, tokens.length - 1); onDrag({b:1, dy:state.listy - E.clip(id * TOKEN_HEIGHT - half(AR.h - TOKEN_HEIGHT), 0, Math.max(0, tokens.length * TOKEN_HEIGHT - AR.h))}); changeId(id); drawProgressBar(); From 05b75d2241844d4858992b46335f1d141b680f1a Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Wed, 23 Mar 2022 16:30:24 +0800 Subject: [PATCH 28/64] Update app.js Fix missing break. --- apps/authentiwatch/app.js | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index 3920ebddd..3a452b6e9 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -353,6 +353,7 @@ function onSwipe(e) { state.hotp.hotp = CALCULATING; drawToken(state.id); } + break; } timerCalc(); } From b3cc125a3f733b8f8a9cd591d537f947ca2488b1 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Wed, 23 Mar 2022 16:36:42 +0800 Subject: [PATCH 29/64] Update app.js Use let instead of var. --- apps/authentiwatch/app.js | 41 +++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index 3a452b6e9..5198254af 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -22,8 +22,8 @@ if (settings.tokens) tokens = settings.tokens; /* v0.03+ settings */ function b32decode(seedstr) { // RFC4648 - var buf = 0, bitcount = 0, retstr = ""; - for (var c of seedstr.toUpperCase()) { + let buf = 0, bitcount = 0, retstr = ""; + for (let c of seedstr.toUpperCase()) { if (c == '0') c = 'O'; if (c == '1') c = 'I'; if (c == '8') c = 'B'; @@ -39,63 +39,62 @@ function b32decode(seedstr) { } } } - var retbuf = new Uint8Array(retstr.length); - for (var i in retstr) { + let retbuf = new Uint8Array(retstr.length); + for (let i in retstr) { retbuf[i] = retstr.charCodeAt(i); } return retbuf; } function hmac(key, message, algo) { - var a = algos[algo.toUpperCase()]; + let a = algos[algo.toUpperCase()]; // RFC2104 if (key.length > a.blksz) { key = a.sha(key); } - var istr = new Uint8Array(a.blksz + message.length); - var ostr = new Uint8Array(a.blksz + a.retsz); - for (var i = 0; i < a.blksz; ++i) { - var c = (i < key.length) ? key[i] : 0; + let istr = new Uint8Array(a.blksz + message.length); + let ostr = new Uint8Array(a.blksz + a.retsz); + for (let i = 0; i < a.blksz; ++i) { + let c = (i < key.length) ? key[i] : 0; istr[i] = c ^ 0x36; ostr[i] = c ^ 0x5C; } istr.set(message, a.blksz); ostr.set(a.sha(istr), a.blksz); - var ret = a.sha(ostr); + let ret = a.sha(ostr); // RFC4226 dynamic truncation - var v = new DataView(ret, ret[ret.length - 1] & 0x0F, 4); + let v = new DataView(ret, ret[ret.length - 1] & 0x0F, 4); return v.getUint32(0) & 0x7FFFFFFF; } function formatOtp(otp, digits) { // add 0 padding - var ret = "" + otp % Math.pow(10, digits); + let ret = "" + otp % Math.pow(10, digits); while (ret.length < digits) { ret = "0" + ret; } // add a space after every 3rd or 4th digit - var re = (digits % 3 == 0 || (digits % 3 >= digits % 4 && digits % 4 != 0)) ? "" : "."; + let re = (digits % 3 == 0 || (digits % 3 >= digits % 4 && digits % 4 != 0)) ? "" : "."; return ret.replace(new RegExp("(..." + re + ")", "g"), "$1 ").trim(); } function hotp(token) { - var d = Date.now(); - var tick, next; + let d = Date.now(); + let tick, next; if (token.period > 0) { // RFC6238 - timed - var seconds = Math.floor(d / 1000); - tick = Math.floor(seconds / token.period); + tick = Math.floor(Math.floor(d / 1000) / token.period); next = (tick + 1) * token.period * 1000; } else { // RFC4226 - counter tick = -token.period; next = d + 30000; } - var msg = new Uint8Array(8); - var v = new DataView(msg.buffer); + let msg = new Uint8Array(8); + let v = new DataView(msg.buffer); v.setUint32(0, tick >> 16 >> 16); v.setUint32(4, tick & 0xFFFFFFFF); - var ret; + let ret; try { ret = hmac(b32decode(token.secret), msg, token.algorithm); ret = formatOtp(ret, token.digits); @@ -117,7 +116,7 @@ var state = { }; function sizeFont(id, txt, w) { - var sz = fontszCache[id]; + let sz = fontszCache[id]; if (sz) { g.setFont("Vector", sz); } else { From 6158d76f176315b014498e997a93a6ad1a267b30 Mon Sep 17 00:00:00 2001 From: Micha <97034053+foostuff@users.noreply.github.com> Date: Wed, 23 Mar 2022 14:37:44 +0100 Subject: [PATCH 30/64] configurable drag gestures --- apps/clockcal/app.js | 172 ++++++++++++++++++++++++------------------- 1 file changed, 95 insertions(+), 77 deletions(-) diff --git a/apps/clockcal/app.js b/apps/clockcal/app.js index 86fa0815a..5e8c7f796 100644 --- a/apps/clockcal/app.js +++ b/apps/clockcal/app.js @@ -4,12 +4,13 @@ var s = Object.assign({ CAL_ROWS: 4, //number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets. BUZZ_ON_BT: true, //2x slow buzz on disconnect, 2x fast buzz on connect. Will be extra widget eventually MODE24: true, //24h mode vs 12h mode - FIRSTDAYOFFSET: 6, //First day of the week: 0-6: Sun, Sat, Fri, Thu, Wed, Tue, Mon + FIRSTDAY: 6, //First day of the week: mo, tu, we, th, fr, sa, su REDSUN: true, // Use red color for sunday? REDSAT: true, // Use red color for saturday? - DRAGENABLED: true, - DRAGMUSIC: true, - DRAGMESSAGES: true + DRAGDOWN: "[AI:messg]", + DRAGRIGHT: "[AI:music]", + DRAGLEFT: "[ignore]", + DRAGUP: "[calend.]" }, require('Storage').readJSON("clockcal.json", true) || {}); const h = g.getHeight(); @@ -27,13 +28,13 @@ var monthOffset = 0; */ function drawFullCalendar(monthOffset) { addMonths = function (_d, _am) { - var ay = 0, m = _d.getMonth(), y = _d.getFullYear(); - while ((m + _am) > 11) { ay++; _am -= 12; } - while ((m + _am) < 0) { ay--; _am += 12; } - n = new Date(_d.getTime()); - n.setMonth(m + _am); - n.setFullYear(y + ay); - return n; + var ay = 0, m = _d.getMonth(), y = _d.getFullYear(); + while ((m + _am) > 11) { ay++; _am -= 12; } + while ((m + _am) < 0) { ay--; _am += 12; } + n = new Date(_d.getTime()); + n.setMonth(m + _am); + n.setFullYear(y + ay); + return n; }; monthOffset = (typeof monthOffset == "undefined") ? 0 : monthOffset; state = "calendar"; @@ -45,22 +46,22 @@ function drawFullCalendar(monthOffset) { if (typeof minuteInterval !== "undefined") clearTimeout(minuteInterval); d = addMonths(Date(), monthOffset); tdy = Date().getDate() + "." + Date().getMonth(); - newmonth=false; + newmonth = false; c_y = 0; g.reset(); g.setBgColor(0); g.clear(); - var prevmonth = addMonths(d, -1) + var prevmonth = addMonths(d, -1); const today = prevmonth.getDate(); var rD = new Date(prevmonth.getTime()); rD.setDate(rD.getDate() - (today - 1)); - const dow = (s.FIRSTDAYOFFSET + rD.getDay()) % 7; + const dow = (s.FIRSTDAY + rD.getDay()) % 7; rD.setDate(rD.getDate() - dow); var rDate = rD.getDate(); bottomrightY = c_y - 3; - clrsun=s.REDSUN?'#f00':'#fff'; - clrsat=s.REDSUN?'#f00':'#fff'; - var fg=[clrsun,'#fff','#fff','#fff','#fff','#fff',clrsat]; + clrsun = s.REDSUN ? '#f00' : '#fff'; + clrsat = s.REDSUN ? '#f00' : '#fff'; + var fg = [clrsun, '#fff', '#fff', '#fff', '#fff', '#fff', clrsat]; for (var y = 1; y <= 11; y++) { bottomrightY += CELL_H; bottomrightX = -2; @@ -69,14 +70,14 @@ function drawFullCalendar(monthOffset) { rMonth = rD.getMonth(); rDate = rD.getDate(); if (tdy == rDate + "." + rMonth) { - caldrawToday(rDate); + caldrawToday(rDate); } else if (rDate == 1) { - caldrawFirst(rDate); + caldrawFirst(rDate); } else { - caldrawNormal(rDate,fg[rD.getDay()]); + caldrawNormal(rDate, fg[rD.getDay()]); } if (newmonth && x == 7) { - caldrawMonth(rDate,monthclr[rMonth % 6],months[rMonth],rD); + caldrawMonth(rDate, monthclr[rMonth % 6], months[rMonth], rD); } rD.setDate(rDate + 1); } @@ -84,7 +85,7 @@ function drawFullCalendar(monthOffset) { delete addMonths; if (DEBUG) console.log("Calendar performance (ms):" + (Date().getTime() - start)); } -function caldrawMonth(rDate,c,m,rD) { +function caldrawMonth(rDate, c, m, rD) { g.setColor(c); g.setFont("Vector", 18); g.setFontAlign(-1, 1, 1); @@ -93,29 +94,29 @@ function caldrawMonth(rDate,c,m,rD) { newmonth = false; } function caldrawToday(rDate) { - g.setFont("Vector", 16); - g.setFontAlign(1, 1); - g.setColor('#0f0'); - g.fillRect(bottomrightX - CELL2_W + 1, bottomrightY - CELL_H - 1, bottomrightX, bottomrightY - 2); - g.setColor('#000'); - g.drawString(rDate, bottomrightX, bottomrightY); + g.setFont("Vector", 16); + g.setFontAlign(1, 1); + g.setColor('#0f0'); + g.fillRect(bottomrightX - CELL2_W + 1, bottomrightY - CELL_H - 1, bottomrightX, bottomrightY - 2); + g.setColor('#000'); + g.drawString(rDate, bottomrightX, bottomrightY); } function caldrawFirst(rDate) { - g.flip(); - g.setFont("Vector", 16); - g.setFontAlign(1, 1); - bottomrightY += 3; - newmonth = true; - g.setColor('#0ff'); - g.fillRect(bottomrightX - CELL2_W + 1, bottomrightY - CELL_H - 1, bottomrightX, bottomrightY - 2); - g.setColor('#000'); - g.drawString(rDate, bottomrightX, bottomrightY); + g.flip(); + g.setFont("Vector", 16); + g.setFontAlign(1, 1); + bottomrightY += 3; + newmonth = true; + g.setColor('#0ff'); + g.fillRect(bottomrightX - CELL2_W + 1, bottomrightY - CELL_H - 1, bottomrightX, bottomrightY - 2); + g.setColor('#000'); + g.drawString(rDate, bottomrightX, bottomrightY); } -function caldrawNormal(rDate,c) { - g.setFont("Vector", 16); - g.setFontAlign(1, 1); - g.setColor(c); - g.drawString(rDate, bottomrightX, bottomrightY);//100 +function caldrawNormal(rDate, c) { + g.setFont("Vector", 16); + g.setFontAlign(1, 1); + g.setColor(c); + g.drawString(rDate, bottomrightX, bottomrightY);//100 } function drawMinutes() { if (DEBUG) console.log("|-->minutes"); @@ -163,7 +164,7 @@ function drawWatch() { g.clear(); drawMinutes(); if (!dimSeconds) drawSeconds(); - const dow = (s.FIRSTDAYOFFSET + d.getDay()) % 7; //MO=0, SU=6 + const dow = (s.FIRSTDAY + d.getDay()) % 7; //MO=0, SU=6 const today = d.getDate(); var rD = new Date(d.getTime()); rD.setDate(rD.getDate() - dow); @@ -205,27 +206,52 @@ function BTevent() { setTimeout(function () { Bangle.buzz(interval); }, interval * 3); } } - +function action(a) { + g.reset(); + if (typeof secondInterval !== "undefined") clearTimeout(secondInterval); + if (DEBUG) console.log("action:" + a); + switch (a) { + case "[ignore]": + break; + case "[calend.]": + drawFullCalendar(); + break; + case "[AI:music]": + l = require("Storage").list(RegExp("music.*app.js")); + if (l.length > 0) { + load(l[0]); + } else E.showAlert("Music app not found", "Not found").then(drawWatch); + break; + case "[AI:messg]": + l = require("Storage").list(RegExp("message.*app.js")); + if (l.length > 0) { + load(l[0]); + } else E.showAlert("Message app not found", "Not found").then(drawWatch); + break; + default: + l = require("Storage").list(RegExp(a + ".app.js")); + if (l.length > 0) { + load(l[0]); + } else E.showAlert(a + ": App not found", "Not found").then(drawWatch); + break; + } +} function input(dir) { - if (s.DRAGENABLED) { - Bangle.buzz(100,1); - console.log("swipe:"+dir); + Bangle.buzz(100, 1); + if (DEBUG) console.log("swipe:" + dir); switch (dir) { case "r": if (state == "calendar") { drawWatch(); } else { - if (s.DRAGMUSIC) { - l=require("Storage").list(RegExp("music.*app")); - if (l.length > 0) { - load(l[0]); - } else Bangle.buzz(3000,1);//not found - } + action(s.DRAGRIGHT); } break; case "l": if (state == "calendar") { drawWatch(); + } else { + action(s.DRAGLEFT); } break; case "d": @@ -233,21 +259,15 @@ function input(dir) { monthOffset--; drawFullCalendar(monthOffset); } else { - if (s.DRAGMESSAGES) { - l=require("Storage").list(RegExp("message.*app")); - if (l.length > 0) { - load(l[0]); - } else Bangle.buzz(3000,1);//not found - } + action(s.DRAGDOWN); } break; case "u": - if (state == "watch") { - state = "calendar"; - drawFullCalendar(0); - } else if (state == "calendar") { + if (state == "calendar") { monthOffset++; drawFullCalendar(monthOffset); + } else { + action(s.DRAGUP); } break; default: @@ -255,26 +275,24 @@ function input(dir) { drawWatch(); } break; + } - } } let drag; Bangle.on("drag", e => { - if (s.DRAGENABLED) { - if (!drag) { - drag = { x: e.x, y: e.y }; - } else if (!e.b) { - const dx = e.x - drag.x, dy = e.y - drag.y; - var dir = "t"; - if (Math.abs(dx) > Math.abs(dy) + 10) { - dir = (dx > 0) ? "r" : "l"; - } else if (Math.abs(dy) > Math.abs(dx) + 10) { - dir = (dy > 0) ? "d" : "u"; - } - drag = null; - input(dir); + if (!drag) { + drag = { x: e.x, y: e.y }; + } else if (!e.b) { + const dx = e.x - drag.x, dy = e.y - drag.y; + var dir = "t"; + if (Math.abs(dx) > Math.abs(dy) + 20) { + dir = (dx > 0) ? "r" : "l"; + } else if (Math.abs(dy) > Math.abs(dx) + 20) { + dir = (dy > 0) ? "d" : "u"; } + drag = null; + input(dir); } }); From d0426a2093caf025b0fdf3b4ae930bca83b9ee75 Mon Sep 17 00:00:00 2001 From: Micha <97034053+foostuff@users.noreply.github.com> Date: Wed, 23 Mar 2022 14:38:42 +0100 Subject: [PATCH 31/64] Configurable drag gestures --- apps/clockcal/settings.js | 65 +++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/apps/clockcal/settings.js b/apps/clockcal/settings.js index c4ec764c9..abedad99b 100644 --- a/apps/clockcal/settings.js +++ b/apps/clockcal/settings.js @@ -1,19 +1,22 @@ (function (back) { var FILE = "clockcal.json"; - - settings = Object.assign({ + defaults={ CAL_ROWS: 4, //number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets. BUZZ_ON_BT: true, //2x slow buzz on disconnect, 2x fast buzz on connect. Will be extra widget eventually MODE24: true, //24h mode vs 12h mode FIRSTDAY: 6, //First day of the week: mo, tu, we, th, fr, sa, su REDSUN: true, // Use red color for sunday? REDSAT: true, // Use red color for saturday? - DRAGENABLED: true, //Enable drag gestures (bigger calendar etc) - DRAGMUSIC: true, //Enable drag down for music (looks for "music*app") - DRAGMESSAGES: true //Enable drag right for messages (looks for "message*app") - }, require('Storage').readJSON(FILE, true) || {}); - + DRAGDOWN: "[AI:messg]", + DRAGRIGHT: "[AI:music]", + DRAGLEFT: "[ignore]", + DRAGUP: "[calend.]" + }; + settings = Object.assign(defaults, require('Storage').readJSON(FILE, true) || {}); + actions = ["[ignore]","[calend.]","[AI:music]","[AI:messg]"]; + require("Storage").list(RegExp(".app.js")).forEach(element => actions.push(element.replace(".app.js",""))); + function writeSettings() { require('Storage').writeJSON(FILE, settings); } @@ -70,27 +73,39 @@ writeSettings(); } }, - 'Swipes (big cal.)?': { - value: settings.DRAGENABLED, - format: v => v ? "On" : "Off", + 'Drag Up ': { + min:0, max:actions.length-1, + value: actions.indexOf(settings.DRAGUP), + format: v => actions[v], onchange: v => { - settings.DRAGENABLED = v; + settings.DRAGUP = actions[v]; writeSettings(); } }, - 'Swipes (music)?': { - value: settings.DRAGMUSIC, - format: v => v ? "On" : "Off", + 'Drag Right': { + min:0, max:actions.length-1, + value: actions.indexOf(settings.DRAGRIGHT), + format: v => actions[v], onchange: v => { - settings.DRAGMUSIC = v; + settings.DRAGRIGHT = actions[v]; writeSettings(); } }, - 'Swipes (messg)?': { - value: settings.DRAGMESSAGES, - format: v => v ? "On" : "Off", + 'Drag Down': { + min:0, max:actions.length-1, + value: actions.indexOf(settings.DRAGDOWN), + format: v => actions[v], onchange: v => { - settings.DRAGMESSAGES = v; + settings.DRGDOWN = actions[v]; + writeSettings(); + } + }, + 'Drag Left': { + min:0, max:actions.length-1, + value: actions.indexOf(settings.DRAGLEFT), + format: v => actions[v], + onchange: v => { + settings.DRAGLEFT = actions[v]; writeSettings(); } }, @@ -100,17 +115,7 @@ format: v => ["No", "Yes"][v], onchange: v => { if (v == 1) { - settings = { - CAL_ROWS: 4, //number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets. - BUZZ_ON_BT: true, //2x slow buzz on disconnect, 2x fast buzz on connect. - MODE24: true, //24h mode vs 12h mode - FIRSTDAY: 6, //First day of the week: mo, tu, we, th, fr, sa, su - REDSUN: true, // Use red color for sunday? - REDSAT: true, // Use red color for saturday? - DRAGENABLED: true, - DRAGMUSIC: true, - DRAGMESSAGES: true - }; + settings = defaults; writeSettings(); load(); } From abf684449780be69a38ad881f9f7c61ed9894a42 Mon Sep 17 00:00:00 2001 From: Micha <97034053+foostuff@users.noreply.github.com> Date: Wed, 23 Mar 2022 14:40:31 +0100 Subject: [PATCH 32/64] increment version --- apps/clockcal/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/clockcal/metadata.json b/apps/clockcal/metadata.json index a42e1ad2e..dde32f746 100644 --- a/apps/clockcal/metadata.json +++ b/apps/clockcal/metadata.json @@ -1,7 +1,7 @@ { "id": "clockcal", "name": "Clock & Calendar", - "version": "0.2", + "version": "0.3", "description": "Clock with Calendar", "readme":"README.md", "icon": "app.png", From 00d74ceef61fb3596b8468db3219b7e6598962d5 Mon Sep 17 00:00:00 2001 From: Micha <97034053+foostuff@users.noreply.github.com> Date: Wed, 23 Mar 2022 14:46:03 +0100 Subject: [PATCH 33/64] Update ChangeLog --- apps/clockcal/ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/clockcal/ChangeLog b/apps/clockcal/ChangeLog index 299b1ec69..f73c6ed97 100644 --- a/apps/clockcal/ChangeLog +++ b/apps/clockcal/ChangeLog @@ -1,2 +1,3 @@ 0.01: Initial upload 0.2: Added scrollable calendar and swipe gestures +0.3: Configurable drag gestures From d6c8f634c7689662dea624ab07f91ee2469d1bd3 Mon Sep 17 00:00:00 2001 From: Micha <97034053+foostuff@users.noreply.github.com> Date: Wed, 23 Mar 2022 14:56:17 +0100 Subject: [PATCH 34/64] Update README.md --- apps/clockcal/README.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/apps/clockcal/README.md b/apps/clockcal/README.md index da6887177..d30205be0 100644 --- a/apps/clockcal/README.md +++ b/apps/clockcal/README.md @@ -9,25 +9,24 @@ I know that it seems redundant because there already **is** a *time&cal*-app, bu |![unlocked screen](screenshot2.png)|unlocked: smaller clock, but with seconds| |![big calendar](screenshot3.png)|swipe up for big calendar, (up down to scroll, left/right to exit)| - - - ## Configurable Features - Number of calendar rows (weeks) - Buzz on connect/disconnect (I know, this should be an extra widget, but for now, it is included) -- Clock Mode (24h/12h). Doesn't have an am/pm indicator. It's only there because it was easy. +- Clock Mode (24h/12h). (No am/pm indicator) - First day of the week -- Red Saturday -- Red Sunday -- Swipes (to disable all gestures) -- Swipes: music (swipe down) -- Spipes: messages (swipe right) +- Red Saturday/Sunday +- Swipe/Drag gestures to launch features or apps. ## Auto detects your message/music apps: -- swiping down will search your files for an app with the string "music" in its filename and launch it -- swiping right will search your files for an app with the string "message" in its filename and launch it. -- Configurable apps coming soon. +- swiping down will search your files for an app with the string "message" in its filename and launch it. (configurable) +- swiping right will search your files for an app with the string "music" in its filename and launch it. (configurable) ## Feedback The clock works for me in a 24h/MondayFirst/WeekendFree environment but is not well-tested with other settings. So if something isn't working, please tell me: https://github.com/foostuff/BangleApps/issues + +## Planned features: + - Internal lightweight music control, because switching apps has a loading time. + - Clean up settings + - Maybe am/pm indicator for 12h-users + - Step count (optional) From 00ad2a12780da264d897ddd1c39d06074211cb97 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Wed, 23 Mar 2022 22:24:14 +0800 Subject: [PATCH 35/64] Update app.js Define some constants. Add RFC comments. --- apps/authentiwatch/app.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index 5198254af..6e2951b9f 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -2,6 +2,8 @@ const COUNTER_TRIANGLE_SIZE = 10; const TOKEN_EXTRA_HEIGHT = 16; var TOKEN_DIGITS_HEIGHT = 30; var TOKEN_HEIGHT = TOKEN_DIGITS_HEIGHT + TOKEN_EXTRA_HEIGHT; +const PROGRESSBAR_HEIGHT = 3; +const IDLE_REPEATS = 1; // when idle, the number of extra timed periods to show before hiding const SETTINGS = "authentiwatch.json"; // Hash functions const crypto = require("crypto"); @@ -21,7 +23,7 @@ if (settings.data ) tokens = settings.data ; /* v0.02 settings */ if (settings.tokens) tokens = settings.tokens; /* v0.03+ settings */ function b32decode(seedstr) { - // RFC4648 + // RFC4648 Base16/32/64 Data Encodings let buf = 0, bitcount = 0, retstr = ""; for (let c of seedstr.toUpperCase()) { if (c == '0') c = 'O'; @@ -48,7 +50,7 @@ function b32decode(seedstr) { function hmac(key, message, algo) { let a = algos[algo.toUpperCase()]; - // RFC2104 + // RFC2104 HMAC if (key.length > a.blksz) { key = a.sha(key); } @@ -62,7 +64,7 @@ function hmac(key, message, algo) { istr.set(message, a.blksz); ostr.set(a.sha(istr), a.blksz); let ret = a.sha(ostr); - // RFC4226 dynamic truncation + // RFC4226 HOTP (dynamic truncation) let v = new DataView(ret, ret[ret.length - 1] & 0x0F, 4); return v.getUint32(0) & 0x7FFFFFFF; } @@ -187,7 +189,7 @@ function drawProgressBar() { let y2 = y1 + TOKEN_HEIGHT - 1; if (y2 >= AR.y && y1 <= AR.y2) { // token visible - y1 = y2 - 3; + y1 = y2 - PROGRESSBAR_HEIGHT; if (y1 <= AR.y2) { // progress bar visible @@ -278,7 +280,7 @@ function changeId(id) { } function onDrag(e) { - state.cnt = 1; + state.cnt = IDLE_REPEATS; if (e.b != 0 && e.dy != 0) { let y = E.clip(state.listy - E.clip(e.dy, -AR.h, AR.h), 0, Math.max(0, tokens.length * TOKEN_HEIGHT - AR.h)); if (state.listy != y) { @@ -312,7 +314,7 @@ function onDrag(e) { } function onTouch(zone, e) { - state.cnt = 1; + state.cnt = IDLE_REPEATS; if (e) { let id = Math.floor((state.listy + e.y - AR.y) / TOKEN_HEIGHT); if (id == state.id || tokens.length == 0 || id >= tokens.length) { @@ -340,7 +342,7 @@ function onTouch(zone, e) { } function onSwipe(e) { - state.cnt = 1; + state.cnt = IDLE_REPEATS; switch (e) { case 1: exitApp(); @@ -358,7 +360,7 @@ function onSwipe(e) { } function bangleBtn(e) { - state.cnt = 1; + state.cnt = IDLE_REPEATS; if (tokens.length > 0) { let id = E.clip(state.id + e, 0, tokens.length - 1); onDrag({b:1, dy:state.listy - E.clip(id * TOKEN_HEIGHT - half(AR.h - TOKEN_HEIGHT), 0, Math.max(0, tokens.length * TOKEN_HEIGHT - AR.h))}); From 287cbf6eea1376f02e1d5472a1c9b9956c953205 Mon Sep 17 00:00:00 2001 From: marko Date: Wed, 23 Mar 2022 14:56:07 -0400 Subject: [PATCH 36/64] New app 2047++ --- apps/2047++/2047++.app.js | 129 ++++++++++++++++++++++++++++++++++++++ apps/2047++/README.md | 6 ++ apps/2047++/app-icon.js | 1 + apps/2047++/app.png | Bin 0 -> 759 bytes apps/2047++/metadata.json | 14 +++++ 5 files changed, 150 insertions(+) create mode 100644 apps/2047++/2047++.app.js create mode 100644 apps/2047++/README.md create mode 100644 apps/2047++/app-icon.js create mode 100644 apps/2047++/app.png create mode 100644 apps/2047++/metadata.json diff --git a/apps/2047++/2047++.app.js b/apps/2047++/2047++.app.js new file mode 100644 index 000000000..b508d8f07 --- /dev/null +++ b/apps/2047++/2047++.app.js @@ -0,0 +1,129 @@ + + +class TwoK { + constructor() { + this.b = Array(4).fill().map(() => Array(4).fill(0)); + this.score = 0; + this.cmap = {0: "#caa", 2:"#ccc", 4: "#bcc", 8: "#ba6", 16: "#e61", 32: "#d20", 64: "#d00", 128: "#da0", 256: "#ec0", 512: "#dd0"}; + } + drawBRect(x1, y1, x2, y2, th, c, cf) { + g.setColor(c); + for (i=0; i 4) g.setColor(1, 1, 1); + else g.setColor(0, 0, 0); + g.setFont("Vector", bh*(b>8 ? (b>64 ? (b>512 ? 0.32 : 0.4) : 0.6) : 0.7)); + if (b>0) g.drawString(b.toString(), xo+(x+0.5)*bw+1, yo+(y+0.5)*bh); + } + } + shift(d) { // +/-1: shift x, +/- 2: shift y + var crc = E.CRC32(this.b.toString()); + if (d==-1) { // shift x left + for (y=0; y<4; ++y) { + for (x=2; x>=0; x--) + if (this.b[y][x]==0) { + for (i=x; i<3; i++) this.b[y][i] = this.b[y][i+1]; + this.b[y][3] = 0; moved = true; + } + for (x=0; x<3; ++x) + if (this.b[y][x]==this.b[y][x+1]) { + this.score += 2*this.b[y][x]; + this.b[y][x] += this.b[y][x+1]; + for (j=x+1; j<3; ++j) this.b[y][j] = this.b[y][j+1]; + this.b[y][3] = 0; moved = true; + } + } + } + else if (d==1) { // shift x right + for (y=0; y<4; ++y) { + for (x=1; x<4; x++) + if (this.b[y][x]==0) { + for (i=x; i>0; i--) this.b[y][i] = this.b[y][i-1]; + this.b[y][0] = 0; + } + for (x=3; x>0; --x) + if (this.b[y][x]==this.b[y][x-1]) { + this.score += 2*this.b[y][x]; + this.b[y][x] += this.b[y][x-1] ; + for (j=x-1; j>0; j--) this.b[y][j] = this.b[y][j-1]; + this.b[y][0] = 0; + } + } + } + else if (d==-2) { // shift y down + for (x=0; x<4; ++x) { + for (y=1; y<4; y++) + if (this.b[y][x]==0) { + for (i=y; i>0; i--) this.b[i][x] = this.b[i-1][x]; + this.b[0][x] = 0; + } + for (y=3; y>0; y--) + if (this.b[y][x]==this.b[y-1][x] || this.b[y][x]==0) { + this.score += 2*this.b[y][x]; + this.b[y][x] += this.b[y-1][x]; + for (j=y-1; j>0; j--) this.b[j][x] = this.b[j-1][x]; + this.b[0][x] = 0; + } + } + } + else if (d==2) { // shift y up + for (x=0; x<4; ++x) { + for (y=2; y>=0; y--) + if (this.b[y][x]==0) { + for (i=y; i<3; i++) this.b[i][x] = this.b[i+1][x]; + this.b[3][x] = 0; + } + for (y=0; y<3; ++y) + if (this.b[y][x]==this.b[y+1][x] || this.b[y][x]==0) { + this.score += 2*this.b[y][x]; + this.b[y][x] += this.b[y+1][x]; + for (j=y+1; j<3; ++j) this.b[j][x] = this.b[j+1][x]; + this.b[3][x] = 0; + } + } + } + return (E.CRC32(this.b.toString())!=crc); + } + addDigit() { + var d = Math.random()>0.9 ? 4 : 2; + var id = Math.floor(Math.random()*16); + while (this.b[Math.floor(id/4)][id%4] > 0) id = Math.floor(Math.random()*16); + this.b[Math.floor(id/4)][id%4] = d; + } +} + +function dragHandler(e) { + if (e.b && (Math.abs(e.dx)>7 || Math.abs(e.dy)>7)) { + var res = false; + if (Math.abs(e.dx)>Math.abs(e.dy)) { + if (e.dx>0) res = twok.shift(1); + if (e.dx<0) res = twok.shift(-1); + } + else { + if (e.dy>0) res = twok.shift(-2); + if (e.dy<0) res = twok.shift(2); + } + if (res) twok.addDigit(); + twok.render(); + } +} + +var twok = new TwoK(); +twok.addDigit(); twok.addDigit(); +twok.render(); +Bangle.on("drag", dragHandler); diff --git a/apps/2047++/README.md b/apps/2047++/README.md new file mode 100644 index 000000000..5b70423e4 --- /dev/null +++ b/apps/2047++/README.md @@ -0,0 +1,6 @@ + +# Game of 2047++ + +Tile shifting game inspired by the well known 2048 game. Also very similar to another Bangle game, Game1024. + +Attempt to combine equal numbers by swiping left, right, up or down. diff --git a/apps/2047++/app-icon.js b/apps/2047++/app-icon.js new file mode 100644 index 000000000..4086d1879 --- /dev/null +++ b/apps/2047++/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A31gAeFtoxPF9wujGBYQG1YAWF6ur5gAYGIovOFzIABF6ReaMAwv/F/4v/F7ejv9/0Yvq1Eylksv4vqvIuBF9ZeDF9ZeBqovr1AsB0YvrLwXMF9ReDF9ZeBq1/v4vBqowKF7lWFYIAFF/7vXAAa/qF+jxB0YvsABov/F/4v/F6WsF7YgEF5xgaLwgvPGIQAWDwwvQADwvJGEguKF+AxhFpoA/AH4A/AFI=")) \ No newline at end of file diff --git a/apps/2047++/app.png b/apps/2047++/app.png new file mode 100644 index 0000000000000000000000000000000000000000..d1fb4a5e5cbadce3da7b36806d4907f680f8f1bb GIT binary patch literal 759 zcmVYCuD-J|F04S(qype>Ll^oVPK(#&(MEqX}dr+-4N41U&D6G<#pk8p=ry~PL z8P~wDv0Fxk@G(D*-Esf`5DS+uaYFpJB+Wpns^Zb}E7&tzFF7%tm102J6nd69|4+P3 zr1VfMt5{y0fI>`0^KD2mu=F8{y6M673*Til--d7lyX64hte$~F4EL^Xh;F_M;RY5n zQPi8Q(T|*z{|6UpV5c0w+w9;*9}v8ZE@h%j4t73gCg!Qfe`}$Ac#sB~}C%=m9 zQmlcEAIAXzTR($HE?;WPt>nU3$%Ta*5c>_tUp2cB`Ud77yzh$Lczg#y>rX6t^Z|D> zcQA?REP&Q#P6pBq$e1?!8Tl#X8W=W?3|OSe*3omHjttb47#RG02|5f6e$ zObTJ!ml%i%20ylab9U#WUDz$7W@n({nZhq+5~`IO*5Pi07qm053E*;P(4-Kmo@>1; z>;uNw7hc?M3Z*1!=?Nlw%8PRi7>4l#z$>YW4#!KwFcx?T+e^N5I_>#$tusSJ+zSuc p5YZ-MEM*wRg54#bi;K&M^BdjW!v$Q*XjK3J002ovPDHLkV1lLySIGbX literal 0 HcmV?d00001 diff --git a/apps/2047++/metadata.json b/apps/2047++/metadata.json new file mode 100644 index 000000000..f887d95d9 --- /dev/null +++ b/apps/2047++/metadata.json @@ -0,0 +1,14 @@ +{ "id": "2047++", + "name": "2047++", + "shortName":"2047++", + "icon": "app.png", + "version":"0.01", + "description": "Bangle version of a tile shifting game", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "tags": "game", + "storage": [ + {"name":"2047++.app.js","url":"2047++.app.js"}, + {"name":"2047++.img","url":"app-icon.js","evaluate":true} + ] +} From 1e10f4a25eb1c7a48513694b71fa1b0aee623217 Mon Sep 17 00:00:00 2001 From: marko Date: Wed, 23 Mar 2022 14:59:23 -0400 Subject: [PATCH 37/64] Change name of new app --- apps/2047++/2047++.app.js | 129 -------------------------------------- apps/2047++/README.md | 6 -- apps/2047++/app-icon.js | 1 - apps/2047++/app.png | Bin 759 -> 0 bytes apps/2047++/metadata.json | 14 ----- 5 files changed, 150 deletions(-) delete mode 100644 apps/2047++/2047++.app.js delete mode 100644 apps/2047++/README.md delete mode 100644 apps/2047++/app-icon.js delete mode 100644 apps/2047++/app.png delete mode 100644 apps/2047++/metadata.json diff --git a/apps/2047++/2047++.app.js b/apps/2047++/2047++.app.js deleted file mode 100644 index b508d8f07..000000000 --- a/apps/2047++/2047++.app.js +++ /dev/null @@ -1,129 +0,0 @@ - - -class TwoK { - constructor() { - this.b = Array(4).fill().map(() => Array(4).fill(0)); - this.score = 0; - this.cmap = {0: "#caa", 2:"#ccc", 4: "#bcc", 8: "#ba6", 16: "#e61", 32: "#d20", 64: "#d00", 128: "#da0", 256: "#ec0", 512: "#dd0"}; - } - drawBRect(x1, y1, x2, y2, th, c, cf) { - g.setColor(c); - for (i=0; i 4) g.setColor(1, 1, 1); - else g.setColor(0, 0, 0); - g.setFont("Vector", bh*(b>8 ? (b>64 ? (b>512 ? 0.32 : 0.4) : 0.6) : 0.7)); - if (b>0) g.drawString(b.toString(), xo+(x+0.5)*bw+1, yo+(y+0.5)*bh); - } - } - shift(d) { // +/-1: shift x, +/- 2: shift y - var crc = E.CRC32(this.b.toString()); - if (d==-1) { // shift x left - for (y=0; y<4; ++y) { - for (x=2; x>=0; x--) - if (this.b[y][x]==0) { - for (i=x; i<3; i++) this.b[y][i] = this.b[y][i+1]; - this.b[y][3] = 0; moved = true; - } - for (x=0; x<3; ++x) - if (this.b[y][x]==this.b[y][x+1]) { - this.score += 2*this.b[y][x]; - this.b[y][x] += this.b[y][x+1]; - for (j=x+1; j<3; ++j) this.b[y][j] = this.b[y][j+1]; - this.b[y][3] = 0; moved = true; - } - } - } - else if (d==1) { // shift x right - for (y=0; y<4; ++y) { - for (x=1; x<4; x++) - if (this.b[y][x]==0) { - for (i=x; i>0; i--) this.b[y][i] = this.b[y][i-1]; - this.b[y][0] = 0; - } - for (x=3; x>0; --x) - if (this.b[y][x]==this.b[y][x-1]) { - this.score += 2*this.b[y][x]; - this.b[y][x] += this.b[y][x-1] ; - for (j=x-1; j>0; j--) this.b[y][j] = this.b[y][j-1]; - this.b[y][0] = 0; - } - } - } - else if (d==-2) { // shift y down - for (x=0; x<4; ++x) { - for (y=1; y<4; y++) - if (this.b[y][x]==0) { - for (i=y; i>0; i--) this.b[i][x] = this.b[i-1][x]; - this.b[0][x] = 0; - } - for (y=3; y>0; y--) - if (this.b[y][x]==this.b[y-1][x] || this.b[y][x]==0) { - this.score += 2*this.b[y][x]; - this.b[y][x] += this.b[y-1][x]; - for (j=y-1; j>0; j--) this.b[j][x] = this.b[j-1][x]; - this.b[0][x] = 0; - } - } - } - else if (d==2) { // shift y up - for (x=0; x<4; ++x) { - for (y=2; y>=0; y--) - if (this.b[y][x]==0) { - for (i=y; i<3; i++) this.b[i][x] = this.b[i+1][x]; - this.b[3][x] = 0; - } - for (y=0; y<3; ++y) - if (this.b[y][x]==this.b[y+1][x] || this.b[y][x]==0) { - this.score += 2*this.b[y][x]; - this.b[y][x] += this.b[y+1][x]; - for (j=y+1; j<3; ++j) this.b[j][x] = this.b[j+1][x]; - this.b[3][x] = 0; - } - } - } - return (E.CRC32(this.b.toString())!=crc); - } - addDigit() { - var d = Math.random()>0.9 ? 4 : 2; - var id = Math.floor(Math.random()*16); - while (this.b[Math.floor(id/4)][id%4] > 0) id = Math.floor(Math.random()*16); - this.b[Math.floor(id/4)][id%4] = d; - } -} - -function dragHandler(e) { - if (e.b && (Math.abs(e.dx)>7 || Math.abs(e.dy)>7)) { - var res = false; - if (Math.abs(e.dx)>Math.abs(e.dy)) { - if (e.dx>0) res = twok.shift(1); - if (e.dx<0) res = twok.shift(-1); - } - else { - if (e.dy>0) res = twok.shift(-2); - if (e.dy<0) res = twok.shift(2); - } - if (res) twok.addDigit(); - twok.render(); - } -} - -var twok = new TwoK(); -twok.addDigit(); twok.addDigit(); -twok.render(); -Bangle.on("drag", dragHandler); diff --git a/apps/2047++/README.md b/apps/2047++/README.md deleted file mode 100644 index 5b70423e4..000000000 --- a/apps/2047++/README.md +++ /dev/null @@ -1,6 +0,0 @@ - -# Game of 2047++ - -Tile shifting game inspired by the well known 2048 game. Also very similar to another Bangle game, Game1024. - -Attempt to combine equal numbers by swiping left, right, up or down. diff --git a/apps/2047++/app-icon.js b/apps/2047++/app-icon.js deleted file mode 100644 index 4086d1879..000000000 --- a/apps/2047++/app-icon.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A31gAeFtoxPF9wujGBYQG1YAWF6ur5gAYGIovOFzIABF6ReaMAwv/F/4v/F7ejv9/0Yvq1Eylksv4vqvIuBF9ZeDF9ZeBqovr1AsB0YvrLwXMF9ReDF9ZeBq1/v4vBqowKF7lWFYIAFF/7vXAAa/qF+jxB0YvsABov/F/4v/F6WsF7YgEF5xgaLwgvPGIQAWDwwvQADwvJGEguKF+AxhFpoA/AH4A/AFI=")) \ No newline at end of file diff --git a/apps/2047++/app.png b/apps/2047++/app.png deleted file mode 100644 index d1fb4a5e5cbadce3da7b36806d4907f680f8f1bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 759 zcmVYCuD-J|F04S(qype>Ll^oVPK(#&(MEqX}dr+-4N41U&D6G<#pk8p=ry~PL z8P~wDv0Fxk@G(D*-Esf`5DS+uaYFpJB+Wpns^Zb}E7&tzFF7%tm102J6nd69|4+P3 zr1VfMt5{y0fI>`0^KD2mu=F8{y6M673*Til--d7lyX64hte$~F4EL^Xh;F_M;RY5n zQPi8Q(T|*z{|6UpV5c0w+w9;*9}v8ZE@h%j4t73gCg!Qfe`}$Ac#sB~}C%=m9 zQmlcEAIAXzTR($HE?;WPt>nU3$%Ta*5c>_tUp2cB`Ud77yzh$Lczg#y>rX6t^Z|D> zcQA?REP&Q#P6pBq$e1?!8Tl#X8W=W?3|OSe*3omHjttb47#RG02|5f6e$ zObTJ!ml%i%20ylab9U#WUDz$7W@n({nZhq+5~`IO*5Pi07qm053E*;P(4-Kmo@>1; z>;uNw7hc?M3Z*1!=?Nlw%8PRi7>4l#z$>YW4#!KwFcx?T+e^N5I_>#$tusSJ+zSuc p5YZ-MEM*wRg54#bi;K&M^BdjW!v$Q*XjK3J002ovPDHLkV1lLySIGbX diff --git a/apps/2047++/metadata.json b/apps/2047++/metadata.json deleted file mode 100644 index f887d95d9..000000000 --- a/apps/2047++/metadata.json +++ /dev/null @@ -1,14 +0,0 @@ -{ "id": "2047++", - "name": "2047++", - "shortName":"2047++", - "icon": "app.png", - "version":"0.01", - "description": "Bangle version of a tile shifting game", - "supports" : ["BANGLEJS2"], - "readme": "README.md", - "tags": "game", - "storage": [ - {"name":"2047++.app.js","url":"2047++.app.js"}, - {"name":"2047++.img","url":"app-icon.js","evaluate":true} - ] -} From 80246d2398a93311aaa131886f873b54da705a66 Mon Sep 17 00:00:00 2001 From: marko Date: Wed, 23 Mar 2022 15:00:54 -0400 Subject: [PATCH 38/64] New app 2047pp --- apps/2047pp/2047pp.app.js | 129 ++++++++++++++++++++++++++++++++++++++ apps/2047pp/README.md | 6 ++ apps/2047pp/app-icon.js | 1 + apps/2047pp/app.png | Bin 0 -> 759 bytes apps/2047pp/metadata.json | 14 +++++ 5 files changed, 150 insertions(+) create mode 100644 apps/2047pp/2047pp.app.js create mode 100644 apps/2047pp/README.md create mode 100644 apps/2047pp/app-icon.js create mode 100644 apps/2047pp/app.png create mode 100644 apps/2047pp/metadata.json diff --git a/apps/2047pp/2047pp.app.js b/apps/2047pp/2047pp.app.js new file mode 100644 index 000000000..b508d8f07 --- /dev/null +++ b/apps/2047pp/2047pp.app.js @@ -0,0 +1,129 @@ + + +class TwoK { + constructor() { + this.b = Array(4).fill().map(() => Array(4).fill(0)); + this.score = 0; + this.cmap = {0: "#caa", 2:"#ccc", 4: "#bcc", 8: "#ba6", 16: "#e61", 32: "#d20", 64: "#d00", 128: "#da0", 256: "#ec0", 512: "#dd0"}; + } + drawBRect(x1, y1, x2, y2, th, c, cf) { + g.setColor(c); + for (i=0; i 4) g.setColor(1, 1, 1); + else g.setColor(0, 0, 0); + g.setFont("Vector", bh*(b>8 ? (b>64 ? (b>512 ? 0.32 : 0.4) : 0.6) : 0.7)); + if (b>0) g.drawString(b.toString(), xo+(x+0.5)*bw+1, yo+(y+0.5)*bh); + } + } + shift(d) { // +/-1: shift x, +/- 2: shift y + var crc = E.CRC32(this.b.toString()); + if (d==-1) { // shift x left + for (y=0; y<4; ++y) { + for (x=2; x>=0; x--) + if (this.b[y][x]==0) { + for (i=x; i<3; i++) this.b[y][i] = this.b[y][i+1]; + this.b[y][3] = 0; moved = true; + } + for (x=0; x<3; ++x) + if (this.b[y][x]==this.b[y][x+1]) { + this.score += 2*this.b[y][x]; + this.b[y][x] += this.b[y][x+1]; + for (j=x+1; j<3; ++j) this.b[y][j] = this.b[y][j+1]; + this.b[y][3] = 0; moved = true; + } + } + } + else if (d==1) { // shift x right + for (y=0; y<4; ++y) { + for (x=1; x<4; x++) + if (this.b[y][x]==0) { + for (i=x; i>0; i--) this.b[y][i] = this.b[y][i-1]; + this.b[y][0] = 0; + } + for (x=3; x>0; --x) + if (this.b[y][x]==this.b[y][x-1]) { + this.score += 2*this.b[y][x]; + this.b[y][x] += this.b[y][x-1] ; + for (j=x-1; j>0; j--) this.b[y][j] = this.b[y][j-1]; + this.b[y][0] = 0; + } + } + } + else if (d==-2) { // shift y down + for (x=0; x<4; ++x) { + for (y=1; y<4; y++) + if (this.b[y][x]==0) { + for (i=y; i>0; i--) this.b[i][x] = this.b[i-1][x]; + this.b[0][x] = 0; + } + for (y=3; y>0; y--) + if (this.b[y][x]==this.b[y-1][x] || this.b[y][x]==0) { + this.score += 2*this.b[y][x]; + this.b[y][x] += this.b[y-1][x]; + for (j=y-1; j>0; j--) this.b[j][x] = this.b[j-1][x]; + this.b[0][x] = 0; + } + } + } + else if (d==2) { // shift y up + for (x=0; x<4; ++x) { + for (y=2; y>=0; y--) + if (this.b[y][x]==0) { + for (i=y; i<3; i++) this.b[i][x] = this.b[i+1][x]; + this.b[3][x] = 0; + } + for (y=0; y<3; ++y) + if (this.b[y][x]==this.b[y+1][x] || this.b[y][x]==0) { + this.score += 2*this.b[y][x]; + this.b[y][x] += this.b[y+1][x]; + for (j=y+1; j<3; ++j) this.b[j][x] = this.b[j+1][x]; + this.b[3][x] = 0; + } + } + } + return (E.CRC32(this.b.toString())!=crc); + } + addDigit() { + var d = Math.random()>0.9 ? 4 : 2; + var id = Math.floor(Math.random()*16); + while (this.b[Math.floor(id/4)][id%4] > 0) id = Math.floor(Math.random()*16); + this.b[Math.floor(id/4)][id%4] = d; + } +} + +function dragHandler(e) { + if (e.b && (Math.abs(e.dx)>7 || Math.abs(e.dy)>7)) { + var res = false; + if (Math.abs(e.dx)>Math.abs(e.dy)) { + if (e.dx>0) res = twok.shift(1); + if (e.dx<0) res = twok.shift(-1); + } + else { + if (e.dy>0) res = twok.shift(-2); + if (e.dy<0) res = twok.shift(2); + } + if (res) twok.addDigit(); + twok.render(); + } +} + +var twok = new TwoK(); +twok.addDigit(); twok.addDigit(); +twok.render(); +Bangle.on("drag", dragHandler); diff --git a/apps/2047pp/README.md b/apps/2047pp/README.md new file mode 100644 index 000000000..e685946c1 --- /dev/null +++ b/apps/2047pp/README.md @@ -0,0 +1,6 @@ + +# Game of 2047pp (2047++) + +Tile shifting game inspired by the well known 2048 game. Also very similar to another Bangle game, Game1024. + +Attempt to combine equal numbers by swiping left, right, up or down. diff --git a/apps/2047pp/app-icon.js b/apps/2047pp/app-icon.js new file mode 100644 index 000000000..4086d1879 --- /dev/null +++ b/apps/2047pp/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A31gAeFtoxPF9wujGBYQG1YAWF6ur5gAYGIovOFzIABF6ReaMAwv/F/4v/F7ejv9/0Yvq1Eylksv4vqvIuBF9ZeDF9ZeBqovr1AsB0YvrLwXMF9ReDF9ZeBq1/v4vBqowKF7lWFYIAFF/7vXAAa/qF+jxB0YvsABov/F/4v/F6WsF7YgEF5xgaLwgvPGIQAWDwwvQADwvJGEguKF+AxhFpoA/AH4A/AFI=")) \ No newline at end of file diff --git a/apps/2047pp/app.png b/apps/2047pp/app.png new file mode 100644 index 0000000000000000000000000000000000000000..d1fb4a5e5cbadce3da7b36806d4907f680f8f1bb GIT binary patch literal 759 zcmVYCuD-J|F04S(qype>Ll^oVPK(#&(MEqX}dr+-4N41U&D6G<#pk8p=ry~PL z8P~wDv0Fxk@G(D*-Esf`5DS+uaYFpJB+Wpns^Zb}E7&tzFF7%tm102J6nd69|4+P3 zr1VfMt5{y0fI>`0^KD2mu=F8{y6M673*Til--d7lyX64hte$~F4EL^Xh;F_M;RY5n zQPi8Q(T|*z{|6UpV5c0w+w9;*9}v8ZE@h%j4t73gCg!Qfe`}$Ac#sB~}C%=m9 zQmlcEAIAXzTR($HE?;WPt>nU3$%Ta*5c>_tUp2cB`Ud77yzh$Lczg#y>rX6t^Z|D> zcQA?REP&Q#P6pBq$e1?!8Tl#X8W=W?3|OSe*3omHjttb47#RG02|5f6e$ zObTJ!ml%i%20ylab9U#WUDz$7W@n({nZhq+5~`IO*5Pi07qm053E*;P(4-Kmo@>1; z>;uNw7hc?M3Z*1!=?Nlw%8PRi7>4l#z$>YW4#!KwFcx?T+e^N5I_>#$tusSJ+zSuc p5YZ-MEM*wRg54#bi;K&M^BdjW!v$Q*XjK3J002ovPDHLkV1lLySIGbX literal 0 HcmV?d00001 diff --git a/apps/2047pp/metadata.json b/apps/2047pp/metadata.json new file mode 100644 index 000000000..7c120efec --- /dev/null +++ b/apps/2047pp/metadata.json @@ -0,0 +1,14 @@ +{ "id": "2047pp", + "name": "2047pp", + "shortName":"2047pp", + "icon": "app.png", + "version":"0.01", + "description": "Bangle version of a tile shifting game", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "tags": "game", + "storage": [ + {"name":"2047pp.app.js","url":"2047pp.app.js"}, + {"name":"2047pp.img","url":"app-icon.js","evaluate":true} + ] +} From 0342b131249937c8a692f0a500748d264b3ab05e Mon Sep 17 00:00:00 2001 From: marko Date: Wed, 23 Mar 2022 15:38:16 -0400 Subject: [PATCH 39/64] Support Bangles 1&2 --- apps/2047pp/2047pp.app.js | 31 +++++++++++++++++++++---------- apps/2047pp/README.md | 3 ++- apps/2047pp/metadata.json | 2 +- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/apps/2047pp/2047pp.app.js b/apps/2047pp/2047pp.app.js index b508d8f07..9b2283036 100644 --- a/apps/2047pp/2047pp.app.js +++ b/apps/2047pp/2047pp.app.js @@ -1,15 +1,13 @@ - - class TwoK { constructor() { this.b = Array(4).fill().map(() => Array(4).fill(0)); this.score = 0; this.cmap = {0: "#caa", 2:"#ccc", 4: "#bcc", 8: "#ba6", 16: "#e61", 32: "#d20", 64: "#d00", 128: "#da0", 256: "#ec0", 512: "#dd0"}; } - drawBRect(x1, y1, x2, y2, th, c, cf) { + drawBRect(x1, y1, x2, y2, th, c, cf, fill) { g.setColor(c); for (i=0; i 4) g.setColor(1, 1, 1); else g.setColor(0, 0, 0); g.setFont("Vector", bh*(b>8 ? (b>64 ? (b>512 ? 0.32 : 0.4) : 0.6) : 0.7)); @@ -38,14 +36,14 @@ class TwoK { for (x=2; x>=0; x--) if (this.b[y][x]==0) { for (i=x; i<3; i++) this.b[y][i] = this.b[y][i+1]; - this.b[y][3] = 0; moved = true; + this.b[y][3] = 0; } for (x=0; x<3; ++x) if (this.b[y][x]==this.b[y][x+1]) { this.score += 2*this.b[y][x]; this.b[y][x] += this.b[y][x+1]; for (j=x+1; j<3; ++j) this.b[y][j] = this.b[y][j+1]; - this.b[y][3] = 0; moved = true; + this.b[y][3] = 0; } } } @@ -123,7 +121,20 @@ function dragHandler(e) { } } +function swipeHandler() { + +} + +function buttonHandler() { + +} + var twok = new TwoK(); twok.addDigit(); twok.addDigit(); twok.render(); -Bangle.on("drag", dragHandler); +if (process.env.HWVERSION==2) Bangle.on("drag", dragHandler); +else if (process.env.HWVERSION==1) { + Bangle.on("swipe", (e) => { res = twok.shift(e); if (res) twok.addDigit(); twok.render(); }); + setWatch(() => { res = twok.shift(2); if (res) twok.addDigit(); twok.render(); }, BTN1, {repeat: true}); + setWatch(() => { res = twok.shift(-2); if (res) twok.addDigit(); twok.render(); }, BTN3, {repeat: true}); +} diff --git a/apps/2047pp/README.md b/apps/2047pp/README.md index e685946c1..8228892fd 100644 --- a/apps/2047pp/README.md +++ b/apps/2047pp/README.md @@ -3,4 +3,5 @@ Tile shifting game inspired by the well known 2048 game. Also very similar to another Bangle game, Game1024. -Attempt to combine equal numbers by swiping left, right, up or down. +Attempt to combine equal numbers by swiping left, right, up or down (on Bangle 2) or swiping left/right and using +the top/bottom button (Bangle 1). diff --git a/apps/2047pp/metadata.json b/apps/2047pp/metadata.json index 7c120efec..0241e9933 100644 --- a/apps/2047pp/metadata.json +++ b/apps/2047pp/metadata.json @@ -4,7 +4,7 @@ "icon": "app.png", "version":"0.01", "description": "Bangle version of a tile shifting game", - "supports" : ["BANGLEJS2"], + "supports" : ["BANGLEJS1","BANGLEJS2"], "readme": "README.md", "tags": "game", "storage": [ From 0c24bd397aeb82f5d3d1af2794e95942e9039c5f Mon Sep 17 00:00:00 2001 From: marko Date: Wed, 23 Mar 2022 15:40:28 -0400 Subject: [PATCH 40/64] Use correct string for supported devices --- apps/2047pp/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/2047pp/metadata.json b/apps/2047pp/metadata.json index 0241e9933..0a362c583 100644 --- a/apps/2047pp/metadata.json +++ b/apps/2047pp/metadata.json @@ -4,7 +4,7 @@ "icon": "app.png", "version":"0.01", "description": "Bangle version of a tile shifting game", - "supports" : ["BANGLEJS1","BANGLEJS2"], + "supports" : ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "tags": "game", "storage": [ From 60c3a3249459c7219ed28b8b1c8d6b9f7cd98fe8 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Wed, 23 Mar 2022 21:52:26 +0100 Subject: [PATCH 41/64] waypointer Bangle.js 2 compatibility --- apps/waypointer/ChangeLog | 2 ++ apps/waypointer/README.md | 2 +- apps/waypointer/app.js | 55 ++++++++++++++++++----------------- apps/waypointer/metadata.json | 4 +-- 4 files changed, 34 insertions(+), 29 deletions(-) create mode 100644 apps/waypointer/ChangeLog diff --git a/apps/waypointer/ChangeLog b/apps/waypointer/ChangeLog new file mode 100644 index 000000000..1b584f7dd --- /dev/null +++ b/apps/waypointer/ChangeLog @@ -0,0 +1,2 @@ +0.01: New app! +0.02: Make Bangle.js 2 compatible diff --git a/apps/waypointer/README.md b/apps/waypointer/README.md index e98fdbb7e..c0b4c5125 100644 --- a/apps/waypointer/README.md +++ b/apps/waypointer/README.md @@ -24,7 +24,7 @@ need to travel in to reach the selected waypoint. The blue text is the name of the current waypoint. NONE means that there is no waypoint set and so bearing and distance will remain at 0. To select a waypoint, press BTN2 (middle) and wait for the blue text to turn -white. Then use BTN1 and BTN3 to select a waypoint. The waypoint +white. Then use BTN1 and BTN3 (swipe up/down on Bangle.js 2) to select a waypoint. The waypoint choice is fixed by pressing BTN2 again. In the screen shot below a waypoint giving the location of Stone Henge has been selected. diff --git a/apps/waypointer/app.js b/apps/waypointer/app.js index d3aab7c50..615fbbc36 100644 --- a/apps/waypointer/app.js +++ b/apps/waypointer/app.js @@ -1,24 +1,25 @@ -var pal_by = new Uint16Array([0x0000,0xFFC0],0,1); // black, yellow -var pal_bw = new Uint16Array([0x0000,0xffff],0,1); // black, white -var pal_bb = new Uint16Array([0x0000,0x07ff],0,1); // black, blue +const scale = g.getWidth()/240; +var pal_by = new Uint16Array([g.getBgColor(),0xFFC0],0,1); // black, yellow +var pal_bw = new Uint16Array([g.getBgColor(),g.getColor()],0,1); // black, white +var pal_bb = new Uint16Array([g.getBgColor(),0x07ff],0,1); // black, blue // having 3 2 color pallette keeps the memory requirement lower -var buf1 = Graphics.createArrayBuffer(160,160,1, {msb:true}); -var buf2 = Graphics.createArrayBuffer(80,40,1, {msb:true}); +var buf1 = Graphics.createArrayBuffer(160*scale,160*scale,1, {msb:true}); +var buf2 = Graphics.createArrayBuffer(g.getWidth()/3,40*scale,1, {msb:true}); var arrow_img = require("heatshrink").decompress(atob("lEowIPMjAEDngEDvwED/4DCgP/wAEBgf/4AEBg//8AEBh//+AEBj///AEBn///gEBv///wmCAAImCAAIoBFggE/AkaaEABo=")); function flip1(x,y) { - g.drawImage({width:160,height:160,bpp:1,buffer:buf1.buffer, palette:pal_by},x,y); + g.drawImage({width:160*scale,height:160*scale,bpp:1,buffer:buf1.buffer, palette:pal_by},x,y); buf1.clear(); } function flip2_bw(x,y) { - g.drawImage({width:80,height:40,bpp:1,buffer:buf2.buffer, palette:pal_bw},x,y); + g.drawImage({width:g.getWidth()/3,height:40*scale,bpp:1,buffer:buf2.buffer, palette:pal_bw},x,y); buf2.clear(); } function flip2_bb(x,y) { - g.drawImage({width:80,height:40,bpp:1,buffer:buf2.buffer, palette:pal_bb},x,y); + g.drawImage({width:g.getWidth()/3,height:40*scale,bpp:1,buffer:buf2.buffer, palette:pal_bb},x,y); buf2.clear(); } @@ -51,12 +52,12 @@ function drawCompass(course) { previous.course = course; buf1.setColor(1); - buf1.fillCircle(80,80,79,79); + buf1.fillCircle(buf1.getWidth()/2,buf1.getHeight()/2,79*scale); buf1.setColor(0); - buf1.fillCircle(80,80,69,69); + buf1.fillCircle(buf1.getWidth()/2,buf1.getHeight()/2,69*scale); buf1.setColor(1); - buf1.drawImage(arrow_img, 80, 80, {scale:3, rotate:radians(course)} ); - flip1(40, 30); + buf1.drawImage(arrow_img, buf1.getWidth()/2, buf1.getHeight()/2, {scale:3*scale, rotate:radians(course)} ); + flip1(40*scale, Bangle.appRect.y+6*scale); } /***** COMPASS CODE ***********/ @@ -138,7 +139,7 @@ function distance(a,b){ function drawN(){ - buf2.setFont("Vector",24); + buf2.setFont("Vector",24*scale); var bs = wp_bearing.toString(); bs = wp_bearing<10?"00"+bs : wp_bearing<100 ?"0"+bs : bs; var dst = loc.distance(dist); @@ -147,12 +148,12 @@ function drawN(){ // show distance on the left if (previous.dst !== dst) { - previous.dst = dst + previous.dst = dst; buf2.setColor(1); buf2.setFontAlign(-1,-1); - buf2.setFont("Vector", 20); + buf2.setFont("Vector", 20*scale); buf2.drawString(dst,0,0); - flip2_bw(0, 200); + flip2_bw(0, g.getHeight()-40*scale); } // bearing, place in middle at bottom of compass @@ -160,9 +161,9 @@ function drawN(){ previous.bs = bs; buf2.setColor(1); buf2.setFontAlign(0, -1); - buf2.setFont("Vector",38); - buf2.drawString(bs,40,0); - flip2_bw(80, 200); + buf2.setFont("Vector",38*scale); + buf2.drawString(bs,40*scale,0); + flip2_bw(g.getWidth()/3, g.getHeight()-40*scale); } // waypoint name on right @@ -170,13 +171,13 @@ function drawN(){ previous.selected = selected; buf2.setColor(1); buf2.setFontAlign(1,-1); // right, bottom - buf2.setFont("Vector", 20); - buf2.drawString(wp.name, 80, 0); + buf2.setFont("Vector", 20*scale); + buf2.drawString(wp.name, 80*scale, 0); if (selected) - flip2_bw(160, 200); + flip2_bw(g.getWidth()/3*2, g.getHeight()-40*scale); else - flip2_bb(160, 200); + flip2_bb(g.getWidth()/3*2, g.getHeight()-40*scale); } } @@ -229,9 +230,11 @@ function startdraw(){ } function setButtons(){ - setWatch(nextwp.bind(null,-1), BTN1, {repeat:true,edge:"falling"}); - setWatch(doselect, BTN2, {repeat:true,edge:"falling"}); - setWatch(nextwp.bind(null,1), BTN3, {repeat:true,edge:"falling"}); + Bangle.setUI("updown", d=>{ + if (d<0) { nextwp(-1); } + else if (d>0) { nextwp(1); } + else { doselect(); } + }); } Bangle.on('lcdPower',function(on) { diff --git a/apps/waypointer/metadata.json b/apps/waypointer/metadata.json index cb477107b..111259bbc 100644 --- a/apps/waypointer/metadata.json +++ b/apps/waypointer/metadata.json @@ -1,11 +1,11 @@ { "id": "waypointer", "name": "Way Pointer", - "version": "0.01", + "version": "0.02", "description": "Navigate to a waypoint using the GPS for bearing and compass to point way, uses the same waypoint interface as GPS Navigation", "icon": "waypointer.png", "tags": "tool,outdoors,gps", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", "interface": "waypoints.html", "storage": [ From fd8cc60283512c3aa649f525a4d01482af79e531 Mon Sep 17 00:00:00 2001 From: marko Date: Wed, 23 Mar 2022 17:24:42 -0400 Subject: [PATCH 42/64] Make border thicknesses more consistent; add screenshot; allow running in emulator. --- apps/2047pp/2047pp.app.js | 8 ++++---- apps/2047pp/2047pp_screenshot.png | Bin 0 -> 4541 bytes apps/2047pp/README.md | 2 ++ apps/2047pp/metadata.json | 1 + 4 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 apps/2047pp/2047pp_screenshot.png diff --git a/apps/2047pp/2047pp.app.js b/apps/2047pp/2047pp.app.js index 9b2283036..58738d04a 100644 --- a/apps/2047pp/2047pp.app.js +++ b/apps/2047pp/2047pp.app.js @@ -18,11 +18,11 @@ class TwoK { bw = Math.floor(w/4); g.clearRect(0, 0, g.getWidth()-1, yo).setFontAlign(0, 0, 0); g.setFont("Vector", 16).setColor("#fff").drawString("Score:"+this.score.toString(), g.getWidth()/2, 8); - this.drawBRect(xo-2, yo-2, xo+w+2, yo+h, 2, "#a88", "#caa", false); + this.drawBRect(xo-3, yo-3, xo+w+2, yo+h+2, 4, "#a88", "#caa", false); for (y=0; y<4; ++y) for (x=0; x<4; ++x) { b = this.b[y][x]; - this.drawBRect(xo+x*bw, yo+y*bh-2, xo+(x+1)*bh, yo+(y+1)*bh-2, 4, "#a88", this.cmap[b], true); + this.drawBRect(xo+x*bw, yo+y*bh-1, xo+(x+1)*bh-1, yo+(y+1)*bh-2, 4, "#a88", this.cmap[b], true); if (b > 4) g.setColor(1, 1, 1); else g.setColor(0, 0, 0); g.setFont("Vector", bh*(b>8 ? (b>64 ? (b>512 ? 0.32 : 0.4) : 0.6) : 0.7)); @@ -35,7 +35,7 @@ class TwoK { for (y=0; y<4; ++y) { for (x=2; x>=0; x--) if (this.b[y][x]==0) { - for (i=x; i<3; i++) this.b[y][i] = this.b[y][i+1]; + for (i=x; i<3; i++) this.b[y][i] = this.b[y][i+1]; this.b[y][3] = 0; } for (x=0; x<3; ++x) @@ -133,7 +133,7 @@ var twok = new TwoK(); twok.addDigit(); twok.addDigit(); twok.render(); if (process.env.HWVERSION==2) Bangle.on("drag", dragHandler); -else if (process.env.HWVERSION==1) { +if (process.env.HWVERSION==1) { Bangle.on("swipe", (e) => { res = twok.shift(e); if (res) twok.addDigit(); twok.render(); }); setWatch(() => { res = twok.shift(2); if (res) twok.addDigit(); twok.render(); }, BTN1, {repeat: true}); setWatch(() => { res = twok.shift(-2); if (res) twok.addDigit(); twok.render(); }, BTN3, {repeat: true}); diff --git a/apps/2047pp/2047pp_screenshot.png b/apps/2047pp/2047pp_screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..8c407fb6fe4b78d2f60c30651c60f8513b5c658d GIT binary patch literal 4541 zcmb7IeLR!v|G)0dHnvO;jF41Ehnc7j<{^iv6cR%7w1%Pxqhd_8IW4K^(L)KTwDOR7 zikP~kA}SAgs>WFdQN%op%J1f!*ZKYN{pb7rW4rHN_x1T)*XMJ6-tW)*Uc94&jXYiz z4*-zgxnrv{a+NN8WUeIRz-D5Dw-l)7IVsiw%R9=Da08V82n~HFK1fT@J?grx`!HyioMg5>d)+R z)=tU^O%<$7YFRznr?2Dctv@_G{IjCxcSTE{HCc+{4)%T>oPTT)G$45L!$t7%MLxUM z$?UZLv8I}-miAbHx__&i2#@BnW$#&?7oSgTZjay$ji z@{!9^I_h)9+xxNlzCh8VFmL%OZaO4+dgngih@*7>ozy>27Z!52DG9&xFqXTm z+We!2wEg|N_nIEDBk8jnM*MMMVU zx~KeGk+_4bkuvp+oi0|ri|bZ|yH7-q_)I1>p40}gQchO;ixw@-lrCqZ%w&b+Z-3g= zI=o)$9Db+!h+y`bYQw~Q{-GHDRk8gF;HA@Mcc!hYtMvYaN1@=Z(9>b)(65tAi!{Lf z#wvx&wqt)D-V9=j?*~ZMA)eRmzqC=Dhx(6!H1LGOE_e4)q2ecQ!D(}I`}czK6iTy# z;ts^>Di*I!hkylZY`_!`3=Fv_oEzXO+%T_5M6Cgt4uTr;+{{e*yT}*q0fG6Vs_&D> zGFL@Td})Zg@KPHvcK9iwfEhU?ZmDi45bdaM=P=L4#Ni{vE#$FF<5(EPxG{Uzu8ukk zmcU?$^{tAQ_YMQl24G~Hpyx4&(Q}@1s=p-gJ^$M3EpZJpDFBm41Cmt(N5w7mo)e0* zW&0T3lVd;ncPD0iSR>kAWEg!`h=;hCjcTVoqRz>%<+~3J0I?8!@^t3|*zA|*VRpKq zPCy{RFl}1a(iWV_wl#bx8Nt5~bn;EXMRH3GSjiYX;7IBF8cEuH493a#$7{;c`Tjz`VP(zrvYANgF03ql9M0W~v4&Z;Pn4Iy zv14v)z(4DPSE{LoENjg+Pe@wo``xKBiTsr3TJruGa!{E{3;F&4?sq@0SKD*&Zj*hM zYmVxR4lMnvX=PK6uhppA8VPLmRM{w~Z`UzqsTs&4lN3XdvcSph>�s2LAZhiWN(X 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 literal 0 HcmV?d00001 diff --git a/apps/2047pp/README.md b/apps/2047pp/README.md index 8228892fd..cac3323a6 100644 --- a/apps/2047pp/README.md +++ b/apps/2047pp/README.md @@ -5,3 +5,5 @@ Tile shifting game inspired by the well known 2048 game. Also very similar to an Attempt to combine equal numbers by swiping left, right, up or down (on Bangle 2) or swiping left/right and using the top/bottom button (Bangle 1). + +![Screenshot](./2047pp_screenshot.png) diff --git a/apps/2047pp/metadata.json b/apps/2047pp/metadata.json index 0a362c583..f0fd6c1e3 100644 --- a/apps/2047pp/metadata.json +++ b/apps/2047pp/metadata.json @@ -5,6 +5,7 @@ "version":"0.01", "description": "Bangle version of a tile shifting game", "supports" : ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "readme": "README.md", "tags": "game", "storage": [ From dd27b11eb87b5087e668549ab59b908260c10545 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Thu, 24 Mar 2022 09:43:32 +0800 Subject: [PATCH 43/64] Update app.js Fix automatic font sizing. --- apps/authentiwatch/app.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index 6e2951b9f..05d94fc46 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -119,15 +119,14 @@ var state = { function sizeFont(id, txt, w) { let sz = fontszCache[id]; - if (sz) { - g.setFont("Vector", sz); - } else { + if (!sz) { sz = TOKEN_DIGITS_HEIGHT; do { g.setFont("Vector", sz--); } while (g.stringWidth(txt) > w); - fontszCache[id] = sz + 1; + fontszCache[id] = ++sz; } + g.setFont("Vector", sz); } tokenY = id => id * TOKEN_HEIGHT + AR.y - state.listy; From ae74a508384a9f92ed3acc7bf776106a251fcd58 Mon Sep 17 00:00:00 2001 From: Alessandro Cocco Date: Wed, 23 Mar 2022 22:54:32 +0100 Subject: [PATCH 44/64] [Wave Clock] Show the day of the week --- apps/waveclk/ChangeLog | 1 + apps/waveclk/app.js | 4 ++++ apps/waveclk/metadata.json | 2 +- apps/waveclk/screenshot.png | Bin 2884 -> 6237 bytes 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/waveclk/ChangeLog b/apps/waveclk/ChangeLog index 8c2a33143..f1fb77c59 100644 --- a/apps/waveclk/ChangeLog +++ b/apps/waveclk/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Load widgets after setUI so widclk knows when to hide +0.03: Show the day of the week diff --git a/apps/waveclk/app.js b/apps/waveclk/app.js index f1c67ce2f..18b28500b 100644 --- a/apps/waveclk/app.js +++ b/apps/waveclk/app.js @@ -41,6 +41,7 @@ function draw() { var date = new Date(); var timeStr = require("locale").time(date,1); var dateStr = require("locale").date(date).toUpperCase(); + var dowStr = require("locale").dow(date).toUpperCase(); // draw time g.setFontAlign(0,0).setFont("ZCOOL"); g.drawString(timeStr,x,y); @@ -48,6 +49,9 @@ function draw() { y += 35; g.setFontAlign(0,0,1).setFont("6x8"); g.drawString(dateStr,g.getWidth()-8,g.getHeight()/2); + // draw the day of the week + g.setFontAlign(0,0,3).setFont("6x8"); + g.drawString(dowStr,8,g.getHeight()/2); // queue draw in one minute queueDraw(); } diff --git a/apps/waveclk/metadata.json b/apps/waveclk/metadata.json index a8d270da2..9ba2798ff 100644 --- a/apps/waveclk/metadata.json +++ b/apps/waveclk/metadata.json @@ -1,7 +1,7 @@ { "id": "waveclk", "name": "Wave Clock", - "version": "0.02", + "version": "0.03", "description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/)", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], diff --git a/apps/waveclk/screenshot.png b/apps/waveclk/screenshot.png index 7f05ce68880935db0de4aaf840077e9b30413db7..161ef96ef0194e95c23821555e073a21607dfc83 100644 GIT binary patch literal 6237 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 Hu0mjfXH(f- literal 2884 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 iR539+GBi3hI4dwQIxsMs^94u%0000 Date: Thu, 24 Mar 2022 15:52:19 +0000 Subject: [PATCH 45/64] first hack at support for running the App Loader inside Gadgetbridge --- gadgetbridge.js | 162 ++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 1 + 2 files changed, 163 insertions(+) create mode 100644 gadgetbridge.js diff --git a/gadgetbridge.js b/gadgetbridge.js new file mode 100644 index 000000000..679fffc60 --- /dev/null +++ b/gadgetbridge.js @@ -0,0 +1,162 @@ +/* Detects if we're running under Gadgetbridge in a WebView, and if +so it overwrites the 'Puck' library with a special one that calls back +into Gadgetbridge to handle watch communications */ + +/*// test code +Android = { + bangleTx : function(data) { + console.log("TX : "+JSON.stringify(data)); + } +};*/ + +if (typeof Android!=="undefined") { + console.log("Running under Gadgetbridge, overwrite Puck library"); + + var isBusy = false; + var queue = []; + var connection = { + cb : function(data) {}, + write : function(data, writecb) { + Android.bangleTx(data); + Puck.writeProgress(data.length, data.length); + if (writecb) setTimeout(writecb,10); + }, + close : function() {}, + received : "", + hadData : false + } + + function bangleRx(data) { +// document.getElementById("status").innerText = "RX:"+data; + connection.received += data; + connection.hadData = true; + if (connection.cb) connection.cb(data); + } + + function log(level, s) { + if (Puck.log) Puck.log(level, s); + } + + function handleQueue() { + if (!queue.length) return; + var q = queue.shift(); + log(3,"Executing "+JSON.stringify(q)+" from queue"); + if (q.type == "write") Puck.write(q.data, q.callback, q.callbackNewline); + else log(1,"Unknown queue item "+JSON.stringify(q)); + } + + /* convenience function... Write data, call the callback with data: + callbackNewline = false => if no new data received for ~0.2 sec + callbackNewline = true => after a newline */ + function write(data, callback, callbackNewline) { + let result; + /// If there wasn't a callback function, then promisify + if (typeof callback !== 'function') { + callbackNewline = callback; + + result = new Promise((resolve, reject) => callback = (value, err) => { + if (err) reject(err); + else resolve(value); + }); + } + + if (isBusy) { + log(3, "Busy - adding Puck.write to queue"); + queue.push({type:"write", data:data, callback:callback, callbackNewline:callbackNewline}); + return result; + } + + var cbTimeout; + function onWritten() { + if (callbackNewline) { + connection.cb = function(d) { + var newLineIdx = connection.received.indexOf("\n"); + if (newLineIdx>=0) { + var l = connection.received.substr(0,newLineIdx); + connection.received = connection.received.substr(newLineIdx+1); + connection.cb = undefined; + if (cbTimeout) clearTimeout(cbTimeout); + cbTimeout = undefined; + if (callback) + callback(l); + isBusy = false; + handleQueue(); + } + }; + } + // wait for any received data if we have a callback... + var maxTime = 300; // 30 sec - Max time we wait in total, even if getting data + var dataWaitTime = callbackNewline ? 100/*10 sec if waiting for newline*/ : 3/*300ms*/; + var maxDataTime = dataWaitTime; // max time we wait after having received data + cbTimeout = setTimeout(function timeout() { + cbTimeout = undefined; + if (maxTime) maxTime--; + if (maxDataTime) maxDataTime--; + if (connection.hadData) maxDataTime=dataWaitTime; + if (maxDataTime && maxTime) { + cbTimeout = setTimeout(timeout, 100); + } else { + connection.cb = undefined; + if (callback) + callback(connection.received); + isBusy = false; + handleQueue(); + connection.received = ""; + } + connection.hadData = false; + }, 100); + } + + if (!connection.txInProgress) connection.received = ""; + isBusy = true; + connection.write(data, onWritten); + return result + } + + // ---------------------------------------------------------- + + Puck = { + /// Are we writing debug information? 0 is no, 1 is some, 2 is more, 3 is all. + debug : Puck.debug, + /// Should we use flow control? Default is true + flowControl : true, + /// Used internally to write log information - you can replace this with your own function + log : function(level, s) { if (level <= this.debug) console.log(" "+s)}, + /// Called with the current send progress or undefined when done - you can replace this with your own function + writeProgress : Puck.writeProgress, + connect : function(callback) { + setTimeout(callback, 10); + }, + write : write, + eval : function(expr, cb) { + const response = write('\x10Bluetooth.println(JSON.stringify(' + expr + '))\n', true) + .then(function (d) { + try { + return JSON.parse(d); + } catch (e) { + log(1, "Unable to decode " + JSON.stringify(d) + ", got " + e.toString()); + return Promise.reject(d); + } + }); + if (cb) { + return void response.then(cb, (err) => cb(null, err)); + } else { + return response; + } + }, + isConnected : function() { return true; }, + getConnection : function() { return connection; }, + close : function() { + if (connection) + connection.close(); + }, + }; + // no need for header + document.getElementsByTagName("header")[0].style="display:none"; + // force connection attempt automatically + setTimeout(function() { + getInstalledApps(true).catch(err => { + showToast("Device connection failed, "+err,"error"); + }); + }, 100); +} diff --git a/index.html b/index.html index a418b48eb..bd8ddea5a 100644 --- a/index.html +++ b/index.html @@ -179,5 +179,6 @@ + From 0a347ee03166662d5dbed0eb3d7ba292079dd15f Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 24 Mar 2022 16:16:25 +0000 Subject: [PATCH 46/64] boot 0.46: Fix no clock found error on Bangle.js 2 --- apps/boot/ChangeLog | 1 + apps/boot/bootloader.js | 2 +- apps/boot/metadata.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index ef437fe3b..87b5f7def 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -49,3 +49,4 @@ 0.43: Fix Gadgetbridge handling with Programmable:off 0.44: Write .boot0 without ever having it all in RAM (fix Bangle.js 1 issues with BTHRM) 0.45: Fix 0.44 regression (auto-add semi-colon between each boot code chunk) +0.46: Fix no clock found error on Bangle.js 2 diff --git a/apps/boot/bootloader.js b/apps/boot/bootloader.js index 3cf885ac9..45e271f30 100644 --- a/apps/boot/bootloader.js +++ b/apps/boot/bootloader.js @@ -14,6 +14,6 @@ if (!clockApp) { if (clockApp) clockApp = require("Storage").read(clockApp.src); } -if (!clockApp) clockApp=`E.showMessage("No Clock Found");setWatch(()=>{Bangle.showLauncher();}, BTN2, {repeat:false,edge:"falling"});`; +if (!clockApp) clockApp=`E.showMessage("No Clock Found");setWatch(()=>{Bangle.showLauncher();}, global.BTN2||BTN, {repeat:false,edge:"falling"});`; eval(clockApp); delete clockApp; diff --git a/apps/boot/metadata.json b/apps/boot/metadata.json index c21ab6833..11884576f 100644 --- a/apps/boot/metadata.json +++ b/apps/boot/metadata.json @@ -1,7 +1,7 @@ { "id": "boot", "name": "Bootloader", - "version": "0.45", + "version": "0.46", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "icon": "bootloader.png", "type": "bootloader", From 6dbe7dfee1ddc3a661c509fc298b36063f033a28 Mon Sep 17 00:00:00 2001 From: chiefdaft Date: Fri, 25 Mar 2022 00:31:38 +0100 Subject: [PATCH 47/64] Bug: Fixed issue #1609 added a message popup state handler to control unwanted screen redraw --- apps/game1024/app.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/apps/game1024/app.js b/apps/game1024/app.js index 9f6081376..133630634 100644 --- a/apps/game1024/app.js +++ b/apps/game1024/app.js @@ -144,6 +144,13 @@ const buttons = { }, add: function(btn) { this.all.push(btn); + }, + isPopUpActive: false, + activatePopUp: function() { + this.isPopUpActive = true; + }, + deActivatePopUp: function() { + this.isPopUpActive = false; } }; /** @@ -253,7 +260,6 @@ const dragThreshold = 10; const clickThreshold = 3; let allSquares = []; -// let buttons = []; class Button { constructor(name, x0, y0, width, height, text, bg, fg, cb, enabled) { @@ -483,6 +489,7 @@ function initGame() { Bangle.drawWidgets(); } function drawPopUp(message,cb) { + buttons.activatePopUp(); g.setColor('#FFFFFF'); let rDims = Bangle.appRect; g.fillPoly([rDims.x+10, rDims.y+20, @@ -505,6 +512,7 @@ function drawPopUp(message,cb) { g.drawString(message, rDims.x+20, rDims.y+20); buttons.add(btnYes); buttons.add(btnNo); + } function handlePopUpClicks(btn) { const name = btn.name; @@ -512,6 +520,7 @@ function handlePopUpClicks(btn) { buttons.all.pop(); // remove the yes button buttons.all.forEach(b => {b.enable();}); // enable the remaining buttons again debug(() => console.log("Button name =", name)); + buttons.deActivatePopUp(); switch (name) { case 'yes': resetGame(); @@ -568,14 +577,13 @@ function handleclick(e) { // Handle a drag event (moving the stones around) function handledrag(e) { - /*debug(Math.abs(e.dx) > Math.abs(e.dy) ? - (e.dx > 0 ? e => console.log('To the right') : e => console.log('To the left') ) : - (e.dy > 0 ? e => console.log('Move down') : e => console.log('Move up') )); - */ - // [move.right, move.left, move.up, move.down] - runGame((Math.abs(e.dx) > Math.abs(e.dy) ? - (e.dx > 0 ? mover.direction.right : mover.direction.left ) : - (e.dy > 0 ? mover.direction.down : mover.direction.up ))); + // Stop moving things around when the popup message is active + // Bangleapps issue #1609 + if (!(buttons.isPopUpActive)) { + runGame((Math.abs(e.dx) > Math.abs(e.dy) ? + (e.dx > 0 ? mover.direction.right : mover.direction.left ) : + (e.dy > 0 ? mover.direction.down : mover.direction.up ))); + } } // Evaluate "drag" events from the UI and call handlers for drags or clicks // The UI sends a drag as a series of events indicating partial movements From a46051a88ec208a12edf48cf4ee7a3a2824a1dfc Mon Sep 17 00:00:00 2001 From: chiefdaft Date: Fri, 25 Mar 2022 00:38:17 +0100 Subject: [PATCH 48/64] v0.06 added to metadata and changelog --- apps/game1024/ChangeLog | 3 ++- apps/game1024/metadata.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/game1024/ChangeLog b/apps/game1024/ChangeLog index ffb1f94bc..8759fb428 100644 --- a/apps/game1024/ChangeLog +++ b/apps/game1024/ChangeLog @@ -2,4 +2,5 @@ 0.02: Temporary intermediate version 0.03: Basic colors 0.04: Bug fix score reset after Game Over, new icon -0.05: Chevron marker on the randomly added square \ No newline at end of file +0.05: Chevron marker on the randomly added square +0.06: Fixed issue 1609 added a message popup state handler to control unwanted screen redraw \ No newline at end of file diff --git a/apps/game1024/metadata.json b/apps/game1024/metadata.json index 557d77b89..73d7607f3 100644 --- a/apps/game1024/metadata.json +++ b/apps/game1024/metadata.json @@ -1,7 +1,7 @@ { "id": "game1024", "name": "1024 Game", "shortName" : "1024 Game", - "version": "0.05", + "version": "0.06", "icon": "game1024.png", "screenshots": [ {"url":"screenshot.png" } ], "readme":"README.md", From f8d64ab7dd51ce9e391b27001b364226d2021f94 Mon Sep 17 00:00:00 2001 From: Smooklu <37220586+Smooklu@users.noreply.github.com> Date: Thu, 24 Mar 2022 20:19:24 -0500 Subject: [PATCH 49/64] Create app.js --- apps/gsat/app.js | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 apps/gsat/app.js diff --git a/apps/gsat/app.js b/apps/gsat/app.js new file mode 100644 index 000000000..3a7d443fe --- /dev/null +++ b/apps/gsat/app.js @@ -0,0 +1,38 @@ +// Clear screen +g.clear(); + +const secsinmin = 60; +const quickfixperiod = 900; +var seconds = 1200; + +function countSecs() { + if (seconds != 0) {seconds -=1;} + console.log(seconds); +} +function drawTime() { + g.clear(); + g.setFontAlign(0,0); + g.setFont('Vector', 12); + g.drawString('Geek Squad Appointment Timer', 125, 20); + if (seconds == 0) { + g.setFont('Vector', 35); + g.drawString('Appointment', 125, 100); + g.drawString('finished!', 125, 150); + Bangle.buzz(); + return; + } + min = seconds / secsinmin; + if (seconds < quickfixperiod) { + g.setFont('Vector', 20); + g.drawString('Quick Fix', 125, 50); + g.drawString('Period Passed!', 125, 75); + } + g.setFont('Vector', 50); + g.drawString(Math.ceil(min), 125, 125); + g.setFont('Vector', 25); + g.drawString('minutes', 125, 165); + g.drawString('remaining', 125, 195); +} +drawTime(); +setInterval(countSecs, 1000); +setInterval(drawTime, 60000); From 840cecf976d2444edcae2c7466fc8a3c798594c3 Mon Sep 17 00:00:00 2001 From: Smooklu <37220586+Smooklu@users.noreply.github.com> Date: Thu, 24 Mar 2022 20:22:45 -0500 Subject: [PATCH 50/64] Create metadata.json --- apps/gsat/metadata.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 apps/gsat/metadata.json diff --git a/apps/gsat/metadata.json b/apps/gsat/metadata.json new file mode 100644 index 000000000..cda1f7096 --- /dev/null +++ b/apps/gsat/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "gsat", + "name": "Geek Squad Appointment Timer", + "shortName": "gsat", + "version": "0.01", + "description": "Starts a 20 minute timer for appointments at Geek Squad.", + "icon": "app.png", + "tags": "tool", + "readme": "README.md", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"gsat.app.js","url":"app.js"}, + ] +} From 027048302aa4ba67c68d7bffd1f4960048f2e687 Mon Sep 17 00:00:00 2001 From: Smooklu <37220586+Smooklu@users.noreply.github.com> Date: Thu, 24 Mar 2022 20:23:20 -0500 Subject: [PATCH 51/64] Add app png --- apps/gsat/icons8-clock-48.png | Bin 0 -> 929 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/gsat/icons8-clock-48.png diff --git a/apps/gsat/icons8-clock-48.png b/apps/gsat/icons8-clock-48.png new file mode 100644 index 0000000000000000000000000000000000000000..cf057046be6eca51564a213bc3e7307271464009 GIT binary patch literal 929 zcmV;S177@zP)hL^4Q9~j~`7LX9JB(!Q` zf!g{&{RcFW&Q~mny+EQd5wRkG(GU|*v#i00#krWf=gz{s+-Te{nPTqF`ObG{=bo84 z7wV|v|Hht3JPkk}FaUG|9Y7m!Ao=|p@Bx?yW`SwoCGazne{UQEt^-TJmSbB2t^w^e zR}HiRcY*JgVs~m~;0|!8R^p4mY6RiBtpaENr0^Uv>(vCT0r!Cspcg0r`;t~jo{azx zfb|`CCKF##(F8oL;_xjnl#FrV8%ADg6Xz30z8#UnEWQMc5jI;nU_9Z{{Eq{QD8L7t!&STXpy9ZFm@kj^1Fem7{l-O!eBYr8>b)d^b3W}9W~#A6U*g95Oi>^=c4 z)~Zv`8K;EXvhfK3pp)!7am&FTkR7(HNX8sFd;lS+^%_N}!U1DH{Ly|K9>B#6_hueBM`PvzgXPMrIx>vGBiU)6Z4 zEpn3sb9fkCLB-z-YcKpmwJ#|r9RWl zC5~dFfRx^~zcZo4lLpcS%$dU%U1Vo|3%C)3JcraOHF*iC9^?bK^S~v{rNu|U-T>|j zG$FN0?PUAC7)9=|wrEE3yiS#A@zILA+sGlcN|VDaIFIb+1>`8+h@2|>kwB-& z?yx%JM4NdN29eBAO@MFvNQ4*G7A;7*Pj7ZUZ3CJ0gS8T$MjKKwTeK9b&o&dl(GUSK zKeKitb( Date: Thu, 24 Mar 2022 20:23:55 -0500 Subject: [PATCH 52/64] Delete icons8-clock-48.png --- apps/gsat/icons8-clock-48.png | Bin 929 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 apps/gsat/icons8-clock-48.png diff --git a/apps/gsat/icons8-clock-48.png b/apps/gsat/icons8-clock-48.png deleted file mode 100644 index cf057046be6eca51564a213bc3e7307271464009..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 929 zcmV;S177@zP)hL^4Q9~j~`7LX9JB(!Q` zf!g{&{RcFW&Q~mny+EQd5wRkG(GU|*v#i00#krWf=gz{s+-Te{nPTqF`ObG{=bo84 z7wV|v|Hht3JPkk}FaUG|9Y7m!Ao=|p@Bx?yW`SwoCGazne{UQEt^-TJmSbB2t^w^e zR}HiRcY*JgVs~m~;0|!8R^p4mY6RiBtpaENr0^Uv>(vCT0r!Cspcg0r`;t~jo{azx zfb|`CCKF##(F8oL;_xjnl#FrV8%ADg6Xz30z8#UnEWQMc5jI;nU_9Z{{Eq{QD8L7t!&STXpy9ZFm@kj^1Fem7{l-O!eBYr8>b)d^b3W}9W~#A6U*g95Oi>^=c4 z)~Zv`8K;EXvhfK3pp)!7am&FTkR7(HNX8sFd;lS+^%_N}!U1DH{Ly|K9>B#6_hueBM`PvzgXPMrIx>vGBiU)6Z4 zEpn3sb9fkCLB-z-YcKpmwJ#|r9RWl zC5~dFfRx^~zcZo4lLpcS%$dU%U1Vo|3%C)3JcraOHF*iC9^?bK^S~v{rNu|U-T>|j zG$FN0?PUAC7)9=|wrEE3yiS#A@zILA+sGlcN|VDaIFIb+1>`8+h@2|>kwB-& z?yx%JM4NdN29eBAO@MFvNQ4*G7A;7*Pj7ZUZ3CJ0gS8T$MjKKwTeK9b&o&dl(GUSK zKeKitb( Date: Thu, 24 Mar 2022 20:24:22 -0500 Subject: [PATCH 53/64] Add files via upload --- apps/gsat/app.png | Bin 0 -> 929 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/gsat/app.png diff --git a/apps/gsat/app.png b/apps/gsat/app.png new file mode 100644 index 0000000000000000000000000000000000000000..cf057046be6eca51564a213bc3e7307271464009 GIT binary patch literal 929 zcmV;S177@zP)hL^4Q9~j~`7LX9JB(!Q` zf!g{&{RcFW&Q~mny+EQd5wRkG(GU|*v#i00#krWf=gz{s+-Te{nPTqF`ObG{=bo84 z7wV|v|Hht3JPkk}FaUG|9Y7m!Ao=|p@Bx?yW`SwoCGazne{UQEt^-TJmSbB2t^w^e zR}HiRcY*JgVs~m~;0|!8R^p4mY6RiBtpaENr0^Uv>(vCT0r!Cspcg0r`;t~jo{azx zfb|`CCKF##(F8oL;_xjnl#FrV8%ADg6Xz30z8#UnEWQMc5jI;nU_9Z{{Eq{QD8L7t!&STXpy9ZFm@kj^1Fem7{l-O!eBYr8>b)d^b3W}9W~#A6U*g95Oi>^=c4 z)~Zv`8K;EXvhfK3pp)!7am&FTkR7(HNX8sFd;lS+^%_N}!U1DH{Ly|K9>B#6_hueBM`PvzgXPMrIx>vGBiU)6Z4 zEpn3sb9fkCLB-z-YcKpmwJ#|r9RWl zC5~dFfRx^~zcZo4lLpcS%$dU%U1Vo|3%C)3JcraOHF*iC9^?bK^S~v{rNu|U-T>|j zG$FN0?PUAC7)9=|wrEE3yiS#A@zILA+sGlcN|VDaIFIb+1>`8+h@2|>kwB-& z?yx%JM4NdN29eBAO@MFvNQ4*G7A;7*Pj7ZUZ3CJ0gS8T$MjKKwTeK9b&o&dl(GUSK zKeKitb( Date: Thu, 24 Mar 2022 20:25:34 -0500 Subject: [PATCH 54/64] Create README.md --- apps/gsat/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 apps/gsat/README.md diff --git a/apps/gsat/README.md b/apps/gsat/README.md new file mode 100644 index 000000000..faf986947 --- /dev/null +++ b/apps/gsat/README.md @@ -0,0 +1,3 @@ +# Geek Squad Appointment Timer + +An app dedicated to setting a 20 minute timer for Geek Squad Appointments. From b0f530107d33d849fe0f83b2e7fc0a0e9e7d11bf Mon Sep 17 00:00:00 2001 From: Smooklu <37220586+Smooklu@users.noreply.github.com> Date: Thu, 24 Mar 2022 20:26:21 -0500 Subject: [PATCH 55/64] Create ChangeLog --- apps/gsat/ChangeLog | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/gsat/ChangeLog diff --git a/apps/gsat/ChangeLog b/apps/gsat/ChangeLog new file mode 100644 index 000000000..48156d0d4 --- /dev/null +++ b/apps/gsat/ChangeLog @@ -0,0 +1 @@ +0.01: Added Source Code From 62b2d94e9363257c5c321ae7aa80da8c7dc62034 Mon Sep 17 00:00:00 2001 From: Smooklu <37220586+Smooklu@users.noreply.github.com> Date: Thu, 24 Mar 2022 20:27:52 -0500 Subject: [PATCH 56/64] Update metadata.json --- apps/gsat/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gsat/metadata.json b/apps/gsat/metadata.json index cda1f7096..678e1eb19 100644 --- a/apps/gsat/metadata.json +++ b/apps/gsat/metadata.json @@ -9,6 +9,6 @@ "readme": "README.md", "supports": ["BANGLEJS"], "storage": [ - {"name":"gsat.app.js","url":"app.js"}, + {"name":"gsat.app.js","url":"app.js"} ] } From 8f3f229c5519812dbc0ccd2c7e0e9b6ab411d006 Mon Sep 17 00:00:00 2001 From: Smooklu <37220586+Smooklu@users.noreply.github.com> Date: Thu, 24 Mar 2022 20:31:26 -0500 Subject: [PATCH 57/64] Create app-icon.js --- apps/gsat/app-icon.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/gsat/app-icon.js diff --git a/apps/gsat/app-icon.js b/apps/gsat/app-icon.js new file mode 100644 index 000000000..06f93e2ef --- /dev/null +++ b/apps/gsat/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwIdah/wAof//4ECgYFB4AFBg4FB8AFBj/wh/4AoM/wEB/gFBvwCEBAU/AQP4gfAj8AgPwAoMPwED8AFBg/AAYIBDA4ngg4TB4EBApkPKgJSBJQIFTMgIFCJIIFDKoIFEvgFBGoMAnw7DP4IFEh+BAoItBg+DNIQwBMIaeCKoKxCPoIzCEgKVHUIqtFXIrFFaIrdFdIwAV")) From b50f0d7a565994688ced0d52b1ecb64b249e19a9 Mon Sep 17 00:00:00 2001 From: Smooklu <37220586+Smooklu@users.noreply.github.com> Date: Thu, 24 Mar 2022 20:31:55 -0500 Subject: [PATCH 58/64] Update metadata.json --- apps/gsat/metadata.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/gsat/metadata.json b/apps/gsat/metadata.json index 678e1eb19..0e81c0513 100644 --- a/apps/gsat/metadata.json +++ b/apps/gsat/metadata.json @@ -9,6 +9,7 @@ "readme": "README.md", "supports": ["BANGLEJS"], "storage": [ - {"name":"gsat.app.js","url":"app.js"} + {"name":"gsat.app.js","url":"app.js"}, + {"name":"gsat.img","url":"app-icon.js","evaluate":true} ] } From 9af8213b92312c846ce4e521c51af803f280c2f0 Mon Sep 17 00:00:00 2001 From: Smooklu <37220586+Smooklu@users.noreply.github.com> Date: Thu, 24 Mar 2022 20:38:20 -0500 Subject: [PATCH 59/64] Update README.md --- apps/gsat/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/gsat/README.md b/apps/gsat/README.md index faf986947..17880e287 100644 --- a/apps/gsat/README.md +++ b/apps/gsat/README.md @@ -1,3 +1,4 @@ # Geek Squad Appointment Timer An app dedicated to setting a 20 minute timer for Geek Squad Appointments. +![screenshot](https://user-images.githubusercontent.com/37220586/160037582-71b46f82-aa7c-47cf-a42a-5e4bb72705c4.png) From e7ee68d5642d6b8c0a7d06406328f2a8350eab2b Mon Sep 17 00:00:00 2001 From: Smooklu <37220586+Smooklu@users.noreply.github.com> Date: Thu, 24 Mar 2022 20:42:15 -0500 Subject: [PATCH 60/64] Update README.md --- apps/gsat/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/gsat/README.md b/apps/gsat/README.md index 17880e287..faf986947 100644 --- a/apps/gsat/README.md +++ b/apps/gsat/README.md @@ -1,4 +1,3 @@ # Geek Squad Appointment Timer An app dedicated to setting a 20 minute timer for Geek Squad Appointments. -![screenshot](https://user-images.githubusercontent.com/37220586/160037582-71b46f82-aa7c-47cf-a42a-5e4bb72705c4.png) From 451ecd7226339b676f954588703502a7617f9af2 Mon Sep 17 00:00:00 2001 From: Smooklu <37220586+Smooklu@users.noreply.github.com> Date: Thu, 24 Mar 2022 20:42:36 -0500 Subject: [PATCH 61/64] Add files via upload --- apps/gsat/screenshot.png | Bin 0 -> 4250 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/gsat/screenshot.png diff --git a/apps/gsat/screenshot.png b/apps/gsat/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..032319bf674745ec454e9a16645aff39ba44d93f GIT binary patch literal 4250 zcmcgvc{r478-E)~$w2&EzMl74fA{bH-S>0P0&XZQC?N;{ zfUvRA2`d1Av~u1z8zG#QnA@-6oX;k-(P<0-h`i#y>tcTvkpzG(rp709t*_&z`kJD8 z%69O#xyNl_t>lp(3yAN__-LTUcZ+h}gV;{Iau3jbS2QX0+ea36#((DNnuGa5FgKP+ z2P*0QM-y8|!WgH866_izTAn*eFOcL?;!tee1kW$xT8BZ01q;v=S5-2(}=@ewRv$;=v0KZ2D~A!%RFGL^F6+@|D|6D zem7(SJzeUzJh(nFrTZl^%|`uLACy5Vp;hTl4=Y+aJ#SLc1g>FNF0PJ8nmtO=Jt zWd&R_(&PQ-VlYY(+C3w`nEg5IsJ@tdRkfg1w7bH2qVqgUDx1k#aJbE!@<{jz|9l;A z&R7yS_7L8vV_Kf#87gC0Y&9P~8TDw2x-#AtTcW|DG*qb$kjklhN4oN<OouWH0!;mRvv?SfSej4$9MND)~YZwD#&Y3`y@ELnL zhX58Q%??it&zC`8tm9#r{@H0914*R7Govzr+R3E}DswlW&eWql{%}q2 ztGM~njV1@TTeDStA;60?DV}>5Xqm{AyK*&4C^P?j@ySp&lH&EA z_y#$oLszg0wGA%vRdb0N9Ss4wdfjrQ$S*ZnvodT~g38T(f@z?C!AoQqUx$CB*pB7( zzcBOH^St3@ML~qt%RRJtra*k?UTd{dSlmot1ep2l>;)r$K3A*sV&B++(D};(iaVN` zZiD7NDD5F#JFl3_|#s@?rM#qn%BBBkMVnGOouFlZRkLf%hho^p6JgJdL3 zdD~@OKFHXumoE+i_+z$>PG_E^*Z3ZIf}3`^K7Q;-!xorg09)KCd5_&)5X?Aqfqm!G+x%S34wt)F-Ovw+nasXq-p)7*aZFV23>#r9p_pJ7 z`j#L$?4(*!vjt!0az3?Z2H}2nA!8=1Y{1-N@Jieh-X1Sn8Qq9eF`3Bn>(tT0 z%@g)a=hPf;zE;W*yXE+I`1C#8`~JRdt&I1ZfSdF+t_s_kioNgVGy;jM0aMR0S3C+j zMGq;0T#Py-93$7X`H00DzWilYl$jc6jWtLFf_HCUWaqaW+7BA~7)JLG8u$zaCmBPU zFFn5RG62 zsCVu=p4k;Q#G^)s@mUP<%vSQyc|<-$lMpR;oR5RL0&glv5FmwYK}>IszxqMu$Nkj9CJ zL_w?uF8H`yiRU}hS0@LaK{2*-!q5F)@}#uaC4)}45KoT*86$86X%I(<2w0D-EWBdk zDS1LM~N$;SCO2`rO(TmvMqNsXc`gHPupnIMzJ+ zpBj@(#pMrHgv&56i<6UbxXH|r8R9jygZCs9lrHb|+p*d=9~-U6-YnsM&O4^ew( zlgbr|T@MmU8@)g!g5W3JhSvFYU+}|^^&tqy3a;4D*gdLxZd2~z0A5Dnh9V$@nVZYT zJ`caPuZkCNCj0pZuPIIMOb@O;4Z;NIwWayo7*C%y1UI6KGSY&pmBD2H$d@;p_D^u_ hYr6cOT2SsQkk>x|=EPt9I9VS6jP>Csa*v~K{RJk1|9$`f literal 0 HcmV?d00001 From 9eff57362b1ff350b4bb320ae2f406c8c887ab76 Mon Sep 17 00:00:00 2001 From: Smooklu <37220586+Smooklu@users.noreply.github.com> Date: Thu, 24 Mar 2022 20:43:08 -0500 Subject: [PATCH 62/64] Update metadata.json --- apps/gsat/metadata.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/gsat/metadata.json b/apps/gsat/metadata.json index 0e81c0513..bdc8fee32 100644 --- a/apps/gsat/metadata.json +++ b/apps/gsat/metadata.json @@ -8,6 +8,7 @@ "tags": "tool", "readme": "README.md", "supports": ["BANGLEJS"], + "screenshots": [{"url":"screenshot.png"}] "storage": [ {"name":"gsat.app.js","url":"app.js"}, {"name":"gsat.img","url":"app-icon.js","evaluate":true} From db88e1800d1dec7d707d5a72697bc85d214ec4eb Mon Sep 17 00:00:00 2001 From: Smooklu <37220586+Smooklu@users.noreply.github.com> Date: Thu, 24 Mar 2022 20:44:12 -0500 Subject: [PATCH 63/64] Update metadata.json --- apps/gsat/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gsat/metadata.json b/apps/gsat/metadata.json index bdc8fee32..878d213e4 100644 --- a/apps/gsat/metadata.json +++ b/apps/gsat/metadata.json @@ -8,7 +8,7 @@ "tags": "tool", "readme": "README.md", "supports": ["BANGLEJS"], - "screenshots": [{"url":"screenshot.png"}] + "screenshots": [{"url":"screenshot.png"}], "storage": [ {"name":"gsat.app.js","url":"app.js"}, {"name":"gsat.img","url":"app-icon.js","evaluate":true} From 9a0c31c56d41912074ba41ca034baeb7cdf17b12 Mon Sep 17 00:00:00 2001 From: Salim Blume Date: Thu, 24 Mar 2022 22:19:11 -0500 Subject: [PATCH 64/64] Fix run notifications bugs and improve accuracy --- apps/recorder/ChangeLog | 1 + apps/recorder/metadata.json | 2 +- apps/recorder/widget.js | 3 +-- apps/run/ChangeLog | 1 + apps/run/app.js | 4 ++-- apps/run/metadata.json | 2 +- apps/run/settings.js | 12 ++++++------ modules/exstats.js | 12 +++++++----- 8 files changed, 20 insertions(+), 17 deletions(-) diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog index 90937e160..e9877808c 100644 --- a/apps/recorder/ChangeLog +++ b/apps/recorder/ChangeLog @@ -17,3 +17,4 @@ 0.11: Fix KML and GPX export when there is no GPS data 0.12: Fix 'Back' label positioning on track/graph display, make translateable 0.13: Fix for when widget is used before app +0.14: Remove unneeded variable assignment \ No newline at end of file diff --git a/apps/recorder/metadata.json b/apps/recorder/metadata.json index e2400603d..d715af38d 100644 --- a/apps/recorder/metadata.json +++ b/apps/recorder/metadata.json @@ -2,7 +2,7 @@ "id": "recorder", "name": "Recorder", "shortName": "Recorder", - "version": "0.13", + "version": "0.14", "description": "Record GPS position, heart rate and more in the background, then download to your PC.", "icon": "app.png", "tags": "tool,outdoors,gps,widget", diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js index 221bc6c1a..4a105754b 100644 --- a/apps/recorder/widget.js +++ b/apps/recorder/widget.js @@ -248,7 +248,7 @@ } var buttons={Yes:"yes",No:"no"}; if (newFileName) buttons["New"] = "new"; - var prompt = E.showPrompt("Overwrite\nLog " + settings.file.match(/\d+/)[0] + "?",{title:"Recorder",buttons:buttons}).then(selection=>{ + return E.showPrompt("Overwrite\nLog " + settings.file.match(/\d+/)[0] + "?",{title:"Recorder",buttons:buttons}).then(selection=>{ if (selection==="no") return false; // just cancel if (selection==="yes") { require("Storage").open(settings.file,"r").erase(); @@ -259,7 +259,6 @@ } return WIDGETS["recorder"].setRecording(1); }); - return prompt; } settings.recording = isOn; updateSettings(settings); diff --git a/apps/run/ChangeLog b/apps/run/ChangeLog index 401a68de9..46fdb7e7e 100644 --- a/apps/run/ChangeLog +++ b/apps/run/ChangeLog @@ -9,3 +9,4 @@ 0.08: Added support for notifications from exstats. Support all stats from exstats 0.09: Fix broken start/stop if recording not enabled (fix #1561) 0.10: Don't allow the same setting to be chosen for 2 boxes (fix #1578) +0.11: Notifications fixes \ No newline at end of file diff --git a/apps/run/app.js b/apps/run/app.js index d066c8b1f..fb8158e58 100644 --- a/apps/run/app.js +++ b/apps/run/app.js @@ -59,7 +59,7 @@ function onStartStop() { layout.render(); }) ); - } else { + } else if (!settings.record && WIDGETS["recorder"]) { prepPromises.push( WIDGETS["recorder"].setRecording(false) ); @@ -124,7 +124,7 @@ function configureNotification(stat) { } Object.keys(settings.notify).forEach((statType) => { - if (settings.notify[statType].increment > 0) { + if (settings.notify[statType].increment > 0 && exs.stats[statType]) { configureNotification(exs.stats[statType]); } }); diff --git a/apps/run/metadata.json b/apps/run/metadata.json index 51239d297..09e5a3bed 100644 --- a/apps/run/metadata.json +++ b/apps/run/metadata.json @@ -1,6 +1,6 @@ { "id": "run", "name": "Run", - "version":"0.10", + "version":"0.11", "description": "Displays distance, time, steps, cadence, pace and more for runners.", "icon": "app.png", "tags": "run,running,fitness,outdoors,gps", diff --git a/apps/run/settings.js b/apps/run/settings.js index 949f7a235..6a7d169c4 100644 --- a/apps/run/settings.js +++ b/apps/run/settings.js @@ -90,8 +90,8 @@ [[300, 1],[300, 0],[300, 1],[300, 0],[300, 1]], ]; notificationsMenu[/*LANG*/"Dist Pattern"] = { - value: Math.max(0,vibPatterns.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.dist.notifications))), - min: 0, max: vibPatterns.length, + value: Math.max(0,vibTimes.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.dist.notifications))), + min: 0, max: vibTimes.length, format: v => vibPatterns[v]||/*LANG*/"Off", onchange: v => { settings.notify.dist.notifications = vibTimes[v]; @@ -100,8 +100,8 @@ } } notificationsMenu[/*LANG*/"Step Pattern"] = { - value: Math.max(0,vibPatterns.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.step.notifications))), - min: 0, max: vibPatterns.length, + value: Math.max(0,vibTimes.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.step.notifications))), + min: 0, max: vibTimes.length, format: v => vibPatterns[v]||/*LANG*/"Off", onchange: v => { settings.notify.step.notifications = vibTimes[v]; @@ -110,8 +110,8 @@ } } notificationsMenu[/*LANG*/"Time Pattern"] = { - value: Math.max(0,vibPatterns.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.time.notifications))), - min: 0, max: vibPatterns.length, + value: Math.max(0,vibTimes.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.time.notifications))), + min: 0, max: vibTimes.length, format: v => vibPatterns[v]||/*LANG*/"Off", onchange: v => { settings.notify.time.notifications = vibTimes[v]; diff --git a/modules/exstats.js b/modules/exstats.js index b106622d0..ec0a838a7 100644 --- a/modules/exstats.js +++ b/modules/exstats.js @@ -139,9 +139,9 @@ Bangle.on("GPS", function(fix) { if (stats["pacea"]) stats["pacea"].emit("changed",stats["pacea"]); if (stats["pacec"]) stats["pacec"].emit("changed",stats["pacec"]); if (stats["speed"]) stats["speed"].emit("changed",stats["speed"]); - if (state.notify.dist.increment > 0 && state.notify.dist.next <= stats["dist"]) { + if (state.notify.dist.increment > 0 && state.notify.dist.next <= state.distance) { stats["dist"].emit("notify",stats["dist"]); - state.notify.dist.next = stats["dist"] + state.notify.dist.increment; + state.notify.dist.next = state.notify.dist.next + state.notify.dist.increment; } }); @@ -152,7 +152,7 @@ Bangle.on("step", function(steps) { state.lastStepCount = steps; if (state.notify.step.increment > 0 && state.notify.step.next <= steps) { stats["step"].emit("notify",stats["step"]); - state.notify.step.next = steps + state.notify.step.increment; + state.notify.step.next = state.notify.step.next + state.notify.step.increment; } }); Bangle.on("HRM", function(h) { @@ -285,7 +285,7 @@ exports.getStats = function(statIDs, options) { } if (state.notify.time.increment > 0 && state.notify.time.next <= now) { stats["time"].emit("notify",stats["time"]); - state.notify.time.next = now + state.notify.time.increment; + state.notify.time.next = state.notify.time.next + state.notify.time.increment; } }, 1000); function reset() { @@ -299,6 +299,8 @@ exports.getStats = function(statIDs, options) { state.curSpeed = 0; state.BPM = 0; state.BPMage = 0; + state.thisGPS = {}; + state.lastGPS = {}; state.notify = options.notify; if (options.notify.dist.increment > 0) { state.notify.dist.next = state.distance + options.notify.dist.increment; @@ -338,7 +340,7 @@ exports.appendMenuItems = function(menu, settings, saveSettings) { } exports.appendNotifyMenuItems = function(menu, settings, saveSettings) { var distNames = ['Off', "1000m","1 mile","1/2 Mthn", "Marathon",]; - var distAmts = [0, 1000,1609,21098,42195]; + var distAmts = [0, 1000, 1609, 21098, 42195]; menu['Ntfy Dist'] = { min: 0, max: distNames.length-1, value: Math.max(distAmts.indexOf(settings.notify.dist.increment),0),