Merge branch 'master' into fix/run-resume

Conflicts:
	apps/runplus/ChangeLog
master
Rob Pilling 2025-04-22 21:46:52 +01:00
commit 51dc8f1133
26 changed files with 359 additions and 27 deletions

View File

@ -5,3 +5,4 @@
0.05: Allow toggling of "max" values (screen tap) and recording (button press) 0.05: Allow toggling of "max" values (screen tap) and recording (button press)
0.06: Fix local unit setting 0.06: Fix local unit setting
0.07: Minor code improvements 0.07: Minor code improvements
0.08: Ensure graphics state is reset at the start of a draw (such as if a widget has altered state)

View File

@ -196,7 +196,7 @@ var emulator = (process.env.BOARD=="EMSCRIPTEN" || process.env.BOARD=="EMSCRIPTE
var SATinView = 0; var SATinView = 0;
function drawFix(dat) { function drawFix(dat) {
g.clearRect(0,screenYstart,screenW,screenH); g.reset().clearRect(0,screenYstart,screenW,screenH);
var v = ''; var v = '';
var u=''; var u='';
@ -234,7 +234,6 @@ function drawFix(dat) {
drawSats('View:' + SATinView); drawSats('View:' + SATinView);
} }
} }
g.reset();
} }
@ -404,8 +403,8 @@ function onGPS(fix) {
} }
function updateClock() { function updateClock() {
drawTime();
g.reset(); g.reset();
drawTime();
if ( emulator ) { if ( emulator ) {
max.spd++; max.alt++; max.spd++; max.alt++;
@ -499,7 +498,7 @@ function nextMode() {
function start() { function start() {
Bangle.setBarometerPower(1); // needs some time... Bangle.setBarometerPower(1); // needs some time...
g.clearRect(0,screenYstart,screenW,screenH); g.reset().clearRect(0,screenYstart,screenW,screenH);
onGPS(lf); onGPS(lf);
Bangle.setGPSPower(1); Bangle.setGPSPower(1);
Bangle.on('GPS', onGPS); Bangle.on('GPS', onGPS);

View File

@ -2,7 +2,7 @@
"id": "bikespeedo", "id": "bikespeedo",
"name": "Bike Speedometer (beta)", "name": "Bike Speedometer (beta)",
"shortName": "Bike Speedometer", "shortName": "Bike Speedometer",
"version": "0.07", "version": "0.08",
"description": "Shows GPS speed, GPS heading, Compass heading, GPS altitude and Barometer altitude from internal sources", "description": "Shows GPS speed, GPS heading, Compass heading, GPS altitude and Barometer altitude from internal sources",
"icon": "app.png", "icon": "app.png",
"screenshots": [{"url":"Screenshot.png"}], "screenshots": [{"url":"Screenshot.png"}],

1
apps/jsonclock/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: first release

20
apps/jsonclock/README.md Normal file
View File

@ -0,0 +1,20 @@
# JSON Clock ![](app.png)
*JSON view of the time.*
Written by: [David Volovskiy](https://github.com/voloved) and fully
inspired by [This FitBit face](https://github.com/Sharkgrammer/clockface.json)
* Displays a JSON that shows the date, time, step count, sunrise/set and battery.
* If the Bangle's theme is dark, then it'll show in dark view (`Settings>System>Theme>Dark BW`)
* It'll show 12 hrs if it's set so in `Settings>System>Locale>Time Format`
* Along with the physical button, you can leave the clock by pressing the `X` next to the `clockface.json` in the tab.
* If the steps cannot be gotten, they won't display
* If the location isn't set, then the JSON array will ignore the sun info and instead display the time as a struct.
## Screenshots
![](dark.png)
![](white.png)
![](no_steps.png)
![](no_location.png)
![](dark-emulator.png)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwcCBhEcuPHAQsDwE48AQDnEDx048YCEDQ/AEZAxEnADCEZAKBg5HQCIxiJuARPAEZHBgEBwQGBwE04VNmBHHXYXDhlAg0YsIbBEY8AZQmDhBHcFwcg4AOE4YpCI4swI4NgAQUAAQRHDg0IkAdDkGSAoRBNiBZWjgDCI4UBLAqPHKQMgCJQWFQIIROI6BzD4DXBw0YI5EAhEhwwIBoEwdQwAEWYIDBEYJIYa4REBSQKBBI5kJkmCgECpkwQIprEgcMmOAEZCPVJQYANhwRQaiIjRABqPCgLPEkilBNYxoCmHDa4L+BDAqzFgUIkGCjEgHhwxGAA4uEGYxHImPAahBHDgxEByEAAIIAqI4T+DABXgEaICEADoA="))

259
apps/jsonclock/app.js Normal file
View File

@ -0,0 +1,259 @@
{
const SunCalc = require("suncalc"); // from modules folder
const storage = require('Storage');
const widget_utils = require('widget_utils');
const global_settings = storage.readJSON("setting.json", true) || {};
const LOCATION_FILE = "mylocation.json";
const h = g.getHeight();
const w = g.getWidth();
let location;
let cachedSunTimes = null;
let lastSunCalcDate = null;
var prevVals = {};
let drawTimeout;
var settings = {
hr_12: global_settings["12hour"] !== undefined ? global_settings["12hour"] : false,
dark_mode: g.theme.dark
};
let clrs = {
tab: "#505050", // grey
keys: settings.dark_mode ? "#4287f5" : "#0000FF", // blue
strings: settings.dark_mode ? "#F0A000" : "#FF0000", // orange or red
ints: settings.dark_mode ? "#00FF00" : "#005F00", // green
bg: g.theme.bg,
brackets: g.theme.fg,
};
let jsonText;
let lines = [];
let fontSize = 13;
const lineHeight = 16;
const buttonHeight = 12;
const buttonX = 78;
const buttonY = 3;
let valuePositions = [];
const headerHeight = 16;
const usableHeight = h - headerHeight;
const maxLines = Math.floor(usableHeight / lineHeight);
var numWidth = 0;
// requires the myLocation app
let loadLocation = function() {
location = require("Storage").readJSON(LOCATION_FILE, 1) || {};
location.lat = location.lat || 0;
location.lon = location.lon || 0;
location.location = location.location || null;
};
let getHr = function(h) {
var amPm = "";
if (settings.hr_12) {
amPm = h < 12 ? "AM" : "PM";
h = h % 12;
if (h == 0) h = 12;
}
return [h, amPm];
};
let extractTime = function(d) {
const out = getHr(d.getHours());
const h = out[0];
const amPm = out[1];
const m = d.getMinutes();
return `${h}:${("0"+m).substr(-2)}${amPm}`;
};
let extractDate = function(d) {
const weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
];
const weekday = weekdays[d.getDay()];
const month = months[d.getMonth()];
const day = d.getDate();
return `${weekday} ${month} ${day}`;
};
let getSteps = function() {
try {
return Bangle.getHealthStatus("day").steps;
} catch (e) {
if (WIDGETS.wpedom !== undefined)
return WIDGETS.wpedom.getSteps();
else
return null;
}
};
let getVal = function(now, loc) {
const vals = {};
const currentDateStr = extractDate(now);
if (loc.location) {
if (lastSunCalcDate !== currentDateStr) {
cachedSunTimes = SunCalc.getTimes(now, location.lat, location.lon);
lastSunCalcDate = currentDateStr;
}
vals.rise = extractTime(cachedSunTimes.sunrise);
vals.set = extractTime(cachedSunTimes.sunset);
}
vals.time = extractTime(now);
vals.date = currentDateStr;
vals.batt_pct = E.getBattery();
vals.steps = getSteps();
return vals;
};
let loadJson = function() {
const now = new Date();
const vals = getVal(now, location);
//vals.steps = null; // For testing; uncomment to see the steps not appear
//location.location = null; // For testing, if null, the time becomes an struct to take up sun's struct
let raw;
if (location.location !== null) {
raw = {
time: vals.time,
dt: vals.date,
sun: {
rise: vals.rise,
set: vals.set,
},
"batt_%": vals.batt_pct,
};
} else {
raw = {
time: {
hr: getHr(now.getHours())[0],
min: now.getMinutes(),
},
dt: vals.date,
"batt_%": vals.batt_pct,
};
}
if (vals.steps != null) raw.steps = vals.steps;
jsonText = JSON.stringify(raw, null, 2);
lines = jsonText.split("\n");
};
let draw = function() {
g.clear();
g.setFontAlign(-1, -1);
g.setFont("Vector", 10);
valuePositions = [];
g.setColor(clrs.tab);
g.fillRect(90, 0, w, headerHeight);
g.setColor(clrs.brackets);
g.drawString("clockface.json", 3, 3);
g.setFont("Vector", buttonHeight);
g.drawString("X", buttonX, buttonY);
g.setFont("Vector", fontSize);
for (let i = 0; i < maxLines; i++) {
const y = headerHeight + i * lineHeight;
const lineNumberStr = (i + 1).toString().padStart(2, " ") + " ";
g.drawString(lineNumberStr, 0, y);
numWidth = Math.max(numWidth, g.stringWidth(lineNumberStr));
}
redrawValues();
};
let redraw = function() {
for (let i = 0; i < maxLines; i++) {
const lineIndex = i;
const line = lines[lineIndex];
if (!line) continue;
const y = headerHeight + i * lineHeight;
const indentMatch = line.match(/^(\s*)/);
const indent = indentMatch ? indentMatch[1] : "";
const kvMatch = line.trim().match(/^"([^"]+)":\s*(.+)$/);
if (kvMatch) {
const key = kvMatch[1];
let value = kvMatch[2];
if (prevVals.key == value) continue;
prevVals.key = value;
// Key
g.setColor(clrs.keys);
g.drawString(indent + `"${key}"`, numWidth, y);
const keyWidth = g.stringWidth(indent + `"${key}"`);
const valueX = numWidth + keyWidth;
const valueText = ": " + value;
// Value color
if (value.startsWith('"')) {
g.setColor(clrs.strings);
} else if (value.startsWith('{') || value.startsWith('}')) {
g.setColor(clrs.brackets);
} else {
g.setColor(clrs.ints);
}
g.drawString(valueText, valueX, y);
valuePositions.push({
key,
x: valueX,
y,
text: value
});
} else {
g.setColor(clrs.brackets);
g.drawString(line, numWidth, y);
}
}
};
let clearVals = function() {
g.setFont("Vector", fontSize);
g.setFontAlign(-1, -1);
valuePositions.forEach(pos => {
g.setColor(clrs.bg);
g.fillRect(pos.x, pos.y, w, pos.y + lineHeight);
});
};
let redrawValues = function() {
loadJson();
clearVals();
redraw();
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
redrawValues();
}, 60000 - (Date.now() % 60000));
};
Bangle.on('touch', (zone, e) => {
if (e.x >= (buttonY - buttonHeight) && e.x <= (buttonX + buttonHeight) &&
(e.y >= (buttonY - buttonHeight) && e.y <= (buttonY + buttonHeight))) {
Bangle.showLauncher(); // Exit app
}
});
Bangle.on('backlight', function(on) {
if (on) {
redrawValues();
}
});
Bangle.setUI("clock");
loadLocation();
Bangle.loadWidgets();
widget_utils.hide();
draw();
}

BIN
apps/jsonclock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
apps/jsonclock/dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
apps/jsonclock/light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,15 @@
{ "id": "jsonclock",
"name": "JsonClock",
"version": "0.01",
"description": "JSON view of the time, date, steps, battery, and sunrise and sunset times",
"icon": "app.png",
"screenshots": [{"url":"dark-emulator.png"}],
"readme": "README.md",
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"jsonclock.app.js","url":"app.js"},
{"name":"jsonclock.img","url":"app-icon.js","evaluate":true}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
apps/jsonclock/no_steps.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -28,5 +28,6 @@ Write to correct settings file, fixing settings not working.
0.25: Fix step count bug when runs are resumed after a long time 0.25: Fix step count bug when runs are resumed after a long time
0.26: Add ability to zoom in on a single stat by tapping it 0.26: Add ability to zoom in on a single stat by tapping it
0.27: Allow setting to alway resume an activity 0.27: Allow setting to alway resume an activity
0.28: Change the "time" stat to show active time (duration) rather than 0.28: Add vibration feedback on start/stop
0.29: Change the "time" stat to show active time (duration) rather than
elapsed time (fix #3802) elapsed time (fix #3802)

View File

@ -27,6 +27,7 @@ let settings = Object.assign({
B6: "caden", B6: "caden",
paceLength: 1000, paceLength: 1000,
alwaysResume: false, alwaysResume: false,
vibrate: false,
notify: { notify: {
dist: { dist: {
value: 0, value: 0,
@ -59,6 +60,8 @@ function setStatus(running) {
// Called to start/stop running // Called to start/stop running
function onStartStop() { function onStartStop() {
if (settings.vibrate) Bangle.buzz(250);
if (screen === "karvonen") { if (screen === "karvonen") {
// start/stop on the karvonen screen reverts us to the main screen // start/stop on the karvonen screen reverts us to the main screen
setScreen("main"); setScreen("main");

View File

@ -1,7 +1,7 @@
{ {
"id": "runplus", "id": "runplus",
"name": "Run+", "name": "Run+",
"version": "0.28", "version": "0.29",
"description": "Displays distance, time, steps, cadence, pace and more for runners. Based on the Run app, but extended with additional screens for heart rate interval training and individual stat focus.", "description": "Displays distance, time, steps, cadence, pace and more for runners. Based on the Run app, but extended with additional screens for heart rate interval training and individual stat focus.",
"icon": "app.png", "icon": "app.png",
"tags": "run,running,fitness,outdoors,gps,karvonen,karvonnen", "tags": "run,running,fitness,outdoors,gps,karvonen,karvonnen",

View File

@ -18,6 +18,7 @@
B6: "caden", B6: "caden",
paceLength: 1000, // TODO: Default to either 1km or 1mi based on locale paceLength: 1000, // TODO: Default to either 1km or 1mi based on locale
alwaysResume: false, alwaysResume: false,
vibrate: false,
notify: { notify: {
dist: { dist: {
increment: 0, increment: 0,
@ -80,6 +81,13 @@
saveSettings(); saveSettings();
}, },
}; };
menu[/*LANG*/"Start/stop vibrate"] = {
value : settings.vibrate,
onchange : v => {
settings.vibrate = v;
saveSettings();
},
};
var notificationsMenu = { var notificationsMenu = {
'< Back': function() { E.showMenu(menu) }, '< Back': function() { E.showMenu(menu) },
} }

View File

@ -85,3 +85,4 @@ of 'Select Clock'
0.74: Add extra layer of checks before allowing a factory reset (fix #3476) 0.74: Add extra layer of checks before allowing a factory reset (fix #3476)
0.75: Restore previous menu's scroll positions 0.75: Restore previous menu's scroll positions
0.76: Add altitude calibration menu (and update README after menu changed) 0.76: Add altitude calibration menu (and update README after menu changed)
0.77: Save altitude calibration when user exits via reset

View File

@ -1,7 +1,7 @@
{ {
"id": "setting", "id": "setting",
"name": "Settings", "name": "Settings",
"version": "0.76", "version": "0.77",
"description": "A menu for setting up Bangle.js", "description": "A menu for setting up Bangle.js",
"icon": "settings.png", "icon": "settings.png",
"tags": "tool,system", "tags": "tool,system",

View File

@ -1036,8 +1036,14 @@ function showAltitude() {
menuAltitude.value = Math.round(pressure.altitude); menuAltitude.value = Math.round(pressure.altitude);
m.draw(); m.draw();
} }
function altitudeDone() {
settings.seaLevelPressure = seaLevelPressure;
updateSettings();
}
Bangle.setBarometerPower(1,"settings"); Bangle.setBarometerPower(1,"settings");
Bangle.on("pressure",onPressure); Bangle.on("pressure",onPressure);
E.on("kill", altitudeDone);
var seaLevelPressure = Bangle.getOptions().seaLevelPressure; var seaLevelPressure = Bangle.getOptions().seaLevelPressure;
if (!isFinite(seaLevelPressure)) seaLevelPressure=1013.25; if (!isFinite(seaLevelPressure)) seaLevelPressure=1013.25;
var menuPressure = {value:"-"}; var menuPressure = {value:"-"};
@ -1045,8 +1051,8 @@ function showAltitude() {
var m = E.showMenu({ "" : {title:/*LANG*/"Altitude",back:() => { var m = E.showMenu({ "" : {title:/*LANG*/"Altitude",back:() => {
Bangle.setBarometerPower(0,"settings"); Bangle.setBarometerPower(0,"settings");
Bangle.removeListener("pressure",onPressure); Bangle.removeListener("pressure",onPressure);
settings.seaLevelPressure = seaLevelPressure; E.removeListener("kill",altitudeDone);
updateSettings(); altitudeDone();
popMenu(systemMenu()); popMenu(systemMenu());
}}, }},
/*LANG*/"Pressure (hPa)" : menuPressure, /*LANG*/"Pressure (hPa)" : menuPressure,
@ -1054,17 +1060,17 @@ function showAltitude() {
/*LANG*/"Adjust up" : function() { /*LANG*/"Adjust up" : function() {
Bangle.buzz(80); Bangle.buzz(80);
seaLevelPressure++; seaLevelPressure++;
Bangle.setOptions({seaLevelPressure:seaLevelPressure}); Bangle.setOptions({seaLevelPressure});
}, },
/*LANG*/"Adjust down" : function() { /*LANG*/"Adjust down" : function() {
Bangle.buzz(80); Bangle.buzz(80);
seaLevelPressure--; seaLevelPressure--;
Bangle.setOptions({seaLevelPressure:seaLevelPressure}); Bangle.setOptions({seaLevelPressure});
}, },
/*LANG*/"Set Default" : function() { /*LANG*/"Set Default" : function() {
Bangle.buzz(); Bangle.buzz();
seaLevelPressure=1013.25; seaLevelPressure=1013.25;
Bangle.setOptions({seaLevelPressure:seaLevelPressure}); Bangle.setOptions({seaLevelPressure});
} }
}); });
} }

View File

@ -7,3 +7,4 @@
0.07: Use default Bangle formatter for booleans 0.07: Use default Bangle formatter for booleans
0.08: Better formula for the moon's phase 0.08: Better formula for the moon's phase
0.09: Fix variable declaration 0.09: Fix variable declaration
0.10: Added ability to hide widget

View File

@ -1,7 +1,7 @@
{ {
"id": "widmp", "id": "widmp",
"name": "Moon Phase", "name": "Moon Phase",
"version": "0.09", "version": "0.10",
"description": "Display the current moon phase in blueish (in light mode) or white (in dark mode) for both hemispheres. In the southern hemisphere the 'My Location' app is needed.", "description": "Display the current moon phase in blueish (in light mode) or white (in dark mode) for both hemispheres. In the southern hemisphere the 'My Location' app is needed.",
"icon": "widget.png", "icon": "widget.png",
"type": "widget", "type": "widget",

View File

@ -2,6 +2,7 @@
var settings = Object.assign({ var settings = Object.assign({
default_colour: true, default_colour: true,
hide: false,
red: 0, red: 0,
green: 0, green: 0,
blue: 0, blue: 0,
@ -30,6 +31,13 @@
writeSettings(); writeSettings();
} }
}, },
"Hide Widget": {
value: settings.hide,
onchange: () => {
settings.hide = !settings.hide;
writeSettings();
}
},
"Custom...": () => E.showMenu(custommenu) "Custom...": () => E.showMenu(custommenu)
}; };

View File

@ -3,6 +3,7 @@
var lastCalculated = 0; // When we last calculated the phase var lastCalculated = 0; // When we last calculated the phase
var phase = 0; // The last phase we calculated var phase = 0; // The last phase we calculated
var southernHemisphere = false; // when in southern hemisphere -- use the "My Location" App var southernHemisphere = false; // when in southern hemisphere -- use the "My Location" App
var settings;
// https://github.com/deirdreobyrne/LunarPhase // https://github.com/deirdreobyrne/LunarPhase
function moonPhase(sec) { function moonPhase(sec) {
@ -40,13 +41,17 @@
} }
} }
function setMoonColour(g) { function reloadSettings() {
var settings = Object.assign({ settings = Object.assign({
default_colour: true, default_colour: true,
hide: false,
red: 0, red: 0,
green: 0, green: 0,
blue: 0, blue: 0,
}, require('Storage').readJSON("widmp.json", true) || {}); }, require('Storage').readJSON("widmp.json", true) || {});
}
function setMoonColour(g) {
if (settings.default_colour) { if (settings.default_colour) {
if (g.theme.dark) { if (g.theme.dark) {
g.setColor(0xffff); // white g.setColor(0xffff); // white
@ -62,6 +67,7 @@
function draw() { function draw() {
if (settings.hide) return;
const CenterX = this.x + 12, CenterY = this.y + 12, Radius = 11; const CenterX = this.x + 12, CenterY = this.y + 12, Radius = 11;
let leftFactor, rightFactor; let leftFactor, rightFactor;
@ -90,9 +96,11 @@
drawMoonPhase(CenterX,CenterY, Radius, leftFactor,rightFactor); drawMoonPhase(CenterX,CenterY, Radius, leftFactor,rightFactor);
} }
reloadSettings();
var wid = settings.hide ? 0 : 24;
WIDGETS["widmp"] = { WIDGETS["widmp"] = {
area: "tr", area: "tr",
width: 24, width: wid,
draw: draw draw: draw
}; };

2
core

@ -1 +1 @@
Subproject commit bf8d62eae968525b0964952ce84ad9bfe020e1d4 Subproject commit 49bab4e3c2483109e013fa3049dc6debf82b9a78