From 2ae961ee567e57361b3f8c7141429c0597bd6e9e Mon Sep 17 00:00:00 2001 From: Randy Heydon Date: Thu, 6 Feb 2025 06:55:38 -0500 Subject: [PATCH 01/14] calendar: properly display synced all-day events. Discovered that, in the events synchronized from Gadgetbridge, all-day events have timestamps that disregard local timezones. Specifically, all-day events always start at midnight UTC, which caused calendar to display them starting at a different time depending on time zone offset (e.g. for my offset of -5, I see all-day events start at 19:00 the day before). Borrowed logic from the agenda app to correct this. This logic shifts an all-day event's start time to start at midnight in the current time zone. Also specify all-day events as type "o" (other) so the full day is highlighted and the start time is not displayed. Note that this does nothing to handle all-day events that span more than one day (only the first day is highlighted in the calendar). Also note that I'm not sure how this will handle time zone changes, like shifting to/from daylight savings time. --- apps/calendar/ChangeLog | 1 + apps/calendar/calendar.js | 8 +++++--- apps/calendar/metadata.json | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/calendar/ChangeLog b/apps/calendar/ChangeLog index d737ec6d4..b5a5fbe2f 100644 --- a/apps/calendar/ChangeLog +++ b/apps/calendar/ChangeLog @@ -20,3 +20,4 @@ 0.17: Load holidays before events so the latter is not overpainted 0.18: Minor code improvements 0.19: Read events synchronized from Gadgetbridge +0.20: Correct start time of all-day events synchronized from Gadgetbridge diff --git a/apps/calendar/calendar.js b/apps/calendar/calendar.js index ea06b70e8..d6eefce39 100644 --- a/apps/calendar/calendar.js +++ b/apps/calendar/calendar.js @@ -62,9 +62,11 @@ const loadEvents = () => { })); // all events synchronized from Gadgetbridge events = events.concat((require("Storage").readJSON("android.calendar.json",1) || []).map(a => { - // timestamp is in seconds, Date requires milliseconds - const date = new Date(a.timestamp * 1000); - return {date: date, msg: a.title, type: "e"}; + // All-day events always start at 00:00:00 UTC, so we need to "undo" the + // timezone offsetting to make sure that the day is correct. + const offset = a.allDay ? new Date().getTimezoneOffset() * 60 : 0 + const date = new Date((a.timestamp+offset) * 1000); + return {date: date, msg: a.title, type: a.allDay ? "o" : "e"}; })); }; diff --git a/apps/calendar/metadata.json b/apps/calendar/metadata.json index 5f5f21b27..36713a487 100644 --- a/apps/calendar/metadata.json +++ b/apps/calendar/metadata.json @@ -1,7 +1,7 @@ { "id": "calendar", "name": "Calendar", - "version": "0.19", + "version": "0.20", "description": "Monthly calendar, displays holidays uploaded from the web interface and scheduled events.", "icon": "calendar.png", "screenshots": [{"url":"screenshot_calendar.png"}], From 205e95347ee757bbbbf010574742ffb7f1559216 Mon Sep 17 00:00:00 2001 From: Logan B <3870583+thinkpoop@users.noreply.github.com> Date: Sat, 8 Feb 2025 14:27:01 -0600 Subject: [PATCH 02/14] [gbmusic] fix #3737; fix autostart saved state; pass playpause to gb instead of play; allow widget clicks --- apps/gbmusic/ChangeLog | 1 + apps/gbmusic/app.js | 16 +++++++++++----- apps/gbmusic/boot.js | 4 ++-- apps/gbmusic/metadata.json | 2 +- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/apps/gbmusic/ChangeLog b/apps/gbmusic/ChangeLog index 0275542fb..433c58368 100644 --- a/apps/gbmusic/ChangeLog +++ b/apps/gbmusic/ChangeLog @@ -12,3 +12,4 @@ 0.11: Use default Bangle formatter for booleans 0.12: Issue newline before GB commands (solves issue with console.log and ignored commands) 0.13: Upgrade to new translation system +0.14: Fix auto-start saved state; fix clearing track number; allow widget clicks diff --git a/apps/gbmusic/app.js b/apps/gbmusic/app.js index 7f5aad8f3..8cf813fb9 100644 --- a/apps/gbmusic/app.js +++ b/apps/gbmusic/app.js @@ -91,8 +91,8 @@ function rScroller(l) { const w = g.stringWidth(l.label)+40, y = l.y+l.h/2; l.offset = l.offset%w; - g.setClipRect(l.x, l.y, l.x+l.w-1, l.y+l.h-1) - .setColor(l.col).setBgColor(l.bgCol) // need to set colors: iScroll calls this function outside Layout + //g.setClipRect(l.x, l.y, l.x+l.w-1, l.y+l.h-1) + g.setColor(l.col).setBgColor(l.bgCol) // need to set colors: iScroll calls this function outside Layout .setFontAlign(-1, 0) // left center .clearRect(l.x, l.y, l.x+l.w-1, l.y+l.h-1) .drawString(l.label, l.x-l.offset+40, y) @@ -433,15 +433,21 @@ function sendCommand(command) { drawControls(); } +function handleTouch(btn, pos) { + if (pos === undefined || pos.y >= Bangle.appRect.y) { + togglePlay(); + } +} + function togglePlay() { - sendCommand(stat==="play" ? "pause" : "play"); + sendCommand(stat==="play" ? "pause" : "playpause"); } /** * Setup touch+swipe for Bangle.js 1 */ function touch1() { - Bangle.on("touch", togglePlay); + Bangle.on("touch", handleTouch); Bangle.on("swipe", dir => { sendCommand(dir===1 ? "previous" : "next"); }); @@ -450,7 +456,7 @@ function touch1() { * Setup touch+swipe for Bangle.js 2 */ function touch2() { - Bangle.on("touch", togglePlay); + Bangle.on("touch", handleTouch); // swiping let drag; Bangle.on("drag", e => { diff --git a/apps/gbmusic/boot.js b/apps/gbmusic/boot.js index 154f85c2b..6cd544a21 100644 --- a/apps/gbmusic/boot.js +++ b/apps/gbmusic/boot.js @@ -10,7 +10,7 @@ setTimeout( // make other boot code run first, so we override e.g. android.boot. * Only runs while other apps are loaded */ function check() { - if (s!=="play" || !i || !a || !Bangle.CLOCK) return; // only launch app if we know which song we are playing, and autoLoad is enabled + if ((!s || s.state!=="play") || !i || !a || !Bangle.CLOCK) return; // only launch app if we know which song we are playing, and autoLoad is enabled delete (i.t); // store info and launch music app require("Storage").writeJSON("gbmusic.load.json", { @@ -27,7 +27,7 @@ setTimeout( // make other boot code run first, so we override e.g. android.boot. i = e; return APP ? info(e) : check(); case "musicstate": - s = e.state; + s = e; return APP ? state(e) : check(); default: // pass on other events diff --git a/apps/gbmusic/metadata.json b/apps/gbmusic/metadata.json index 0024a1708..a463e6ea0 100644 --- a/apps/gbmusic/metadata.json +++ b/apps/gbmusic/metadata.json @@ -2,7 +2,7 @@ "id": "gbmusic", "name": "Gadgetbridge Music Controls", "shortName": "Music Controls", - "version": "0.13", + "version": "0.14", "description": "Control the music on your Gadgetbridge-connected phone", "icon": "icon.png", "screenshots": [{"url":"screenshot_v1_d.png"},{"url":"screenshot_v1_l.png"}, From 63797c402abde3f827f30e9e9d1e8b1ab1789be8 Mon Sep 17 00:00:00 2001 From: Logan B <3870583+thinkpoop@users.noreply.github.com> Date: Sat, 8 Feb 2025 16:01:18 -0600 Subject: [PATCH 03/14] [gbmusic] fix lint warnings --- apps/gbmusic/app.js | 109 ++++++++++++++++++++++--------------------- apps/gbmusic/boot.js | 11 +++-- 2 files changed, 62 insertions(+), 58 deletions(-) diff --git a/apps/gbmusic/app.js b/apps/gbmusic/app.js index 8cf813fb9..1f6f6c620 100644 --- a/apps/gbmusic/app.js +++ b/apps/gbmusic/app.js @@ -128,57 +128,60 @@ function rInfo(l) { .setFontAlign(0, -1) // center top .drawString(l.label, l.x+l.w/2, l.y); } -/** - * Render icon - * @param l - */ -function rIcon(l) { - const x2 = l.x+l.w-1, - y2 = l.y+l.h-1; - switch(l.icon) { - case "pause": { - const w13 = l.w/3; - g.drawRect(l.x, l.y, l.x+w13, y2); - g.drawRect(l.x+l.w-w13, l.y, x2, y2); - break; - } - case "play": { - g.drawPoly([ - l.x, l.y, - x2, l.y+l.h/2, - l.x, y2, - ], true); - break; - } - case "previous": { - const w15 = l.w*1/5; - g.drawPoly([ - x2, l.y, - l.x+w15, l.y+l.h/2, - x2, y2, - ], true); - g.drawRect(l.x, l.y, l.x+w15, y2); - break; - } - case "next": { - const w45 = l.w*4/5; - g.drawPoly([ - l.x, l.y, - l.x+w45, l.y+l.h/2, - l.x, y2, - ], true); - g.drawRect(l.x+w45, l.y, x2, y2); - break; - } - default: { // red X - console.log(`Unknown icon: ${l.icon}`); - g.setColor("#f00") - .drawRect(l.x, l.y, x2, y2) - .drawLine(l.x, l.y, x2, y2) - .drawLine(l.x, y2, x2, l.y); - } - } -} + +// *** Unused Function *** +// // /** +// // * Render icon +// // * @param l +// // */ +// // function rIcon(l) { +// // const x2 = l.x+l.w-1, +// // y2 = l.y+l.h-1; +// // switch(l.icon) { +// // case "pause": { +// // const w13 = l.w/3; +// // g.drawRect(l.x, l.y, l.x+w13, y2); +// // g.drawRect(l.x+l.w-w13, l.y, x2, y2); +// // break; +// // } +// // case "play": { +// // g.drawPoly([ +// // l.x, l.y, +// // x2, l.y+l.h/2, +// // l.x, y2, +// // ], true); +// // break; +// // } +// // case "previous": { +// // const w15 = l.w*1/5; +// // g.drawPoly([ +// // x2, l.y, +// // l.x+w15, l.y+l.h/2, +// // x2, y2, +// // ], true); +// // g.drawRect(l.x, l.y, l.x+w15, y2); +// // break; +// // } +// // case "next": { +// // const w45 = l.w*4/5; +// // g.drawPoly([ +// // l.x, l.y, +// // l.x+w45, l.y+l.h/2, +// // l.x, y2, +// // ], true); +// // g.drawRect(l.x+w45, l.y, x2, y2); +// // break; +// // } +// // default: { // red X +// // console.log(`Unknown icon: ${l.icon}`); +// // g.setColor("#f00") +// // .drawRect(l.x, l.y, x2, y2) +// // .drawLine(l.x, l.y, x2, y2) +// // .drawLine(l.x, y2, x2, l.y); +// // } +// // } +// // } + let layout; function makeUI() { Bangle.loadWidgets(); @@ -489,10 +492,10 @@ function startLCDWatch() { Bangle.on("lcdPower", (on) => { if (on) { // redraw and resume scrolling - tick(); + //tick(); // Not sure what this function was; currently undefined layout.render(); fadeOut(); - if (offset.offset!==null) { + if (layout.title.offset!==null) { // Making an assumption about what offset.offset was supposed to be if (!iScroll) { iScroll = setInterval(scroll, 200); } diff --git a/apps/gbmusic/boot.js b/apps/gbmusic/boot.js index 6cd544a21..2f1c09d8a 100644 --- a/apps/gbmusic/boot.js +++ b/apps/gbmusic/boot.js @@ -1,6 +1,6 @@ setTimeout( // make other boot code run first, so we override e.g. android.boot.js GB () => { - const APP = global.__FILE__==="gbmusic.app.js", + const APP = globalThis.__FILE__==="gbmusic.app.js", a = !!(require("Storage").readJSON("gbmusic.json", 1) || {}).autoStart; let s, i; // state, info @@ -20,18 +20,19 @@ setTimeout( // make other boot code run first, so we override e.g. android.boot. load("gbmusic.app.js"); } - global.GB = (_GB => e => { + + globalThis.GB = (_GB => e => { // we eat music events! switch(e.t) { case "musicinfo": i = e; - return APP ? info(e) : check(); + return APP ? globalThis.info(e) : check(); case "musicstate": s = e; - return APP ? state(e) : check(); + return APP ? globalThis.state(e) : check(); default: // pass on other events if (_GB) setTimeout(_GB, 0, e); } - })(global.GB); + })(globalThis.GB); }, 1); From 3e6683a0adea391ba781d3154319790892c3d6cd Mon Sep 17 00:00:00 2001 From: smulrine Date: Tue, 11 Feb 2025 21:42:10 +0000 Subject: [PATCH 04/14] Create app.js --- apps/pacer/app.js | 845 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 845 insertions(+) create mode 100644 apps/pacer/app.js diff --git a/apps/pacer/app.js b/apps/pacer/app.js new file mode 100644 index 000000000..1422f7810 --- /dev/null +++ b/apps/pacer/app.js @@ -0,0 +1,845 @@ +Bangle.loadWidgets(); +g.clear(); +Bangle.drawWidgets(); + +var cfg; +try { + cfg = require("Storage").readJSON("pacer.json",true)||{}; +} +catch(err) { + cfg = {}; +} +if (process.env.BOARD == 'BANGLEJS2') + var bangle2 = true; +else + var bangle2 = false; + +var laps = ["Off","0.25","0.5","1","2","5","10"]; +var fg = 1; +var fixed = false; +var started = false; +var startHidden = false; +var recording = false; +var invID = 0; +var intID = 0; +var startID = 0; +var cadID = 0; +var finID = 0; +var lapID = 0; +var steps = 0; +var sats = 0; +var ctr = 0; +var elapsed_ms = 0; +var finish_ms = 0; +var lap_start_ms = 0; +var lap_ms; +var gps = {fix:0,satellites:0}; +var fp; +var start_time; +var current_time; +var paused_time = 0; +var last_time = 0; +var begin_pause; +var next_lap = 0.0; +var skip_ctr = 0; +var skip_max = 0; +var force_write = true; +var show_lap = false; +var lcd_on = true; +var dist = 0.0; +var pdist = 0.0; +var oldDist = 0.0; +var oldLat = -1; +var oldLon = -1; +var oldTime = -1; +var cadence = 0; +var pace = 0; +var ppace = 0; +var R = 6371; +var stepTimes = []; +var dists = []; + +function pace_str(pval) { + var psecs = 295 + 5 * pval; + return ''+Math.floor(psecs/60)+':'+('0'+psecs%60).substr(-2); +} + +function defaults() { + if (typeof(cfg.record) != 'boolean') + cfg.record = true; + if (typeof(cfg.metric) != 'boolean') + cfg.metric = false; + if (typeof(cfg.lap_idx) != 'number') + cfg.lap_idx = 3; + if (typeof(cfg.dark) != 'boolean') + cfg.dark = true; + if (typeof(cfg.eco) != 'boolean') + cfg.eco = false; + if (typeof(cfg.storage) != 'boolean') + cfg.storage = false; + if (typeof(cfg.show_steps) != 'boolean') + cfg.show_steps = false; + if (typeof(cfg.pacer) != 'number') + cfg.pacer = 0; + fg = cfg.dark?1:0; +} + +function genFilename() { + var today=new Date(); + return ('.pacer'+today.getFullYear()+('0'+(today.getMonth()+1)).substr(-2)+('0'+today.getDate()).substr(-2)+('0'+today.getHours()).substr(-2)+('0'+today.getMinutes()).substr(-2)+('0'+today.getSeconds()).substr(-2)+'.csv'); +} + +function doCadence() { + if (steps > 0) + clearInterval(cadID); + cadID = setTimeout(function() { + cadence = 0; + }, 2000); + if (recording) { + steps++; + stepTimes.push(Date.now()); + stepTimes = stepTimes.slice(-20); + const elapsed = stepTimes[stepTimes.length - 1] - stepTimes[0]; + cadence = elapsed ? Math.round(60000 * (stepTimes.length - 1) / elapsed) : 0; + } else + stepTimes = []; +} + +function doPace(thistime,thisdist) { + dists.push([thistime,thisdist]); + dists = dists.slice(-30); + const thiselapsed = dists[dists.length - 1][0] - dists[0][0]; + const thisdistance = dists[dists.length - 1][1] - dists[0][1]; + pace = thisdistance ? ((thiselapsed) / thisdistance) / 1000 : 0; +} + +function countStep() { + if (recording) + steps++; +} + +function calcCrow(lat1, lon1, lat2, lon2) +{ + var dLat = toRad(lat2-lat1); + var dLon = toRad(lon2-lon1); + var lat1r = toRad(lat1); + var lat2r = toRad(lat2); + + var a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1r) * Math.cos(lat2r); + var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); + var d = R * c; + if (isNaN(d)) + return 0; + else + return d; +} + +function toRad(Value) +{ + return Value * Math.PI / 180; +} + +function saveGPS(fix) { + var newLat, newLon, newTime, newDist; + + try { + newTime = fix.time.getTime(); + } + catch(err) { + newTime = NaN; + } + newLat = fix.lat; + newLon = fix.lon; + gps = fix; + if (!cfg.storage) { + if (gps.satellites >= 8) + skip_max = 0; + else if (gps.satellites < 4) + skip_max = 5; + else + skip_max = 8 - gps.satellites; + } + if (recording && cfg.pacer > 0 && skip_ctr >= (cfg.storage ? 9 : skip_max)) + pdist = (elapsed_ms / 1000) / ppace; + if (isNaN(newLat) || isNaN(newLon) || isNaN(newTime)) { + skip_ctr = 0; + skip_max = 0; + force_write = true; + } else { + if (oldLat != -1 && recording) { + skip_ctr++; + if (skip_ctr > (cfg.storage ? 9 : skip_max)) { + skip_ctr = 0; + oldDist = dist; + newDist = calcCrow(oldLat, oldLon, newLat, newLon); + dist += newDist; + doPace(newTime,dist); + oldLat = newLat; + oldLon = newLon; + oldTime = newTime; + } + } else { + oldLat = newLat; + oldLon = newLon; + oldTime = newTime; + } + if (recording && cfg.record && (force_write || skip_ctr == 0)) { + fp.write([gps.time.getTime(),gps.lat.toFixed(5),gps.lon.toFixed(5),gps.alt].join(",")+"\n"); + last_time = gps.time; + if (force_write) { + skip_ctr = 0; + force_write = false; + } + } + } +} + +function drawInvert() { + // not applicable to bangle2 + g.drawImage(atob("DQ0BD4HjHwT4L8D+B/A/gfwL4J8EeMD4AA=="),225,26); +} + +function drawSatIcon() { + if (bangle2) + g.drawImage(atob("CQkBIDo7i+Pj6O4uAgA="),3,53); + else + g.drawImage(atob("DAwBEAOAcQ84T8D4HwPyHPCOAcAI"),4,66); +} + +function drawStepsIcon() { + if (bangle2) + g.drawimage(atob("CQkBBhudzudzgBzsMAA="),3,139); + else + g.drawImage(atob("DAwBAMAcMeOeeeeeeAecAcOYOAGA"),4,197); +} + +function drawCadenceIcon() { + if (bangle2) + g.drawImage(atob("CQkBCB4SEBgMBCQ8CAA="),3,139); + else + g.drawImage(atob("DAwBBAAwP4YxxDwDwDwjjGH8DAAg"),4,197); +} + +function hideStart() { + g.clearRect(bangle2?162:226,bangle2?81:113,bangle2?174:238,bangle2?93:125); +} + +function drawStart() { + hideStart(); + g.fillPoly([bangle2?162:226,bangle2?81:113,bangle2?162:226,bangle2?93:125,bangle2?174:238,bangle2?87:119,bangle2?162:226,bangle2?81:113]); +} + +function drawPause() { + hideStart(); + g.fillRect(bangle2?165:227,bangle2?82:113,bangle2?167:230,bangle2?92:125); + g.fillRect(bangle2?171:234,bangle2?82:113,bangle2?173:237,bangle2?92:125); +} + +function drawStop() { + // not applicable to bangle2 + g.fillRect(226,202,237,213); +} + +function drawExit() { + if (bangle2) + g.drawImage(atob("CQkBwfHdx8HB8d3HwQg="),165,82); + else + g.drawImage(atob("DAwBwD4HcOOcH4DwDwH4OccO4HwD"),226,202); +} + +function setColours() { + g.setBgColor(1-fg,1-fg,1-fg); + g.setColor(fg,fg,fg); +} + +function setScreenMode() { + g.reset(); + setColours(); + g.clearRect(0,24,bangle2?175:239,bangle2?151:215); +} + +function doLayout() { + setColours(); + if (!bangle2) + drawInvert(); + drawSatIcon(); + drawDist(); + drawTime(); + if (cfg.pacer == 0) + drawPace(); + else { + drawPacer(); + drawSmallPace(); + } + g.setFont("6x8",bangle2?2:3); + if (cfg.show_steps) { + drawStepsIcon(); + g.drawString(steps.toString(),bangle2?15:20,bangle2?134:190,true); + } else { + drawCadenceIcon(); + g.drawString(cadence.toString()+" ",bangle2?15:20,bangle2?134:190,true); + } + drawStart(); + if (!bangle2) + drawStop(); +} + +function drawDist() { + g.setFont("6x8",bangle2?4:5); // 3:5? + var dStr = dist.toString(); + if (dStr.indexOf('.') == -1) + dStr += '.0'; + g.drawString(((' '+(dStr.split('.'))[0])).substr(-2),bangle2?33:53,bangle2?26:35,true); + g.fillRect(bangle2?80:112,bangle2?51:66,bangle2?82:115,bangle2?53:69); + g.drawString(((dStr.split('.'))[1]+'0').substr(0,2),bangle2?86:120,bangle2?26:35,true); + g.setFont("6x8",2); + g.drawString(cfg.metric?"K":"M",bangle2?134:180,bangle2?40:56,true); +} + +function drawPacer() { + g.setFont("6x8",bangle2?3:5); + var pstr=(pdist>dist?'-':'+')+(Math.floor(Math.abs(dist-pdist)))%100; + g.drawString(pstr,bangle2?(49-(pstr.length>2?18:0)):(53-(pstr.length>2?30:0)),bangle2?107:145,true); + g.fillRect(bangle2?84:112,bangle2?126:176,bangle2?85:115,bangle2?127:179); + g.drawString(('0'+Math.floor(Math.abs(((dist-pdist)*100)%100))).substr(-2),bangle2?89:120,bangle2?107:145,true); + g.setFont("6x8",bangle2?1:2); + g.drawString(cfg.metric?"K":"M",bangle2?125:180,bangle2?121:166,true); +} + +function drawPace() { + g.setFont("6x8",bangle2?3:5); + if (pace > 0 && pace < 6000) + g.drawString((' '+Math.floor(pace/60)).substr(-2),bangle2?49:53,bangle2?107:145,true); + else + g.drawString("--",bangle2?49:53,bangle2?107:145,true); + g.fillRect(bangle2?84:112,bangle2?117:160,bangle2?85:115,bangle2?118:163); + g.fillRect(bangle2?84:112,bangle2?120:166,bangle2?85:115,bangle2?121:169); + if (pace > 0 && pace < 6000) + g.drawString(('0'+Math.floor(pace%60)).substr(-2),bangle2?89:120,bangle2?107:145,true); + else + g.drawString("--",bangle2?89:120,bangle2?107:145,true); + g.setFont("6x8",bangle2?1:2); + g.drawString(cfg.metric?"/K":"/M",bangle2?124:178,bangle2?121:166,true); +} + +function drawSmallPace() { + g.setFont("6x8",bangle2?2:3); + if (pace > 0 && pace < 6000) + g.drawString((' '+Math.floor(pace/60)).substr(-2),bangle2?113:136,bangle2?134:190,true); + else + g.drawString("--",bangle2?113:136,bangle2?134:190,true); + if (bangle2) { + g.setPixel(136,140); + g.setPixel(136,142); + } else { + g.fillRect(172,199,173,200); + g.fillRect(172,203,173,204); + } + if (pace > 0 && pace < 6000) + g.drawString(('0'+Math.floor(pace%60)).substr(-2),bangle2?138:176,bangle2?134:190,true); + else + g.drawString("--",bangle2?138:176,bangle2?134:190,true); + g.setFont("6x8",1); + g.drawString(cfg.metric?"/K":"/M",bangle2?164:212,bangle2?141:204,true); +} + +function drawTime() { + var seconds; + var minutes; + var hours; + + setColours(); + g.setFont("6x8",bangle2?5:7); + seconds = parseInt(elapsed_ms/1000) % 60; + minutes = parseInt(elapsed_ms/60000) % 60; + hours = parseInt(elapsed_ms/3600000) % 10; + g.drawString(hours.toString(),bangle2?6:5,bangle2?63:82,true); + g.fillRect(bangle2?34:44,bangle2?79:103,bangle2?36:48,bangle2?81:107); + g.fillRect(bangle2?34:44,bangle2?84:112,bangle2?36:48,bangle2?86:116); + g.drawString(('0'+minutes).substr(-2),bangle2?40:53,bangle2?63:82,true); + g.fillRect(bangle2?98:134,bangle2?79:103,bangle2?100:138,bangle2?81:107); + g.fillRect(bangle2?98:134,bangle2?84:112,bangle2?100:138,bangle2?86:116); + g.drawString(('0'+seconds).substr(-2),bangle2?104:143,bangle2?63:82,true); +} + +function drawGPSBox() { + g.drawRect(2,26,bangle2?12:17,bangle2?51:63); +} + +function drawGPS() { + g.clearRect(3,27,bangle2?11:16,bangle2?50:62); + if (gps.satellites > 0) { + if (!gps.fix) + g.setColor("#FF0000"); + else if (gps.satellites < 4) + g.setColor("#FF5500"); + else if (gps.satellites < 6) + g.setColor("#FF8800"); + else if (gps.satellites < 8) + g.setColor("#FFCC00"); + else + g.setColor("#00FF00"); + g.fillRect(3,bangle2?50:62,bangle2?11:16,(bangle2?50:62)-(gps.satellites>12?12:gps.satellites)*(bangle2?2:3)+1); + g.setColor(fg,fg,fg); + } +} + +function hideLap() { + show_lap = false; + g.reset(); + setColours(); + g.clearRect(bangle2?6:5,bangle2?63:82,bangle2?162:224,bangle2?129:182); + if (recording) { + current_time = Date.now(); + elapsed_ms = current_time - (start_time + paused_time); + } else + elapsed_ms = begin_pause - (start_time + paused_time); + drawTime(); + if (cfg.pacer == 0) + drawPace(); + else { + drawPacer(); + drawSmallPace(); + } +} + +function showLap() { + g.clearRect(bangle2?6:5,bangle2?63:82,bangle2?162:224,bangle2?129:182); + g.drawRect(bangle2?21:28,bangle2?68:90,bangle2?147:201,bangle2?124:174); + g.drawRect(bangle2?23:30,bangle2?70:92,bangle2?145:199,bangle2?122:172); + g.setFont("6x8",bangle2?1:2); + g.drawString("Last lap",bangle2?61:68,bangle2?77:102,true); + g.setFont("6x8",bangle2?3:5); + if (lap_ms < 600000) { + g.drawString((''+Math.floor(lap_ms/60000)),bangle2?57:69,bangle2?89:122,true); + g.fillRect(bangle2?74:98,bangle2?99:137,bangle2?75:101,bangle2?100:140); + g.fillRect(bangle2?74:98,bangle2?102:143,bangle2?75:101,bangle2?103:146); + g.drawString(('0'+Math.floor((lap_ms%60000)/1000)).substr(-2),bangle2?78:106,bangle2?89:122,true); + } else if (lap_ms < 3600000) { + g.drawString((''+Math.floor(lap_ms/60000)),bangle2?48:54,bangle2?89:122,true); + g.fillRect(bangle2?83:113,bangle2?99:137,bangle2?84:116,bangle2?100:140); + g.fillRect(bangle2?83:113,bangle2?102:143,bangle2?84:116,bangle2?103:146); + g.drawString(('0'+Math.floor((lap_ms%60000)/1000)).substr(-2),bangle2?87:121,bangle2?89:122,true); + } else { + g.drawString((''+Math.floor(lap_ms/3600000)).substr(-1),bangle2?37:35,bangle2?89:122,true); + g.fillRect(bangle2?54:64,bangle2?99:137,bangle2?55:67,bangle2?100:140); + g.fillRect(bangle2?54:64,bangle2?102:143,bangle2?55:67,bangle2?103:146); + g.drawString(('0'+Math.floor((lap_ms%3600000)/60000)).substr(-2),bangle2?58:72,bangle2?89:122,true); + g.fillRect(bangle2?93:131,bangle2?99:137,bangle2?94:134,bangle2?100:140); + g.fillRect(bangle2?93:131,bangle2?102:143,bangle2?94:134,bangle2?103:146); + g.drawString(('0'+Math.floor((lap_ms%60000)/1000)).substr(-2),bangle2?97:139,bangle2?89:122,true); + } + Bangle.setLCDPower(true); +} + +function mainLoop() { + g.reset(); + setColours(); + current_time = Date.now(); + if (started) { + elapsed_ms = current_time - (start_time + paused_time); + if (oldDist != dist) { + drawDist(); + if (cfg.lap_idx > 0 && dist >= next_lap ) { + show_lap = true; + next_lap += parseFloat(laps[cfg.lap_idx]); + lap_ms = elapsed_ms - lap_start_ms; + lap_start_ms = elapsed_ms; + Bangle.buzz(); + lapID = setTimeout(hideLap,5000); + showLap(); + } + } + } else + elapsed_ms = 0; + drawSats = false; + if (recording) { + if (!show_lap) + drawTime(); + g.setFont("6x8",bangle2?2:3); + if (cfg.show_steps) + g.drawString(steps.toString(),bangle2?15:20,bangle2?134:190,true); + else + g.drawString(cadence.toString()+" ",bangle2?15:20,bangle2?134:190,true); + } /* else + g.setFont("6x8",3); */ + if (!show_lap) + if (cfg.pacer == 0) + drawPace(); + else { + drawPacer(); + drawSmallPace(); + } + if (gps.fix) { + if (!started && startHidden) { + startHidden = false; + if (!bangle2) + startID = setWatch(start, BTN2); + else + startID = setWatch(start, BTN1, {edge: 'falling'}); + drawStart(); + } + if (!fixed) { + fixed = true; + drawSats = true; + } + } else { + if (!started && !startHidden) { + startHidden = true; + clearWatch(startID); + hideStart(); + } + if (fixed) { + fixed = false; + drawSats = true; + } + } + if (gps.satellites != sats) { + sats = gps.satellites; + drawSats = true; + } + if (drawSats) + drawGPS(); + if (ctr++%10 == 0) { + g.reset(); + Bangle.drawWidgets(); + } +} + +function restart(e) { + if (bangle2 && (e.time - e.lastTime > 0.5)) { + finish(); + } + g.reset(); + setColours(); + paused_time += (Date.now() - begin_pause); + pace = 0; + drawPause(); + oldDist = dist; + skip_ctr = 0; + force_write = true; + recording = true; + Bangle.buzz(); + if (!bangle2) + setWatch(pause, BTN2); + else + setWatch(pause, BTN1, { edge: 'falling' }); +} + +function pause(e) { + if (bangle2 && (e.time - e.lastTime > 0.5)) { + finish(); + } + g.reset(); + setColours(); + begin_pause = Date.now(); + elapsed_ms = begin_pause - (start_time + paused_time); + finish_ms = elapsed_ms; + drawDist(); + recording = false; + if (!show_lap) + drawTime(); + drawStart(); + oldTime = -1; + if (!isNaN(gps.time) && !isNaN(gps.lat) && !isNaN(gps.lon) && !isNaN(gps.alt) && cfg.record && (last_time != gps.time)) + fp.write([gps.time.getTime(),gps.lat.toFixed(5),gps.lon.toFixed(5),gps.alt].join(",")+"\n"); + Bangle.buzz(); + dists = []; + if (!bangle2) + setWatch(restart, BTN2); + else + setWatch(restart, BTN1, { edge: 'falling' }); +} + +function start() { + g.reset(); + setColours(); + if (cfg.eco){ + Bangle.setLCDPower(true); + Bangle.setLCDTimeout(10); + } + if (cfg.record) + fp = require("Storage").open(genFilename(),"w"); + start_time = Date.now(); + drawPause(); + Bangle.buzz(); + started = true; + recording = true; + if (!bangle2) + setWatch(pause, BTN2); + else + setWatch(pause, BTN1, { edge: 'falling' }); + if (cfg.show_steps) + Bangle.on('step',countStep); + else + Bangle.on('step',doCadence); + clearInterval(intID); + intID = setInterval(mainLoop,200); + if (cfg.lap_idx > 0) + next_lap = parseFloat(laps[cfg.lap_idx]); +} + +function endScreen() { + fg = 1-fg; + setScreenMode(); + if (!bangle2) + drawInvert(); + drawExit(); + g.setFont("6x8",bangle2?1:2); + var dStr = dist.toString(); + if (dStr.indexOf('.') == -1) + dStr += '.00'; + dStr = dStr.slice(0, (dStr.indexOf('.'))+3); + if (bangle2) + g.drawString('Distance: '+dStr+(cfg.metric?'K':'M'),38,43); + else { + //g.drawString('Distance: '+dist.toFixed(2),19,53); + g.drawString('Distance: '+dStr,19,53); + g.setFont("6x8",1); + //g.drawString(cfg.metric?'K':'M',139+12*(dist.toFixed(2).length),60); + g.drawString(cfg.metric?'K':'M',139+12*(dStr.length),60); + g.setFont("6x8",2); + } + g.drawString('Time: '+parseInt(finish_ms/3600000)%10+':'+('0'+parseInt(finish_ms/60000)%60).substr(-2)+':'+('0'+parseInt(finish_ms/1000)%60).substr(-2),bangle2?62:67,bangle2?63:83); + var avgPace = dist?((finish_ms/dist)/1000):0; + var paceStr = 'Avg Pace: '+parseInt(avgPace/60)+':'+('0'+parseInt(avgPace%60)).substr(-2); + if (bangle2) + g.drawString(paceStr+(cfg.metric?'/K':'/M'),38,83); + else { + g.drawString(paceStr,19,113); + g.setFont("6x8",1); + g.drawString(cfg.metric?'/K':'/M',19+12*(paceStr.length),120); + g.setFont("6x8",2); + } + g.drawString("Steps: "+steps,bangle2?56:55,bangle2?103:143); + var avgCadence = steps?(60*steps/(finish_ms/1000)):0; + g.drawString("Cadence: "+parseInt(avgCadence),bangle2?44:31,bangle2?123:173); + g.reset(); + Bangle.drawWidgets(); +} + +function finish() { + if (recording) { + finish_ms = elapsed_ms; + if (!isNaN(gps.time) && !isNaN(gps.lat) && !isNaN(gps.lon) && !isNaN(gps.alt) && cfg.record && (last_time != gps.time)) + fp.write([gps.time.getTime(),gps.lat.toFixed(5),gps.lon.toFixed(5),gps.alt].join(",")+"\n"); + } + recording = false; + Bangle.setGPSPower(0); + Bangle.on('step',function(){}); + Bangle.on('GPS',function(){}); + clearInterval(lapID); + clearInterval(intID); + if (!bangle2) { + clearWatch(finID); + clearWatch(invID); + } + if (!bangle2) { + setWatch(function() {if (lcd_on) endScreen();}, BTN1, {repeat:true}); + setWatch(function() {load();},BTN3); + } else + setWatch(function() {load();},BTN1); + fg = 1-fg; + endScreen(); +} + +function startScreen() { + clearInterval(intID); + if (!bangle2) { + clearWatch(invID); + } + clearWatch(finID); + setScreenMode(); + doLayout(); + drawGPSBox(); + Bangle.buzz(); + if (!bangle2) { + invID = setWatch(invertRunning, BTN1, {repeat:true}); + startID = setWatch(start, BTN2); + finID = setWatch(finish, BTN3); + } else + startID = setWatch(start, BTN1, {edge: 'falling'}); + fixed = false; + intID = setInterval(mainLoop,1000); +} + +function invertRunning() { + // not applicable to bangle2 + if (!lcd_on) + return; + fg = 1-fg; + setScreenMode(); + if (started) + if (recording) { + current_time = Date.now(); + elapsed_ms = current_time - (start_time + paused_time); + } else + elapsed_ms = begin_pause - (start_time + paused_time); + else + elapsed_ms = 0; + drawInvert(); + drawSatIcon(); + drawGPSBox(); + drawGPS(); + drawDist(); + if (show_lap) + showLap(); + else { + drawTime(); + if (cfg.pacer == 0) + drawPace(); + else { + drawPacer(); + drawSmallPace(); + } + } + g.setFont("6x8",3); + if (cfg.show_steps) { + drawStepsIcon(); + g.drawString(steps.toString(),20,190,true); + } else { + drawCadenceIcon(); + g.drawString(cadence.toString()+" ",20,190,true); + } + if (recording) + drawPause(); + else if (started || gps.fix) + drawStart(); + drawStop(); + g.reset(); + Bangle.drawWidgets(); +} + +function drawDots() { + if (ctr % 4 == 0) + g.drawString(" ",bangle2?116:176,bangle2?90:108,true); + else if (ctr % 4 == 1) + g.drawString(".",bangle2?116:176,bangle2?90:108); + else if (ctr % 4 == 2) + g.drawString("..",bangle2?116:176,bangle2?90:108); + else + g.drawString("...",bangle2?116:176,bangle2?90:108); +} + +function invertWaiting() { + /* not applicable to bangle2 */ + fg = 1-fg; + setScreenMode(); + drawInvert(); + drawExit(); + g.setFont("6x8",2); + g.drawString("Locating",68,88); + g.drawString("Satellites",56,108); + ctr--; + drawDots(); + g.reset(); + Bangle.drawWidgets(); + ctr++; +} + +function awaitGPSLoop() { + g.reset(); + setColours(); + g.setFont("6x8",bangle2?1:2); + drawDots(); + if (gps.fix) + startScreen(); + if (ctr % 10 == 0) { + g.reset(); + Bangle.drawWidgets(); + } + ctr++; +} + +function awaitGPS() { + Bangle.setOptions({wakeOnTwist:false}); + Bangle.setGPSPower(1); + Bangle.on('GPS', saveGPS); + Bangle.setLCDPower(true); + Bangle.setLCDTimeout(0); + g.reset(); + setColours(); + if (!bangle2) { + drawInvert(); + invID = setWatch(invertWaiting, BTN1, {repeat:true}); + } + drawExit(); + g.setFont("6x8",bangle2?1:2); + // g.drawString("Locating",bangle2?36:68,bangle2?72:88); + // g.drawString("Satellites",bangle2?24:56,bangle2?92:108); + g.drawString("Locating",bangle2?62:68,bangle2?78:88); + g.drawString("Satellites",56,bangle2?90:108); + intID = setInterval(awaitGPSLoop,1000); + if (bangle2) + finID = setWatch(function(){load();},BTN1); + else + finID = setWatch(function(){load();},BTN3); +} + +function main() { + require("Storage").write("pacer.json", cfg); + E.showMenu(); + if (cfg.eco) + Bangle.on('lcdPower', function(on) {setTimeout(function(){lcd_on = on;}, 500);}); + fg = cfg.dark?1:0; + R = cfg.metric?6371:3959; + ppace = 295 + 5 * cfg.pacer; + setScreenMode(); + awaitGPS(); +} + +defaults(); + +var main_menu = { + "" : { "title" : "Pacer"}, + "Start": function() { main(); }, + "Recording" : { + value : cfg.record, + format : v => v?"On":"Off", + onchange : v => { cfg.record = v; }, + }, + "Units" : { + value : cfg.metric, + format : v => v?"Metric":"Imperial", + onchange : v => { cfg.metric = v; }, + }, + "Lap" : { + value : cfg.lap_idx, + format : v => laps[v], + min : 0, max : 6, + onchange : v => { cfg.lap_idx = v; } + }, + "Dark mode" : { + value : cfg.dark, + format : v => v?"On":"Off", + onchange : v => { cfg.dark = v; }, + }, + "Eco battery" : { + value : cfg.eco, + format : v => v?"On":"Off", + onchange : v => { cfg.eco = v; }, + }, + "Eco storage" : { + value : cfg.storage, + format : v => v?"On":"Off", + onchange : v => { cfg.storage = v; }, + }, + "Steps" : { + value : cfg.show_steps, + format : v => v?"Count":"Cadence", + onchange : v => { cfg.show_steps = v; }, + }, + "Pacer" : { + value : cfg.pacer, + format : v => v==0?"Off":pace_str(v), + min : 0, max : 121, + onchange : v => { cfg.pacer = v; } + }, +}; + +if (!bangle2) { + Bangle.setLCDMode(); +} +Bangle.setLCDBrightness(1); +setScreenMode(); +E.showMenu(main_menu); From 9425a3eef4c7e0f8ca8e618d9db5b2331cb4f3b7 Mon Sep 17 00:00:00 2001 From: smulrine Date: Tue, 11 Feb 2025 21:44:04 +0000 Subject: [PATCH 05/14] Add files via upload --- apps/pacer/app-icon.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/pacer/app-icon.js diff --git a/apps/pacer/app-icon.js b/apps/pacer/app-icon.js new file mode 100644 index 000000000..33eeed26b --- /dev/null +++ b/apps/pacer/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4cA///un/+2Eqee1nV+X26NjtfNGLsf+AQOg+kzAROnskyfACJs5CINACJshCKAjMgfJBoPwgpHLiGSpMk3kHyneO5OECIMl23Aj+AO5QRD7ZSJgPpknZkmNCIJFJhFJk1wgFtCIN4CJFCpMwAgIRCt5HIoVN8B/BCIVmEZFyt6RCCIb7Ih5SCmYRCkjoM7YRB78k6ARO++kzwQKhgRC4GEzARKswRD8mT///Lg8DtmbCILvEEw8DkmzCIUcCIWTN40bkmWUoUCCIWbCJGSrIpC5YoB5x7HCINLRZcAg3bsgwBsARNtgRBlgRLmyNBCIMlwAQJgLkBtuyMwIRHvJUBpP2GoMGCIOwCI1SBQOSr3bA4PJSQKtGSoWSpXYBAMZAwKcGoQRCpoLCZAOSoARFh5HCk4IDtmpnikMAAMOpO8CJ0KpKQKAAlyfoQANqVMCByGBt4ROgWSfhgACjmTvAROimTCB0A8mbYgwAIwmYEZ/07wRPj/wCJ4AI")) From d1b344dbf70ef725ec738a79c055eb09fd65a852 Mon Sep 17 00:00:00 2001 From: smulrine Date: Tue, 11 Feb 2025 21:45:29 +0000 Subject: [PATCH 06/14] Add files via upload --- apps/pacer/README.md | 56 +++++++++++ apps/pacer/interface.html | 198 ++++++++++++++++++++++++++++++++++++++ apps/pacer/metadata.json | 19 ++++ 3 files changed, 273 insertions(+) create mode 100644 apps/pacer/README.md create mode 100644 apps/pacer/interface.html create mode 100644 apps/pacer/metadata.json diff --git a/apps/pacer/README.md b/apps/pacer/README.md new file mode 100644 index 000000000..0824bc7f4 --- /dev/null +++ b/apps/pacer/README.md @@ -0,0 +1,56 @@ +## Pacer + +![icon](app.png) + +Run with a virtual partner at your chosen pace, and export the GPX data +from the Bangle.js App Store. + +## Usage + +Pacer starts up with a menu. + +* **Recording** - whether to record the run +* **Units** - imperial or metric +* **Lap** - the multiple of a mile or kilometer to use for splits +* **Dark mode** - use black or white background +* **Eco battery** - display will turn off after 10 seconds +* **Eco storage** - only record GPS position every 10 seconds +* **Steps** - display step count or cadence +* **Pacer** - pace of virtual partner + +On selecting **Start**, GPS position will be detected. A run cannot be +started without a GPS fix. The watch touchscreen is disabled while the +app is running. + +The app will run on Bangle.js 1 and 2, although use on Bangle.js 2 is not +recommended due to poor GPS accuracy. + +On a Bangle.js 1, the top button reverses the screen colours, the middle +button starts, pauses or resumes a run, and the bottom button ends the run. + +On a Bangle.js 2, a short press of the button starts, pauses or resumes a +run, and a long press (over 0.5 seconds, but under 2!) ends the run. Note +that holding the button for 2 seconds will exit back to the default clock +app. + +## Downloading + +GPX tracks can be downloaded using the +[App Loader](https://banglejs.com/apps/?id=pacer). Connect the +Bangle.js and click on the Pacer app's disk icon to see the tracks +available for downloading. + +## Tips + +For best results, only start a run when the satellite signal strength bar is +green. + +Use the [Assisted GPS Updater](https://banglejs.com/apps/#AGPS) to improve +the time taken to get a GPS fix. + +## Bugs + +The eco settings are unlikely to be useful. + +GPS track smoothing is accomplished simply by reducing the frequency with +which readings are taken, depending on signal strength. diff --git a/apps/pacer/interface.html b/apps/pacer/interface.html new file mode 100644 index 000000000..71e8842d4 --- /dev/null +++ b/apps/pacer/interface.html @@ -0,0 +1,198 @@ + + + + + +
+
+ + + + + + + diff --git a/apps/pacer/metadata.json b/apps/pacer/metadata.json new file mode 100644 index 000000000..1fb5fcdcd --- /dev/null +++ b/apps/pacer/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "pacer", + "name": "Pacer", + "version": "0.01", + "description": "Run with a virtual partner", + "icon": "app.png", + "tags": "run,running,fitness,outdoors,gps", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "interface": "interface.html", + "storage": [ + {"name":"pacer.app.js","url":"app.js"}, + {"name":"pacer.img","url":"app-icon.js","evaluate":true} + ], + "data": [ + {"name":"pacer.json"}, + {"wildcard":".pacer*.csv","storageFile":true} + ] +} From 0f766ab1b6706685418f1fe242ea6785bbfd0035 Mon Sep 17 00:00:00 2001 From: smulrine Date: Tue, 11 Feb 2025 22:22:09 +0000 Subject: [PATCH 07/14] Add files via upload --- apps/pacer/app.png | Bin 0 -> 2263 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/pacer/app.png diff --git a/apps/pacer/app.png b/apps/pacer/app.png new file mode 100644 index 0000000000000000000000000000000000000000..92148056878e20aa72bc542fb374db0203226a15 GIT binary patch literal 2263 zcmV;|2q^c7P)+aIooC0iiNvOPJ!q`T*Q=ljmRXYbt$@Etd7Fq2nSmQebjP)3rX1sG0Xp&)N& zJg%~*R-H1(i!o>dPVAW{RV(Q;Lr0Gvt<`5q0007hAKdu&g(kPh_1w&5pPr5f2k&2+ zu}!TM?-)7$$#G%w003fI3ge1qapTRId@#IhVHHr{mwUvwBOwZ2wW&RVRbdQ5NS>Fy{GdM_T30PX1f@%Kt z4I6&3YiISod6Q*Q*($(LN-z|xX4-uF=DzfKAYL8`3HWU9PZb(DeQfBc@kN@9OzGqN zU49 zhoxh)K%r7ks`_Y-w%-J#L3=}+)zTK^c>n-tZ8CeVT|8ffETJchSAW?Q?Z55g)f-5q zHd88&x#0ri^T8Mm4Jt7S#$h82Dq6m!w*q?A1E>EqH&?1s?#6La&2jAEX^W4&+mqj( zJteBUiUM2SkODD=#YlkwtgS8Z%2XqeM5oVx&Yix6J-9wyqtQqklnTn=>hgxv;tIg)OSQ44lAYFCBk?1I+7a+PHl0rvL!8 ze_3>u^ZShyO#=XcA%B>rwXxyoh04WuqGJ(4Z7k>AK5O}jD@hmd){YIYH@9V{id+Qn zIYH1CkkC^LSX(VYCEHOP{_pj9UTbQ&Ra7uyj1~YuL{U&Q`)5V~Fhh$1ilfmtp00g{3grA#XK+biX+x)`bht(}!S#{lR zwcJI5&O8>N zZTACwgZt^|c+SsNv^JaLIt#I0MgRdlytU}_5_;#&-%h{n)Xm25PLknegX1`)YBIpj z_@M5r0~AA8qzSpUmYMeZqcNs0J9f0Tk|_1KoKvK7r7HS)*tme7d2qk^j>FAvO^N`M z$%MBBiQ_>5$1oHGmhpm2sfh5QrBV>n5^$NVn2Jz#BM$)3`SDV@uzlsh?P>`N z*tzwk@%7%^OI#?DEhQF%CPNPd*?lVr1w#;Y)VoQ}mu51Vf{EttY6&;7J*26QCJz9> z1p@HEVgW9|c8eiM0tlr+NlD>CqItVvCK6O$UUsO-t(n-$hz(nM^n#CF*!dUV%uWj1Ykk0Bbk%TI}}Nb(`M5nG^vvpDcWF*ywTF zbeRS^p1?z=7^u1S?Mp=};)4d2b>;f?6$iWeZ7qNO5+ll)K+97jLhy7u2LLFYB?E)5bD~tV-=9OZ7DbZ>0N7mi+C&9!KJ9baBJO!`k_23p ztE6ODpZejUfKY%x(tM}R8E>G7z+mANE|96{Xd9}s8rQx0_N7?9o63G)oQlkcSy!uo z5C9So@T>>OkOvepYQz^G&7YL0fQV_WIa0DlOi{}TLKOQj|9?Y*z;T@atOY|`N=uK1 z;z?Ff@yd2vApPYmt(cfLhJw*~B=7-0TseQ{@RUVImLwt|qFJZ^{OE1~*qsPaJmz*& zzneee((K7IXO9RUIRXI|2`JQ(DCU6PPR}T~zC5pRxUuUo+gx|E(d+Eko+##k2|x%i zT0(C~HyCB%F`vf`UbpM)Q)^B=NECO#1k@aQc12G9@Vu_oufKWi9^-Uv>I?3G2 Date: Wed, 12 Feb 2025 00:11:22 +0000 Subject: [PATCH 08/14] minor code fixes --- apps/pacer/app.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/pacer/app.js b/apps/pacer/app.js index 1422f7810..d12a13b01 100644 --- a/apps/pacer/app.js +++ b/apps/pacer/app.js @@ -46,12 +46,12 @@ var skip_max = 0; var force_write = true; var show_lap = false; var lcd_on = true; +var drawSats = false; var dist = 0.0; var pdist = 0.0; var oldDist = 0.0; var oldLat = -1; var oldLon = -1; -var oldTime = -1; var cadence = 0; var pace = 0; var ppace = 0; @@ -176,12 +176,10 @@ function saveGPS(fix) { doPace(newTime,dist); oldLat = newLat; oldLon = newLon; - oldTime = newTime; } } else { oldLat = newLat; oldLon = newLon; - oldTime = newTime; } if (recording && cfg.record && (force_write || skip_ctr == 0)) { fp.write([gps.time.getTime(),gps.lat.toFixed(5),gps.lon.toFixed(5),gps.alt].join(",")+"\n"); @@ -540,7 +538,6 @@ function pause(e) { if (!show_lap) drawTime(); drawStart(); - oldTime = -1; if (!isNaN(gps.time) && !isNaN(gps.lat) && !isNaN(gps.lon) && !isNaN(gps.alt) && cfg.record && (last_time != gps.time)) fp.write([gps.time.getTime(),gps.lat.toFixed(5),gps.lon.toFixed(5),gps.alt].join(",")+"\n"); Bangle.buzz(); From 13b16ac87dccc022f63ace1827a84ce9ea70d83f Mon Sep 17 00:00:00 2001 From: Logan B <3870583+thinkpoop@users.noreply.github.com> Date: Wed, 12 Feb 2025 11:17:54 -0600 Subject: [PATCH 09/14] [gbmusic] delete commented out code --- apps/gbmusic/app.js | 54 --------------------------------------------- 1 file changed, 54 deletions(-) diff --git a/apps/gbmusic/app.js b/apps/gbmusic/app.js index 1f6f6c620..50e7cf500 100644 --- a/apps/gbmusic/app.js +++ b/apps/gbmusic/app.js @@ -91,7 +91,6 @@ function rScroller(l) { const w = g.stringWidth(l.label)+40, y = l.y+l.h/2; l.offset = l.offset%w; - //g.setClipRect(l.x, l.y, l.x+l.w-1, l.y+l.h-1) g.setColor(l.col).setBgColor(l.bgCol) // need to set colors: iScroll calls this function outside Layout .setFontAlign(-1, 0) // left center .clearRect(l.x, l.y, l.x+l.w-1, l.y+l.h-1) @@ -129,58 +128,6 @@ function rInfo(l) { .drawString(l.label, l.x+l.w/2, l.y); } -// *** Unused Function *** -// // /** -// // * Render icon -// // * @param l -// // */ -// // function rIcon(l) { -// // const x2 = l.x+l.w-1, -// // y2 = l.y+l.h-1; -// // switch(l.icon) { -// // case "pause": { -// // const w13 = l.w/3; -// // g.drawRect(l.x, l.y, l.x+w13, y2); -// // g.drawRect(l.x+l.w-w13, l.y, x2, y2); -// // break; -// // } -// // case "play": { -// // g.drawPoly([ -// // l.x, l.y, -// // x2, l.y+l.h/2, -// // l.x, y2, -// // ], true); -// // break; -// // } -// // case "previous": { -// // const w15 = l.w*1/5; -// // g.drawPoly([ -// // x2, l.y, -// // l.x+w15, l.y+l.h/2, -// // x2, y2, -// // ], true); -// // g.drawRect(l.x, l.y, l.x+w15, y2); -// // break; -// // } -// // case "next": { -// // const w45 = l.w*4/5; -// // g.drawPoly([ -// // l.x, l.y, -// // l.x+w45, l.y+l.h/2, -// // l.x, y2, -// // ], true); -// // g.drawRect(l.x+w45, l.y, x2, y2); -// // break; -// // } -// // default: { // red X -// // console.log(`Unknown icon: ${l.icon}`); -// // g.setColor("#f00") -// // .drawRect(l.x, l.y, x2, y2) -// // .drawLine(l.x, l.y, x2, y2) -// // .drawLine(l.x, y2, x2, l.y); -// // } -// // } -// // } let layout; function makeUI() { @@ -492,7 +439,6 @@ function startLCDWatch() { Bangle.on("lcdPower", (on) => { if (on) { // redraw and resume scrolling - //tick(); // Not sure what this function was; currently undefined layout.render(); fadeOut(); if (layout.title.offset!==null) { // Making an assumption about what offset.offset was supposed to be From 15688b545b60fe7d8b2beb074352cea184889da9 Mon Sep 17 00:00:00 2001 From: smulrine Date: Wed, 12 Feb 2025 23:18:36 +0000 Subject: [PATCH 10/14] Add files via upload --- apps/pacer/screenshot.png | Bin 0 -> 2592 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/pacer/screenshot.png diff --git a/apps/pacer/screenshot.png b/apps/pacer/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..d97f04d7d9c7619d8b802b15905a92869e412f23 GIT binary patch literal 2592 zcmb7``8(8o7so%}8Dq>COK6yssfc7ZNLjN?m@;T`yRB0~Su(QUwjm`?gu+imA39r`$@M}S6AXsz)^|B_alH1y=VR<8_*VMs_D$EpNkg|2> zE6!UqH#M)DX2iN92jJqb1ICn9H9xhDmYxm3(O~nnRnKGLeQBX7XO%b`$WfA<9sB?lBDt<15d)@H^<_AF;DFkPylIqCz5g&11p)9XX`95k6 zuHe(1x2KU5yx9(Wt!hK6ZIdB13ch*_uY|hmSM$EI^~gg;Dg+b$ZUU;P-L&Ofrb*M@ zpHF^y3n&C_=i18HauVaKY;zE?ULwnG&uZDIGRhqT!Qb9pm*Zm!^69Ud(NsSbPknMh zm%9km;wBm_#8IS&qINOet|!OH58h4-vKLw5R;0W^*9KM0V6aL2`_d|`;cmsazG85M zFL#hD+$eL!qKm?`8a~Fo2HL{4gTbJI|7*l)|7oOZ){4{R@`or07#(R#;s++WqEe$=gGy#iY z2NeMv+z>Dop=F8wrz#8Nxe9sxM!rHDHMvSxG&IKQ0?(afYnaGvNgJ+^L%>74CX+!i| z9Nwq%Nhcn(Zxrzr)2LRP#1~7GUjAHbeSD}OjOhe*S+cd#K#J*xz54LP_4KJA91L(U zsrOwq)vkHu4+HI8``oF`KAHDyx9^%Mvyad%Zhb8SRDUTjl5$%Ko}(UXBi@OOGJ%-d z;&q$F3x3lgoi87D!v9iH!{9X;grLldP8j4lw^>e49&xFS|K5uyNHK~0>e?KSv<7!f znsi_?-k@58kcErPjh!C97^6pBM>d_|zekFzDYp@?`SAJblRn!YH+2GfC_)qK^JAPox+0aIC71Ocw2v1@-TicM|$qY2_ zbgv*2lFBFNCMY^&y}O8~H*sAFzyE!U$a*z8{d~ieAmLyKUF3JYaYeWJ7SCwRvkLSq zK=gIrS}6kX-#0raEXFL59#Z@ho}{4t zkJc)$SGC-&?y8^Df8^krB0wn72<**=2z9`LUuoDiErRac)-!nHgk?xYtZCk>h@LYF z;JSWC)kAtCa)iE9&v{d2fb#FGB1cxw5^`NIp*&KjB4UEqbFB8QsvDmrhv1JNm7<>qct7F zcK#F4f&DOSpuiBOFJRCx&_4!?)m=mr47})_N3X{d2V~EWP4%ZEat??8N29@Q{Xw0C z+}N2{p!AgB<}Y!3uKY7!dP=IUn$*43XSnu8eyl?>(&9cQ@}lv2jiSJtMnxd`Sck&D z#w(s~6ou45`F=ouMcWi=j{m;MI-uKUYRj+lNrZHqUJ5|2W#SPL6c{lL-qyMRm_5a9 zsR;^pl&`F9KGerPIJhm?I#Dc2qbg4BPz%-zhQAxZO z=d5ty#{o&}%;Rfr>)-s9 z8tp@rp|#g2xJ=+XqOGek#?&%7p4< zU4NcOs)Nn4XYL{?z@hp{!F%;hy#>czqm}YdOB?SNS!(NfBxiw@m>y>=q@OoTN#Kod zS3)O@BfxYLmhaunl(8GU!VZk74^`Bn#@XuyB+}Mh&mu!TYY`03eZzah&FP|^fsYH0_$BA872-t~ z(wu#MbIQERV|=AOKidNs%2W*#HLD1hrA%UVP%J6I@mYC0iId&Db-Zho9a}<3MM^Lg^?}sNvaF&=y06d5L}v1;Q3MAN4^iJiuFi10P$%!R^?R{eWH4ny?OVNQx4G wDG4HTkQ9HWJz%ngiM>d5-2YVe|7pw-nGNqLH6zQY@J>EpX>M&+X+n?tFVEPW{r~^~ literal 0 HcmV?d00001 From faf65dd93af47662188cb4f7fe670e71e7dbdc73 Mon Sep 17 00:00:00 2001 From: smulrine Date: Wed, 12 Feb 2025 23:20:06 +0000 Subject: [PATCH 11/14] added screenshot --- apps/pacer/metadata.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/pacer/metadata.json b/apps/pacer/metadata.json index 1fb5fcdcd..87d5fb1ce 100644 --- a/apps/pacer/metadata.json +++ b/apps/pacer/metadata.json @@ -8,6 +8,7 @@ "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "interface": "interface.html", + "screenshots": [{"url":"screenshot.png"}], "storage": [ {"name":"pacer.app.js","url":"app.js"}, {"name":"pacer.img","url":"app-icon.js","evaluate":true} From 2aadfe9bc862bccac3feaea6299e4d2cbaaecec6 Mon Sep 17 00:00:00 2001 From: smulrine Date: Wed, 12 Feb 2025 23:21:51 +0000 Subject: [PATCH 12/14] fixed Bangle.js2 exit icon --- apps/pacer/app.js | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/apps/pacer/app.js b/apps/pacer/app.js index d12a13b01..6b2b9efce 100644 --- a/apps/pacer/app.js +++ b/apps/pacer/app.js @@ -46,7 +46,7 @@ var skip_max = 0; var force_write = true; var show_lap = false; var lcd_on = true; -var drawSats = false; +var drawSats = true; var dist = 0.0; var pdist = 0.0; var oldDist = 0.0; @@ -507,21 +507,22 @@ function mainLoop() { function restart(e) { if (bangle2 && (e.time - e.lastTime > 0.5)) { finish(); + } else { + g.reset(); + setColours(); + paused_time += (Date.now() - begin_pause); + pace = 0; + drawPause(); + oldDist = dist; + skip_ctr = 0; + force_write = true; + recording = true; + Bangle.buzz(); + if (!bangle2) + setWatch(pause, BTN2); + else + setWatch(pause, BTN1, { edge: 'falling' }); } - g.reset(); - setColours(); - paused_time += (Date.now() - begin_pause); - pace = 0; - drawPause(); - oldDist = dist; - skip_ctr = 0; - force_write = true; - recording = true; - Bangle.buzz(); - if (!bangle2) - setWatch(pause, BTN2); - else - setWatch(pause, BTN1, { edge: 'falling' }); } function pause(e) { From 879485b664dbd3ac28852653e4706211542e56fd Mon Sep 17 00:00:00 2001 From: Logan B <3870583+thinkpoop@users.noreply.github.com> Date: Wed, 12 Feb 2025 18:29:00 -0600 Subject: [PATCH 13/14] [gbmusic] update sendCommand parameter jsdoc --- apps/gbmusic/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gbmusic/app.js b/apps/gbmusic/app.js index 50e7cf500..b09d772ff 100644 --- a/apps/gbmusic/app.js +++ b/apps/gbmusic/app.js @@ -367,7 +367,7 @@ function handleButton2Press() { let tCommand = {}; /** * Send command and highlight corresponding control - * @param {string} command - "play"/"pause"/"next"/"previous"/"volumeup"/"volumedown" + * @param {"play"|"pause"|"playpause"|"next"|"previous"|"volumeup"|"volumedown"} command */ function sendCommand(command) { Bluetooth.println(""); From 7a245cc8af83841d789ad565d01839c7cefeae5f Mon Sep 17 00:00:00 2001 From: Logan B <3870583+thinkpoop@users.noreply.github.com> Date: Wed, 12 Feb 2025 18:31:39 -0600 Subject: [PATCH 14/14] [gbmusic] always send playpause when toggling play/pause --- apps/gbmusic/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gbmusic/app.js b/apps/gbmusic/app.js index b09d772ff..31e56ec39 100644 --- a/apps/gbmusic/app.js +++ b/apps/gbmusic/app.js @@ -390,7 +390,7 @@ function handleTouch(btn, pos) { } function togglePlay() { - sendCommand(stat==="play" ? "pause" : "playpause"); + sendCommand("playpause"); } /**