forked from FOSS/BangleApps
Merge branch 'espruino:master' into master
commit
f1d0088fe7
72
apps.json
72
apps.json
|
@ -2,7 +2,7 @@
|
||||||
{
|
{
|
||||||
"id": "fwupdate",
|
"id": "fwupdate",
|
||||||
"name": "Firmware Update",
|
"name": "Firmware Update",
|
||||||
"version": "0.02",
|
"version": "0.03",
|
||||||
"description": "[BETA] Uploads new Espruino firmwares to Bangle.js 2. For now, please use the instructions under https://www.espruino.com/Bangle.js2#firmware-updates",
|
"description": "[BETA] Uploads new Espruino firmwares to Bangle.js 2. For now, please use the instructions under https://www.espruino.com/Bangle.js2#firmware-updates",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "RAM",
|
"type": "RAM",
|
||||||
|
@ -768,7 +768,7 @@
|
||||||
"id": "recorder",
|
"id": "recorder",
|
||||||
"name": "Recorder (BETA)",
|
"name": "Recorder (BETA)",
|
||||||
"shortName": "Recorder",
|
"shortName": "Recorder",
|
||||||
"version": "0.05",
|
"version": "0.06",
|
||||||
"description": "Record GPS position, heart rate and more in the background, then download to your PC.",
|
"description": "Record GPS position, heart rate and more in the background, then download to your PC.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool,outdoors,gps,widget",
|
"tags": "tool,outdoors,gps,widget",
|
||||||
|
@ -936,7 +936,7 @@
|
||||||
"id": "widbatpc",
|
"id": "widbatpc",
|
||||||
"name": "Battery Level Widget (with percentage)",
|
"name": "Battery Level Widget (with percentage)",
|
||||||
"shortName": "Battery Widget",
|
"shortName": "Battery Widget",
|
||||||
"version": "0.15",
|
"version": "0.16",
|
||||||
"description": "Show the current battery level and charging status in the top right of the clock, with charge percentage",
|
"description": "Show the current battery level and charging status in the top right of the clock, with charge percentage",
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
"type": "widget",
|
"type": "widget",
|
||||||
|
@ -1040,16 +1040,19 @@
|
||||||
"id": "bthrm",
|
"id": "bthrm",
|
||||||
"name": "Bluetooth Heart Rate Monitor",
|
"name": "Bluetooth Heart Rate Monitor",
|
||||||
"shortName": "BT HRM",
|
"shortName": "BT HRM",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "boot",
|
"type": "app",
|
||||||
"tags": "health,bluetooth",
|
"tags": "health,bluetooth",
|
||||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
|
{"name":"bthrm.app.js","url":"bthrm.js"},
|
||||||
|
{"name":"bthrm.recorder.js","url":"recorder.js"},
|
||||||
{"name":"bthrm.boot.js","url":"boot.js"},
|
{"name":"bthrm.boot.js","url":"boot.js"},
|
||||||
{"name":"bthrm.img","url":"app-icon.js","evaluate":true}
|
{"name":"bthrm.img","url":"app-icon.js","evaluate":true},
|
||||||
|
{"name":"bthrm.settings.js","url":"settings.js"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1501,7 +1504,7 @@
|
||||||
{
|
{
|
||||||
"id": "gpsinfo",
|
"id": "gpsinfo",
|
||||||
"name": "GPS Info",
|
"name": "GPS Info",
|
||||||
"version": "0.06",
|
"version": "0.08",
|
||||||
"description": "An application that displays information about altitude, lat/lon, satellites and time",
|
"description": "An application that displays information about altitude, lat/lon, satellites and time",
|
||||||
"icon": "gps-info.png",
|
"icon": "gps-info.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
|
@ -1590,7 +1593,7 @@
|
||||||
{
|
{
|
||||||
"id": "widpedom",
|
"id": "widpedom",
|
||||||
"name": "Pedometer widget",
|
"name": "Pedometer widget",
|
||||||
"version": "0.20",
|
"version": "0.22",
|
||||||
"description": "Daily pedometer widget",
|
"description": "Daily pedometer widget",
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
"type": "widget",
|
"type": "widget",
|
||||||
|
@ -4239,8 +4242,9 @@
|
||||||
{
|
{
|
||||||
"id": "antonclk",
|
"id": "antonclk",
|
||||||
"name": "Anton Clock",
|
"name": "Anton Clock",
|
||||||
"version": "0.03",
|
"version": "0.04",
|
||||||
"description": "A simple clock using the bold Anton font.",
|
"description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.",
|
||||||
|
"readme":"README.md",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot.png"}],
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
@ -4249,8 +4253,10 @@
|
||||||
"allow_emulator": true,
|
"allow_emulator": true,
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"antonclk.app.js","url":"app.js"},
|
{"name":"antonclk.app.js","url":"app.js"},
|
||||||
|
{"name":"antonclk.settings.js","url":"settings.js"},
|
||||||
{"name":"antonclk.img","url":"app-icon.js","evaluate":true}
|
{"name":"antonclk.img","url":"app-icon.js","evaluate":true}
|
||||||
]
|
],
|
||||||
|
"data": [{"name":"antonclk.json"}]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "waveclk",
|
"id": "waveclk",
|
||||||
|
@ -4474,7 +4480,7 @@
|
||||||
"name": "A Battery Widget (with percentage)",
|
"name": "A Battery Widget (with percentage)",
|
||||||
"shortName":"A Battery Widget",
|
"shortName":"A Battery Widget",
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
"version":"1.02",
|
"version":"1.03",
|
||||||
"type": "widget",
|
"type": "widget",
|
||||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
|
@ -4984,7 +4990,7 @@
|
||||||
{ "id": "pooqround",
|
{ "id": "pooqround",
|
||||||
"name": "pooq Round watch face",
|
"name": "pooq Round watch face",
|
||||||
"shortName":"pooq Round",
|
"shortName":"pooq Round",
|
||||||
"version":"0.01",
|
"version":"0.02",
|
||||||
"description": "A 24 hour analogue watchface with high legibility and a novel style.",
|
"description": "A 24 hour analogue watchface with high legibility and a novel style.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
@ -5162,15 +5168,16 @@
|
||||||
{
|
{
|
||||||
"id": "promenu",
|
"id": "promenu",
|
||||||
"name": "Pro Menu",
|
"name": "Pro Menu",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "Replace Bangle.js 1's built in menu function.",
|
"description": "Replace the built in menu function. Supports Bangle.js 1 and Bangle.js 2.",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"type": "boot",
|
"type": "boot",
|
||||||
"tags": "system",
|
"tags": "system",
|
||||||
"supports": ["BANGLEJS"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"screenshots": [{"url":"pro-menu-screenshot.png"}],
|
"screenshots": [{"url":"pro-menu-screenshot.png"}],
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"promenu.boot.js","url":"boot.js"},
|
{"name":"promenu.boot.js","url":"boot.js","supports": ["BANGLEJS"]},
|
||||||
|
{"name":"promenu.boot.js","url":"bootb2.js","supports": ["BANGLEJS2"]},
|
||||||
{"name":"promenu.img","url":"promenuIcon.js","evaluate":true}
|
{"name":"promenu.img","url":"promenuIcon.js","evaluate":true}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -5427,5 +5434,36 @@
|
||||||
{"name":"puzzle15.img","url":"puzzle15.app-icon.js","evaluate":true}
|
{"name":"puzzle15.img","url":"puzzle15.app-icon.js","evaluate":true}
|
||||||
],
|
],
|
||||||
"data": [{"name":"puzzle15.json"}]
|
"data": [{"name":"puzzle15.json"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flipper",
|
||||||
|
"name": "flipper",
|
||||||
|
"version": "0.01",
|
||||||
|
"description": "Switch between dark and light theme and vice versa, combine with pattern launcher and swipe to flip.",
|
||||||
|
"readme":"README.md",
|
||||||
|
"screenshots": [{"url":"flipper.png"}],
|
||||||
|
"icon": "flipper.png",
|
||||||
|
"type": "app",
|
||||||
|
"tags": "game",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"allow_emulator": true,
|
||||||
|
"storage": [
|
||||||
|
{"name":"flipper.app.js","url":"flipper.app.js"},
|
||||||
|
{"name":"flipper.img","url":"flipper.icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "id": "ruuviwatch",
|
||||||
|
"name": "Ruuvi Watch",
|
||||||
|
"shortName":"Ruuvi Watch",
|
||||||
|
"icon": "ruuviwatch.png",
|
||||||
|
"version":"1.01",
|
||||||
|
"description": "Keep an eye on RuuviTag devices (https://ruuvi.com). Only shows RuuviTags using the v5 format.",
|
||||||
|
"readme":"README.md",
|
||||||
|
"tags": "bluetooth",
|
||||||
|
"supports": ["BANGLEJS"],
|
||||||
|
"storage": [
|
||||||
|
{"name":"ruuviwatch.app.js","url":"ruuviwatch.app.js"},
|
||||||
|
{"name":"ruuviwatch.img","url":"ruuviwatch.app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Load widgets after setUI so widclk knows when to hide
|
0.02: Load widgets after setUI so widclk knows when to hide
|
||||||
0.03: Clock now shows day of week under date.
|
0.03: Clock now shows day of week under date.
|
||||||
|
0.04: Clock can optionally show seconds, date optionally in ISO-8601 format, weekdays and uppercase configurable, too.
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
# Anton Clock - Large font digital watch with seconds and date
|
||||||
|
|
||||||
|
Anton clock uses the "Anton" bold font to show the time in a clear, easily readable manner. On the Bangle.js 2, the time can be read easily even if the screen is locked and unlit.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
The basic time representation only shows hours and minutes of the current time. However, Anton clock can show additional information:
|
||||||
|
|
||||||
|
* Seconds can be shown, either always or only if the screen is unlocked.
|
||||||
|
* To help easy recognition, the seconds can be coloured in blue on the Bangle.js 2.
|
||||||
|
* Date can be shown in three different formats:
|
||||||
|
* ISO-8601: 2021-12-19
|
||||||
|
* short local format: 19/12/2021, 19.12.2021
|
||||||
|
* long local format: DEC 19 2021
|
||||||
|
* Weekday can be shown (on seconds screen only instead of year)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Install Anton clock through the Bangle.js app loader.
|
||||||
|
Configure it through the default Bangle.js configuration mechanism
|
||||||
|
(Settings app, "Apps" menu, "Anton clock" submenu).
|
||||||
|
If you like it, make it your default watch face
|
||||||
|
(Settings app, "System" menu, "Clock" submenu, select "Anton clock").
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Anton clock is configured by the standard settings mechanism of Bangle.js's operating system:
|
||||||
|
Open the "Settings" app, then the "Apps" submenu and below it the "Anton clock" menu.
|
||||||
|
You configure Anton clock through several "on/off" switches in two menus.
|
||||||
|
|
||||||
|
### The main menu
|
||||||
|
|
||||||
|
The main menu contains several settings covering Anton clock in general.
|
||||||
|
|
||||||
|
* **Seconds...** - Opens the submenu for configuring the presentation of the current time's seconds.
|
||||||
|
* **Date** - Format of the date representation. Possible values are
|
||||||
|
* **Long** - "Long" date format in the current locale. Usually with the month as name, not number.
|
||||||
|
* **Short** - "Short" date format in the current locale. Usually with the month as number.
|
||||||
|
* **ISO8601** - Show the date in ISO-8601 format (YYYY-MM-DD), irrespective of the current locale.
|
||||||
|
* **Show Weekday** - Weekday is shown in the time presentation without seconds.
|
||||||
|
Weekday name depends on the current locale.
|
||||||
|
If seconds are shown, the weekday is never shown as there is not enough space on the watch face.
|
||||||
|
* **Uppercase** - Weekday name and month name in the long format are converted to upper case letters.
|
||||||
|
This can improve readability.
|
||||||
|
* **Vector font** - Use the built-in vector font for dates and weekday.
|
||||||
|
This can improve readability.
|
||||||
|
Otherwise, a scaled version of the built-in 6x8 pixels font is used.
|
||||||
|
|
||||||
|
### The "Seconds" submenu
|
||||||
|
|
||||||
|
The "Seconds" submenu configures how (and if) seconds are shown on the "Anton" watch face.
|
||||||
|
|
||||||
|
* **Show** - Configure when the seconds should be shown at all:
|
||||||
|
* **Never** - Seconds are never shown.
|
||||||
|
In this case, hour and minute are a bit more centered on the screen and the clock will always only update every minute.
|
||||||
|
This saves battery power.
|
||||||
|
* **Unlocked** - Seconds are shown if the display is unlocked.
|
||||||
|
On locked displays, only hour, minutes, date and optionally the weekday are shown.
|
||||||
|
_This option is highly recommended on the Bangle.js 2!_
|
||||||
|
* **Always** - Seconds are _always_ shown, irrespective of the display's unlock state.
|
||||||
|
_Enabling this option increases power consumption as the watch face will update once per second instead of once per minute._
|
||||||
|
* **With ":"** - If enabled, a colon ":" is prepended to the seconds.
|
||||||
|
This resembles the usual time representation "hh:mm:ss", even though the seconds are printed on a separate line.
|
||||||
|
* **Color** - If enabled, seconds are shown in blue instead of black.
|
||||||
|
If the date is shown on the seconds screen, it is colored read instead of black.
|
||||||
|
This make the visual orientation much easier on the watch face.
|
||||||
|
* **Date** - It is possible to show the date together with the seconds:
|
||||||
|
* **No** - Date is _not_ shown in the seconds screen.
|
||||||
|
In this case, the seconds are centered below hour and minute.
|
||||||
|
* **Year** - Date is shown with day, month, and year. If "Date" in the main settings is configured to _ISO8601_, this is used here, too. Otherwise, the short local format is used.
|
||||||
|
* **Weekday** - Date is shown with day, month, and weekday.
|
||||||
|
|
||||||
|
The date is coloured in red if the "Coloured" option is chosen.
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
Anton clock makes use of core Bangle.js 2 features (coloured display, display lock state). It also runs on the Bangle.js 1 but these features are not available there due to hardware restrictions.
|
File diff suppressed because one or more lines are too long
Binary file not shown.
Before Width: | Height: | Size: 759 B After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
Before Width: | Height: | Size: 696 B After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,99 @@
|
||||||
|
// Settings menu for the enhanced Anton clock
|
||||||
|
|
||||||
|
(function(back) {
|
||||||
|
var FILE = "antonclk.json";
|
||||||
|
// Load settings
|
||||||
|
var settings = Object.assign({
|
||||||
|
secondsOnUnlock: false,
|
||||||
|
}, require('Storage').readJSON(FILE, true) || {});
|
||||||
|
|
||||||
|
function writeSettings() {
|
||||||
|
require('Storage').writeJSON(FILE, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method which uses int-based menu item for set of string values
|
||||||
|
function stringItems(startvalue, writer, values) {
|
||||||
|
return {
|
||||||
|
value: (startvalue === undefined ? 0 : values.indexOf(startvalue)),
|
||||||
|
format: v => values[v],
|
||||||
|
min: 0,
|
||||||
|
max: values.length - 1,
|
||||||
|
wrap: true,
|
||||||
|
step: 1,
|
||||||
|
onchange: v => {
|
||||||
|
writer(values[v]);
|
||||||
|
writeSettings();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method which breaks string set settings down to local settings object
|
||||||
|
function stringInSettings(name, values) {
|
||||||
|
return stringItems(settings[name], v => settings[name] = v, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
var mainmenu = {
|
||||||
|
"": {
|
||||||
|
"title": "Anton clock"
|
||||||
|
},
|
||||||
|
"< Back": () => back(),
|
||||||
|
"Seconds...": () => E.showMenu(secmenu),
|
||||||
|
"Date": stringInSettings("dateOnMain", ["Short", "Long", "ISO8601"]),
|
||||||
|
"Show Weekday": {
|
||||||
|
value: (settings.weekDay !== undefined ? settings.weekDay : true),
|
||||||
|
format: v => v ? "On" : "Off",
|
||||||
|
onchange: v => {
|
||||||
|
settings.weekDay = v;
|
||||||
|
writeSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Uppercase": {
|
||||||
|
value: (settings.upperCase !== undefined ? settings.upperCase : false),
|
||||||
|
format: v => v ? "On" : "Off",
|
||||||
|
onchange: v => {
|
||||||
|
settings.upperCase = v;
|
||||||
|
writeSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Vector font": {
|
||||||
|
value: (settings.vectorFont !== undefined ? settings.vectorFont : false),
|
||||||
|
format: v => v ? "On" : "Off",
|
||||||
|
onchange: v => {
|
||||||
|
settings.vectorFont = v;
|
||||||
|
writeSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Submenu
|
||||||
|
var secmenu = {
|
||||||
|
"": {
|
||||||
|
"title": "Show seconds..."
|
||||||
|
},
|
||||||
|
"< Back": () => E.showMenu(mainmenu),
|
||||||
|
"Show": stringInSettings("secondsMode", ["Never", "Unlocked", "Always"]),
|
||||||
|
"With \":\"": {
|
||||||
|
value: (settings.secondsWithColon !== undefined ? settings.secondsWithColon : false),
|
||||||
|
format: v => v ? "On" : "Off",
|
||||||
|
onchange: v => {
|
||||||
|
settings.secondsWithColon = v;
|
||||||
|
writeSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Color": {
|
||||||
|
value: (settings.secondsColoured !== undefined ? settings.secondsColoured : false),
|
||||||
|
format: v => v ? "On" : "Off",
|
||||||
|
onchange: v => {
|
||||||
|
settings.secondsColoured = v;
|
||||||
|
writeSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Date": stringInSettings("dateOnSecs", ["No", "Year", "Weekday"])
|
||||||
|
};
|
||||||
|
|
||||||
|
// Actually display the menu
|
||||||
|
E.showMenu(mainmenu);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// end of file
|
|
@ -1 +1,4 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
|
0.02: Make overriding the HRM event optional
|
||||||
|
Emit BTHRM event for external sensor
|
||||||
|
Add recorder app plugin
|
||||||
|
|
|
@ -3,23 +3,42 @@
|
||||||
var gatt;
|
var gatt;
|
||||||
var status;
|
var status;
|
||||||
|
|
||||||
Bangle.isHRMOn = function() {
|
var origIsHRMOn = Bangle.isHRMOn;
|
||||||
|
|
||||||
|
Bangle.isBTHRMOn = function(){
|
||||||
return (status=="searching" || status=="connecting") || (gatt!==undefined);
|
return (status=="searching" || status=="connecting") || (gatt!==undefined);
|
||||||
}
|
}
|
||||||
Bangle.setHRMPower = function(isOn, app) {
|
|
||||||
|
Bangle.isHRMOn = function() {
|
||||||
|
var settings = require('Storage').readJSON("bthrm.json", true) || {};
|
||||||
|
|
||||||
|
print(settings);
|
||||||
|
if (settings.enabled && !settings.replace){
|
||||||
|
return origIsHRMOn();
|
||||||
|
} else if (settings.enabled && settings.replace){
|
||||||
|
return Bangle.isBTHRMOn();
|
||||||
|
}
|
||||||
|
return origIsHRMOn() || Bangle.isBTHRMOn();
|
||||||
|
}
|
||||||
|
|
||||||
|
Bangle.setBTHRMPower = function(isOn, app) {
|
||||||
|
|
||||||
|
|
||||||
|
var settings = require('Storage').readJSON("bthrm.json", true) || {};
|
||||||
|
|
||||||
// Do app power handling
|
// Do app power handling
|
||||||
if (!app) app="?";
|
if (!app) app="?";
|
||||||
log("setHRMPower ->", isOn, app);
|
log("setBTHRMPower ->", isOn, app);
|
||||||
if (Bangle._PWR===undefined) Bangle._PWR={};
|
if (Bangle._PWR===undefined) Bangle._PWR={};
|
||||||
if (Bangle._PWR.HRM===undefined) Bangle._PWR.HRM=[];
|
if (Bangle._PWR.BTHRM===undefined) Bangle._PWR.BTHRM=[];
|
||||||
if (isOn && !Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM.push(app);
|
if (isOn && !Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM.push(app);
|
||||||
if (!isOn && Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM = Bangle._PWR.HRM.filter(a=>a!=app);
|
if (!isOn && Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM = Bangle._PWR.BTHRM.filter(a=>a!=app);
|
||||||
isOn = Bangle._PWR.HRM.length;
|
isOn = Bangle._PWR.BTHRM.length;
|
||||||
// so now we know if we're really on
|
// so now we know if we're really on
|
||||||
if (isOn) {
|
if (isOn) {
|
||||||
log("setHRMPower on", app);
|
log("setBTHRMPower on", app);
|
||||||
if (!Bangle.isHRMOn()) {
|
if (!Bangle.isBTHRMOn()) {
|
||||||
log("HRM not already on");
|
log("BTHRM not already on");
|
||||||
status = "searching";
|
status = "searching";
|
||||||
NRF.requestDevice({ filters: [{ services: ['180D'] }] }).then(function(device) {
|
NRF.requestDevice({ filters: [{ services: ['180D'] }] }).then(function(device) {
|
||||||
log("Found device "+device.id);
|
log("Found device "+device.id);
|
||||||
|
@ -49,7 +68,11 @@
|
||||||
if (flags&16) {
|
if (flags&16) {
|
||||||
var interval = dv.getUint16(idx,1); // in milliseconds
|
var interval = dv.getUint16(idx,1); // in milliseconds
|
||||||
}*/
|
}*/
|
||||||
Bangle.emit('HRM',{
|
|
||||||
|
|
||||||
|
var eventName = settings.replace ? "HRM" : "BTHRM";
|
||||||
|
|
||||||
|
Bangle.emit(eventName, {
|
||||||
bpm:bpm,
|
bpm:bpm,
|
||||||
confidence:100
|
confidence:100
|
||||||
});
|
});
|
||||||
|
@ -65,15 +88,27 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else { // not on
|
} else { // not on
|
||||||
log("setHRMPower off", app);
|
log("setBTHRMPower off", app);
|
||||||
if (gatt) {
|
if (gatt) {
|
||||||
log("HRM connected - disconnecting");
|
log("BTHRM connected - disconnecting");
|
||||||
status = undefined;
|
status = undefined;
|
||||||
try {gatt.disconnect();}catch(e) {
|
try {gatt.disconnect();}catch(e) {
|
||||||
log("HRM disconnect error", e);
|
log("BTHRM disconnect error", e);
|
||||||
}
|
}
|
||||||
gatt = undefined;
|
gatt = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var origSetHRMPower = Bangle.setHRMPower;
|
||||||
|
|
||||||
|
Bangle.setHRMPower = function(isOn, app) {
|
||||||
|
var settings = require('Storage').readJSON("bthrm.json", true) || {};
|
||||||
|
if (settings.enabled || !isOn){
|
||||||
|
Bangle.setBTHRMPower(isOn, app);
|
||||||
|
}
|
||||||
|
if (settings.enabled && !settings.replace || !isOn){
|
||||||
|
origSetHRMPower(isOn, app);
|
||||||
|
}
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
var btm = g.getHeight()-1;
|
||||||
|
var eventInt = null;
|
||||||
|
var eventBt = null;
|
||||||
|
var counterInt = 0;
|
||||||
|
var counterBt = 0;
|
||||||
|
|
||||||
|
|
||||||
|
function draw(y, event, type, counter) {
|
||||||
|
var px = g.getWidth()/2;
|
||||||
|
g.reset();
|
||||||
|
g.setFontAlign(0,0);
|
||||||
|
g.clearRect(0,y,g.getWidth(),y+80);
|
||||||
|
if (type == null || event == null || counter == 0) return;
|
||||||
|
var str = event.bpm + "";
|
||||||
|
g.setFontVector(40).drawString(str,px,y+20);
|
||||||
|
str = "Confidence: " + event.confidence;
|
||||||
|
g.setFontVector(12).drawString(str,px,y+50);
|
||||||
|
str = "Event: " + type;
|
||||||
|
g.setFontVector(12).drawString(str,px,y+60);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBtHrm(e) {
|
||||||
|
print("Event for BT " + JSON.stringify(e));
|
||||||
|
counterBt += 5;
|
||||||
|
eventBt = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHrm(e) {
|
||||||
|
print("Event for Int " + JSON.stringify(e));
|
||||||
|
counterInt += 5;
|
||||||
|
eventInt = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bangle.on('BTHRM', onBtHrm);
|
||||||
|
Bangle.on('HRM', onHrm);
|
||||||
|
|
||||||
|
Bangle.setHRMPower(1,'bthrm')
|
||||||
|
Bangle.setBTHRMPower(1,'bthrm')
|
||||||
|
|
||||||
|
g.clear();
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
||||||
|
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16);
|
||||||
|
|
||||||
|
function drawInt(){
|
||||||
|
counterInt--;
|
||||||
|
if (counterInt < 0) counterInt = 0;
|
||||||
|
if (counterInt > 5) counterInt = 5;
|
||||||
|
draw(24, eventInt, "HRM", counterInt);
|
||||||
|
}
|
||||||
|
function drawBt(){
|
||||||
|
counterBt--;
|
||||||
|
if (counterBt < 0) counterBt = 0;
|
||||||
|
if (counterBt > 5) counterBt = 5;
|
||||||
|
draw(100, eventBt, "BTHRM", counterBt);
|
||||||
|
}
|
||||||
|
|
||||||
|
var interval = setInterval(drawInt, 1000);
|
||||||
|
var interval = setInterval(drawBt, 1000);
|
|
@ -0,0 +1,27 @@
|
||||||
|
(function(recorders) {
|
||||||
|
recorders.bthrm = function() {
|
||||||
|
var bpm = 0;
|
||||||
|
function onHRM(h) {
|
||||||
|
bpm = h.bpm;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name : "BTHR",
|
||||||
|
fields : ["BT Heartrate"],
|
||||||
|
getValues : () => {
|
||||||
|
result = [bpm];
|
||||||
|
bpm = 0;
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
start : () => {
|
||||||
|
Bangle.on('BTHRM', onHRM);
|
||||||
|
Bangle.setBTHRMPower(1,"recorder");
|
||||||
|
},
|
||||||
|
stop : () => {
|
||||||
|
Bangle.removeListener('BTHRM', onHRM);
|
||||||
|
Bangle.setBTHRMPower(0,"recorder");
|
||||||
|
},
|
||||||
|
draw : (x,y) => g.setColor(Bangle.isBTHRMOn()?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
(function(back) {
|
||||||
|
var FILE = "bthrm.json";
|
||||||
|
|
||||||
|
var settings = Object.assign({
|
||||||
|
enabled: true,
|
||||||
|
replace: true,
|
||||||
|
}, require('Storage').readJSON(FILE, true) || {});
|
||||||
|
|
||||||
|
function writeSettings() {
|
||||||
|
require('Storage').writeJSON(FILE, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
E.showMenu({
|
||||||
|
'': { 'title': 'Bluetooth HRM' },
|
||||||
|
'< Back': back,
|
||||||
|
'Use BT HRM': {
|
||||||
|
value: !!settings.enabled,
|
||||||
|
format: v => settings.enabled ? "On" : "Off",
|
||||||
|
onchange: v => {
|
||||||
|
settings.enabled = v;
|
||||||
|
writeSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Use HRM event': {
|
||||||
|
value: !!settings.replace,
|
||||||
|
format: v => settings.replace ? "On" : "Off",
|
||||||
|
onchange: v => {
|
||||||
|
settings.replace = v;
|
||||||
|
writeSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
|
@ -11,4 +11,4 @@ The year itself begins on the December solstice. Because that always happens, th
|
||||||
|
|
||||||
The epoch (year numbering) begins in the last year when the perihelion coincided with the June solstice, near the beginning of the Holocene era. That astronomical basis makes the calendar free from politics, religion, or geography.
|
The epoch (year numbering) begins in the last year when the perihelion coincided with the June solstice, near the beginning of the Holocene era. That astronomical basis makes the calendar free from politics, religion, or geography.
|
||||||
|
|
||||||
While the year number remains cardinal, BTN5 toggles between cardinal and ordinal for the rest of the calendar segments. BTN4 adds or removes a quickly changing digit to or from the clock.
|
While the year number remains cardinal, tapping on the right side of the watch face toggles between cardinal and ordinal for the rest of the calendar segments. Tapping on the left adds or removes a quickly changing digit to or from the clock.
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: first release
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Flipper
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*A utility to switch from the dark to the light theme and vice versa*
|
||||||
|
|
||||||
|
* If the current theme is dark it will switch to the light theme
|
||||||
|
* If the current theme is light it will switch to the dark theme
|
||||||
|
* Combine with the awesome pattern launcher and it saves loads of time
|
||||||
|
|
||||||
|
|
||||||
|
## Demo Video
|
||||||
|
|
||||||
|
There's no screenshot but there is a [demo video.](https://espruino.microco.sm/api/v1/files/9caa1afef7e4cce1d9b518af2dd271f1a57c5ecc.mp4)
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
* Nothing left to add
|
||||||
|
|
||||||
|
Written by: [Hugh Barney](https://github.com/hughbarney) For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/)
|
|
@ -0,0 +1,39 @@
|
||||||
|
const storage = require('Storage');
|
||||||
|
let settings = storage.readJSON('setting.json', 1);
|
||||||
|
|
||||||
|
function cl(x) { return g.setColor(x).getColor(); }
|
||||||
|
|
||||||
|
function upd(th) {
|
||||||
|
g.theme = th;
|
||||||
|
settings.theme = th;
|
||||||
|
storage.write('setting.json', settings);
|
||||||
|
delete g.reset;
|
||||||
|
g._reset = g.reset;
|
||||||
|
g.reset = function(n) { return g._reset().setColor(th.fg).setBgColor(th.bg); };
|
||||||
|
g.clear = function(n) { if (n) g.reset(); return g.clearRect(0,0,g.getWidth(),g.getHeight()); };
|
||||||
|
g.clear(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function flipTheme() {
|
||||||
|
if (!g.theme.dark) {
|
||||||
|
upd({
|
||||||
|
fg:cl("#fff"), bg:cl("#000"),
|
||||||
|
fg2:cl("#0ff"), bg2:cl("#000"),
|
||||||
|
fgH:cl("#fff"), bgH:cl("#00f"),
|
||||||
|
dark:true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
upd({
|
||||||
|
fg:cl("#000"), bg:cl("#fff"),
|
||||||
|
fg2:cl("#000"), bg2:cl("#cff"),
|
||||||
|
fgH:cl("#000"), bgH:cl("#0ff"),
|
||||||
|
dark:false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
flipTheme();
|
||||||
|
setTimeout(load, 20);
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEw4X/AAO/mMUzs975K+ggLKysUAYNVqoLFitUoAKBqtQBYkJBIQABqwLEgQLEqtABggJDqkVBaoNCBZQwEgILWgoJENYsVBIcVBYpDEgpSIBYMBKQg6CuogCBY1UgoLCXAQLDqAsDBYhSBqEJHAoLDoEBcQ4LBEwILIMooLdIg4LaVoyaGERLcFao4LIdRAACYYUQBY5RKAH4Ar"))
|
Binary file not shown.
After Width: | Height: | Size: 644 B |
|
@ -2,3 +2,5 @@
|
||||||
0.02: Add support for ZIPs
|
0.02: Add support for ZIPs
|
||||||
Find and download ZIPs direct from the Espruino website
|
Find and download ZIPs direct from the Espruino website
|
||||||
Take 'beta' tag off
|
Take 'beta' tag off
|
||||||
|
0.03: Improve bootloader update safety. Now sets unsafeFlash:1 to allow flash with 2v11 and later
|
||||||
|
Add CRC checks for common bootloaders that we know don't work
|
||||||
|
|
|
@ -60,6 +60,7 @@ function onInit(device) {
|
||||||
document.getElementById("fw-unknown").style = "display:none";
|
document.getElementById("fw-unknown").style = "display:none";
|
||||||
document.getElementById("fw-ok").style = "";
|
document.getElementById("fw-ok").style = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkForFileOnServer() {
|
function checkForFileOnServer() {
|
||||||
|
@ -264,6 +265,8 @@ function createJS_app(binary, startAddress, endAddress) {
|
||||||
bin32[3] = VERSION; // VERSION! Use this to test ourselves
|
bin32[3] = VERSION; // VERSION! Use this to test ourselves
|
||||||
console.log("CRC 0x"+bin32[2].toString(16));
|
console.log("CRC 0x"+bin32[2].toString(16));
|
||||||
hexJS = "";//`\x10if (E.CRC32(E.memoryArea(${startAddress},${endAddress-startAddress}))==${bin32[2]}) { print("FIRMWARE UP TO DATE!"); load();}\n`;
|
hexJS = "";//`\x10if (E.CRC32(E.memoryArea(${startAddress},${endAddress-startAddress}))==${bin32[2]}) { print("FIRMWARE UP TO DATE!"); load();}\n`;
|
||||||
|
hexJS += `\x10if (E.CRC32(E.memoryArea(0xF7000,0x7000))==1339551013) { print("BOOTLOADER 2v10.219 needs update"); load();}\n`;
|
||||||
|
hexJS += `\x10if (E.CRC32(E.memoryArea(0xF7000,0x7000))==1207580954) { print("BOOTLOADER 2v10.236 needs update"); load();}\n`;
|
||||||
hexJS += '\x10var s = require("Storage");\n';
|
hexJS += '\x10var s = require("Storage");\n';
|
||||||
hexJS += '\x10s.erase(".firmware");\n';
|
hexJS += '\x10s.erase(".firmware");\n';
|
||||||
var CHUNKSIZE = 2048;
|
var CHUNKSIZE = 2048;
|
||||||
|
@ -291,20 +294,14 @@ function createJS_bootloader(binary, startAddress, endAddress) {
|
||||||
var chunk = btoa(new Uint8Array(binary.buffer, binary.byteOffset+i, l));
|
var chunk = btoa(new Uint8Array(binary.buffer, binary.byteOffset+i, l));
|
||||||
hexJS += '\x10_fw.set(atob("'+chunk+'"), 0x'+(i).toString(16)+');\n';
|
hexJS += '\x10_fw.set(atob("'+chunk+'"), 0x'+(i).toString(16)+');\n';
|
||||||
}
|
}
|
||||||
// hexJS += `\x10(function() {
|
hexJS += `\x10(function() { if (E.CRC32(_fw)!=${crc}) throw "Invalid CRC: 0x"+E.CRC32(_fw).toString(16);\n`;
|
||||||
// if (E.CRC32(_fw)!=${crc}) throw "Invalid CRC!";
|
hexJS += 'E.showMessage("Flashing Bootloader...")\n';
|
||||||
// var f = require("Flash");
|
hexJS += 'E.setFlags({unsafeFlash:1})\n';
|
||||||
// for (var i=${startAddress};i<${endAddress};i+=4096) f.erasePage(i);
|
hexJS += 'var f = require("Flash");\n';
|
||||||
// f.write(_fw,${startAddress});
|
|
||||||
// E.reboot();
|
|
||||||
// })();\n`;
|
|
||||||
hexJS += `\x10if (E.CRC32(_fw)!=${crc}) throw "Invalid CRC: 0x"+E.CRC32(_fw).toString(16);\n`;
|
|
||||||
hexJS += '\x10var f = require("Flash");\n';
|
|
||||||
for (var i=startAddress;i<endAddress;i+=4096)
|
for (var i=startAddress;i<endAddress;i+=4096)
|
||||||
hexJS += '\x10f.erasePage(0x'+i.toString(16)+');\n';
|
hexJS += 'f.erasePage(0x'+i.toString(16)+');\n';
|
||||||
hexJS += `\x10f.write(_fw,${startAddress});\n`;
|
hexJS += `f.write(_fw,${startAddress});\n`;
|
||||||
// hexJS += '\x10setTimeout(()=>E.showMessage("Rebooting..."),50);\n';
|
hexJS += `})()\n`;
|
||||||
// hexJS += '\x10setTimeout(()=>E.reboot(), 2000);\n';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function fileLoaded() {
|
function fileLoaded() {
|
||||||
|
|
|
@ -2,4 +2,6 @@
|
||||||
0.03: Show number of satellites while waiting for fix
|
0.03: Show number of satellites while waiting for fix
|
||||||
0.04: Add Maidenhead readout of GPS location
|
0.04: Add Maidenhead readout of GPS location
|
||||||
0.05: Refactor to use 'layout' library for multi-device support
|
0.05: Refactor to use 'layout' library for multi-device support
|
||||||
0.06: Added number of satellites in view and fixed crash with GPS time
|
0.06: Add number of satellites in view and fix crash with GPS time
|
||||||
|
0.07: Resolve one FIFO_FULL case and exit App with button press
|
||||||
|
0.08: Leave GPS power switched on on exit (will switch off after 0.5 seconds anyway)
|
||||||
|
|
|
@ -19,6 +19,7 @@ var lastFix = {
|
||||||
var SATinView = 0;
|
var SATinView = 0;
|
||||||
var nofBD = 0;
|
var nofBD = 0;
|
||||||
var nofGP = 0;
|
var nofGP = 0;
|
||||||
|
var listenerGPSraw = 1;
|
||||||
|
|
||||||
function formatTime(now) {
|
function formatTime(now) {
|
||||||
if (now == undefined) {
|
if (now == undefined) {
|
||||||
|
@ -93,6 +94,10 @@ function onGPS(fix) {
|
||||||
}
|
}
|
||||||
lastFix = fix;
|
lastFix = fix;
|
||||||
if (fix.fix) {
|
if (fix.fix) {
|
||||||
|
if (listenerGPSraw == 1) {
|
||||||
|
Bangle.removeListener('GPS-raw', onGPSraw);
|
||||||
|
listenerGPSraw = 0;
|
||||||
|
}
|
||||||
var locale = require("locale");
|
var locale = require("locale");
|
||||||
var satellites = fix.satellites;
|
var satellites = fix.satellites;
|
||||||
var maidenhead = getMaidenHead(fix.lat,fix.lon);
|
var maidenhead = getMaidenHead(fix.lat,fix.lon);
|
||||||
|
@ -104,6 +109,10 @@ function onGPS(fix) {
|
||||||
layout.sat.label = "Satellites: "+satellites;
|
layout.sat.label = "Satellites: "+satellites;
|
||||||
layout.maidenhead.label = "Maidenhead: "+maidenhead;
|
layout.maidenhead.label = "Maidenhead: "+maidenhead;
|
||||||
} else {
|
} else {
|
||||||
|
if (listenerGPSraw == 0) {
|
||||||
|
Bangle.on('GPS-raw', onGPSraw);
|
||||||
|
listenerGPSraw = 1;
|
||||||
|
}
|
||||||
layout.sat.label = fix.satellites;
|
layout.sat.label = fix.satellites;
|
||||||
layout.progress.label = "in view: " + SATinView;
|
layout.progress.label = "in view: " + SATinView;
|
||||||
}
|
}
|
||||||
|
@ -111,15 +120,23 @@ function onGPS(fix) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onGPSraw(nmea) {
|
function onGPSraw(nmea) {
|
||||||
if (nmea.slice(3,6) == "GSV") {
|
|
||||||
// console.log(nmea);
|
|
||||||
if (nmea.slice(0,7) == "$BDGSV,") nofBD = Number(nmea.slice(11,13));
|
if (nmea.slice(0,7) == "$BDGSV,") nofBD = Number(nmea.slice(11,13));
|
||||||
if (nmea.slice(0,7) == "$GPGSV,") nofGP = Number(nmea.slice(11,13));
|
if (nmea.slice(0,7) == "$GPGSV,") nofGP = Number(nmea.slice(11,13));
|
||||||
SATinView = nofBD + nofGP;
|
SATinView = nofBD + nofGP;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
Bangle.on('GPS', onGPS);
|
Bangle.on('GPS', onGPS);
|
||||||
Bangle.on('GPS-raw', onGPSraw);
|
Bangle.on('GPS-raw', onGPSraw);
|
||||||
|
|
||||||
|
function exitApp() {
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
setWatch(_=>exitApp(), BTN1);
|
||||||
|
if (global.BTN2) {
|
||||||
|
setWatch(_=>exitApp(), BTN2);
|
||||||
|
setWatch(_=>exitApp(), BTN3);
|
||||||
|
}
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
0.00: Initial check-in.
|
0.00: Initial check-in.
|
||||||
0.01: Add tap-to-decorate feature. Bugfixes.
|
0.01: Add tap-to-decorate feature. Bugfixes.
|
||||||
|
0.02: Add autorotate while charging. Remove autolight. Tweak fonts. Add some haptic feedback on touchscreen operations.
|
||||||
|
|
|
@ -20,8 +20,7 @@ you can quickly alter the number of ‘hands’ on the display. When the watch i
|
||||||
or down to remove the distraction. There's also a setting that displays the second hand, but only if the watch is perfectly face-to-the-sky,
|
or down to remove the distraction. There's also a setting that displays the second hand, but only if the watch is perfectly face-to-the-sky,
|
||||||
in case you want the ability to check the _exact_ time, hands free, without the impact on battery life this usually entails.
|
in case you want the ability to check the _exact_ time, hands free, without the impact on battery life this usually entails.
|
||||||
|
|
||||||
In some versions of the Bangle.js firmware, the backlight doesn't come on automatically when you twist your wrist. There's currently a workaround
|
While charging the main display automatically rotates so that noon is up. This can be disabled.
|
||||||
for this integrated into the watchface; you can disable it in the menu, if you prefer.
|
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,8 @@
|
||||||
//
|
//
|
||||||
// This only works for Bangle 2.
|
// This only works for Bangle 2.
|
||||||
|
|
||||||
const isString = x => typeof x === 'string';
|
const isString = x => typeof x === 'string',
|
||||||
const imageWidth = i => isString(i) ? i.charCodeAt(0) : i.width;
|
imageWidth = i => isString(i) ? i.charCodeAt(0) : i.width;
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
/* System integration */
|
/* System integration */
|
||||||
|
@ -115,9 +115,9 @@ class RoundOptions extends Options {
|
||||||
onchange: x => this.calendric = x,
|
onchange: x => this.calendric = x,
|
||||||
format: x => ['none', 'day', 'date', 'both', 'month', 'full'][x],
|
format: x => ['none', 'day', 'date', 'both', 'month', 'full'][x],
|
||||||
},
|
},
|
||||||
'Auto-Illum.': {
|
'Autorotate': {
|
||||||
init: _ => this.autolight,
|
init: _ => this.autorotate,
|
||||||
onchange: x => this.autolight = x
|
onchange: x => this.autorotate = x
|
||||||
},
|
},
|
||||||
Defaults: _ => {this.reset(); this.interact();}
|
Defaults: _ => {this.reset(); this.interact();}
|
||||||
});
|
});
|
||||||
|
@ -133,7 +133,7 @@ RoundOptions.defaults = {
|
||||||
calendric: 5,
|
calendric: 5,
|
||||||
dayFg: '#fff',
|
dayFg: '#fff',
|
||||||
nightFg: '#000',
|
nightFg: '#000',
|
||||||
autolight: true,
|
autorotate: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -144,29 +144,29 @@ const dec = x => E.toString(heatshrink.decompress(atob(x)));
|
||||||
const y10F = [
|
const y10F = [
|
||||||
dec(
|
dec(
|
||||||
'g///EAh////AA4IIBgPwgE+gAOBg/AngXB+EPAYM8gfggEfgF8D4OAj4dB8EDAYI' +
|
'g///EAh////AA4IIBgPwgE+gAOBg/AngXB+EPAYM8gfggEfgF8D4OAj4dB8EDAYI' +
|
||||||
'fBBAISBAAMOAYUB4AECnEAkAuBgEQBAPgIYX8IYX/wYDCEwIiMMgUYgECCIZlBAY' +
|
'fBBAISBAAMOAYUB4AECnEAkAuBgEQBAPgIYX8IYX/wYDCEwIiMMgUcgECCIZlBAY' +
|
||||||
'N4CoRUBIoMP8AZBge8CgMB8+BCAPw+F/gf8jxDB/0D4BGBEQMPAYIeBoAfBnEwge' +
|
'N4CoRUBIoMP8AZBge8MoMB8+B8B4B+E/gf4jw/B/kD4ADBEQMPSYXgoAfBnEwgeA' +
|
||||||
'Ah0cB4MDx4PBgHn4EB8E7LQM8h/eJ4MDBgIpB+H+g/wnE/WwMMO4P8LwM/XAJLBT' +
|
'hw7BvEDx4PBgHn4EB8E7LQM8h/eJ4MDBgIpB+H+g/wnE/WwMMG4ReBn4zBJYKcDH' +
|
||||||
'gY7BAAN/wC9CQwV+jwDB/4pBgP/EQKYBBIIxBPQP+SATfCIYIiCO4I9BBwM//hlB' +
|
'4IABv+AXoSGCv0eAYP/FIMB/4iBTAIJBGIJ6B/yQCb4RDBEQTlBHoIOBn51BwC+B' +
|
||||||
'PQJlCwYGBTAPgIgM4CYM8hwKBMoODegPA8F+gZlBewP4hz/BE4QrBGgM/LAV//4+' +
|
'MoWDAwKYBRgKYBCYM8hwKBMoODegPA8F+gZlBewP4hz/BE4QrBGgM/LAV//4+BAY' +
|
||||||
'BAYJyBPwM/KQMeGQMPFwM8H4UHBIPwGQNwn4yBnhxBGQJxBGQK5BGQKWDOwUACAM' +
|
'JyBPwM/KQMeGQMPFwM8H4UHBIPwGQNwZgPwnhxBGQJxBGQK5BGQKWDOwUACALlBI' +
|
||||||
'D/BDCNYPg///8E5HwR2BIwMDSgK0FSocMAYTLBAAYpBQAPnDwJGBEwK+B/hlB+F8' +
|
'YRrB8H///gnI+COwJGBgaUBWgqVDhgDCZYIADFIKAB84eBIwImBXwP8MoPwviYCI' +
|
||||||
'TARABTAJABTAPBMoR+BMoKXBDoX5DwIuBMoUPS4THCGwJbBhAaBvh5B+EHwPAOwP' +
|
'AKYBIAKYB4JlCPwJlBS4IdC/IeBFwJlCh6XCY4Q2BLYMIDQN8PIPwg+B4B2B8FwG' +
|
||||||
'guA1BvCcB4E8nxlBn1/VoIyBwDKBO4SGCgA='
|
'oN4TgPAnk+MoM+v6tBGQOAZQJ3CQwUAA'
|
||||||
), 48, dec('hgAI'), 34
|
), 48, dec('hgAI'), 34
|
||||||
];const y1F = [
|
];const y1F = [
|
||||||
dec(
|
dec(
|
||||||
'g//AAPggE/AoX8gF/AoX+gF8CoU+gHwAoUPgAZBEIQFGCIodFFIo1FIIoADnAFEj' +
|
'g//AAPggE/AoX8gF/AoX+gF8CoU+gHwAoUPgAZBEIQFGCIodFFIo1FIIoADnEAgQ' +
|
||||||
'gFEh0AhA1EiAFCgeAFIf/4A1DFQIED/5MDGB6OEjAECHIIYDhkAuAFCjwFEj6DEn' +
|
'FCjkAgwFCh0Ahg1EBoIABgeAFIf/4A1DFQIED/5MDGAYADEQYwDRwgMDhAYEH4Nw' +
|
||||||
'+AAod74AFD/PgvAtC+Hwv/wgZSBvEfLwc8RISOBGAJsBVAXgggEBE4PgIgJLC8E8' +
|
'AoUeAok/QYl/wAFD/fAHgUD+PgvAFBj/g+E/4EBLAN4j5SCgE8h4EB/AwCAoOAVA' +
|
||||||
'I4fgXQS/B8IhBGwOA8YFCgfA9+eAoMB4H/j/ACIPA/kPCQJCB/DMDMoMBboYVBKo' +
|
'PgggeBFoPgQgRLB8E8I4fgXQS/B8KwBMgOA8YFCgfA9+eAoMB4H/j/ACIPA/kPCQ' +
|
||||||
'IDBSYeAAoYlCAATpEg/4Xwc/QIcPFoJcBQIP8GILXCDYLXBbId//BeCL4QwDgIwD' +
|
'JIB/DMDMoJSBboQVBKoIDBSYZOBAAQlCAATpEg/4Xwc/QIZyBwBcBgf//gxBa4Qb' +
|
||||||
'AAIXBDAQfCEYSPBAoaPCPQKPCAoZgBAoYvBAoIXBBAIFB/ALDEoJHBAoaPDaQSPB' +
|
'Ba4LZDv/4LwRfCGAcBGAYABC4IYCD4QjCR4IFDR4R6BR4QFDMAIFDF4IFBC4IIBA' +
|
||||||
'AoKcBJgY9DTQX/EoKmCC4SyCYYJJB+CHBj+Aj8ASYJNBBINwIIOAM4ILDAYN/wAB' +
|
'oLEBBYQlBI4IFDR4ZrBR4QFBTgJMDHoaaCdQSmCC4SyCYYJJB+CHBj+Aj8ASYJNB' +
|
||||||
'BB4JBBI45vCRYgADApEHL4pHB8AECFIPhAYLCCAggFBAgaNCYwgFEbAkAwAFEc4S' +
|
'BINwIIOAM4ILDAYN/wABBB4JBBI45vCRYgADApEHL4pHB8AECFIPhAYLCCAggFBA' +
|
||||||
'PCj/+LIKPBv6PEAoRnBFIMDFYLXCKoTLDa4YRDBYIdDh4FDMoQ1DK4ZBBMQIDBJY' +
|
'gaNCYwgFEbAkAwAFEc4SPCj/+LIKPBv6PEAoRnBFIMDFYLXCKoTLDa4YRDBYIdDh' +
|
||||||
'bWBFIMEIIQpBgxxBgZRBh8AAYN8AoQVBjgbBAoTZBvwRCvEBF4IdB+E/OIp9CJgZ' +
|
'4FDMoQ1DK4ZBBMQIDBJYbWBFIMEIIQpBgxxBgZRBh8AAYN8AoQVBjgbBAoTZBvwR' +
|
||||||
'BCQQUAA='
|
'CvEBF4IdB+E/OIp9CJgZBCQQUAA='
|
||||||
), 48, dec('hgAI'), 48
|
), 48, dec('hgAI'), 48
|
||||||
];const y10sF = [
|
];const y10sF = [
|
||||||
dec(
|
dec(
|
||||||
|
@ -194,20 +194,20 @@ const y10F = [
|
||||||
];const d1F = [
|
];const d1F = [
|
||||||
dec(
|
dec(
|
||||||
'AB1/+AECj///4FCAgP/8EAgf/4F//EAg4CBgf8gEPwAUBn0AhwaCAYMeAoUPgEcA' +
|
'AB1/+AECj///4FCAgP/8EAgf/4F//EAg4CBgf8gEPwAUBn0AhwaCAYMeAoUPgEcA' +
|
||||||
'oUHAowRFDoopFGopBFJopZGBgIKCABlAIIcA4AFDgIFEgZBCAoMHAohVBAoY6CHg' +
|
'oUHAowRFDoopFGopBFJopZGBgIKCAB5BBgA1CAoMBAokDCIgTCAYRTDAoI6CHgU/' +
|
||||||
'U/Aol/AogADGoQFUABEMAQM/AQN8bIRZBRgJ5BLILhBgP3LIcD84rDg/HWYcPw4F' +
|
'Aol/Aog1GAqgAIhgCBn4CBvjZCLIKMBPIJZBcIMB+4lBMoMD84rDg/HL4cPw4FDj' +
|
||||||
'Dj4PBAoU+Aol8Aon4PocB+CJDgfgAoXgh/ATYX4v+AU4X//w/DbYQFCCwJ3PvDIE' +
|
'5rEnwFEvgFE/AFBaYMB+CJCwED8AFC8EP4CbC/F/wCnC//+H4bbCAoQWBO594EAI' +
|
||||||
'NYQCCdoJ6CgfAiCGCI4NwgEeFwISCLoMeJwJdCnkfHYd4v4FD+f5AoUB9/BAoUD/' +
|
'TBgBrCAQTtBPQUD4EQQwRHBuEAjwuBCQRdBjxOBLoU8j47DvF/Aofz/IFCgPv4IF' +
|
||||||
'4jCh8HG4IpCh5DBAIMeE4Q/BvjMCfoP8Z4Uf//wCgInB/5lCABs+AoicBAAUDAok' +
|
'Cgf/EYUPg43BFIUPIYIBBjwnCH4N8ZgT9B/jPCj//+AUBE4P/MoQANnwFETgIACg' +
|
||||||
'P9wFDv+OCAjUCHQP4AoY5BAoUHEIIFCv5JBAoLQBLQYqEApQpDArIAJv5IBnBTCV' +
|
'YFEh/uAod/xwQEagQ6B/AFDHIIFCg4hBAoV/JIIFBaAJaDFQgFKFIYFZABN/JAM4' +
|
||||||
'4McJAQFBcYLvBB4IkBd4N4cYQBBeoLdBCYIFDngFECoIFDOwIdCc4QpCFwIZCjwu' +
|
'KYSvBjhICAoLjBd4IPBEgLvBvDjCAIL1BboITBAoc8AogVBAoZ2BDoTnCFIQuBDI' +
|
||||||
'BEoU8FwIxCvAIBEIPB+AUBJIP/8AmBLYWAd4RnBdx4XCcYf/Dgn//AuEP4LjBXoJ' +
|
'UeFwIlCnguBGIV4BAIhB4PwCgJJB//gEwJbCwDvCM4LuPC4TjD/4cE//4Fwh/BcY' +
|
||||||
'AC//vQYT0BBIKDC+CZBOIM/wAFDVYIFCgIrBAoUDPoIdCO4QnBaQYnBGoQVBIIZI' +
|
'K9BIAX/96DCegIJBQYXwTIJxBn+AAoarBAoUBFYIFCgZ9BDoR3CE4LSDE4I1CCoJ' +
|
||||||
'CJoTNCLIY4CAYIaDAAKRCAASRDAAIaEYAQtDYAI5DRgZFCAAYuCQoQuBAgIFBvEH' +
|
'BDJARNCZoRZDHAQDBDQYABSIQACSIYABDQjACFobABHIaMDIoQADFwSFCFwIEBAo' +
|
||||||
'AgIFB+CgBAAMB86lE76EBFwX/GocPNoYmBIwk/HQl8LpIAQRId/SoYDB4ZJCUoPn' +
|
'N4g4EBAoPwUAIABgPnUonfQgIuC/41Dh5tDEwJGEn46EvhdJACCJDv6VDAYPDJIS' +
|
||||||
'VoUHwP3Y4YYBY4k+Y4h5BdILhBd4YFFCIodFFIo1FIIpNFLIplGAArMFn6oBHYMA' +
|
'lB86tCg+B+7HDDALHEnzHEPILpBcILvDAooRFDoopFGopBFJopZFMowAFZgs/VAI' +
|
||||||
'DYQFBgP5E4IFBgfgUgIFCwBZBEAL1BPYZbDA4Z7DLYRtCBYYlDBoIxCEYMBHoIvC' +
|
'7BgAbCAoMB/InBAoMD8CkBAoWALIIgBeoJ7DLYYHDPYZbCNoQLDEoYNBGIQjBgI9' +
|
||||||
'HAI7Dh5PBI4X/LIX//7+Dn52Eh4QCA=='
|
'BF4Q4BHYcPJ4JHC/5ZC///fwc/OwkPCAQA=='
|
||||||
), 48, dec('ikPigAGA'), 48
|
), 48, dec('ikPigAGA'), 48
|
||||||
];const dowF = [
|
];const dowF = [
|
||||||
dec(
|
dec(
|
||||||
|
@ -220,10 +220,10 @@ const y10F = [
|
||||||
'kDMIgeBFIQEBBYRTBCAZ3FAggAMg4zEj7LEn7LEv++AodzxwFD+ePAofjw4FVDoo' +
|
'kDMIgeBFIQEBBYRTBCAZ3FAggAMg4zEj7LEn7LEv++AodzxwFD+ePAofjw4FVDoo' +
|
||||||
'pFv+eIImcJomYLImAAoZeEAtTyBAAQFEVYIFDSQIvhAojaCFwgABh4YEngFEuAqJ' +
|
'pFv+eIImcJomYLImAAoZeEAtTyBAAQFEVYIFDSQIvhAojaCFwgABh4YEngFEuAqJ' +
|
||||||
'gPAAocDApYuEgP/fgl/+B9HAAv+Aon8HQMOIAkeAokcAohaDAoM4Aol4AohmDAoJ' +
|
'gPAAocDApYuEgP/fgl/+B9HAAv+Aon8HQMOIAkeAokcAohaDAoM4Aol4AohmDAoJ' +
|
||||||
'BDAoJsDAo7vhABbJDAo9/AojEFMYbKMArCBDFI41FWIYABggFEgbuCDYMPLIQbBj' +
|
'BDAoJsDAo7vhABZuBQYoFDv4FEYgpjDZRgFYGYYpHGoqxDAAMEAokDdwQbBh//DY' +
|
||||||
'//wBdCn0H4DZCvEBb4YZBdYZBBAofgCIQFDDoIFFDoPggYFBF4IFBGoI7B+AFCE4' +
|
'cf/+ALoU+g/AbIV4gLfDDILrDIIIFD8ARCAoYdBAoodB8EDAoIvBAoI1BHYPwAoQ' +
|
||||||
'NwCIIlCuAdBIYU4gPwn5VBjC7B/y0Dv/4YwcPCwMAjJlCAAM584FDufDCAUA8eBA' +
|
'nBuARBEoVwDoJDCnEB+E/KoMYXYP+Wgd//DGDh4WBgEZMoQABnPnAodz4YQCgHjw' +
|
||||||
'p/zC4n5EYj1BAoc//4RDU4IFDA=='
|
'IFP+YXE/IjEeoIFDn//CIanBAoY='
|
||||||
), 48, dec('kElkMljsljw='), 48
|
), 48, dec('kElkMljsljw='), 48
|
||||||
];const mF = [
|
];const mF = [
|
||||||
dec(
|
dec(
|
||||||
|
@ -322,21 +322,20 @@ class Round {
|
||||||
this.r = this.xc - this.minR;
|
this.r = this.xc - this.minR;
|
||||||
}
|
}
|
||||||
|
|
||||||
reset(clear) {this.state = {}; clear && this.g.clear(true);}
|
reset(clear) {this.state = {}; clear == null || this.g.clear(true).setRotation(clear);}
|
||||||
|
|
||||||
doIcons(which) {
|
doIcons(which) {
|
||||||
this.state[which] = null;
|
this.state[which] = null;
|
||||||
this.render(new Date()); // Not quite right, I think.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enhanceUntil(t) {this.enhance = t;}
|
enhanceUntil(t) {this.enhance = t;}
|
||||||
|
|
||||||
pie(f, a0, a1, invert) {
|
pie(f, a0, a1, invert) {
|
||||||
if (!invert) return this.pie(f, a1, a0 + 1, true);
|
if (!invert) return this.pie(f, a1, a0 + 1, true);
|
||||||
let t0 = Math.tan(a0 * 2 * Math.PI), t1 = Math.tan(a1 * 2 * Math.PI);
|
const t0 = Math.tan(a0 * 2 * Math.PI), t1 = Math.tan(a1 * 2 * Math.PI);
|
||||||
let i0 = Math.floor(a0 * 4 + 0.5), i1 = Math.floor(a1 * 4 + 0.5);
|
let i0 = Math.floor(a0 * 4 + 0.5), i1 = Math.floor(a1 * 4 + 0.5);
|
||||||
let x = f.getWidth() / 2, y = f.getHeight() / 2;
|
const x = f.getWidth() / 2, y = f.getHeight() / 2;
|
||||||
let poly = [
|
const poly = [
|
||||||
x + (i1 & 2 ? -x : x) * (i1 & 1 ? 1 : t1),
|
x + (i1 & 2 ? -x : x) * (i1 & 1 ? 1 : t1),
|
||||||
y + (i1 & 2 ? y : -y) / (i1 & 1 ? t1 : 1),
|
y + (i1 & 2 ? y : -y) / (i1 & 1 ? t1 : 1),
|
||||||
x,
|
x,
|
||||||
|
@ -348,16 +347,17 @@ class Round {
|
||||||
for (i0++; i0 <= i1; i0++) poly.push(
|
for (i0++; i0 <= i1; i0++) poly.push(
|
||||||
3 * i0 & 2 ? f.getWidth() : 0, i0 & 2 ? f.getHeight() : 0
|
3 * i0 & 2 ? f.getWidth() : 0, i0 & 2 ? f.getHeight() : 0
|
||||||
);
|
);
|
||||||
f.setColor(0).fillPoly(poly);
|
return f.setColor(0).fillPoly(poly);
|
||||||
}
|
}
|
||||||
|
|
||||||
hand(t, d, c0, r0, c1, r1) {
|
hand(t, d, c0, r0, c1, r1) {
|
||||||
|
const g = this.g;
|
||||||
t *= Math.PI / 30;
|
t *= Math.PI / 30;
|
||||||
const r = this.r;
|
const r = this.r,
|
||||||
const z = 2 * r0 + 1;
|
z = 2 * r0 + 1,
|
||||||
const x = this.xc + r * Math.sin(t), y = this.yc - r * Math.cos(t);
|
x = this.xc + r * Math.sin(t), y = this.yc - r * Math.cos(t),
|
||||||
const x0 = x - r0, y0 = y - r0;
|
x0 = x - r0, y0 = y - r0;
|
||||||
d = d ? d[0] : Graphics.createArrayBuffer(z, z, 16, {msb: true});
|
d = d ? d[0] : Graphics.createArrayBuffer(z, z, 4, {msb: true});
|
||||||
for (let i = 0; i < z; i++) for (let j = 0; j < z; j++) {
|
for (let i = 0; i < z; i++) for (let j = 0; j < z; j++) {
|
||||||
d.setPixel(i, j, g.getPixel(x0 + i, y0 + j));
|
d.setPixel(i, j, g.getPixel(x0 + i, y0 + j));
|
||||||
}
|
}
|
||||||
|
@ -366,24 +366,20 @@ class Round {
|
||||||
return [d, x0, y0];
|
return [d, x0, y0];
|
||||||
}
|
}
|
||||||
|
|
||||||
render(d) {
|
render(d, rate) {
|
||||||
const g = this.g;
|
const g = this.g, b = this.b, bI = this.bI, c = this.c, cI = this.cI,
|
||||||
const b = this.b, bI = this.bI;
|
e = d < this.enhance,
|
||||||
const c = this.c, cI = this.cI;
|
state = this.state, options = this.options,
|
||||||
const e = d < this.enhance;
|
cal = options.calendric, res = options.resolution,
|
||||||
const state = this.state;
|
dow = (e || cal === 1 || cal > 2) && d.getDay(),
|
||||||
const options = this.options;
|
ts = res < 2 && d.getSeconds(),
|
||||||
const cal = options.calendric;
|
tm = (e || res < 3) && d.getMinutes() + ts / 60,
|
||||||
const res = options.resolution;
|
th = d.getHours() + d.getMinutes() / 60,
|
||||||
const dow = (e || cal == 1 || cal > 2) && d.getDay();
|
dd = (e || cal > 1) && d.getDate(),
|
||||||
const ts = res < 2 && d.getSeconds();
|
dm = (e || cal > 3) && d.getMonth(),
|
||||||
const tm = (e || res < 3) && d.getMinutes() + ts / 60;
|
dy = (e || cal > 4) && d.getFullYear();
|
||||||
const th = d.getHours() + d.getMinutes() / 60;
|
const xc = this.xc, yc = this.yc, r = this.r,
|
||||||
const dd = (e || cal > 1) && d.getDate();
|
dlr = xc * 3/4, dlw = 8, dlhw = 4;
|
||||||
const dm = (e || cal > 3) && d.getMonth();
|
|
||||||
const dy = (e || cal > 4) && d.getFullYear();
|
|
||||||
const xc = this.xc, yc = this.yc, r = this.r;
|
|
||||||
const dlr = xc * 3/4, dlw = 8, dlhw = 4;
|
|
||||||
|
|
||||||
// Restore saveunders for fast-moving, overdrawing indicators.
|
// Restore saveunders for fast-moving, overdrawing indicators.
|
||||||
if (state.sd) g.drawImage.apply(g, state.sd);
|
if (state.sd) g.drawImage.apply(g, state.sd);
|
||||||
|
@ -397,10 +393,10 @@ class Round {
|
||||||
state.dow = dow;
|
state.dow = dow;
|
||||||
}
|
}
|
||||||
|
|
||||||
const locked = Bangle.isLocked();
|
const locked = Bangle.isLocked(),
|
||||||
const charging = Bangle.isCharging();
|
charging = Bangle.isCharging(),
|
||||||
const battery = E.getBattery();
|
battery = E.getBattery(),
|
||||||
const HRMOn = Bangle.isHRMOn();
|
HRMOn = Bangle.isHRMOn();
|
||||||
if (dy !== state.dy ||
|
if (dy !== state.dy ||
|
||||||
locked !== state.locked ||
|
locked !== state.locked ||
|
||||||
charging !== state.charging ||
|
charging !== state.charging ||
|
||||||
|
@ -463,6 +459,7 @@ class Round {
|
||||||
this.hand(tm, state.md, g.theme.bg, this.minR, g.theme.fg, this.minR - 1) :
|
this.hand(tm, state.md, g.theme.bg, this.minR, g.theme.fg, this.minR - 1) :
|
||||||
null;
|
null;
|
||||||
state.sd = ts === +ts ?
|
state.sd = ts === +ts ?
|
||||||
|
rate > 1000 ? this.hand(ts, state.sd, g.theme.fg2, this.secR, g.theme.bg, 2) :
|
||||||
this.hand(ts, state.sd, g.theme.fg2, this.secR) :
|
this.hand(ts, state.sd, g.theme.fg2, this.secR) :
|
||||||
null;
|
null;
|
||||||
}
|
}
|
||||||
|
@ -482,13 +479,23 @@ class Clock {
|
||||||
|
|
||||||
this.listeners = {
|
this.listeners = {
|
||||||
lcdPower: on => on ? this.active() : this.inactive(),
|
lcdPower: on => on ? this.active() : this.inactive(),
|
||||||
charging: () => {face.doIcons('charging'); this.active();},
|
charging: on => {
|
||||||
|
face.doIcons('charging');
|
||||||
|
if (on) {
|
||||||
|
this.listeners.accel =
|
||||||
|
a => this.orientation(a) === this.attitude || this.active();
|
||||||
|
Bangle.on('accel', this.listeners.accel);
|
||||||
|
} else {
|
||||||
|
Bangle.removeListener('accel', this.listeners.accel);
|
||||||
|
delete this.listeners.accel;
|
||||||
|
}
|
||||||
|
this.active();
|
||||||
|
},
|
||||||
lock: () => {face.doIcons('locked'); this.active();},
|
lock: () => {face.doIcons('locked'); this.active();},
|
||||||
faceUp: up => {
|
faceUp: up => {
|
||||||
this.conservative = !up;
|
this.conservative = !up;
|
||||||
this.active();
|
this.active();
|
||||||
},
|
},
|
||||||
twist: _ => this.options.autolight && Bangle.setLCDPower(true),
|
|
||||||
drag: e => {
|
drag: e => {
|
||||||
if (this.t0) {
|
if (this.t0) {
|
||||||
if (e.b) {
|
if (e.b) {
|
||||||
|
@ -498,20 +505,23 @@ class Clock {
|
||||||
if (e.y - this.e0.y < -50) {
|
if (e.y - this.e0.y < -50) {
|
||||||
this.options.resolution > 0 && this.options.resolution--;
|
this.options.resolution > 0 && this.options.resolution--;
|
||||||
this.rates.clock = this.timescales[this.options.resolution];
|
this.rates.clock = this.timescales[this.options.resolution];
|
||||||
|
this.ack();
|
||||||
this.active();
|
this.active();
|
||||||
} else if (e.y - this.e0.y > 50) {
|
} else if (e.y - this.e0.y > 50) {
|
||||||
this.options.resolution < this.timescales.length - 1 &&
|
this.options.resolution < this.timescales.length - 1 &&
|
||||||
this.options.resolution++;
|
this.options.resolution++;
|
||||||
this.rates.clock = this.timescales[this.options.resolution];
|
this.rates.clock = this.timescales[this.options.resolution];
|
||||||
|
this.ack();
|
||||||
this.active();
|
this.active();
|
||||||
} else if (this.yX - this.yN < 20) {
|
} else if (this.yX - this.yN < 20) {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
if (now - this.t0 < 250) {
|
if (now - this.t0 < 250) {
|
||||||
|
this.ack();
|
||||||
face.enhanceUntil(now + 30000);
|
face.enhanceUntil(now + 30000);
|
||||||
face.render(now);
|
this.active();
|
||||||
} else if (now - this.t0 > 500) {
|
} else if (now - this.t0 > 500) {
|
||||||
this.stop();
|
this.stop();
|
||||||
this.options.interact();
|
this.ack().then(_ => this.options.interact());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.t0 = null;
|
this.t0 = null;
|
||||||
|
@ -524,9 +534,25 @@ class Clock {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ack() {
|
||||||
|
return Bangle.buzz(33);
|
||||||
|
}
|
||||||
|
|
||||||
|
orientation(a) {
|
||||||
|
return Math.abs(a.z) < 0.85 ?
|
||||||
|
Math.abs(a.y) > Math.abs(a.x) ? a.y < 0 ? 0 : 2 : a.x > 0 ? 1 : 3 :
|
||||||
|
0;
|
||||||
|
}
|
||||||
|
|
||||||
|
rotation() {
|
||||||
|
return this.options.autorotate && Bangle.isCharging() ?
|
||||||
|
this.orientation(Bangle.getAccel()) :
|
||||||
|
0;
|
||||||
|
}
|
||||||
|
|
||||||
redraw(rate) {
|
redraw(rate) {
|
||||||
const now = this.updated = new Date();
|
const now = this.updated = new Date();
|
||||||
if (this.refresh) this.face.reset(true);
|
if (this.refresh) this.face.reset(this.attitude = this.rotation());
|
||||||
this.refresh = false;
|
this.refresh = false;
|
||||||
rate = this.face.render(now, rate);
|
rate = this.face.render(now, rate);
|
||||||
if (rate !== this.rates.face) {
|
if (rate !== this.rates.face) {
|
||||||
|
@ -541,13 +567,13 @@ class Clock {
|
||||||
this.exception && clearTimeout(this.exception);
|
this.exception && clearTimeout(this.exception);
|
||||||
this.interval && clearInterval(this.interval);
|
this.interval && clearInterval(this.interval);
|
||||||
this.timeout = this.exception = this.interval = this.rate = null;
|
this.timeout = this.exception = this.interval = this.rate = null;
|
||||||
this.face.reset(false); // Cancel any ongoing background rendering
|
this.face.reset(); // Cancel any ongoing background rendering
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
active() {
|
active() {
|
||||||
const prev = this.rate;
|
const prev = this.rate,
|
||||||
const now = Date.now();
|
now = Date.now();
|
||||||
let rate = Infinity;
|
let rate = Infinity;
|
||||||
for (const k in this.rates) {
|
for (const k in this.rates) {
|
||||||
let r = this.rates[k];
|
let r = this.rates[k];
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// pooqRoman resource maker
|
// pooqRoman resource maker
|
||||||
//
|
//
|
||||||
// Copyright (c) 2021 Stephen P Spackman
|
// Copyright (c) 2021, 2022 Stephen P Spackman
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -147,18 +147,18 @@ res += prepFont('y10', `
|
||||||
xxx
|
xxx
|
||||||
xxx
|
xxx
|
||||||
-2--------------------------------
|
-2--------------------------------
|
||||||
x xx
|
x xxx
|
||||||
xx xxx
|
xx xxx
|
||||||
xxxx xxx
|
xxxx xxx
|
||||||
xxxxx xxx
|
xxxxx xxx
|
||||||
xxxxxxx xxx
|
xxxxxxx xxx
|
||||||
xxxx xxx xxx
|
xxxx xxx xxx
|
||||||
xxxx xxxx xxx
|
xxxx xxxx xxxx
|
||||||
xxxx xxxx xxx
|
xxxx xxxxx xxxx
|
||||||
xxxx xxxxxxxx xxxxxxx
|
xxxx xxxxxxx xxxxxx
|
||||||
xxxx xxxxxxxxxxxxxxxxxxx
|
xxxx xxxxxxxxxxxxxxxxx
|
||||||
xxxx xxxxxxxxxxxxxx
|
xxxx xxxxxxxxxxxxxx
|
||||||
xxxx xxxxxxxxxx
|
xxxx xxxxxxxxx
|
||||||
-3--------------------------------
|
-3--------------------------------
|
||||||
xxx x xxx
|
xxx x xxx
|
||||||
xxx xx xxx
|
xxx xx xxx
|
||||||
|
@ -270,10 +270,10 @@ res += prepFont('y1', `
|
||||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
-1----------------------------------------------
|
-1----------------------------------------------
|
||||||
xxx
|
xxx
|
||||||
xxx
|
|
||||||
xxx
|
|
||||||
xxx x
|
|
||||||
xxx x
|
xxx x
|
||||||
|
xxx xx
|
||||||
|
xxx xx
|
||||||
|
xxx xxx
|
||||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
@ -282,18 +282,18 @@ res += prepFont('y1', `
|
||||||
xxx
|
xxx
|
||||||
xxx
|
xxx
|
||||||
-2----------------------------------------------
|
-2----------------------------------------------
|
||||||
x xx
|
x xxx
|
||||||
xx xxx
|
xx xxx
|
||||||
xxxx xxx
|
xxxx xxx
|
||||||
xxxxx xxx
|
xxxxxx xxx
|
||||||
xxxxxxx xxx
|
|
||||||
xxxxxxxx xxx
|
xxxxxxxx xxx
|
||||||
xxxx xxxxx xxx
|
xxxx xxxxx xxx
|
||||||
xxxx xxxxxxx xxxx
|
xxxx xxxxxx xxxx
|
||||||
xxxx xxxxxxxxxxxxx xxxxxxxxxxx
|
xxxx xxxxxxxx xxxx
|
||||||
xxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
xxxx xxxxxxxxxxx xxxxxxxx
|
||||||
|
xxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
xxxx xxxxxxxxxxxxxxxxxxxxxxxxx
|
xxxx xxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
xxxx xxxxxxxxxxxxxx
|
xxxx xxxxxxxxxxxxxxxxx
|
||||||
-3----------------------------------------------
|
-3----------------------------------------------
|
||||||
xxx x xxx
|
xxx x xxx
|
||||||
xxx xx xxx
|
xxx xx xxx
|
||||||
|
@ -645,12 +645,12 @@ xxxx xxxx
|
||||||
-1----------------------------------------------
|
-1----------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
xxx
|
||||||
xxx x
|
xxx x
|
||||||
xxx xx
|
xxx xx
|
||||||
|
xxx xx
|
||||||
xxx xxx
|
xxx xxx
|
||||||
xxx xxx
|
xxx xxx
|
||||||
xxx xxxx
|
|
||||||
xxx xxxx
|
|
||||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
@ -993,9 +993,9 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
xxx
|
xxx
|
||||||
|
|
||||||
|
|
||||||
xxxxxxxxxxxxxxxx
|
xxxxxxxxxxxxxxxxxxxx
|
||||||
xxxxxxxxxxxxxxxx
|
xxxxxxxxxxxxxxxxxxxx
|
||||||
xxxxxxxxxxxxxxx
|
xxxxxxxxxxxxxxxxxxx
|
||||||
xxxx
|
xxxx
|
||||||
xxxx
|
xxxx
|
||||||
xxx
|
xxx
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
|
0.02: Add Bangle.js 2 Support
|
||||||
|
|
|
@ -70,7 +70,7 @@ E.showMenu = function(items) {
|
||||||
if(g.theme.dark){
|
if(g.theme.dark){
|
||||||
fillRectRnd(x+2,iy+1,x2,iy+options.fontHeight-3,7,hl ? g.theme.bgH : g.theme.bg+20);
|
fillRectRnd(x+2,iy+1,x2,iy+options.fontHeight-3,7,hl ? g.theme.bgH : g.theme.bg+20);
|
||||||
}else{
|
}else{
|
||||||
fillRectRnd(x+2,iy+1,x2,iy+options.fontHeight-3,7,hl ? g.theme.bgH : g.theme.bg-10);
|
fillRectRnd(x+2,iy+1,x2,iy+options.fontHeight-3,7,hl ? g.theme.bgH : g.theme.bg-20);
|
||||||
}
|
}
|
||||||
g.setColor(hl ? g.theme.fgH : g.theme.fg);
|
g.setColor(hl ? g.theme.fgH : g.theme.fg);
|
||||||
g.setFontAlign(-1,-1);
|
g.setFontAlign(-1,-1);
|
||||||
|
|
|
@ -0,0 +1,142 @@
|
||||||
|
E.showMenu = function(items) {
|
||||||
|
function RectRnd(x1,y1,x2,y2,r) {
|
||||||
|
pp = [];
|
||||||
|
pp.push.apply(pp,g.quadraticBezier([x2-r,y1, x2,y1,x2,y1+r]));
|
||||||
|
pp.push.apply(pp,g.quadraticBezier([x2,y2-r,x2,y2,x2-r,y2]));
|
||||||
|
pp.push.apply(pp,g.quadraticBezier([x1+r,y2,x1,y2,x1,y2-r]));
|
||||||
|
pp.push.apply(pp,g.quadraticBezier([x1,y1+r,x1,y1,x1+r,y1]));
|
||||||
|
return pp;
|
||||||
|
}
|
||||||
|
function fillRectRnd(x1,y1,x2,y2,r,c) {
|
||||||
|
g.setColor(c);
|
||||||
|
g.fillPoly(RectRnd(x1,y1,x2,y2,r),1);
|
||||||
|
g.setColor(255,255,255);
|
||||||
|
}
|
||||||
|
function drawRectRnd(x1,y1,x2,y2,r,c) {
|
||||||
|
g.setColor(c);
|
||||||
|
g.drawPoly(RectRnd(x1,y1,x2,y2,r),1);
|
||||||
|
g.setColor(255,255,255);
|
||||||
|
}
|
||||||
|
g.reset().clearRect(Bangle.appRect); // clear if no menu supplied
|
||||||
|
Bangle.setLCDPower(1); // ensure screen is on
|
||||||
|
if (!items) {
|
||||||
|
Bangle.setUI();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var menuItems = Object.keys(items);
|
||||||
|
var options = items[""];
|
||||||
|
if (options) menuItems.splice(menuItems.indexOf(""),1);
|
||||||
|
if (!(options instanceof Object)) options = {};
|
||||||
|
options.fontHeight = options.fontHeight||25;
|
||||||
|
if (options.selected === undefined)
|
||||||
|
options.selected = 0;
|
||||||
|
var ar = Bangle.appRect;
|
||||||
|
var x = ar.x;
|
||||||
|
var x2 = ar.x2;
|
||||||
|
var y = ar.y;
|
||||||
|
var y2 = ar.y2 - 12; // padding at end for arrow
|
||||||
|
if (options.title)
|
||||||
|
y += 22;
|
||||||
|
var loc = require("locale");
|
||||||
|
var l = {
|
||||||
|
lastIdx : 0,
|
||||||
|
draw : function(rowmin,rowmax) {
|
||||||
|
var rows = 0|Math.min((y2-y) / options.fontHeight,menuItems.length);
|
||||||
|
var idx = E.clip(options.selected-( rows>>1),0,menuItems.length-rows);
|
||||||
|
if (idx!=l.lastIdx) rowmin=undefined; // redraw all if we scrolled
|
||||||
|
l.lastIdx = idx;
|
||||||
|
var iy = y;
|
||||||
|
g.reset().setFontAlign(0,-1,0).setFont('12x20');
|
||||||
|
if (options.predraw) options.predraw(g);
|
||||||
|
if (rowmin===undefined && options.title)
|
||||||
|
g.drawString(options.title,(x+x2)/2,y-21).drawLine(x,y-2,x2,y-2).
|
||||||
|
setColor(g.theme.fg).setBgColor(g.theme.bg);
|
||||||
|
iy += 4;
|
||||||
|
if (rowmin!==undefined) {
|
||||||
|
if (idx<rowmin) {
|
||||||
|
iy += options.fontHeight*(rowmin-idx);
|
||||||
|
idx=rowmin;
|
||||||
|
}
|
||||||
|
if (idx+rows>rowmax) {
|
||||||
|
rows = 1+rowmax-rowmin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (rows--) {
|
||||||
|
var name = menuItems[idx];
|
||||||
|
var item = items[name];
|
||||||
|
var hl = (idx==options.selected && !l.selectEdit);
|
||||||
|
if(g.theme.dark){
|
||||||
|
fillRectRnd(x,iy,x2,iy+options.fontHeight-3,7,hl ? g.theme.bgH : g.theme.bg+40);
|
||||||
|
}else{
|
||||||
|
fillRectRnd(x,iy,x2,iy+options.fontHeight-3,7,hl ? g.theme.bgH : g.theme.bg-20);
|
||||||
|
}
|
||||||
|
g.setColor(hl ? g.theme.fgH : g.theme.fg);
|
||||||
|
g.setFontAlign(-1,-1);
|
||||||
|
var v = item.value;
|
||||||
|
v = loc.translate(""+v);
|
||||||
|
if(loc.translate(name).length >= 17-v.length && "object" == typeof item){
|
||||||
|
if (item.format) v=item.format(v);
|
||||||
|
g.drawString(loc.translate(name).substring(0, 12-v.length)+"...",x+3.7,iy+2.7);
|
||||||
|
}else{
|
||||||
|
if(loc.translate(name).length >= 15){
|
||||||
|
g.drawString(loc.translate(name).substring(0, 15)+"...",x+3.7,iy+2.7);
|
||||||
|
}else{
|
||||||
|
g.drawString(loc.translate(name),x+3.7,iy+2.7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ("object" == typeof item) {
|
||||||
|
var xo = x2;
|
||||||
|
var v = item.value;
|
||||||
|
if (item.format) v=item.format(v);
|
||||||
|
v = loc.translate(""+v);
|
||||||
|
if (l.selectEdit && idx==options.selected) {
|
||||||
|
xo -= 24 + 1;
|
||||||
|
g.setColor(g.theme.fgH).drawImage("\x0c\x05\x81\x00 \x07\x00\xF9\xF0\x0E\x00@",xo,iy+(options.fontHeight-10)/2,{scale:2});
|
||||||
|
}
|
||||||
|
g.setFontAlign(1,-1);
|
||||||
|
g.drawString(v,xo-2,iy+1);
|
||||||
|
}
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
iy += options.fontHeight;
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
g.setFontAlign(-1,-1);
|
||||||
|
g.setColor((idx<menuItems.length)?g.theme.fg:g.theme.bg).fillPoly([72,166,104,166,88,174]);
|
||||||
|
g.flip();
|
||||||
|
},
|
||||||
|
select : function() {
|
||||||
|
var item = items[menuItems[options.selected]];
|
||||||
|
if ("function" == typeof item) item(l);
|
||||||
|
else if ("object" == typeof item) {
|
||||||
|
// if a number, go into 'edit mode'
|
||||||
|
if ("number" == typeof item.value)
|
||||||
|
l.selectEdit = l.selectEdit?undefined:item;
|
||||||
|
else { // else just toggle bools
|
||||||
|
if ("boolean" == typeof item.value) item.value=!item.value;
|
||||||
|
if (item.onchange) item.onchange(item.value);
|
||||||
|
}
|
||||||
|
l.draw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
move : function(dir) {
|
||||||
|
var item = l.selectEdit;
|
||||||
|
if (item) {
|
||||||
|
item.value -= (dir||1)*(item.step||1);
|
||||||
|
if (item.min!==undefined && item.value<item.min) item.value = item.wrap ? item.max : item.min;
|
||||||
|
if (item.max!==undefined && item.value>item.max) item.value = item.wrap ? item.min : item.max;
|
||||||
|
if (item.onchange) item.onchange(item.value);
|
||||||
|
l.draw(options.selected,options.selected);
|
||||||
|
} else {
|
||||||
|
var lastSelected=options.selected;
|
||||||
|
options.selected = (dir+options.selected+menuItems.length)%menuItems.length;
|
||||||
|
l.draw(Math.min(lastSelected,options.selected), Math.max(lastSelected,options.selected));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
l.draw();
|
||||||
|
Bangle.setUI("updown",dir => {
|
||||||
|
if (dir) l.move(dir);
|
||||||
|
else l.select();
|
||||||
|
});
|
||||||
|
return l;
|
||||||
|
};
|
|
@ -2,11 +2,19 @@
|
||||||
|
|
||||||
Directly launch apps from the clock screen with custom patterns.
|
Directly launch apps from the clock screen with custom patterns.
|
||||||
|
|
||||||
## Usage
|
## Installation and Usage
|
||||||
|
|
||||||
|
Install Pattern Launcher alongside your main laucher app.
|
||||||
|
_Do not delete that launcher!_
|
||||||
|
Pattern Launcher is designed as an additional app launching utility, not as a replacement for the main launcher.
|
||||||
|
|
||||||
|
In the main launcher, start Pattern Launcher in the app menu to assign the pattern configuration (see below).
|
||||||
|
Note that this actually among the applications, _not_ in the application settings!
|
||||||
|
|
||||||
Create patterns and link them to apps in the Pattern Launcher app.
|
Create patterns and link them to apps in the Pattern Launcher app.
|
||||||
|
|
||||||
Then launch the linked apps directly from the clock screen by simply drawing the desired pattern.
|
Then launch the linked apps directly from the clock screen by simply drawing the desired pattern.
|
||||||
|
Note that this does only work in the clock screen, not if other applications run.
|
||||||
|
|
||||||
## Add Pattern Screenshots
|
## Add Pattern Screenshots
|
||||||
|
|
||||||
|
@ -28,7 +36,8 @@ Then launch the linked apps directly from the clock screen by simply drawing the
|
||||||
|
|
||||||
## Detailed Steps
|
## Detailed Steps
|
||||||
|
|
||||||
From the main menu you can:
|
The main menu of Pattern Launcher is accessible from the _application_ starter of the main launcher.
|
||||||
|
From there you can:
|
||||||
|
|
||||||
- Add a new pattern and link it to an app (first entry)
|
- Add a new pattern and link it to an app (first entry)
|
||||||
- To create a new pattern first select "Add Pattern"
|
- To create a new pattern first select "Add Pattern"
|
||||||
|
@ -60,6 +69,16 @@ Make sure the watch is unlocked before you start drawing. If this bothers you, y
|
||||||
|
|
||||||
Please note that drawing on the clock screen will not visually show the pattern you drew. It will start the app as soon as the pattern was recognized - this might take 1 or 2 seconds! If still nothing happens, that might be a bug, sorry!
|
Please note that drawing on the clock screen will not visually show the pattern you drew. It will start the app as soon as the pattern was recognized - this might take 1 or 2 seconds! If still nothing happens, that might be a bug, sorry!
|
||||||
|
|
||||||
|
4. Where can I configure the patterns?
|
||||||
|
|
||||||
|
You have to start the "Pattern Launcher" app from the main app launcher's app selection.
|
||||||
|
|
||||||
|
5. Do I have to delete my former app launcher so that Pattern Launcher is the only installed launcher?
|
||||||
|
|
||||||
|
No! Pattern Launcher works alongside another "main" launcher.
|
||||||
|
If you have deleted that one, you do not have a general purpose app launcher any more and cannot access Pattern Launcher's configuration.
|
||||||
|
If you already have deleted your main launcher accidentially, just reinstall it from the app loader.
|
||||||
|
|
||||||
## Authors
|
## Authors
|
||||||
|
|
||||||
Initial creation: [crazysaem](https://github.com/crazysaem)
|
Initial creation: [crazysaem](https://github.com/crazysaem)
|
||||||
|
@ -67,3 +86,5 @@ Initial creation: [crazysaem](https://github.com/crazysaem)
|
||||||
Improve pattern detection code readability: [PaddeK](http://forum.espruino.com/profiles/117930/)
|
Improve pattern detection code readability: [PaddeK](http://forum.espruino.com/profiles/117930/)
|
||||||
|
|
||||||
Improve pattern rendering: [HughB](http://forum.espruino.com/profiles/167235/)
|
Improve pattern rendering: [HughB](http://forum.espruino.com/profiles/167235/)
|
||||||
|
|
||||||
|
Doc additions: [dirkhillbrecht](http://forum.espruino.com/profiles/182498/)
|
||||||
|
|
|
@ -4,3 +4,7 @@
|
||||||
0.03: Fix theme and maps/graphing if no GPS
|
0.03: Fix theme and maps/graphing if no GPS
|
||||||
0.04: Multiple bugfixes
|
0.04: Multiple bugfixes
|
||||||
0.05: Add recording for coresensor
|
0.05: Add recording for coresensor
|
||||||
|
0.06: Add recording for battery stats
|
||||||
|
Fix execution of other recorders (*.recorder.js)
|
||||||
|
Modified icons and colors for better visibility
|
||||||
|
Only show plotting speed if Latitude is available
|
||||||
|
|
|
@ -16,7 +16,8 @@ You can record
|
||||||
* **Time** The current time
|
* **Time** The current time
|
||||||
* **GPS** GPS Latitude, Longitude and Altitude
|
* **GPS** GPS Latitude, Longitude and Altitude
|
||||||
* **Steps** Steps counted by the step counter
|
* **Steps** Steps counted by the step counter
|
||||||
* **HR** Heart rate
|
* **HR** Heart rate and confidence
|
||||||
|
* **BAT** Battery percentage and voltage
|
||||||
* **Core** CoreTemp body temperature
|
* **Core** CoreTemp body temperature
|
||||||
|
|
||||||
**Note:** It is possible for other apps to record information using this app
|
**Note:** It is possible for other apps to record information using this app
|
||||||
|
@ -25,4 +26,4 @@ function in `widget.js` for more information.
|
||||||
|
|
||||||
## Tips
|
## Tips
|
||||||
|
|
||||||
When recording GPS, it usually takes several minutes for the watch to get a [GPS fix](https://en.wikipedia.org/wiki/Time_to_first_fix). There is a grey satellite symbol, which you will see turn red when you get an actual GPS Fix. You can [upload assistant files](https://banglejs.com/apps/#assisted%20gps%20update) to speed up the time spent on getting a GPS fix.
|
When recording GPS, it usually takes several minutes for the watch to get a [GPS fix](https://en.wikipedia.org/wiki/Time_to_first_fix). There is a red satellite symbol, which you will see turn green when you get an actual GPS Fix. You can [upload assistant files](https://banglejs.com/apps/#assisted%20gps%20update) to speed up the time spent on getting a GPS fix.
|
||||||
|
|
|
@ -199,6 +199,7 @@ function viewTrack(filename, info) {
|
||||||
menu['Plot Alt.'] = function() {
|
menu['Plot Alt.'] = function() {
|
||||||
plotGraph(info, "Altitude");
|
plotGraph(info, "Altitude");
|
||||||
};
|
};
|
||||||
|
if (info.fields.includes("Latitude"))
|
||||||
menu['Plot Speed'] = function() {
|
menu['Plot Speed'] = function() {
|
||||||
plotGraph(info, "Speed");
|
plotGraph(info, "Speed");
|
||||||
};
|
};
|
||||||
|
|
|
@ -48,41 +48,50 @@
|
||||||
Bangle.removeListener('GPS', onGPS);
|
Bangle.removeListener('GPS', onGPS);
|
||||||
Bangle.setGPSPower(0,"recorder");
|
Bangle.setGPSPower(0,"recorder");
|
||||||
},
|
},
|
||||||
draw : (x,y) => g.setColor(hasFix?"#0ff":"#888").drawImage(atob("DAyBAAACADgDuBOAeA4AzAHADgAAAA=="),x,y)
|
draw : (x,y) => g.setColor(hasFix?"#0f0":"#f88").drawImage(atob("DAwBEAKARAKQE4DwHkPqPRGKAEAA"),x,y)
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
hrm:function() {
|
hrm:function() {
|
||||||
var bpm = 0, bpmConfidence = 0;
|
var bpm = 0, bpmConfidence = 0;
|
||||||
var hasBPM = false;
|
|
||||||
function onHRM(h) {
|
function onHRM(h) {
|
||||||
if (h.confidence >= bpmConfidence) {
|
if (h.confidence >= bpmConfidence) {
|
||||||
bpmConfidence = h.confidence;
|
bpmConfidence = h.confidence;
|
||||||
bpm = h.bpm;
|
bpm = h.bpm;
|
||||||
if (bpmConfidence) hasBPM = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
name : "HR",
|
name : "HR",
|
||||||
fields : ["Heartrate"],
|
fields : ["Heartrate", "Confidence"],
|
||||||
getValues : () => {
|
getValues : () => {
|
||||||
var r = [bpmConfidence?bpm:""];
|
var r = [bpm,bpmConfidence];
|
||||||
bpm = 0; bpmConfidence = 0;
|
bpm = 0; bpmConfidence = 0;
|
||||||
return r;
|
return r;
|
||||||
},
|
},
|
||||||
start : () => {
|
start : () => {
|
||||||
hasBPM = false;
|
|
||||||
Bangle.on('HRM', onHRM);
|
Bangle.on('HRM', onHRM);
|
||||||
Bangle.setHRMPower(1,"recorder");
|
Bangle.setHRMPower(1,"recorder");
|
||||||
},
|
},
|
||||||
stop : () => {
|
stop : () => {
|
||||||
hasBPM = false;
|
|
||||||
Bangle.removeListener('HRM', onHRM);
|
Bangle.removeListener('HRM', onHRM);
|
||||||
Bangle.setHRMPower(0,"recorder");
|
Bangle.setHRMPower(0,"recorder");
|
||||||
},
|
},
|
||||||
draw : (x,y) => g.setColor(hasBPM?"#f00":"#888").drawImage(atob("DAyBAAAAAD/H/n/n/j/D/B+AYAAAAA=="),x,y)
|
draw : (x,y) => g.setColor(Bangle.isHRMOn()?"#f00":"#f88").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
bat:function() {
|
||||||
|
return {
|
||||||
|
name : "BAT",
|
||||||
|
fields : ["Battery Percentage", "Battery Voltage", "Charging"],
|
||||||
|
getValues : () => {
|
||||||
|
return [E.getBattery(), NRF.getBattery(), Bangle.isCharging()];
|
||||||
|
},
|
||||||
|
start : () => {
|
||||||
|
},
|
||||||
|
stop : () => {
|
||||||
|
},
|
||||||
|
draw : (x,y) => g.setColor(Bangle.isCharging() ? "#0f0" : "#ff0").drawImage(atob("DAwBAABgH4G4EYG4H4H4H4GIH4AA"),x,y)
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
temp:function() {
|
temp:function() {
|
||||||
var core = 0, skin = 0;
|
var core = 0, skin = 0;
|
||||||
var hasCore = false;
|
var hasCore = false;
|
||||||
|
@ -106,7 +115,7 @@
|
||||||
hasCore = false;
|
hasCore = false;
|
||||||
Bangle.removeListener('CoreTemp', onCore);
|
Bangle.removeListener('CoreTemp', onCore);
|
||||||
},
|
},
|
||||||
draw : (x,y) => g.setColor(hasCore?"#0f0":"#888").drawImage(atob("DAyBAAHh0js3EuDMA8A8AWBnDj9A8A=="),x,y)
|
draw : (x,y) => g.setColor(hasCore?"#0f0":"#8f8").drawImage(atob("DAwBAAAOAKPOfgZgZgZgZgfgPAAA"),x,y)
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
steps:function() {
|
steps:function() {
|
||||||
|
@ -121,7 +130,7 @@
|
||||||
},
|
},
|
||||||
start : () => { lastSteps = Bangle.getStepCount(); },
|
start : () => { lastSteps = Bangle.getStepCount(); },
|
||||||
stop : () => {},
|
stop : () => {},
|
||||||
draw : (x,y) => g.reset().drawImage(atob("DAyBAAADDHnnnnnnnnnnjDmDnDnAAA=="),x,y)
|
draw : (x,y) => g.reset().drawImage(atob("DAwBAAMMeeeeeeeecOMMAAMMMMAA"),x,y)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// TODO: recAltitude from pressure sensor
|
// TODO: recAltitude from pressure sensor
|
||||||
|
@ -138,7 +147,7 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
*/
|
*/
|
||||||
require("Storage").list(/^.*\.recorder\.js$/).forEach(fn=>eval(fn)(recorders));
|
require("Storage").list(/^.*\.recorder\.js$/).forEach(fn=>eval(require("Storage").read(fn))(recorders));
|
||||||
return recorders;
|
return recorders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
1.00: Hello Ruuvi Watch!
|
||||||
|
1.01: Clear gfx on startup.
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Ruuvi Watch
|
||||||
|
|
||||||
|
Watch the status of [RuuviTags](https://ruuvi.com) in range.
|
||||||
|
|
||||||
|
- Id
|
||||||
|
- Temperature (°C)
|
||||||
|
- Humidity (%)
|
||||||
|
- Pressure (hPa)
|
||||||
|
- Battery voltage
|
||||||
|
|
||||||
|
Also shows how "fresh" the data is (age of reading).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
- Scans for devices when launched and every N seconds.
|
||||||
|
- Page trough devices with BTN1/BTN3.
|
||||||
|
- Trigger scan with BTN2.
|
||||||
|
|
||||||
|
## Todo / ideas
|
||||||
|
|
||||||
|
- Allow to "name" known devices
|
||||||
|
- Prevent flicker when updating
|
||||||
|
- Include more data
|
||||||
|
- Support older Ruuvi protocols
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwhC/AH4A/ABMP/4ACCyIVDAAXwCyoYPIggAFCx4oEDBw/JJJguCBhAwLBZYjKBQQeGCIYNHB45bIBw4gIRgw+NC4wwJJ5YRLC5DzFCJBGMEYoSEFxoMEBQIXEF4gVFF5QcEC553JC5QRITgy/NVxIXGf5QlFIwy4IGBQuFC5JhGCwpGGERZOEBQ4MEDAwJJGAzdJCxLVJFxoYLCxoYICx6/GCqAA/AH4A/ACA"))
|
|
@ -0,0 +1,151 @@
|
||||||
|
require("Storage").write("ruuviwatch.info",{
|
||||||
|
"id":"ruuviwatch",
|
||||||
|
"name":"Ruuvi Watch",
|
||||||
|
"src":"ruuviwatch.app.js",
|
||||||
|
"icon":"ruuviwatch.img"
|
||||||
|
});
|
||||||
|
|
||||||
|
const lookup = {};
|
||||||
|
const ruuvis = [];
|
||||||
|
let current = 0;
|
||||||
|
|
||||||
|
function int2Hex (str) {
|
||||||
|
return ('0' + str.toString(16).toUpperCase()).slice(-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function p(data) {
|
||||||
|
const OFFSET = 7; // 0-4 header, 5-6 Ruuvi id
|
||||||
|
const robject = {};
|
||||||
|
robject.version = data[OFFSET];
|
||||||
|
|
||||||
|
let temperature = (data[OFFSET+1] << 8) | (data[OFFSET+2] & 0xff);
|
||||||
|
if (temperature > 32767) {
|
||||||
|
temperature -= 65534;
|
||||||
|
}
|
||||||
|
robject.temperature = temperature / 200.0;
|
||||||
|
|
||||||
|
robject.humidity = (((data[OFFSET+3] & 0xff) << 8) | (data[OFFSET+4] & 0xff)) / 400.0;
|
||||||
|
robject.pressure = ((((data[OFFSET+5] & 0xff) << 8) | (data[OFFSET+6] & 0xff)) + 50000) / 100.0;
|
||||||
|
|
||||||
|
let accelerationX = (data[OFFSET+7] << 8) | (data[OFFSET+8] & 0xff);
|
||||||
|
if (accelerationX > 32767) accelerationX -= 65536; // two's complement
|
||||||
|
robject.accelerationX = accelerationX / 1000.0;
|
||||||
|
|
||||||
|
let accelerationY = (data[OFFSET+9] << 8) | (data[OFFSET+10] & 0xff);
|
||||||
|
if (accelerationY > 32767) accelerationY -= 65536; // two's complement
|
||||||
|
robject.accelerationY = accelerationY / 1000.0;
|
||||||
|
|
||||||
|
let accelerationZ = (data[OFFSET+11] << 8) | (data[OFFSET+12] & 0xff);
|
||||||
|
if (accelerationZ > 32767) accelerationZ -= 65536; // two's complement
|
||||||
|
robject.accelerationZ = accelerationZ / 1000.0;
|
||||||
|
|
||||||
|
const powerInfo = ((data[OFFSET+13] & 0xff) << 8) | (data[OFFSET+14] & 0xff);
|
||||||
|
robject.battery = ((powerInfo >>> 5) + 1600) / 1000.0;
|
||||||
|
robject.txPower = (powerInfo & 0b11111) * 2 - 40;
|
||||||
|
robject.movementCounter = data[OFFSET+15] & 0xff;
|
||||||
|
robject.measurementSequenceNumber = ((data[OFFSET+16] & 0xff) << 8) | (data[OFFSET+17] & 0xff);
|
||||||
|
|
||||||
|
robject.mac = [
|
||||||
|
int2Hex(data[OFFSET+18]),
|
||||||
|
int2Hex(data[OFFSET+19]),
|
||||||
|
int2Hex(data[OFFSET+20]),
|
||||||
|
int2Hex(data[OFFSET+21]),
|
||||||
|
int2Hex(data[OFFSET+22]),
|
||||||
|
int2Hex(data[OFFSET+23])
|
||||||
|
].join(':');
|
||||||
|
|
||||||
|
robject.name = "Ruuvi " + int2Hex(data[OFFSET+22]) + int2Hex(data[OFFSET+23]);
|
||||||
|
return robject;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAge(created) {
|
||||||
|
const now = new Date().getTime();
|
||||||
|
const ago = ((now - created) / 1000).toFixed(0);
|
||||||
|
return ago > 0 ? ago + "s ago" : "now";
|
||||||
|
}
|
||||||
|
|
||||||
|
function redraw() {
|
||||||
|
if (ruuvis.length > 0 && ruuvis[current]) {
|
||||||
|
const ruuvi = ruuvis[current];
|
||||||
|
g.clear();
|
||||||
|
g.setFontAlign(0,0);
|
||||||
|
g.setFont("Vector",12);
|
||||||
|
g.drawString(" (" + (current+1) + "/" + ruuvis.length + ")", g.getWidth()/2, 10);
|
||||||
|
g.setFont("Vector",20);
|
||||||
|
g.drawString(ruuvi.name, g.getWidth()/2, 30);
|
||||||
|
g.setFont("Vector",12);
|
||||||
|
const age = getAge(ruuvi.time);
|
||||||
|
if(age > (5*60)) {
|
||||||
|
g.setColor("#ff0000");
|
||||||
|
} else if (age > 60) {
|
||||||
|
g.setColor("#f39c12");
|
||||||
|
} else {
|
||||||
|
g.setColor("#2ecc71");
|
||||||
|
}
|
||||||
|
g.drawString(age, g.getWidth()/2, 50);
|
||||||
|
g.setColor("#ffffff");
|
||||||
|
g.setFont("Vector",60);
|
||||||
|
g.drawString(ruuvi.temperature.toFixed(2) + "°c", g.getWidth()/2, g.getHeight()/2);
|
||||||
|
g.setFontAlign(0,1);
|
||||||
|
g.setFont("Vector",20);
|
||||||
|
g.drawString(ruuvi.humidity + "% " + ruuvi.pressure + "hPa ", g.getWidth()/2, g.getHeight()-30);
|
||||||
|
g.setFont("Vector",12);
|
||||||
|
g.drawString(ruuvi.battery + "v", g.getWidth()/2, g.getHeight()-10);
|
||||||
|
} else {
|
||||||
|
g.clear();
|
||||||
|
g.drawImage(require("Storage").read("ruuviwatch.img"), g.getWidth()/2-24, g.getHeight()/2-24);
|
||||||
|
g.setFontAlign(0,0);
|
||||||
|
g.setFont("Vector",16);
|
||||||
|
g.drawString("Looking for Ruuvi...", g.getWidth()/2, g.getHeight()/2 + 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function scan() {
|
||||||
|
NRF.findDevices(function(devices) {
|
||||||
|
let foundNew = false;
|
||||||
|
devices.forEach(device => {
|
||||||
|
const data = p(device.data);
|
||||||
|
data.time = new Date().getTime();
|
||||||
|
const idx = lookup[data.name];
|
||||||
|
if (idx !== undefined) {
|
||||||
|
ruuvis[idx] = data;
|
||||||
|
} else {
|
||||||
|
lookup[data.name] = ruuvis.push(data)-1;
|
||||||
|
foundNew = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
redraw();
|
||||||
|
if (foundNew) {
|
||||||
|
Bangle.buzz();
|
||||||
|
g.flip();
|
||||||
|
}
|
||||||
|
|
||||||
|
}, {timeout : 2000, filters : [{ manufacturerData:{0x0499:{}} }] });
|
||||||
|
}
|
||||||
|
|
||||||
|
g.clear();
|
||||||
|
g.drawImage(require("Storage").read("ruuviwatch.img"), g.getWidth()/2-24, g.getHeight()/2-24);
|
||||||
|
|
||||||
|
var drawInterval = setInterval(redraw, 1000);
|
||||||
|
var scanInterval = setInterval(scan, 10000);
|
||||||
|
setWatch(() => {
|
||||||
|
current--;
|
||||||
|
if (current < 0) {
|
||||||
|
current = ruuvis.length-1;
|
||||||
|
}
|
||||||
|
redraw();
|
||||||
|
}, BTN1, {repeat:true});
|
||||||
|
|
||||||
|
setWatch(() => {
|
||||||
|
scan();
|
||||||
|
}, BTN2, {repeat:true});
|
||||||
|
|
||||||
|
setWatch(() => {
|
||||||
|
current++;
|
||||||
|
if (current >= ruuvis.length) {
|
||||||
|
current = 0;
|
||||||
|
}
|
||||||
|
redraw();
|
||||||
|
}, BTN3, {repeat:true});
|
||||||
|
|
||||||
|
scan();
|
Binary file not shown.
After Width: | Height: | Size: 665 B |
|
@ -1,3 +1,4 @@
|
||||||
1.00: Release for Bangle 2 (2021/11/18)
|
1.00: Release for Bangle 2 (2021/11/18)
|
||||||
1.01: Internal id update to wid_* as per Gordon's request (2021/11/21)
|
1.01: Internal id update to wid_* as per Gordon's request (2021/11/21)
|
||||||
1.02: Support dark themes
|
1.02: Support dark themes
|
||||||
|
1.03: Increase screen update rate when charging
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
(function(){
|
(function(){
|
||||||
|
const intervalLow = 60000; // update time when not charging
|
||||||
|
const intervalHigh = 2000; // update time when charging
|
||||||
|
|
||||||
let COLORS = {
|
let COLORS = {
|
||||||
'white': g.theme.dark ? "#000" : "#fff",
|
'white': g.theme.dark ? "#000" : "#fff",
|
||||||
'black': g.theme.dark ? "#fff" : "#000",
|
'black': g.theme.dark ? "#fff" : "#000",
|
||||||
|
@ -36,10 +39,14 @@
|
||||||
g.setFontAlign(0,0);
|
g.setFontAlign(0,0);
|
||||||
g.setFont('6x8');
|
g.setFont('6x8');
|
||||||
g.drawString(l, x + 14, y + 10);
|
g.drawString(l, x + 14, y + 10);
|
||||||
|
|
||||||
|
if (Bangle.isCharging()) changeInterval(id, intervalHigh);
|
||||||
|
else changeInterval(id, intervalLow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Bangle.on('charging',function(charging) { draw(); });
|
Bangle.on('charging',function(charging) { draw(); });
|
||||||
setInterval(()=>WIDGETS["wid_a_battery_widget"].draw(), 60000);
|
var id = setInterval(()=>WIDGETS["wid_a_battery_widget"].draw(), intervalLow);
|
||||||
|
|
||||||
WIDGETS["wid_a_battery_widget"]={area:"tr",width:30,draw:draw};
|
WIDGETS["wid_a_battery_widget"]={area:"tr",width:30,draw:draw};
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -12,3 +12,4 @@
|
||||||
0.13: Fillbar setting added, see README
|
0.13: Fillbar setting added, see README
|
||||||
0.14: Fix drawing the bar when charging
|
0.14: Fix drawing the bar when charging
|
||||||
0.15: Added option to always display the icon when charging (useful if 'hide if charge greater than' is enabled)
|
0.15: Added option to always display the icon when charging (useful if 'hide if charge greater than' is enabled)
|
||||||
|
0.16: Increase screen update rate when charging
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
(function(){
|
(function(){
|
||||||
|
const intervalLow = 60000; // update time when not charging
|
||||||
|
const intervalHigh = 2000; // update time when charging
|
||||||
|
|
||||||
let COLORS = {};
|
let COLORS = {};
|
||||||
|
|
||||||
if (process.env.HWVERSION == 1) {
|
if (process.env.HWVERSION == 1) {
|
||||||
|
@ -19,11 +22,11 @@
|
||||||
'low': "#f00", // Red
|
'low': "#f00", // Red
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const SETTINGS_FILE = 'widbatpc.json'
|
const SETTINGS_FILE = 'widbatpc.json';
|
||||||
|
|
||||||
let settings
|
let settings;
|
||||||
function loadSettings() {
|
function loadSettings() {
|
||||||
settings = require('Storage').readJSON(SETTINGS_FILE, 1) || {}
|
settings = require('Storage').readJSON(SETTINGS_FILE, 1) || {};
|
||||||
const DEFAULTS = {
|
const DEFAULTS = {
|
||||||
'color': 'By Level',
|
'color': 'By Level',
|
||||||
'percentage': true,
|
'percentage': true,
|
||||||
|
@ -32,17 +35,17 @@
|
||||||
'alwaysoncharge': false,
|
'alwaysoncharge': false,
|
||||||
};
|
};
|
||||||
Object.keys(DEFAULTS).forEach(k=>{
|
Object.keys(DEFAULTS).forEach(k=>{
|
||||||
if (settings[k]===undefined) settings[k]=DEFAULTS[k]
|
if (settings[k]===undefined) settings[k]=DEFAULTS[k];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function setting(key) {
|
function setting(key) {
|
||||||
if (!settings) { loadSettings() }
|
if (!settings) { loadSettings(); }
|
||||||
return settings[key];
|
return settings[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
const levelColor = (l) => {
|
const levelColor = (l) => {
|
||||||
// "charging" is very bright -> percentage is hard to read, "high" is ok(ish)
|
// "charging" is very bright -> percentage is hard to read, "high" is ok(ish)
|
||||||
const green = setting('percentage') ? COLORS.high : COLORS.charging
|
const green = setting('percentage') ? COLORS.high : COLORS.charging;
|
||||||
switch (setting('color')) {
|
switch (setting('color')) {
|
||||||
case 'Monochrome': return COLORS.white; // no chance of reading the percentage here :-(
|
case 'Monochrome': return COLORS.white; // no chance of reading the percentage here :-(
|
||||||
case 'Green': return green;
|
case 'Green': return green;
|
||||||
|
@ -59,10 +62,11 @@
|
||||||
if (l >= 15) return COLORS.ok;
|
if (l >= 15) return COLORS.ok;
|
||||||
return COLORS.low;
|
return COLORS.low;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
const chargerColor = () => {
|
const chargerColor = () => {
|
||||||
return (setting('color') === 'Monochrome') ? COLORS.white : COLORS.charging
|
return (setting('color') === 'Monochrome') ? COLORS.white : COLORS.charging;
|
||||||
}
|
};
|
||||||
|
|
||||||
// sets width, returns true if it changed
|
// sets width, returns true if it changed
|
||||||
function setWidth() {
|
function setWidth() {
|
||||||
var w = 40;
|
var w = 40;
|
||||||
|
@ -77,6 +81,7 @@
|
||||||
WIDGETS["batpc"].width = w;
|
WIDGETS["batpc"].width = w;
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw() {
|
function draw() {
|
||||||
// if hidden, don't draw
|
// if hidden, don't draw
|
||||||
if (!WIDGETS["batpc"].width) return;
|
if (!WIDGETS["batpc"].width) return;
|
||||||
|
@ -106,11 +111,11 @@
|
||||||
if (!setting('percentage')) {
|
if (!setting('percentage')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let gfx = g
|
let gfx = g;
|
||||||
if (setting('color') === 'Monochrome') {
|
if (setting('color') === 'Monochrome') {
|
||||||
// draw text inverted on battery level
|
// draw text inverted on battery level
|
||||||
gfx = Graphics.createCallback(g.getWidth(),g.getHeight(), 1,
|
gfx = Graphics.createCallback(g.getWidth(),g.getHeight(), 1,
|
||||||
(x,y) => {g.setPixel(x,y,x<=xl?0:-1)})
|
(x,y) => {g.setPixel(x,y,x<=xl?0:-1);});
|
||||||
}
|
}
|
||||||
gfx.setFontAlign(-1,-1);
|
gfx.setFontAlign(-1,-1);
|
||||||
if (l >= 100) {
|
if (l >= 100) {
|
||||||
|
@ -122,19 +127,24 @@
|
||||||
gfx.drawString(l, x + 6, y + 4);
|
gfx.drawString(l, x + 6, y + 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// reload widget, e.g. when settings have changed
|
// reload widget, e.g. when settings have changed
|
||||||
function reload() {
|
function reload() {
|
||||||
loadSettings()
|
loadSettings();
|
||||||
// need to redraw all widgets, because changing the "charger" setting
|
// need to redraw all widgets, because changing the "charger" setting
|
||||||
// can affect the width and mess with the whole widget layout
|
// can affect the width and mess with the whole widget layout
|
||||||
setWidth()
|
setWidth();
|
||||||
g.clear();
|
g.clear();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
}
|
}
|
||||||
|
|
||||||
// update widget - redraw just widget, or all widgets if size changed
|
// update widget - redraw just widget, or all widgets if size changed
|
||||||
function update() {
|
function update() {
|
||||||
if (setWidth()) Bangle.drawWidgets();
|
if (setWidth()) Bangle.drawWidgets();
|
||||||
else WIDGETS["batpc"].draw();
|
else WIDGETS["batpc"].draw();
|
||||||
|
|
||||||
|
if (Bangle.isCharging()) changeInterval(id, intervalHigh);
|
||||||
|
else changeInterval(id, intervalLow);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bangle.on('charging',function(charging) {
|
Bangle.on('charging',function(charging) {
|
||||||
|
@ -142,20 +152,13 @@
|
||||||
update();
|
update();
|
||||||
g.flip();
|
g.flip();
|
||||||
});
|
});
|
||||||
var batteryInterval;
|
|
||||||
Bangle.on('lcdPower', function(on) {
|
Bangle.on('lcdPower', function(on) {
|
||||||
if (on) {
|
if (on) update();
|
||||||
update();
|
|
||||||
// refresh once a minute if LCD on
|
|
||||||
if (!batteryInterval)
|
|
||||||
batteryInterval = setInterval(update, 60000);
|
|
||||||
} else {
|
|
||||||
if (batteryInterval) {
|
|
||||||
clearInterval(batteryInterval);
|
|
||||||
batteryInterval = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var id = setInterval(()=>WIDGETS["batpc"].draw(), intervalLow);
|
||||||
|
|
||||||
WIDGETS["batpc"]={area:"tr",width:40,draw:draw,reload:reload};
|
WIDGETS["batpc"]={area:"tr",width:40,draw:draw,reload:reload};
|
||||||
setWidth();
|
setWidth();
|
||||||
})()
|
})();
|
||||||
|
|
|
@ -20,3 +20,5 @@
|
||||||
Fix issue with widget overwrite in large font mode
|
Fix issue with widget overwrite in large font mode
|
||||||
Memory usage enhancements
|
Memory usage enhancements
|
||||||
0.20: Fix issue where step count would randomly reset
|
0.20: Fix issue where step count would randomly reset
|
||||||
|
0.21: Memory usage improvements, fix widget initial width (fix #1170)
|
||||||
|
0.22: Fix 'stps' regression for 0.21 (fix #1233)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
(() => {
|
(() => {
|
||||||
const PEDOMFILE = "wpedom.json"
|
|
||||||
// Last time Bangle.on('step' was called
|
// Last time Bangle.on('step' was called
|
||||||
let lastUpdate = new Date();
|
let lastUpdate = new Date();
|
||||||
// Last step count when Bangle.on('step' was called
|
// Last step count when Bangle.on('step' was called
|
||||||
|
@ -8,19 +7,14 @@
|
||||||
let settings;
|
let settings;
|
||||||
|
|
||||||
function loadSettings() {
|
function loadSettings() {
|
||||||
const d = require('Storage').readJSON(PEDOMFILE, 1) || {};
|
const d = require('Storage').readJSON("wpedom.json", 1) || {};
|
||||||
settings = d.settings || {};
|
settings = Object.assign({
|
||||||
}
|
|
||||||
|
|
||||||
function setting(key) {
|
|
||||||
if (!settings) { loadSettings() }
|
|
||||||
const DEFAULTS = {
|
|
||||||
'goal': 10000,
|
'goal': 10000,
|
||||||
'progress': false,
|
'progress': false,
|
||||||
'large': false,
|
'large': false,
|
||||||
'hide': false
|
'hide': false
|
||||||
}
|
}, d.settings || {});
|
||||||
return (key in settings) ? settings[key] : DEFAULTS[key];
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bangle.on('step', stepCount => {
|
Bangle.on('step', stepCount => {
|
||||||
|
@ -31,10 +25,10 @@
|
||||||
if (lastUpdate.getDate() == date.getDate()){
|
if (lastUpdate.getDate() == date.getDate()){
|
||||||
stp_today += steps;
|
stp_today += steps;
|
||||||
} else {
|
} else {
|
||||||
// TODO: could save this to PEDOMFILE for lastUpdate's day?
|
// TODO: could save this to "wpedom.json" for lastUpdate's day?
|
||||||
stp_today = steps;
|
stp_today = steps;
|
||||||
}
|
}
|
||||||
if (stp_today === setting('goal')
|
if (stp_today === settings.goal
|
||||||
&& !(require('Storage').readJSON('setting.json',1)||{}).quiet) {
|
&& !(require('Storage').readJSON('setting.json',1)||{}).quiet) {
|
||||||
let b = 3, buzz = () => {
|
let b = 3, buzz = () => {
|
||||||
if (b--) Bangle.buzz().then(() => setTimeout(buzz, 100))
|
if (b--) Bangle.buzz().then(() => setTimeout(buzz, 100))
|
||||||
|
@ -51,29 +45,31 @@
|
||||||
});
|
});
|
||||||
// When unloading, save state
|
// When unloading, save state
|
||||||
E.on('kill', () => {
|
E.on('kill', () => {
|
||||||
if (!settings) { loadSettings() }
|
require("Storage").writeJSON("wpedom.json",{
|
||||||
let d = {
|
|
||||||
lastUpdate : lastUpdate.valueOf(),
|
lastUpdate : lastUpdate.valueOf(),
|
||||||
stepsToday : stp_today,
|
stepsToday : stp_today,
|
||||||
settings : settings,
|
settings : settings,
|
||||||
};
|
});
|
||||||
require("Storage").write(PEDOMFILE,d);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// add your widget
|
// add your widget
|
||||||
WIDGETS["wpedom"]={area:"tl",width:26,
|
WIDGETS["wpedom"]={area:"tl",width:0,
|
||||||
redraw:function() { // work out the width, and queue a full redraw if needed
|
getWidth:function() {
|
||||||
let stps = stp_today.toString();
|
let stps = stp_today.toString();
|
||||||
let newWidth = 24;
|
let newWidth = 24;
|
||||||
if (setting('hide'))
|
if (settings.hide)
|
||||||
newWidth = 0;
|
newWidth = 0;
|
||||||
else {
|
else {
|
||||||
if (setting('large')) {
|
if (settings.large) {
|
||||||
newWidth = 12 * stps.length + 3;
|
newWidth = 12 * stps.length + 3;
|
||||||
if (setting('progress'))
|
if (settings.progress)
|
||||||
newWidth += 24;
|
newWidth += 24;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return newWidth;
|
||||||
|
},
|
||||||
|
redraw:function() { // work out the width, and queue a full redraw if needed
|
||||||
|
let newWidth = this.getWidth();
|
||||||
if (newWidth!=this.width) {
|
if (newWidth!=this.width) {
|
||||||
// width has changed, re-layout all widgets
|
// width has changed, re-layout all widgets
|
||||||
this.width = newWidth;
|
this.width = newWidth;
|
||||||
|
@ -84,14 +80,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
draw:function() {
|
draw:function() {
|
||||||
if (setting('hide')) return;
|
if (settings.hide) return;
|
||||||
if (stp_today > 99999)
|
if (stp_today > 99999)
|
||||||
stp_today = stp_today % 100000; // cap to five digits + comma = 6 characters
|
stp_today = stp_today % 100000; // cap to five digits + comma = 6 characters
|
||||||
let stps = stp_today.toString();
|
let stps = stp_today.toString();
|
||||||
g.reset().clearRect(this.x, this.y, this.x + this.width, this.y + 23); // erase background
|
g.reset().clearRect(this.x, this.y, this.x + this.width, this.y + 23); // erase background
|
||||||
if (setting('progress')) {
|
if (settings.progress) {
|
||||||
const width = 23, half = 11;
|
const width = 23, half = 11;
|
||||||
const goal = setting('goal'), left = Math.max(goal-stps,0);
|
const goal = settings.goal, left = Math.max(goal-stps,0);
|
||||||
// blue or dark green
|
// blue or dark green
|
||||||
g.setColor(left ? "#08f" : "#080").fillCircle(this.x + half, this.y + half, half);
|
g.setColor(left ? "#08f" : "#080").fillCircle(this.x + half, this.y + half, half);
|
||||||
if (left) {
|
if (left) {
|
||||||
|
@ -113,10 +109,10 @@
|
||||||
}
|
}
|
||||||
g.reset();
|
g.reset();
|
||||||
}
|
}
|
||||||
if (setting('large')) {
|
if (settings.large) {
|
||||||
g.setFont("6x8",2);
|
g.setFont("6x8",2);
|
||||||
g.setFontAlign(-1, 0);
|
g.setFontAlign(-1, 0);
|
||||||
g.drawString(stps, this.x + (setting('progress')?28:4), this.y + 12);
|
g.drawString(stps, this.x + (settings.progress?28:4), this.y + 12);
|
||||||
} else {
|
} else {
|
||||||
let w = 24;
|
let w = 24;
|
||||||
if (stps.length > 3){
|
if (stps.length > 3){
|
||||||
|
@ -137,11 +133,12 @@
|
||||||
getSteps:()=>stp_today
|
getSteps:()=>stp_today
|
||||||
};
|
};
|
||||||
// Load data at startup
|
// Load data at startup
|
||||||
let pedomData = require("Storage").readJSON(PEDOMFILE,1);
|
let pedomData = loadSettings();
|
||||||
if (pedomData) {
|
if (pedomData) {
|
||||||
if (pedomData.lastUpdate)
|
if (pedomData.lastUpdate)
|
||||||
lastUpdate = new Date(pedomData.lastUpdate);
|
lastUpdate = new Date(pedomData.lastUpdate);
|
||||||
stp_today = pedomData.stepsToday|0;
|
stp_today = pedomData.stepsToday|0;
|
||||||
delete pedomData;
|
delete pedomData;
|
||||||
}
|
}
|
||||||
|
WIDGETS["wpedom"].width = WIDGETS["wpedom"].getWidth();
|
||||||
})()
|
})()
|
||||||
|
|
Loading…
Reference in New Issue