diff --git a/apps/Uke/ChangeLog b/apps/Uke/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/Uke/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/Uke/README.md b/apps/Uke/README.md new file mode 100644 index 000000000..49ceea1ed --- /dev/null +++ b/apps/Uke/README.md @@ -0,0 +1,11 @@ +# Uke Chords + +An app that simply describes finger placements on a Ukulele to form common chords. + +## Usage + +Use the button to scroll through the available chords. + +## Creator + +NovaDawn999 diff --git a/apps/Uke/app-icon.js b/apps/Uke/app-icon.js new file mode 100644 index 000000000..17b77ab32 --- /dev/null +++ b/apps/Uke/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwgtqiAXWiMRDKsBolBCqcQilEoQwTiMUoMkkJGUiQwUFwVCGCcUoVEkJ5SgJ2CAQMROyIsBoVDoIXQgMSiJ2EPB4uBdwMieCMBCoIZCDoJdQAAMSUYUBLqIXBIhxCBCAJdDIZwPBTgIAEFxrOCAAIuTCwVELoQuToIuRgIuDCoUiFxjNCFwq7BC5YWBFoZdDAQIXLCwpdEogXKLYgWBXYZ9BC5SKDCwQYCkIHBC5IuFFQIYBiQhCC5JdFCoIYBBIYXJIwlEFwUUBIYXOLgIYDA4ReJC4i4BI4RODOxj/CAQIyBFwSOMoIYCagQ4BCxQXEigrBiS7CLpRHGAIMiMwYXMQoYwCSogXKU4gwCC6gwCC6ApEUoIFDRxR4Fd4QXReAgcEC5hIFLyAwJFxwwIiIWODATbDCyIYCAAQWSACY")) diff --git a/apps/Uke/app.js b/apps/Uke/app.js new file mode 100644 index 000000000..c60c49a6b --- /dev/null +++ b/apps/Uke/app.js @@ -0,0 +1,131 @@ +const stringInterval = 30; +const stringLength = 131; +const fretHeight = 35; +const fingerOffset = 17; +const x = 44; +const y = 32; + +//chords +const cc = [ + "C", + "x", + "x", + "x", + "33" +]; + +const dd = [ + "D", + "23", + "22", + "24", + "x" +]; + +const gg = [ + "G", + "x", + "21", + "33", + "22", +]; + +const am = [ + "Am", + "22", + "x", + "x", + "x" +]; + +const em = [ + "Em", + "x", + "43", + "32", + "21" +]; + +const aa = [ + "A", + "22", + "11", + "x", + "x" +]; + +const ff = [ + "F", + "22", + "x", + "11", + "x" +]; + +var ee = [ + "E", + "33", + "32", + "34", + "11" +]; + +var index = 0; +var chords = []; + +function init() { + g.setFontAlign(0,0); // center font + g.setFont("6x8",2); // bitmap font, 8x magnified + chords.push(cc, dd, gg, am, em, aa, ff, ee); +} + +function drawBase() { + for (let i = 0; i < 4; i++) { + g.drawLine(x + i * stringInterval, y, x + i * stringInterval, y + stringLength); + g.fillRect(x- 1, y + i * fretHeight - 1, x + stringInterval * 3 + 1, y + i * fretHeight + 1); + } +} + +function drawChord(chord) { + g.drawString(chord[0], g.getWidth() * 0.5 + 2, 18); + for (let i = 0; i < chord.length; i++) { + if (i === 0 || chord[i][0] === "x") { + continue; + } + if (chord[i][0] === "0") { + g.drawString(chord[i][1], x + (i - 1) * stringInterval + 1, y + fretHeight * chord[i][0], true); + g.drawCircle(x + (i - 1) * stringInterval -1, y + fretHeight * chord[i][0], 8); + } + else { + g.drawString(chord[i][1], x + (i - 1) * stringInterval + 1, y -fingerOffset + fretHeight * chord[i][0], true); + g.drawCircle(x + (i - 1) * stringInterval -1, y -fingerOffset + fretHeight * chord[i][0], 8); + } + } +} + +function buttonPress() { + setWatch(() => { + buttonPress(); + }, BTN); + index++; + if (index >= chords.length) { index = 0; } + draw(); +} + +function draw() { + g.clear(); + drawBase(); + drawChord(chords[index]); +} + + + +function main() { + init(); + draw(); + setWatch(() => { + buttonPress(); + }, BTN); +} + +main(); diff --git a/apps/Uke/app.png b/apps/Uke/app.png new file mode 100644 index 000000000..7a6dd67ea Binary files /dev/null and b/apps/Uke/app.png differ diff --git a/apps/Uke/metadata.json b/apps/Uke/metadata.json new file mode 100644 index 000000000..10c3b3e79 --- /dev/null +++ b/apps/Uke/metadata.json @@ -0,0 +1,14 @@ +{ "id": "Uke", + "name": "Uke Chords", + "shortName":"Uke", + "version":"0.01", + "description": "Wrist mounted ukulele chords", + "icon": "app.png", + "tags": "uke, chords", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"Uke.app.js","url":"app.js"}, + {"name":"Uke.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/advcasio/ChangeLog b/apps/advcasio/ChangeLog index fd37c324e..bc4850635 100644 --- a/apps/advcasio/ChangeLog +++ b/apps/advcasio/ChangeLog @@ -1,4 +1,5 @@ 0.01: AdvCasio first version 0.02: Remove un-needed fonts to improve memory usage 0.03: Tell clock widgets to hide. -0.04: Swipe down to see widgets, step counter now just uses getHealthStatus +0.04: Swipe down to see widgets, step counter now just uses getHealthStatus +0.05: Report latest HRM rather than HRM 10 minutes ago (fix #2395) \ No newline at end of file diff --git a/apps/advcasio/app.js b/apps/advcasio/app.js index 9d246b7ef..d951da4cc 100644 --- a/apps/advcasio/app.js +++ b/apps/advcasio/app.js @@ -122,7 +122,7 @@ function draw() { g.setFontAlign(0,-1); g.setFont("8x12", 2); g.drawString(getTemperature(), 155, 132); - g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), 109, 98); + g.drawString(Math.round(Bangle.getHealthStatus().bpm||Bangle.getHealthStatus("last").bpm), 109, 98); g.drawString(getSteps(), 158, 98); g.setFontAlign(-1,-1); diff --git a/apps/advcasio/metadata.json b/apps/advcasio/metadata.json index 25dc1243a..32f5de7d3 100644 --- a/apps/advcasio/metadata.json +++ b/apps/advcasio/metadata.json @@ -1,7 +1,7 @@ { "id": "advcasio", "name": "Advanced Casio Clock", "shortName":"advcasio", - "version":"0.04", + "version":"0.05", "description": "An over-engineered clock inspired by Casio watches. It has a 4 days weather, a timer using swipe and a scratchpad. Can be updated using a dedicated webapp.", "icon": "app.png", "tags": "clock", diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index 55bf56c16..2262f20d8 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -23,4 +23,6 @@ 0.22: Handle connection events for GPS forwarding from phone 0.23: Handle 'act' Gadgetbridge messages for realtime activity monitoring 0.24: Handle new 'nav' event for navigation -0.25: Added option to 'ignore' an app from the message \ No newline at end of file +0.25: Added option to 'ignore' an app from the message +0.26: Change handling of GPS status to depend on GPS events instead of connection events + diff --git a/apps/android/boot.js b/apps/android/boot.js index 73d4dda67..8e2e64dd0 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -6,6 +6,7 @@ var lastMsg; // for music messages - may not be needed now... var actInterval; // Realtime activity reporting interval when `act` is true var actHRMHandler; // For Realtime activity reporting + var gpsState = {}; // keep information on GPS via Gadgetbridge // this settings var is deleted after this executes to save memory var settings = require("Storage").readJSON("android.settings.json",1)||{}; @@ -137,16 +138,38 @@ }, // {t:"gps", lat, lon, alt, speed, course, time, satellites, hdop, externalSource:true } "gps": function() { - const settings = require("Storage").readJSON("android.settings.json",1)||{}; if (!settings.overwriteGps) return; + // modify event for using it as Bangle GPS event delete event.t; - event.satellites = NaN; + if (!isFinite(event.satellites)) event.satellites = NaN; if (!isFinite(event.course)) event.course = NaN; event.fix = 1; if (event.long!==undefined) { // for earlier Gadgetbridge implementations event.lon = event.long; delete event.long; } + if (event.time){ + event.time = new Date(event.time); + } + + if (!gpsState.lastGPSEvent) { + // this is the first event, save time of arrival and deactivate internal GPS + Bangle.moveGPSPower(0); + } else { + // this is the second event, store the intervall for expecting the next GPS event + gpsState.interval = Date.now() - gpsState.lastGPSEvent; + } + gpsState.lastGPSEvent = Date.now(); + // in any case, cleanup the GPS state in case no new events arrive + if (gpsState.timeoutGPS) clearTimeout(gpsState.timeoutGPS); + gpsState.timeoutGPS = setTimeout(()=>{ + // reset state + gpsState.lastGPSEvent = undefined; + gpsState.timeoutGPS = undefined; + gpsState.interval = undefined; + // did not get an expected GPS event but have GPS clients, switch back to internal GPS + if (Bangle.isGPSOn()) Bangle.moveGPSPower(1); + }, (gpsState.interval || 10000) + 1000); Bangle.emit('GPS', event); }, // {t:"is_gps_active"} @@ -258,10 +281,9 @@ if (isFinite(msg.id)) return gbSend({ t: "notify", n:"MUTE", id: msg.id }); }; // GPS overwrite logic - if (settings.overwriteGps) { // if the overwrite option is set../ + if (settings.overwriteGps) { // if the overwrite option is set.. const origSetGPSPower = Bangle.setGPSPower; - // migrate all GPS clients to the other variant on connection events - let handleConnection = (state) => { + Bangle.moveGPSPower = (state) => { if (Bangle.isGPSOn()){ let orig = Bangle._PWR.GPS; delete Bangle._PWR.GPS; @@ -269,39 +291,45 @@ Bangle._PWR.GPS = orig; } }; - NRF.on('connect', ()=>{handleConnection(0);}); - NRF.on('disconnect', ()=>{handleConnection(1);}); - // Work around Serial1 for GPS not working when connected to something + // work around Serial1 for GPS not working when connected to something let serialTimeout; let wrap = function(f){ return (s)=>{ if (serialTimeout) clearTimeout(serialTimeout); - handleConnection(1); + origSetGPSPower(1, "androidgpsserial"); f(s); serialTimeout = setTimeout(()=>{ serialTimeout = undefined; - if (NRF.getSecurityStatus().connected) handleConnection(0); + origSetGPSPower(0, "androidgpsserial"); }, 10000); }; }; Serial1.println = wrap(Serial1.println); Serial1.write = wrap(Serial1.write); - // Replace set GPS power logic to suppress activation of gps (and instead request it from the phone) - Bangle.setGPSPower = (isOn, appID) => { - // if not connected use internal GPS power function - if (!NRF.getSecurityStatus().connected) return origSetGPSPower(isOn, appID); - if (!Bangle._PWR) Bangle._PWR={}; - if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[]; - if (!appID) appID="?"; - if (isOn && !Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.push(appID); - if (!isOn && Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.splice(Bangle._PWR.GPS.indexOf(appID),1); - let pwr = Bangle._PWR.GPS.length>0; + // replace set GPS power logic to suppress activation of gps (and instead request it from the phone) + Bangle.setGPSPower = ((isOn, appID) => { + let pwr; + if (!this.lastGPSEvent){ + // use internal GPS power function if no gps event has arrived from GadgetBridge + pwr = origSetGPSPower(isOn, appID); + } else { + // we are currently expecting the next GPS event from GadgetBridge, keep track of GPS state per app + if (!Bangle._PWR) Bangle._PWR={}; + if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[]; + if (!appID) appID="?"; + if (isOn && !Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.push(appID); + if (!isOn && Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.splice(Bangle._PWR.GPS.indexOf(appID),1); + pwr = Bangle._PWR.GPS.length>0; + // stop internal GPS, no clients left + if (!pwr) origSetGPSPower(0); + } + // always update Gadgetbridge on current power state gbSend({ t: "gps_power", status: pwr }); return pwr; - }; - // Allow checking for GPS via GadgetBridge + }).bind(gpsState); + // allow checking for GPS via GadgetBridge Bangle.isGPSOn = () => { return !!(Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0); }; diff --git a/apps/android/metadata.json b/apps/android/metadata.json index 26f646162..c5312a33e 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -2,7 +2,7 @@ "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.25", + "version": "0.26", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", "icon": "app.png", "tags": "tool,system,messages,notifications,gadgetbridge", diff --git a/apps/android/test.js b/apps/android/test.js index 88a7c0566..007b06fc7 100644 --- a/apps/android/test.js +++ b/apps/android/test.js @@ -28,11 +28,12 @@ let sec = { }; NRF.getSecurityStatus = () => sec; +// add an empty starting point to make the asserts work +Bangle._PWR={}; -setTimeout(() => { - // add an empty starting point to make the asserts work - Bangle._PWR={}; +let teststeps = []; +teststeps.push(()=>{ print("Not connected, should use internal GPS"); assertTrue(!NRF.getSecurityStatus().connected, "Not connected"); @@ -51,6 +52,9 @@ setTimeout(() => { assertFalse(Bangle.isGPSOn(), "isGPSOn"); assertFalse(internalOn(), "Internal GPS off"); +}); + +teststeps.push(()=>{ print("Connected, should use GB GPS"); sec.connected = true; @@ -60,67 +64,90 @@ setTimeout(() => { assertFalse(Bangle.isGPSOn(), "isGPSOn"); assertFalse(internalOn(), "Internal GPS off"); + + print("Internal GPS stays on until the first GadgetBridge event arrives"); assertTrue(Bangle.setGPSPower(1, "test"), "Switch GPS on"); assertNotEmpty(Bangle._PWR.GPS, "GPS"); assertTrue(Bangle.isGPSOn(), "isGPSOn"); - assertFalse(internalOn(), "Internal GPS off"); + assertTrue(internalOn(), "Internal GPS on"); + print("Send minimal GadgetBridge GPS event to trigger switch"); + GB({t:"gps"}); +}); + +teststeps.push(()=>{ + print("GPS should be on, internal off"); + + assertNotEmpty(Bangle._PWR.GPS, "GPS"); + assertTrue(Bangle.isGPSOn(), "isGPSOn"); + assertFalse(internalOn(), "Internal GPS off"); +}); + +teststeps.push(()=>{ + print("Switching GPS off turns both GadgetBridge as well as internal off"); assertFalse(Bangle.setGPSPower(0, "test"), "Switch GPS off"); assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS"); assertFalse(Bangle.isGPSOn(), "isGPSOn"); assertFalse(internalOn(), "Internal GPS off"); +}); - print("Connected, then reconnect cycle"); - sec.connected = true; +teststeps.push(()=>{ + print("Wait for all timeouts to run out"); + return 12000; +}); - assertTrue(NRF.getSecurityStatus().connected, "Connected"); - - assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS"); - assertFalse(Bangle.isGPSOn(), "isGPSOn"); - assertFalse(internalOn(), "Internal GPS off"); +teststeps.push(()=>{ + print("Check auto switch when no GPS event arrives"); assertTrue(Bangle.setGPSPower(1, "test"), "Switch GPS on"); + assertNotEmpty(Bangle._PWR.GPS, "GPS"); + assertTrue(Bangle.isGPSOn(), "isGPSOn"); + assertTrue(internalOn(), "Internal GPS on"); + + print("Send minimal GadgetBridge GPS event to trigger switch"); + GB({t:"gps"}); + + print("Internal should be switched off now"); + assertNotEmpty(Bangle._PWR.GPS, "GPS"); assertTrue(Bangle.isGPSOn(), "isGPSOn"); assertFalse(internalOn(), "Internal GPS off"); - NRF.emit("disconnect", {}); - print("disconnect"); - sec.connected = false; + //wait on next test + return 12000; +}); - setTimeout(() => { +teststeps.push(()=>{ + print("Check state and disable GPS, internal should be on"); - assertNotEmpty(Bangle._PWR.GPS, "GPS"); - assertTrue(Bangle.isGPSOn(), "isGPSOn"); - assertTrue(internalOn(), "Internal GPS on"); + assertNotEmpty(Bangle._PWR.GPS, "GPS"); + assertTrue(Bangle.isGPSOn(), "isGPSOn"); + assertTrue(internalOn(), "Internal GPS on"); - print("connect"); - sec.connected = true; - NRF.emit("connect", {}); + assertFalse(Bangle.setGPSPower(0, "test"), "Switch GPS off"); +}); - setTimeout(() => { - assertNotEmpty(Bangle._PWR.GPS, "GPS"); - assertTrue(Bangle.isGPSOn(), "isGPSOn"); - assertFalse(internalOn(), "Internal GPS off"); +teststeps.push(()=>{ + print("Result Overall is " + (result ? "OK" : "FAIL")); +}); - assertFalse(Bangle.setGPSPower(0, "test"), "Switch GPS off"); +let wrap = (functions) => { + if (functions.length > 0) { + setTimeout(()=>{ + let waitingTime = functions.shift()(); + if (waitingTime){ + print("WAITING: ", waitingTime); + setTimeout(()=>{wrap(functions);}, waitingTime); + } else + wrap(functions); + },0); + } +}; - assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS"); - assertFalse(Bangle.isGPSOn(), "isGPSOn"); - assertFalse(internalOn(), "Internal GPS off"); +setTimeout(()=>{ + wrap(teststeps); +}, 5000); - setTimeout(() => { - print("Test disconnect without gps on"); - - assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS"); - assertFalse(Bangle.isGPSOn(), "isGPSOn"); - assertFalse(internalOn(), "Internal GPS off"); - - print("Result Overall is " + (result ? "OK" : "FAIL")); - }, 0); - }, 0); - }, 0); -}, 5000); \ No newline at end of file diff --git a/apps/cassioWatch/ChangeLog b/apps/cassioWatch/ChangeLog index 1180554ff..e0abc576c 100644 --- a/apps/cassioWatch/ChangeLog +++ b/apps/cassioWatch/ChangeLog @@ -10,4 +10,5 @@ 0.9: Remove ESLint spaces 0.10: Show daily steps, heartrate and the temperature if weather information is available. 0.11: Tell clock widgets to hide. -0.12: Swipe down to see widgets, step counter now just uses getHealthStatus +0.12: Swipe down to see widgets, step counter now just uses getHealthStatus +0.13: Report latest HRM rather than HRM 10 minutes ago (fix #2395) \ No newline at end of file diff --git a/apps/cassioWatch/app.js b/apps/cassioWatch/app.js index 19dd883d2..68c8a3ceb 100644 --- a/apps/cassioWatch/app.js +++ b/apps/cassioWatch/app.js @@ -121,7 +121,7 @@ function draw() { g.setFontAlign(0,-1); g.setFont("8x12", 2); g.drawString(getTemperature(), 155, 132); - g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), 109, 98); + g.drawString(Math.round(Bangle.getHealthStatus().bpm||Bangle.getHealthStatus("last").bpm), 109, 98); g.drawString(getSteps(), 158, 98); g.setFontAlign(-1,-1); diff --git a/apps/cassioWatch/metadata.json b/apps/cassioWatch/metadata.json index 5ac4502fd..4b9985c82 100644 --- a/apps/cassioWatch/metadata.json +++ b/apps/cassioWatch/metadata.json @@ -4,7 +4,7 @@ "description": "Animated Clock with Space Cassio Watch Style", "screenshots": [{ "url": "screens/screen_night.png" },{ "url": "screens/screen_day.png" }], "icon": "app.png", - "version": "0.12", + "version": "0.13", "type": "clock", "tags": "clock, weather, cassio, retro", "supports": ["BANGLEJS2"], diff --git a/apps/chargerot/ChangeLog b/apps/chargerot/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/chargerot/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/chargerot/README.md b/apps/chargerot/README.md new file mode 100644 index 000000000..764a5ffda --- /dev/null +++ b/apps/chargerot/README.md @@ -0,0 +1,10 @@ +# Charge LCD rotation + +This simple app is for handling all types of charging cradles i.e.: +- [Official Bangle.js 2 dock](https://shop.espruino.com/banglejs2-dock) +- [Many more you can 3d print](https://www.thingiverse.com/search?q=banglejs+dock&page=1&type=things&sort=relevant) + +## Setup +In app settings set desired rotation. +App will swap screen rotation when charged and return to default one (you can change this in settings app) when undocked. + diff --git a/apps/chargerot/boot.js b/apps/chargerot/boot.js new file mode 100644 index 000000000..0a4361c50 --- /dev/null +++ b/apps/chargerot/boot.js @@ -0,0 +1,14 @@ +(() => { + const chargingRotation = 0 | require('Storage').readJSON("chargerot.settings.json").rotate; + const defaultRotation = 0 | require('Storage').readJSON("setting.json").rotate; + if (Bangle.isCharging()) g.setRotation(chargingRotation&3,chargingRotation>>2).clear(); + Bangle.on('charging', (charging) => { + if (charging) { + g.setRotation(chargingRotation&3,chargingRotation>>2).clear(); + Bangle.showClock(); + } else { + g.setRotation(defaultRotation&3,defaultRotation>>2).clear(); + Bangle.showClock(); + } + }); +})(); diff --git a/apps/chargerot/icon.png b/apps/chargerot/icon.png new file mode 100644 index 000000000..3347098e5 Binary files /dev/null and b/apps/chargerot/icon.png differ diff --git a/apps/chargerot/metadata.json b/apps/chargerot/metadata.json new file mode 100644 index 000000000..99c97070e --- /dev/null +++ b/apps/chargerot/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "chargerot", + "name": "Charge LCD rotation", + "version": "0.01", + "description": "When charging, this app can rotate your screen and revert it when unplugged. Made for all sort of cradles.", + "icon": "icon.png", + "tags": "battery", + "readme": "README.md", + "type": "bootloader", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"chargerot.boot.js","url":"boot.js"}, + {"name":"chargerot.settings.js","url":"settings.js"} + ] +} diff --git a/apps/chargerot/settings.js b/apps/chargerot/settings.js new file mode 100644 index 000000000..eaaa488f1 --- /dev/null +++ b/apps/chargerot/settings.js @@ -0,0 +1,28 @@ +(function(back) { + var rotNames = [/*LANG*/"No",/*LANG*/"Rotate CW",/*LANG*/"Left Handed",/*LANG*/"Rotate CCW",/*LANG*/"Mirror"]; + var FILE = "chargerot.settings.json"; + var appSettings = Object.assign({ + rotate: 0, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, appSettings); + } + + + E.showMenu({ + "" : { "title" : "Charging rotation" }, + "< Back" : () => back(), + 'Rotate': { + value: 0|appSettings.rotate, + min: 0, + max: rotNames.length-1, + format: v=> rotNames[v], + onchange: v => { + appSettings.rotate = 0 | v; + writeSettings(); + } + }, + }); + // If(true) big(); +}) \ No newline at end of file diff --git a/apps/fwupdate/custom.html b/apps/fwupdate/custom.html index 90f7c642b..6dcaad035 100644 --- a/apps/fwupdate/custom.html +++ b/apps/fwupdate/custom.html @@ -100,6 +100,7 @@ function onInit(device) { if (crc==2560806221) version = "2v15"; if (crc==2886730689) version = "2v16"; if (crc==156320890) version = "2v17"; + if (crc==4012421318) version = "2v18"; if (!ok) { version += `(⚠ update required)`; } diff --git a/apps/lcars/ChangeLog b/apps/lcars/ChangeLog index 7deef5a4b..fb778c278 100644 --- a/apps/lcars/ChangeLog +++ b/apps/lcars/ChangeLog @@ -24,3 +24,4 @@ 0.24: Add ability to disable alarm functionality. 0.25: Add more colors to the settings and add the ability to disable the data charts+Markup. 0.26: Use widget_utils. +0.27: Report latest HRM rather than HRM 10 minutes ago (fix #2395) \ No newline at end of file diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 4a5539c7a..cbb6e6ad5 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -240,7 +240,7 @@ function _drawData(key, y, c){ value = E.getAnalogVRef().toFixed(2) + "V"; } else if(key == "HRM"){ - value = Math.round(Bangle.getHealthStatus("last").bpm); + value = Math.round(Bangle.getHealthStatus().bpm||Bangle.getHealthStatus("last").bpm); } else if (key == "TEMP"){ var weather = getWeather(); @@ -710,7 +710,7 @@ Bangle.on('touch', function(btn, e){ var is_right = e.x > right; var is_upper = e.y < upper; var is_lower = e.y > lower; - + if(!settings.disableData){ if(is_left && lcarsViewPos == 1){ feedback(); diff --git a/apps/lcars/metadata.json b/apps/lcars/metadata.json index 787ca9046..65c59081f 100644 --- a/apps/lcars/metadata.json +++ b/apps/lcars/metadata.json @@ -3,7 +3,7 @@ "name": "LCARS Clock", "shortName":"LCARS", "icon": "lcars.png", - "version":"0.26", + "version":"0.27", "readme": "README.md", "supports": ["BANGLEJS2"], "description": "Library Computer Access Retrieval System (LCARS) clock.", diff --git a/apps/pebbled/ChangeLog b/apps/pebbled/ChangeLog index d2f71f908..a0127ffce 100644 --- a/apps/pebbled/ChangeLog +++ b/apps/pebbled/ChangeLog @@ -2,3 +2,4 @@ 0.02: Tell clock widgets to hide. 0.03: Swipe down to see widgets Support for fast loading +0.04: Localisation request: added Miles and AM/PM diff --git a/apps/pebbled/metadata.json b/apps/pebbled/metadata.json index 9e71a914b..62fabc3e7 100644 --- a/apps/pebbled/metadata.json +++ b/apps/pebbled/metadata.json @@ -2,7 +2,7 @@ "id": "pebbled", "name": "Pebble Clock with distance", "shortName": "Pebble + distance", - "version": "0.03", + "version": "0.04", "description": "Fork of Pebble Clock with distance in KM. Both step count and the distance are on the main screen. Default step length = 0.75m (can be changed in settings).", "readme": "README.md", "icon": "pebbled.png", diff --git a/apps/pebbled/pebbled.app.js b/apps/pebbled/pebbled.app.js index 627a7651c..54b56712c 100644 --- a/apps/pebbled/pebbled.app.js +++ b/apps/pebbled/pebbled.app.js @@ -16,6 +16,15 @@ let drawTimeout; let loadSettings = function() { settings = require("Storage").readJSON(SETTINGS_FILE,1)|| {'bg': '#0f0', 'color': 'Green', 'avStep': 0.75}; }; + +let tConv24 = function(time24) { + var ts = time24; + var H = +ts.substr(0, 2); + var h = (H % 12) || 12; + h = (h < 10)?("0"+h):h; + ts = h + ts.substr(2, 3); + return ts; +} const img = require("heatshrink").decompress(atob("oFAwkEogA/AH4A/AH4A/AH4A/AE8AAAoeXoAfeDQUBmcyD7A+Dh///8QD649CiAfaHwUvD4sEHy0DDYIfEICg+Cn4fHICY+DD4nxcgojOHwgfEIAYfRCIQaDD4ZAFD5r7DH4//kAfRCIZ/GAAnwD5p9DX44fTHgYSBf4ofVDAQEBl4fFUAgfOXoQzBgIfFBAIfPP4RAEAoYAB+cRiK/SG4h/WIBAfXIA7CBAAswD55AHn6fUIBMCD65AHl4gCmcziAfQQJqfQQJpiDgk0IDXxQLRAEECaBM+QgRYRYgUIA0CD4ggSQJiDCiAKBICszAAswD55AHABKBVD7BAFABIqBD5pAFABPxD55AOD6BADiIAJQAyxLABwf/gaAPAH4A/AH4ARA==")); @@ -30,16 +39,19 @@ let batteryWarning = false; let draw = function() { let date = new Date(); let da = date.toString().split(" "); - let timeStr = da[4].substr(0,5); + let timeStr = settings.localization === "US" ? tConv24(da[4].substr(0,5)) : da[4].substr(0,5); const t = 6; let stps = Bangle.getHealthStatus("day").steps; + const distInKm = (stps / 1000 * settings.avStep).toFixed(2); + const distance = settings.localization === "US" ? (distInKm / 1.609).toFixed(2) : distInKm; + const distanceStr = settings.localization === "US" ? distance + ' MI' : distance + ' KM'; - // turn the warning on once we have dipped below 15% - if (E.getBattery() < 15) + // turn the warning on once we have dipped below 25% + if (E.getBattery() < 25) batteryWarning = true; - // turn the warning off once we have dipped above 20% - if (E.getBattery() > 20) + // turn the warning off once we have dipped above 30% + if (E.getBattery() > 30) batteryWarning = false; g.reset(); @@ -88,7 +100,7 @@ let draw = function() { g.setColor('#fff'); // white on blue or red best contrast else g.setColor('#000'); // otherwise black regardless of theme - g.drawString((stps / 1000 * settings.avStep).toFixed(2) + ' KM', w/2, ha + 107); + g.drawString(distanceStr, w/2, ha + 107); // queue next draw if (drawTimeout) clearTimeout(drawTimeout); diff --git a/apps/pebbled/pebbled.settings.js b/apps/pebbled/pebbled.settings.js index d6c84d5d1..f4ca1d394 100644 --- a/apps/pebbled/pebbled.settings.js +++ b/apps/pebbled/pebbled.settings.js @@ -2,7 +2,7 @@ const SETTINGS_FILE = "pebbleDistance.json"; // initialize with default settings... - let s = {'bg': '#0f0', 'color': 'Green', 'avStep': 0.75}; + let s = {'bg': '#0f0', 'color': 'Green', 'avStep': 0.75, 'localization': 'World'}; // ...and overwrite them with any saved values // This way saved values are preserved if a new version adds more settings @@ -20,9 +20,10 @@ var color_options = ['Green','Orange','Cyan','Purple','Red','Blue']; var bg_code = ['#0f0','#ff0','#0ff','#f0f','#f00','#00f']; + var local_options = ['World', 'US']; E.showMenu({ - '': { 'title': 'Pebble Clock' }, + '': { 'title': 'PebbleD Clock' }, '< Back': back, 'Color': { value: 0 | color_options.indexOf(s.color), @@ -43,6 +44,15 @@ s.avStep = v; save(); } + }, + 'Localization': { + value: 0 | local_options.indexOf(s.localization), + min: 0, max: 1, + format: v => local_options[v], + onchange: v => { + s.localization = local_options[v]; + save(); + }, } }); }); diff --git a/apps/popconlaunch/ChangeLog b/apps/popconlaunch/ChangeLog index c430b4412..e174349bf 100644 --- a/apps/popconlaunch/ChangeLog +++ b/apps/popconlaunch/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Trim old entries from the popcon app cache 0.03: Avoid polluting global scope +0.04: Add settings app for resetting popcon diff --git a/apps/popconlaunch/boot.js b/apps/popconlaunch/boot.js index 0080a66f9..60c8ffcb7 100644 --- a/apps/popconlaunch/boot.js +++ b/apps/popconlaunch/boot.js @@ -26,9 +26,9 @@ trimCache(cache); require("Storage").writeJSON("popcon.cache.json", cache); if (orderChanged) { - var info = oldRead("popcon.info", true); + var info = oldRead("popconlaunch.info", true); info.cacheBuster = !info.cacheBuster; - require("Storage").writeJSON("popcon.info", info); + require("Storage").writeJSON("popconlaunch.info", info); } }; var sortCache = function () { diff --git a/apps/popconlaunch/boot.ts b/apps/popconlaunch/boot.ts index 4fe638f75..a6b52fb0f 100644 --- a/apps/popconlaunch/boot.ts +++ b/apps/popconlaunch/boot.ts @@ -38,9 +38,9 @@ const saveCache = (cache: Cache, orderChanged: boolean) => { require("Storage").writeJSON("popcon.cache.json", cache); if(orderChanged){ // ensure launchers reload their caches: - const info: AppInfo & { cacheBuster?: boolean } = oldRead("popcon.info", true); + const info: AppInfo & { cacheBuster?: boolean } = oldRead("popconlaunch.info", true); info.cacheBuster = !info.cacheBuster; - require("Storage").writeJSON("popcon.info", info); + require("Storage").writeJSON("popconlaunch.info", info); } }; diff --git a/apps/popconlaunch/metadata.json b/apps/popconlaunch/metadata.json index be3bc6a92..9e1f096d4 100644 --- a/apps/popconlaunch/metadata.json +++ b/apps/popconlaunch/metadata.json @@ -2,16 +2,17 @@ "id": "popconlaunch", "name": "Popcon Launcher", "shortName": "Popcon", - "version": "0.03", + "version": "0.04", "description": "Launcher modification - your launchers will display your favourite (popular) apps first. Overrides `readJSON`, may slow down your watch", "readme": "README.md", "icon": "app.png", "type": "bootloader", - "tags": "tool,system,launcher", + "tags": "tool,system", "supports": ["BANGLEJS2"], "storage": [ {"name":"popcon.boot.js","url":"boot.js"}, - {"name":"popcon.img","url":"icon.js","evaluate":true} + {"name":"popcon.img","url":"icon.js","evaluate":true}, + {"name":"popcon.settings.js","url":"settings.js"} ], "data": [ {"name":"popcon.cache.json"} diff --git a/apps/popconlaunch/settings.js b/apps/popconlaunch/settings.js new file mode 100644 index 000000000..29528c5dd --- /dev/null +++ b/apps/popconlaunch/settings.js @@ -0,0 +1,15 @@ +(function (back) { + var menu = { + '': { 'title': 'Popcon' }, + '< Back': back, + 'Reset app popularities': function () { + var S = require("Storage"); + S.erase("popcon.cache.json"); + var info = S.readJSON("popconlaunch.info", true); + info.cacheBuster = !info.cacheBuster; + S.writeJSON("popconlaunch.info", info); + E.showMessage("Popcon reset", "Done"); + }, + }; + E.showMenu(menu); +}); diff --git a/apps/popconlaunch/settings.ts b/apps/popconlaunch/settings.ts new file mode 100644 index 000000000..e301df9b2 --- /dev/null +++ b/apps/popconlaunch/settings.ts @@ -0,0 +1,18 @@ +(function(back) { + const menu = { + '': {'title': 'Popcon'}, + '< Back': back, + 'Reset app popularities': () => { + const S = require("Storage"); + S.erase("popcon.cache.json"); + + const info: AppInfo & { cacheBuster?: boolean } = S.readJSON("popconlaunch.info", true); + info.cacheBuster = !info.cacheBuster; + S.writeJSON("popconlaunch.info", info); + + E.showMessage("Popcon reset", "Done"); + }, + }; + + E.showMenu(menu); +}) satisfies SettingsFunc