David Volovskiy 2025-03-06 08:29:40 -05:00
commit bd77cf2ee9
47 changed files with 472 additions and 524 deletions

View File

@ -1 +1 @@
0.1: New app introduced to the app loader!
0.01: New app introduced to the app loader!

View File

@ -1 +0,0 @@
0.1: New app introduced to the app loader!

View File

@ -2,7 +2,7 @@
"id": "batterybooster",
"name": "Battery Booster",
"icon": "app.png",
"version": "0.1",
"version": "0.01",
"description": "A bootloader app which adds scripts to boost battery life of your Bangle.js 2",
"type": "bootloader",
"tags": "tools,system",
@ -16,4 +16,4 @@
"url": "boot.js"
}
]
}
}

View File

@ -1,6 +1 @@
0.27: New app introduced to the app loader!
0.28: Adjusted icon size to better fit with native dimensions (48x48)
0.29: Draw function optimization
0.30: Refine initial scrollbar position
0.31: Coloring for scrollbar
0.32: Coloring for scrollbar
0.01: New app introduced to the app loader!

View File

@ -1,6 +0,0 @@
0.27: New app introduced to the app loader!
0.28: Adjusted icon size to better fit with native dimensions (48x48)
0.29: Draw function optimization
0.30: Refine initial scrollbar position
0.31: Coloring for scrollbar
0.32: Coloring for scrollbar

View File

@ -2,7 +2,7 @@
"id": "cutelauncher",
"name": "Cute Launcher",
"shortName": "Cute Launcher",
"version": "0.32",
"version": "0.01",
"description": "A simple launcher app for Bangle.js 2 that makes use of the full touchscreen",
"icon": "app.png",
"type": "launch",
@ -35,4 +35,4 @@
"name": "cutelauncher.settings.json"
}
]
}
}

View File

@ -1,3 +1,4 @@
0.01: New app!
0.02: Submitted to app loader
0.03: Do not invert colors
0.04: Disable powersaving optional

View File

@ -15,6 +15,9 @@ let backlightSetting = storage.readJSON('setting.json').brightness; // LCD brigh
let angle = 0; // Store the angle of rotation
let image; // Cache the image here because we access it in multiple places
let appsettings = storage.readJSON('setting.json') || {};
let disablePowerSaving = appsettings.disablePowerSaving || true; // Default to false if not set
function drawMenu() {
Bangle.removeListener('touch', drawMenu); // We no longer want touching to reload the menu
Bangle.setOptions(cachedOptions); // The drawImage function set no timeout, undo that
@ -23,17 +26,18 @@ function drawMenu() {
E.showMenu(imageMenu);
}
//eslint-disable-next-line no-unused-vars
function drawImage(fileName) {
E.showMenu(); // Remove the menu to prevent it from breaking things
setTimeout(() => { Bangle.on('touch', drawMenu); }, 300); // Touch the screen to go back to the image menu (300ms timeout to allow user to lift finger)
Bangle.setOptions({ // Disable display power saving while showing the image
lockTimeout: 0,
lcdPowerTimeout: 0,
backlightTimeout: 0
});
if (disablePowerSaving) {
Bangle.setOptions({
lockTimeout: 0,
lcdPowerTimeout: 0,
backlightTimeout: 0
});
}
Bangle.setLCDBrightness(1); // Full brightness
image = eval(storage.read(fileName)); // Sadly, the only reasonable way to do this
g.clear().reset().setBgColor(0).setColor("#fff").drawImage(image, 88, 88, { rotate: angle });
}

View File

@ -1,7 +1,7 @@
{
"id": "gallery",
"name": "Gallery",
"version": "0.03",
"version": "0.04",
"description": "A gallery that lets you view images uploaded with the IDE (see README)",
"readme": "README.md",
"icon": "icon.png",
@ -22,6 +22,10 @@
"name": "gallery.img",
"url": "icon.js",
"evaluate": true
},
{ "name":"gallery.settings.js",
"url":"settings.js"
}
]
],
"data": [{"name":"gallery.json"}]
}

22
apps/gallery/settings.js Normal file
View File

@ -0,0 +1,22 @@
(function back() {
const storage = require('Storage');
// Load existing settings or initialize defaults
let settings = storage.readJSON('setting.json') || {};
settings.disablePowerSaving = settings.disablePowerSaving || false; // Default to false if not set
function saveSettings() {
storage.write('setting.json', settings);
}
E.showMenu({
'': { 'title': 'Gallery Settings' },
'Disable Power Saving': {
value: settings.disablePowerSaving,
onchange: v => {
settings.disablePowerSaving = v;
saveSettings();
}
},
'< Back': () => load()
});
})

View File

@ -0,0 +1,3 @@
{
"disablePowerSaving": true
}

1
apps/getaddr/ChangeLog Normal file
View File

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

24
apps/getaddr/README.md Normal file
View File

@ -0,0 +1,24 @@
# Get Address App
This app uses the GPS and compass capabilities of the Bangle.js 2 watch to display your current location and direction.
The app uses the Nominatim API to reverse geocode your location and display the street and house number. It also uses the compass to display your current direction.
## Screenshots
![](image1.png)
![](image2.png)
## Requirements
To run this app, you will need to meet the following requirements:
1. Android Integration app or iOS integration app must be installed.
2. In Bangle.js Gadgetbridge connect the watch, then navigate to the **Gear icon** and enable "Use GPS data from phone".
3. In the same settings dialog scroll down and enable "Allow internet access".
4. Under **System** > **Settings** > **General Settings**, press "Get location" once and wait until it finds a location
5. In the same settings dialog enable "Keep location up to date".
7. The watch must have a clear view of the sky to receive GPS signals.
8. The compass must be calibrated by moving the watch in a figure-eight motion.
Note: This app requires an active internet connection to function. It uses the Nominatim API to reverse geocode your location, and it may not work in areas with limited or no internet connectivity.
by [Online Speech to Text Cloud](https://www.speech-to-text.cloud/)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwYcZhMkyVACBkSpIRBpMgCBUEBwIRCkmACBEBBwYCDIhYRFJRAOFAQVICA0CCJFJNBYCFNwwOHCJpBCCgiMJQY6SECIeQBAYRMBBosVMRAR/CMR9NUKLFVdJpQDyVIAwIFCMRQCICIsSCJMgCK8CCJIQFCKRlFOIwAFhIRHoARZVoi5GAAwRGbogRXdgjmHbRYQKZArCGCK7IECBbIEYRC2IWBC2ICBqkCTxSkGTxYAOA"))

157
apps/getaddr/getaddr.app.js Normal file
View File

@ -0,0 +1,157 @@
// Set the API endpoint and parameters
const nominatimApi = 'https://nominatim.openstreetmap.org';
const locale = require('locale');
let lang = locale.name;
if (lang.toLowerCase() === 'system') {
lang = 'en';
} else {
lang = lang.substring(0, 2);
}
const params = {
format: 'json',
addressdetails: 1,
zoom: 18,
extratags: 1
};
// Function to break a string into lines
function breakStringIntoLines(str, maxWidth) {
const words = str.split(' ');
const lines = [];
let currentLine = '';
for (const word of words) {
if (currentLine.length + word.length + 1 > maxWidth) {
lines.push(currentLine);
currentLine = word;
} else {
if (currentLine!== '') {
currentLine +=' ';
}
currentLine += word;
}
}
lines.push(currentLine);
return lines;
}
// Function to clear the screen and display a message
function showMessage(address, error, dir) {
g.clear();
g.reset();
g.setFontVector(16);
const addressLines = breakStringIntoLines(address, 20);
let y = 20;
for (const line of addressLines) {
g.drawString(line, 10, y);
y += 20;
}
if (error) {
y += 10;
const errorLines = breakStringIntoLines(error, 20);
for (const line of errorLines) {
g.drawString(line, 10, y);
y += 20;
}
}
g.drawString(`Direction: ${dir}`, 10, 150);
g.flip();
}
// Function to get the compass direction
function getCompassDirection() {
const compass = Bangle.getCompass();
if (compass && compass.heading) {
const direction = Math.floor(compass.heading);
const directions = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'];
const index = Math.floor(((direction % 360) + 22.5) / 45) % 8;
if (index >= 0 && index < directions.length) {
return directions[index];
} else {
return 'Invalid index';
}
} else {
return 'No compass data';
}
}
// Variable to store the current address and error
let currentAddress = 'Getting address...';
let currentError = '';
let lastUpdateTime = 0;
// Function to get the current location
function getCurrentLocation() {
Bangle.setGPSPower(1);
Bangle.setCompassPower(1);
Bangle.on('GPS', (gps) => {
if (gps.fix) {
const now = Date.now();
if (now - lastUpdateTime < 30000) return;
lastUpdateTime = now;
getStreetAndHouseNumber(gps.lat, gps.lon);
} else {
currentAddress = 'No GPS signal';
currentError = `Sats: ${gps.satellites}`;
showMessage(currentAddress, currentError, getCompassDirection());
}
});
}
// Function to get the street and house number
function getStreetAndHouseNumber(lat, lon) {
const url = `${nominatimApi}/reverse`;
const paramsStr = Object.keys(params).map(key => `${key}=${encodeURIComponent(params[key])}`).join('&');
const fullUrl = `${url}?${paramsStr}&lat=${lat}&lon=${lon}&accept-language=${lang}&format=json`;
Bangle.http(fullUrl).then(data => {
try {
const jsonData = JSON.parse(data.resp);
if (jsonData && jsonData.address) {
let street = jsonData.address.road;
if (street.includes('Straße')) {
street = street.replace('Straße', 'Str.');
} else if (street.includes('Street')) {
street = street.replace('Street', 'St.');
}
const houseNumber = jsonData.address.house_number;
const newAddress = `${street} ${houseNumber}`;
if (newAddress!== currentAddress) {
currentAddress = newAddress;
currentError = '';
}
} else {
const newAddress = 'No address';
if (newAddress!== currentAddress) {
currentAddress = newAddress;
currentError = '';
}
}
} catch (err) {
const newError = `Error: ${err}`;
if (newError!== currentError) {
currentError = newError;
}
}
showMessage(currentAddress, currentError, getCompassDirection());
}).catch(err => {
const newError = `Error: ${err}`;
if (newError!== currentError) {
currentError = newError;
}
showMessage(currentAddress, currentError, getCompassDirection());
});
}
// Main function
function main() {
showMessage('Getting address...', '', getCompassDirection());
getCurrentLocation();
setInterval(() => {
showMessage(currentAddress, currentError, getCompassDirection());
}, 1000);
}
// Call the main function
main();

BIN
apps/getaddr/getaddr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 B

BIN
apps/getaddr/image1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
apps/getaddr/image2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,17 @@
{
"id": "getaddr",
"name": "Get Addr",
"version": "0.01",
"description": "An app that shows the address of the current location",
"readme": "README.md",
"icon": "getaddr.png",
"screenshots": [{"url":"image1.png"}, {"url":"image2.png"}],
"type": "app",
"tags": "gps,outdoors,tools",
"supports": ["BANGLEJS2"],
"dependencies" : {},
"storage": [
{"name":"getaddr.app.js","url":"getaddr.app.js"},
{"name":"getaddr.img","url":"getaddr-icon.js","evaluate":true}
]
}

View File

@ -1 +1 @@
0.97: New app introduced to the app loader!
0.01: New app introduced to the app loader!

View File

@ -1 +0,0 @@
0.97: New app introduced to the app loader!

View File

@ -2,7 +2,7 @@
"id": "onewordclock",
"name": "One Word Clock",
"shortName": "One Word",
"version": "0.97",
"version": "0.01",
"description": "A unique clock that displays a single evocative word for each hour of the day",
"icon": "app.png",
"screenshots": [
@ -35,4 +35,4 @@
"name": "onewordclock.settings.json"
}
]
}
}

View File

@ -1,2 +1,3 @@
0.01: First Release
0.02: Use built-in rounded-rect draw function, faster UI
0.02: Use built-in rounded-rect draw function, faster UI
0.03: Maximum rest time increased from 120s to 600s

View File

@ -1,7 +1,7 @@
{ "id": "rest",
"name": "Rest - Workout Timer App",
"shortName":"Rest",
"version": "0.02",
"version": "0.03",
"description": "Rest timer and Set counter for workout, fitness and lifting things.",
"icon": "app.png",
"screenshots": [{"url": "screenshot1.png"}, {"url": "screenshot2.png"}, {"url": "screenshot3.png"}],

View File

@ -232,11 +232,11 @@ const onTouchPerQuadrantPerMode = {
SET_REST: [
[MAIN_MENU, Bangle.buzz], [null, null],
[null, () => {
restSeconds = Math.min(120, Math.max(0, restSeconds - 15));
restSeconds = Math.min(600, Math.max(0, restSeconds - 15));
Bangle.buzz(100);
}],
[null, () => {
restSeconds = Math.min(120, Math.max(0, restSeconds + 15));
restSeconds = Math.min(600, Math.max(0, restSeconds + 15));
Bangle.buzz(100);
}],
],

View File

@ -1,4 +0,0 @@
0.01: New App!
0.02: Fix case where we tried to push to Bangle.btnWatches but it wasn't defined.
0.03: Throw exception if trying to add custom drag handler on mode updown and leftright.
0.04: Bangle.js 1 support. No change to Bangle.js 2.

View File

@ -1,24 +0,0 @@
# setUI Proposals Preview
Try out changes to setUI that may or may not eventually en up in the Bangle.js firmware.
## Usage
Just install it and it modifies setUI at boot time.
## Features
- Add custom handlers on top of the standard modes as well. Previously this was only possible for mode == "custom".
- The goal here is to make it possible to move all input handling inside `setUI` where today some apps add on extra handlers outside of `setUI` calls.
- Change the default behaviour of the hardware button to act immediately on press down. Previously it has been acting on button release.
- This makes the interaction slightly snappier.
- In addition to the existing `btn` key a new `btnRelease` key can now be specified. `btnRelease` will let you listen to the rising edge of the hardware button.
## Requests
Please report your experience and thoughts on this issue:
[ Discussion: HW buttons should act on 'rising' edge #3435 ](https://github.com/espruino/BangleApps/issues/3435) or on the related forum conversation [Making Bangle.js more responsive](https://forum.espruino.com/conversations/397606/).
## Creator
The changes done here were done by thyttan with help from Gordon Williams.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 990 B

View File

@ -1,118 +0,0 @@
Bangle.setUI = (function(mode, cb) {
var options = {};
if ("object"==typeof mode) {
options = mode;
mode = options.mode;
if (!mode) throw new Error("Missing mode in setUI({...})");
}
var redraw = true;
if (global.WIDGETS && WIDGETS.back) {
redraw = false;
WIDGETS.back.remove(mode && options.back);
}
if (Bangle.btnWatches) {
Bangle.btnWatches.forEach(clearWatch);
delete Bangle.btnWatches;
}
if (Bangle.swipeHandler) {
Bangle.removeListener("swipe", Bangle.swipeHandler);
delete Bangle.swipeHandler;
}
if (Bangle.touchHandler) {
Bangle.removeListener("touch", Bangle.touchHandler);
delete Bangle.touchHandler;
}
delete Bangle.uiRedraw;
delete Bangle.CLOCK;
if (Bangle.uiRemove) {
let r = Bangle.uiRemove;
delete Bangle.uiRemove; // stop recursion if setUI is called inside uiRemove
r();
}
g.reset();// reset graphics state, just in case
if (!mode) return;
if (mode=="updown") {
Bangle.btnWatches = [
setWatch(function() { cb(-1); }, BTN1, {repeat:1,edge:"rising"}),
setWatch(function() { cb(1); }, BTN3, {repeat:1,edge:"rising"}),
setWatch(function() { cb(); }, BTN2, {repeat:1,edge:"rising"})
];
} else if (mode=="leftright") {
Bangle.btnWatches = [
setWatch(function() { cb(-1); }, BTN1, {repeat:1,edge:"rising"}),
setWatch(function() { cb(1); }, BTN3, {repeat:1,edge:"rising"}),
setWatch(function() { cb(); }, BTN2, {repeat:1,edge:"rising"})
];
Bangle.swipeHandler = d => {cb(d);};
Bangle.on("swipe", Bangle.swipeHandler);
Bangle.touchHandler = d => {cb();};
Bangle.on("touch", Bangle.touchHandler);
} else if (mode=="clock") {
Bangle.CLOCK=1;
Bangle.btnWatches = [
setWatch(Bangle.showLauncher, BTN2, {repeat:1,edge:"rising"})
];
} else if (mode=="clockupdown") {
Bangle.CLOCK=1;
Bangle.btnWatches = [
setWatch(function() { cb(-1); }, BTN1, {repeat:1,edge:"rising"}),
setWatch(function() { cb(1); }, BTN3, {repeat:1,edge:"rising"}),
setWatch(Bangle.showLauncher, BTN2, {repeat:1,edge:"rising"})
];
} else if (mode=="custom") {
if (options.clock) {
Bangle.btnWatches = [
setWatch(Bangle.showLauncher, BTN2, {repeat:1,edge:"rising"})
];
}
} else
throw new Error("Unknown UI mode "+E.toJS(mode));
if (options.clock) Bangle.CLOCK=1;
if (options.touch) {
Bangle.touchHandler = options.touch;
Bangle.on("touch", Bangle.touchHandler);
}
if (options.swipe) {
Bangle.swipeHandler = options.swipe;
Bangle.on("swipe", Bangle.swipeHandler);
}
if ((options.btn || options.btnRelease) && !Bangle.btnWatches) Bangle.btnWatches = [];
if (options.btn) Bangle.btnWatches.push(
setWatch(function() { options.btn(1); }, BTN1, {repeat:1,edge:"rising"}),
setWatch(function() { options.btn(2); }, BTN2, {repeat:1,edge:"rising"}),
setWatch(function() { options.btn(3); }, BTN3, {repeat:1,edge:"rising"})
);
if (options.btnRelease) Bangle.btnWatches.push(
setWatch(function() { options.btn(1); }, BTN1, {repeat:1,edge:"falling"}),
setWatch(function() { options.btn(2); }, BTN2, {repeat:1,edge:"falling"}),
setWatch(function() { options.btn(3); }, BTN3, {repeat:1,edge:"falling"})
);
if (options.remove) // handler for removing the UI (intervals/etc)
Bangle.uiRemove = options.remove;
if (options.redraw) // handler for redrawing the UI
Bangle.uiRedraw = options.redraw;
if (options.back) {
var touchHandler = (z) => {
if (z==1) options.back();
};
Bangle.on("touch", touchHandler);
var btnWatch;
if (Bangle.btnWatches===undefined) // only add back button handler if there's no existing watch on BTN1
btnWatch = setWatch(function() {
btnWatch = undefined;
options.back();
}, BTN3, {edge:"rising"});
WIDGETS = Object.assign({back:{
area:"tl", width:24,
draw:e=>g.reset().setColor("#f00").drawImage(atob("GBiBAAAYAAH/gAf/4A//8B//+D///D///H/P/n+H/n8P/n4f/vwAP/wAP34f/n8P/n+H/n/P/j///D///B//+A//8Af/4AH/gAAYAA=="),e.x,e.y),
remove:(noclear)=>{
if (btnWatch) clearWatch(btnWatch);
Bangle.removeListener("touch", touchHandler);
if (!noclear) g.reset().clearRect({x:WIDGETS.back.x, y:WIDGETS.back.y, w:24,h:24});
delete WIDGETS.back;
if (!noclear) Bangle.drawWidgets();
}
}},global.WIDGETS);
if (redraw) Bangle.drawWidgets();
}
})

View File

@ -1,154 +0,0 @@
Bangle.setUI = (function(mode, cb) {
var options = {};
if ("object"==typeof mode) {
options = mode;
mode = options.mode;
if (!mode) throw new Error("Missing mode in setUI({...})");
}
var redraw = true;
if (global.WIDGETS && WIDGETS.back) {
redraw = false;
WIDGETS.back.remove(mode && options.back);
}
if (Bangle.btnWatches) {
Bangle.btnWatches.forEach(clearWatch);
delete Bangle.btnWatches;
}
if (Bangle.swipeHandler) {
Bangle.removeListener("swipe", Bangle.swipeHandler);
delete Bangle.swipeHandler;
}
if (Bangle.dragHandler) {
Bangle.removeListener("drag", Bangle.dragHandler);
delete Bangle.dragHandler;
}
if (Bangle.touchHandler) {
Bangle.removeListener("touch", Bangle.touchHandler);
delete Bangle.touchHandler;
}
delete Bangle.uiRedraw;
delete Bangle.CLOCK;
if (Bangle.uiRemove) {
let r = Bangle.uiRemove;
delete Bangle.uiRemove; // stop recursion if setUI is called inside uiRemove
r();
}
g.reset();// reset graphics state, just in case
if (!mode) return;
function b() {
try{Bangle.buzz(30);}catch(e){}
}
if (mode=="updown") {
if (options.drag) throw new Error("Custom drag handler not supported in mode updown!")
var dy = 0;
Bangle.dragHandler = e=>{
dy += e.dy;
if (!e.b) dy=0;
while (Math.abs(dy)>32) {
if (dy>0) { dy-=32; cb(1) }
else { dy+=32; cb(-1) }
Bangle.buzz(20);
}
};
Bangle.on('drag',Bangle.dragHandler);
Bangle.touchHandler = d => {b();cb();};
Bangle.btnWatches = [
setWatch(function() { b();cb(); }, BTN1, {repeat:1, edge:"rising"}),
];
} else if (mode=="leftright") {
if (options.drag) throw new Error("Custom drag handler not supported in mode leftright!")
var dx = 0;
Bangle.dragHandler = e=>{
dx += e.dx;
if (!e.b) dx=0;
while (Math.abs(dx)>32) {
if (dx>0) { dx-=32; cb(1) }
else { dx+=32; cb(-1) }
Bangle.buzz(20);
}
};
Bangle.on('drag',Bangle.dragHandler);
Bangle.touchHandler = d => {b();cb();};
Bangle.btnWatches = [
setWatch(function() { b();cb(); }, BTN1, {repeat:1, edge:"rising"}),
];
} else if (mode=="clock") {
Bangle.CLOCK=1;
Bangle.btnWatches = [
setWatch(Bangle.showLauncher, BTN1, {repeat:1,edge:"rising"})
];
} else if (mode=="clockupdown") {
Bangle.CLOCK=1;
Bangle.touchHandler = (d,e) => {
if (e.x < 120) return;
b();cb((e.y > 88) ? 1 : -1);
};
Bangle.btnWatches = [
setWatch(Bangle.showLauncher, BTN1, {repeat:1,edge:"rising"})
];
} else if (mode=="custom") {
if (options.clock) {
Bangle.btnWatches = [
setWatch(Bangle.showLauncher, BTN1, {repeat:1,edge:"rising"})
];
}
} else
throw new Error("Unknown UI mode "+E.toJS(mode));
if (options.clock) Bangle.CLOCK=1;
if (options.touch)
Bangle.touchHandler = options.touch;
if (options.drag) {
Bangle.dragHandler = options.drag;
Bangle.on("drag", Bangle.dragHandler);
}
if (options.swipe) {
Bangle.swipeHandler = options.swipe;
Bangle.on("swipe", Bangle.swipeHandler);
}
if ((options.btn || options.btnRelease) && !Bangle.btnWatches) Bangle.btnWatches = [];
if (options.btn) Bangle.btnWatches.push(setWatch(options.btn.bind(options), BTN1, {repeat:1,edge:"rising"}))
if (options.btnRelease) Bangle.btnWatches.push(setWatch(options.btnRelease.bind(options), BTN1, {repeat:1,edge:"falling"}))
if (options.remove) // handler for removing the UI (intervals/etc)
Bangle.uiRemove = options.remove;
if (options.redraw) // handler for redrawing the UI
Bangle.uiRedraw = options.redraw;
if (options.back) {
var touchHandler = (_,e) => {
if (e.y<36 && e.x<48) {
e.handled = true;
E.stopEventPropagation();
options.back();
}
};
Bangle.on("touch", touchHandler);
// If a touch handler was needed for setUI, add it - but ignore touches if they've already gone to the 'back' handler
if (Bangle.touchHandler) {
var uiTouchHandler = Bangle.touchHandler;
Bangle.touchHandler = (_,e) => {
if (!e.handled) uiTouchHandler(_,e);
};
Bangle.on("touch", Bangle.touchHandler);
}
var btnWatch;
if (Bangle.btnWatches===undefined) // only add back button handler if there's no existing watch on BTN1
btnWatch = setWatch(function() {
btnWatch = undefined;
options.back();
}, BTN1, {edge:"rising"});
WIDGETS = Object.assign({back:{
area:"tl", width:24,
draw:e=>g.reset().setColor("#f00").drawImage(atob("GBiBAAAYAAH/gAf/4A//8B//+D///D///H/P/n+H/n8P/n4f/vwAP/wAP34f/n8P/n+H/n/P/j///D///B//+A//8Af/4AH/gAAYAA=="),e.x,e.y),
remove:(noclear)=>{
if (btnWatch) clearWatch(btnWatch);
Bangle.removeListener("touch", touchHandler);
if (!noclear) g.reset().clearRect({x:WIDGETS.back.x, y:WIDGETS.back.y, w:24,h:24});
delete WIDGETS.back;
if (!noclear) Bangle.drawWidgets();
}
}},global.WIDGETS);
if (redraw) Bangle.drawWidgets();
} else { // If a touch handler was needed for setUI, add it
if (Bangle.touchHandler)
Bangle.on("touch", Bangle.touchHandler);
}
})

View File

@ -1,14 +0,0 @@
{ "id": "setuichange",
"name": "SetUI Proposals preview",
"version":"0.04",
"description": "Try out potential future changes to `Bangle.setUI`. Makes hardware button interaction snappier. Makes it possible to set custom event handlers on any type/mode, not just `\"custom\"`. Please provide feedback - see `Read more...` below.",
"icon": "app.png",
"tags": "",
"type": "bootloader",
"supports" : ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"setuichange.0.boot.js","url":"boot-b1.js", "supports": ["BANGLEJS"]},
{"name":"setuichange.0.boot.js","url":"boot-b2.js", "supports": ["BANGLEJS2"]}
]
}

View File

@ -10,3 +10,4 @@
0.09: Add default HRM value, default altitude value
0.10: Add fastloading
0.11: Add option for ISO 8601 date format
0.12: Add day of week line

View File

@ -1,15 +1,15 @@
# Terminal clock
A clock displayed as a terminal cli.
It can display :
A clock displayed as a terminal CLI.
It can display:
- time
- date
- altitude
- hrm
- heart rate
- motion
- steps
"Power saving" setting control the HR and pressure (altitude) sensors.
If "Off" they will always be on.
If "On" the sensors will be turned on every "Power on interval" minutes for 45 secondes
"Power saving" setting control the HR and pressure (altitude) sensors.
If "Off" they will always be on.
If "On" the sensors will be turned on every "Power on interval" minutes for 45 seconds.

View File

@ -11,7 +11,7 @@
let font6x8At2Size = 18;
let font6x8FirstTextSize = 4;
let font6x8DefaultTextSize = 2;
if (process.env.HWVERSION == 1){
if (process.env.HWVERSION == 1) {
paddingY = 3;
font6x8At4Size = 48;
font6x8At2Size = 27;
@ -32,29 +32,32 @@
this.unlock_precision = 1;
if (this.HRMinConfidence === undefined) this.HRMinConfidence = 50;
if (this.PowerOnInterval === undefined) this.PowerOnInterval = 15;
if (this.powerSave===undefined) this.powerSave = this.powerSaving; // migrate old setting
if (this.powerSave===undefined) this.powerSave = true;
if (this.powerSave === undefined) this.powerSave = this.powerSaving; // migrate old setting
if (this.powerSave === undefined) this.powerSave = true;
["L2", "L3", "L4", "L5", "L6", "L7", "L8", "L9"].forEach(k => {
if (this[k]===undefined){
if(k == "L2") this[k] = "Date";
else if(k == "L3") {
["L2", "L3", "L4", "L5", "L6", "L7", "L8", "L9"].forEach((k) => {
if (this[k] === undefined) {
if (k == "L2") this[k] = "Date";
else if (k == "L3") {
this[k] = "HR";
this.showHRM = true;
}else if(k == "L4") this[k] = "Motion";
else if(k == "L5") this[k] = "Steps";
else if(k == "L6") this[k] = ">";
else this[k] = "Empty";
}
else if (this[k]==="HR") this.showHRM = true;
else if (this[k]==="Alt") this.showAltitude = true && process.env.HWVERSION == 2;
} else if (k == "L4") this[k] = "Motion";
else if (k == "L5") this[k] = "Steps";
else if (k == "L6") this[k] = ">";
else this[k] = "Empty";
} else if (this[k] === "HR") this.showHRM = true;
else if (this[k] === "Alt")
this.showAltitude = true && process.env.HWVERSION == 2;
});
// set the services (HRM, pressure sensor, etc....)
if(!this.powerSave){
if (!this.powerSave) {
turnOnServices();
} else{
this.turnOnInterval = setInterval(turnOnServices, this.PowerOnInterval*60000); // every PowerOnInterval min
} else {
this.turnOnInterval = setInterval(
turnOnServices,
this.PowerOnInterval * 60000,
); // every PowerOnInterval min
}
// start the clock unlocked
unlock();
@ -67,102 +70,117 @@
drawTime(date, curPos);
curPos++;
["L2", "L3", "L4", "L5", "L6", "L7", "L8", "L9"].forEach(line => {
if (this[line]==='Date') drawDate(date, curPos);
else if (this[line]==='HR') drawHRM(curPos);
else if (this[line]==='Motion') drawMotion(curPos);
else if (this[line]==='Alt') drawAltitude(curPos);
else if (this[line]==='Steps') drawStepCount(curPos);
else if (this[line]==='>') drawInput(curPos);
["L2", "L3", "L4", "L5", "L6", "L7", "L8", "L9"].forEach((line) => {
if (this[line] === "Date") drawDate(date, this.isoDate, curPos);
else if (this[line] === "DOW") drawDOW(date, curPos);
else if (this[line] === "HR") drawHRM(curPos);
else if (this[line] === "Motion") drawMotion(curPos);
else if (this[line] === "Alt") drawAltitude(curPos);
else if (this[line] === "Steps") drawStepCount(curPos);
else if (this[line] === ">") drawInput(curPos);
curPos++;
});
},
remove: function() {
if (this.turnOnInterval){
remove: function () {
if (this.turnOnInterval) {
clearInterval(this.turnOnInterval);
delete this.turnOnInterval;
}
if (this.turnOffServiceTimeout){
clearTimeout(this.turnOffServiceTimeout)
delete this.turnOffServiceTimeout
if (this.turnOffServiceTimeout) {
clearTimeout(this.turnOffServiceTimeout);
delete this.turnOffServiceTimeout;
}
turnOffServices();
if (this.onLock) Bangle.removeListener('lock', this.onLock);
if (this.onHRM) Bangle.removeListener('HRM', this.onHRM);
if (this.onPressure) Bangle.removeListener('pressure', this.onPressure);
}
if (this.onLock) Bangle.removeListener("lock", this.onLock);
if (this.onHRM) Bangle.removeListener("HRM", this.onHRM);
if (this.onPressure) Bangle.removeListener("pressure", this.onPressure);
},
});
/* ----------------------------
/* ----------------------------
Draw related of specific lines
-------------------------------- */
let drawLine = function(line, pos){
if(pos == 1)
let drawLine = function (line, pos) {
if (pos == 1) {
g.setFont("6x8", font6x8FirstTextSize);
else
} else {
g.setFont("6x8", font6x8DefaultTextSize);
}
let yPos = Bangle.appRect.y +
paddingY * (pos - 1) +
font6x8At4Size * Math.min(1, pos-1) +
font6x8At2Size * Math.max(0, pos-2);
let yPos =
Bangle.appRect.y +
paddingY * (pos - 1) +
font6x8At4Size * Math.min(1, pos - 1) +
font6x8At2Size * Math.max(0, pos - 2);
g.drawString(line, 5, yPos, true);
};
let drawTime = function(now, pos){
let drawTime = function (now, pos) {
let h = now.getHours();
let m = now.getMinutes();
let time = ">" + (""+h).substr(-2) + ":" + ("0"+m).substr(-2);
let time = ">" + ("" + h).substr(-2) + ":" + ("0" + m).substr(-2);
drawLine(time, pos);
};
let drawDate = function(now, pos) {
let drawDate = function (now, pos) {
let date;
if (clock.isoDate) {
let year = now.getFullYear();
let month = now.getMonth() + 1; // Months are 0-11
let month = now.getMonth() + 1; // Months are 0-11
let day = now.getDate();
date = ">" + year + "-" + month + "-" + day;
} else {
let dow = locale.dow(now, 1);
date = locale.date(now, 1).substr(0,6) + locale.date(now, 1).substr(-2);
date = locale.date(now, 1).substr(0, 6); // day and month e.g. 01/02/ from 01/02/2003
date += locale.date(now, 1).substr(-2); // short year e.g. 03 from 01/02/2003
date = ">" + dow + " " + date;
}
drawLine(date, pos);
};
let drawInput = function(pos){
let drawInput = function (pos) {
drawLine(">", pos);
};
let drawStepCount = function(pos){
let drawDOW = function (now, pos) {
drawLine(">" + locale.dow(now, 0), pos);
};
let drawStepCount = function (pos) {
let health = Bangle.getHealthStatus("day");
let steps_formated = ">Steps: " + health.steps;
drawLine(steps_formated, pos);
};
let drawHRM = function(pos){
if(heartRate != 0)
let drawHRM = function (pos) {
if (heartRate != 0) {
drawLine(">HR: " + parseInt(heartRate), pos);
else
} else {
drawLine(
">HR: " + parseInt(Math.round(Bangle.getHealthStatus().bpm||Bangle.getHealthStatus("last").bpm)),
pos);
">HR: " +
parseInt(
Math.round(
Bangle.getHealthStatus().bpm ||
Bangle.getHealthStatus("last").bpm,
),
),
pos,
);
}
};
let drawAltitude = function(pos){
if(altitude > 0)
let drawAltitude = function (pos) {
if (altitude > 0) {
drawLine(">Alt: " + altitude.toFixed(1) + "m", pos);
else
} else {
drawLine(">Alt: unknown", pos);
}
};
let drawMotion = function(pos){
let health = Bangle.getHealthStatus('last');
let drawMotion = function (pos) {
let health = Bangle.getHealthStatus("last");
let steps_formated = ">Motion: " + parseInt(health.movement);
drawLine(steps_formated, pos);
};
@ -171,72 +189,70 @@
Services functions (HRM, pressure, etc...)
-------------------------------------------------- */
let turnOnServices = function(){
if(clock.showHRM){
let turnOnServices = function () {
if (clock.showHRM) {
Bangle.setHRMPower(true, "terminalclock");
}
if(clock.showAltitude){
if (clock.showAltitude) {
Bangle.setBarometerPower(true, "terminalclock");
}
if(clock.powerSave){
if(clock.turnOffServiceTimeout) clearTimeout(clock.turnOffServiceTimeout);
if (clock.powerSave) {
if (clock.turnOffServiceTimeout)
clearTimeout(clock.turnOffServiceTimeout);
clock.turnOffServiceTimeout = setTimeout(function () {
turnOffServices();
}, 45000);
}
};
let turnOffServices = function(){
if(clock.showHRM){
let turnOffServices = function () {
if (clock.showHRM) {
Bangle.setHRMPower(false, "terminalclock");
}
if(clock.showAltitude){
if (clock.showAltitude) {
Bangle.setBarometerPower(false, "terminalclock");
}
};
// set the lock and unlock actions
clock.onLock = lock_event => {
clock.onLock = (lock_event) => {
if (lock_event) lock();
else unlock();
};
Bangle.on("lock", clock.onLock);
clock.onHRM = hrmInfo => {
if(hrmInfo.confidence >= clock.HRMinConfidence)
heartRate = hrmInfo.bpm;
clock.onHRM = (hrmInfo) => {
if (hrmInfo.confidence >= clock.HRMinConfidence) heartRate = hrmInfo.bpm;
};
Bangle.on('HRM', clock.onHRM);
Bangle.on("HRM", clock.onHRM);
const MEDIANLENGTH = 20; // technical
let avr = [], median; // technical
clock.onPressure = pressureInfo => {
while (avr.length>MEDIANLENGTH) avr.pop();
let avr = [],
median; // technical
clock.onPressure = (pressureInfo) => {
while (avr.length > MEDIANLENGTH) avr.pop();
avr.unshift(pressureInfo.altitude);
median = avr.slice().sort();
if (median.length>10) {
let mid = median.length>>1;
altitude = E.sum(median.slice(mid-4,mid+5)) / 9;
}
else
altitude = pressureInfo.altitude;
if (median.length > 10) {
let mid = median.length >> 1;
altitude = E.sum(median.slice(mid - 4, mid + 5)) / 9;
} else altitude = pressureInfo.altitude;
};
Bangle.on('pressure', clock.onPressure);
Bangle.on("pressure", clock.onPressure);
/* -------------------------------------------------
Clock related functions but not in the ClockFace module
---------------------------------------------------- */
let unlock = function(){
if(clock.powerSave){
let unlock = function () {
if (clock.powerSave) {
turnOnServices();
}
clock.precision = clock.unlock_precision;
clock.tick();
};
let lock = function(){
let lock = function () {
clock.precision = clock.lock_precision;
clock.tick();
};

View File

@ -1,25 +1,23 @@
{
{
"id": "terminalclock",
"name": "Terminal Clock",
"shortName":"Terminal Clock",
"description": "A terminal cli like clock displaying multiple sensor data",
"version":"0.11",
"shortName": "Terminal Clock",
"description": "A terminal CLI like clock displaying configurable, multiple sensor data",
"version": "0.12",
"icon": "app.png",
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS", "BANGLEJS2"],
"supports": [
"BANGLEJS",
"BANGLEJS2"
],
"allow_emulator": false,
"readme": "README.md",
"storage": [
{"name": "terminalclock.app.js","url": "app.js"},
{"name": "terminalclock.settings.js","url": "settings.js"},
{"name": "terminalclock.img","url": "app-icon.js","evaluate": true}
{ "name": "terminalclock.app.js", "url": "app.js" },
{ "name": "terminalclock.settings.js", "url": "settings.js" },
{ "name": "terminalclock.img", "url": "app-icon.js", "evaluate": true }
],
"data": [
{"name": "terminalclock.json"}
],
"screenshots": [
{"url": "screenshot1.png"},
{"url": "screenshot2.png"}
]
"data": [{ "name": "terminalclock.json" }],
"screenshots": [{ "url": "screenshot1.png" }, { "url": "screenshot2.png" }]
}

View File

@ -1,23 +1,27 @@
(function(back) {
(function (back) {
var FILE = "terminalclock.json";
// Load settings
var settings = Object.assign({
// TerminalClock specific
isoDate: false,
HRMinConfidence: 50,
PowerOnInterval: 15,
L2: 'Date',
L3: 'HR',
L4: 'Motion',
L5: 'Steps',
L6: '>',
L7: 'Empty',
L8: 'Empty',
L9: 'Empty',
}, require('Storage').readJSON(FILE, true) || {});
var settings = Object.assign(
{
// TerminalClock specific
isoDate: false,
HRMinConfidence: 50,
PowerOnInterval: 15,
L2: "Date",
L3: "HR",
L4: "Motion",
L5: "Steps",
L6: ">",
L7: "Empty",
L8: "Empty",
L9: "Empty",
},
require("Storage").readJSON(FILE, true) || {},
);
// ClockFace lib: migrate "don't load widgets" to "hide widgets"
if (!("hideWidgets" in settings)) {
if (("loadWidgets" in settings) && !settings.loadWidgets) settings.hideWidgets = 1;
if ("loadWidgets" in settings && !settings.loadWidgets)
settings.hideWidgets = 1;
else settings.hideWidgets = 0;
}
delete settings.loadWidgets;
@ -29,20 +33,30 @@
delete settings.powerSaving;
function writeSettings() {
require('Storage').writeJSON(FILE, settings);
require("Storage").writeJSON(FILE, settings);
}
if(process.env.HWVERSION == 2) {
var lineType = ['Date', 'HR', 'Motion', 'Alt', 'Steps', '>', 'Empty'];
} else{
var lineType = ['Date', 'HR', 'Motion', 'Steps', '>', 'Empty'];
if (process.env.HWVERSION == 2) {
var lineType = [
"Date",
"DOW",
"HR",
"Motion",
"Alt",
"Steps",
">",
"Empty",
];
} else {
var lineType = ["Date", "DOW", "HR", "Motion", "Steps", ">", "Empty"];
}
function getLineChooser(lineID){
function getLineChooser(lineID) {
return {
value: lineType.indexOf(settings[lineID]),
min: 0, max: lineType.length-1,
format: v => lineType[v],
onchange: v => {
min: 0,
max: lineType.length - 1,
format: (v) => lineType[v],
onchange: (v) => {
settings[lineID] = lineType[v];
writeSettings();
},
@ -50,35 +64,38 @@
}
var lineMenu = {
'< Back': function() { E.showMenu(getMainMenu());},
'Line 2': getLineChooser('L2'),
'Line 3': getLineChooser('L3'),
'Line 4': getLineChooser('L4'),
'Line 5': getLineChooser('L5'),
'Line 6': getLineChooser('L6'),
'Line 7': getLineChooser('L7'),
'Line 8': getLineChooser('L8'),
'Line 9': getLineChooser('L9'),
"< Back": function () {
E.showMenu(getMainMenu());
},
"Line 2": getLineChooser("L2"),
"Line 3": getLineChooser("L3"),
"Line 4": getLineChooser("L4"),
"Line 5": getLineChooser("L5"),
"Line 6": getLineChooser("L6"),
"Line 7": getLineChooser("L7"),
"Line 8": getLineChooser("L8"),
"Line 9": getLineChooser("L9"),
};
function getMainMenu(){
function getMainMenu() {
var mainMenu = {
"" : { "title" : "Terminal Clock" },
"< Back" : () => back(),
'HR confidence': {
"": { title: "Terminal Clock" },
"< Back": () => back(),
"HR confidence": {
value: settings.HRMinConfidence,
min: 0, max: 100,
onchange: v => {
min: 0,
max: 100,
onchange: (v) => {
settings.HRMinConfidence = v;
writeSettings();
}
},
},
},
"ISO date": {
value: !!settings.isoDate,
onchange: v => {
onchange: (v) => {
settings.isoDate = v;
writeSettings();
}
},
},
};
const save = (key, v) => {
@ -89,19 +106,22 @@
hideWidgets: settings.hideWidgets,
powerSave: settings.powerSave,
});
if(settings.powerSave){
mainMenu['Power on interval'] = {
if (settings.powerSave) {
mainMenu["Power on interval"] = {
value: settings.PowerOnInterval,
min: 3, max: 60,
onchange: v => {
min: 3,
max: 60,
onchange: (v) => {
settings.PowerOnInterval = v;
writeSettings();
},
format: x => x + "m"
format: (x) => x + "m",
};
}
mainMenu['Lines'] = function() { E.showMenu(lineMenu);};
mainMenu["Lines"] = function () {
E.showMenu(lineMenu);
};
return mainMenu;
}

View File

@ -1 +1 @@
0.7: New Text Reader App - Initial version with file browsing and reading capabilities
0.01: New Text Reader App - Initial version with file browsing and reading capabilities

View File

@ -2,7 +2,7 @@
"id": "textreader",
"name": "Text Reader",
"shortName": "TextReader",
"version": "0.7",
"version": "0.01",
"description": "Read text files from your Bangle.js filesystem",
"type": "app",
"tags": "tool,files,reader",
@ -27,4 +27,4 @@
"evaluate": true
}
]
}
}

View File

@ -1,4 +1 @@
0.12: First deployment!
0.13: Refinment of themes and more themes!
0.14: Fixed overlapping theme colors
0.15: Better LoTR theme
0.01: First deployment!

View File

@ -2,7 +2,7 @@
"id": "themes",
"name": "Themes",
"shortName": "Themes",
"version": "0.15",
"version": "0.01",
"description": "Color palettes at your disposal",
"type": "app",
"tags": "tool",
@ -44,4 +44,4 @@
"url": "themes.json"
}
]
}
}

View File

@ -7,3 +7,4 @@
0.07: Fix problem with "Bangle.CLOCK": github.com/espruino/BangleApps/issues/1437
0.08: Redraw widgets only once per minute
0.09: Workaround for issue in 2v14 firmware (fix #1959)
0.10: Ensure that we only display seconds if loaded when unlocked (fix #3690)

View File

@ -13,7 +13,7 @@ function padNum(n, l) {
var rects = {};
var rectsToClear = {};
var commands = [];
var showSeconds = true;
var showSeconds = !Bangle.isLocked();
function pushCommand(command) {
var hash = E.CRC32(E.toJS(arguments));

View File

@ -1,7 +1,7 @@
{
"id": "vectorclock",
"name": "Vector Clock",
"version": "0.09",
"version": "0.10",
"description": "A digital clock that uses the built-in vector font.",
"icon": "app.png",
"type": "clock",

View File

@ -53,8 +53,10 @@ When you first load QuickWeather, it will take you through the setup process. Yo
* Expiration timespan can be set after which the local weather data is considered as invalid
* Widget can be hidden
* To change the units for wind speed, you can install the [`Languages` app](https://banglejs.com/apps/?id=locale) which
allows you to choose the units used for speed/distance/temperature and so on.
## Controls
BTN2: opens the launcher (Bangle.js 1)
BTN: opens the launcher (Bangle.js 2)
* BTN2: opens the launcher (Bangle.js 1)
* BTN: opens the launcher (Bangle.js 2)

View File

@ -7,7 +7,7 @@
"screenshots": [{"url":"screenshot.png"}],
"tags": "widget,outdoors,clkinfo",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "readme.md",
"readme": "README.md",
"storage": [
{"name":"weather.app.js","url":"app.js"},
{"name":"weather.wid.js","url":"widget.js"},

View File

@ -16,6 +16,11 @@ See the [Bangle.js Gadgetbridge documentation](https://www.espruino.com/Gadgetbr
![Screenshot2](screens/screen2.png)
## Wind speed units
If you want to change the units for wind speed, you can install the [`Languages` app](https://banglejs.com/apps/?id=locale) which
allows you to choose the units used for speed/distance/temperature and so on.
## Creator
James Gough

@ -1 +1 @@
Subproject commit eb1a20ce892e28e89686f022cbda7cd66896d23b
Subproject commit a4e8ee137e720ba48d3ef758b5b2d12a8845c73e