Merge branch 'espruino:master' into nav_testing

master
stweedo 2023-05-30 01:45:03 -05:00 committed by GitHub
commit 1dc18b40ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 435 additions and 90 deletions

1
apps/Uke/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New App!

11
apps/Uke/README.md Normal file
View File

@ -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

1
apps/Uke/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwgtqiAXWiMRDKsBolBCqcQilEoQwTiMUoMkkJGUiQwUFwVCGCcUoVEkJ5SgJ2CAQMROyIsBoVDoIXQgMSiJ2EPB4uBdwMieCMBCoIZCDoJdQAAMSUYUBLqIXBIhxCBCAJdDIZwPBTgIAEFxrOCAAIuTCwVELoQuToIuRgIuDCoUiFxjNCFwq7BC5YWBFoZdDAQIXLCwpdEogXKLYgWBXYZ9BC5SKDCwQYCkIHBC5IuFFQIYBiQhCC5JdFCoIYBBIYXJIwlEFwUUBIYXOLgIYDA4ReJC4i4BI4RODOxj/CAQIyBFwSOMoIYCagQ4BCxQXEigrBiS7CLpRHGAIMiMwYXMQoYwCSogXKU4gwCC6gwCC6ApEUoIFDRxR4Fd4QXReAgcEC5hIFLyAwJFxwwIiIWODATbDCyIYCAAQWSACY"))

131
apps/Uke/app.js Normal file
View File

@ -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();

BIN
apps/Uke/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

14
apps/Uke/metadata.json Normal file
View File

@ -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}
]
}

View File

@ -1,4 +1,5 @@
0.01: AdvCasio first version 0.01: AdvCasio first version
0.02: Remove un-needed fonts to improve memory usage 0.02: Remove un-needed fonts to improve memory usage
0.03: Tell clock widgets to hide. 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)

View File

@ -122,7 +122,7 @@ function draw() {
g.setFontAlign(0,-1); g.setFontAlign(0,-1);
g.setFont("8x12", 2); g.setFont("8x12", 2);
g.drawString(getTemperature(), 155, 132); 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.drawString(getSteps(), 158, 98);
g.setFontAlign(-1,-1); g.setFontAlign(-1,-1);

View File

@ -1,7 +1,7 @@
{ "id": "advcasio", { "id": "advcasio",
"name": "Advanced Casio Clock", "name": "Advanced Casio Clock",
"shortName":"advcasio", "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.", "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", "icon": "app.png",
"tags": "clock", "tags": "clock",

View File

@ -23,4 +23,6 @@
0.22: Handle connection events for GPS forwarding from phone 0.22: Handle connection events for GPS forwarding from phone
0.23: Handle 'act' Gadgetbridge messages for realtime activity monitoring 0.23: Handle 'act' Gadgetbridge messages for realtime activity monitoring
0.24: Handle new 'nav' event for navigation 0.24: Handle new 'nav' event for navigation
0.25: Added option to 'ignore' an app from the message 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

View File

@ -6,6 +6,7 @@
var lastMsg; // for music messages - may not be needed now... var lastMsg; // for music messages - may not be needed now...
var actInterval; // Realtime activity reporting interval when `act` is true var actInterval; // Realtime activity reporting interval when `act` is true
var actHRMHandler; // For Realtime activity reporting 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 // this settings var is deleted after this executes to save memory
var settings = require("Storage").readJSON("android.settings.json",1)||{}; 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 } // {t:"gps", lat, lon, alt, speed, course, time, satellites, hdop, externalSource:true }
"gps": function() { "gps": function() {
const settings = require("Storage").readJSON("android.settings.json",1)||{};
if (!settings.overwriteGps) return; if (!settings.overwriteGps) return;
// modify event for using it as Bangle GPS event
delete event.t; delete event.t;
event.satellites = NaN; if (!isFinite(event.satellites)) event.satellites = NaN;
if (!isFinite(event.course)) event.course = NaN; if (!isFinite(event.course)) event.course = NaN;
event.fix = 1; event.fix = 1;
if (event.long!==undefined) { // for earlier Gadgetbridge implementations if (event.long!==undefined) { // for earlier Gadgetbridge implementations
event.lon = event.long; event.lon = event.long;
delete 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); Bangle.emit('GPS', event);
}, },
// {t:"is_gps_active"} // {t:"is_gps_active"}
@ -258,10 +281,9 @@
if (isFinite(msg.id)) return gbSend({ t: "notify", n:"MUTE", id: msg.id }); if (isFinite(msg.id)) return gbSend({ t: "notify", n:"MUTE", id: msg.id });
}; };
// GPS overwrite logic // 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; const origSetGPSPower = Bangle.setGPSPower;
// migrate all GPS clients to the other variant on connection events Bangle.moveGPSPower = (state) => {
let handleConnection = (state) => {
if (Bangle.isGPSOn()){ if (Bangle.isGPSOn()){
let orig = Bangle._PWR.GPS; let orig = Bangle._PWR.GPS;
delete Bangle._PWR.GPS; delete Bangle._PWR.GPS;
@ -269,39 +291,45 @@
Bangle._PWR.GPS = orig; 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 serialTimeout;
let wrap = function(f){ let wrap = function(f){
return (s)=>{ return (s)=>{
if (serialTimeout) clearTimeout(serialTimeout); if (serialTimeout) clearTimeout(serialTimeout);
handleConnection(1); origSetGPSPower(1, "androidgpsserial");
f(s); f(s);
serialTimeout = setTimeout(()=>{ serialTimeout = setTimeout(()=>{
serialTimeout = undefined; serialTimeout = undefined;
if (NRF.getSecurityStatus().connected) handleConnection(0); origSetGPSPower(0, "androidgpsserial");
}, 10000); }, 10000);
}; };
}; };
Serial1.println = wrap(Serial1.println); Serial1.println = wrap(Serial1.println);
Serial1.write = wrap(Serial1.write); Serial1.write = wrap(Serial1.write);
// Replace set GPS power logic to suppress activation of gps (and instead request it from the phone) // replace set GPS power logic to suppress activation of gps (and instead request it from the phone)
Bangle.setGPSPower = (isOn, appID) => { Bangle.setGPSPower = ((isOn, appID) => {
// if not connected use internal GPS power function let pwr;
if (!NRF.getSecurityStatus().connected) return origSetGPSPower(isOn, appID); if (!this.lastGPSEvent){
if (!Bangle._PWR) Bangle._PWR={}; // use internal GPS power function if no gps event has arrived from GadgetBridge
if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[]; pwr = origSetGPSPower(isOn, appID);
if (!appID) appID="?"; } else {
if (isOn && !Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.push(appID); // we are currently expecting the next GPS event from GadgetBridge, keep track of GPS state per app
if (!isOn && Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.splice(Bangle._PWR.GPS.indexOf(appID),1); if (!Bangle._PWR) Bangle._PWR={};
let pwr = Bangle._PWR.GPS.length>0; 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 }); gbSend({ t: "gps_power", status: pwr });
return pwr; return pwr;
}; }).bind(gpsState);
// Allow checking for GPS via GadgetBridge // allow checking for GPS via GadgetBridge
Bangle.isGPSOn = () => { Bangle.isGPSOn = () => {
return !!(Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0); return !!(Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0);
}; };

View File

@ -2,7 +2,7 @@
"id": "android", "id": "android",
"name": "Android Integration", "name": "Android Integration",
"shortName": "Android", "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.", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
"icon": "app.png", "icon": "app.png",
"tags": "tool,system,messages,notifications,gadgetbridge", "tags": "tool,system,messages,notifications,gadgetbridge",

View File

@ -28,11 +28,12 @@ let sec = {
}; };
NRF.getSecurityStatus = () => sec; NRF.getSecurityStatus = () => sec;
// add an empty starting point to make the asserts work
Bangle._PWR={};
setTimeout(() => { let teststeps = [];
// add an empty starting point to make the asserts work
Bangle._PWR={};
teststeps.push(()=>{
print("Not connected, should use internal GPS"); print("Not connected, should use internal GPS");
assertTrue(!NRF.getSecurityStatus().connected, "Not connected"); assertTrue(!NRF.getSecurityStatus().connected, "Not connected");
@ -51,6 +52,9 @@ setTimeout(() => {
assertFalse(Bangle.isGPSOn(), "isGPSOn"); assertFalse(Bangle.isGPSOn(), "isGPSOn");
assertFalse(internalOn(), "Internal GPS off"); assertFalse(internalOn(), "Internal GPS off");
});
teststeps.push(()=>{
print("Connected, should use GB GPS"); print("Connected, should use GB GPS");
sec.connected = true; sec.connected = true;
@ -60,67 +64,90 @@ setTimeout(() => {
assertFalse(Bangle.isGPSOn(), "isGPSOn"); assertFalse(Bangle.isGPSOn(), "isGPSOn");
assertFalse(internalOn(), "Internal GPS off"); assertFalse(internalOn(), "Internal GPS off");
print("Internal GPS stays on until the first GadgetBridge event arrives");
assertTrue(Bangle.setGPSPower(1, "test"), "Switch GPS on"); assertTrue(Bangle.setGPSPower(1, "test"), "Switch GPS on");
assertNotEmpty(Bangle._PWR.GPS, "GPS"); assertNotEmpty(Bangle._PWR.GPS, "GPS");
assertTrue(Bangle.isGPSOn(), "isGPSOn"); 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"); assertFalse(Bangle.setGPSPower(0, "test"), "Switch GPS off");
assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS"); assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS");
assertFalse(Bangle.isGPSOn(), "isGPSOn"); assertFalse(Bangle.isGPSOn(), "isGPSOn");
assertFalse(internalOn(), "Internal GPS off"); assertFalse(internalOn(), "Internal GPS off");
});
print("Connected, then reconnect cycle"); teststeps.push(()=>{
sec.connected = true; print("Wait for all timeouts to run out");
return 12000;
});
assertTrue(NRF.getSecurityStatus().connected, "Connected"); teststeps.push(()=>{
print("Check auto switch when no GPS event arrives");
assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS");
assertFalse(Bangle.isGPSOn(), "isGPSOn");
assertFalse(internalOn(), "Internal GPS off");
assertTrue(Bangle.setGPSPower(1, "test"), "Switch GPS on"); 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"); assertNotEmpty(Bangle._PWR.GPS, "GPS");
assertTrue(Bangle.isGPSOn(), "isGPSOn"); assertTrue(Bangle.isGPSOn(), "isGPSOn");
assertFalse(internalOn(), "Internal GPS off"); assertFalse(internalOn(), "Internal GPS off");
NRF.emit("disconnect", {}); //wait on next test
print("disconnect"); return 12000;
sec.connected = false; });
setTimeout(() => { teststeps.push(()=>{
print("Check state and disable GPS, internal should be on");
assertNotEmpty(Bangle._PWR.GPS, "GPS"); assertNotEmpty(Bangle._PWR.GPS, "GPS");
assertTrue(Bangle.isGPSOn(), "isGPSOn"); assertTrue(Bangle.isGPSOn(), "isGPSOn");
assertTrue(internalOn(), "Internal GPS on"); assertTrue(internalOn(), "Internal GPS on");
print("connect"); assertFalse(Bangle.setGPSPower(0, "test"), "Switch GPS off");
sec.connected = true; });
NRF.emit("connect", {});
setTimeout(() => { teststeps.push(()=>{
assertNotEmpty(Bangle._PWR.GPS, "GPS"); print("Result Overall is " + (result ? "OK" : "FAIL"));
assertTrue(Bangle.isGPSOn(), "isGPSOn"); });
assertFalse(internalOn(), "Internal GPS off");
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"); setTimeout(()=>{
assertFalse(Bangle.isGPSOn(), "isGPSOn"); wrap(teststeps);
assertFalse(internalOn(), "Internal GPS off"); }, 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);

View File

@ -10,4 +10,5 @@
0.9: Remove ESLint spaces 0.9: Remove ESLint spaces
0.10: Show daily steps, heartrate and the temperature if weather information is available. 0.10: Show daily steps, heartrate and the temperature if weather information is available.
0.11: Tell clock widgets to hide. 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)

View File

@ -121,7 +121,7 @@ function draw() {
g.setFontAlign(0,-1); g.setFontAlign(0,-1);
g.setFont("8x12", 2); g.setFont("8x12", 2);
g.drawString(getTemperature(), 155, 132); 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.drawString(getSteps(), 158, 98);
g.setFontAlign(-1,-1); g.setFontAlign(-1,-1);

View File

@ -4,7 +4,7 @@
"description": "Animated Clock with Space Cassio Watch Style", "description": "Animated Clock with Space Cassio Watch Style",
"screenshots": [{ "url": "screens/screen_night.png" },{ "url": "screens/screen_day.png" }], "screenshots": [{ "url": "screens/screen_night.png" },{ "url": "screens/screen_day.png" }],
"icon": "app.png", "icon": "app.png",
"version": "0.12", "version": "0.13",
"type": "clock", "type": "clock",
"tags": "clock, weather, cassio, retro", "tags": "clock, weather, cassio, retro",
"supports": ["BANGLEJS2"], "supports": ["BANGLEJS2"],

1
apps/chargerot/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New App!

10
apps/chargerot/README.md Normal file
View File

@ -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.

14
apps/chargerot/boot.js Normal file
View File

@ -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();
}
});
})();

BIN
apps/chargerot/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -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"}
]
}

View File

@ -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();
})

View File

@ -100,6 +100,7 @@ function onInit(device) {
if (crc==2560806221) version = "2v15"; if (crc==2560806221) version = "2v15";
if (crc==2886730689) version = "2v16"; if (crc==2886730689) version = "2v16";
if (crc==156320890) version = "2v17"; if (crc==156320890) version = "2v17";
if (crc==4012421318) version = "2v18";
if (!ok) { if (!ok) {
version += `(&#9888; update required)`; version += `(&#9888; update required)`;
} }

View File

@ -24,3 +24,4 @@
0.24: Add ability to disable alarm functionality. 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.25: Add more colors to the settings and add the ability to disable the data charts+Markup.
0.26: Use widget_utils. 0.26: Use widget_utils.
0.27: Report latest HRM rather than HRM 10 minutes ago (fix #2395)

View File

@ -240,7 +240,7 @@ function _drawData(key, y, c){
value = E.getAnalogVRef().toFixed(2) + "V"; value = E.getAnalogVRef().toFixed(2) + "V";
} else if(key == "HRM"){ } 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"){ } else if (key == "TEMP"){
var weather = getWeather(); var weather = getWeather();
@ -710,7 +710,7 @@ Bangle.on('touch', function(btn, e){
var is_right = e.x > right; var is_right = e.x > right;
var is_upper = e.y < upper; var is_upper = e.y < upper;
var is_lower = e.y > lower; var is_lower = e.y > lower;
if(!settings.disableData){ if(!settings.disableData){
if(is_left && lcarsViewPos == 1){ if(is_left && lcarsViewPos == 1){
feedback(); feedback();

View File

@ -3,7 +3,7 @@
"name": "LCARS Clock", "name": "LCARS Clock",
"shortName":"LCARS", "shortName":"LCARS",
"icon": "lcars.png", "icon": "lcars.png",
"version":"0.26", "version":"0.27",
"readme": "README.md", "readme": "README.md",
"supports": ["BANGLEJS2"], "supports": ["BANGLEJS2"],
"description": "Library Computer Access Retrieval System (LCARS) clock.", "description": "Library Computer Access Retrieval System (LCARS) clock.",

View File

@ -2,3 +2,4 @@
0.02: Tell clock widgets to hide. 0.02: Tell clock widgets to hide.
0.03: Swipe down to see widgets 0.03: Swipe down to see widgets
Support for fast loading Support for fast loading
0.04: Localisation request: added Miles and AM/PM

View File

@ -2,7 +2,7 @@
"id": "pebbled", "id": "pebbled",
"name": "Pebble Clock with distance", "name": "Pebble Clock with distance",
"shortName": "Pebble + 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).", "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", "readme": "README.md",
"icon": "pebbled.png", "icon": "pebbled.png",

View File

@ -16,6 +16,15 @@ let drawTimeout;
let loadSettings = function() { let loadSettings = function() {
settings = require("Storage").readJSON(SETTINGS_FILE,1)|| {'bg': '#0f0', 'color': 'Green', 'avStep': 0.75}; 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==")); 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 draw = function() {
let date = new Date(); let date = new Date();
let da = date.toString().split(" "); 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; const t = 6;
let stps = Bangle.getHealthStatus("day").steps; 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% // turn the warning on once we have dipped below 25%
if (E.getBattery() < 15) if (E.getBattery() < 25)
batteryWarning = true; batteryWarning = true;
// turn the warning off once we have dipped above 20% // turn the warning off once we have dipped above 30%
if (E.getBattery() > 20) if (E.getBattery() > 30)
batteryWarning = false; batteryWarning = false;
g.reset(); g.reset();
@ -88,7 +100,7 @@ let draw = function() {
g.setColor('#fff'); // white on blue or red best contrast g.setColor('#fff'); // white on blue or red best contrast
else else
g.setColor('#000'); // otherwise black regardless of theme 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 // queue next draw
if (drawTimeout) clearTimeout(drawTimeout); if (drawTimeout) clearTimeout(drawTimeout);

View File

@ -2,7 +2,7 @@
const SETTINGS_FILE = "pebbleDistance.json"; const SETTINGS_FILE = "pebbleDistance.json";
// initialize with default settings... // 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 // ...and overwrite them with any saved values
// This way saved values are preserved if a new version adds more settings // 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 color_options = ['Green','Orange','Cyan','Purple','Red','Blue'];
var bg_code = ['#0f0','#ff0','#0ff','#f0f','#f00','#00f']; var bg_code = ['#0f0','#ff0','#0ff','#f0f','#f00','#00f'];
var local_options = ['World', 'US'];
E.showMenu({ E.showMenu({
'': { 'title': 'Pebble Clock' }, '': { 'title': 'PebbleD Clock' },
'< Back': back, '< Back': back,
'Color': { 'Color': {
value: 0 | color_options.indexOf(s.color), value: 0 | color_options.indexOf(s.color),
@ -43,6 +44,15 @@
s.avStep = v; s.avStep = v;
save(); 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();
},
} }
}); });
}); });

View File

@ -1,3 +1,4 @@
0.01: New App! 0.01: New App!
0.02: Trim old entries from the popcon app cache 0.02: Trim old entries from the popcon app cache
0.03: Avoid polluting global scope 0.03: Avoid polluting global scope
0.04: Add settings app for resetting popcon

View File

@ -26,9 +26,9 @@
trimCache(cache); trimCache(cache);
require("Storage").writeJSON("popcon.cache.json", cache); require("Storage").writeJSON("popcon.cache.json", cache);
if (orderChanged) { if (orderChanged) {
var info = oldRead("popcon.info", true); var info = oldRead("popconlaunch.info", true);
info.cacheBuster = !info.cacheBuster; info.cacheBuster = !info.cacheBuster;
require("Storage").writeJSON("popcon.info", info); require("Storage").writeJSON("popconlaunch.info", info);
} }
}; };
var sortCache = function () { var sortCache = function () {

View File

@ -38,9 +38,9 @@ const saveCache = (cache: Cache, orderChanged: boolean) => {
require("Storage").writeJSON("popcon.cache.json", cache); require("Storage").writeJSON("popcon.cache.json", cache);
if(orderChanged){ if(orderChanged){
// ensure launchers reload their caches: // 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; info.cacheBuster = !info.cacheBuster;
require("Storage").writeJSON("popcon.info", info); require("Storage").writeJSON("popconlaunch.info", info);
} }
}; };

View File

@ -2,16 +2,17 @@
"id": "popconlaunch", "id": "popconlaunch",
"name": "Popcon Launcher", "name": "Popcon Launcher",
"shortName": "Popcon", "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", "description": "Launcher modification - your launchers will display your favourite (popular) apps first. Overrides `readJSON`, may slow down your watch",
"readme": "README.md", "readme": "README.md",
"icon": "app.png", "icon": "app.png",
"type": "bootloader", "type": "bootloader",
"tags": "tool,system,launcher", "tags": "tool,system",
"supports": ["BANGLEJS2"], "supports": ["BANGLEJS2"],
"storage": [ "storage": [
{"name":"popcon.boot.js","url":"boot.js"}, {"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": [ "data": [
{"name":"popcon.cache.json"} {"name":"popcon.cache.json"}

View File

@ -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);
});

View File

@ -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