Merge branch 'master' into gpsmagdir

pull/2581/head
Erik Andresen 2023-02-28 19:24:14 +01:00
commit d2e0bc8da0
45 changed files with 989 additions and 145 deletions

View File

@ -10,6 +10,12 @@ Standard # of drag handlers: 0-10 (Default: 0, must be changed for backswipe to
Standard # of handlers settings are used to fine tune when backswipe should trigger the back function. E.g. when using a keyboard that works on drags, we don't want the backswipe to trigger when we just wanted to select a letter. This might not be able to cover all cases however.
To get an indication for standard # of handlers `Bangle["#onswipe"]` and `Bangle["#ondrag"]` can be entered in the [Espruino Web IDE](https://www.espruino.com/ide) console field. They return `undefined` if no handler is active, a function if one is active, or a list of functions if multiple are active. Calling this on the clock app is a good start.
## TODO
- Possibly add option to tweak standard # of handlers on per app basis.
## Creator
Kedlub

View File

@ -0,0 +1,2 @@
0.01: Initial fork from messages_light
0.02: Fix touch/drag/swipe handlers not being restored correctly if a message is removed

View File

@ -0,0 +1,24 @@
# Messages overlay app
This app handles the display of messages and message notifications as an overlay pop up.
It is a GUI replacement for the `messages` apps.
Messages are ephemeral and not stored on the Bangle.
## Usage
Close app by tapping the X and scroll by swiping. The border of the pop up changes color if the Bangle is locked. The color depends on your currently active theme.
## Firmware hint
Current stable firmware draws incorrect colors for emojis. Nightly firmware builds correct this.
## Low memory mode
If free memory is below 2000 blocks, the overlay automatically only uses 1 bit depth. Default uses roundabout 1300 blocks, while low memory mode uses about 600.
## Creator
[halemmerich](https://github.com/halemmerich)
Forked from messages_light by Rarder44

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,7 @@
//override require to filter require("message")
global.require_real=global.require;
global.require = (_require => file => {
if (file==="messages") file = "messagesoverlay";
return _require(file);
})(require);

486
apps/messagesoverlay/lib.js Normal file
View File

@ -0,0 +1,486 @@
/* MESSAGES is a list of:
{id:int,
src,
title,
subject,
body,
sender,
tel:string,
new:true // not read yet
}
*/
const ovrx = 10;
const ovry = 10;
const ovrw = g.getWidth()-2*ovrx;
const ovrh = g.getHeight()-2*ovry;
let _g = g;
let lockListener;
let quiet;
let LOG = function() {
//print.apply(null, arguments);
};
let isQuiet = function(){
if (quiet == undefined) quiet = (require('Storage').readJSON('setting.json', 1) || {}).quiet;
return quiet;
};
let settings = {
fontSmall:"6x8",
fontMedium:"Vector:14",
fontBig:"Vector:20",
fontLarge:"Vector:30",
};
let eventQueue = [];
let callInProgress = false;
let show = function(ovr){
let img = ovr;
if (ovr.getBPP() == 1) {
img = ovr.asImage();
img.palette = new Uint16Array([_g.theme.fg,_g.theme.bg]);
}
Bangle.setLCDOverlay(img, ovrx, ovry);
};
let manageEvent = function(ovr, event) {
event.new = true;
LOG("manageEvent");
if (event.id == "call") {
showCall(ovr, event);
return;
}
switch (event.t) {
case "add":
eventQueue.unshift(event);
if (!callInProgress)
showMessage(ovr, event);
break;
case "modify":
let find = false;
eventQueue.forEach(element => {
if (element.id == event.id) {
find = true;
Object.assign(element, event);
}
});
if (!find)
eventQueue.unshift(event);
if (!callInProgress)
showMessage(ovr, event);
break;
case "remove":
if (eventQueue.length == 0 && !callInProgress)
next(ovr);
if (!callInProgress && eventQueue[0] !== undefined && eventQueue[0].id == event.id)
next(ovr);
else {
eventQueue.length = 0; // empty existing queue
eventQueue.forEach(element => {
if (element.id != event.id)
neweventQueue.push(element);
});
}
break;
case "musicstate":
case "musicinfo":
break;
}
};
let roundedRect = function(ovr, x,y,w,h,filled){
var poly = [
x,y+4,
x+4,y,
x+w-5,y,
x+w-1,y+4,
x+w-1,y+h-5,
x+w-5,y+h-1,
x+4,y+h-1,
x,y+h-5,
x,y+4
];
ovr.drawPoly(poly,true);
if (filled) ovr.fillPoly(poly,true);
};
let drawScreen = function(ovr, title, titleFont, src, iconcolor, icon){
ovr.setBgColor(ovr.theme.bg2);
ovr.clearRect(2,2,ovr.getWidth()-3,37);
ovr.setColor(ovr.theme.fg2);
ovr.setFont(settings.fontSmall);
ovr.setFontAlign(0,-1);
let textCenter = (ovr.getWidth()+35-26)/2;
if (src) {
let shortened = src;
while (ovr.stringWidth(shortened) > ovr.getWidth()-80) shortened = shortened.substring(0,shortened.length-2);
if (shortened.length != src.length) shortened += "...";
ovr.drawString(shortened, textCenter, 2);
}
ovr.setFontAlign(0,0);
ovr.setFont(titleFont);
if (title) ovr.drawString(title, textCenter, 38/2 + 5);
ovr.setColor(ovr.theme.fg2);
ovr.setFont(settings.fontMedium);
roundedRect(ovr, ovr.getWidth()-26,5,22,30,false);
ovr.setFont("Vector:16");
ovr.drawString("X",ovr.getWidth()-14,21);
ovr.setColor("#888");
roundedRect(ovr, 5,5,30,30,true);
ovr.setColor(ovr.getBPP() != 1 ? iconcolor : ovr.theme.bg2);
ovr.drawImage(icon,8,8);
};
let showMessage = function(ovr, msg) {
LOG("showMessage");
ovr.setBgColor(ovr.theme.bg);
if (typeof msg.CanscrollDown === "undefined")
msg.CanscrollDown = false;
if (typeof msg.CanscrollUp === "undefined")
msg.CanscrollUp = false;
// Normal text message display
let title = msg.title,
titleFont = settings.fontLarge,
lines;
if (title) {
let w = ovr.getWidth() - 35 - 26;
if (ovr.setFont(titleFont).stringWidth(title) > w)
titleFont = settings.fontMedium;
if (ovr.setFont(titleFont).stringWidth(title) > w) {
lines = ovr.wrapString(title, w);
title = (lines.length > 2) ? lines.slice(0, 2).join("\n") + "..." : lines.join("\n");
}
}
drawScreen(ovr, title, titleFont, msg.src || /*LANG*/ "Message", require("messageicons").getColor(msg), require("messageicons").getImage(msg));
if (!isQuiet() && msg.new) {
msg.new = false;
Bangle.buzz();
}
drawMessage(ovr, msg);
};
let drawBorder = function(ovr) {
if (Bangle.isLocked())
ovr.setColor(ovr.theme.fgH);
else
ovr.setColor(ovr.theme.fg);
ovr.drawRect(0,0,ovr.getWidth()-1,ovr.getHeight()-1);
ovr.drawRect(1,1,ovr.getWidth()-2,ovr.getHeight()-2);
show(ovr);
if (!isQuiet()) Bangle.setLCDPower(1);
};
let showCall = function(ovr, msg) {
LOG("showCall");
LOG(msg);
if (msg.t == "remove") {
LOG("hide call screen");
next(ovr); //dont shift
return;
}
callInProgress = true;
let title = msg.title,
titleFont = settings.fontLarge,
lines;
if (title) {
let w = ovr.getWidth() - 35 -26;
if (ovr.setFont(titleFont).stringWidth(title) > w)
titleFont = settings.fontMedium;
if (ovr.setFont(titleFont).stringWidth(title) > w) {
lines = ovr.wrapString(title, w);
title = (lines.length > 2) ? lines.slice(0, 2).join("\n") + "..." : lines.join("\n");
}
}
drawScreen(ovr, title, titleFont, msg.src || /*LANG*/ "Message", require("messageicons").getColor(msg), require("messageicons").getImage(msg));
stopCallBuzz();
if (!isQuiet()) {
if (msg.new) {
msg.new = false;
if (callBuzzTimer) clearInterval(callBuzzTimer);
callBuzzTimer = setInterval(function() {
Bangle.buzz(500);
}, 1000);
Bangle.buzz(500);
}
}
drawMessage(ovr, msg);
};
let next = function(ovr) {
LOG("next");
stopCallBuzz();
if (!callInProgress)
eventQueue.shift();
callInProgress = false;
if (eventQueue.length == 0) {
LOG("no element in queue - closing");
cleanup();
return;
}
showMessage(ovr, eventQueue[0]);
};
let showMapMessage = function(ovr, msg) {
ovr.clearRect(2,2,ovr.getWidth()-3,ovr.getHeight()-3);
drawMessage(ovr, {
body: "Not implemented!"
});
};
let callBuzzTimer = null;
let stopCallBuzz = function() {
if (callBuzzTimer) {
clearInterval(callBuzzTimer);
callBuzzTimer = undefined;
}
};
let drawTriangleUp = function(ovr) {
ovr.reset();
ovr.fillPoly([ovr.getWidth()-9, 46,ovr.getWidth()-14, 56,ovr.getWidth()-4, 56]);
};
let drawTriangleDown = function(ovr) {
ovr.reset();
ovr.fillPoly([ovr.getWidth()-9, ovr.getHeight()-6, ovr.getWidth()-14, ovr.getHeight()-16, ovr.getWidth()-4, ovr.getHeight()-16]);
};
let scrollUp = function(ovr) {
msg = eventQueue[0];
LOG("up", msg);
if (typeof msg.FirstLine === "undefined")
msg.FirstLine = 0;
if (typeof msg.CanscrollUp === "undefined")
msg.CanscrollUp = false;
if (!msg.CanscrollUp) return;
msg.FirstLine = msg.FirstLine > 0 ? msg.FirstLine - 1 : 0;
drawMessage(ovr, msg);
};
let scrollDown = function(ovr) {
msg = eventQueue[0];
LOG("down", msg);
if (typeof msg.FirstLine === "undefined")
msg.FirstLine = 0;
if (typeof msg.CanscrollDown === "undefined")
msg.CanscrollDown = false;
if (!msg.CanscrollDown) return;
msg.FirstLine = msg.FirstLine + 1;
drawMessage(ovr, msg);
};
let drawMessage = function(ovr, msg) {
let MyWrapString = function(str, maxWidth) {
str = str.replace("\r\n", "\n").replace("\r", "\n");
return ovr.wrapString(str, maxWidth);
};
if (typeof msg.FirstLine === "undefined") msg.FirstLine = 0;
let bodyFont = typeof msg.bodyFont === "undefined" ? settings.fontMedium : msg.bodyFont;
let Padding = 3;
if (typeof msg.lines === "undefined") {
ovr.setFont(bodyFont);
msg.lines = MyWrapString(msg.body, ovr.getWidth() - (Padding * 2));
if (msg.lines.length <= 2) {
bodyFont = ovr.getFonts().includes("Vector") ? "Vector:20" : "6x8:3";
ovr.setFont(bodyFont);
msg.lines = MyWrapString(msg.body, ovr.getWidth() - (Padding * 2));
msg.bodyFont = bodyFont;
}
}
let NumLines = 7;
let linesToPrint = (msg.lines.length > NumLines) ? msg.lines.slice(msg.FirstLine, msg.FirstLine + NumLines) : msg.lines;
let yText = 40;
ovr.setBgColor(ovr.theme.bg);
ovr.setColor(ovr.theme.fg);
ovr.clearRect(2, yText, ovrw-3, ovrh-3);
let xText = Padding;
yText += Padding;
ovr.setFont(bodyFont);
let HText = ovr.getFontHeight();
yText = ((ovrh - yText) / 2) - (linesToPrint.length * HText / 2) + yText;
if (linesToPrint.length <= 3) {
ovr.setFontAlign(0, -1);
xText = ovr.getWidth() / 2;
} else
ovr.setFontAlign(-1, -1);
linesToPrint.forEach((line, i) => {
ovr.drawString(line, xText, yText + HText * i);
});
if (msg.FirstLine != 0) {
msg.CanscrollUp = true;
drawTriangleUp(ovr);
} else
msg.CanscrollUp = false;
if (msg.FirstLine + linesToPrint.length < msg.lines.length) {
msg.CanscrollDown = true;
drawTriangleDown(ovr);
} else
msg.CanscrollDown = false;
show(ovr);
if (!isQuiet()) Bangle.setLCDPower(1);
};
let getSwipeHandler = function(ovr){
return (lr, ud) => {
if (ud == 1) {
scrollUp(ovr);
} else if (ud == -1){
scrollDown(ovr);
}
};
};
let getTouchHandler = function(ovr){
return (_, xy) => {
if (xy.y < ovry + 40){
next(ovr);
}
};
};
let restoreHandler = function(event){
LOG("Restore", event, backup[event]);
Bangle.removeAllListeners(event);
Bangle["#on" + event]=backup[event];
backup[event] = undefined;
};
let backupHandler = function(event){
if (backupDone) return; // do not backup, overlay is already up
backup[event] = Bangle["#on" + event];
LOG("Backed up", backup[event]);
Bangle.removeAllListeners(event);
};
let cleanup = function(){
if (lockListener) {
Bangle.removeListener("lock", lockListener);
lockListener = undefined;
}
restoreHandler("touch");
restoreHandler("swipe");
restoreHandler("drag");
Bangle.setLCDOverlay();
backupDone = false;
ovr = undefined;
quiet = undefined;
};
let backup = {};
let backupDone = false;
let main = function(ovr, event) {
LOG("Main", event, settings);
if (!lockListener) {
lockListener = function (){
drawBorder(ovr);
};
Bangle.on('lock', lockListener);
}
backupHandler("touch");
backupHandler("swipe");
backupHandler("drag");
if (!backupDone){
Bangle.on('touch', getTouchHandler(ovr));
Bangle.on('swipe', getSwipeHandler(ovr));
}
backupDone=true;
if (event !== undefined){
drawBorder(ovr);
manageEvent(ovr, event);
} else {
LOG("No event given");
cleanup();
}
};
let ovr;
exports.pushMessage = function(event) {
if( event.id=="music") return require_real("messages").pushMessage(event);
bpp = 4;
if (process.memory().free < 2000) bpp = 1;
if (!ovr) {
ovr = Graphics.createArrayBuffer(ovrw, ovrh, bpp, {
msb: true
});
} else {
ovr.clear();
}
g = ovr;
if (bpp == 4)
ovr.theme = g.theme;
else
ovr.theme = { fg:0, bg:1, fg2:1, bg2:0, fgH:1, bgH:0 };
main(ovr, event);
g = _g;
};
//Call original message library
exports.clearAll = function() { return require_real("messages").clearAll();};
exports.getMessages = function() { return require_real("messages").getMessages();};
exports.status = function() { return require_real("messages").status();};
exports.buzz = function() { return require_real("messages").buzz(msgSrc);};
exports.stopBuzz = function() { return require_real("messages").stopBuzz();};

View File

@ -0,0 +1,17 @@
{
"id": "messagesoverlay",
"name": "Messages Overlay",
"version": "0.02",
"description": "An overlay based implementation of a messages UI (display notifications from iOS and Gadgetbridge/Android)",
"icon": "app.png",
"type": "bootloader",
"tags": "tool,system",
"supports": ["BANGLEJS2"],
"dependencies" : { "messageicons":"module","messages":"app" },
"readme": "README.md",
"storage": [
{"name":"messagesoverlay","url":"lib.js"},
{"name":"messagesoverlay.boot.js","url":"boot.js"}
],
"screenshots": [{"url":"screen_call.png"} ,{"url":"screen_message.png"} ]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -7,3 +7,5 @@
0.06: Allow logging of some things using power
Add widget for live monitoring of power use
0.07: Convert Yes/No On/Off in settings to checkboxes
0.08: Fix the wrapping of intervals/timeouts with parameters
Fix the widget drawing if widgets are hidden and Bangle.setLCDBrightness is called

View File

@ -19,6 +19,14 @@ You can switch on logging in the options to diagnose unexpected power use. Curre
Do not use trace logging for extended time, it uses a lot of storage and can fill up the flash quite quick.
### TODO
* Wrap functions given as strings to setTimeout/setInterval
* Handle eval in setTimeout/setInterval
* Track functions executed as event handlers
* Track buzzer
* Modify browser interface to estimate power use like widget does
## Internals
Battery calibration offset is set by writing `batFullVoltage` in setting.json

View File

@ -77,14 +77,14 @@
let functions = {};
let wrapDeferred = ((o,t) => (a) => {
if (a == eval){
if (a == eval || typeof a == "string") {
return o.apply(this, arguments);
} else {
let wrapped = a;
if (!a.__wrapped){
wrapped = ()=>{
let start = Date.now();
let result = a.apply(undefined, arguments.slice(1));
let result = a.apply(undefined, arguments.slice(2)); // function arguments for deferred calls start at index 2, first is function, second is time
let end = Date.now()-start;
let f = a.toString().substring(0,100);
if (settings.logDetails) logDeferred(t, end, f);

View File

@ -2,7 +2,7 @@
"id": "powermanager",
"name": "Power Manager",
"shortName": "Power Manager",
"version": "0.07",
"version": "0.08",
"description": "Allow configuration of warnings and thresholds for battery charging and display.",
"icon": "app.png",
"type": "bootloader",
@ -20,6 +20,7 @@
"data": [
{"name":"powermanager.hw.json"},
{"name":"powermanager.def.json"},
{"name":"powermanager.json"},
{"name":"powermanager.log"}
]
}

View File

@ -24,7 +24,7 @@ currently-running apps */
let brightnessSetting = settings.brightness || 1;
Bangle.setLCDBrightness = ((o) => (a) => {
brightnessSetting = a;
draw(WIDGETS.powermanager);
WIDGETS.powermanager.draw(WIDGETS.powermanager);
return o(a);
})(Bangle.setLCDBrightness);

View File

@ -13,5 +13,7 @@
0.12: Fix for recorder not stopping at end of run. Bug introduced in 0.11
0.13: Revert #1578 (stop duplicate entries) as with 2v12 menus it causes other boxes to be wiped (fix #1643)
0.14: Fix Bangle.js 1 issue where after the 'overwrite track' menu, the start/stop button stopped working
0.15: Diverge from the standard "Run" app. Swipe to intensity interface a la Karvonnen (curtesy of FTeacher at https://github.com/f-teacher)
0.15: Diverge from the standard "Run" app. Swipe to intensity interface a la Karvonen (curtesy of FTeacher at https://github.com/f-teacher)
Keep run state between runs (allowing you to exit and restart the app)
0.16: Don't clear zone 2b indicator segment when updating HRM reading.
Write to correct settings file, fixing settings not working.

View File

@ -67,3 +67,10 @@ app loader, the module is automatically included in the app's source. However
when developing via the IDE the module won't get pulled in by default.
There are some options to fix this easily - please check out the [modules README.md file](https://github.com/espruino/BangleApps/blob/master/modules/README.md)
## Contributors (Run and Run+)
gfwilliams
hughbarney
GrandVizierOlaf
BartS23
f-teacher
thyttan

View File

@ -2,7 +2,7 @@
let wu = require("widget_utils");
let runInterval;
let karvonnenActive = false;
let karvonenActive = false;
// Run interface wrapped in a function
let ExStats = require("exstats");
let B2 = process.env.HWVERSION===2;
@ -63,7 +63,6 @@ function setStatus(running) {
function onStartStop() {
let running = !exs.state.active;
let prepPromises = [];
// start/stop recording
// Do this first in case recorder needs to prompt for
// an overwrite before we start tracking exstats
@ -155,7 +154,7 @@ Bangle.on("GPS", function(fix) {
}
});
// run() function used to switch between traditional run UI and karvonnen UI
// run() function used to switch between traditional run UI and karvonen UI
function run() {
wu.show();
layout.lazy = false;
@ -165,35 +164,35 @@ function run() {
if (!runInterval){
runInterval = setInterval(function() {
layout.clock.label = locale.time(new Date(),1);
if (!isMenuDisplayed && !karvonnenActive) layout.render();
if (!isMenuDisplayed && !karvonenActive) layout.render();
}, 1000);
}
}
run();
///////////////////////////////////////////////
// Karvonnen
// Karvonen
///////////////////////////////////////////////
function stopRunUI() {
// stop updating and drawing the traditional run app UI
clearInterval(runInterval);
runInterval = undefined;
karvonnenActive = true;
karvonenActive = true;
}
function stopKarvonnenUI() {
function stopKarvonenUI() {
g.reset().clear();
clearInterval(karvonnenInterval);
karvonnenInterval = undefined;
karvonnenActive = false;
clearInterval(karvonenInterval);
karvonenInterval = undefined;
karvonenActive = false;
}
let karvonnenInterval;
let karvonenInterval;
// Define the function to go back and forth between the different UI's
function swipeHandler(LR,_) {
if (LR==-1 && karvonnenActive && !isMenuDisplayed) {stopKarvonnenUI(); run();}
if (LR==1 && !karvonnenActive && !isMenuDisplayed) {stopRunUI(); karvonnenInterval = eval(require("Storage").read("runplus_karvonnen"))(settings.HRM, exs.stats.bpm);}
if (LR==-1 && karvonenActive && !isMenuDisplayed) {stopKarvonenUI(); run();}
if (LR==1 && !karvonenActive && !isMenuDisplayed) {stopRunUI(); karvonenInterval = eval(require("Storage").read("runplus_karvonen"))(settings.HRM, exs.stats.bpm);}
}
// Listen for swipes with the swipeHandler
Bangle.on("swipe", swipeHandler);

View File

@ -1,6 +1,6 @@
(function karvonnen(hrmSettings, exsHrmStats) {
(function karvonen(hrmSettings, exsHrmStats) {
//This app is an extra feature implementation for the Run.app of the bangle.js. It's called run+
//The calculation of the Heart Rate Zones is based on the Karvonnen method. It requires to know maximum and minimum heart rates. More precise calculation methods require a lab.
//The calculation of the Heart Rate Zones is based on the Karvonen method. It requires to know maximum and minimum heart rates. More precise calculation methods require a lab.
//Other methods are even more approximative.
let wu = require("widget_utils");
wu.hide();
@ -56,7 +56,7 @@
let hr = exsHrmStats.getValue();
let hr1 = hr;
// These letiables display next and previous HR zone.
//get the hrzones right. The calculation of the Heart rate zones here is based on the Karvonnen method
//get the hrzones right. The calculation of the Heart rate zones here is based on the Karvonen method
//60-70% of HRR+minHR = zone2. //70-80% of HRR+minHR = zone3. //80-90% of HRR+minHR = zone4. //90-99% of HRR+minHR = zone5. //=>99% of HRR+minHR = serious risk of heart attack
let minzone2 = hrr * 0.6 + minhr;
let maxzone2 = hrr * 0.7 + minhr;
@ -67,7 +67,7 @@
// HR data: large, readable, in the middle of the screen
function drawHR() {
g.setFontAlign(-1,0,0);
g.clearRect(Rdiv(x,11/4),Rdiv(y,2)-25,Rdiv(x,11/4)+50*2,Rdiv(y,2)+25);
g.clearRect(Rdiv(x,11/4),Rdiv(y,2)-25,Rdiv(x,11/4)+50*2-14,Rdiv(y,2)+25);
g.setColor(g.theme.fg);
g.setFont("Vector",50);
g.drawString(hr, Rdiv(x,11/4), Rdiv(y,2)+4);
@ -207,9 +207,9 @@
initDraw();
// check for updates every second.
karvonnenInterval = setInterval(function() {
if (!isMenuDisplayed && karvonnenActive) updateUI();
karvonenInterval = setInterval(function() {
if (!isMenuDisplayed && karvonenActive) updateUI();
}, 1000);
return karvonnenInterval;
return karvonenInterval;
})

View File

@ -1,10 +1,10 @@
{
"id": "runplus",
"name": "Run+",
"version": "0.15",
"version": "0.16",
"description": "Displays distance, time, steps, cadence, pace and more for runners. Based on the Run app, but extended with additional screen for heart rate interval training.",
"icon": "app.png",
"tags": "run,running,fitness,outdoors,gps,karvonnen",
"tags": "run,running,fitness,outdoors,gps,karvonen,karvonnen",
"supports": [
"BANGLEJS2"
],
@ -29,8 +29,8 @@
"url": "settings.js"
},
{
"name": "runplus_karvonnen",
"url": "karvonnen.js"
"name": "runplus_karvonen",
"url": "karvonen.js"
}
],
"data": [

View File

@ -1,5 +1,5 @@
(function(back) {
const SETTINGS_FILE = "run.json";
const SETTINGS_FILE = "runplus.json";
var ExStats = require("exstats");
var statsList = ExStats.getList();
statsList.unshift({name:"-",id:""}); // add blank menu item

View File

@ -1,2 +1,3 @@
0.01: 1st version: saves values to csv
0.02: added HTML interface
0.03: Added Stop/start recording, change BG color, filesize info

View File

@ -9,6 +9,12 @@ Bangle JS1
![](photo_banglejs1.jpg)
UI for bangleJS1
![](bangle.js_UI.png)
UI for bangleJS2
![](bangle.js2_UI.png)
Screenshot BJS2
![](ss_emul_bjs2.png)
@ -30,18 +36,35 @@ Screenshot data file content
Open and see a temperature in the screen
Download the CSV file and process in your favourite spreadsheet software
if you have any problem enable the modedebug in code; v_mode_debug=1 or 2
## Features
Colours, all inputs , graph, widgets loaded
Counter for Times Display
- Cross compatibility (JS1,JS2) and widgets compatibility
- BG/FG Colour, Export to file and counter of saved records per session
- File operations: Info, delete (no yet)
## Pending/future Features
- Buttons layout: btn txt(BJS1) , on screen button (BJS2)
- Long press touch to delete file (BJS1,BJS2)
- File operations: Delete
## Controls
## Controls/UI
- Left area: Back/Exit/launcher
- BTN3 (long press)(BJS1): default Exit/kill app
- BTN1 (BJS2): "Launcher" / open "Messages"
- BTN2 (BJS1): "Launcher" / open "Messages"
- BTN1 (BJS1): Change FG Color
- BTN3 (BJS1): Change BG Color
- Right area: Change FG Color
- Swipe left: Change BG Color
- Swipe right: Increase/Decrease Hour circle/Points
exit: left side
## Creator
Daniel Perez
For suggestions or feedback
https://github.com/dapgo/my_espruino_smartwatch_things

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

View File

@ -1,7 +1,7 @@
{
"id": "tempmonitor",
"name": "Temperature monitor",
"version": "0.02",
"version": "0.03",
"description": "Displays the current temperature and stores in a CSV file",
"icon": "app.png",
"tags": "tool",

View File

@ -1,42 +1,69 @@
// Temperature monitor that saves a log of measures
// standalone ver for developer, to remove testing lines
// delimiter ; (excel) or , (oldscool)
/* REFACTOR and remove commented code related to
SetUI, Layout, and setWatch( function(b) { }, BTN1, { repeat: true, edge:'falling' })
*/
{
var v_mode_debug=0; //, 0=no, 1 min, 2 prone detail
//var required for drawing with dynamic screen
var rect = Bangle.appRect;
var history = [];
var readFreq=5000; //ms //PEND add to settings
var saveFreq=60000; //ms 1min
var readFreq=4000; //ms //PEND add to settings
if (v_mode_debug>0) var saveFreq=6000; //ms for testin 6sec
else var saveFreq=60000; //ms 1min
var v_saveToFile= new Boolean(true); //true save //false
//with upload file º is not displayed properly
//with upload RAM º is displayed
var v_t_symbol="";//ºC
var v_saved_entries=0;
var filename ="temphistory.csv";
var v_filename ="temphistory.csv";
var lastMeasure = new String();
var v_model=process.env.BOARD;
var v_color_erase=g.getBgColor(); //original BG color overwritten on SetVariables
var v_color=g.getColor();//original FG color
var id_rec_intv; //var for the recording interval
if (readFreq>saveFreq) console.log("Read refresh freq should be higher than saving");
if (v_mode_debug>0) console.log("original BG/FG color="+v_color_erase+" / "+v_color);
function SetVariables(){
//EMSCRIPTEN,EMSCRIPTEN2
if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') {
v_font_size1=16;
v_font_size2=60;
//g.setColor("#0ff"); //light color
v_font_size2=50;
}else{
v_font_size1=11;
//Banglejs2 or others
v_font_size1=11; //too small?
v_font_size2=40;
//g.setColor("#000"); //black or dark
}
//overwriting default BG, is better detect?
if (g.theme.dark==1) v_color_erase=0x0000; //dynamic; //bg black
else if (g.theme.dark==0) v_color_erase=0xFFFF; //dynamic; //bg white
}
function onTemperature(v_temp) {
if (v_mode_debug>1) console.log("v_temp in "+v_temp);
//print result
function printTemperature(v_temp) {
if (v_mode_debug>1) console.log("v_temp in "+v_temp+" entries "+v_saved_entries);
ClearBox();
//g.setFont("6x8",2).setFontAlign(0,0);
g.setFontVector(v_font_size1).setFontAlign(0,0);
var x = (rect.x+rect.x2)/2;
var x = (rect.x+(rect.x2-60))/2;//-60 space for graph and layout buttons
var y = (rect.y+rect.y2)/2 + 20;
g.drawString("Records: "+v_saved_entries, x, rect.y+35);
g.drawString("Temperature:", x, rect.y+37+v_font_size1);
if (v_saveToFile==true) {
// if (v_mode_debug>0) console.log("prev color="+v_color);
printInfo("Recording : "+v_saved_entries, '#CC3333',x,rect.y+30);
//g.setColor('#CC3333'); //red
// g.drawString("Recording : "+v_saved_entries, x, rect.y+35);
//g.setColor(v_color);//restore default color
}
else printInfo("Rec paused : "+v_saved_entries, v_color,x,rect.y+30);
//else g.drawString("Rec paused : "+v_saved_entries, x, rect.y+35);
//space for printing info
g.drawString("Temperature:", x, rect.y+45+(v_font_size1*2));
//dynamic font (g.getWidth() > 200 ? 60 : 40)
g.setFontVector(v_font_size2).setFontAlign(0,0);
// Avg of temperature readings
@ -48,33 +75,68 @@ function onTemperature(v_temp) {
lastMeasure=avrTemp.toString();
if (lastMeasure.length>4) lastMeasure=lastMeasure.substr(0,4);
//DRAW temperature in the center
g.drawString(" ", x-20, y);
g.drawString(v_temp+v_t_symbol, x-20, y);
//remove g.drawString(" ", x-20, y);
g.drawString(v_temp+v_t_symbol, x, y);
g.flip();
}
// from: BJS2 pressure sensor, BJS1 inbuilt thermistor
function drawTemperature() {
function getTemperature() {
if(v_model.substr(0,10)!='EMSCRIPTEN'){
if (Bangle.getPressure) {
Bangle.getPressure().then(p =>{if (p) onTemperature(p);});
} else onTemperature(E.getTemperature());
Bangle.getPressure().then(p =>{if (p) printTemperature(p);});
} else printTemperature(E.getTemperature());
}
else onTemperature(11);//fake temp for emulators
else printTemperature(11.25);//fake temperature medition for emulators
}
function saveToFile() {
/* Note that it changes BG and also FG to an opposite*/
function changeBGcolor(){
//pend to refactor
if (v_mode_debug>1) console.log("before BG/FG "+v_color_erase+" /"+v_color);
v_color_erase=0xFFFF-v_color_erase;
v_color=0xFFFF-v_color;
if (v_mode_debug>1) console.log("after result BG/FG "+v_color_erase+" /"+v_color);
//g.setColor(color_result);
g.setBgColor(v_color_erase);// 0 white, 1 black
g.setColor(v_color);
//move to event?
ClearScreen();
ClearBox();
drawGraph();
getTemperature();
//setDrawLayout(); //uncomment if layout can work with setUI
//g.clear();//impact on widgets
}
function saveToFile(){
//input global vars: lastMeasure
var a=new Date();
var strlastSaveTime=new String();
strlastSaveTime=a.toISOString();
//strlastSaveTime=strlastSaveTime.concat(a.getFullYear(),a.getMonth()+1,a.getDate(),a.getHours(),a.getMinutes());;
if (v_mode_debug==1) console.log("saving="+strlastSaveTime+";"+a.getHours()+":"+a.getMinutes()+";"+lastMeasure);
if (v_mode_debug>1) console.log("saving="+strlastSaveTime+";"+a.getHours()+":"+a.getMinutes()+";"+lastMeasure);
if (v_saveToFile==true){
//write(strlastSaveTime+";"+
require("Storage").open(filename,"a").write((a.getMonth()+1)+";"+a.getDate()+";"+a.getHours()+":"+a.getMinutes()+";"+lastMeasure+"\n");
//(getTime()+",");
v_saved_entries=v_saved_entries+1;
//var f = require("Storage").open(v_filename,"r");
// f=require("Storage").read(v_filename+"\1");//suffix required load completely!!
//note that .read uses Storage Class .open uses StorageFile Class , difference in file chunks
// if (v_mode_debug>0) console.log("f "+f);
var f = require("Storage").open(v_filename,"r");
if ((v_mode_debug>0) && (v_saved_entries==0)) console.log("file info:"+f);
if (f.len>0) {
if (!f) {
require("Storage").open(v_filename,"w").write("Month;Day;Time;Temp"+"\n");
if (v_mode_debug>0) console.log("not exist but created "+f);
}
else{
require("Storage").open(v_filename,"a").write((a.getMonth()+1)+";"+a.getDate()+";"+a.getHours()+":"+a.getMinutes()+";"+lastMeasure+"\n");
//(getTime()+",");
v_saved_entries=v_saved_entries+1;
if (v_mode_debug>1) console.log("append to already exist "+f.name+" , "+v_saved_entries);
}
}
}
else if (v_mode_debug>0) console.log("recording mode stopped");
}
function drawGraph(){
@ -83,17 +145,19 @@ function drawGraph(){
transparent : 0,
buffer : require("heatshrink").decompress(atob("AEFt2AMKm3bsAMJjdt23ABhEB+/7tgaJ///DRUP//7tuADRP923YDRXbDRfymwaJhu/koaK7eyiwaK3cLDRlWDRY1NKBY1Ztu5kjmJg3cyVI7YMHgdu5Mkyu2fxHkyVJjdgDRFJkmRDRPsDQNbDQ5QBGoONKBJrBoxQIQwO2eRcbtu24AMIFIQLJAH4AMA=="))
};
g.drawImage(img_obj_thermo,rect.x2-50,rect.y2/2);
g.drawImage(img_obj_thermo,rect.x2-60,rect.y2/2);
g.flip();
}
function ClearScreen(){
//avoid widget areas
g.reset(1).clearRect(rect.x, rect.y+24, rect.x2, rect.y2-24);
g.setBgColor(v_color_erase);
g.clearRect(rect.x, rect.y+24, rect.x2, rect.y2-24);
g.flip();
}
function ClearBox(){
//custom boxarea , left space for static graph at right
g.reset(1).clearRect(rect.x, rect.y+24, rect.x2-50, rect.y2-24);
g.setBgColor(v_color_erase);
g.clearRect(rect.x, rect.y+24, rect.x2-60, rect.y2-24);
g.flip();
}
function introPage(){
@ -109,30 +173,140 @@ function introPage(){
g.drawString("Read freq(ms): "+readFreq, x, y );
g.drawString("Save to file: "+v_saveToFile, x, y+ ((v_font_size1*1)+2) );
g.drawString("Save freq(ms):"+saveFreq, x, y+((v_font_size1*2)+2) );
fr=require("Storage").read(filename+"\1");//suffix required
if (fr) g.drawString("Current filesize:"+fr.length.toString()+"kb", x, y+((v_font_size1*3)+2) );
fr=require("Storage").read(v_filename+"\1");//suffix required
if (fr) g.drawString("Filesize:"+fr.length.toString()+"kb", x, y+((v_font_size1*3)+2) );
else g.drawString("File not exist", x, y+((v_font_size1*3)+2));
}
function printInfo(pmsg, pcolor,px,py){
g.setColor(pcolor);
g.setFontVector(v_font_size1).setFontAlign(0,0);
g.drawString(pmsg, px,py+v_font_size1);
g.setColor(v_color);//restore default color
}
function toggleRecMode(duration, exectime){
//bydefault float, standard epoch requires *1000
if (v_mode_debug>0) console.log("duration"+duration);
if (duration>2) { //delete file
var x = (rect.x+(rect.x2-60))/2;
printInfo("Deleting file",'#CC3333',x, rect.y+32+v_font_size1);
// g.setColor('#CC3333'); //red
//too long "Deleting file: "+v_filename,
// for StorageFiles created with require("Storage").open(filename, ...)
//require("Storage").erase(v_filename);
//TODO refactor in a new function
//var mifile = require("Storage").open(v_filename,"w");
var mifile = require("Storage").open("temphistory.csv","w");
var v_output=mifile.erase();
//mifile.StorageFile.erase();
if (v_mode_debug>0) console.log("output"+v_output);
setTimeout(function() { if (v_mode_debug>0) console.log("pause for 1 sec");},1000);
return; //leave this function
}
if (v_saveToFile) v_saveToFile=false;
else v_saveToFile=true;
if (v_mode_debug>0) console.log("recording? "+v_saveToFile);
setRecordingFreq();
}
function setRecordingFreq(){
if (v_saveToFile==true) { //TODO now start on false btn will no enable
id_rec_intv=setInterval(function() {
saveToFile();
}, saveFreq); //ms
if (v_mode_debug>0) console.log("interval id / frq"+id_rec_intv+" / "+saveFreq);
}
else if (id_rec_intv){
clearInterval(id_rec_intv);
if (v_mode_debug>0) console.log("rec interval removed, id "+id_rec_intv);
id_rec_intv=0; // to reset var
}
}
function UserInput(){
//theoretically incompatible with Layout
Bangle.setUI({
mode : "custom",
//adds a back icon on top widget area
back : function() {load();},
//touch : function(n,e) {}, // optional - handler for 'touch' events
// righ/Left 1/-1 , updown
swipe : function(dir_rl,dir_ud) {
if(dir_rl == 1) {
if (v_mode_debug>0) console.log("swipe right: ");
getFileInfo(v_filename);
}
else if (dir_rl == -1){
if (v_mode_debug>0) console.log("swipe left: ");
changeBGcolor();
}
},
touch : function(tzone,tobj){
if ((process.env.HWVERSION == 2)&&(v_mode_debug>0)){
console.log("tobj x,y,type : "+tobj.x+" "+tobj.y+" "+tobj.type);
}
switch(tzone){
//case 1: //left , back managed by setUI
case 2: // right disable/enable recording
toggleRecMode(0); //toggleRecMode(duration, exectime)
break;
// case 3: console.log("Touch 3 aka 1+2 not for BJS1 emul");//center 1+2
// break;
}
},
//inferior to
btn : function(btn) {
if(btn == 1) {
if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') toggleRecMode(1); //console.log("btn1 BJS1");
else mainBtnShortcut(); //console.log("btn1 BJS2");
}
else if (btn == 2) mainBtnShortcut(); //console.log("btn2 BJS1");
else if (btn == 3) changeBGcolor(); //console.log("btn3 BJS1");
}
}); //endof setUI
}
function mainBtnShortcut() {
//if messages app installed shortcut otherwise default access to launcher
if (require("Storage").read("messagegui.app.js")===undefined)
{
if (require("Storage").read("messagelist.app.js")===undefined) Bangle.showLauncher(); // implies btn2(js1) btn(js2)- launcher
else if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') load("messagelist.app.js");
else load("messagelist.app.js");
}
else if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') load("messagegui.app.js");
else load("messagegui.app.js");
}
// Show file size
function getFileInfo(v_filename) {
var f = require("Storage").open(v_filename,"r");
//todo refactor and reuse common code
g.setFontVector(v_font_size1).setFontAlign(0,0);
var x = (rect.x+(rect.x2-60))/2;
printInfo("file size:"+f.len,v_color,x, rect.y+32+v_font_size1);
// g.drawString("file size:"+f.len, x, rect.y+37+v_font_size1);
if (v_mode_debug>0) console.log("file "+v_filename+" size: "+f.len);
}// not used
//MAIN
SetVariables();
Bangle.loadWidgets();
Bangle.setUI({
mode : "custom",
back : function() {load();}
});
ClearScreen();
introPage();
//setDrawLayout(); //uncomment if layout can work with setUI
UserInput(); //inc SetUI and back icon
setInterval(function() {
drawTemperature();
getTemperature();
}, readFreq); //ms
if (v_saveToFile==true) {
setInterval(function() {
saveToFile();
}, saveFreq); //ms
}
setTimeout(ClearScreen, 3500);
setTimeout(drawGraph,4000);
setTimeout(drawTemperature,4500);
setRecordingFreq();
}

View File

@ -1 +1 @@
{"id":"tempmonitor","name":"tempmonitor","src":"tempmonitor.app.js","icon":"tempmonitor.img","version":"0.01","files":"tempmonitor.info,tempmonitor.app.js,tempmonitor.img"}
{"id":"tempmonitor","name":"tempmonitor","src":"tempmonitor.app.js","icon":"tempmonitor.img","version":"0.03","files":"tempmonitor.info,tempmonitor.app.js,tempmonitor.img"}

View File

@ -6,11 +6,11 @@
);
const iconWidth = 18;
function draw(this: { x: number; y: number }) {
function draw(this: { x?: number; y?: number }) {
g.reset();
if (Bangle.isCharging()) {
g.setColor('#FD0');
g.drawImage(icon, this.x + 1, this.y + 1, {
g.drawImage(icon, this.x! + 1, this.y! + 1, {
scale: 0.6875,
});
}

View File

@ -6,3 +6,6 @@
Update to match the default alarm widget, and not show itself when an alarm is hidden.
0.04: Fix check for active alarm
0.05: Convert Yes/No On/Off in settings to checkboxes
0.06: Remember next alarm to reduce calculation amount
Redraw only every hour when no alarm in next 24h
0.07: Fix when no alarms are present

View File

@ -2,7 +2,7 @@
"id": "widalarmeta",
"name": "Alarm & Timer ETA",
"shortName": "Alarm ETA",
"version": "0.05",
"version": "0.07",
"description": "A widget that displays the time to the next Alarm or Timer in hours and minutes, maximum 24h (configurable).",
"icon": "widget.png",
"type": "widget",

View File

@ -1,24 +1,45 @@
(() => {
require("Font5x9Numeric7Seg").add(Graphics);
const alarms = require("Storage").readJSON("sched.json",1) || [];
const config = Object.assign({
maxhours: 24,
drawBell: false,
showSeconds: 0, // 0=never, 1=only when display is unlocked, 2=for less than a minute
}, require("Storage").readJSON("widalarmeta.json",1) || {});
function draw() {
const times = alarms
.map(alarm =>
alarm.hidden !== true
&& require("sched").getTimeToAlarm(alarm)
)
.filter(a => a !== undefined);
const next = times.length > 0 ? Math.min.apply(null, times) : 0;
function getNextAlarm(date) {
const alarms = (require("Storage").readJSON("sched.json",1) || []).filter(alarm => alarm.on && alarm.hidden !== true);
WIDGETS["widalarmeta"].numActiveAlarms = alarms.length;
if (alarms.length > 0) {
const times = alarms.map(alarm => require("sched").getTimeToAlarm(alarm, date) || Number.POSITIVE_INFINITY);
const eta = Math.min.apply(null, times);
if (eta !== Number.POSITIVE_INFINITY) {
const idx = times.indexOf(eta);
const alarm = alarms[idx];
delete alarm.msg; delete alarm.id; delete alarm.data; // free some memory
return alarm;
}
}
} // getNextAlarm
function draw(fromInterval) {
if (this.nextAlarm === undefined) {
let alarm = getNextAlarm();
if (alarm === undefined) {
// try again with next hour
const nextHour = new Date();
nextHour.setHours(nextHour.getHours()+1);
alarm = getNextAlarm(nextHour);
}
if (alarm !== undefined) {
this.nextAlarm = alarm;
}
}
const next = this.nextAlarm !== undefined ? require("sched").getTimeToAlarm(this.nextAlarm) : 0;
let calcWidth = 0;
let drawSeconds = false;
if (next > 0 && next < config.maxhours*60*60*1000) {
if (next > 0 && next <= config.maxhours*60*60*1000) {
const hours = Math.floor((next-1) / 3600000).toString();
const minutes = Math.floor(((next-1) % 3600000) / 60000).toString();
const seconds = Math.floor(((next-1) % 60000) / 1000).toString();
@ -39,10 +60,14 @@
if (drawSeconds) {
calcWidth += 3*5;
}
} else if (config.drawBell && alarms.some(alarm=>alarm.on&&(alarm.hidden!==true))) {
// next alarm too far in future, draw only widalarm bell
g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
this.bellVisible = false;
} else if (config.drawBell && this.numActiveAlarms > 0) {
calcWidth = 24;
// next alarm too far in future, draw only widalarm bell
if (this.bellVisible !== true || fromInterval !== true) {
g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
this.bellVisible = true;
}
}
if (this.width !== calcWidth) {
@ -51,8 +76,8 @@
Bangle.drawWidgets();
}
// redraw next full minute or second
const period = drawSeconds ? 1000 : 60000;
// redraw next hour when no alarm else full minute or second
const period = next === 0 ? 3600000 : (drawSeconds ? 1000 : 60000);
let timeout = next > 0 ? next % period : period - (Date.now() % period);
if (timeout === 0) {
timeout += period;
@ -62,8 +87,8 @@
clearTimeout(this.timeoutId);
}
this.timeoutId = setTimeout(()=>{
this.timeoutId = undefined;
this.draw();
WIDGETS["widalarmeta"].timeoutId = undefined;
WIDGETS["widalarmeta"].draw(true);
}, timeout);
} /* draw */

View File

@ -7,3 +7,4 @@
Show difference of last measurement to pressure average of the the last three hours in the widget
Only use valid pressure values
0.06: Fix exception
0.07: Ensure barometer gets turned off after a few readings (isBarometerOn broken in 2v16)

View File

@ -2,7 +2,7 @@
"id": "widbaroalarm",
"name": "Barometer Alarm Widget",
"shortName": "Barometer Alarm",
"version": "0.06",
"version": "0.07",
"description": "A widget that can alarm on when the pressure reaches defined thresholds.",
"icon": "widget.png",
"type": "widget",

View File

@ -211,6 +211,28 @@ function calculcate3hAveragePressure() {
}
}
function barometerPressureHandler(e) {
const MEDIANLENGTH = 20;
while (currentPressures.length > MEDIANLENGTH)
currentPressures.pop();
const pressure = e.pressure;
if (isValidPressureValue(pressure)) {
currentPressures.unshift(pressure);
median = currentPressures.slice().sort();
if (median.length > 10) {
var mid = median.length >> 1;
medianPressure = Math.round(E.sum(median.slice(mid - 4, mid + 5)) / 9);
if (medianPressure > 0) {
turnOff();
draw();
handlePressureValue(medianPressure);
}
}
}
}
/*
turn on barometer power
take multiple measurements
@ -219,37 +241,15 @@ function calculcate3hAveragePressure() {
turn off barometer power
*/
function getPressureValue() {
if (stop)
return;
const MEDIANLENGTH = 20;
if (stop) return;
Bangle.setBarometerPower(true, "widbaroalarm");
Bangle.on('pressure', function(e) {
while (currentPressures.length > MEDIANLENGTH)
currentPressures.pop();
const pressure = e.pressure;
if (isValidPressureValue(pressure)) {
currentPressures.unshift(pressure);
median = currentPressures.slice().sort();
if (median.length > 10) {
var mid = median.length >> 1;
medianPressure = Math.round(E.sum(median.slice(mid - 4, mid + 5)) / 9);
if (medianPressure > 0) {
turnOff();
draw();
handlePressureValue(medianPressure);
}
}
}
});
setTimeout(function() { turnOff(); }, 30000);
Bangle.on('pressure', barometerPressureHandler);
setTimeout(turnOff, 30000);
}
function turnOff() {
if (Bangle.isBarometerOn())
Bangle.setBarometerPower(false, "widbaroalarm");
Bangle.removeListener('pressure', barometerPressureHandler);
Bangle.setBarometerPower(false, "widbaroalarm");
}
function draw() {

View File

@ -7,3 +7,4 @@
0.08: Ensure battery updates every 60s even if LCD was on at boot and stays on
0.09: Misc speed/memory tweaks
0.10: Color changes due to the battery level
0.11: Change level for medium charge (50% -> 40%), and darken color on light themes as yellow was almost invisible

View File

@ -1,7 +1,7 @@
{
"id": "widbat",
"name": "Battery Level Widget",
"version": "0.10",
"version": "0.11",
"description": "Show the current battery level and charging status in the top right of the clock",
"icon": "widget.png",
"type": "widget",

View File

@ -33,7 +33,7 @@
g.setColor(g.theme.fg).fillRect(x,y+2,x+s-4,y+21).clearRect(x+2,y+4,x+s-6,y+19).fillRect(x+s-3,y+10,x+s,y+14);
var battery = E.getBattery();
if(battery < 20) {g.setColor("#f00");}
else if (battery < 50) {g.setColor("#ff0");}
else if (battery < 40) {g.setColor(g.theme.dark ? "#ff0" : "#f80");}
else {g.setColor("#0f0");}
g.fillRect(x+4,y+6,x+4+battery*(s-12)/100,y+17);
}};

View File

@ -15,3 +15,4 @@
0.16: Increase screen update rate when charging
0.17: Add option 'Remove Jitter'='Drop only' to prevent percentage from getting up again when not charging
Add option to disable vibration when charger connects
0.18: Only redraw when values change

View File

@ -2,7 +2,7 @@
"id": "widbatpc",
"name": "Battery Level Widget (with percentage)",
"shortName": "Battery Widget",
"version": "0.17",
"version": "0.18",
"description": "Show the current battery level and charging status in the top right of the clock, with charge percentage",
"icon": "widget.png",
"type": "widget",

View File

@ -86,7 +86,7 @@
return changed;
}
function draw() {
function draw(fromInterval) {
// if hidden, don't draw
if (!WIDGETS["batpc"].width) return;
// else...
@ -103,6 +103,14 @@
l = prevMin;
}
}
if (fromInterval === true && this.prevLevel === l && this.prevCharging === Bangle.isCharging()) {
return; // unchanged, do nothing
}
this.prevLevel = l;
this.prevCharging = Bangle.isCharging();
const c = levelColor(l);
if (Bangle.isCharging() && setting('charger')) {
@ -173,7 +181,7 @@
if (on) update();
});
var id = setInterval(()=>WIDGETS["batpc"].draw(), intervalLow);
var id = setInterval(()=>WIDGETS["batpc"].draw(true), intervalLow);
WIDGETS["batpc"]={area:"tr",width:40,draw:draw,reload:reload};
setWidth();

View File

@ -1 +1,2 @@
0.01: First commit
0.02: Add tap-to-lock functionality

View File

@ -1,8 +1,8 @@
{
"id": "widlockunlock",
"name": "Lock/Unlock Widget",
"version": "0.01",
"description": "On devices with always-on display (Bangle.js 2) this displays lock icon whenever the display is locked, or an unlock icon otherwise",
"version": "0.02",
"description": "On devices with always-on display (Bangle.js 2) this displays lock icon whenever the display is locked, or an unlock icon otherwise. Tap to lock the lcd",
"icon": "widget.png",
"type": "widget",
"tags": "widget,lock",

View File

@ -1,5 +1,28 @@
Bangle.on("lockunlock", function() {
Bangle.drawWidgets();
Bangle.on("lock", () => Bangle.drawWidgets());
Bangle.on('touch', (_btn, xy) => {
const oversize = 5;
const w = WIDGETS.lockunlock;
const x = xy.x;
const y = xy.y;
if(w.x - oversize <= x && x < w.x + 14 + oversize
&& w.y - oversize <= y && y < w.y + 24 + oversize)
{
Bangle.setLocked(true);
const backlightTimeout = Bangle.getOptions().backlightTimeout; // ms
// seems to be a race/if we don't give the firmware enough time,
// it won't timeout the backlight and we'll restore it in our setTimeout below
Bangle.setOptions({ backlightTimeout: 100 });
setTimeout(() => {
Bangle.setOptions({ backlightTimeout });
}, 300);
}
});
WIDGETS["lockunlock"]={area:"tl",sortorder:10,width:14,draw:function(w) {
g.reset().drawImage(atob(Bangle.isLocked() ? "DBGBAAAA8DnDDCBCBP////////n/n/n//////z/A" : "DBGBAAAA8BnDDCBABP///8A8A8Y8Y8Y8A8A//z/A"), w.x+1, w.y+3);

View File

@ -44,6 +44,7 @@ declare module ClockInfo {
w: number,
h: number,
draw(itm: MenuItem, info: Item, options: InteractiveOptions): void,
app?: string, // used to remember clock_info locations, per app
};
type InteractiveOptions =

View File

@ -83,7 +83,10 @@ type WidgetArea = "tl" | "tr" | "bl" | "br";
type Widget = {
area: WidgetArea;
width: number;
draw: (this: { x: number; y: number }) => void;
sortorder?: number;
draw: (this: Widget, w: Widget) => void;
x?: number;
y?: number;
};
declare const WIDGETS: { [key: string]: Widget };
@ -8688,6 +8691,15 @@ interface ObjectConstructor {
*/
entries(object: any): Array<[string, any]>;
/**
* Transforms an array of key-value pairs into an object
*
* @param {any} entries - An array of `[key,value]` pairs to be used to create an object
* @returns {any} An object containing all the specified pairs
* @url http://www.espruino.com/Reference#l_Object_fromEntries
*/
fromEntries(entries: any): any;
/**
* Creates a new object with the specified prototype object and properties.
* properties are currently unsupported.
@ -8709,6 +8721,15 @@ interface ObjectConstructor {
*/
getOwnPropertyDescriptor(obj: any, name: any): any;
/**
* Get information on all properties in the object (from `Object.getOwnPropertyDescriptor`), or just `{}` if no properties
*
* @param {any} obj - The object
* @returns {any} An object containing all the property descriptors of an object
* @url http://www.espruino.com/Reference#l_Object_getOwnPropertyDescriptors
*/
getOwnPropertyDescriptors(obj: any): any;
/**
* Add a new property to the Object. 'Desc' is an object with the following fields:
* * `configurable` (bool = false) - can this property be changed/deleted (not
@ -8945,32 +8966,32 @@ interface Function {
/**
* This executes the function with the supplied 'this' argument and parameters
*
* @param {any} this - The value to use as the 'this' argument when executing the function
* @param {any} thisArg - The value to use as the 'this' argument when executing the function
* @param {any} params - Optional Parameters
* @returns {any} The return value of executing this function
* @url http://www.espruino.com/Reference#l_Function_call
*/
call(this: any, ...params: any[]): any;
call(thisArg: any, ...params: any[]): any;
/**
* This executes the function with the supplied 'this' argument and parameters
*
* @param {any} this - The value to use as the 'this' argument when executing the function
* @param {any} thisArg - The value to use as the 'this' argument when executing the function
* @param {any} args - Optional Array of Arguments
* @returns {any} The return value of executing this function
* @url http://www.espruino.com/Reference#l_Function_apply
*/
apply(this: any, args: any): any;
apply(thisArg: any, args: ArrayLike<any>): any;
/**
* This executes the function with the supplied 'this' argument and parameters
*
* @param {any} this - The value to use as the 'this' argument when executing the function
* @param {any} thisArg - The value to use as the 'this' argument when executing the function
* @param {any} params - Optional Default parameters that are prepended to the call
* @returns {any} The 'bound' function
* @url http://www.espruino.com/Reference#l_Function_bind
*/
bind(this: any, ...params: any[]): any;
bind(thisArg: any, ...params: any[]): any;
}
/**