Merge branch 'master' into runplus

pull/2600/head
Gordon Williams 2023-02-27 09:36:21 +00:00 committed by GitHub
commit 7386e0c78d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
104 changed files with 1065 additions and 358 deletions

View File

@ -3,3 +3,4 @@
0.03: Exit as first menu option, dont show decimal places for seconds
0.04: Localisation, change Exit->Back to allow back-arrow to appear on 2v13 firmware
0.05: Add max G values during recording, record actual G values and magnitude to CSV
0.06: Convert Yes/No On/Off in settings to checkboxes

View File

@ -26,8 +26,7 @@ function showMenu() {
viewLogs();
},
/*LANG*/"Log raw data" : {
value : logRawData,
format : v => v?/*LANG*/"Yes":/*LANG*/"No",
value : !!logRawData,
onchange : v => { logRawData=v; }
},
};

View File

@ -2,7 +2,7 @@
"id": "accellog",
"name": "Acceleration Logger",
"shortName": "Accel Log",
"version": "0.05",
"version": "0.06",
"description": "Logs XYZ acceleration data to a CSV file that can be downloaded to your PC",
"icon": "app.png",
"tags": "outdoor",

View File

@ -8,3 +8,4 @@
0.08: Use default Bangle formatter for booleans
0.09: New app screen (instead of showing settings or the alert) and some optimisations
0.10: Add software back button via setUI
0.11: Add setting to unlock screen

View File

@ -26,6 +26,12 @@
if (!(storage.readJSON('setting.json', 1) || {}).quiet) {
Bangle.buzz(400);
}
if ((storage.readJSON('activityreminder.s.json', 1) || {}).unlock) {
Bangle.setLocked(false);
Bangle.setLCDPower(1);
}
setTimeout(load, 20000);
}

View File

@ -3,7 +3,7 @@
"name": "Activity Reminder",
"shortName":"Activity Reminder",
"description": "A reminder to take short walks for the ones with a sedentary lifestyle",
"version":"0.10",
"version":"0.11",
"icon": "app.png",
"type": "app",
"tags": "tool,activity",

View File

@ -75,7 +75,14 @@
settings.tempThreshold = v;
activityreminder.writeSettings(settings);
}
},
'Unlock on alarm': {
value: !!settings.unlock,
onchange: v => {
settings.unlock = v;
activityreminder.writeSettings(settings);
}
},
};
return mainMenu;

View File

@ -12,7 +12,7 @@
var mainmenu = {
"" : { "title" : "Android" },
"< Back" : back,
/*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" },
/*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?/*LANG*/"Yes":/*LANG*/"No" },
/*LANG*/"Find Phone" : () => E.showMenu({
"" : { "title" : /*LANG*/"Find Phone" },
"< Back" : ()=>E.showMenu(mainmenu),

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 @@
0.01: New App!

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwhC/AH4ARkQAHBwsBiIACiAHBgQXIkAXJiIuKGAwWEC4cjmYABn//AAMyC63yC653FC6HwC5aQBC5ybIC44WChGAWxMgC44rCxGIZxYXFIoYXBGAQNCAAQXILYYXBGAUDBoK0EC5AsBC4QwEC5wAEC853BhAWDI6CPCFwp3OX4ouCC8xHXCAJ3VX94XCwBHVGIiPTU4oNCAAQWBX5gDBgQRCAAoXGGAUIFwQXHkAXHJIgABCw4IBC5sAiIAEiAgHAAQXLHBAYIC+6wJQYIADgIXGGBJ3FC4iOBAH4A/ACAA=="))

41
apps/blescanner/app.js Normal file
View File

@ -0,0 +1,41 @@
E.showMessage("Scanning...");
var devices = [];
setInterval(function() {
NRF.findDevices(function(devs) {
devs.forEach(dev=>{
var existing = devices.find(d=>d.id==dev.id);
if (existing) {
existing.timeout = 0;
existing.rssi = (existing.rssi*3 + dev.rssi)/4;
} else {
dev.timeout = 0;
dev.new = 0;
devices.push(dev);
}
});
devices.forEach(d=>{d.timeout++;d.new++});
devices = devices.filter(dev=>dev.timeout<8);
devices.sort((a,b)=>b.rssi - a.rssi);
g.clear(1).setFont("12x20");
var wasNew = false;
devices.forEach((d,y)=>{
y*=20;
var n = d.name;
if (!n) n=d.id.substr(0,22);
if (d.new<4) {
g.fillRect(0,y,g.getWidth(),y+19);
g.setColor(g.theme.bg);
if (d.rssi > -70) wasNew = true;
} else {
g.setColor(g.theme.fg);
}
g.setFontAlign(-1,-1);
g.drawString(n,0,y);
g.setFontAlign(1,-1);
g.drawString(0|d.rssi,g.getWidth()-1,y);
});
g.flip();
Bangle.setLCDBrightness(wasNew);
}, 1200);
}, 1500);

BIN
apps/blescanner/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,14 @@
{ "id": "blescanner",
"name": "BLE Scanner",
"shortName":"BLE Scan",
"version":"0.01",
"description": "Scans for bluetooth devices nearby and shows their names on the screen ordered by signal strength. The most recently discovered items are highlighted.",
"icon": "app.png",
"screenshots" : [ { "url":"screenshot.png" } ],
"tags": "tool,bluetooth",
"supports" : ["BANGLEJS2"],
"storage": [
{"name":"blescanner.app.js","url":"app.js"},
{"name":"blescanner.img","url":"app-icon.js","evaluate":true}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

16
apps/btadv/README.md Normal file
View File

@ -0,0 +1,16 @@
# Bluetooth Advert
This app advertises and exports (over Bluetooth) live data from the bangle's sensors:
- Heart Rate
- Accelerometer readings
- Pressure
- GPS information
- Magnetic flux
Swipe in any direction to access settings, and tap a setting to toggle it.
Hit back to return to the details screen, which shows sensor data being exported.
# TypeScript
This app is written in TypeScript, see [typescript/README.md](/typescript/README.md) for more info

View File

@ -1,15 +1,5 @@
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __assign = Object.assign;
var Layout = require("Layout");
Bangle.loadWidgets();
Bangle.drawWidgets();

View File

@ -1,3 +1,6 @@
// ts helpers:
const __assign = Object.assign;
const Layout = require("Layout") as Layout_.Layout;
Bangle.loadWidgets();

View File

@ -96,7 +96,7 @@ function draw(){
if (!isNaN(bt.battery)) layout.btBattery.label = bt.battery + "%";
if (bt.rr) layout.btRR.label = bt.rr.join(",");
if (!isNaN(bt.location)) layout.btLocation.label = BODY_LOCS[bt.location];
if (bt.contact !== undefined) layout.btContact.label = bt.contact ? "Yes":"No";
if (bt.contact !== undefined) layout.btContact.label = bt.contact ? /*LANG*/"Yes":/*LANG*/"No";
if (!isNaN(bt.energy)) layout.btEnergy.label = bt.energy.toFixed(0) + "kJ";
} else {
layout.bt.label = "--";

View File

@ -9,3 +9,7 @@ Tap to start, tap again to pause. Tap again to restart
## Requests
[Contact Rob](https://www.github.com/bobrippling) for features/bugs
# TypeScript
This app is written in TypeScript, see [typescript/README.md](/typescript/README.md) for more info

View File

@ -4,3 +4,4 @@
0.04: Use default Bangle formatter for booleans
0.05: Improved colors (connected vs disconnected)
0.06: Tell clock widgets to hide.
0.07: Convert Yes/No On/Off in settings to checkboxes

View File

@ -1,7 +1,7 @@
{
"id": "clockcal",
"name": "Clock & Calendar",
"version": "0.06",
"version": "0.07",
"description": "Clock with Calendar",
"readme":"README.md",
"icon": "app.png",

View File

@ -106,18 +106,11 @@
writeSettings();
}
},
'Load deafauls?': {
value: 0,
min: 0, max: 1,
format: v => ["No", "Yes"][v],
onchange: v => {
if (v == 1) {
'Load defaults': () => {
settings = defaults;
writeSettings();
load();
}
}
},
};
// Show the menu
E.showMenu(menu);

View File

@ -6,3 +6,4 @@
0.06: Now read wheel rev as well as cadence sensor
Improve connection code
0.07: Make Bangle.js 2 compatible
0.08: Convert Yes/No On/Off in settings to checkboxes

View File

@ -2,7 +2,7 @@
"id": "cscsensor",
"name": "Cycling speed sensor",
"shortName": "CSCSensor",
"version": "0.07",
"version": "0.08",
"description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch",
"icon": "icons8-cycling-48.png",
"tags": "outdoors,exercise,ble,bluetooth",

View File

@ -23,17 +23,17 @@
}
}
const menu = {
'': { 'title': 'Cycle speed sensor' },
'': { 'title': /*LANG*/'Cycle speed sensor' },
'< Back': back,
'Wheel circ.(mm)': {
/*LANG*/'Wheel circ.(mm)': {
value: s.wheelcirc,
min: 800,
max: 2400,
step: 5,
onchange: save('wheelcirc'),
},
'Reset total distance': function() {
E.showPrompt("Zero total distance?", {buttons: {"No":false, "Yes":true}}).then(function(v) {
/*LANG*/'Reset total distance': function() {
E.showPrompt(/*LANG*/"Zero total distance?", {buttons: {/*LANG*/"No":false, /*LANG*/"Yes":true}}).then(function(v) {
if (v) {
s['totaldist'] = 0;
storage.write(SETTINGS_FILE, s);

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Add lightning
0.03: Convert Yes/No On/Off in settings to checkboxes

View File

@ -1,7 +1,7 @@
{ "id": "f9lander",
"name": "Falcon9 Lander",
"shortName":"F9lander",
"version":"0.02",
"version":"0.03",
"description": "Land a rocket booster",
"icon": "f9lander.png",
"screenshots" : [ { "url":"f9lander_screenshot1.png" }, { "url":"f9lander_screenshot2.png" }, { "url":"f9lander_screenshot3.png" }],

View File

@ -2,7 +2,6 @@
/**
* @param {function} back Use back() to return to settings menu
*/
const boolFormat = v => v ? /*LANG*/"On" : /*LANG*/"Off";
(function(back) {
const SETTINGS_FILE = 'f9settings.json'
// initialize with default settings...
@ -27,8 +26,7 @@ const boolFormat = v => v ? /*LANG*/"On" : /*LANG*/"Off";
'': { 'title': 'OpenWind' },
'< Back': back,
'Lightning': {
value: settings.lightning,
format: boolFormat,
value: !!settings.lightning,
onchange: save('lightning'),
}
}

View File

@ -7,7 +7,7 @@ var m;
var files;
function delete_file(fn) {
E.showPrompt("Delete\n"+fn+"?", {buttons: {"No":false, "Yes":true}}).then(function(v) {
E.showPrompt(/*LANG*/"Delete\n"+fn+"?", {buttons: {/*LANG*/"No":false, /*LANG*/"Yes":true}}).then(function(v) {
if (v) {
if (fn.charCodeAt(fn.length-1)==1) {
var fh = STOR.open(fn.substr(0, fn.length-1), "r");

View File

@ -24,13 +24,13 @@
var mainmenu = {
"" : { "title" : "Gadgetbridge" },
"< Back" : back,
"Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" },
"Show Icon" : {
/*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?/*LANG*/"Yes":/*LANG*/"No" },
/*LANG*/"Show Icon" : {
value: settings().showIcon,
onchange: setIcon
},
"Find Phone" : function() { E.showMenu(findPhone); },
"Record HRM" : {
/*LANG*/"Find Phone" : function() { E.showMenu(findPhone); },
/*LANG*/"Record HRM" : {
value: !!settings().hrm,
onchange: v => updateSetting('hrm', v)
}
@ -38,8 +38,8 @@
var findPhone = {
"" : { "title" : "-- Find Phone --" },
"On" : _=>gb({t:"findPhone",n:true}),
"Off" : _=>gb({t:"findPhone",n:false}),
/*LANG*/"On" : _=>gb({t:"findPhone",n:true}),
/*LANG*/"Off" : _=>gb({t:"findPhone",n:false}),
"< Back" : function() { E.showMenu(mainmenu); },
};

View File

@ -73,3 +73,5 @@
* Display current and next segment in red so that you know where to go.
* Avoid angles flickering at low speed at the cost of less refresh.
* Splash screen while waiting for gps signal.
0.17: Convert Yes/No On/Off in settings to checkboxes

View File

@ -2,7 +2,7 @@
"id": "gipy",
"name": "Gipy",
"shortName": "Gipy",
"version": "0.16",
"version": "0.17",
"description": "Follow gpx files using the gps. Don't get lost in your bike trips and hikes.",
"allow_emulator":false,
"icon": "gipy.png",

View File

@ -19,8 +19,7 @@
"< Back": () => back(),
"keep gps alive": {
value: !!settings.keep_gps_alive, // !! converts undefined to false
format: (v) => (v ? "Yes" : "No"),
onchange: (v) => {
onchange: v => {
settings.keep_gps_alive = v;
writeSettings();
},

View File

@ -46,8 +46,8 @@ var mainMenu = {
"Reset Homework": function() {
E.showPrompt("Are you sure you want to delete homework.txt?", {
buttons: {
"No": false,
"Yes": true
/*LANG*/"No": false,
/*LANG*/"Yes": true
}
}).then(function(v) {
if (v) {

View File

@ -21,11 +21,11 @@ var screens = [
name: "Hardware",
items: [
{name: "Battery", fun: () => E.getBattery() + "%"},
{name: "Charge?", fun: () => Bangle.isCharging() ? "Yes" : "No"},
{name: "Charge?", fun: () => Bangle.isCharging() ? /*LANG*/"Yes" : /*LANG*/"No"},
{name: "TempInt.", fun: () => locale.temp(parseInt(E.getTemperature()))},
{name: "Bluetooth", fun: () => NRF.getSecurityStatus().connected ? "Conn" : "NoConn"},
{name: "GPS", fun: () => Bangle.isGPSOn() ? "On" : "Off"},
{name: "Compass", fun: () => Bangle.isCompassOn() ? "On" : "Off"},
{name: "Bluetooth", fun: () => NRF.getSecurityStatus().connected ? /*LANG*/"Conn" : /*LANG*/"NoConn"},
{name: "GPS", fun: () => Bangle.isGPSOn() ? /*LANG*/"On" : /*LANG*/"Off"},
{name: "Compass", fun: () => Bangle.isCompassOn() ? /*LANG*/"On" : /*LANG*/"Off"},
]
},
{

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Introduced settings to customize the layout and functionality of the keyboard.
0.03: Convert Yes/No On/Off in settings to checkboxes

View File

@ -1,6 +1,6 @@
{ "id": "kbtouch",
"name": "Touch keyboard",
"version":"0.02",
"version":"0.03",
"description": "A library for text input via onscreen keyboard",
"icon": "app.png",
"type":"textinput",

View File

@ -25,22 +25,16 @@
onchange: v => updateSetting("textSize", v)
},
/*LANG*/'Offset keyboard': {
value: settings().offsetKeyboard,
min: 0, max: 1,
format: v => [/*LANG*/"No",/*LANG*/"Yes"][v],
onchange: v => updateSetting("offsetKeyboard", v)
value: !!settings().offsetKeyboard,
onchange: v => updateSetting("offsetKeyboard", v?1:0)
},
/*LANG*/'Loop around': {
value: settings().loopAround,
min: 0, max: 1,
format: v => [/*LANG*/"No",/*LANG*/"Yes"][v],
onchange: v => updateSetting("loopAround", v)
value: !!settings().loopAround,
onchange: v => updateSetting("loopAround", v?1:0)
},
/*LANG*/'One-to-one input and release to select': {
value: settings().oneToOne,
min: 0, max: 1,
format: v => [/*LANG*/"No",/*LANG*/"Yes"][v],
onchange: v => updateSetting("oneToOne", v)
value: !!settings().oneToOne,
onchange: v => updateSetting("oneToOne", v?1:0)
},
/*LANG*/'Speed scaling': {
value: settings().speedScaling,
@ -49,10 +43,8 @@
onchange: v => updateSetting("speedScaling", v)
}
///*LANG*/'Release to select': {
// value: 1|settings().fontSize,
// min: 0, max: 1,
// format: v => [/*LANG*/"No",/*LANG*/"Yes"][v],
// onchange: v => updateSetting("releaseToSelect", v)
// value: !!(1|settings().fontSize),
// onchange: v => updateSetting("releaseToSelect", v?1:0)
//}
};
E.showMenu(mainmenu);

View File

@ -73,7 +73,7 @@ let lastTemp = 0;
const phone = {
get status() {
return NRF.getSecurityStatus().connected ? "Yes" : "No";
return NRF.getSecurityStatus().connected ? /*LANG*/"Yes" : /*LANG*/"No";
},
message: null,
messageTimeout: null,

View File

@ -87,3 +87,4 @@
0.62: Remove '.show' field, tidyup and fix .open if fast load not enabled
0.63: Fix messages app loading on clock without fast load
0.64: Ensure we don't get 'undefined' as the message body
0.65: Make sure messages are saved if not in the clock app (fix #2460)

View File

@ -29,7 +29,7 @@ exports.listener = function(type, msg) {
}
const appSettings = require("Storage").readJSON("messages.settings.json", 1) || {};
let loadMessages = (Bangle.CLOCK || event.important); // should we load the messages app?
let loadMessages = (Bangle.CLOCK || msg.important); // should we load the messages app?
if (type==="music") {
if (Bangle.CLOCK && msg.state && msg.title && appSettings.openMusic) loadMessages = true;
else return;

View File

@ -2,7 +2,7 @@
"id": "messagegui",
"name": "Message UI",
"shortName": "Messages",
"version": "0.64",
"version": "0.65",
"description": "Default app to display notifications from iOS and Gadgetbridge/Android",
"icon": "app.png",
"type": "app",

View File

@ -3,3 +3,4 @@
0.03: Fix icons broken in 0v02 (#2386)
Store all icons in a separate binary file (much faster lookup)
0.04: Add message icon for 'clock'
0.05: Add message icon for 'jira'

Binary file not shown.

View File

@ -87,6 +87,7 @@ exports.getColor = function(msg,options) {
if (st.iconColorMode == 'mono') return options.default;
const s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase();
return {
// This file is generated by /icons/generate.js. If you need to modify its content, you should do it there instead.
// generic colors, using B2-safe colors
// DO NOT USE BLACK OR WHITE HERE, just leave the declaration out and then the theme's fg color will be used
"airbnb": "#ff385c", // https://news.airbnb.com/media-assets/category/brand/
@ -107,6 +108,7 @@ exports.getColor = function(msg,options) {
"google home": "#fbbc05",
// "home assistant": "#41bdf5", // ha-blue is #41bdf5, but that's the background
"instagram": "#ff0069", // https://about.instagram.com/brand/gradient
"jira": "#0052cc", //https://atlassian.design/resources/logo-library
"lieferando": "#ff8000",
"linkedin": "#0a66c2", // https://brand.linkedin.com/
"messenger": "#0078ff",

View File

@ -42,6 +42,7 @@
{ "app":"google play store", "icon":"google play store.png" },
{ "app":"home assistant", "icon":"home assistant.png" },
{ "app":"instagram", "icon":"instagram.png" },
{ "app":"jira", "icon":"jira.png" },
{ "app":"kalender", "icon":"kalender.png" },
{ "app":"keep notes", "icon":"google keep.png" },
{ "app":"lieferando", "icon":"lieferando.png" },

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 B

View File

@ -2,7 +2,7 @@ exports.getImage = function(msg) {
if (msg.img) return atob(msg.img);
let s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase();
if (msg.id=="music") s="music";
let match = ",default|0,airbnb|1,alarm|2,alarmclockreceiver|2,amazon shopping|3,bibel|4,bitwarden|5,1password|5,lastpass|5,dashlane|5,bring|6,calendar|7,etar|7,chat|8,chrome|9,clock|2,corona-warn|10,bmo|11,desjardins|11,rbc mobile|11,nbc|11,rabobank|11,scotiabank|11,td (canada)|11,discord|12,drive|13,element|14,facebook|15,messenger|16,firefox|17,firefox beta|17,firefox nightly|17,f-droid|5,neo store|5,aurora droid|5,github|18,gitlab|19,gmx|20,google|21,google home|22,google play store|23,home assistant|24,instagram|25,kalender|26,keep notes|27,lieferando|28,linkedin|29,maps|30,organic maps|30,osmand|30,mastodon|31,fedilab|31,tooot|31,tusky|31,mattermost|32,n26|33,netflix|34,news|35,cbc news|35,rc info|35,reuters|35,ap news|35,la presse|35,nbc news|35,nextbike|36,nina|37,outlook mail|38,paypal|39,phone|40,plex|41,pocket|42,post & dhl|43,proton mail|44,reddit|45,sync pro|45,sync dev|45,boost|45,infinity|45,slide|45,signal|46,skype|47,slack|48,snapchat|49,starbucks|50,steam|51,teams|52,telegram|53,telegram foss|53,threema|54,tiktok|55,to do|56,opentasks|56,tasks|56,transit|57,twitch|58,twitter|59,uber|60,lyft|60,vlc|61,warnapp|62,whatsapp|63,wordfeud|64,youtube|65,newpipe|65,zoom|66,meet|66,music|67,sms message|0,mail|0,gmail|0,".match(new RegExp(`,${s}\\|(\\d+)`))
let match = ",default|0,airbnb|1,alarm|2,alarmclockreceiver|2,amazon shopping|3,bibel|4,bitwarden|5,1password|5,lastpass|5,dashlane|5,bring|6,calendar|7,etar|7,chat|8,chrome|9,clock|2,corona-warn|10,bmo|11,desjardins|11,rbc mobile|11,nbc|11,rabobank|11,scotiabank|11,td (canada)|11,discord|12,drive|13,element|14,facebook|15,messenger|16,firefox|17,firefox beta|17,firefox nightly|17,f-droid|5,neo store|5,aurora droid|5,github|18,gitlab|19,gmx|20,google|21,google home|22,google play store|23,home assistant|24,instagram|25,jira|26,kalender|27,keep notes|28,lieferando|29,linkedin|30,maps|31,organic maps|31,osmand|31,mastodon|32,fedilab|32,tooot|32,tusky|32,mattermost|33,n26|34,netflix|35,news|36,cbc news|36,rc info|36,reuters|36,ap news|36,la presse|36,nbc news|36,nextbike|37,nina|38,outlook mail|39,paypal|40,phone|41,plex|42,pocket|43,post & dhl|44,proton mail|45,reddit|46,sync pro|46,sync dev|46,boost|46,infinity|46,slide|46,signal|47,skype|48,slack|49,snapchat|50,starbucks|51,steam|52,teams|53,telegram|54,telegram foss|54,threema|55,tiktok|56,to do|57,opentasks|57,tasks|57,transit|58,twitch|59,twitter|60,uber|61,lyft|61,vlc|62,warnapp|63,whatsapp|64,wordfeud|65,youtube|66,newpipe|66,zoom|67,meet|67,music|68,sms message|0,mail|0,gmail|0,".match(new RegExp(`,${s}\\|(\\d+)`))
return require("Storage").read("messageicons.img", (match===null)?0:match[1]*76, 76);
};
@ -13,6 +13,7 @@ exports.getColor = function(msg,options) {
if (st.iconColorMode == 'mono') return options.default;
const s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase();
return {
// This file is generated by /icons/generate.js. If you need to modify its content, you should do it there instead.
// generic colors, using B2-safe colors
// DO NOT USE BLACK OR WHITE HERE, just leave the declaration out and then the theme's fg color will be used
"airbnb": "#ff385c", // https://news.airbnb.com/media-assets/category/brand/
@ -33,6 +34,7 @@ exports.getColor = function(msg,options) {
"google home": "#fbbc05",
// "home assistant": "#41bdf5", // ha-blue is #41bdf5, but that's the background
"instagram": "#ff0069", // https://about.instagram.com/brand/gradient
"jira": "#0052cc", //https://atlassian.design/resources/logo-library
"lieferando": "#ff8000",
"linkedin": "#0a66c2", // https://brand.linkedin.com/
"messenger": "#0078ff",

View File

@ -1,7 +1,7 @@
{
"id": "messageicons",
"name": "Message Icons",
"version": "0.04",
"version": "0.05",
"description": "Library containing a list of icons and colors for apps",
"icon": "app.png",
"type": "module",

View File

@ -3,4 +3,5 @@
0.03: Optional show lock status via color
0.04: Ensure that widgets are always hidden in fullscreen mode
0.05: Better lock/unlock animation
0.06: Use widget_utils.
0.06: Use widget_utils
0.07: Convert Yes/No On/Off in settings to checkboxes

View File

@ -2,7 +2,7 @@
"id": "neonx",
"name": "Neon X & IO X Clock",
"shortName": "Neon X Clock",
"version": "0.06",
"version": "0.07",
"description": "Pebble Neon X & Neon IO X for Bangle.js",
"icon": "neonx.png",
"type": "clock",

View File

@ -25,11 +25,9 @@
"" : { "title":"Neon X & IO"},
"< Back": back,
"Neon IO X": {
value: 0 | neonXSettings.io,
min: 0, max: 1,
format: v => v ? "On" : "Off",
value: !!neonXSettings.io,
onchange: v => {
neonXSettings.io = v;
neonXSettings.io = v?1:0;
updateSettings();
}
},
@ -43,27 +41,23 @@
}
},
"Date on touch": {
value: 0 | neonXSettings.showDate,
min: 0, max: 1,
format: v => v ? "On" : "Off",
value: !!neonXSettings.showDate,
onchange: v => {
neonXSettings.showDate = v;
neonXSettings.showDate = v?1:0;
updateSettings();
}
},
'Fullscreen': {
value: false | neonXSettings.fullscreen,
format: () => (neonXSettings.fullscreen ? 'Yes' : 'No'),
onchange: () => {
neonXSettings.fullscreen = !neonXSettings.fullscreen;
value: !!neonXSettings.fullscreen,
onchange: v => {
neonXSettings.fullscreen = v;
updateSettings();
},
},
'Show lock': {
value: false | neonXSettings.showLock,
format: () => (neonXSettings.showLock ? 'Yes' : 'No'),
onchange: () => {
neonXSettings.showLock = !neonXSettings.showLock;
value: !!neonXSettings.showLock,
onchange: v => {
neonXSettings.showLock = v;
updateSettings();
},
},

View File

@ -8,3 +8,4 @@
0.08: Add new draw styles, tidy up draw functionality
0.09: Tweak for faster rendering
0.10: Enhance for use with Bangle2, insert new draw mode 'thickfill'
0.11: Convert Yes/No On/Off in settings to checkboxes

View File

@ -2,7 +2,7 @@
"id": "numerals",
"name": "Numerals Clock",
"shortName": "Numerals Clock",
"version": "0.10",
"version": "0.11",
"description": "A simple big numerals clock",
"icon": "numerals.png",
"type": "clock",

View File

@ -30,10 +30,8 @@
onchange: v=> { numeralsSettings.drawMode=dm[v]; updateSettings();}
},
"Date on touch": {
value: 0|numeralsSettings.showDate,
min:0,max:1,
format: v=>v?"On":"Off",
onchange: v=> { numeralsSettings.showDate=v; updateSettings();}
value: !!numeralsSettings.showDate,
onchange: v=> { numeralsSettings.showDate=v?1:0; updateSettings();}
},
"< back": back
};

View File

@ -4,3 +4,8 @@
0.04: Remove calibration with current voltage (Calibrate->Auto) as it is now handled by settings app
Allow automatic calibration on every charge longer than 3 hours
0.05: Add back button to settings menu.
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

@ -7,6 +7,26 @@ Features:
* Force monotonic battery percentage or voltage
* Automatic calibration on charging uninterrupted longer than 3 hours (reloads of the watch reset the timer).
## Widget
The widget shows an approximate current power use. There is a power gauge showing the estimation of the currently used power and the currently active sensor with the biggest power draw.
G for GPS, H for pulse sensor and C for compass.
## Logging
You can switch on logging in the options to diagnose unexpected power use. Currently the logging can capture the code running from timeouts and intervals and the power up and down of some devices. The captured times are probably not perfect but should be good enough to indicate problems.
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
@ -14,6 +34,10 @@ Battery calibration offset is set by writing `batFullVoltage` in setting.json
## TODO
* Optionally keep battery history and show as graph
* Capture some more stuff in logging
* Event driven code execution
* Buzzer
* Better tracking of display on time
## Creator

View File

@ -4,10 +4,115 @@
require('Storage').readJSON("powermanager.json", true) || {}
);
if (settings.log) {
let logFile = require('Storage').open("powermanager.log","a");
let def = require('Storage').readJSON("powermanager.def.json", true) || {};
if (!def.start) def.start = Date.now();
if (!def.deferred) def.deferred = {};
let hw = require('Storage').readJSON("powermanager.hw.json", true) || {};
if (!hw.start) hw.start = Date.now();
if (!hw.power) hw.power = {};
const saveEvery = 1000 * 60 * 5;
const TO_WRAP = ["GPS","Compass","Barometer","HRM","LCD"];
let save = ()=>{
let defExists = require("Storage").read("powermanager.def.json")!==undefined;
if (!(!defExists && def.saved)){
def.saved = Date.now();
require('Storage').writeJSON("powermanager.def.json", def);
}
let hwExists = require("Storage").read("powermanager.hw.json")!==undefined;
if (!(!hwExists && hw.saved)){
hw.saved = Date.now();
require('Storage').writeJSON("powermanager.hw.json", hw);
}
}
setInterval(save, saveEvery);
E.on("kill", ()=>{
for (let c of TO_WRAP){
if (lastPowerOn[c] && Bangle["is"+c+"On"]()){
hw.power[c] += Date.now() - lastPowerOn[c];
}
}
save();
});
let logPower = (type, oldstate, state, app) => {
logFile.write("p," + type + ',' + (oldstate?1:0) + ',' + (state?1:0) + ',' + app + "\n");
};
let logDeferred = (type, duration, source) => {
logFile.write(type + ',' + duration + ',' + source.replace(/\n/g, " ").replace(/,/g,"") + "\n");
};
let lastPowerOn = {};
for (let c of TO_WRAP){
let functionName = "set" + c + "Power";
let checkName = "is" + c + "On";
let type = c + "";
lastPowerOn[type] = (!lastPowerOn[type] && Bangle[checkName]()) ? Date.now() : undefined;
lastPowerOn[type] = Date.now();
Bangle[functionName] = ((o) => (a,b) => {
let oldstate = Bangle[checkName]();
let result = o(a,b);
if (!lastPowerOn[type] && result) {
//switched on, store time
lastPowerOn[type] = Date.now();
} else if (lastPowerOn[type] && !result){
//switched off
hw.power[type] += Date.now() - lastPowerOn[type];
lastPowerOn[type] = undefined;
}
if (settings.logDetails) logPower(type, oldstate, result, b);
return result;
})(Bangle[functionName]);
}
let functions = {};
let wrapDeferred = ((o,t) => (a) => {
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(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);
if (!def.deferred[f]) def.deferred[f] = 0;
def.deferred[f] += end;
return result;
};
//copy over properties of functions
for (let p in a){
wrapped[p] = a[p];
}
//mark function as wrapped
wrapped.__wrapped = true;
}
let newArgs = arguments.slice();
newArgs[0] = wrapped;
return o.apply(this, newArgs);
}
});
global.setTimeout = wrapDeferred(global.setTimeout, "t");
global.setInterval = wrapDeferred(global.setInterval, "i");
}
if (settings.warnEnabled){
var chargingInterval;
function handleCharging(charging){
let handleCharging = (charging) => {
if (charging){
if (chargingInterval) clearInterval(chargingInterval);
chargingInterval = setInterval(()=>{
@ -20,7 +125,7 @@
clearInterval(chargingInterval);
chargingInterval = undefined;
}
}
};
Bangle.on("charging",handleCharging);
handleCharging(Bangle.isCharging());

View File

@ -2,5 +2,6 @@
"warnEnabled": false,
"warn": 96,
"forceMonoVoltage": false,
"forceMonoPercentage": false
"forceMonoPercentage": false,
"log": false
}

View File

@ -0,0 +1,268 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<div id="content"></div>
<script src="../../core/lib/interface.js"></script>
<script>
var domContent = document.getElementById("content");
function download(filename, callback) {
Util.showModal("Downloading power info...");
Util.readStorage(filename, data => {
Util.hideModal();
callback(data);
});
}
function show() {
Util.showModal("Loading...");
domContent.innerHTML = "";
var htmlOverview = `<table class="table table-striped table-hover">
<div>This needs "Logging" to be enabled in power manager settings. The deferred function calls table is only updated on the bangle on reloads.</div>
<thead>
<tr>
<th>Type</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>Deferred function calls</td>
<td>
<button class="btn btn-primary" filename="powermanager.def.json" task="deftable">Table</button>
<button class="btn btn-error" filename="powermanager.def.json" task="clear" style="float: right;margin-right: 5px;">Clear</button>
</td>
</tr>
<tr>
<td>Hardware</td>
<td>
<button class="btn btn-primary" filename="powermanager.hw.json" task="hardwaretable">Table</button>
<button class="btn btn-error" filename="powermanager.hw.json" task="clear" style="float: right;margin-right: 5px;">Clear</button>
</td>
</tr>
<tr>
<td>Details (Trace)</td>
<td>
<button class="btn btn-primary" filename="powermanager.log" task="detailstable">Table</button>
<button class="btn btn-error" filename="powermanager.log" task="detailsclear" style="float: right;margin-right: 5px;">Clear</button>
</td>
</tr>
</tbody>
</table>`;
domContent.innerHTML = htmlOverview;
Util.hideModal();
var buttons = domContent.querySelectorAll("button");
for (var i=0;i<buttons.length;i++) {
buttons[i].addEventListener("click",event => {
var button = event.currentTarget;
var filename = button.getAttribute("filename");
if (!filename) return;
var task = button.getAttribute("task");
if (task=="detailsclear") {
Util.showModal("Clearing...");
Util.eraseStorageFile(filename,()=>{
Util.hideModal();
show();
});
}
if (task=="clear") {
Util.showModal("Clearing...");
Util.eraseStorage(filename,()=>{
Util.hideModal();
show();
});
}
if (task=="deftable") {
viewDeferredTable(filename);
}
if (task=="hardwaretable") {
viewHardwareTable(filename);
}
if (task=="detailstable") {
viewDetailsTable(filename);
}
});
}
}
function viewDeferredTable(filename) {
Puck.eval(`require("Storage").list("powermanager.def.json").length > 0`, (f)=>{
if (f) {
Util.showModal("Reading summarized info...");
Util.readStorage(
filename, data => {
Util.hideModal();
let parsed = JSON.parse(data);
let sum = 0;
let rows = [];
for (var i in parsed.deferred) {
sum += parsed.deferred[i];
rows.push({func: i, time: parsed.deferred[i]});
}
rows.sort((a,b)=>{return b.time/sum - a.time/sum;});
let tableRows = "";
for (var i in rows) {
let c = rows[i];
tableRows += `<tr>
<td>${(c.time/1000).toFixed(2)}s</td>
<td>${(c.time/sum*100).toFixed(2)}%</td>
<td><pre>${c.func}</pre></td>`
}
let duration = parsed.saved - parsed.start;
var htmlOverview = `<h1>Deferred function calls</h1>
<button class="btn btn-primary" id="back" style="float: right;margin-right: 5px;margin-left: 10px;">Back</button>
<div>
This are functions used in timeouts and intervals and their accumulated execution times. Recorded in a time span of <b>${Math.round((duration)/1000)}s</b>. Timeouts/intervals have run for <b>${Math.round(sum/1000)}s (${(sum/duration*100).toFixed(2)}%)</b>. Percentages are calculated from summarized timeout/interval running time.
</div>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Time</th>
<th>Percentage</th>
<th>Function</th>
</tr>
</thead>
<tbody>\n`;
htmlOverview += tableRows;
htmlOverview += `</tbody></table>`;
domContent.innerHTML = htmlOverview;
domContent.querySelector("#back").addEventListener("click",event => {
show();
});
//try finding possible sources for the given function, currently does not work because function.toString() not being identical to code in the *.js files.
/*Puck.eval(`require("Storage").list(/.*.js$/)`, (f)=>{
console.log("Found files:", f, rows[1].func);
for (let file of f){
let query = `require("Storage").read('${file}').includes('${rows[1].func}')`;
console.log("Query: " + query);
Puck.eval(query, (r)=>{
if (r) domContent.querySelector("#row_1").innerHTML = file;
console.log("Found", file, r)
});
}
});*/
});
} else {
var htmlOverview = `<h1>Deferred function calls</h1>
<button class="btn btn-primary" id="back" style="float: right;margin-right: 5px;">Back</button>
<div>
No data available.
</div>`;
domContent.innerHTML = htmlOverview;
domContent.querySelector("#back").addEventListener("click",event => {
show();
});
}
});
}
function viewHardwareTable(filename) {
Puck.eval(`require("Storage").list("powermanager.hw.json").length > 0`, (f)=>{
if (f) {
Util.showModal("Reading hardware info...");
Util.readStorage(
filename, data => {
Util.hideModal();
let parsed = JSON.parse(data);
console.log("Hardware", parsed);
let duration = parsed.saved - parsed.start;
let rows = [];
for (var i in parsed.power) {
rows.push({func: i, time: parsed.power[i]});
}
rows.sort((a,b)=>{return b.time/duration - a.time/duration;});
let tableRows = "";
for (var i in rows) {
let c = rows[i];
tableRows += `<tr>
<td>${(c.time/1000).toFixed(2)}s</td>
<td>${(c.time/duration*100).toFixed(2)}%</td>
<td>${c.func}</td>`
}
var htmlOverview = `<h1>Hardware power</h1>
<button class="btn btn-primary" id="back" style="float: right;margin-right: 5px;margin-left: 10px;">Back</button>
<div>
Recorded in a time span of <b>${Math.round(duration/1000)}s</b>. Percentages are calculated from recording time.
</div>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Time</th>
<th>Percentage</th>
<th>Device</th>
</tr>
</thead>
<tbody>\n`;
htmlOverview += tableRows;
htmlOverview += `</tbody></table>`;
domContent.innerHTML = htmlOverview;
domContent.querySelector("#back").addEventListener("click",event => {
show();
});
});
} else {
var htmlOverview = `<h1>Hardware power</h1>
<button class="btn btn-primary" id="back" style="float: right;margin-right: 5px;">Back</button>
<div>
No data available.
</div>`;
domContent.innerHTML = htmlOverview;
domContent.querySelector("#back").addEventListener("click",event => {
show();
});
}
});
}
function viewDetailsTable(filename) {
Util.showModal("Reading details info...");
Util.readStorageFile(
filename, data => {
Util.hideModal();
var htmlOverview = `<h1>Detailed logging</h1>
This is a trace log of all logged power entries, first column denotes the type. p for power, i for interval and t for timeout. Power is logged with old state, new state and calling app if available. Functions are logged with execution duraion and source if available.
<button class="btn btn-primary" id="back" style="float: right;margin-right: 5px;">Back</button>
<table class="table table-striped table-hover">
<tbody>\n`;
let rows = data.trim().split("\n");
for (var row of rows) {
let cols = row.split(",");
htmlOverview += `<tr>
<td>${cols[0]}</td>
<td>${cols[1]}</td>
<td>${cols[2]}</td>
<td>${cols[3]}</td>
<td>${cols[4]}</td>
</tr>`
}
htmlOverview += `</tbody></table>`;
domContent.innerHTML = htmlOverview;
domContent.querySelector("#back").addEventListener("click",event => {
show();
});
});
}
function onInit() {
show();
}
</script>
</body>
</html>

View File

@ -2,17 +2,25 @@
"id": "powermanager",
"name": "Power Manager",
"shortName": "Power Manager",
"version": "0.05",
"version": "0.08",
"description": "Allow configuration of warnings and thresholds for battery charging and display.",
"icon": "app.png",
"type": "bootloader",
"tags": "tool",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"interface": "interface.html",
"storage": [
{"name":"powermanager.boot.js","url":"boot.js"},
{"name":"powermanager.settings.js","url":"settings.js"},
{"name":"powermanager.wid.js","url":"widget.js"},
{"name":"powermanager","url":"lib.js"},
{"name":"powermanager.default.json","url":"default.json"}
],
"data": [
{"name":"powermanager.hw.json"},
{"name":"powermanager.def.json"},
{"name":"powermanager.json"},
{"name":"powermanager.log"}
]
}

View File

@ -24,6 +24,9 @@
'title': 'Power Manager'
},
"< Back" : back,
'Widget': function() {
E.showMenu(submenu_widget);
},
'Monotonic percentage': {
value: !!settings.forceMonoPercentage,
onchange: v => {
@ -41,6 +44,9 @@
},
'Calibrate': function() {
E.showMenu(submenu_calibrate);
},
'Logging': function() {
E.showMenu(submenu_logging);
}
};
@ -100,7 +106,6 @@
},
'Enabled': {
value: !!settings.warnEnabled,
format: v => settings.warnEnabled ? "On" : "Off",
onchange: v => {
writeSettings("warnEnabled", v);
}
@ -117,5 +122,61 @@
}
};
var submenu_logging = {
'': {
title: "Logging",
back: function() {
E.showMenu(mainmenu);
},
},
'Enabled': {
value: !!settings.log,
onchange: v => {
writeSettings("log", v);
}
},
'Trace': {
value: !!settings.logDetails,
onchange: v => {
writeSettings("logDetails", v);
}
}
}
var submenu_widget = {
'': {
title: "Widget",
back: function() {
E.showMenu(mainmenu);
},
},
'Enabled': {
value: !!settings.widget,
onchange: v => {
writeSettings("widget", v);
}
},
'Refresh': {
min: 0.5,
max: 60,
step: 0.5,
value: settings.refreshUnlocked || 1,
format: v => v + "s",
onchange: v => {
writeSettings("refreshUnlocked", v);
}
},
'Refresh locked': {
min: 5,
max: 120,
step: 5,
value: settings.refreshLocked || 60,
format: v => v + "s",
onchange: v => {
writeSettings("refreshLocked", v);
}
}
}
E.showMenu(mainmenu);
})

116
apps/powermanager/widget.js Normal file
View File

@ -0,0 +1,116 @@
/* run widgets in their own function scope so they don't interfere with
currently-running apps */
(() => {
const s = require("Storage").readJSON("powermanager.json") || {};
if (!s.widget) return;
const SYSTICKMAX = peek32(0xE000E014);
const SYSTICKWAIT = SYSTICKMAX/64000; // 64 MHz clock rate, Systick counting down on every non idle clock
const GU = require("graphics_utils");
const APPROX_IDLE = 0.3;
const APPROX_HIGH_BW_BLE = 0.5;
const APPROX_COMPASS = process.HWVERSION == 2 ? 5.5 : 2;
const APPROX_HRM = process.HWVERSION == 2 ? 1 : 2.5;
const APPROX_CPU = 3;
const APPROX_GPS = process.HWVERSION == 2 ? 25 : 30;
const APPROX_TOUCH = 2.5;
const APPROX_BACKLIGHT = process.HWVERSION == 2 ? 16 : 40;
const MAX = APPROX_IDLE + APPROX_HIGH_BW_BLE + APPROX_COMPASS + APPROX_HRM + APPROX_CPU + APPROX_GPS + APPROX_TOUCH + APPROX_BACKLIGHT;
let settings = require("Storage").readJSON("setting.json") || {};
let brightnessSetting = settings.brightness || 1;
Bangle.setLCDBrightness = ((o) => (a) => {
brightnessSetting = a;
WIDGETS.powermanager.draw(WIDGETS.powermanager);
return o(a);
})(Bangle.setLCDBrightness);
let brightness = () => {
return process.HWVERSION == 2 ? (brightnessSetting * APPROX_BACKLIGHT) : (brightnessSetting * 0.9 * 33 + 7);
};
function doDraw(w, cpu){
g.reset();
let current = APPROX_IDLE + cpu * APPROX_CPU;
let mostExpensive = "P";
if (!Bangle.isLocked()) current += APPROX_TOUCH + brightness();
if (Bangle.isCompassOn()) {
current += APPROX_COMPASS;
mostExpensive = "C";
}
if (Bangle.isHRMOn()) {
current += APPROX_HRM;
mostExpensive = "H";
}
if (Bangle.isGPSOn()) {
current += APPROX_GPS;
mostExpensive = "G";
}
current = current / MAX;
g.clearRect(w.x, w.y, w.x + 23, w.y + 23);
g.setColor(g.theme.fg);
g.setFont6x15();
g.setFontAlign(0, 0);
g.drawString(mostExpensive, w.x + 12, w.y + 15);
let end = 135 + (current * (405 - 135));
g.setColor(current > 0.7 ? "#f00" : (current > 0.3 ? "#ff0" : "#0f0"));
GU.fillArc(g, w.x + 12, w.y + 12, 9, 12, GU.degreesToRadians(135), GU.degreesToRadians(end), GU.degreesToRadians(30));
g.setColor(g.theme.fg);
let endCpu = 135 + (cpu * (405 - 135));
GU.fillArc(g, w.x + 12, w.y + 12, 5.5, 8, GU.degreesToRadians(135), GU.degreesToRadians(endCpu), GU.degreesToRadians(30));
}
let sTimeout;
let s2Timeout;
let systickDiff;
function draw(w) {
let nextRefresh = Bangle.isLocked() ? ((s.refreshLocked || 60) * 1000 ): ((s.refreshUnlocked || 1) * 1000)
if (s2Timeout) clearTimeout(s2Timeout);
if (sTimeout) clearTimeout(sTimeout);
let t,systickNow;
sTimeout = setTimeout(()=>{
systickNow = peek32(0xE000E018);
t = Date.now();
}, nextRefresh - SYSTICKWAIT - 100);
s2Timeout = setTimeout(() => {
let tLater = Date.now();
let systickLater = peek32(0xE000E018);
systickDiff = systickLater - systickNow;
if (systickDiff < 0) systickDiff += SYSTICKMAX;
}, nextRefresh - 100);
doDraw(w, systickDiff ? (1 - systickDiff/SYSTICKMAX) : 0);
if (w.timeoutId !== undefined) {
clearTimeout(w.timeoutId);
}
w.timeoutId = setTimeout(() => {
w.timeoutId = undefined;
w.draw(w);
}, nextRefresh);
}
// add your widget
WIDGETS.powermanager = {
area: "tl",
width: 24,
draw: draw
};
Bangle.on("lock", ()=>{WIDGETS.powermanager.draw(WIDGETS.powermanager);});
// conserve memory
delete settings;
})();

View File

@ -25,3 +25,4 @@
0.19: Fix track plotting code
0.20: Automatic translation of some more strings.
0.21: Speed report now uses speed units from locale
0.22: Convert Yes/No On/Off in settings to checkboxes

View File

@ -56,7 +56,6 @@ function showMainMenu() {
'< Back': ()=>{load();},
/*LANG*/'RECORD': {
value: !!settings.recording,
format: v=>v?/*LANG*/"On":/*LANG*/"Off",
onchange: v => {
setTimeout(function() {
E.showMenu();

View File

@ -2,7 +2,7 @@
"id": "recorder",
"name": "Recorder",
"shortName": "Recorder",
"version": "0.21",
"version": "0.22",
"description": "Record GPS position, heart rate and more in the background, then download to your PC.",
"icon": "app.png",
"tags": "tool,outdoors,gps,widget",

View File

@ -13,3 +13,4 @@
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: Keep run state between runs (allowing you to exit and restart the app)

View File

@ -41,6 +41,13 @@ var statIDs = [settings.B1,settings.B2,settings.B3,settings.B4,settings.B5,setti
var exs = ExStats.getStats(statIDs, settings);
// ---------------------------
function setStatus(running) {
layout.button.label = running ? "STOP" : "START";
layout.status.label = running ? "RUN" : "STOP";
layout.status.bgCol = running ? "#0f0" : "#f00";
layout.render();
}
// Called to start/stop running
function onStartStop() {
var running = !exs.state.active;
@ -77,12 +84,9 @@ function onStartStop() {
} else {
exs.stop();
}
layout.button.label = running ? "STOP" : "START";
layout.status.label = running ? "RUN" : "STOP";
layout.status.bgCol = running ? "#0f0" : "#f00";
// if stopping running, don't clear state
// so we can at least refer to what we've done
layout.render();
setStatus(running);
});
}
@ -105,13 +109,14 @@ for (var i=0;i<statIDs.length;i+=2) {
lc.push({ type:"h", filly:1, c:[
{type:"txt", font:fontHeading, label:"GPS", id:"gps", fillx:1, bgCol:"#f00" },
{type:"txt", font:fontHeading, label:"00:00", id:"clock", fillx:1, bgCol:g.theme.fg, col:g.theme.bg },
{type:"txt", font:fontHeading, label:"STOP", id:"status", fillx:1 }
{type:"txt", font:fontHeading, label:"---", id:"status", fillx:1 }
]});
// Now calculate the layout
var layout = new Layout( {
type:"v", c: lc
},{lazy:true, btns:[{ label:"START", cb: onStartStop, id:"button"}]});
},{lazy:true, btns:[{ label:"---", cb: onStartStop, id:"button"}]});
delete lc;
setStatus(exs.state.active);
layout.render();
function configureNotification(stat) {

View File

@ -1,6 +1,6 @@
{ "id": "run",
"name": "Run",
"version":"0.14",
"version":"0.15",
"description": "Displays distance, time, steps, cadence, pace and more for runners.",
"icon": "app.png",
"tags": "run,running,fitness,outdoors,gps",

View File

@ -14,5 +14,6 @@
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 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

@ -52,11 +52,18 @@ let statIDs = [settings.B1,settings.B2,settings.B3,settings.B4,settings.B5,setti
let exs = ExStats.getStats(statIDs, settings);
// ---------------------------
function setStatus(running) {
layout.button.label = running ? "STOP" : "START";
layout.status.label = running ? "RUN" : "STOP";
layout.status.bgCol = running ? "#0f0" : "#f00";
layout.render();
}
// Called to start/stop running
function onStartStop() {
let running = !exs.state.active;
let prepPromises = [];
https://github.com/espruino/BangleApps/pull/2600/conflict?name=apps%252Frunplus%252Fapp.js&ancestor_oid=15e0427592259f3112ee70d43bc7ce6fab20e1d8&base_oid=06270850f707ea94957a2990fb83425e7dc68d7f&head_oid=633e47983e015446796f69837501c8fc72255031
// start/stop recording
// Do this first in case recorder needs to prompt for
// an overwrite before we start tracking exstats
@ -88,12 +95,9 @@ function onStartStop() {
} else {
exs.stop();
}
layout.button.label = running ? "STOP" : "START";
layout.status.label = running ? "RUN" : "STOP";
layout.status.bgCol = running ? "#0f0" : "#f00";
// if stopping running, don't clear state
// so we can at least refer to what we've done
layout.render();
setStatus(running);
});
}
@ -116,13 +120,14 @@ for (let i=0;i<statIDs.length;i+=2) {
lc.push({ type:"h", filly:1, c:[
{type:"txt", font:fontHeading, label:"GPS", id:"gps", fillx:1, bgCol:"#f00" },
{type:"txt", font:fontHeading, label:"00:00", id:"clock", fillx:1, bgCol:g.theme.fg, col:g.theme.bg },
{type:"txt", font:fontHeading, label:"STOP", id:"status", fillx:1 }
{type:"txt", font:fontHeading, label:"---", id:"status", fillx:1 }
]});
// Now calculate the layout
let layout = new Layout( {
type:"v", c: lc
},{lazy:true, btns:[{ label:"START", cb: ()=>{if (karvonenActive) {stopKarvonenUI();run();} onStartStop();}, id:"button"}]});
},{lazy:true, btns:[{ label:"---", cb: ()=>{if (karvonnenActive) {stopKarvonnenUI();run();} onStartStop();}, id:"button"}]});
delete lc;
setStatus(exs.state.active);
layout.render();
function configureNotification(stat) {

View File

@ -63,3 +63,4 @@
0.56: make System menu items shorter and more consistant, Eg 'Clock', intead
of 'Select Clock'
0.57: Settings.log = 0,1,2,3 for off,display,log,both
0.58: On/Off settings items now use checkboxes

View File

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

View File

@ -64,8 +64,6 @@ if (("object" != typeof settings) ||
("object" != typeof settings.options))
resetSettings();
const boolFormat = v => v ? /*LANG*/"On" : /*LANG*/"Off";
function showMainMenu() {
const mainmenu = {
@ -102,7 +100,6 @@ function showAlertsMenu() {
if (BANGLEJS2) {
beepMenuItem = {
value: settings.beep!=false,
format: boolFormat,
onchange: v => {
settings.beep = v;
updateSettings();
@ -134,7 +131,6 @@ function showAlertsMenu() {
/*LANG*/'Beep': beepMenuItem,
/*LANG*/'Vibration': {
value: settings.vibrate,
format: boolFormat,
onchange: () => {
settings.vibrate = !settings.vibrate;
updateSettings();
@ -169,7 +165,6 @@ function showBLEMenu() {
/*LANG*/'Make Connectable': ()=>makeConnectable(),
/*LANG*/'BLE': {
value: settings.ble,
format: boolFormat,
onchange: () => {
settings.ble = !settings.ble;
updateSettings();
@ -177,7 +172,6 @@ function showBLEMenu() {
},
/*LANG*/'Programmable': {
value: settings.blerepl,
format: boolFormat,
onchange: () => {
settings.blerepl = !settings.blerepl;
updateSettings();
@ -428,7 +422,6 @@ function showLCDMenu() {
},
/*LANG*/'Wake on BTN1': {
value: settings.options.wakeOnBTN1,
format: boolFormat,
onchange: () => {
settings.options.wakeOnBTN1 = !settings.options.wakeOnBTN1;
updateOptions();
@ -439,7 +432,6 @@ function showLCDMenu() {
Object.assign(lcdMenu, {
/*LANG*/'Wake on BTN2': {
value: settings.options.wakeOnBTN2,
format: boolFormat,
onchange: () => {
settings.options.wakeOnBTN2 = !settings.options.wakeOnBTN2;
updateOptions();
@ -447,7 +439,6 @@ function showLCDMenu() {
},
/*LANG*/'Wake on BTN3': {
value: settings.options.wakeOnBTN3,
format: boolFormat,
onchange: () => {
settings.options.wakeOnBTN3 = !settings.options.wakeOnBTN3;
updateOptions();
@ -456,7 +447,6 @@ function showLCDMenu() {
Object.assign(lcdMenu, {
/*LANG*/'Wake on FaceUp': {
value: settings.options.wakeOnFaceUp,
format: boolFormat,
onchange: () => {
settings.options.wakeOnFaceUp = !settings.options.wakeOnFaceUp;
updateOptions();
@ -464,7 +454,6 @@ function showLCDMenu() {
},
/*LANG*/'Wake on Touch': {
value: settings.options.wakeOnTouch,
format: boolFormat,
onchange: () => {
settings.options.wakeOnTouch = !settings.options.wakeOnTouch;
updateOptions();
@ -472,7 +461,6 @@ function showLCDMenu() {
},
/*LANG*/'Wake on Twist': {
value: settings.options.wakeOnTwist,
format: boolFormat,
onchange: () => {
settings.options.wakeOnTwist = !settings.options.wakeOnTwist;
updateOptions();

View File

@ -17,3 +17,5 @@
0.14: Reduce update interval of current time when seconds are not shown
Limit logging on Bangle.js 1 to one day due to low memory
Add plot logged data to settings
0.15: Convert Yes/No On/Off in settings to checkboxes
0.16: Fix Keep alarm enabled inverted settings

View File

@ -2,7 +2,7 @@
"id": "sleepphasealarm",
"name": "SleepPhaseAlarm",
"shortName": "SleepPhaseAlarm",
"version": "0.14",
"version": "0.16",
"description": "Uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments (ESS, see https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en). This app will read the next alarm from the alarm application and will wake you up to 30 minutes early at the best guessed time when you are almost already awake.",
"icon": "app.png",
"tags": "tool,alarm",

View File

@ -90,10 +90,9 @@
E.showMenu({
"" : { "title" : "SleepPhaseAlarm" },
'Keep alarm enabled': {
value: !!config.settings.disableAlarm,
format: v => v?"No":"Yes",
value: !config.settings.disableAlarm,
onchange: v => {
config.settings.disableAlarm = v;
config.settings.disableAlarm = !v;
writeSettings();
}
}, "< Back" : () => back(),

View File

@ -9,3 +9,4 @@
0.09: Added button control toggle and other live controls to new settings screen.
0.10: Tell clock widgets to hide.
0.11: Added new styling and watch faces
0.12: Convert Yes/No On/Off in settings to checkboxes

View File

@ -1,7 +1,7 @@
{
"id": "slidingtext",
"name": "Sliding Clock",
"version": "0.11",
"version": "0.12",
"description": "Inspired by the Pebble sliding clock, old times are scrolled off the screen and new times on. You are also able to change language on the fly so you can see the time written in other languages using button 1. Currently English, French, Japanese, Spanish and German are supported",
"icon": "slidingtext.png",
"screenshots": [{"url":"slidingtext-screenshot.english.png"},{"url":"slidingtext-screenshot.english2.png"},{"url":"slidingtext-screenshot.hybrid.png"}],

View File

@ -173,8 +173,7 @@
"Colour": stringInSettings("color_scheme", ["black","white", "red","grey","purple","blue"]),
"Style": stringInSettings("date_format", locales, (l)=>locale_mappings[l] ),
"Live Control": {
value: (settings.enable_live_controls !== undefined ? settings.enable_live_controls : true),
format: v => v ? "On" : "Off",
value: (settings.enable_live_controls !== undefined ? !!settings.enable_live_controls : true),
onchange: v => {
settings.enable_live_controls = v;
writeSettings();

View File

@ -15,8 +15,8 @@
'': {'title': 'SloMo Clock'},
'< Back': back,
'Colours' : function() { E.showMenu(colMenu); }
//,'Widget Space Top' : {value : settings.widTop, format : v => v?"On":"Off",onchange : () => { settings.widTop = !settings.widTop; writeSettings(); }
//,'Widget Space Bottom' : {value : settings.widBot, format : v => v?"On":"Off",onchange : () => { settings.widBot = !settings.widBot; writeSettings(); }
//,'Widget Space Top' : {value : settings.widTop, onchange : () => { settings.widTop = !settings.widTop; writeSettings(); }
//,'Widget Space Bottom' : {value : settings.widBot, onchange : () => { settings.widBot = !settings.widBot; writeSettings(); }
};
const colMenu = {

View File

@ -6,3 +6,4 @@
0.06: Added adjustment for Bangle.js magnetometer heading fix
0.07: Add settings file with the option to disable the slow direction updates
0.08: Use tilt compensation from new magnav library
0.09: Convert Yes/No On/Off in settings to checkboxes

View File

@ -1,7 +1,7 @@
{
"id": "waypointer",
"name": "Way Pointer",
"version": "0.08",
"version": "0.09",
"description": "Navigate to a waypoint using the GPS for bearing and compass to point way, uses the same waypoint interface as GPS Navigation",
"icon": "waypointer.png",
"tags": "tool,outdoors,gps",

View File

@ -15,7 +15,6 @@
"< Back" : () => back(),
'Smooth arrow rot': {
value: !!settings.smoothDirection,
format: v => v?"Yes":"No",
onchange: v => {
settings.smoothDirection = v;
writeSettings();

View File

@ -0,0 +1,8 @@
# Charging Status
This widget shows a yellow lightning icon to indicate that the watch is charging. A short buzz is also emitted on initial charging connection.
Nothing is shown when not charging.
# TypeScript
This app is written in TypeScript, see [typescript/README.md](/typescript/README.md) for more info

View File

@ -5,3 +5,4 @@
0.03: Fix Bell not appearing on alarms > 24h and redrawing interval
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

View File

@ -2,7 +2,7 @@
"id": "widalarmeta",
"name": "Alarm & Timer ETA",
"shortName": "Alarm ETA",
"version": "0.04",
"version": "0.05",
"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

@ -26,7 +26,6 @@
},
/*LANG*/'Draw bell': {
value: !!settings.drawBell,
format: v => v?/*LANG*/"Yes":/*LANG*/"No",
onchange: v => {
settings.drawBell = v;
writeSettings();

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

@ -1,2 +1,3 @@
0.01: Initial version
0.02: Checks for correct firmware; E.setDST(...) moved to boot.js
0.03: Convert Yes/No On/Off in settings to checkboxes

View File

@ -1,6 +1,6 @@
{ "id": "widdst",
"name": "Daylight Saving",
"version":"0.02",
"version":"0.03",
"description": "Widget to set daylight saving rules. Requires Espruino 2v14.49 or later - see the instructions below for more information.",
"icon": "icon.png",
"type": "widget",

View File

@ -138,16 +138,14 @@
},
"< Back": () => back(),
/*LANG*/"Enabled": {
value: settings.has_dst,
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
value: !!settings.has_dst,
onchange: v => {
settings.has_dst = v;
writeSettings();
}
},
/*LANG*/"Icon": {
value: settings.show_icon,
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
value: !!settings.show_icon,
onchange: v => {
settings.show_icon = v;
writeSettings();

View File

@ -148,7 +148,7 @@
</label>
<label class="form-switch">
<input type="checkbox" id="settings-minify">
<i class="form-icon"></i> Minify apps before upload (smaller, faster apps - BETA)
<i class="form-icon"></i> Minify apps before upload (BETA, not recommended. Uploads smaller, faster apps but this may cause some apps to stop working)
</label>
<label class="form-switch">
<input type="checkbox" id="settings-settime">

View File

@ -93,6 +93,16 @@ var state = {
// list of active stats (indexed by ID)
var stats = {};
const DATA_FILE = "exstats.json";
// Load the state from a saved file if there was one
state = Object.assign(state, require("Storage").readJSON(DATA_FILE,1)||{});
// force step history to a uint8array
state.stepHistory = new Uint8Array(state.stepHistory);
// when we exit, write the current state
E.on('kill', function() {
require("Storage").writeJSON(DATA_FILE, state);
});
// distance between 2 lat and lons, in meters, Mean Earth Radius = 6371km
// https://www.movable-type.co.uk/scripts/latlong.html
// (Equirectangular approximation)
@ -359,9 +369,10 @@ exports.getStats = function(statIDs, options) {
state.notify.time.next = state.startTime + options.notify.time.increment;
}
}
reset();
if (!state.active) reset(); // we might already be active
return {
stats : stats, state : state,
stats : stats,
state : state,
start : function() {
state.active = true;
reset();

View File

@ -13,7 +13,7 @@
"removeComments": true,
"newLine": "lf",
"noEmitHelpers": false,
"noEmitHelpers": true, // we link to specific banglejs implementations
"noEmitOnError": false,
"preserveConstEnums": false,
"importsNotUsedAsValues": "error",

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

@ -8688,6 +8688,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 +8718,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 +8963,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;
}
/**