Merge branch 'espruino:master' into master
|
|
@ -2,3 +2,4 @@
|
||||||
0.02: Load AGPS data on app start and automatically in background
|
0.02: Load AGPS data on app start and automatically in background
|
||||||
0.03: Do not load AGPS data on boot
|
0.03: Do not load AGPS data on boot
|
||||||
Increase minimum interval to 6 hours
|
Increase minimum interval to 6 hours
|
||||||
|
0.04: Write AGPS data chunks with delay to improve reliability
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ function updateAgps() {
|
||||||
g.clear();
|
g.clear();
|
||||||
if (!waiting) {
|
if (!waiting) {
|
||||||
waiting = true;
|
waiting = true;
|
||||||
display("Updating A-GPS...");
|
display("Updating A-GPS...", "takes ~ 10 seconds");
|
||||||
require("agpsdata").pull(function() {
|
require("agpsdata").pull(function() {
|
||||||
waiting = false;
|
waiting = false;
|
||||||
display("A-GPS updated.", "touch to close");
|
display("A-GPS updated.", "touch to close");
|
||||||
|
|
|
||||||
|
|
@ -8,41 +8,52 @@ var FILE = "agpsdata.settings.json";
|
||||||
var settings;
|
var settings;
|
||||||
readSettings();
|
readSettings();
|
||||||
|
|
||||||
function setAGPS(data) {
|
function setAGPS(b64) {
|
||||||
var js = jsFromBase64(data);
|
return new Promise(function(resolve, reject) {
|
||||||
try {
|
var initCommands = "Bangle.setGPSPower(1);\n"; // turn GPS on
|
||||||
eval(js);
|
const gnsstype = settings.gnsstype || 1; // default GPS
|
||||||
return true;
|
initCommands += `Serial1.println("${CASIC_CHECKSUM("$PCAS04," + gnsstype)}")\n`; // set GNSS mode
|
||||||
}
|
// What about:
|
||||||
catch(e) {
|
// NAV-TIMEUTC (0x01 0x10)
|
||||||
console.log("error:", e);
|
// NAV-PV (0x01 0x03)
|
||||||
}
|
// or AGPS.zip uses AID-INI (0x0B 0x01)
|
||||||
return false;
|
|
||||||
|
eval(initCommands);
|
||||||
|
|
||||||
|
try {
|
||||||
|
writeChunks(atob(b64), resolve);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("error:", e);
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function jsFromBase64(b64) {
|
var chunkI = 0;
|
||||||
var bin = atob(b64);
|
function writeChunks(bin, resolve) {
|
||||||
var chunkSize = 128;
|
return new Promise(function(resolve2) {
|
||||||
var js = "Bangle.setGPSPower(1);\n"; // turn GPS on
|
const chunkSize = 128;
|
||||||
var gnsstype = settings.gnsstype || 1; // default GPS
|
setTimeout(function() {
|
||||||
js += `Serial1.println("${CASIC_CHECKSUM("$PCAS04,"+gnsstype)}")\n`; // set GNSS mode
|
if (chunkI < bin.length) {
|
||||||
// What about:
|
var chunk = bin.substr(chunkI, chunkSize);
|
||||||
// NAV-TIMEUTC (0x01 0x10)
|
js = `Serial1.write(atob("${btoa(chunk)}"))\n`;
|
||||||
// NAV-PV (0x01 0x03)
|
eval(js);
|
||||||
// or AGPS.zip uses AID-INI (0x0B 0x01)
|
|
||||||
|
|
||||||
for (var i=0;i<bin.length;i+=chunkSize) {
|
chunkI += chunkSize;
|
||||||
var chunk = bin.substr(i,chunkSize);
|
writeChunks(bin, resolve);
|
||||||
js += `Serial1.write(atob("${btoa(chunk)}"))\n`;
|
} else {
|
||||||
}
|
if (resolve)
|
||||||
return js;
|
resolve(); // call outer resolve
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function CASIC_CHECKSUM(cmd) {
|
function CASIC_CHECKSUM(cmd) {
|
||||||
var cs = 0;
|
var cs = 0;
|
||||||
for (var i=1;i<cmd.length;i++)
|
for (var i = 1; i < cmd.length; i++)
|
||||||
cs = cs ^ cmd.charCodeAt(i);
|
cs = cs ^ cmd.charCodeAt(i);
|
||||||
return cmd+"*"+cs.toString(16).toUpperCase().padStart(2, '0');
|
return cmd + "*" + cs.toString(16).toUpperCase().padStart(2, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateLastUpdate() {
|
function updateLastUpdate() {
|
||||||
|
|
@ -53,23 +64,30 @@ function updateLastUpdate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.pull = function(successCallback, failureCallback) {
|
exports.pull = function(successCallback, failureCallback) {
|
||||||
let uri = "https://www.espruino.com/agps/casic.base64";
|
const uri = "https://www.espruino.com/agps/casic.base64";
|
||||||
if (Bangle.http){
|
if (Bangle.http) {
|
||||||
Bangle.http(uri, {timeout:10000}).then(event => {
|
Bangle.http(uri, {timeout : 10000})
|
||||||
let result = setAGPS(event.resp);
|
.then(event => {
|
||||||
if (result) {
|
setAGPS(event.resp)
|
||||||
updateLastUpdate();
|
.then(r => {
|
||||||
if (successCallback) successCallback();
|
updateLastUpdate();
|
||||||
} else {
|
if (successCallback)
|
||||||
console.log("error applying AGPS data");
|
successCallback();
|
||||||
if (failureCallback) failureCallback("Error applying AGPS data");
|
})
|
||||||
}
|
.catch((e) => {
|
||||||
}).catch((e)=>{
|
console.log("error", e);
|
||||||
console.log("error", e);
|
if (failureCallback)
|
||||||
if (failureCallback) failureCallback(e);
|
failureCallback(e);
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log("error", e);
|
||||||
|
if (failureCallback)
|
||||||
|
failureCallback(e);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log("error: No http method found");
|
console.log("error: No http method found");
|
||||||
if (failureCallback) failureCallback(/*LANG*/"No http method");
|
if (failureCallback)
|
||||||
|
failureCallback(/*LANG*/ "No http method");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "A-GPS Data Downloader App",
|
"name": "A-GPS Data Downloader App",
|
||||||
"shortName":"A-GPS Data",
|
"shortName":"A-GPS Data",
|
||||||
"icon": "agpsdata.png",
|
"icon": "agpsdata.png",
|
||||||
"version":"0.03",
|
"version":"0.04",
|
||||||
"description": "Once installed, this app allows you to download assisted GPS (A-GPS) data directly to your Bangle.js **via Gadgetbridge on an Android phone** when you run the app. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.",
|
"description": "Once installed, this app allows you to download assisted GPS (A-GPS) data directly to your Bangle.js **via Gadgetbridge on an Android phone** when you run the app. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.",
|
||||||
"tags": "boot,tool,assisted,gps,agps,http",
|
"tags": "boot,tool,assisted,gps,agps,http",
|
||||||
"allow_emulator":true,
|
"allow_emulator":true,
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
0.01: New app!
|
0.01: New app!
|
||||||
0.02: Design improvements and fixes.
|
0.02: Design improvements and fixes.
|
||||||
|
0.03: Indicate battery level through line occurrence.
|
||||||
|
|
@ -8,14 +8,16 @@ The original output of stable diffusion is shown here:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
And my implementation is shown here:
|
My implementation is shown below. Note that horizontal lines occur randomly, but the
|
||||||
|
probability is correlated with the battery level. So if your screen contains only
|
||||||
|
a few lines its time to charge your bangle again ;)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
# Thanks to
|
# Thanks to
|
||||||
The great open source community: I used an open source diffusion model (https://github.com/CompVis/stable-diffusion)
|
The great open-source community: I used an open-source diffusion model (https://github.com/CompVis/stable-diffusion)
|
||||||
to generate a watch face for the open source smartwatch BangleJs.
|
to generate a watch face for the open-source smartwatch BangleJs.
|
||||||
|
|
||||||
## Creator
|
## Creator
|
||||||
- [David Peer](https://github.com/peerdavid).
|
- [David Peer](https://github.com/peerdavid).
|
||||||
|
|
@ -37,8 +37,15 @@ function drawBackground() {
|
||||||
g.setFontAlign(0,0);
|
g.setFontAlign(0,0);
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
|
|
||||||
y = 0;
|
var bat = E.getBattery() / 100.0;
|
||||||
|
var y = 0;
|
||||||
while(y < H){
|
while(y < H){
|
||||||
|
// Show less lines in case of small battery level.
|
||||||
|
if(Math.random() > bat){
|
||||||
|
y += 5;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
y += 3 + Math.floor(Math.random() * 10);
|
y += 3 + Math.floor(Math.random() * 10);
|
||||||
g.drawLine(0, y, W, y);
|
g.drawLine(0, y, W, y);
|
||||||
g.drawLine(0, y+1, W, y+1);
|
g.drawLine(0, y+1, W, y+1);
|
||||||
|
|
@ -103,7 +110,7 @@ function drawDate(){
|
||||||
g.setFontAlign(0,0);
|
g.setFontAlign(0,0);
|
||||||
g.setFontGochiHand();
|
g.setFontGochiHand();
|
||||||
|
|
||||||
var text = ("0"+date.getDate()).substr(-2) + "/" + ("0"+date.getMonth()).substr(-2);
|
var text = ("0"+date.getDate()).substr(-2) + "/" + ("0"+(date.getMonth()+1)).substr(-2);
|
||||||
var w = g.stringWidth(text);
|
var w = g.stringWidth(text);
|
||||||
g.setColor(g.theme.bg);
|
g.setColor(g.theme.bg);
|
||||||
g.fillRect(cx-w/2-4, 20, cx+w/2+4, 40+12);
|
g.fillRect(cx-w/2-4, 20, cx+w/2+4, 40+12);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "AI Clock",
|
"name": "AI Clock",
|
||||||
"shortName":"AI Clock",
|
"shortName":"AI Clock",
|
||||||
"icon": "aiclock.png",
|
"icon": "aiclock.png",
|
||||||
"version":"0.02",
|
"version":"0.03",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"supports": ["BANGLEJS2"],
|
"supports": ["BANGLEJS2"],
|
||||||
"description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.",
|
"description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
0.01: New App!
|
||||||
|
0.02: Added adjustment for Bangle.js magnetometer heading fix
|
||||||
|
|
@ -224,7 +224,7 @@ Bangle.on('mag', function (m) {
|
||||||
if (isNaN(m.heading))
|
if (isNaN(m.heading))
|
||||||
compass_heading = "---";
|
compass_heading = "---";
|
||||||
else
|
else
|
||||||
compass_heading = 360 - Math.round(m.heading);
|
compass_heading = Math.round(m.heading);
|
||||||
current_colour = g.getColor();
|
current_colour = g.getColor();
|
||||||
g.reset();
|
g.reset();
|
||||||
g.setColor(background_colour);
|
g.setColor(background_colour);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "alpinenav",
|
"id": "alpinenav",
|
||||||
"name": "Alpine Nav",
|
"name": "Alpine Nav",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "App that performs GPS monitoring to track and display position relative to a given origin in realtime",
|
"description": "App that performs GPS monitoring to track and display position relative to a given origin in realtime",
|
||||||
"icon": "app-icon.png",
|
"icon": "app-icon.png",
|
||||||
"tags": "outdoors,gps",
|
"tags": "outdoors,gps",
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,5 @@
|
||||||
0.08: fixed calendar weeknumber not shortened to two digits
|
0.08: fixed calendar weeknumber not shortened to two digits
|
||||||
0.09: Use default Bangle formatter for booleans
|
0.09: Use default Bangle formatter for booleans
|
||||||
0.10: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16
|
0.10: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16
|
||||||
|
0.11: Moved enhanced Anton clock to 'Anton Clock Plus' and stripped this clock back down to make it faster for new users (270ms -> 170ms)
|
||||||
|
Modified to avoid leaving functions defined when using setUI({remove:...})
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 14 KiB |
|
|
@ -1,9 +1,8 @@
|
||||||
{
|
{
|
||||||
"id": "antonclk",
|
"id": "antonclk",
|
||||||
"name": "Anton Clock",
|
"name": "Anton Clock",
|
||||||
"version": "0.10",
|
"version": "0.11",
|
||||||
"description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.",
|
"description": "A simple clock using the bold Anton font. See `Anton Clock Plus` for an enhanced version",
|
||||||
"readme":"README.md",
|
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot.png"}],
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
|
@ -12,8 +11,6 @@
|
||||||
"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"}]
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 2.8 KiB |
|
|
@ -0,0 +1,15 @@
|
||||||
|
0.01: New App!
|
||||||
|
0.02: Load widgets after setUI so widclk knows when to hide
|
||||||
|
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.05: Clock can optionally show ISO-8601 calendar weeknumber (default: Off)
|
||||||
|
when weekday name "Off": week #:<num>
|
||||||
|
when weekday name "On": weekday name is cut at 6th position and .#<week num> is added
|
||||||
|
0.06: fixes #1271 - wrong settings name
|
||||||
|
when weekday name and calendar weeknumber are on then display is <weekday short> #<calweek>
|
||||||
|
week is buffered until date or timezone changes
|
||||||
|
0.07: align default settings with app.js (otherwise the initial displayed settings will be confusing to users)
|
||||||
|
0.08: fixed calendar weeknumber not shortened to two digits
|
||||||
|
0.09: Use default Bangle formatter for booleans
|
||||||
|
0.10: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16
|
||||||
|
Modified to avoid leaving functions defined when using setUI({remove:...})
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Anton Clock - Large font digital watch with seconds and date
|
# Anton Clock Plus - 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.
|
Anton Clock Plus 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
|
## Features
|
||||||
|
|
||||||
|
|
@ -16,16 +16,16 @@ The basic time representation only shows hours and minutes of the current time.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Install Anton clock through the Bangle.js app loader.
|
* Install Anton Clock Plus through the Bangle.js app loader.
|
||||||
Configure it through the default Bangle.js configuration mechanism
|
* Configure it through the default Bangle.js configuration mechanism
|
||||||
(Settings app, "Apps" menu, "Anton clock" submenu).
|
(Settings app, "Apps" menu, "Anton clock" submenu).
|
||||||
If you like it, make it your default watch face
|
* If you like it, make it your default watch face
|
||||||
(Settings app, "System" menu, "Clock" submenu, select "Anton clock").
|
(Settings app, "System" menu, "Clock" submenu, select "Anton clock").
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Anton clock is configured by the standard settings mechanism of Bangle.js's operating system:
|
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.
|
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.
|
You configure Anton clock through several "on/off" switches in two menus.
|
||||||
|
|
||||||
### The main menu
|
### The main menu
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwgf/AH4At/l/Aofgh4DB+EAj4REQoM/AgP4AoeACIoLCg4FB4AFDCIwLCgAROgYIB8EBAoUH/gVBCIxQBCKYHBCJp9DI4ICBLJYRCn4RQEYMOR5ARDIgIRMYQZZBgARGZwZBDCKQrCgEDR5AdBUIQRJDoLXFCJD7J/xrICIQFCn4RH/4LDAoTaCCI4Ar/LLDCBfypMkCgMkyV/CJOSCIOf5IRGFwOfCJNP//JnmT588z/+pM/BYIRCk4RC/88+f/n4RCngRCz1JCIf5/nzGoQRIHwXPCIPJI4f8CJHJGQJKCCI59LCI5ZCCJ/+v/kBoM/+V/HIJrHBYJWB/JKB5x9JEYP8AQKdBpwRL841Dp41KZoTxBHYTXBWY77PCKKhJ/4/CcgMkXoQAiA="))
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"id": "antonclkplus",
|
||||||
|
"name": "Anton Clock Plus",
|
||||||
|
"shortName": "Anton Clock+",
|
||||||
|
"version": "0.10",
|
||||||
|
"description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.",
|
||||||
|
"readme":"README.md",
|
||||||
|
"icon": "app.png",
|
||||||
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
"type": "clock",
|
||||||
|
"tags": "clock",
|
||||||
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
|
"allow_emulator": true,
|
||||||
|
"storage": [
|
||||||
|
{"name":"antonclkplus.app.js","url":"app.js"},
|
||||||
|
{"name":"antonclkplus.settings.js","url":"settings.js"},
|
||||||
|
{"name":"antonclkplus.img","url":"app-icon.js","evaluate":true}
|
||||||
|
],
|
||||||
|
"data": [{"name":"antonclkplus.json"}]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
|
|
@ -2,3 +2,4 @@
|
||||||
0.02: Fixed Whirlpool galaxy RA/DA, larger compass display, fixed moonphase overlapping battery widget
|
0.02: Fixed Whirlpool galaxy RA/DA, larger compass display, fixed moonphase overlapping battery widget
|
||||||
0.03: Update to use Bangle.setUI instead of setWatch
|
0.03: Update to use Bangle.setUI instead of setWatch
|
||||||
0.04: Tell clock widgets to hide.
|
0.04: Tell clock widgets to hide.
|
||||||
|
0.05: Added adjustment for Bangle.js magnetometer heading fix
|
||||||
|
|
|
||||||
|
|
@ -834,7 +834,7 @@ Bangle.on('mag', function (m) {
|
||||||
if (isNaN(m.heading))
|
if (isNaN(m.heading))
|
||||||
compass_heading = "---";
|
compass_heading = "---";
|
||||||
else
|
else
|
||||||
compass_heading = 360 - Math.round(m.heading);
|
compass_heading = Math.round(m.heading);
|
||||||
// g.setColor("#000000");
|
// g.setColor("#000000");
|
||||||
// g.fillRect(160, 10, 160, 20);
|
// g.fillRect(160, 10, 160, 20);
|
||||||
g.setColor(display_colour);
|
g.setColor(display_colour);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "astral",
|
"id": "astral",
|
||||||
"name": "Astral Clock",
|
"name": "Astral Clock",
|
||||||
"version": "0.04",
|
"version": "0.05",
|
||||||
"description": "Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting. See Readme before using.",
|
"description": "Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting. See Readme before using.",
|
||||||
"icon": "app-icon.png",
|
"icon": "app-icon.png",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
0.01: Display pressure as number and hand
|
0.01: Display pressure as number and hand
|
||||||
0.02: Use theme color
|
0.02: Use theme color
|
||||||
0.03: workaround for some firmwares that return 'undefined' for first call to barometer
|
0.03: workaround for some firmwares that return 'undefined' for first call to barometer
|
||||||
|
0.04: Update every second, go back with short button press
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ function drawTicks(){
|
||||||
function drawScaleLabels(){
|
function drawScaleLabels(){
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
g.setFont("Vector",12);
|
g.setFont("Vector",12);
|
||||||
|
g.setFontAlign(-1,-1);
|
||||||
|
|
||||||
let label = MIN;
|
let label = MIN;
|
||||||
for (let i=0;i <= NUMBER_OF_LABELS; i++){
|
for (let i=0;i <= NUMBER_OF_LABELS; i++){
|
||||||
|
|
@ -103,22 +104,29 @@ function drawIcons() {
|
||||||
}
|
}
|
||||||
|
|
||||||
g.setBgColor(g.theme.bg);
|
g.setBgColor(g.theme.bg);
|
||||||
g.clear();
|
|
||||||
|
|
||||||
drawTicks();
|
|
||||||
drawScaleLabels();
|
|
||||||
drawIcons();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
function baroHandler(data) {
|
function baroHandler(data) {
|
||||||
if (data===undefined) // workaround for https://github.com/espruino/BangleApps/issues/1429
|
g.clear();
|
||||||
setTimeout(() => Bangle.getPressure().then(baroHandler), 500);
|
|
||||||
else
|
drawTicks();
|
||||||
|
drawScaleLabels();
|
||||||
|
drawIcons();
|
||||||
|
if (data!==undefined) {
|
||||||
drawHand(Math.round(data.pressure));
|
drawHand(Math.round(data.pressure));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Bangle.getPressure().then(baroHandler);
|
Bangle.getPressure().then(baroHandler);
|
||||||
|
setInterval(() => Bangle.getPressure().then(baroHandler), 1000);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
print(e.message);
|
if (e !== undefined) {
|
||||||
print("barometer not supporter, show a demo value");
|
print(e.message);
|
||||||
|
}
|
||||||
|
print("barometer not supported, show a demo value");
|
||||||
drawHand(MIN);
|
drawHand(MIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bangle.setUI({
|
||||||
|
mode : "custom",
|
||||||
|
back : function() {load();}
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{ "id": "barometer",
|
{ "id": "barometer",
|
||||||
"name": "Barometer",
|
"name": "Barometer",
|
||||||
"shortName":"Barometer",
|
"shortName":"Barometer",
|
||||||
"version":"0.03",
|
"version":"0.04",
|
||||||
"description": "A simple barometer that displays the current air pressure",
|
"description": "A simple barometer that displays the current air pressure",
|
||||||
"icon": "barometer.png",
|
"icon": "barometer.png",
|
||||||
"tags": "tool,outdoors",
|
"tags": "tool,outdoors",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: First version
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
# BarWatch - an experimental watch
|
||||||
|
|
||||||
|
For too long the watches have shown the time with digits or hands. No more!
|
||||||
|
With this stylish watch the time is represented by bars. Up to 24 as the day goes by.
|
||||||
|
Practical? Not really, but a different look!
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("l0uwkE/4A/AH4A/AB0gicQmUB+EPgEigExh8gj8A+ECAgMQn4WCgcACyotWC34W/C34W/CycACw0wgYWFBYIWCAAc/+YGHCAgNFACkxl8hGYwAMLYUvCykQC34WycoIW/C34W0gAWTmUjkUzkbmSAFY="))
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
// timeout used to update every minute
|
||||||
|
var drawTimeout;
|
||||||
|
|
||||||
|
// schedule a draw for the next minute
|
||||||
|
function queueDraw() {
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = setTimeout(function() {
|
||||||
|
drawTimeout = undefined;
|
||||||
|
draw();
|
||||||
|
}, 60000 - (Date.now() % 60000));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
g.reset();
|
||||||
|
|
||||||
|
if(g.theme.dark){
|
||||||
|
g.setColor(1,1,1);
|
||||||
|
}else{
|
||||||
|
g.setColor(0,0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// work out how to display the current time
|
||||||
|
var d = new Date();
|
||||||
|
var h = d.getHours(), m = d.getMinutes();
|
||||||
|
|
||||||
|
// hour bars
|
||||||
|
var bx_offset = 10, by_offset = 35;
|
||||||
|
var b_width = 8, b_height = 60;
|
||||||
|
var b_space = 5;
|
||||||
|
|
||||||
|
for(var i=0; i<h; i++){
|
||||||
|
if(i > 11){
|
||||||
|
by_offset = 105;
|
||||||
|
}
|
||||||
|
var iter = i % 12;
|
||||||
|
//console.log(iter);
|
||||||
|
g.fillRect(bx_offset+(b_width*(iter+1))+(b_space*iter),
|
||||||
|
by_offset,
|
||||||
|
bx_offset+(b_width*iter)+(b_space*iter),
|
||||||
|
by_offset+b_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// minute bar
|
||||||
|
if(h > 11){
|
||||||
|
by_offset = 105;
|
||||||
|
}
|
||||||
|
var m_bar = h % 12;
|
||||||
|
if(m != 0){
|
||||||
|
g.fillRect(bx_offset+(b_width*(m_bar+1))+(b_space*m_bar),
|
||||||
|
by_offset+b_height-m,
|
||||||
|
bx_offset+(b_width*m_bar)+(b_space*m_bar),
|
||||||
|
by_offset+b_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// queue draw in one minute
|
||||||
|
queueDraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the screen once, at startup
|
||||||
|
g.clear();
|
||||||
|
// draw immediately at first
|
||||||
|
draw();
|
||||||
|
// Stop updates when LCD is off, restart when on
|
||||||
|
Bangle.on('lcdPower',on=>{
|
||||||
|
if (on) {
|
||||||
|
draw(); // draw immediately, queue redraw
|
||||||
|
} else { // stop draw timer
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Bangle.setUI("clock");
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
After Width: | Height: | Size: 973 B |
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"id": "barwatch",
|
||||||
|
"name": "BarWatch",
|
||||||
|
"shortName":"BarWatch",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "A watch that displays the time using bars. One bar for each hour.",
|
||||||
|
"readme": "README.md",
|
||||||
|
"icon": "screenshot.png",
|
||||||
|
"tags": "clock",
|
||||||
|
"type": "clock",
|
||||||
|
"allow_emulator":true,
|
||||||
|
"screenshots" : [ { "url": "screenshot.png" } ],
|
||||||
|
"supports" : ["BANGLEJS2"],
|
||||||
|
"storage": [
|
||||||
|
{"name":"barwatch.app.js","url":"app.js"},
|
||||||
|
{"name":"barwatch.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -0,0 +1,3 @@
|
||||||
|
0.01: New App!
|
||||||
|
0.02: Added adjustment for Bangle.js magnetometer heading fix
|
||||||
|
Bangle.js 2 compatibility
|
||||||
|
|
@ -1 +1 @@
|
||||||
require("heatshrink").decompress(atob("mEwghC/AB0O/4AG8AXNgYXHmAXl94XH+AXNn4XH/wXW+YX/C6oWHAAIXN7sz9vdAAoXN9sznvuAAXf/vuC53jC4Xd7wXQ93jn3u9vv9vt7wXT/4tBAgIXQ7wvCC4PgC5sO6czIQJfBC6PumaPDC6wwCC50NYAJcBVgIDBCxrAFbgYXP7yoDF6TADL4YXPVAIXCRyAXC7wXW9zwBC6cNC9zABC4gWQC653CR4fQC6x3TF6gXXI4M9d6wAEC9EN73dAAZfQgczAAkwC/4XXAH4"))
|
require("heatshrink").decompress(atob("mEw4cA///wH9/++1P+u3//3/qv/gv+KHkJkmABxcBBwNJkmQCJYOByQCCCBUCCItJkARQkgQHggLBku25IRDJQ4LCtu27Mt2RKJCInbAQIRLpYROglt24OB6wSC7dwLQ4LB9u2EgfbsARJ8u2mwRO+u3CNJtHCJFpCINALJoRCpCiGBoMSdQcpegIRGyaPB+QRDkARIyQRBc4YRKyet23iCJxHB6QRBzOJCJ+dCJY1CpfMGphrCp2YNZlL54CBEZgLBAQoRBiTFFCNMvmQRPndiEcJHEyQQECJMpAYIRQyARQwAROI4IAGB4wCBNAoRmhIRHCA4A/AAo"))
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,8 @@
|
||||||
var img_nofix = require("heatshrink").decompress(atob("mUyxH+ACYhJDygtYGsqLVF8u02gziGBoyhQ5gwDGRozRGCQydGCgybGCwyZC5gAaGPQwnGRAwpGQ4xwGFYyFDKsrlYxYDCsBmUyg4yXLyUsFwMyq1WAgUsNCRjUmVXroAEq8yMbcllkskwCEkplDmQwDq0sC54xEHQ9RqQAGqIwCFgOBAASYBSgMBltRAA0sgJsOGJeBxAAGwMrgIXIloxOJYNSvl8CwIDCqMBlYxNC4wxQDIOCwVYDIIDBGJ9YwV8rADBwRJCSqAVCAYaVMC4oxCPYYxQSo4xMSpIxPY4T5HY54XIMbIxKgwXKfKjhEllWGJNWlgXJGLNXruCGI+CrtXGKP+GJB9HMZ6VO/wxJcI8lfJclfKAxKfJEAGJIXLGKSvBWYQZCMZbfEqTHBGJYyFfIo1DGJ4tDGJQwCGJB9IMZyVNGIYyEfJQxPfJgwEMgoZJgAxMltRAA0tGJQyEksslkmAQklGINXxDTBFwIDCq8rC4YACC4gwJMowAJldWAAwwBABowIGJ4AYGJIymGBQylGBgyjGBwyhGCAzeF6YycGCwzYF7IzVF7o1PDqYA=="));
|
var img_nofix = require("heatshrink").decompress(atob("mUyxH+ACYhJDygtYGsqLVF8u02gziGBoyhQ5gwDGRozRGCQydGCgybGCwyZC5gAaGPQwnGRAwpGQ4xwGFYyFDKsrlYxYDCsBmUyg4yXLyUsFwMyq1WAgUsNCRjUmVXroAEq8yMbcllkskwCEkplDmQwDq0sC54xEHQ9RqQAGqIwCFgOBAASYBSgMBltRAA0sgJsOGJeBxAAGwMrgIXIloxOJYNSvl8CwIDCqMBlYxNC4wxQDIOCwVYDIIDBGJ9YwV8rADBwRJCSqAVCAYaVMC4oxCPYYxQSo4xMSpIxPY4T5HY54XIMbIxKgwXKfKjhEllWGJNWlgXJGLNXruCGI+CrtXGKP+GJB9HMZ6VO/wxJcI8lfJclfKAxKfJEAGJIXLGKSvBWYQZCMZbfEqTHBGJYyFfIo1DGJ4tDGJQwCGJB9IMZyVNGIYyEfJQxPfJgwEMgoZJgAxMltRAA0tGJQyEksslkmAQklGINXxDTBFwIDCq8rC4YACC4gwJMowAJldWAAwwBABowIGJ4AYGJIymGBQylGBgyjGBwyhGCAzeF6YycGCwzYF7IzVF7o1PDqYA=="));
|
||||||
var img_fix = require("heatshrink").decompress(atob("mUyxH+ACYhJDygtYGsqLVF94zaDYkq6wAOlQyYJo2A63VAAIoC2m0GI16My5/H5/V64ABGQIwBGQ+rTKwWHkhiBGIYwDGQ3VZioVIqoiBGAJhEGRFPGSYTIYwQxCGA4yFqodJGKeqSgQwJGQmkGKQSJfAYwLGQfPDxQwRgHVfAi/EAA4xLGQwRLYwb5BABoxQCBcA43G5wABAgIAMEBgxQ0QxB54xB5gAG4xgBBYOiGJ4PMGInPGIhcCGIt4EJoxPvHM5oxBGAnO6xrCGoXMqgxdpwxD5qQFL4QADlQxdgAhBGILIDMYoADEBwwPgCHBfQzHDAAb4NACTIIAA74OACLIIMo7GOACQoBZAoHBHQPNA4QwggGiZBA5B54HBY0DIKMYtUGMMqFYLIGY4jGhZAr6FAAYwiZAgxIY0TIFfQgADvAfR/zISGJTGR/wxRkj6CGJBiSGKL6DGP4xOGSKVDGAwxRGAQxU5oxcGR75DGJEkGCYxPlXM5vPGA/MlQxUGR1OGIL4I5lOGCgyOqgxBShHMqgwVGJt4GJd4GKwyMvHG5vGABAxMGBQyM1mtABWsGC4yLGBYABGDAyKGKwwQGZKVUF6b/OABowWGbAvZGaovdGp4dTA"));
|
var img_fix = require("heatshrink").decompress(atob("mUyxH+ACYhJDygtYGsqLVF94zaDYkq6wAOlQyYJo2A63VAAIoC2m0GI16My5/H5/V64ABGQIwBGQ+rTKwWHkhiBGIYwDGQ3VZioVIqoiBGAJhEGRFPGSYTIYwQxCGA4yFqodJGKeqSgQwJGQmkGKQSJfAYwLGQfPDxQwRgHVfAi/EAA4xLGQwRLYwb5BABoxQCBcA43G5wABAgIAMEBgxQ0QxB54xB5gAG4xgBBYOiGJ4PMGInPGIhcCGIt4EJoxPvHM5oxBGAnO6xrCGoXMqgxdpwxD5qQFL4QADlQxdgAhBGILIDMYoADEBwwPgCHBfQzHDAAb4NACTIIAA74OACLIIMo7GOACQoBZAoHBHQPNA4QwggGiZBA5B54HBY0DIKMYtUGMMqFYLIGY4jGhZAr6FAAYwiZAgxIY0TIFfQgADvAfR/zISGJTGR/wxRkj6CGJBiSGKL6DGP4xOGSKVDGAwxRGAQxU5oxcGR75DGJEkGCYxPlXM5vPGA/MlQxUGR1OGIL4I5lOGCgyOqgxBShHMqgwVGJt4GJd4GKwyMvHG5vGABAxMGBQyM1mtABWsGC4yLGBYABGDAyKGKwwQGZKVUF6b/OABowWGbAvZGaovdGp4dTA"));
|
||||||
|
|
||||||
|
var W = g.getWidth(), H = g.getHeight();
|
||||||
|
|
||||||
// https://github.com/Leaflet/Leaflet/blob/master/src/geo/projection/Projection.SphericalMercator.js
|
// https://github.com/Leaflet/Leaflet/blob/master/src/geo/projection/Projection.SphericalMercator.js
|
||||||
function project(latlong) {
|
function project(latlong) {
|
||||||
var d = Math.PI / 180,
|
var d = Math.PI / 180,
|
||||||
|
|
@ -170,32 +172,30 @@ Bangle.on('GPS', function(f) {
|
||||||
|
|
||||||
Bangle.on('mag', function(m) {
|
Bangle.on('mag', function(m) {
|
||||||
if (!Bangle.isLCDOn()) return;
|
if (!Bangle.isLCDOn()) return;
|
||||||
var headingrad = m.heading*Math.PI/180; // in radians
|
var headingrad = (360-m.heading)*Math.PI/180; // in radians
|
||||||
if (!isFinite(headingrad)) headingrad=0;
|
if (!isFinite(headingrad)) headingrad=0;
|
||||||
if (nearest)
|
if (nearest)
|
||||||
g.drawImage(img_fix,120,120,{
|
g.drawImage(img_fix,W/2,H/2,{
|
||||||
rotate: (Math.PI/2)+headingrad-nearestangle,
|
rotate: (Math.PI/2)+headingrad-nearestangle,
|
||||||
scale:3,
|
scale:3,
|
||||||
});
|
});
|
||||||
else
|
else
|
||||||
g.drawImage(img_nofix,120,120,{
|
g.drawImage(img_nofix,W/2,H/2,{
|
||||||
rotate: headingrad,
|
rotate: headingrad,
|
||||||
scale:2,
|
scale:2,
|
||||||
});
|
});
|
||||||
g.clearRect(60,0,180,24);
|
g.clearRect(0,0,W,24).setFontAlign(0,0).setFont("6x8");
|
||||||
g.setFontAlign(0,0);
|
|
||||||
g.setFont("6x8");
|
|
||||||
if (fix.fix) {
|
if (fix.fix) {
|
||||||
g.drawString(nearest ? nearest.name : "---",120,4);
|
g.drawString(nearest ? nearest.name : "---",W/2,4);
|
||||||
g.setFont("6x8",2);
|
g.setFont("6x8",2);
|
||||||
g.drawString(nearest ? Math.round(nearestdist)+"m" : "---",120,16);
|
g.drawString(nearest ? Math.round(nearestdist)+"m" : "---",W/2,16);
|
||||||
} else {
|
} else {
|
||||||
g.drawString(fix.satellites+" satellites",120,4);
|
g.drawString(fix.satellites+" satellites",W/2,4);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Bangle.setCompassPower(1);
|
Bangle.setCompassPower(1);
|
||||||
Bangle.setGPSPower(1);
|
Bangle.setGPSPower(1);
|
||||||
g.clear();`;
|
g.setColor("#fff").setBgColor("#000").clear();`;
|
||||||
|
|
||||||
sendCustomizedApp({
|
sendCustomizedApp({
|
||||||
storage:[
|
storage:[
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"id": "beer",
|
"id": "beer",
|
||||||
"name": "Beer Compass",
|
"name": "Beer Compass",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "Uploads all the pubs in an area onto your watch, so it can always point you at the nearest one",
|
"description": "Uploads all the pubs in an area onto your watch, so it can always point you at the nearest one",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "",
|
"tags": "",
|
||||||
"supports": ["BANGLEJS"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"custom": "custom.html",
|
"custom": "custom.html",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"beer.app.js"},
|
{"name":"beer.app.js"},
|
||||||
|
|
|
||||||
|
|
@ -55,3 +55,7 @@
|
||||||
0.49: Store first found clock as a setting to speed up further boots
|
0.49: Store first found clock as a setting to speed up further boots
|
||||||
0.50: Allow setting of screen rotation
|
0.50: Allow setting of screen rotation
|
||||||
Remove support for 2v11 and earlier firmware
|
Remove support for 2v11 and earlier firmware
|
||||||
|
0.51: Remove patches for 2v10 firmware (BEEPSET and setUI)
|
||||||
|
Add patch to ensure that compass heading is corrected on pre-2v15.68 firmware
|
||||||
|
Ensure clock is only fast-loaded if it doesn't contain widgets
|
||||||
|
0.52: Ensure heading patch for pre-2v15.68 firmware applies to getCompass
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
// This runs after a 'fresh' boot
|
// This runs after a 'fresh' boot
|
||||||
var s = require("Storage").readJSON("setting.json",1)||{};
|
var s = require("Storage").readJSON("setting.json",1)||{};
|
||||||
|
/* If were being called from JS code in order to load the clock quickly (eg from a launcher)
|
||||||
|
and the clock in question doesn't have widgets, force a normal 'load' as this will then
|
||||||
|
reset everything and remove the widgets. */
|
||||||
|
if (global.__FILE__ && !s.clockHasWidgets) {load();throw "Clock has no widgets, can't fast load";}
|
||||||
|
// Otherwise continue to try and load the clock
|
||||||
var _clkApp = require("Storage").read(s.clock);
|
var _clkApp = require("Storage").read(s.clock);
|
||||||
if (!_clkApp) {
|
if (!_clkApp) {
|
||||||
_clkApp = require("Storage").list(/\.info$/)
|
_clkApp = require("Storage").list(/\.info$/)
|
||||||
|
|
@ -14,6 +19,7 @@ if (!_clkApp) {
|
||||||
if (_clkApp){
|
if (_clkApp){
|
||||||
s.clock = _clkApp.src;
|
s.clock = _clkApp.src;
|
||||||
_clkApp = require("Storage").read(_clkApp.src);
|
_clkApp = require("Storage").read(_clkApp.src);
|
||||||
|
s.clockHasWidgets = _clkApp.includes("Bangle.loadWidgets");
|
||||||
require("Storage").writeJSON("setting.json", s);
|
require("Storage").writeJSON("setting.json", s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,23 +62,6 @@ if (s.ble===false) boot += `if (!NRF.getSecurityStatus().connected) NRF.sleep();
|
||||||
if (s.timeout!==undefined) boot += `Bangle.setLCDTimeout(${s.timeout});\n`;
|
if (s.timeout!==undefined) boot += `Bangle.setLCDTimeout(${s.timeout});\n`;
|
||||||
if (!s.timeout) boot += `Bangle.setLCDPower(1);\n`;
|
if (!s.timeout) boot += `Bangle.setLCDPower(1);\n`;
|
||||||
boot += `E.setTimeZone(${s.timezone});`;
|
boot += `E.setTimeZone(${s.timezone});`;
|
||||||
// Set vibrate, beep, etc IF on older firmwares
|
|
||||||
if (!Bangle.F_BEEPSET) {
|
|
||||||
if (!s.vibrate) boot += `Bangle.buzz=Promise.resolve;\n`
|
|
||||||
if (s.beep===false) boot += `Bangle.beep=Promise.resolve;\n`
|
|
||||||
else if (s.beep=="vib" && !BANGLEJS2) boot += `Bangle.beep = function (time, freq) {
|
|
||||||
return new Promise(function(resolve) {
|
|
||||||
if ((0|freq)<=0) freq=4000;
|
|
||||||
if ((0|time)<=0) time=200;
|
|
||||||
if (time>5000) time=5000;
|
|
||||||
analogWrite(D13,0.1,{freq:freq});
|
|
||||||
setTimeout(function() {
|
|
||||||
digitalWrite(D13,0);
|
|
||||||
resolve();
|
|
||||||
}, time);
|
|
||||||
});
|
|
||||||
};\n`;
|
|
||||||
}
|
|
||||||
// Draw out of memory errors onto the screen
|
// Draw out of memory errors onto the screen
|
||||||
boot += `E.on('errorFlag', function(errorFlags) {
|
boot += `E.on('errorFlag', function(errorFlags) {
|
||||||
g.reset(1).setColor("#ff0000").setFont("6x8").setFontAlign(0,1).drawString(errorFlags,g.getWidth()/2,g.getHeight()-1).flip();
|
g.reset(1).setColor("#ff0000").setFont("6x8").setFontAlign(0,1).drawString(errorFlags,g.getWidth()/2,g.getHeight()-1).flip();
|
||||||
|
|
@ -93,28 +76,13 @@ if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightne
|
||||||
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${E.toJS(s.passkey.toString())}, mitm:1, display:1});\n`;
|
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${E.toJS(s.passkey.toString())}, mitm:1, display:1});\n`;
|
||||||
if (s.whitelist) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`;
|
if (s.whitelist) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`;
|
||||||
if (s.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation
|
if (s.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation
|
||||||
// Pre-2v10 firmwares without a theme/setUI
|
// ================================================== FIXING OLDER FIRMWARES
|
||||||
delete g.theme; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
// 2v15.68 and before had compass heading inverted.
|
||||||
if (!g.theme) {
|
if (process.version.replace("v","")<215.68)
|
||||||
boot += `g.theme={fg:-1,bg:0,fg2:-1,bg2:7,fgH:-1,bgH:0x02F7,dark:true};\n`;
|
boot += `Bangle.on('mag',e=>{if(!isNaN(e.heading))e.heading=360-e.heading;});
|
||||||
}
|
Bangle.getCompass=(c=>(()=>{e=c();if(!isNaN(e.heading))e.heading=360-e.heading;return e;}))(Bangle.getCompass);`;
|
||||||
try {
|
|
||||||
Bangle.setUI({}); // In 2v12.xx we added the option for mode to be an object - for 2v12 and earlier, add a fix if it fails with an object supplied
|
|
||||||
} catch(e) {
|
|
||||||
boot += `Bangle._setUI = Bangle.setUI;
|
|
||||||
Bangle.setUI=function(mode, cb) {
|
|
||||||
if (Bangle.uiRemove) {
|
|
||||||
Bangle.uiRemove();
|
|
||||||
delete Bangle.uiRemove;
|
|
||||||
}
|
|
||||||
if ("object"==typeof mode) {
|
|
||||||
// TODO: handle mode.back?
|
|
||||||
mode = mode.mode;
|
|
||||||
}
|
|
||||||
Bangle._setUI(mode, cb);
|
|
||||||
};\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// ================================================== BOOT.JS
|
||||||
// Append *.boot.js files
|
// Append *.boot.js files
|
||||||
// These could change bleServices/bleServiceOptions if needed
|
// These could change bleServices/bleServiceOptions if needed
|
||||||
var bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
|
var bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "boot",
|
"id": "boot",
|
||||||
"name": "Bootloader",
|
"name": "Bootloader",
|
||||||
"version": "0.50",
|
"version": "0.52",
|
||||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||||
"icon": "bootloader.png",
|
"icon": "bootloader.png",
|
||||||
"type": "bootloader",
|
"type": "bootloader",
|
||||||
|
|
|
||||||
|
|
@ -29,3 +29,8 @@
|
||||||
Use default boolean formatter in custom menu and directly apply config if useful
|
Use default boolean formatter in custom menu and directly apply config if useful
|
||||||
Allow recording unmodified internal HR
|
Allow recording unmodified internal HR
|
||||||
Better connection retry handling
|
Better connection retry handling
|
||||||
|
0.13: Less time used during boot if disabled
|
||||||
|
0.14: Allow bonding (Debug menu)
|
||||||
|
Prevent mixing of BT and internal HRM events if both are enabled
|
||||||
|
Always use a grace period (default 0 ms) to decouple some connection steps
|
||||||
|
Device not found errors now utilize increasing timeouts
|
||||||
|
|
|
||||||
|
|
@ -1,633 +1 @@
|
||||||
(function() {
|
if ((require('Storage').readJSON("bthrm.json", true) || {}).enabled != false) require("bthrm").enable();
|
||||||
var settings = Object.assign(
|
|
||||||
require('Storage').readJSON("bthrm.default.json", true) || {},
|
|
||||||
require('Storage').readJSON("bthrm.json", true) || {}
|
|
||||||
);
|
|
||||||
|
|
||||||
var log = function(text, param){
|
|
||||||
if (global.showStatusInfo)
|
|
||||||
showStatusInfo(text);
|
|
||||||
if (settings.debuglog){
|
|
||||||
var logline = new Date().toISOString() + " - " + text;
|
|
||||||
if (param) logline += ": " + JSON.stringify(param);
|
|
||||||
print(logline);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
log("Settings: ", settings);
|
|
||||||
|
|
||||||
if (settings.enabled){
|
|
||||||
|
|
||||||
var clearCache = function() {
|
|
||||||
return require('Storage').erase("bthrm.cache.json");
|
|
||||||
};
|
|
||||||
|
|
||||||
var getCache = function() {
|
|
||||||
var cache = require('Storage').readJSON("bthrm.cache.json", true) || {};
|
|
||||||
if (settings.btid && settings.btid === cache.id) return cache;
|
|
||||||
clearCache();
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
|
|
||||||
var addNotificationHandler = function(characteristic) {
|
|
||||||
log("Setting notification handler"/*supportedCharacteristics[characteristic.uuid].handler*/);
|
|
||||||
characteristic.on('characteristicvaluechanged', (ev) => supportedCharacteristics[characteristic.uuid].handler(ev.target.value));
|
|
||||||
};
|
|
||||||
|
|
||||||
var writeCache = function(cache) {
|
|
||||||
var oldCache = getCache();
|
|
||||||
if (oldCache !== cache) {
|
|
||||||
log("Writing cache");
|
|
||||||
require('Storage').writeJSON("bthrm.cache.json", cache);
|
|
||||||
} else {
|
|
||||||
log("No changes, don't write cache");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var characteristicsToCache = function(characteristics) {
|
|
||||||
log("Cache characteristics");
|
|
||||||
var cache = getCache();
|
|
||||||
if (!cache.characteristics) cache.characteristics = {};
|
|
||||||
for (var c of characteristics){
|
|
||||||
//"handle_value":16,"handle_decl":15
|
|
||||||
log("Saving handle " + c.handle_value + " for characteristic: ", c);
|
|
||||||
cache.characteristics[c.uuid] = {
|
|
||||||
"handle": c.handle_value,
|
|
||||||
"uuid": c.uuid,
|
|
||||||
"notify": c.properties.notify,
|
|
||||||
"read": c.properties.read
|
|
||||||
};
|
|
||||||
}
|
|
||||||
writeCache(cache);
|
|
||||||
};
|
|
||||||
|
|
||||||
var characteristicsFromCache = function(device) {
|
|
||||||
var service = { device : device }; // fake a BluetoothRemoteGATTService
|
|
||||||
log("Read cached characteristics");
|
|
||||||
var cache = getCache();
|
|
||||||
if (!cache.characteristics) return [];
|
|
||||||
var restored = [];
|
|
||||||
for (var c in cache.characteristics){
|
|
||||||
var cached = cache.characteristics[c];
|
|
||||||
var r = new BluetoothRemoteGATTCharacteristic();
|
|
||||||
log("Restoring characteristic ", cached);
|
|
||||||
r.handle_value = cached.handle;
|
|
||||||
r.uuid = cached.uuid;
|
|
||||||
r.properties = {};
|
|
||||||
r.properties.notify = cached.notify;
|
|
||||||
r.properties.read = cached.read;
|
|
||||||
r.service = service;
|
|
||||||
addNotificationHandler(r);
|
|
||||||
log("Restored characteristic: ", r);
|
|
||||||
restored.push(r);
|
|
||||||
}
|
|
||||||
return restored;
|
|
||||||
};
|
|
||||||
|
|
||||||
log("Start");
|
|
||||||
|
|
||||||
var lastReceivedData={
|
|
||||||
};
|
|
||||||
|
|
||||||
var supportedServices = [
|
|
||||||
"0x180d", // Heart Rate
|
|
||||||
"0x180f", // Battery
|
|
||||||
];
|
|
||||||
|
|
||||||
var bpmTimeout;
|
|
||||||
|
|
||||||
var supportedCharacteristics = {
|
|
||||||
"0x2a37": {
|
|
||||||
//Heart rate measurement
|
|
||||||
active: false,
|
|
||||||
handler: function (dv){
|
|
||||||
var flags = dv.getUint8(0);
|
|
||||||
|
|
||||||
var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit
|
|
||||||
supportedCharacteristics["0x2a37"].active = bpm > 0;
|
|
||||||
log("BTHRM BPM " + supportedCharacteristics["0x2a37"].active);
|
|
||||||
if (supportedCharacteristics["0x2a37"].active) stopFallback();
|
|
||||||
if (bpmTimeout) clearTimeout(bpmTimeout);
|
|
||||||
bpmTimeout = setTimeout(()=>{
|
|
||||||
supportedCharacteristics["0x2a37"].active = false;
|
|
||||||
startFallback();
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
var sensorContact;
|
|
||||||
|
|
||||||
if (flags & 2){
|
|
||||||
sensorContact = !!(flags & 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
var idx = 2 + (flags&1);
|
|
||||||
|
|
||||||
var energyExpended;
|
|
||||||
if (flags & 8){
|
|
||||||
energyExpended = dv.getUint16(idx,1);
|
|
||||||
idx += 2;
|
|
||||||
}
|
|
||||||
var interval;
|
|
||||||
if (flags & 16) {
|
|
||||||
interval = [];
|
|
||||||
var maxIntervalBytes = (dv.byteLength - idx);
|
|
||||||
log("Found " + (maxIntervalBytes / 2) + " rr data fields");
|
|
||||||
for(var i = 0 ; i < maxIntervalBytes / 2; i++){
|
|
||||||
interval[i] = dv.getUint16(idx,1); // in milliseconds
|
|
||||||
idx += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var location;
|
|
||||||
if (lastReceivedData && lastReceivedData["0x180d"] && lastReceivedData["0x180d"]["0x2a38"]){
|
|
||||||
location = lastReceivedData["0x180d"]["0x2a38"];
|
|
||||||
}
|
|
||||||
|
|
||||||
var battery;
|
|
||||||
if (lastReceivedData && lastReceivedData["0x180f"] && lastReceivedData["0x180f"]["0x2a19"]){
|
|
||||||
battery = lastReceivedData["0x180f"]["0x2a19"];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.replace){
|
|
||||||
var repEvent = {
|
|
||||||
bpm: bpm,
|
|
||||||
confidence: (sensorContact || sensorContact === undefined)? 100 : 0,
|
|
||||||
src: "bthrm"
|
|
||||||
};
|
|
||||||
|
|
||||||
log("Emitting HRM", repEvent);
|
|
||||||
Bangle.emit("HRM_int", repEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
var newEvent = {
|
|
||||||
bpm: bpm
|
|
||||||
};
|
|
||||||
|
|
||||||
if (location) newEvent.location = location;
|
|
||||||
if (interval) newEvent.rr = interval;
|
|
||||||
if (energyExpended) newEvent.energy = energyExpended;
|
|
||||||
if (battery) newEvent.battery = battery;
|
|
||||||
if (sensorContact) newEvent.contact = sensorContact;
|
|
||||||
|
|
||||||
log("Emitting BTHRM", newEvent);
|
|
||||||
Bangle.emit("BTHRM", newEvent);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"0x2a38": {
|
|
||||||
//Body sensor location
|
|
||||||
handler: function(dv){
|
|
||||||
if (!lastReceivedData["0x180d"]) lastReceivedData["0x180d"] = {};
|
|
||||||
lastReceivedData["0x180d"]["0x2a38"] = parseInt(dv.buffer, 10);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"0x2a19": {
|
|
||||||
//Battery
|
|
||||||
handler: function (dv){
|
|
||||||
if (!lastReceivedData["0x180f"]) lastReceivedData["0x180f"] = {};
|
|
||||||
lastReceivedData["0x180f"]["0x2a19"] = dv.getUint8(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var device;
|
|
||||||
var gatt;
|
|
||||||
var characteristics = [];
|
|
||||||
var blockInit = false;
|
|
||||||
var currentRetryTimeout;
|
|
||||||
var initialRetryTime = 40;
|
|
||||||
var maxRetryTime = 60000;
|
|
||||||
var retryTime = initialRetryTime;
|
|
||||||
|
|
||||||
var connectSettings = {
|
|
||||||
minInterval: 7.5,
|
|
||||||
maxInterval: 1500
|
|
||||||
};
|
|
||||||
|
|
||||||
var waitingPromise = function(timeout) {
|
|
||||||
return new Promise(function(resolve){
|
|
||||||
log("Start waiting for " + timeout);
|
|
||||||
setTimeout(()=>{
|
|
||||||
log("Done waiting for " + timeout);
|
|
||||||
resolve();
|
|
||||||
}, timeout);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (settings.enabled){
|
|
||||||
Bangle.isBTHRMActive = function (){
|
|
||||||
return supportedCharacteristics["0x2a37"].active;
|
|
||||||
};
|
|
||||||
|
|
||||||
Bangle.isBTHRMOn = function(){
|
|
||||||
return (Bangle._PWR && Bangle._PWR.BTHRM && Bangle._PWR.BTHRM.length > 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
Bangle.isBTHRMConnected = function(){
|
|
||||||
return gatt && gatt.connected;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.replace){
|
|
||||||
Bangle.origIsHRMOn = Bangle.isHRMOn;
|
|
||||||
|
|
||||||
Bangle.isHRMOn = function() {
|
|
||||||
if (settings.enabled && !settings.replace){
|
|
||||||
return Bangle.origIsHRMOn();
|
|
||||||
} else if (settings.enabled && settings.replace){
|
|
||||||
return Bangle.isBTHRMOn();
|
|
||||||
}
|
|
||||||
return Bangle.origIsHRMOn() || Bangle.isBTHRMOn();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var clearRetryTimeout = function(resetTime) {
|
|
||||||
if (currentRetryTimeout){
|
|
||||||
log("Clearing timeout " + currentRetryTimeout);
|
|
||||||
clearTimeout(currentRetryTimeout);
|
|
||||||
currentRetryTimeout = undefined;
|
|
||||||
}
|
|
||||||
if (resetTime) {
|
|
||||||
log("Resetting retry time");
|
|
||||||
retryTime = initialRetryTime;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var retry = function() {
|
|
||||||
log("Retry");
|
|
||||||
|
|
||||||
if (!currentRetryTimeout){
|
|
||||||
|
|
||||||
var clampedTime = retryTime < 100 ? 100 : retryTime;
|
|
||||||
|
|
||||||
log("Set timeout for retry as " + clampedTime);
|
|
||||||
clearRetryTimeout();
|
|
||||||
currentRetryTimeout = setTimeout(() => {
|
|
||||||
log("Retrying");
|
|
||||||
currentRetryTimeout = undefined;
|
|
||||||
initBt();
|
|
||||||
}, clampedTime);
|
|
||||||
|
|
||||||
retryTime = Math.pow(clampedTime, 1.1);
|
|
||||||
if (retryTime > maxRetryTime){
|
|
||||||
retryTime = maxRetryTime;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log("Already in retry...");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var buzzing = false;
|
|
||||||
var onDisconnect = function(reason) {
|
|
||||||
log("Disconnect: " + reason);
|
|
||||||
log("GATT", gatt);
|
|
||||||
log("Characteristics", characteristics);
|
|
||||||
clearRetryTimeout(reason != "Connection Timeout");
|
|
||||||
supportedCharacteristics["0x2a37"].active = false;
|
|
||||||
startFallback();
|
|
||||||
blockInit = false;
|
|
||||||
if (settings.warnDisconnect && !buzzing){
|
|
||||||
buzzing = true;
|
|
||||||
Bangle.buzz(500,0.3).then(()=>waitingPromise(4500)).then(()=>{buzzing = false;});
|
|
||||||
}
|
|
||||||
if (Bangle.isBTHRMOn()){
|
|
||||||
retry();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var createCharacteristicPromise = function(newCharacteristic) {
|
|
||||||
log("Create characteristic promise", newCharacteristic);
|
|
||||||
var result = Promise.resolve();
|
|
||||||
// For values that can be read, go ahead and read them, even if we might be notified in the future
|
|
||||||
// Allows for getting initial state of infrequently updating characteristics, like battery
|
|
||||||
if (newCharacteristic.readValue){
|
|
||||||
result = result.then(()=>{
|
|
||||||
log("Reading data", newCharacteristic);
|
|
||||||
return newCharacteristic.readValue().then((data)=>{
|
|
||||||
if (supportedCharacteristics[newCharacteristic.uuid] && supportedCharacteristics[newCharacteristic.uuid].handler) {
|
|
||||||
supportedCharacteristics[newCharacteristic.uuid].handler(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (newCharacteristic.properties.notify){
|
|
||||||
result = result.then(()=>{
|
|
||||||
log("Starting notifications", newCharacteristic);
|
|
||||||
var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic));
|
|
||||||
if (settings.gracePeriodNotification > 0){
|
|
||||||
log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications");
|
|
||||||
startPromise = startPromise.then(()=>{
|
|
||||||
log("Wait after connect");
|
|
||||||
return waitingPromise(settings.gracePeriodNotification);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return startPromise;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return result.then(()=>log("Handled characteristic", newCharacteristic));
|
|
||||||
};
|
|
||||||
|
|
||||||
var attachCharacteristicPromise = function(promise, characteristic) {
|
|
||||||
return promise.then(()=>{
|
|
||||||
log("Handling characteristic:", characteristic);
|
|
||||||
return createCharacteristicPromise(characteristic);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var createCharacteristicsPromise = function(newCharacteristics) {
|
|
||||||
log("Create characteristics promis ", newCharacteristics);
|
|
||||||
var result = Promise.resolve();
|
|
||||||
for (var c of newCharacteristics){
|
|
||||||
if (!supportedCharacteristics[c.uuid]) continue;
|
|
||||||
log("Supporting characteristic", c);
|
|
||||||
characteristics.push(c);
|
|
||||||
if (c.properties.notify){
|
|
||||||
addNotificationHandler(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
result = attachCharacteristicPromise(result, c);
|
|
||||||
}
|
|
||||||
return result.then(()=>log("Handled characteristics"));
|
|
||||||
};
|
|
||||||
|
|
||||||
var createServicePromise = function(service) {
|
|
||||||
log("Create service promise", service);
|
|
||||||
var result = Promise.resolve();
|
|
||||||
result = result.then(()=>{
|
|
||||||
log("Handling service" + service.uuid);
|
|
||||||
return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c));
|
|
||||||
});
|
|
||||||
return result.then(()=>log("Handled service" + service.uuid));
|
|
||||||
};
|
|
||||||
|
|
||||||
var attachServicePromise = function(promise, service) {
|
|
||||||
return promise.then(()=>createServicePromise(service));
|
|
||||||
};
|
|
||||||
|
|
||||||
var initBt = function () {
|
|
||||||
log("initBt with blockInit: " + blockInit);
|
|
||||||
if (blockInit){
|
|
||||||
retry();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockInit = true;
|
|
||||||
|
|
||||||
var promise;
|
|
||||||
var filters;
|
|
||||||
|
|
||||||
if (!device){
|
|
||||||
if (settings.btid){
|
|
||||||
log("Configured device id", settings.btid);
|
|
||||||
filters = [{ id: settings.btid }];
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log("Requesting device with filters", filters);
|
|
||||||
promise = NRF.requestDevice({ filters: filters, active: true });
|
|
||||||
|
|
||||||
if (settings.gracePeriodRequest){
|
|
||||||
log("Add " + settings.gracePeriodRequest + "ms grace period after request");
|
|
||||||
}
|
|
||||||
|
|
||||||
promise = promise.then((d)=>{
|
|
||||||
log("Got device", d);
|
|
||||||
d.on('gattserverdisconnected', onDisconnect);
|
|
||||||
device = d;
|
|
||||||
});
|
|
||||||
|
|
||||||
promise = promise.then(()=>{
|
|
||||||
log("Wait after request");
|
|
||||||
return waitingPromise(settings.gracePeriodRequest);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
promise = Promise.resolve();
|
|
||||||
log("Reuse device", device);
|
|
||||||
}
|
|
||||||
|
|
||||||
promise = promise.then(()=>{
|
|
||||||
if (gatt){
|
|
||||||
log("Reuse GATT", gatt);
|
|
||||||
} else {
|
|
||||||
log("GATT is new", gatt);
|
|
||||||
characteristics = [];
|
|
||||||
var cachedId = getCache().id;
|
|
||||||
if (device.id !== cachedId){
|
|
||||||
log("Device ID changed from " + cachedId + " to " + device.id + ", clearing cache");
|
|
||||||
clearCache();
|
|
||||||
}
|
|
||||||
var newCache = getCache();
|
|
||||||
newCache.id = device.id;
|
|
||||||
writeCache(newCache);
|
|
||||||
gatt = device.gatt;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve(gatt);
|
|
||||||
});
|
|
||||||
|
|
||||||
promise = promise.then((gatt)=>{
|
|
||||||
if (!gatt.connected){
|
|
||||||
log("Connecting...");
|
|
||||||
var connectPromise = gatt.connect(connectSettings).then(function() {
|
|
||||||
log("Connected.");
|
|
||||||
});
|
|
||||||
if (settings.gracePeriodConnect > 0){
|
|
||||||
log("Add " + settings.gracePeriodConnect + "ms grace period after connecting");
|
|
||||||
connectPromise = connectPromise.then(()=>{
|
|
||||||
log("Wait after connect");
|
|
||||||
return waitingPromise(settings.gracePeriodConnect);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return connectPromise;
|
|
||||||
} else {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* promise = promise.then(() => {
|
|
||||||
log(JSON.stringify(gatt.getSecurityStatus()));
|
|
||||||
if (gatt.getSecurityStatus()['bonded']) {
|
|
||||||
log("Already bonded");
|
|
||||||
return Promise.resolve();
|
|
||||||
} else {
|
|
||||||
log("Start bonding");
|
|
||||||
return gatt.startBonding()
|
|
||||||
.then(() => console.log(gatt.getSecurityStatus()));
|
|
||||||
}
|
|
||||||
});*/
|
|
||||||
|
|
||||||
promise = promise.then(()=>{
|
|
||||||
if (!characteristics || characteristics.length === 0){
|
|
||||||
characteristics = characteristicsFromCache(device);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
promise = promise.then(()=>{
|
|
||||||
var characteristicsPromise = Promise.resolve();
|
|
||||||
if (characteristics.length === 0){
|
|
||||||
characteristicsPromise = characteristicsPromise.then(()=>{
|
|
||||||
log("Getting services");
|
|
||||||
return gatt.getPrimaryServices();
|
|
||||||
});
|
|
||||||
|
|
||||||
characteristicsPromise = characteristicsPromise.then((services)=>{
|
|
||||||
log("Got services", services);
|
|
||||||
var result = Promise.resolve();
|
|
||||||
for (var service of services){
|
|
||||||
if (!(supportedServices.includes(service.uuid))) continue;
|
|
||||||
log("Supporting service", service.uuid);
|
|
||||||
result = attachServicePromise(result, service);
|
|
||||||
}
|
|
||||||
if (settings.gracePeriodService > 0) {
|
|
||||||
log("Add " + settings.gracePeriodService + "ms grace period after services");
|
|
||||||
result = result.then(()=>{
|
|
||||||
log("Wait after services");
|
|
||||||
return waitingPromise(settings.gracePeriodService);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
for (var characteristic of characteristics){
|
|
||||||
characteristicsPromise = attachCharacteristicPromise(characteristicsPromise, characteristic, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return characteristicsPromise;
|
|
||||||
});
|
|
||||||
|
|
||||||
return promise.then(()=>{
|
|
||||||
log("Connection established, waiting for notifications");
|
|
||||||
characteristicsToCache(characteristics);
|
|
||||||
clearRetryTimeout(true);
|
|
||||||
}).catch((e) => {
|
|
||||||
characteristics = [];
|
|
||||||
log("Error:", e);
|
|
||||||
onDisconnect(e);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Bangle.setBTHRMPower = function(isOn, app) {
|
|
||||||
// Do app power handling
|
|
||||||
if (!app) app="?";
|
|
||||||
if (Bangle._PWR===undefined) Bangle._PWR={};
|
|
||||||
if (Bangle._PWR.BTHRM===undefined) Bangle._PWR.BTHRM=[];
|
|
||||||
if (isOn && !Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM.push(app);
|
|
||||||
if (!isOn && Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM = Bangle._PWR.BTHRM.filter(a=>a!==app);
|
|
||||||
isOn = Bangle._PWR.BTHRM.length;
|
|
||||||
// so now we know if we're really on
|
|
||||||
if (isOn) {
|
|
||||||
switchFallback();
|
|
||||||
if (!Bangle.isBTHRMConnected()) initBt();
|
|
||||||
} else { // not on
|
|
||||||
log("Power off for " + app);
|
|
||||||
clearRetryTimeout(true);
|
|
||||||
if (gatt) {
|
|
||||||
if (gatt.connected){
|
|
||||||
log("Disconnect with gatt", gatt);
|
|
||||||
try{
|
|
||||||
gatt.disconnect().then(()=>{
|
|
||||||
log("Successful disconnect");
|
|
||||||
}).catch((e)=>{
|
|
||||||
log("Error during disconnect promise", e);
|
|
||||||
});
|
|
||||||
} catch (e){
|
|
||||||
log("Error during disconnect attempt", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (settings.replace){
|
|
||||||
Bangle.on("HRM", (e) => {
|
|
||||||
e.modified = true;
|
|
||||||
Bangle.emit("HRM_int", e);
|
|
||||||
});
|
|
||||||
|
|
||||||
Bangle.origOn = Bangle.on;
|
|
||||||
Bangle.on = function(name, callback) {
|
|
||||||
if (name == "HRM") {
|
|
||||||
Bangle.origOn("HRM_int", callback);
|
|
||||||
} else {
|
|
||||||
Bangle.origOn(name, callback);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Bangle.origRemoveListener = Bangle.removeListener;
|
|
||||||
Bangle.removeListener = function(name, callback) {
|
|
||||||
if (name == "HRM") {
|
|
||||||
Bangle.origRemoveListener("HRM_int", callback);
|
|
||||||
} else {
|
|
||||||
Bangle.origRemoveListener(name, callback);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Bangle.origSetHRMPower = Bangle.setHRMPower;
|
|
||||||
|
|
||||||
if (settings.startWithHrm){
|
|
||||||
|
|
||||||
Bangle.setHRMPower = function(isOn, app) {
|
|
||||||
log("setHRMPower for " + app + ": " + (isOn?"on":"off"));
|
|
||||||
if (settings.enabled){
|
|
||||||
Bangle.setBTHRMPower(isOn, app);
|
|
||||||
}
|
|
||||||
if ((settings.enabled && !settings.replace) || !settings.enabled){
|
|
||||||
Bangle.origSetHRMPower(isOn, app);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var fallbackActive = false;
|
|
||||||
var inSwitch = false;
|
|
||||||
|
|
||||||
var stopFallback = function(){
|
|
||||||
if (fallbackActive){
|
|
||||||
Bangle.origSetHRMPower(0, "bthrm_fallback");
|
|
||||||
fallbackActive = false;
|
|
||||||
log("Fallback to HRM disabled");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var startFallback = function(){
|
|
||||||
if (!fallbackActive && settings.allowFallback) {
|
|
||||||
fallbackActive = true;
|
|
||||||
Bangle.origSetHRMPower(1, "bthrm_fallback");
|
|
||||||
log("Fallback to HRM enabled");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var switchFallback = function() {
|
|
||||||
log("Check falling back to HRM");
|
|
||||||
if (!inSwitch){
|
|
||||||
inSwitch = true;
|
|
||||||
if (Bangle.isBTHRMActive()){
|
|
||||||
stopFallback();
|
|
||||||
} else {
|
|
||||||
startFallback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inSwitch = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (settings.replace){
|
|
||||||
log("Replace HRM event");
|
|
||||||
if (Bangle._PWR && Bangle._PWR.HRM){
|
|
||||||
for (var i = 0; i < Bangle._PWR.HRM.length; i++){
|
|
||||||
var app = Bangle._PWR.HRM[i];
|
|
||||||
log("Moving app " + app);
|
|
||||||
Bangle.origSetHRMPower(0, app);
|
|
||||||
Bangle.setBTHRMPower(1, app);
|
|
||||||
if (Bangle._PWR.HRM===undefined) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
E.on("kill", ()=>{
|
|
||||||
if (gatt && gatt.connected){
|
|
||||||
log("Got killed, trying to disconnect");
|
|
||||||
gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect on kill", e));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
|
||||||
|
|
@ -16,5 +16,6 @@
|
||||||
"gracePeriodNotification": 0,
|
"gracePeriodNotification": 0,
|
||||||
"gracePeriodConnect": 0,
|
"gracePeriodConnect": 0,
|
||||||
"gracePeriodService": 0,
|
"gracePeriodService": 0,
|
||||||
"gracePeriodRequest": 0
|
"gracePeriodRequest": 0,
|
||||||
|
"bonding": false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,634 @@
|
||||||
|
exports.enable = () => {
|
||||||
|
var settings = Object.assign(
|
||||||
|
require('Storage').readJSON("bthrm.default.json", true) || {},
|
||||||
|
require('Storage').readJSON("bthrm.json", true) || {}
|
||||||
|
);
|
||||||
|
|
||||||
|
var log = function(text, param){
|
||||||
|
if (global.showStatusInfo)
|
||||||
|
showStatusInfo(text);
|
||||||
|
if (settings.debuglog){
|
||||||
|
var logline = new Date().toISOString() + " - " + text;
|
||||||
|
if (param) logline += ": " + JSON.stringify(param);
|
||||||
|
print(logline);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
log("Settings: ", settings);
|
||||||
|
|
||||||
|
if (settings.enabled){
|
||||||
|
|
||||||
|
var clearCache = function() {
|
||||||
|
return require('Storage').erase("bthrm.cache.json");
|
||||||
|
};
|
||||||
|
|
||||||
|
var getCache = function() {
|
||||||
|
var cache = require('Storage').readJSON("bthrm.cache.json", true) || {};
|
||||||
|
if (settings.btid && settings.btid === cache.id) return cache;
|
||||||
|
clearCache();
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
var addNotificationHandler = function(characteristic) {
|
||||||
|
log("Setting notification handler"/*supportedCharacteristics[characteristic.uuid].handler*/);
|
||||||
|
characteristic.on('characteristicvaluechanged', (ev) => supportedCharacteristics[characteristic.uuid].handler(ev.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
var writeCache = function(cache) {
|
||||||
|
var oldCache = getCache();
|
||||||
|
if (oldCache !== cache) {
|
||||||
|
log("Writing cache");
|
||||||
|
require('Storage').writeJSON("bthrm.cache.json", cache);
|
||||||
|
} else {
|
||||||
|
log("No changes, don't write cache");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var characteristicsToCache = function(characteristics) {
|
||||||
|
log("Cache characteristics");
|
||||||
|
var cache = getCache();
|
||||||
|
if (!cache.characteristics) cache.characteristics = {};
|
||||||
|
for (var c of characteristics){
|
||||||
|
//"handle_value":16,"handle_decl":15
|
||||||
|
log("Saving handle " + c.handle_value + " for characteristic: ", c);
|
||||||
|
cache.characteristics[c.uuid] = {
|
||||||
|
"handle": c.handle_value,
|
||||||
|
"uuid": c.uuid,
|
||||||
|
"notify": c.properties.notify,
|
||||||
|
"read": c.properties.read
|
||||||
|
};
|
||||||
|
}
|
||||||
|
writeCache(cache);
|
||||||
|
};
|
||||||
|
|
||||||
|
var characteristicsFromCache = function(device) {
|
||||||
|
var service = { device : device }; // fake a BluetoothRemoteGATTService
|
||||||
|
log("Read cached characteristics");
|
||||||
|
var cache = getCache();
|
||||||
|
if (!cache.characteristics) return [];
|
||||||
|
var restored = [];
|
||||||
|
for (var c in cache.characteristics){
|
||||||
|
var cached = cache.characteristics[c];
|
||||||
|
var r = new BluetoothRemoteGATTCharacteristic();
|
||||||
|
log("Restoring characteristic ", cached);
|
||||||
|
r.handle_value = cached.handle;
|
||||||
|
r.uuid = cached.uuid;
|
||||||
|
r.properties = {};
|
||||||
|
r.properties.notify = cached.notify;
|
||||||
|
r.properties.read = cached.read;
|
||||||
|
r.service = service;
|
||||||
|
addNotificationHandler(r);
|
||||||
|
log("Restored characteristic: ", r);
|
||||||
|
restored.push(r);
|
||||||
|
}
|
||||||
|
return restored;
|
||||||
|
};
|
||||||
|
|
||||||
|
log("Start");
|
||||||
|
|
||||||
|
var lastReceivedData={
|
||||||
|
};
|
||||||
|
|
||||||
|
var supportedServices = [
|
||||||
|
"0x180d", // Heart Rate
|
||||||
|
"0x180f", // Battery
|
||||||
|
];
|
||||||
|
|
||||||
|
var bpmTimeout;
|
||||||
|
|
||||||
|
var supportedCharacteristics = {
|
||||||
|
"0x2a37": {
|
||||||
|
//Heart rate measurement
|
||||||
|
active: false,
|
||||||
|
handler: function (dv){
|
||||||
|
var flags = dv.getUint8(0);
|
||||||
|
|
||||||
|
var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit
|
||||||
|
supportedCharacteristics["0x2a37"].active = bpm > 0;
|
||||||
|
log("BTHRM BPM " + supportedCharacteristics["0x2a37"].active);
|
||||||
|
if (supportedCharacteristics["0x2a37"].active) stopFallback();
|
||||||
|
if (bpmTimeout) clearTimeout(bpmTimeout);
|
||||||
|
bpmTimeout = setTimeout(()=>{
|
||||||
|
bpmTimeout = undefined;
|
||||||
|
supportedCharacteristics["0x2a37"].active = false;
|
||||||
|
startFallback();
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
var sensorContact;
|
||||||
|
|
||||||
|
if (flags & 2){
|
||||||
|
sensorContact = !!(flags & 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
var idx = 2 + (flags&1);
|
||||||
|
|
||||||
|
var energyExpended;
|
||||||
|
if (flags & 8){
|
||||||
|
energyExpended = dv.getUint16(idx,1);
|
||||||
|
idx += 2;
|
||||||
|
}
|
||||||
|
var interval;
|
||||||
|
if (flags & 16) {
|
||||||
|
interval = [];
|
||||||
|
var maxIntervalBytes = (dv.byteLength - idx);
|
||||||
|
log("Found " + (maxIntervalBytes / 2) + " rr data fields");
|
||||||
|
for(var i = 0 ; i < maxIntervalBytes / 2; i++){
|
||||||
|
interval[i] = dv.getUint16(idx,1); // in milliseconds
|
||||||
|
idx += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var location;
|
||||||
|
if (lastReceivedData && lastReceivedData["0x180d"] && lastReceivedData["0x180d"]["0x2a38"]){
|
||||||
|
location = lastReceivedData["0x180d"]["0x2a38"];
|
||||||
|
}
|
||||||
|
|
||||||
|
var battery;
|
||||||
|
if (lastReceivedData && lastReceivedData["0x180f"] && lastReceivedData["0x180f"]["0x2a19"]){
|
||||||
|
battery = lastReceivedData["0x180f"]["0x2a19"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.replace){
|
||||||
|
var repEvent = {
|
||||||
|
bpm: bpm,
|
||||||
|
confidence: (sensorContact || sensorContact === undefined)? 100 : 0,
|
||||||
|
src: "bthrm"
|
||||||
|
};
|
||||||
|
|
||||||
|
log("Emitting aggregated HRM", repEvent);
|
||||||
|
Bangle.emit("HRM_R", repEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
var newEvent = {
|
||||||
|
bpm: bpm
|
||||||
|
};
|
||||||
|
|
||||||
|
if (location) newEvent.location = location;
|
||||||
|
if (interval) newEvent.rr = interval;
|
||||||
|
if (energyExpended) newEvent.energy = energyExpended;
|
||||||
|
if (battery) newEvent.battery = battery;
|
||||||
|
if (sensorContact) newEvent.contact = sensorContact;
|
||||||
|
|
||||||
|
log("Emitting BTHRM", newEvent);
|
||||||
|
Bangle.emit("BTHRM", newEvent);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x2a38": {
|
||||||
|
//Body sensor location
|
||||||
|
handler: function(dv){
|
||||||
|
if (!lastReceivedData["0x180d"]) lastReceivedData["0x180d"] = {};
|
||||||
|
lastReceivedData["0x180d"]["0x2a38"] = parseInt(dv.buffer, 10);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x2a19": {
|
||||||
|
//Battery
|
||||||
|
handler: function (dv){
|
||||||
|
if (!lastReceivedData["0x180f"]) lastReceivedData["0x180f"] = {};
|
||||||
|
lastReceivedData["0x180f"]["0x2a19"] = dv.getUint8(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var device;
|
||||||
|
var gatt;
|
||||||
|
var characteristics = [];
|
||||||
|
var blockInit = false;
|
||||||
|
var currentRetryTimeout;
|
||||||
|
var initialRetryTime = 40;
|
||||||
|
var maxRetryTime = 60000;
|
||||||
|
var retryTime = initialRetryTime;
|
||||||
|
|
||||||
|
var connectSettings = {
|
||||||
|
minInterval: 7.5,
|
||||||
|
maxInterval: 1500
|
||||||
|
};
|
||||||
|
|
||||||
|
var waitingPromise = function(timeout) {
|
||||||
|
return new Promise(function(resolve){
|
||||||
|
log("Start waiting for " + timeout);
|
||||||
|
setTimeout(()=>{
|
||||||
|
log("Done waiting for " + timeout);
|
||||||
|
resolve();
|
||||||
|
}, timeout);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (settings.enabled){
|
||||||
|
Bangle.isBTHRMActive = function (){
|
||||||
|
return supportedCharacteristics["0x2a37"].active;
|
||||||
|
};
|
||||||
|
|
||||||
|
Bangle.isBTHRMOn = function(){
|
||||||
|
return (Bangle._PWR && Bangle._PWR.BTHRM && Bangle._PWR.BTHRM.length > 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
Bangle.isBTHRMConnected = function(){
|
||||||
|
return gatt && gatt.connected;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.replace){
|
||||||
|
Bangle.origIsHRMOn = Bangle.isHRMOn;
|
||||||
|
|
||||||
|
Bangle.isHRMOn = function() {
|
||||||
|
if (settings.enabled && !settings.replace){
|
||||||
|
return Bangle.origIsHRMOn();
|
||||||
|
} else if (settings.enabled && settings.replace){
|
||||||
|
return Bangle.isBTHRMOn();
|
||||||
|
}
|
||||||
|
return Bangle.origIsHRMOn() || Bangle.isBTHRMOn();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var clearRetryTimeout = function(resetTime) {
|
||||||
|
if (currentRetryTimeout){
|
||||||
|
log("Clearing timeout " + currentRetryTimeout);
|
||||||
|
clearTimeout(currentRetryTimeout);
|
||||||
|
currentRetryTimeout = undefined;
|
||||||
|
}
|
||||||
|
if (resetTime) {
|
||||||
|
log("Resetting retry time");
|
||||||
|
retryTime = initialRetryTime;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var retry = function() {
|
||||||
|
log("Retry");
|
||||||
|
|
||||||
|
if (!currentRetryTimeout){
|
||||||
|
|
||||||
|
var clampedTime = retryTime < 100 ? 100 : retryTime;
|
||||||
|
|
||||||
|
log("Set timeout for retry as " + clampedTime);
|
||||||
|
clearRetryTimeout();
|
||||||
|
currentRetryTimeout = setTimeout(() => {
|
||||||
|
log("Retrying");
|
||||||
|
currentRetryTimeout = undefined;
|
||||||
|
initBt();
|
||||||
|
}, clampedTime);
|
||||||
|
|
||||||
|
retryTime = Math.pow(clampedTime, 1.1);
|
||||||
|
if (retryTime > maxRetryTime){
|
||||||
|
retryTime = maxRetryTime;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log("Already in retry...");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var buzzing = false;
|
||||||
|
var onDisconnect = function(reason) {
|
||||||
|
log("Disconnect: " + reason);
|
||||||
|
log("GATT", gatt);
|
||||||
|
log("Characteristics", characteristics);
|
||||||
|
|
||||||
|
var retryTimeResetNeeded = true;
|
||||||
|
retryTimeResetNeeded &= reason != "Connection Timeout";
|
||||||
|
retryTimeResetNeeded &= reason != "No device found matching filters";
|
||||||
|
clearRetryTimeout(retryTimeResetNeeded);
|
||||||
|
supportedCharacteristics["0x2a37"].active = false;
|
||||||
|
startFallback();
|
||||||
|
blockInit = false;
|
||||||
|
if (settings.warnDisconnect && !buzzing){
|
||||||
|
buzzing = true;
|
||||||
|
Bangle.buzz(500,0.3).then(()=>waitingPromise(4500)).then(()=>{buzzing = false;});
|
||||||
|
}
|
||||||
|
if (Bangle.isBTHRMOn()){
|
||||||
|
retry();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var createCharacteristicPromise = function(newCharacteristic) {
|
||||||
|
log("Create characteristic promise", newCharacteristic);
|
||||||
|
var result = Promise.resolve();
|
||||||
|
// For values that can be read, go ahead and read them, even if we might be notified in the future
|
||||||
|
// Allows for getting initial state of infrequently updating characteristics, like battery
|
||||||
|
if (newCharacteristic.readValue){
|
||||||
|
result = result.then(()=>{
|
||||||
|
log("Reading data", newCharacteristic);
|
||||||
|
return newCharacteristic.readValue().then((data)=>{
|
||||||
|
if (supportedCharacteristics[newCharacteristic.uuid] && supportedCharacteristics[newCharacteristic.uuid].handler) {
|
||||||
|
supportedCharacteristics[newCharacteristic.uuid].handler(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (newCharacteristic.properties.notify){
|
||||||
|
result = result.then(()=>{
|
||||||
|
log("Starting notifications", newCharacteristic);
|
||||||
|
var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic));
|
||||||
|
|
||||||
|
log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications");
|
||||||
|
startPromise = startPromise.then(()=>{
|
||||||
|
log("Wait after connect");
|
||||||
|
return waitingPromise(settings.gracePeriodNotification);
|
||||||
|
});
|
||||||
|
|
||||||
|
return startPromise;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result.then(()=>log("Handled characteristic", newCharacteristic));
|
||||||
|
};
|
||||||
|
|
||||||
|
var attachCharacteristicPromise = function(promise, characteristic) {
|
||||||
|
return promise.then(()=>{
|
||||||
|
log("Handling characteristic:", characteristic);
|
||||||
|
return createCharacteristicPromise(characteristic);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var createCharacteristicsPromise = function(newCharacteristics) {
|
||||||
|
log("Create characteristics promis ", newCharacteristics);
|
||||||
|
var result = Promise.resolve();
|
||||||
|
for (var c of newCharacteristics){
|
||||||
|
if (!supportedCharacteristics[c.uuid]) continue;
|
||||||
|
log("Supporting characteristic", c);
|
||||||
|
characteristics.push(c);
|
||||||
|
if (c.properties.notify){
|
||||||
|
addNotificationHandler(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = attachCharacteristicPromise(result, c);
|
||||||
|
}
|
||||||
|
return result.then(()=>log("Handled characteristics"));
|
||||||
|
};
|
||||||
|
|
||||||
|
var createServicePromise = function(service) {
|
||||||
|
log("Create service promise", service);
|
||||||
|
var result = Promise.resolve();
|
||||||
|
result = result.then(()=>{
|
||||||
|
log("Handling service" + service.uuid);
|
||||||
|
return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c));
|
||||||
|
});
|
||||||
|
return result.then(()=>log("Handled service" + service.uuid));
|
||||||
|
};
|
||||||
|
|
||||||
|
var attachServicePromise = function(promise, service) {
|
||||||
|
return promise.then(()=>createServicePromise(service));
|
||||||
|
};
|
||||||
|
|
||||||
|
var initBt = function () {
|
||||||
|
log("initBt with blockInit: " + blockInit);
|
||||||
|
if (blockInit){
|
||||||
|
retry();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockInit = true;
|
||||||
|
|
||||||
|
var promise;
|
||||||
|
var filters;
|
||||||
|
|
||||||
|
if (!device){
|
||||||
|
if (settings.btid){
|
||||||
|
log("Configured device id", settings.btid);
|
||||||
|
filters = [{ id: settings.btid }];
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log("Requesting device with filters", filters);
|
||||||
|
promise = NRF.requestDevice({ filters: filters, active: true });
|
||||||
|
|
||||||
|
if (settings.gracePeriodRequest){
|
||||||
|
log("Add " + settings.gracePeriodRequest + "ms grace period after request");
|
||||||
|
}
|
||||||
|
|
||||||
|
promise = promise.then((d)=>{
|
||||||
|
log("Got device", d);
|
||||||
|
d.on('gattserverdisconnected', onDisconnect);
|
||||||
|
device = d;
|
||||||
|
});
|
||||||
|
|
||||||
|
promise = promise.then(()=>{
|
||||||
|
log("Wait after request");
|
||||||
|
return waitingPromise(settings.gracePeriodRequest);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
promise = Promise.resolve();
|
||||||
|
log("Reuse device", device);
|
||||||
|
}
|
||||||
|
|
||||||
|
promise = promise.then(()=>{
|
||||||
|
if (gatt){
|
||||||
|
log("Reuse GATT", gatt);
|
||||||
|
} else {
|
||||||
|
log("GATT is new", gatt);
|
||||||
|
characteristics = [];
|
||||||
|
var cachedId = getCache().id;
|
||||||
|
if (device.id !== cachedId){
|
||||||
|
log("Device ID changed from " + cachedId + " to " + device.id + ", clearing cache");
|
||||||
|
clearCache();
|
||||||
|
}
|
||||||
|
var newCache = getCache();
|
||||||
|
newCache.id = device.id;
|
||||||
|
writeCache(newCache);
|
||||||
|
gatt = device.gatt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(gatt);
|
||||||
|
});
|
||||||
|
|
||||||
|
promise = promise.then((gatt)=>{
|
||||||
|
if (!gatt.connected){
|
||||||
|
log("Connecting...");
|
||||||
|
var connectPromise = gatt.connect(connectSettings).then(function() {
|
||||||
|
log("Connected.");
|
||||||
|
});
|
||||||
|
log("Add " + settings.gracePeriodConnect + "ms grace period after connecting");
|
||||||
|
connectPromise = connectPromise.then(()=>{
|
||||||
|
log("Wait after connect");
|
||||||
|
return waitingPromise(settings.gracePeriodConnect);
|
||||||
|
});
|
||||||
|
return connectPromise;
|
||||||
|
} else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (settings.bonding){
|
||||||
|
promise = promise.then(() => {
|
||||||
|
log(JSON.stringify(gatt.getSecurityStatus()));
|
||||||
|
if (gatt.getSecurityStatus()['bonded']) {
|
||||||
|
log("Already bonded");
|
||||||
|
return Promise.resolve();
|
||||||
|
} else {
|
||||||
|
log("Start bonding");
|
||||||
|
return gatt.startBonding()
|
||||||
|
.then(() => console.log(gatt.getSecurityStatus()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
promise = promise.then(()=>{
|
||||||
|
if (!characteristics || characteristics.length === 0){
|
||||||
|
characteristics = characteristicsFromCache(device);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
promise = promise.then(()=>{
|
||||||
|
var characteristicsPromise = Promise.resolve();
|
||||||
|
if (characteristics.length === 0){
|
||||||
|
characteristicsPromise = characteristicsPromise.then(()=>{
|
||||||
|
log("Getting services");
|
||||||
|
return gatt.getPrimaryServices();
|
||||||
|
});
|
||||||
|
|
||||||
|
characteristicsPromise = characteristicsPromise.then((services)=>{
|
||||||
|
log("Got services", services);
|
||||||
|
var result = Promise.resolve();
|
||||||
|
for (var service of services){
|
||||||
|
if (!(supportedServices.includes(service.uuid))) continue;
|
||||||
|
log("Supporting service", service.uuid);
|
||||||
|
result = attachServicePromise(result, service);
|
||||||
|
}
|
||||||
|
log("Add " + settings.gracePeriodService + "ms grace period after services");
|
||||||
|
result = result.then(()=>{
|
||||||
|
log("Wait after services");
|
||||||
|
return waitingPromise(settings.gracePeriodService);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
for (var characteristic of characteristics){
|
||||||
|
characteristicsPromise = attachCharacteristicPromise(characteristicsPromise, characteristic, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return characteristicsPromise;
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise.then(()=>{
|
||||||
|
log("Connection established, waiting for notifications");
|
||||||
|
characteristicsToCache(characteristics);
|
||||||
|
clearRetryTimeout(true);
|
||||||
|
}).catch((e) => {
|
||||||
|
characteristics = [];
|
||||||
|
log("Error:", e);
|
||||||
|
onDisconnect(e);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Bangle.setBTHRMPower = function(isOn, app) {
|
||||||
|
// Do app power handling
|
||||||
|
if (!app) app="?";
|
||||||
|
if (Bangle._PWR===undefined) Bangle._PWR={};
|
||||||
|
if (Bangle._PWR.BTHRM===undefined) Bangle._PWR.BTHRM=[];
|
||||||
|
if (isOn && !Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM.push(app);
|
||||||
|
if (!isOn && Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM = Bangle._PWR.BTHRM.filter(a=>a!==app);
|
||||||
|
isOn = Bangle._PWR.BTHRM.length;
|
||||||
|
// so now we know if we're really on
|
||||||
|
if (isOn) {
|
||||||
|
switchFallback();
|
||||||
|
if (!Bangle.isBTHRMConnected()) initBt();
|
||||||
|
} else { // not on
|
||||||
|
log("Power off for " + app);
|
||||||
|
clearRetryTimeout(true);
|
||||||
|
if (gatt) {
|
||||||
|
if (gatt.connected){
|
||||||
|
log("Disconnect with gatt", gatt);
|
||||||
|
try{
|
||||||
|
gatt.disconnect().then(()=>{
|
||||||
|
log("Successful disconnect");
|
||||||
|
}).catch((e)=>{
|
||||||
|
log("Error during disconnect promise", e);
|
||||||
|
});
|
||||||
|
} catch (e){
|
||||||
|
log("Error during disconnect attempt", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (settings.replace){
|
||||||
|
// register a listener for original HRM events and emit as HRM_int
|
||||||
|
Bangle.on("HRM", (e) => {
|
||||||
|
e.modified = true;
|
||||||
|
Bangle.emit("HRM_int", e);
|
||||||
|
if (fallbackActive){
|
||||||
|
// if fallback to internal HRM is active, emit as HRM_R to which everyone listens
|
||||||
|
Bangle.emit("HRM_R", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// force all apps wanting to listen to HRM to actually get events for HRM_R
|
||||||
|
Bangle.on = ( o => (name, cb) => {
|
||||||
|
o = o.bind(Bangle);
|
||||||
|
if (name == "HRM") o("HRM_R", cb);
|
||||||
|
else o(name, cb);
|
||||||
|
})(Bangle.on);
|
||||||
|
|
||||||
|
Bangle.removeListener = ( o => (name, cb) => {
|
||||||
|
o = o.bind(Bangle);
|
||||||
|
if (name == "HRM") o("HRM_R", cb);
|
||||||
|
else o(name, cb);
|
||||||
|
})(Bangle.removeListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bangle.origSetHRMPower = Bangle.setHRMPower;
|
||||||
|
|
||||||
|
if (settings.startWithHrm){
|
||||||
|
Bangle.setHRMPower = function(isOn, app) {
|
||||||
|
log("setHRMPower for " + app + ": " + (isOn?"on":"off"));
|
||||||
|
if (settings.enabled){
|
||||||
|
Bangle.setBTHRMPower(isOn, app);
|
||||||
|
}
|
||||||
|
if ((settings.enabled && !settings.replace) || !settings.enabled){
|
||||||
|
Bangle.origSetHRMPower(isOn, app);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var fallbackActive = false;
|
||||||
|
var inSwitch = false;
|
||||||
|
|
||||||
|
var stopFallback = function(){
|
||||||
|
if (fallbackActive){
|
||||||
|
Bangle.origSetHRMPower(0, "bthrm_fallback");
|
||||||
|
fallbackActive = false;
|
||||||
|
log("Fallback to HRM disabled");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var startFallback = function(){
|
||||||
|
if (!fallbackActive && settings.allowFallback) {
|
||||||
|
fallbackActive = true;
|
||||||
|
Bangle.origSetHRMPower(1, "bthrm_fallback");
|
||||||
|
log("Fallback to HRM enabled");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var switchFallback = function() {
|
||||||
|
log("Check falling back to HRM");
|
||||||
|
if (!inSwitch){
|
||||||
|
inSwitch = true;
|
||||||
|
if (Bangle.isBTHRMActive()){
|
||||||
|
stopFallback();
|
||||||
|
} else {
|
||||||
|
startFallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inSwitch = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (settings.replace){
|
||||||
|
log("Replace HRM event");
|
||||||
|
if (Bangle._PWR && Bangle._PWR.HRM){
|
||||||
|
for (var i = 0; i < Bangle._PWR.HRM.length; i++){
|
||||||
|
var app = Bangle._PWR.HRM[i];
|
||||||
|
log("Moving app " + app);
|
||||||
|
Bangle.origSetHRMPower(0, app);
|
||||||
|
Bangle.setBTHRMPower(1, app);
|
||||||
|
if (Bangle._PWR.HRM===undefined) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
E.on("kill", ()=>{
|
||||||
|
if (gatt && gatt.connected){
|
||||||
|
log("Got killed, trying to disconnect");
|
||||||
|
gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect on kill", e));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "bthrm",
|
"id": "bthrm",
|
||||||
"name": "Bluetooth Heart Rate Monitor",
|
"name": "Bluetooth Heart Rate Monitor",
|
||||||
"shortName": "BT HRM",
|
"shortName": "BT HRM",
|
||||||
"version": "0.12",
|
"version": "0.14",
|
||||||
"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": "app",
|
"type": "app",
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
{"name":"bthrm.0.boot.js","url":"boot.js"},
|
{"name":"bthrm.0.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"},
|
{"name":"bthrm.settings.js","url":"settings.js"},
|
||||||
|
{"name":"bthrm","url":"lib.js"},
|
||||||
{"name":"bthrm.default.json","url":"default.json"}
|
{"name":"bthrm.default.json","url":"default.json"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,12 @@
|
||||||
writeSettings("debuglog",v);
|
writeSettings("debuglog",v);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'Use bonding': {
|
||||||
|
value: !!settings.bonding,
|
||||||
|
onchange: v => {
|
||||||
|
writeSettings("bonding",v);
|
||||||
|
}
|
||||||
|
},
|
||||||
'Grace periods': function() { E.showMenu(submenu_grace); }
|
'Grace periods': function() { E.showMenu(submenu_grace); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,5 @@
|
||||||
0.03: Support for different screen sizes and touchscreen
|
0.03: Support for different screen sizes and touchscreen
|
||||||
0.04: Display current operation on LHS
|
0.04: Display current operation on LHS
|
||||||
0.05: Grid positioning and swipe controls to switch between numbers, operators and special (for Bangle.js 2)
|
0.05: Grid positioning and swipe controls to switch between numbers, operators and special (for Bangle.js 2)
|
||||||
|
0.06: Bangle.js 2: Exit with a short press of the physical button
|
||||||
|
0.07: Bangle.js 2: Exit by pressing upper left corner of the screen
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,20 @@ Basic calculator reminiscent of MacOs's one. Handy for small calculus.
|
||||||
|
|
||||||
## Controls
|
## Controls
|
||||||
|
|
||||||
|
Bangle.js 1
|
||||||
- UP: BTN1
|
- UP: BTN1
|
||||||
- DOWN: BTN3
|
- DOWN: BTN3
|
||||||
- LEFT: BTN4
|
- LEFT: BTN4
|
||||||
- RIGHT: BTN5
|
- RIGHT: BTN5
|
||||||
- SELECT: BTN2
|
- SELECT: BTN2
|
||||||
|
|
||||||
|
Bangle.js 2
|
||||||
|
- Swipes to change visible buttons
|
||||||
|
- Click physical button to exit
|
||||||
|
- Press upper left corner of screen to exit (where the red back button would be)
|
||||||
## Creator
|
## Creator
|
||||||
|
|
||||||
<https://twitter.com/fredericrous>
|
<https://twitter.com/fredericrous>
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
[thyttan](https://github.com/thyttan)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
*
|
*
|
||||||
* Original Author: Frederic Rousseau https://github.com/fredericrous
|
* Original Author: Frederic Rousseau https://github.com/fredericrous
|
||||||
* Created: April 2020
|
* Created: April 2020
|
||||||
|
*
|
||||||
|
* Contributors: thyttan https://github.com/thyttan
|
||||||
*/
|
*/
|
||||||
|
|
||||||
g.clear();
|
g.clear();
|
||||||
|
|
@ -402,43 +404,42 @@ if (process.env.HWVERSION==1) {
|
||||||
swipeEnabled = false;
|
swipeEnabled = false;
|
||||||
drawGlobal();
|
drawGlobal();
|
||||||
} else { // touchscreen?
|
} else { // touchscreen?
|
||||||
selected = "NONE";
|
selected = "NONE";
|
||||||
swipeEnabled = true;
|
swipeEnabled = true;
|
||||||
prepareScreen(numbers, numbersGrid, COLORS.DEFAULT);
|
prepareScreen(numbers, numbersGrid, COLORS.DEFAULT);
|
||||||
prepareScreen(operators, operatorsGrid, COLORS.OPERATOR);
|
prepareScreen(operators, operatorsGrid, COLORS.OPERATOR);
|
||||||
prepareScreen(specials, specialsGrid, COLORS.SPECIAL);
|
prepareScreen(specials, specialsGrid, COLORS.SPECIAL);
|
||||||
drawNumbers();
|
drawNumbers();
|
||||||
Bangle.on('touch',(n,e)=>{
|
|
||||||
for (var key in screen) {
|
Bangle.setUI({
|
||||||
if (typeof screen[key] == "undefined") break;
|
mode : 'custom',
|
||||||
var r = screen[key].xy;
|
back : load, // Clicking physical button or pressing upper left corner turns off (where red back button would be)
|
||||||
if (e.x>=r[0] && e.y>=r[1] &&
|
touch : (n,e)=>{
|
||||||
e.x<r[2] && e.y<r[3]) {
|
for (var key in screen) {
|
||||||
//print("Press "+key);
|
if (typeof screen[key] == "undefined") break;
|
||||||
buttonPress(""+key);
|
var r = screen[key].xy;
|
||||||
|
if (e.x>=r[0] && e.y>=r[1] && e.x<r[2] && e.y<r[3]) {
|
||||||
|
//print("Press "+key);
|
||||||
|
buttonPress(""+key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
swipe : (LR, UD) => {
|
||||||
var lastX = 0, lastY = 0;
|
if (LR == 1) { // right
|
||||||
Bangle.on('drag', (e) => {
|
|
||||||
if (!e.b) {
|
|
||||||
if (lastX > 50) { // right
|
|
||||||
drawSpecials();
|
drawSpecials();
|
||||||
} else if (lastX < -50) { // left
|
}
|
||||||
|
if (LR == -1) { // left
|
||||||
drawOperators();
|
drawOperators();
|
||||||
} else if (lastY > 50) { // down
|
}
|
||||||
drawNumbers();
|
if (UD == 1) { // down
|
||||||
} else if (lastY < -50) { // up
|
drawNumbers();
|
||||||
|
}
|
||||||
|
if (UD == -1) { // up
|
||||||
drawNumbers();
|
drawNumbers();
|
||||||
}
|
}
|
||||||
lastX = 0;
|
|
||||||
lastY = 0;
|
|
||||||
} else {
|
|
||||||
lastX = lastX + e.dx;
|
|
||||||
lastY = lastY + e.dy;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
displayOutput(0);
|
displayOutput(0);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "calculator",
|
"id": "calculator",
|
||||||
"name": "Calculator",
|
"name": "Calculator",
|
||||||
"shortName": "Calculator",
|
"shortName": "Calculator",
|
||||||
"version": "0.05",
|
"version": "0.07",
|
||||||
"description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.",
|
"description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.",
|
||||||
"icon": "calculator.png",
|
"icon": "calculator.png",
|
||||||
"screenshots": [{"url":"screenshot_calculator.png"}],
|
"screenshots": [{"url":"screenshot_calculator.png"}],
|
||||||
|
|
|
||||||
|
|
@ -26,3 +26,7 @@
|
||||||
0.12: Allow configuration of update interval
|
0.12: Allow configuration of update interval
|
||||||
0.13: Load step goal from Bangle health app as fallback
|
0.13: Load step goal from Bangle health app as fallback
|
||||||
Memory optimizations
|
Memory optimizations
|
||||||
|
0.14: Support to show big weather info
|
||||||
|
0.15: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16
|
||||||
|
0.16: Fix const error
|
||||||
|
Use widget_utils if available
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,11 @@ It can show the following information (this can be configured):
|
||||||
* Steps distance
|
* Steps distance
|
||||||
* Heart rate (automatically updates when screen is on and unlocked)
|
* Heart rate (automatically updates when screen is on and unlocked)
|
||||||
* Battery (including charging status and battery low warning)
|
* Battery (including charging status and battery low warning)
|
||||||
* Weather (requires [weather app](https://banglejs.com/apps/#weather))
|
* Weather (requires [OWM weather provider](https://banglejs.com/apps/?id=owmweather))
|
||||||
* Humidity or wind speed as circle progress
|
* Humidity or wind speed as circle progress
|
||||||
* Temperature inside circle
|
* Temperature inside circle
|
||||||
* Condition as icon below circle
|
* Condition as icon below circle
|
||||||
|
* Big weather icon next to clock
|
||||||
* Time and progress until next sunrise or sunset (requires [my location app](https://banglejs.com/apps/#mylocation))
|
* Time and progress until next sunrise or sunset (requires [my location app](https://banglejs.com/apps/#mylocation))
|
||||||
* Temperature, air pressure or altitude from internal pressure sensor
|
* Temperature, air pressure or altitude from internal pressure sensor
|
||||||
|
|
||||||
|
|
@ -27,6 +28,8 @@ The color of each circle can be configured. The following colors are available:
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
## Ideas
|
## Ideas
|
||||||
* Show compass heading
|
* Show compass heading
|
||||||
|
|
@ -35,4 +38,5 @@ The color of each circle can be configured. The following colors are available:
|
||||||
Marco ([myxor](https://github.com/myxor))
|
Marco ([myxor](https://github.com/myxor))
|
||||||
|
|
||||||
## Icons
|
## Icons
|
||||||
Icons taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0
|
Most of the icons are taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0 except the big weather icons which are from
|
||||||
|
[icons8](https://icons8.com/icon/set/weather/small--static--black)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const locale = require("locale");
|
let locale = require("locale");
|
||||||
const storage = require("Storage");
|
let storage = require("Storage");
|
||||||
Graphics.prototype.setFontRobotoRegular50NumericOnly = function(scale) {
|
Graphics.prototype.setFontRobotoRegular50NumericOnly = function(scale) {
|
||||||
// Actual height 39 (40 - 2)
|
// Actual height 39 (40 - 2)
|
||||||
this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAB8AAAAAAAfAAAAAAAPwAAAAAAB8AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAA4AAAAAAB+AAAAAAD/gAAAAAD/4AAAAAH/4AAAAAP/wAAAAAP/gAAAAAf/gAAAAAf/AAAAAA/+AAAAAB/+AAAAAB/8AAAAAD/4AAAAAH/4AAAAAD/wAAAAAA/wAAAAAAPgAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///wAAAB////gAAA////8AAA/////gAAP////8AAH8AAA/gAB8AAAD4AA+AAAAfAAPAAAADwADwAAAA8AA8AAAAPAAPAAAADwADwAAAA8AA8AAAAPAAPgAAAHwAB8AAAD4AAfwAAD+AAD/////AAA/////wAAH////4AAAf///4AAAB///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAPgAAAAAADwAAAAAAB8AAAAAAAfAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAPAAAAAAAH/////wAB/////8AA//////AAP/////wAD/////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAfgAADwAAP4AAB8AAH+AAA/AAD/gAAfwAB/AAAf8AAfAAAP/AAPgAAH7wAD4AAD88AA8AAB+PAAPAAA/DwADwAAfg8AA8AAPwPAAPAAH4DwADwAH8A8AA+AD+APAAPwB/ADwAB/D/gA8AAf//gAPAAD//wADwAAf/wAA8AAD/4AAPAAAHwAADwAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAADgAAAHwAA+AAAD8AAP4AAB/AAD/AAA/wAA/wAAf4AAD+AAHwAAAPgAD4APAB8AA+ADwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA8AH4APAAPgD+AHwAB8B/wD4AAf7/+B+AAD//v//AAA//x//wAAD/4P/4AAAf8B/4AAAAYAH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAHwAAAAAAH8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAB/vAAAAAB/jwAAAAA/g8AAAAA/wPAAAAAfwDwAAAAf4A8AAAAf4APAAAAP8ADwAAAP8AA8AAAH8AAPAAAD/////8AA//////AAP/////wAD/////8AA//////AAAAAAPAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAB/APwAAH//wD+AAD//8A/wAA///AH+AAP//wAPgAD/B4AB8AA8A+AAfAAPAPAADwADwDwAA8AA8A8AAPAAPAPAADwADwD4AA8AA8A+AAPAAPAPwAHwADwD8AD4AA8AfwD+AAPAH///AADwA///wAA8AH//4AAPAAf/4AAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAD//+AAAAD///4AAAD////AAAB////4AAA/78D/AAAfw8AH4AAPweAA+AAD4PgAHwAB8DwAA8AAfA8AAPAAHgPAADwAD4DwAA8AA+A8AAPAAPAPgAHwADwD4AB8AA8AfgA+AAPAH+B/gAAAA///wAAAAH//4AAAAA//8AAAAAH/8AAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAA8AAAABAAPAAAABwADwAAAB8AA8AAAB/AAPAAAB/wADwAAD/8AA8AAD/8AAPAAD/4AADwAD/4AAA8AD/4AAAPAH/wAAADwH/wAAAA8H/wAAAAPH/wAAAAD3/gAAAAA//gAAAAAP/gAAAAAD/gAAAAAA/AAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwA/4AAAH/Af/AAAH/8P/4AAD//n//AAA//7//4AAfx/+A+AAHwD+AHwAD4AfgB8AA8AHwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA+AH4AfAAHwD+AHwAB/D/4D4AAP/+/n+AAD//n//AAAf/w//gAAB/wH/wAAAHwA/4AAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAD/8AAAAAD//wAAAAB//+AAAAA///wAAAAf4H+APAAH4AfgDwAD8AB8A8AA+AAfAPAAPAADwDwADwAA8B8AA8AAPAfAAPAADwHgADwAA8D4AA+AAeB+AAHwAHg/AAB+ADwfgAAP8D4/4AAD////8AAAf///8AAAB///+AAAAP//+AAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAOAAAB8AAHwAAAfgAD8AAAH4AA/AAAB8AAHwAAAOAAA4AAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("DRUcHBwcHBwcHBwcDA=="), 50+(scale<<8)+(1<<16));
|
this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAB8AAAAAAAfAAAAAAAPwAAAAAAB8AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAA4AAAAAAB+AAAAAAD/gAAAAAD/4AAAAAH/4AAAAAP/wAAAAAP/gAAAAAf/gAAAAAf/AAAAAA/+AAAAAB/+AAAAAB/8AAAAAD/4AAAAAH/4AAAAAD/wAAAAAA/wAAAAAAPgAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///wAAAB////gAAA////8AAA/////gAAP////8AAH8AAA/gAB8AAAD4AA+AAAAfAAPAAAADwADwAAAA8AA8AAAAPAAPAAAADwADwAAAA8AA8AAAAPAAPgAAAHwAB8AAAD4AAfwAAD+AAD/////AAA/////wAAH////4AAAf///4AAAB///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAPgAAAAAADwAAAAAAB8AAAAAAAfAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAPAAAAAAAH/////wAB/////8AA//////AAP/////wAD/////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAfgAADwAAP4AAB8AAH+AAA/AAD/gAAfwAB/AAAf8AAfAAAP/AAPgAAH7wAD4AAD88AA8AAB+PAAPAAA/DwADwAAfg8AA8AAPwPAAPAAH4DwADwAH8A8AA+AD+APAAPwB/ADwAB/D/gA8AAf//gAPAAD//wADwAAf/wAA8AAD/4AAPAAAHwAADwAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAADgAAAHwAA+AAAD8AAP4AAB/AAD/AAA/wAA/wAAf4AAD+AAHwAAAPgAD4APAB8AA+ADwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA8AH4APAAPgD+AHwAB8B/wD4AAf7/+B+AAD//v//AAA//x//wAAD/4P/4AAAf8B/4AAAAYAH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAHwAAAAAAH8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAB/vAAAAAB/jwAAAAA/g8AAAAA/wPAAAAAfwDwAAAAf4A8AAAAf4APAAAAP8ADwAAAP8AA8AAAH8AAPAAAD/////8AA//////AAP/////wAD/////8AA//////AAAAAAPAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAB/APwAAH//wD+AAD//8A/wAA///AH+AAP//wAPgAD/B4AB8AA8A+AAfAAPAPAADwADwDwAA8AA8A8AAPAAPAPAADwADwD4AA8AA8A+AAPAAPAPwAHwADwD8AD4AA8AfwD+AAPAH///AADwA///wAA8AH//4AAPAAf/4AAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAD//+AAAAD///4AAAD////AAAB////4AAA/78D/AAAfw8AH4AAPweAA+AAD4PgAHwAB8DwAA8AAfA8AAPAAHgPAADwAD4DwAA8AA+A8AAPAAPAPgAHwADwD4AB8AA8AfgA+AAPAH+B/gAAAA///wAAAAH//4AAAAA//8AAAAAH/8AAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAA8AAAABAAPAAAABwADwAAAB8AA8AAAB/AAPAAAB/wADwAAD/8AA8AAD/8AAPAAD/4AADwAD/4AAA8AD/4AAAPAH/wAAADwH/wAAAA8H/wAAAAPH/wAAAAD3/gAAAAA//gAAAAAP/gAAAAAD/gAAAAAA/AAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwA/4AAAH/Af/AAAH/8P/4AAD//n//AAA//7//4AAfx/+A+AAHwD+AHwAD4AfgB8AA8AHwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA+AH4AfAAHwD+AHwAB/D/4D4AAP/+/n+AAD//n//AAAf/w//gAAB/wH/wAAAHwA/4AAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAD/8AAAAAD//wAAAAB//+AAAAA///wAAAAf4H+APAAH4AfgDwAD8AB8A8AA+AAfAPAAPAADwDwADwAA8B8AA8AAPAfAAPAADwHgADwAA8D4AA+AAeB+AAHwAHg/AAB+ADwfgAAP8D4/4AAD////8AAAf///8AAAB///+AAAAP//+AAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAOAAAB8AAHwAAAfgAD8AAAH4AA/AAAB8AAHwAAAOAAA4AAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("DRUcHBwcHBwcHBwcDA=="), 50+(scale<<8)+(1<<16));
|
||||||
|
|
@ -12,7 +12,7 @@ Graphics.prototype.setFontRobotoRegular21 = function(scale) {
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SETTINGS_FILE = "circlesclock.json";
|
let SETTINGS_FILE = "circlesclock.json";
|
||||||
let settings = Object.assign(
|
let settings = Object.assign(
|
||||||
storage.readJSON("circlesclock.default.json", true) || {},
|
storage.readJSON("circlesclock.default.json", true) || {},
|
||||||
storage.readJSON(SETTINGS_FILE, true) || {}
|
storage.readJSON(SETTINGS_FILE, true) || {}
|
||||||
|
|
@ -29,6 +29,9 @@ if (settings.stepGoal == undefined) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let timerHrm;
|
||||||
|
let drawTimeout;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Read location from myLocation app
|
* Read location from myLocation app
|
||||||
*/
|
*/
|
||||||
|
|
@ -37,29 +40,30 @@ function getLocation() {
|
||||||
}
|
}
|
||||||
let location = getLocation();
|
let location = getLocation();
|
||||||
|
|
||||||
const showWidgets = settings.showWidgets || false;
|
let showWidgets = settings.showWidgets || false;
|
||||||
const circleCount = settings.circleCount || 3;
|
let circleCount = settings.circleCount || 3;
|
||||||
|
let showBigWeather = settings.showBigWeather || false;
|
||||||
|
|
||||||
let hrtValue;
|
let hrtValue;
|
||||||
let now = Math.round(new Date().getTime() / 1000);
|
let now = Math.round(new Date().getTime() / 1000);
|
||||||
|
|
||||||
|
|
||||||
// layout values:
|
// layout values:
|
||||||
const colorFg = g.theme.dark ? '#fff' : '#000';
|
let colorFg = g.theme.dark ? '#fff' : '#000';
|
||||||
const colorBg = g.theme.dark ? '#000' : '#fff';
|
let colorBg = g.theme.dark ? '#000' : '#fff';
|
||||||
const colorGrey = '#808080';
|
let colorGrey = '#808080';
|
||||||
const colorRed = '#ff0000';
|
let colorRed = '#ff0000';
|
||||||
const colorGreen = '#008000';
|
let colorGreen = '#008000';
|
||||||
const colorBlue = '#0000ff';
|
let colorBlue = '#0000ff';
|
||||||
const colorYellow = '#ffff00';
|
let colorYellow = '#ffff00';
|
||||||
const widgetOffset = showWidgets ? 24 : 0;
|
let widgetOffset = showWidgets ? 24 : 0;
|
||||||
const dowOffset = circleCount == 3 ? 20 : 22; // dow offset relative to date
|
let dowOffset = circleCount == 3 ? 20 : 22; // dow offset relative to date
|
||||||
const h = g.getHeight() - widgetOffset;
|
let h = g.getHeight() - widgetOffset;
|
||||||
const w = g.getWidth();
|
let w = g.getWidth();
|
||||||
const hOffset = (circleCount == 3 ? 34 : 30) - widgetOffset;
|
let hOffset = (circleCount == 3 ? 34 : 30) - widgetOffset;
|
||||||
const h1 = Math.round(1 * h / 5 - hOffset);
|
let h1 = Math.round(1 * h / 5 - hOffset);
|
||||||
const h2 = Math.round(3 * h / 5 - hOffset);
|
let h2 = Math.round(3 * h / 5 - hOffset);
|
||||||
const h3 = Math.round(8 * h / 8 - hOffset - 3); // circle y position
|
let h3 = Math.round(8 * h / 8 - hOffset - 3); // circle y position
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* circle x positions
|
* circle x positions
|
||||||
|
|
@ -73,21 +77,22 @@ const h3 = Math.round(8 * h / 8 - hOffset - 3); // circle y position
|
||||||
* | (1) (2) (3) (4) |
|
* | (1) (2) (3) (4) |
|
||||||
* => circles start at 1,3,5,7 / 8
|
* => circles start at 1,3,5,7 / 8
|
||||||
*/
|
*/
|
||||||
const parts = circleCount * 2;
|
let parts = circleCount * 2;
|
||||||
const circlePosX = [
|
let circlePosX = [
|
||||||
Math.round(1 * w / parts), // circle1
|
Math.round(1 * w / parts), // circle1
|
||||||
Math.round(3 * w / parts), // circle2
|
Math.round(3 * w / parts), // circle2
|
||||||
Math.round(5 * w / parts), // circle3
|
Math.round(5 * w / parts), // circle3
|
||||||
Math.round(7 * w / parts), // circle4
|
Math.round(7 * w / parts), // circle4
|
||||||
];
|
];
|
||||||
|
|
||||||
const radiusOuter = circleCount == 3 ? 25 : 20;
|
let radiusOuter = circleCount == 3 ? 25 : 20;
|
||||||
const radiusInner = circleCount == 3 ? 20 : 15;
|
let radiusInner = circleCount == 3 ? 20 : 15;
|
||||||
const circleFontSmall = circleCount == 3 ? "Vector:14" : "Vector:10";
|
let circleFontSmall = circleCount == 3 ? "Vector:14" : "Vector:10";
|
||||||
const circleFont = circleCount == 3 ? "Vector:15" : "Vector:11";
|
let circleFont = circleCount == 3 ? "Vector:15" : "Vector:11";
|
||||||
const circleFontBig = circleCount == 3 ? "Vector:16" : "Vector:12";
|
let circleFontBig = circleCount == 3 ? "Vector:16" : "Vector:12";
|
||||||
const iconOffset = circleCount == 3 ? 6 : 8;
|
let iconOffset = circleCount == 3 ? 6 : 8;
|
||||||
const defaultCircleTypes = ["steps", "hr", "battery", "weather"];
|
let defaultCircleTypes = ["steps", "hr", "battery", "weather"];
|
||||||
|
|
||||||
|
|
||||||
function hideWidgets() {
|
function hideWidgets() {
|
||||||
/*
|
/*
|
||||||
|
|
@ -105,9 +110,16 @@ function hideWidgets() {
|
||||||
|
|
||||||
function draw() {
|
function draw() {
|
||||||
g.clear(true);
|
g.clear(true);
|
||||||
|
let widgetUtils;
|
||||||
|
|
||||||
|
try {
|
||||||
|
widgetUtils = require("widget_utils");
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
if (!showWidgets) {
|
if (!showWidgets) {
|
||||||
hideWidgets();
|
if (widgetUtils) widgetUtils.hide(); else hideWidgets();
|
||||||
} else {
|
} else {
|
||||||
|
if (widgetUtils) widgetUtils.show();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,27 +128,53 @@ function draw() {
|
||||||
|
|
||||||
// time
|
// time
|
||||||
g.setFontRobotoRegular50NumericOnly();
|
g.setFontRobotoRegular50NumericOnly();
|
||||||
g.setFontAlign(0, -1);
|
|
||||||
g.setColor(colorFg);
|
g.setColor(colorFg);
|
||||||
g.drawString(locale.time(new Date(), 1), w / 2, h1 + 6);
|
if (!showBigWeather) {
|
||||||
|
g.setFontAlign(0, -1);
|
||||||
|
g.drawString(locale.time(new Date(), 1), w / 2, h1 + 6);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g.setFontAlign(-1, -1);
|
||||||
|
g.drawString(locale.time(new Date(), 1), 2, h1 + 6);
|
||||||
|
}
|
||||||
now = Math.round(new Date().getTime() / 1000);
|
now = Math.round(new Date().getTime() / 1000);
|
||||||
|
|
||||||
// date & dow
|
// date & dow
|
||||||
g.setFontRobotoRegular21();
|
g.setFontRobotoRegular21();
|
||||||
g.setFontAlign(0, 0);
|
if (!showBigWeather) {
|
||||||
g.drawString(locale.date(new Date()), w / 2, h2);
|
g.setFontAlign(0, 0);
|
||||||
g.drawString(locale.dow(new Date()), w / 2, h2 + dowOffset);
|
g.drawString(locale.date(new Date()), w / 2, h2);
|
||||||
|
g.drawString(locale.dow(new Date()), w / 2, h2 + dowOffset);
|
||||||
|
} else {
|
||||||
|
g.setFontAlign(-1, 0);
|
||||||
|
g.drawString(locale.date(new Date()), 2, h2);
|
||||||
|
g.drawString(locale.dow(new Date()), 2, h2 + dowOffset, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// weather
|
||||||
|
if (showBigWeather) {
|
||||||
|
let weather = getWeather();
|
||||||
|
let tempString = weather ? locale.temp(weather.temp - 273.15) : undefined;
|
||||||
|
g.setFontAlign(1, 0);
|
||||||
|
if (tempString) g.drawString(tempString, w, h2);
|
||||||
|
|
||||||
|
let code = weather ? weather.code : -1;
|
||||||
|
let icon = getWeatherIconByCode(code, true);
|
||||||
|
if (icon) g.drawImage(icon, w - 48, h1, {scale:0.75});
|
||||||
|
}
|
||||||
|
|
||||||
drawCircle(1);
|
drawCircle(1);
|
||||||
drawCircle(2);
|
drawCircle(2);
|
||||||
drawCircle(3);
|
drawCircle(3);
|
||||||
if (circleCount >= 4) drawCircle(4);
|
if (circleCount >= 4) drawCircle(4);
|
||||||
|
|
||||||
|
queueDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawCircle(index) {
|
function drawCircle(index) {
|
||||||
let type = settings['circle' + index];
|
let type = settings['circle' + index];
|
||||||
if (!type) type = defaultCircleTypes[index - 1];
|
if (!type) type = defaultCircleTypes[index - 1];
|
||||||
const w = getCircleXPosition(type);
|
let w = getCircleXPosition(type);
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "steps":
|
case "steps":
|
||||||
|
|
@ -188,7 +226,7 @@ function getCirclePosition(type) {
|
||||||
return circlePositionsCache[type];
|
return circlePositionsCache[type];
|
||||||
}
|
}
|
||||||
for (let i = 1; i <= circleCount; i++) {
|
for (let i = 1; i <= circleCount; i++) {
|
||||||
const setting = settings['circle' + i];
|
let setting = settings['circle' + i];
|
||||||
if (setting == type) {
|
if (setting == type) {
|
||||||
circlePositionsCache[type] = i - 1;
|
circlePositionsCache[type] = i - 1;
|
||||||
return i - 1;
|
return i - 1;
|
||||||
|
|
@ -204,7 +242,7 @@ function getCirclePosition(type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCircleXPosition(type) {
|
function getCircleXPosition(type) {
|
||||||
const circlePos = getCirclePosition(type);
|
let circlePos = getCirclePosition(type);
|
||||||
if (circlePos != undefined) {
|
if (circlePos != undefined) {
|
||||||
return circlePosX[circlePos];
|
return circlePosX[circlePos];
|
||||||
}
|
}
|
||||||
|
|
@ -216,14 +254,14 @@ function isCircleEnabled(type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCircleColor(type) {
|
function getCircleColor(type) {
|
||||||
const pos = getCirclePosition(type);
|
let pos = getCirclePosition(type);
|
||||||
const color = settings["circle" + (pos + 1) + "color"];
|
let color = settings["circle" + (pos + 1) + "color"];
|
||||||
if (color && color != "") return color;
|
if (color && color != "") return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCircleIconColor(type, color, percent) {
|
function getCircleIconColor(type, color, percent) {
|
||||||
const pos = getCirclePosition(type);
|
let pos = getCirclePosition(type);
|
||||||
const colorizeIcon = settings["circle" + (pos + 1) + "colorizeIcon"] == true;
|
let colorizeIcon = settings["circle" + (pos + 1) + "colorizeIcon"] == true;
|
||||||
if (colorizeIcon) {
|
if (colorizeIcon) {
|
||||||
return getGradientColor(color, percent);
|
return getGradientColor(color, percent);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -234,18 +272,18 @@ function getCircleIconColor(type, color, percent) {
|
||||||
function getGradientColor(color, percent) {
|
function getGradientColor(color, percent) {
|
||||||
if (isNaN(percent)) percent = 0;
|
if (isNaN(percent)) percent = 0;
|
||||||
if (percent > 1) percent = 1;
|
if (percent > 1) percent = 1;
|
||||||
const colorList = [
|
let colorList = [
|
||||||
'#00FF00', '#80FF00', '#FFFF00', '#FF8000', '#FF0000'
|
'#00FF00', '#80FF00', '#FFFF00', '#FF8000', '#FF0000'
|
||||||
];
|
];
|
||||||
if (color == "fg") {
|
if (color == "fg") {
|
||||||
color = colorFg;
|
color = colorFg;
|
||||||
}
|
}
|
||||||
if (color == "green-red") {
|
if (color == "green-red") {
|
||||||
const colorIndex = Math.round(colorList.length * percent);
|
let colorIndex = Math.round(colorList.length * percent);
|
||||||
return colorList[Math.min(colorIndex, colorList.length) - 1] || "#00ff00";
|
return colorList[Math.min(colorIndex, colorList.length) - 1] || "#00ff00";
|
||||||
}
|
}
|
||||||
if (color == "red-green") {
|
if (color == "red-green") {
|
||||||
const colorIndex = colorList.length - Math.round(colorList.length * percent);
|
let colorIndex = colorList.length - Math.round(colorList.length * percent);
|
||||||
return colorList[Math.min(colorIndex, colorList.length)] || "#ff0000";
|
return colorList[Math.min(colorIndex, colorList.length)] || "#ff0000";
|
||||||
}
|
}
|
||||||
return color;
|
return color;
|
||||||
|
|
@ -268,14 +306,14 @@ function getImage(graphic, color) {
|
||||||
|
|
||||||
function drawSteps(w) {
|
function drawSteps(w) {
|
||||||
if (!w) w = getCircleXPosition("steps");
|
if (!w) w = getCircleXPosition("steps");
|
||||||
const steps = getSteps();
|
let steps = getSteps();
|
||||||
|
|
||||||
drawCircleBackground(w);
|
drawCircleBackground(w);
|
||||||
|
|
||||||
const color = getCircleColor("steps");
|
let color = getCircleColor("steps");
|
||||||
|
|
||||||
let percent;
|
let percent;
|
||||||
const stepGoal = settings.stepGoal;
|
let stepGoal = settings.stepGoal;
|
||||||
if (stepGoal > 0) {
|
if (stepGoal > 0) {
|
||||||
percent = steps / stepGoal;
|
percent = steps / stepGoal;
|
||||||
if (stepGoal < steps) percent = 1;
|
if (stepGoal < steps) percent = 1;
|
||||||
|
|
@ -291,16 +329,16 @@ function drawSteps(w) {
|
||||||
|
|
||||||
function drawStepsDistance(w) {
|
function drawStepsDistance(w) {
|
||||||
if (!w) w = getCircleXPosition("stepsDistance");
|
if (!w) w = getCircleXPosition("stepsDistance");
|
||||||
const steps = getSteps();
|
let steps = getSteps();
|
||||||
const stepDistance = settings.stepLength;
|
let stepDistance = settings.stepLength;
|
||||||
const stepsDistance = Math.round(steps * stepDistance);
|
let stepsDistance = Math.round(steps * stepDistance);
|
||||||
|
|
||||||
drawCircleBackground(w);
|
drawCircleBackground(w);
|
||||||
|
|
||||||
const color = getCircleColor("stepsDistance");
|
let color = getCircleColor("stepsDistance");
|
||||||
|
|
||||||
let percent;
|
let percent;
|
||||||
const stepDistanceGoal = settings.stepDistanceGoal;
|
let stepDistanceGoal = settings.stepDistanceGoal;
|
||||||
if (stepDistanceGoal > 0) {
|
if (stepDistanceGoal > 0) {
|
||||||
percent = stepsDistance / stepDistanceGoal;
|
percent = stepsDistance / stepDistanceGoal;
|
||||||
if (stepDistanceGoal < stepsDistance) percent = 1;
|
if (stepDistanceGoal < stepsDistance) percent = 1;
|
||||||
|
|
@ -317,16 +355,16 @@ function drawStepsDistance(w) {
|
||||||
function drawHeartRate(w) {
|
function drawHeartRate(w) {
|
||||||
if (!w) w = getCircleXPosition("hr");
|
if (!w) w = getCircleXPosition("hr");
|
||||||
|
|
||||||
const heartIcon = atob("EBCBAAAAAAAeeD/8P/x//n/+P/w//B/4D/AH4APAAYAAAAAA");
|
let heartIcon = atob("EBCBAAAAAAAeeD/8P/x//n/+P/w//B/4D/AH4APAAYAAAAAA");
|
||||||
|
|
||||||
drawCircleBackground(w);
|
drawCircleBackground(w);
|
||||||
|
|
||||||
const color = getCircleColor("hr");
|
let color = getCircleColor("hr");
|
||||||
|
|
||||||
let percent;
|
let percent;
|
||||||
if (hrtValue != undefined) {
|
if (hrtValue != undefined) {
|
||||||
const minHR = settings.minHR;
|
let minHR = settings.minHR;
|
||||||
const maxHR = settings.maxHR;
|
let maxHR = settings.maxHR;
|
||||||
percent = (hrtValue - minHR) / (maxHR - minHR);
|
percent = (hrtValue - minHR) / (maxHR - minHR);
|
||||||
if (isNaN(percent)) percent = 0;
|
if (isNaN(percent)) percent = 0;
|
||||||
drawGauge(w, h3, percent, color);
|
drawGauge(w, h3, percent, color);
|
||||||
|
|
@ -341,9 +379,9 @@ function drawHeartRate(w) {
|
||||||
|
|
||||||
function drawBattery(w) {
|
function drawBattery(w) {
|
||||||
if (!w) w = getCircleXPosition("battery");
|
if (!w) w = getCircleXPosition("battery");
|
||||||
const battery = E.getBattery();
|
let battery = E.getBattery();
|
||||||
|
|
||||||
const powerIcon = atob("EBCBAAAAA8ADwA/wD/AP8A/wD/AP8A/wD/AP8A/wD/AH4AAA");
|
let powerIcon = atob("EBCBAAAAA8ADwA/wD/AP8A/wD/AP8A/wD/AP8A/wD/AH4AAA");
|
||||||
|
|
||||||
drawCircleBackground(w);
|
drawCircleBackground(w);
|
||||||
|
|
||||||
|
|
@ -371,18 +409,18 @@ function drawBattery(w) {
|
||||||
|
|
||||||
function drawWeather(w) {
|
function drawWeather(w) {
|
||||||
if (!w) w = getCircleXPosition("weather");
|
if (!w) w = getCircleXPosition("weather");
|
||||||
const weather = getWeather();
|
let weather = getWeather();
|
||||||
const tempString = weather ? locale.temp(weather.temp - 273.15) : undefined;
|
let tempString = weather ? locale.temp(weather.temp - 273.15) : undefined;
|
||||||
const code = weather ? weather.code : -1;
|
let code = weather ? weather.code : -1;
|
||||||
|
|
||||||
drawCircleBackground(w);
|
drawCircleBackground(w);
|
||||||
|
|
||||||
const color = getCircleColor("weather");
|
let color = getCircleColor("weather");
|
||||||
let percent;
|
let percent;
|
||||||
const data = settings.weatherCircleData;
|
let data = settings.weatherCircleData;
|
||||||
switch (data) {
|
switch (data) {
|
||||||
case "humidity":
|
case "humidity":
|
||||||
const humidity = weather ? weather.hum : undefined;
|
let humidity = weather ? weather.hum : undefined;
|
||||||
if (humidity >= 0) {
|
if (humidity >= 0) {
|
||||||
percent = humidity / 100;
|
percent = humidity / 100;
|
||||||
drawGauge(w, h3, percent, color);
|
drawGauge(w, h3, percent, color);
|
||||||
|
|
@ -390,7 +428,7 @@ function drawWeather(w) {
|
||||||
break;
|
break;
|
||||||
case "wind":
|
case "wind":
|
||||||
if (weather) {
|
if (weather) {
|
||||||
const wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/);
|
let wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/);
|
||||||
if (wind[1] >= 0) {
|
if (wind[1] >= 0) {
|
||||||
if (wind[2] == "kmh") {
|
if (wind[2] == "kmh") {
|
||||||
wind[1] = windAsBeaufort(wind[1]);
|
wind[1] = windAsBeaufort(wind[1]);
|
||||||
|
|
@ -410,25 +448,24 @@ function drawWeather(w) {
|
||||||
writeCircleText(w, tempString ? tempString : "?");
|
writeCircleText(w, tempString ? tempString : "?");
|
||||||
|
|
||||||
if (code > 0) {
|
if (code > 0) {
|
||||||
const icon = getWeatherIconByCode(code);
|
let icon = getWeatherIconByCode(code);
|
||||||
if (icon) g.drawImage(getImage(icon, getCircleIconColor("weather", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
|
if (icon) g.drawImage(getImage(icon, getCircleIconColor("weather", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
|
||||||
} else {
|
} else {
|
||||||
g.drawString("?", w, h3 + radiusOuter);
|
g.drawString("?", w, h3 + radiusOuter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function drawSunProgress(w) {
|
function drawSunProgress(w) {
|
||||||
if (!w) w = getCircleXPosition("sunprogress");
|
if (!w) w = getCircleXPosition("sunprogress");
|
||||||
const percent = getSunProgress();
|
let percent = getSunProgress();
|
||||||
|
|
||||||
// sunset icons:
|
// sunset icons:
|
||||||
const sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA");
|
let sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA");
|
||||||
const sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA");
|
let sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA");
|
||||||
|
|
||||||
drawCircleBackground(w);
|
drawCircleBackground(w);
|
||||||
|
|
||||||
const color = getCircleColor("sunprogress");
|
let color = getCircleColor("sunprogress");
|
||||||
|
|
||||||
drawGauge(w, h3, percent, color);
|
drawGauge(w, h3, percent, color);
|
||||||
|
|
||||||
|
|
@ -436,15 +473,15 @@ function drawSunProgress(w) {
|
||||||
|
|
||||||
let icon = sunSetDown;
|
let icon = sunSetDown;
|
||||||
let text = "?";
|
let text = "?";
|
||||||
const times = getSunData();
|
let times = getSunData();
|
||||||
if (times != undefined) {
|
if (times != undefined) {
|
||||||
const sunRise = Math.round(times.sunrise.getTime() / 1000);
|
let sunRise = Math.round(times.sunrise.getTime() / 1000);
|
||||||
const sunSet = Math.round(times.sunset.getTime() / 1000);
|
let sunSet = Math.round(times.sunset.getTime() / 1000);
|
||||||
if (!isDay()) {
|
if (!isDay()) {
|
||||||
// night
|
// night
|
||||||
if (now > sunRise) {
|
if (now > sunRise) {
|
||||||
// after sunRise
|
// after sunRise
|
||||||
const upcomingSunRise = sunRise + 60 * 60 * 24;
|
let upcomingSunRise = sunRise + 60 * 60 * 24;
|
||||||
text = formatSeconds(upcomingSunRise - now);
|
text = formatSeconds(upcomingSunRise - now);
|
||||||
} else {
|
} else {
|
||||||
text = formatSeconds(sunRise - now);
|
text = formatSeconds(sunRise - now);
|
||||||
|
|
@ -468,12 +505,12 @@ function drawTemperature(w) {
|
||||||
getPressureValue("temperature").then((temperature) => {
|
getPressureValue("temperature").then((temperature) => {
|
||||||
drawCircleBackground(w);
|
drawCircleBackground(w);
|
||||||
|
|
||||||
const color = getCircleColor("temperature");
|
let color = getCircleColor("temperature");
|
||||||
|
|
||||||
let percent;
|
let percent;
|
||||||
if (temperature) {
|
if (temperature) {
|
||||||
const min = -40;
|
let min = -40;
|
||||||
const max = 85;
|
let max = 85;
|
||||||
percent = (temperature - min) / (max - min);
|
percent = (temperature - min) / (max - min);
|
||||||
drawGauge(w, h3, percent, color);
|
drawGauge(w, h3, percent, color);
|
||||||
}
|
}
|
||||||
|
|
@ -494,12 +531,12 @@ function drawPressure(w) {
|
||||||
getPressureValue("pressure").then((pressure) => {
|
getPressureValue("pressure").then((pressure) => {
|
||||||
drawCircleBackground(w);
|
drawCircleBackground(w);
|
||||||
|
|
||||||
const color = getCircleColor("pressure");
|
let color = getCircleColor("pressure");
|
||||||
|
|
||||||
let percent;
|
let percent;
|
||||||
if (pressure && pressure > 0) {
|
if (pressure && pressure > 0) {
|
||||||
const minPressure = 950;
|
let minPressure = 950;
|
||||||
const maxPressure = 1050;
|
let maxPressure = 1050;
|
||||||
percent = (pressure - minPressure) / (maxPressure - minPressure);
|
percent = (pressure - minPressure) / (maxPressure - minPressure);
|
||||||
drawGauge(w, h3, percent, color);
|
drawGauge(w, h3, percent, color);
|
||||||
}
|
}
|
||||||
|
|
@ -520,12 +557,12 @@ function drawAltitude(w) {
|
||||||
getPressureValue("altitude").then((altitude) => {
|
getPressureValue("altitude").then((altitude) => {
|
||||||
drawCircleBackground(w);
|
drawCircleBackground(w);
|
||||||
|
|
||||||
const color = getCircleColor("altitude");
|
let color = getCircleColor("altitude");
|
||||||
|
|
||||||
let percent;
|
let percent;
|
||||||
if (altitude) {
|
if (altitude) {
|
||||||
const min = 0;
|
let min = 0;
|
||||||
const max = 10000;
|
let max = 10000;
|
||||||
percent = (altitude - min) / (max - min);
|
percent = (altitude - min) / (max - min);
|
||||||
drawGauge(w, h3, percent, color);
|
drawGauge(w, h3, percent, color);
|
||||||
}
|
}
|
||||||
|
|
@ -544,7 +581,7 @@ function drawAltitude(w) {
|
||||||
* wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale)
|
* wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale)
|
||||||
*/
|
*/
|
||||||
function windAsBeaufort(windInKmh) {
|
function windAsBeaufort(windInKmh) {
|
||||||
const beaufort = [2, 6, 12, 20, 29, 39, 50, 62, 75, 89, 103, 118];
|
let beaufort = [2, 6, 12, 20, 29, 39, 50, 62, 75, 89, 103, 118];
|
||||||
let l = 0;
|
let l = 0;
|
||||||
while (l < beaufort.length && beaufort[l] < windInKmh) {
|
while (l < beaufort.length && beaufort[l] < windInKmh) {
|
||||||
l++;
|
l++;
|
||||||
|
|
@ -557,19 +594,21 @@ function windAsBeaufort(windInKmh) {
|
||||||
* Choose weather icon to display based on weather conditition code
|
* Choose weather icon to display based on weather conditition code
|
||||||
* https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
|
* https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
|
||||||
*/
|
*/
|
||||||
function getWeatherIconByCode(code) {
|
function getWeatherIconByCode(code, big) {
|
||||||
const codeGroup = Math.round(code / 100);
|
let codeGroup = Math.round(code / 100);
|
||||||
|
if (big == undefined) big = false;
|
||||||
|
|
||||||
// weather icons:
|
// weather icons:
|
||||||
const weatherCloudy = atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA");
|
let weatherCloudy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4AAAAAAAfg+AAAAAAAAfHwAAAAAAAA+eAAAAAAAAB54AAAAAAAAHvAAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD3gAAAAAAAAeeAAAAAAAAB58AAAAAAAAPj4AAAAAAAB8H4AAAAAAAfgP////////8Af////////gA////////8AAf//////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA");
|
||||||
const weatherSunny = atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA");
|
let weatherSunny = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAwADwADAAAAHgAPAAeAAAAfAA8AD4AAAA+ADwAfAAAAB8APAD4AAAAD4B+AfAAAAAHw//D4AAAAAPv//fAAAAAAf///4AAAAAA/4H/AAAAAAB+AH4AAAAAAPgAHwAAAAAA8AAPAAAAAAHwAA+AAAAAAeAAB4AAAAAB4AAHgAAAAAPAAAPAAAA//8AAA//8AD//wAAD//wAP//AAAP//AA//8AAA//8AAADwAADwAAAAAHgAAeAAAAAAeAAB4AAAAAB8AAPgAAAAADwAA8AAAAAAPgAHwAAAAAAfgB+AAAAAAD/gf8AAAAAAf///4AAAAAD7//3wAAAAAfD/8PgAAAAD4B+AfAAAAAfADwA+AAAAD4APAB8AAAAfAA8AD4AAAB4ADwAHgAAADAAPAAMAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA");
|
||||||
const weatherMoon = atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA");
|
let weatherMoon = big ? atob("QEDBAP//wxgAAAYAAAAPAAAAD4AAAA8AAAAPwAAADwAAAA/gAAAPAAAAB/APAP/wAAAH+A8A//AAAAf4DwD/8AAAB/wPAP/wAAAH/gAADwAAAAe+AAAPAAAAB54AAA8AAAAHngAADwAAAAePAAAAAAAAD48OAAAAAAAPDw+AAAAAAB8PD8AAAAAAHg8P4AAAAAA+DwPwAAAAAHwfAfgAAAAB+D4A/AAA8AfwfgB/8AD//+D+AD/8AP//wfgAH/4Af/8B8AAf/wB//APgAAgfgD+AA8AAAAfAH8AHwAAAA+AP8B+AAAAB4Af//4AAAAHgA///gAAAAPAA//8AAAAA8AAf/wAAAADwAAAAAAAAAPAAAAAAAAAA8AcAAAAAAADwD+AAAAAAAfAfgAAAAAAB+D4AAAAAAAB8fAAAAAAAAD54AAAAAAAAHngAAAAAAAAe8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAPeAAAAAAAAB54AAAAAAAAHnwAAAAAAAA+PgAAAAAAAHwfgAAAAAAB+A/////////wB////////+AD////////wAB///////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA");
|
||||||
const weatherPartlyCloudy = atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA");
|
let weatherPartlyCloudy = big ? atob("QEDBAP//wxgAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAABwAPAA4AAAAHgA8AHgAAAAfADwA+AAAAA+AfgHwAAAAB8P/w+AAAAAD7//3wAAAAAH///+BAAAAAP+B/wOAAAAAfgB+B8AAAAD4AD8H4AAAAPAA/wPwAAAB8AH+Af/AAAHgA/AA//AAAeAH4AB/+AADwAfAAH/8A//AD4AAIH4D/8AfAAAAHwP/wB4AAAAPg//AHgAAAAeAA8B+AAAAB4AB4fwAAAADwAHn/AAAAAPAAff8AAAAA8AA/8AAAAADwAD/AAAAAAPAEH4AAAAAA8A4PgAAAAAHwHgcAAAAAAfg+AwAAAAAAfHwAAAAAAAA+eAAAAAAAAB54AAAAAAAAHvAAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD3gAAAAAAAAeeAAAAAAAAB58AAAAAAAAPj4AAAAAAAB8H4AAAAAAAfgP////////8Af////////gA////////8AAf//////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA");
|
||||||
const weatherRainy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA");
|
let weatherRainy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4APAA8AAfg+AA8ADwAAfHwADwAPAAA+eAAPAA8AAB54AAAAAAAAHvAAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AADw8PDwAP8AAPDw8PAA/wAA8PDw8AD3gADw8PDwAeeAAAAAAAAB58AAAAAAAAPj4AAAAAAAB8H4AAAAAAAfgP/w8PDw8P8Af/Dw8PDw/gA/8PDw8PD8AAfw8PDw8OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAPAAAAAAAPAA8AAAAAAA8ADwAAAAAADwAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA");
|
||||||
const weatherPartlyRainy = atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA");
|
let weatherPartlyRainy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4AAAA8AAfg+AAAADwAAfHwAAAAPAAA+eAAAAA8AAB54AAAADwAAHvAAAAAPAAAP8AAAAA8AAA/wAAAADwAAD/AAAA8PAAAP8AAADw8AAA/wAAAPDwAAD3gAAA8PAAAeeAAADw8AAB58AAAPDwAAPj4AAA8PAAB8H4AADw8AAfgP//8PDw//8Af//w8PD//gA///Dw8P/8AAf/8PDw/+AAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA");
|
||||||
const weatherSnowy = atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA");
|
let weatherSnowy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4AAAADwAfg+AAAAAPAAfHwAAAAA8AA+eAAAAADwAB54AA8AD/8AHvAADwAP/wAP8AAPAA//AA/wAA8AD/8AD/AA//AA8AAP8AD/8ADwAA/wAP/wAPAAD3gA//AA8AAeeAAPAAAAAB58AA8AAAAAPj4ADwAAAAB8H4APAAAAAfgP/wAA8A//8Af/AADwD//gA/8AAPAP/8AAfwAA8A/+AAAAAA//AAAAAAAAD/8AAAAAAAAP/wAAAAAAAA//AAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA");
|
||||||
const weatherFoggy = atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA");
|
let weatherFoggy = big ? atob("QEDBAP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAwADwADAAAAHgAPAAeAAAAfAA8AD4AAAA+ADwAfAAAAB8APAD4AAAAD4B+AfAAAAAHw//D4AAAAAPv//fAAAAAAf///4AAAAAA/4H/AAAAAAB+AH4AAAAAAPgAHwAAAAAA8AAPAAAAAAHwAA+AAAAAAeAAB4AAAAAB4AAHgAAAAAPAAAPAAAAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AD///AADwAAAP//8AAeAAAA///wAB4AAAD///AAPgAAAAAAAAA8AAAAAAAAAHwAAAAAAAAB+AAAAAAAAAf8AAAAD///D/4AAAAP//8P3wAAAA///w8PgAAAD///CAfAAAAAAAAAA+AAAAAAAAAB8AAAAAAAAAD4AAAAAAAAAHgAAP//8PAAMAAA///w8AAAAAD///DwAAAAAP//8PAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA");
|
||||||
const weatherStormy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA");
|
let weatherStormy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4AAAAAAAfg+AAAAAAAAfHwAAAAAAAA+eAAAAAAAAB54AAAAD/AAHvAAAAAf4AAP8AAAAB/gAA/wAAAAP8AAD/AAAAA/gAAP8AAAAH+AAA/wAAAAfwAAD3gAAAD/AAAeeAAAAP4AAB58AAAB/AAAPj4AAAH8AAB8H4AAA/gAAfgP//+D//D/8Af//4f/4P/gA///B//B/8AAf/8P/8P+AAAAAAAPgAAAAAAAAB8AAAAAAAAAHwAAAAAAAAA+AAAAAAAAADwAAAAAAAAAfAAAAAAAAAB4AAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA");
|
||||||
|
let unknown = big ? atob("QEDBAP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAf/4AAAAAAAH//4AAAAAAA///wAAAAAAH+B/gAAAAAA/AA/AAAAAAH4AB+AAAAAA/AAD4AAAAAD4H4HwAAAAAfB/4PgAAAAB8P/weAAAAAHg//h4AAAAA+Hw+HwAAAAD4eB8PAAAAAP/wDw8AAAAA//APDwAAAAD/8A8PAAAAAH/gDw8AAAAAAAAfDwAAAAAAAH4fAAAAAAAB/B4AAAAAAAf4HgAAAAAAD/A+AAAAAAAfwHwAAAAAAD8A+AAAAAAAPgH4AAAAAAB8B/AAAAAAAHgf4AAAAAAA+H+AAAAAAADwfwAAAAAAAPD8AAAAAAAA8PAAAAAAAAD/8AAAAAAAAP/wAAAAAAAA//AAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf+AAAAAAAAD/8AAAAAAAAP/wAAAAAAAA//AAAAAAAADw8AAAAAAAAPDwAAAAAAAA8PAAAAAAAADw8AAAAAAAAP/wAAAAAAAA//AAAAAAAAD/8AAAAAAAAH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : undefined;
|
||||||
|
|
||||||
switch (codeGroup) {
|
switch (codeGroup) {
|
||||||
case 2:
|
case 2:
|
||||||
|
|
@ -607,16 +646,16 @@ function getWeatherIconByCode(code) {
|
||||||
return weatherCloudy;
|
return weatherCloudy;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return unknown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function isDay() {
|
function isDay() {
|
||||||
const times = getSunData();
|
let times = getSunData();
|
||||||
if (times == undefined) return true;
|
if (times == undefined) return true;
|
||||||
const sunRise = Math.round(times.sunrise.getTime() / 1000);
|
let sunRise = Math.round(times.sunrise.getTime() / 1000);
|
||||||
const sunSet = Math.round(times.sunset.getTime() / 1000);
|
let sunSet = Math.round(times.sunset.getTime() / 1000);
|
||||||
|
|
||||||
return (now > sunRise && now < sunSet);
|
return (now > sunRise && now < sunSet);
|
||||||
}
|
}
|
||||||
|
|
@ -633,7 +672,7 @@ function formatSeconds(s) {
|
||||||
|
|
||||||
function getSunData() {
|
function getSunData() {
|
||||||
if (location != undefined && location.lat != undefined) {
|
if (location != undefined && location.lat != undefined) {
|
||||||
const SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js");
|
let SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js");
|
||||||
// get today's sunlight times for lat/lon
|
// get today's sunlight times for lat/lon
|
||||||
return SunCalc ? SunCalc.getTimes(new Date(), location.lat, location.lon) : undefined;
|
return SunCalc ? SunCalc.getTimes(new Date(), location.lat, location.lon) : undefined;
|
||||||
}
|
}
|
||||||
|
|
@ -646,14 +685,14 @@ function getSunData() {
|
||||||
* Taken from rebble app and modified
|
* Taken from rebble app and modified
|
||||||
*/
|
*/
|
||||||
function getSunProgress() {
|
function getSunProgress() {
|
||||||
const times = getSunData();
|
let times = getSunData();
|
||||||
if (times == undefined) return 0;
|
if (times == undefined) return 0;
|
||||||
const sunRise = Math.round(times.sunrise.getTime() / 1000);
|
let sunRise = Math.round(times.sunrise.getTime() / 1000);
|
||||||
const sunSet = Math.round(times.sunset.getTime() / 1000);
|
let sunSet = Math.round(times.sunset.getTime() / 1000);
|
||||||
|
|
||||||
if (isDay()) {
|
if (isDay()) {
|
||||||
// during day
|
// during day
|
||||||
const dayLength = sunSet - sunRise;
|
let dayLength = sunSet - sunRise;
|
||||||
if (now > sunRise) {
|
if (now > sunRise) {
|
||||||
return (now - sunRise) / dayLength;
|
return (now - sunRise) / dayLength;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -662,10 +701,10 @@ function getSunProgress() {
|
||||||
} else {
|
} else {
|
||||||
// during night
|
// during night
|
||||||
if (now < sunRise) {
|
if (now < sunRise) {
|
||||||
const prevSunSet = sunSet - 60 * 60 * 24;
|
let prevSunSet = sunSet - 60 * 60 * 24;
|
||||||
return 1 - (sunRise - now) / (sunRise - prevSunSet);
|
return 1 - (sunRise - now) / (sunRise - prevSunSet);
|
||||||
} else {
|
} else {
|
||||||
const upcomingSunRise = sunRise + 60 * 60 * 24;
|
let upcomingSunRise = sunRise + 60 * 60 * 24;
|
||||||
return (upcomingSunRise - now) / (upcomingSunRise - sunSet);
|
return (upcomingSunRise - now) / (upcomingSunRise - sunSet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -700,16 +739,16 @@ function radians(a) {
|
||||||
* This draws the actual gauge consisting out of lots of little filled circles
|
* This draws the actual gauge consisting out of lots of little filled circles
|
||||||
*/
|
*/
|
||||||
function drawGauge(cx, cy, percent, color) {
|
function drawGauge(cx, cy, percent, color) {
|
||||||
const offset = 15;
|
let offset = 15;
|
||||||
const end = 360 - offset;
|
let end = 360 - offset;
|
||||||
const radius = radiusInner + (circleCount == 3 ? 3 : 2);
|
let radius = radiusInner + (circleCount == 3 ? 3 : 2);
|
||||||
const size = radiusOuter - radiusInner - 2;
|
let size = radiusOuter - radiusInner - 2;
|
||||||
|
|
||||||
if (percent <= 0) return; // no gauge needed
|
if (percent <= 0) return; // no gauge needed
|
||||||
if (percent > 1) percent = 1;
|
if (percent > 1) percent = 1;
|
||||||
|
|
||||||
const startRotation = -offset;
|
let startRotation = -offset;
|
||||||
const endRotation = startRotation - ((end - offset) * percent);
|
let endRotation = startRotation - ((end - offset) * percent);
|
||||||
|
|
||||||
color = getGradientColor(color, percent);
|
color = getGradientColor(color, percent);
|
||||||
g.setColor(color);
|
g.setColor(color);
|
||||||
|
|
@ -723,7 +762,7 @@ function drawGauge(cx, cy, percent, color) {
|
||||||
|
|
||||||
function writeCircleText(w, content) {
|
function writeCircleText(w, content) {
|
||||||
if (content == undefined) return;
|
if (content == undefined) return;
|
||||||
const font = String(content).length > 4 ? circleFontSmall : String(content).length > 3 ? circleFont : circleFontBig;
|
let font = String(content).length > 4 ? circleFontSmall : String(content).length > 3 ? circleFont : circleFontBig;
|
||||||
g.setFont(font);
|
g.setFont(font);
|
||||||
|
|
||||||
g.setFontAlign(0, 0);
|
g.setFontAlign(0, 0);
|
||||||
|
|
@ -755,7 +794,7 @@ function getSteps() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWeather() {
|
function getWeather() {
|
||||||
const jsonWeather = storage.readJSON('weather.json');
|
let jsonWeather = storage.readJSON('weather.json');
|
||||||
return jsonWeather && jsonWeather.weather ? jsonWeather.weather : undefined;
|
return jsonWeather && jsonWeather.weather ? jsonWeather.weather : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -796,7 +835,7 @@ function getPressureValue(type) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Bangle.on('lock', function(isLocked) {
|
function onLock(isLocked) {
|
||||||
if (!isLocked) {
|
if (!isLocked) {
|
||||||
draw();
|
draw();
|
||||||
if (isCircleEnabled("hr")) {
|
if (isCircleEnabled("hr")) {
|
||||||
|
|
@ -805,11 +844,10 @@ Bangle.on('lock', function(isLocked) {
|
||||||
} else {
|
} else {
|
||||||
Bangle.setHRMPower(0, "circleclock");
|
Bangle.setHRMPower(0, "circleclock");
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
Bangle.on('lock', onLock);
|
||||||
|
|
||||||
|
function onHRM(hrm) {
|
||||||
let timerHrm;
|
|
||||||
Bangle.on('HRM', function(hrm) {
|
|
||||||
if (isCircleEnabled("hr")) {
|
if (isCircleEnabled("hr")) {
|
||||||
if (hrm.confidence >= (settings.confidence)) {
|
if (hrm.confidence >= (settings.confidence)) {
|
||||||
hrtValue = hrm.bpm;
|
hrtValue = hrm.bpm;
|
||||||
|
|
@ -826,23 +864,48 @@ Bangle.on('HRM', function(hrm) {
|
||||||
}, settings.hrmValidity * 1000);
|
}, settings.hrmValidity * 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
Bangle.on('HRM', onHRM);
|
||||||
|
|
||||||
Bangle.on('charging', function(charging) {
|
function onCharging(charging) {
|
||||||
if (isCircleEnabled("battery")) drawBattery();
|
if (isCircleEnabled("battery")) drawBattery();
|
||||||
});
|
}
|
||||||
|
Bangle.on('charging', onCharging);
|
||||||
|
|
||||||
|
|
||||||
if (isCircleEnabled("hr")) {
|
if (isCircleEnabled("hr")) {
|
||||||
enableHRMSensor();
|
enableHRMSensor();
|
||||||
}
|
}
|
||||||
|
|
||||||
Bangle.setUI("clock");
|
Bangle.setUI({
|
||||||
|
mode : "clock",
|
||||||
|
remove : function() {
|
||||||
|
// Called to unload all of the clock app
|
||||||
|
Bangle.removeListener('charging', onCharging);
|
||||||
|
Bangle.removeListener('lock', onLock);
|
||||||
|
Bangle.removeListener('HRM', onHRM);
|
||||||
|
|
||||||
|
Bangle.setHRMPower(0, "circleclock");
|
||||||
|
|
||||||
|
if (timerHrm) clearTimeout(timerHrm);
|
||||||
|
timerHrm = undefined;
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = undefined;
|
||||||
|
|
||||||
|
delete Graphics.prototype.setFontRobotoRegular50NumericOnly;
|
||||||
|
delete Graphics.prototype.setFontRobotoRegular21;
|
||||||
|
}});
|
||||||
|
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
|
|
||||||
// schedule a draw for the next minute
|
// schedule a draw for the next second or minute
|
||||||
setTimeout(function() {
|
function queueDraw() {
|
||||||
// draw in interval
|
let queueMillis = settings.updateInterval * 1000;
|
||||||
setInterval(draw, settings.updateInterval * 1000);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
}, 60000 - (Date.now() % 60000));
|
drawTimeout = setTimeout(function() {
|
||||||
|
drawTimeout = undefined;
|
||||||
|
draw();
|
||||||
|
}, queueMillis - (Date.now() % queueMillis));
|
||||||
|
}
|
||||||
|
|
||||||
draw();
|
draw();
|
||||||
|
|
|
||||||
|
|
@ -22,5 +22,6 @@
|
||||||
"circle3colorizeIcon": true,
|
"circle3colorizeIcon": true,
|
||||||
"circle4colorizeIcon": false,
|
"circle4colorizeIcon": false,
|
||||||
"hrmValidity": 60,
|
"hrmValidity": 60,
|
||||||
"updateInterval": 60
|
"updateInterval": 60,
|
||||||
|
"showBigWeather": false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{ "id": "circlesclock",
|
{ "id": "circlesclock",
|
||||||
"name": "Circles clock",
|
"name": "Circles clock",
|
||||||
"shortName":"Circles clock",
|
"shortName":"Circles clock",
|
||||||
"version":"0.13",
|
"version":"0.16",
|
||||||
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
|
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],
|
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 4.4 KiB |
|
|
@ -68,6 +68,11 @@
|
||||||
return x + 's';
|
return x + 's';
|
||||||
},
|
},
|
||||||
onchange: x => save('updateInterval', x),
|
onchange: x => save('updateInterval', x),
|
||||||
|
},
|
||||||
|
/*LANG*/'show big weather': {
|
||||||
|
value: !!settings.showBigWeather,
|
||||||
|
format: () => (settings.showBigWeather ? 'Yes' : 'No'),
|
||||||
|
onchange: x => save('showBigWeather', x),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
|
|
|
||||||
|
|
@ -5,3 +5,4 @@
|
||||||
0.05: Fix bearing not clearing correctly (visible in single or double digit bearings)
|
0.05: Fix bearing not clearing correctly (visible in single or double digit bearings)
|
||||||
0.06: Add button for force compass calibration
|
0.06: Add button for force compass calibration
|
||||||
0.07: Use 360-heading to output the correct heading value (fix #1866)
|
0.07: Use 360-heading to output the correct heading value (fix #1866)
|
||||||
|
0.08: Added adjustment for Bangle.js magnetometer heading fix
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ ag.setColor(1).fillCircle(AGM,AGM,AGM-1,AGM-1);
|
||||||
ag.setColor(0).fillCircle(AGM,AGM,AGM-11,AGM-11);
|
ag.setColor(0).fillCircle(AGM,AGM,AGM-11,AGM-11);
|
||||||
|
|
||||||
function arrow(r,c) {
|
function arrow(r,c) {
|
||||||
r=r*Math.PI/180;
|
r=(360-r)*Math.PI/180;
|
||||||
var p = Math.PI/2;
|
var p = Math.PI/2;
|
||||||
ag.setColor(c).fillPoly([
|
ag.setColor(c).fillPoly([
|
||||||
AGM+AGH*Math.sin(r), AGM-AGH*Math.cos(r),
|
AGM+AGH*Math.sin(r), AGM-AGH*Math.cos(r),
|
||||||
|
|
@ -49,7 +49,7 @@ Bangle.on('mag', function(m) {
|
||||||
g.setFontAlign(0,0).setFont("6x8",3);
|
g.setFontAlign(0,0).setFont("6x8",3);
|
||||||
var y = 36;
|
var y = 36;
|
||||||
g.clearRect(M-40,24,M+40,48);
|
g.clearRect(M-40,24,M+40,48);
|
||||||
g.drawString(Math.round(360-m.heading),M,y,true);
|
g.drawString(Math.round(m.heading),M,y,true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "compass",
|
"id": "compass",
|
||||||
"name": "Compass",
|
"name": "Compass",
|
||||||
"version": "0.07",
|
"version": "0.08",
|
||||||
"description": "Simple compass that points North",
|
"description": "Simple compass that points North",
|
||||||
"icon": "compass.png",
|
"icon": "compass.png",
|
||||||
"screenshots": [{"url":"screenshot_compass.png"}],
|
"screenshots": [{"url":"screenshot_compass.png"}],
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
0.01: Create dotmatrix clock app
|
0.01: Create dotmatrix clock app
|
||||||
|
0.02: Added adjustment for Bangle.js magnetometer heading fix
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,7 @@ function drawCompass(lastHeading) {
|
||||||
'NW'
|
'NW'
|
||||||
];
|
];
|
||||||
const cps = Bangle.getCompass();
|
const cps = Bangle.getCompass();
|
||||||
let angle = cps.heading;
|
let angle = 360-cps.heading;
|
||||||
let heading = angle?
|
let heading = angle?
|
||||||
directions[Math.round(((angle %= 360) < 0 ? angle + 360 : angle) / 45) % 8]:
|
directions[Math.round(((angle %= 360) < 0 ? angle + 360 : angle) / 45) % 8]:
|
||||||
"-- ";
|
"-- ";
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "dotmatrixclock",
|
"id": "dotmatrixclock",
|
||||||
"name": "Dotmatrix Clock",
|
"name": "Dotmatrix Clock",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "A clear white-on-blue dotmatrix simulated clock",
|
"description": "A clear white-on-blue dotmatrix simulated clock",
|
||||||
"icon": "dotmatrixclock.png",
|
"icon": "dotmatrixclock.png",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,4 @@
|
||||||
0.03: Made the code shorter and somewhat more readable by writing some functions. Also made it work as a library where it returns the text once finished. The keyboard is now made to exit correctly when the 'back' event is called. The keyboard now uses theme colors correctly, although it still looks best with dark theme. The numbers row is now solidly green - except for highlights.
|
0.03: Made the code shorter and somewhat more readable by writing some functions. Also made it work as a library where it returns the text once finished. The keyboard is now made to exit correctly when the 'back' event is called. The keyboard now uses theme colors correctly, although it still looks best with dark theme. The numbers row is now solidly green - except for highlights.
|
||||||
0.04: Now displays the opened text string at launch.
|
0.04: Now displays the opened text string at launch.
|
||||||
0.05: Now scrolls text when string gets longer than screen width.
|
0.05: Now scrolls text when string gets longer than screen width.
|
||||||
|
0.06: The code is now more reliable and the input snappier. Widgets will be drawn if present.
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
//Keep banglejs screen on for 100 sec at 0.55 power level for development purposes
|
|
||||||
//Bangle.setLCDTimeout(30);
|
|
||||||
//Bangle.setLCDPower(1);
|
|
||||||
|
|
||||||
exports.input = function(options) {
|
exports.input = function(options) {
|
||||||
options = options||{};
|
options = options||{};
|
||||||
var text = options.text;
|
var text = options.text;
|
||||||
if ("string"!=typeof text) text="";
|
if ("string"!=typeof text) text="";
|
||||||
|
|
||||||
|
var R = Bangle.appRect;
|
||||||
var BGCOLOR = g.theme.bg;
|
var BGCOLOR = g.theme.bg;
|
||||||
var HLCOLOR = g.theme.fg;
|
var HLCOLOR = g.theme.fg;
|
||||||
var ABCCOLOR = g.toColor(1,0,0);//'#FF0000';
|
var ABCCOLOR = g.toColor(1,0,0);//'#FF0000';
|
||||||
|
|
@ -17,35 +14,38 @@ exports.input = function(options) {
|
||||||
var SMALLFONTWIDTH = parseInt(SMALLFONT.charAt(0)*parseInt(SMALLFONT.charAt(-1)));
|
var SMALLFONTWIDTH = parseInt(SMALLFONT.charAt(0)*parseInt(SMALLFONT.charAt(-1)));
|
||||||
|
|
||||||
var ABC = 'abcdefghijklmnopqrstuvwxyz'.toUpperCase();
|
var ABC = 'abcdefghijklmnopqrstuvwxyz'.toUpperCase();
|
||||||
var ABCPADDING = (g.getWidth()-6*ABC.length)/2;
|
var ABCPADDING = ((R.x+R.w)-6*ABC.length)/2;
|
||||||
|
|
||||||
var NUM = ' 1234567890!?,.- ';
|
var NUM = ' 1234567890!?,.- ';
|
||||||
var NUMHIDDEN = ' 1234567890!?,.- ';
|
var NUMHIDDEN = ' 1234567890!?,.- ';
|
||||||
var NUMPADDING = (g.getWidth()-6*NUM.length)/2;
|
var NUMPADDING = ((R.x+R.w)-6*NUM.length)/2;
|
||||||
|
|
||||||
var rectHeight = 40;
|
var rectHeight = 40;
|
||||||
|
|
||||||
|
|
||||||
var delSpaceLast;
|
var delSpaceLast;
|
||||||
|
|
||||||
function drawAbcRow() {
|
function drawAbcRow() {
|
||||||
g.clear();
|
g.clear();
|
||||||
|
try { // Draw widgets if they are present in the current app.
|
||||||
|
if (WIDGETS) Bangle.drawWidgets();
|
||||||
|
} catch (_) {}
|
||||||
g.setFont(SMALLFONT);
|
g.setFont(SMALLFONT);
|
||||||
g.setColor(ABCCOLOR);
|
g.setColor(ABCCOLOR);
|
||||||
g.drawString(ABC, ABCPADDING, g.getHeight()/2);
|
g.setFontAlign(-1, -1, 0);
|
||||||
g.fillRect(0, g.getHeight()-26, g.getWidth(), g.getHeight());
|
g.drawString(ABC, ABCPADDING, (R.y+R.h)/2);
|
||||||
|
g.fillRect(0, (R.y+R.h)-26, (R.x+R.w), (R.y+R.h));
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawNumRow() {
|
function drawNumRow() {
|
||||||
g.setFont(SMALLFONT);
|
g.setFont(SMALLFONT);
|
||||||
g.setColor(NUMCOLOR);
|
g.setColor(NUMCOLOR);
|
||||||
g.drawString(NUM, NUMPADDING, g.getHeight()/4);
|
g.setFontAlign(-1, -1, 0);
|
||||||
|
g.drawString(NUM, NUMPADDING, (R.y+R.h)/4);
|
||||||
|
|
||||||
g.fillRect(NUMPADDING, g.getHeight()-rectHeight*4/3, g.getWidth()-NUMPADDING, g.getHeight()-rectHeight*2/3);
|
g.fillRect(NUMPADDING, (R.y+R.h)-rectHeight*4/3, (R.x+R.w)-NUMPADDING, (R.y+R.h)-rectHeight*2/3);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTopString() {
|
function updateTopString() {
|
||||||
"ram"
|
|
||||||
g.setColor(BGCOLOR);
|
g.setColor(BGCOLOR);
|
||||||
g.fillRect(0,4+20,176,13+20);
|
g.fillRect(0,4+20,176,13+20);
|
||||||
g.setColor(0.2,0,0);
|
g.setColor(0.2,0,0);
|
||||||
|
|
@ -54,13 +54,10 @@ exports.input = function(options) {
|
||||||
g.setColor(0.7,0,0);
|
g.setColor(0.7,0,0);
|
||||||
g.fillRect(rectLen+5,4+20,rectLen+10,13+20);
|
g.fillRect(rectLen+5,4+20,rectLen+10,13+20);
|
||||||
g.setColor(1,1,1);
|
g.setColor(1,1,1);
|
||||||
|
g.setFontAlign(-1, -1, 0);
|
||||||
g.drawString(text.length<=27? text.substr(-27, 27) : '<- '+text.substr(-24,24), 5, 5+20);
|
g.drawString(text.length<=27? text.substr(-27, 27) : '<- '+text.substr(-24,24), 5, 5+20);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawAbcRow();
|
|
||||||
drawNumRow();
|
|
||||||
updateTopString();
|
|
||||||
|
|
||||||
var abcHL;
|
var abcHL;
|
||||||
var abcHLPrev = -10;
|
var abcHLPrev = -10;
|
||||||
var numHL;
|
var numHL;
|
||||||
|
|
@ -70,192 +67,180 @@ exports.input = function(options) {
|
||||||
var largeCharOffset = 6;
|
var largeCharOffset = 6;
|
||||||
|
|
||||||
function resetChars(char, HLPrev, typePadding, heightDivisor, rowColor) {
|
function resetChars(char, HLPrev, typePadding, heightDivisor, rowColor) {
|
||||||
"ram"
|
"ram";
|
||||||
// Small character in list
|
// Small character in list
|
||||||
g.setColor(rowColor);
|
g.setColor(rowColor);
|
||||||
g.setFont(SMALLFONT);
|
g.setFont(SMALLFONT);
|
||||||
g.drawString(char, typePadding + HLPrev*6, g.getHeight()/heightDivisor);
|
g.setFontAlign(-1, -1, 0);
|
||||||
|
g.drawString(char, typePadding + HLPrev*6, (R.y+R.h)/heightDivisor);
|
||||||
// Large character
|
// Large character
|
||||||
g.setColor(BGCOLOR);
|
g.setColor(BGCOLOR);
|
||||||
g.fillRect(0,g.getHeight()/3,176,g.getHeight()/3+24);
|
g.fillRect(0,(R.y+R.h)/3,176,(R.y+R.h)/3+24);
|
||||||
//g.drawString(charSet.charAt(HLPrev), typePadding + HLPrev*6 -largeCharOffset, g.getHeight()/3);; //Old implementation where I find the shape and place of letter to remove instead of just a rectangle.
|
//g.drawString(charSet.charAt(HLPrev), typePadding + HLPrev*6 -largeCharOffset, (R.y+R.h)/3);; //Old implementation where I find the shape and place of letter to remove instead of just a rectangle.
|
||||||
// mark in the list
|
// mark in the list
|
||||||
}
|
}
|
||||||
function showChars(char, HL, typePadding, heightDivisor) {
|
function showChars(char, HL, typePadding, heightDivisor) {
|
||||||
"ram"
|
"ram";
|
||||||
// mark in the list
|
// mark in the list
|
||||||
g.setColor(HLCOLOR);
|
g.setColor(HLCOLOR);
|
||||||
g.setFont(SMALLFONT);
|
g.setFont(SMALLFONT);
|
||||||
if (char != 'del' && char != 'space') g.drawString(char, typePadding + HL*6, g.getHeight()/heightDivisor);
|
g.setFontAlign(-1, -1, 0);
|
||||||
|
if (char != 'del' && char != 'space') g.drawString(char, typePadding + HL*6, (R.y+R.h)/heightDivisor);
|
||||||
// show new large character
|
// show new large character
|
||||||
g.setFont(BIGFONT);
|
g.setFont(BIGFONT);
|
||||||
g.drawString(char, typePadding + HL*6 -largeCharOffset, g.getHeight()/3);
|
g.drawString(char, typePadding + HL*6 -largeCharOffset, (R.y+R.h)/3);
|
||||||
g.setFont(SMALLFONT);
|
g.setFont(SMALLFONT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initDraw() {
|
||||||
|
//var R = Bangle.appRect; // To make sure it's properly updated. Not sure if this is needed.
|
||||||
|
drawAbcRow();
|
||||||
|
drawNumRow();
|
||||||
|
updateTopString();
|
||||||
|
}
|
||||||
|
initDraw();
|
||||||
|
//setTimeout(initDraw, 0); // So Bangle.appRect reads the correct environment. It would draw off to the side sometimes otherwise.
|
||||||
|
|
||||||
function changeCase(abcHL) {
|
function changeCase(abcHL) {
|
||||||
g.setColor(BGCOLOR);
|
g.setColor(BGCOLOR);
|
||||||
g.drawString(ABC, ABCPADDING, g.getHeight()/2);
|
g.setFontAlign(-1, -1, 0);
|
||||||
|
g.drawString(ABC, ABCPADDING, (R.y+R.h)/2);
|
||||||
if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) ABC = ABC.toLowerCase();
|
if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) ABC = ABC.toLowerCase();
|
||||||
else ABC = ABC.toUpperCase();
|
else ABC = ABC.toUpperCase();
|
||||||
g.setColor(ABCCOLOR);
|
g.setColor(ABCCOLOR);
|
||||||
g.drawString(ABC, ABCPADDING, g.getHeight()/2);
|
g.drawString(ABC, ABCPADDING, (R.y+R.h)/2);
|
||||||
}
|
}
|
||||||
return new Promise((resolve,reject) => {
|
return new Promise((resolve,reject) => {
|
||||||
// Interpret touch input
|
// Interpret touch input
|
||||||
Bangle.setUI({
|
Bangle.setUI({
|
||||||
mode: 'custom',
|
mode: 'custom',
|
||||||
back: ()=>{
|
back: ()=>{
|
||||||
Bangle.setUI();
|
Bangle.setUI();
|
||||||
g.clearRect(Bangle.appRect);
|
g.clearRect(Bangle.appRect);
|
||||||
resolve(text);
|
resolve(text);
|
||||||
},
|
},
|
||||||
drag: function(event) {
|
drag: function(event) {
|
||||||
|
"ram";
|
||||||
|
// ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||||
|
// Choose character by draging along red rectangle at bottom of screen
|
||||||
|
if (event.y >= ( (R.y+R.h) - 12 )) {
|
||||||
|
// Translate x-position to character
|
||||||
|
if (event.x < ABCPADDING) { abcHL = 0; }
|
||||||
|
else if (event.x >= 176-ABCPADDING) { abcHL = 25; }
|
||||||
|
else { abcHL = Math.floor((event.x-ABCPADDING)/6); }
|
||||||
|
|
||||||
// ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
// Datastream for development purposes
|
||||||
// Choose character by draging along red rectangle at bottom of screen
|
//print(event.x, event.y, event.b, ABC.charAt(abcHL), ABC.charAt(abcHLPrev));
|
||||||
if (event.y >= ( g.getHeight() - 12 )) {
|
|
||||||
// Translate x-position to character
|
|
||||||
if (event.x < ABCPADDING) { abcHL = 0; }
|
|
||||||
else if (event.x >= 176-ABCPADDING) { abcHL = 25; }
|
|
||||||
else { abcHL = Math.floor((event.x-ABCPADDING)/6); }
|
|
||||||
|
|
||||||
// Datastream for development purposes
|
// Unmark previous character and mark the current one...
|
||||||
//print(event.x, event.y, event.b, ABC.charAt(abcHL), ABC.charAt(abcHLPrev));
|
// Handling switching between letters and numbers/punctuation
|
||||||
|
if (typePrev != 'abc') resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
||||||
|
|
||||||
// Unmark previous character and mark the current one...
|
if (abcHL != abcHLPrev) {
|
||||||
// Handling switching between letters and numbers/punctuation
|
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
||||||
if (typePrev != 'abc') resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
showChars(ABC.charAt(abcHL), abcHL, ABCPADDING, 2);
|
||||||
|
|
||||||
if (abcHL != abcHLPrev) {
|
|
||||||
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
|
||||||
showChars(ABC.charAt(abcHL), abcHL, ABCPADDING, 2);
|
|
||||||
}
|
}
|
||||||
// Print string at top of screen
|
// Print string at top of screen
|
||||||
if (event.b == 0) {
|
if (event.b == 0) {
|
||||||
text = text + ABC.charAt(abcHL);
|
text = text + ABC.charAt(abcHL);
|
||||||
updateTopString();
|
|
||||||
|
|
||||||
// Autoswitching letter case
|
|
||||||
if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) changeCase(abcHL);
|
|
||||||
}
|
|
||||||
// Update previous character to current one
|
|
||||||
abcHLPrev = abcHL;
|
|
||||||
typePrev = 'abc';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 12345678901234567890
|
|
||||||
// Choose number or puctuation by draging on green rectangle
|
|
||||||
else if ((event.y < ( g.getHeight() - 12 )) && (event.y > ( g.getHeight() - 52 ))) {
|
|
||||||
// Translate x-position to character
|
|
||||||
if (event.x < NUMPADDING) { numHL = 0; }
|
|
||||||
else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; }
|
|
||||||
else { numHL = Math.floor((event.x-NUMPADDING)/6); }
|
|
||||||
|
|
||||||
// Datastream for development purposes
|
|
||||||
//print(event.x, event.y, event.b, NUM.charAt(numHL), NUM.charAt(numHLPrev));
|
|
||||||
|
|
||||||
// Unmark previous character and mark the current one...
|
|
||||||
// Handling switching between letters and numbers/punctuation
|
|
||||||
if (typePrev != 'num') resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
|
||||||
|
|
||||||
if (numHL != numHLPrev) {
|
|
||||||
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
|
||||||
showChars(NUM.charAt(numHL), numHL, NUMPADDING, 4);
|
|
||||||
}
|
|
||||||
// Print string at top of screen
|
|
||||||
if (event.b == 0) {
|
|
||||||
g.setColor(HLCOLOR);
|
|
||||||
// Backspace if releasing before list of numbers/punctuation
|
|
||||||
if (event.x < NUMPADDING) {
|
|
||||||
// show delete sign
|
|
||||||
showChars('del', 0, g.getWidth()/2 +6 -27 , 4);
|
|
||||||
delSpaceLast = 1;
|
|
||||||
text = text.slice(0, -1);
|
|
||||||
updateTopString();
|
|
||||||
//print(text);
|
|
||||||
}
|
|
||||||
// Append space if releasing after list of numbers/punctuation
|
|
||||||
else if (event.x > g.getWidth()-NUMPADDING) {
|
|
||||||
//show space sign
|
|
||||||
showChars('space', 0, g.getWidth()/2 +6 -6*3*5/2 , 4);
|
|
||||||
delSpaceLast = 1;
|
|
||||||
text = text + ' ';
|
|
||||||
updateTopString();
|
|
||||||
//print(text);
|
|
||||||
}
|
|
||||||
// Append selected number/punctuation
|
|
||||||
else {
|
|
||||||
text = text + NUMHIDDEN.charAt(numHL);
|
|
||||||
updateTopString();
|
updateTopString();
|
||||||
|
|
||||||
// Autoswitching letter case
|
// Autoswitching letter case
|
||||||
if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) changeCase();
|
if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) changeCase(abcHL);
|
||||||
}
|
}
|
||||||
|
// Update previous character to current one
|
||||||
|
abcHLPrev = abcHL;
|
||||||
|
typePrev = 'abc';
|
||||||
}
|
}
|
||||||
// Update previous character to current one
|
|
||||||
numHLPrev = numHL;
|
|
||||||
typePrev = 'num';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 12345678901234567890
|
||||||
|
// Choose number or puctuation by draging on green rectangle
|
||||||
|
else if ((event.y < ( (R.y+R.h) - 12 )) && (event.y > ( (R.y+R.h) - 52 ))) {
|
||||||
|
// Translate x-position to character
|
||||||
|
if (event.x < NUMPADDING) { numHL = 0; }
|
||||||
|
else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; }
|
||||||
|
else { numHL = Math.floor((event.x-NUMPADDING)/6); }
|
||||||
|
|
||||||
|
// Datastream for development purposes
|
||||||
|
//print(event.x, event.y, event.b, NUM.charAt(numHL), NUM.charAt(numHLPrev));
|
||||||
|
|
||||||
|
// Unmark previous character and mark the current one...
|
||||||
|
// Handling switching between letters and numbers/punctuation
|
||||||
|
if (typePrev != 'num') resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
||||||
|
|
||||||
|
if (numHL != numHLPrev) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Make a space or backspace by swiping right or left on screen above green rectangle
|
|
||||||
else if (event.y > 20+4) {
|
|
||||||
if (event.b == 0) {
|
|
||||||
g.setColor(HLCOLOR);
|
|
||||||
if (event.x < g.getWidth()/2) {
|
|
||||||
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
|
||||||
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
||||||
|
showChars(NUM.charAt(numHL), numHL, NUMPADDING, 4);
|
||||||
// show delete sign
|
|
||||||
showChars('del', 0, g.getWidth()/2 +6 -27 , 4);
|
|
||||||
delSpaceLast = 1;
|
|
||||||
|
|
||||||
// Backspace and draw string upper right corner
|
|
||||||
text = text.slice(0, -1);
|
|
||||||
updateTopString();
|
|
||||||
if (text.length==0) changeCase(abcHL);
|
|
||||||
//print(text, 'undid');
|
|
||||||
}
|
}
|
||||||
else {
|
// Print string at top of screen
|
||||||
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
if (event.b == 0) {
|
||||||
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
g.setColor(HLCOLOR);
|
||||||
|
// Backspace if releasing before list of numbers/punctuation
|
||||||
|
if (event.x < NUMPADDING) {
|
||||||
|
// show delete sign
|
||||||
|
showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4);
|
||||||
|
delSpaceLast = 1;
|
||||||
|
text = text.slice(0, -1);
|
||||||
|
updateTopString();
|
||||||
|
//print(text);
|
||||||
|
}
|
||||||
|
// Append space if releasing after list of numbers/punctuation
|
||||||
|
else if (event.x > (R.x+R.w)-NUMPADDING) {
|
||||||
|
//show space sign
|
||||||
|
showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4);
|
||||||
|
delSpaceLast = 1;
|
||||||
|
text = text + ' ';
|
||||||
|
updateTopString();
|
||||||
|
//print(text);
|
||||||
|
}
|
||||||
|
// Append selected number/punctuation
|
||||||
|
else {
|
||||||
|
text = text + NUMHIDDEN.charAt(numHL);
|
||||||
|
updateTopString();
|
||||||
|
|
||||||
//show space sign
|
// Autoswitching letter case
|
||||||
showChars('space', 0, g.getWidth()/2 +6 -6*3*5/2 , 4);
|
if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) changeCase();
|
||||||
delSpaceLast = 1;
|
}
|
||||||
|
}
|
||||||
|
// Update previous character to current one
|
||||||
|
numHLPrev = numHL;
|
||||||
|
typePrev = 'num';
|
||||||
|
}
|
||||||
|
|
||||||
// Append space and draw string upper right corner
|
// Make a space or backspace by swiping right or left on screen above green rectangle
|
||||||
text = text + NUMHIDDEN.charAt(0);
|
else if (event.y > 20+4) {
|
||||||
updateTopString();
|
if (event.b == 0) {
|
||||||
//print(text, 'made space');
|
g.setColor(HLCOLOR);
|
||||||
|
if (event.x < (R.x+R.w)/2) {
|
||||||
|
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
||||||
|
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
||||||
|
|
||||||
|
// show delete sign
|
||||||
|
showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4);
|
||||||
|
delSpaceLast = 1;
|
||||||
|
|
||||||
|
// Backspace and draw string upper right corner
|
||||||
|
text = text.slice(0, -1);
|
||||||
|
updateTopString();
|
||||||
|
if (text.length==0) changeCase(abcHL);
|
||||||
|
//print(text, 'undid');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
||||||
|
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
||||||
|
|
||||||
|
//show space sign
|
||||||
|
showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4);
|
||||||
|
delSpaceLast = 1;
|
||||||
|
|
||||||
|
// Append space and draw string upper right corner
|
||||||
|
text = text + NUMHIDDEN.charAt(0);
|
||||||
|
updateTopString();
|
||||||
|
//print(text, 'made space');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
/* return new Promise((resolve,reject) => {
|
|
||||||
Bangle.setUI({mode:"custom", back:()=>{
|
|
||||||
Bangle.setUI();
|
|
||||||
g.clearRect(Bangle.appRect);
|
|
||||||
Bangle.setUI();
|
|
||||||
resolve(text);
|
|
||||||
}});
|
|
||||||
}); */
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{ "id": "dragboard",
|
{ "id": "dragboard",
|
||||||
"name": "Dragboard",
|
"name": "Dragboard",
|
||||||
"version":"0.05",
|
"version":"0.06",
|
||||||
"description": "A library for text input via swiping keyboard",
|
"description": "A library for text input via swiping keyboard",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type":"textinput",
|
"type":"textinput",
|
||||||
|
|
|
||||||
|
|
@ -14,3 +14,8 @@
|
||||||
0.14: Don't move pages when doing exit swipe - Bangle 2.
|
0.14: Don't move pages when doing exit swipe - Bangle 2.
|
||||||
0.15: 'Swipe to exit'-code is slightly altered to be more reliable - Bangle 2.
|
0.15: 'Swipe to exit'-code is slightly altered to be more reliable - Bangle 2.
|
||||||
0.16: Use default Bangle formatter for booleans
|
0.16: Use default Bangle formatter for booleans
|
||||||
|
0.17: Bangle 2: Fast loading on exit to clock face. Added option for exit to
|
||||||
|
clock face by timeout.
|
||||||
|
0.18: Move interactions inside setUI. Replace "one click exit" with
|
||||||
|
back-functionality through setUI, adding the red back button as well. Hardware
|
||||||
|
button to exit is no longer an option.
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,27 @@
|
||||||
|
{ // must be inside our own scope here so that when we are unloaded everything disappears
|
||||||
|
|
||||||
/* Desktop launcher
|
/* Desktop launcher
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var settings = Object.assign({
|
let settings = Object.assign({
|
||||||
showClocks: true,
|
showClocks: true,
|
||||||
showLaunchers: true,
|
showLaunchers: true,
|
||||||
direct: false,
|
direct: false,
|
||||||
oneClickExit:false,
|
swipeExit: false,
|
||||||
swipeExit: false
|
timeOut: "Off"
|
||||||
}, require('Storage').readJSON("dtlaunch.json", true) || {});
|
}, require('Storage').readJSON("dtlaunch.json", true) || {});
|
||||||
|
|
||||||
if( settings.oneClickExit)
|
let s = require("Storage");
|
||||||
setWatch(_=> load(), BTN1);
|
var apps = s.list(/\.info$/).map(app=>{
|
||||||
|
let a=s.readJSON(app,1);
|
||||||
var s = require("Storage");
|
|
||||||
var apps = s.list(/\.info$/).map(app=>{
|
|
||||||
var a=s.readJSON(app,1);
|
|
||||||
return a && {
|
return a && {
|
||||||
name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src
|
name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src
|
||||||
};}).filter(
|
};}).filter(
|
||||||
app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type));
|
app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type));
|
||||||
|
|
||||||
apps.sort((a,b)=>{
|
apps.sort((a,b)=>{
|
||||||
var n=(0|a.sortorder)-(0|b.sortorder);
|
let n=(0|a.sortorder)-(0|b.sortorder);
|
||||||
if (n) return n; // do sortorder first
|
if (n) return n; // do sortorder first
|
||||||
if (a.name<b.name) return -1;
|
if (a.name<b.name) return -1;
|
||||||
if (a.name>b.name) return 1;
|
if (a.name>b.name) return 1;
|
||||||
|
|
@ -33,29 +32,28 @@ apps.forEach(app=>{
|
||||||
app.icon = s.read(app.icon); // should just be a link to a memory area
|
app.icon = s.read(app.icon); // should just be a link to a memory area
|
||||||
});
|
});
|
||||||
|
|
||||||
var Napps = apps.length;
|
let Napps = apps.length;
|
||||||
var Npages = Math.ceil(Napps/4);
|
let Npages = Math.ceil(Napps/4);
|
||||||
var maxPage = Npages-1;
|
let maxPage = Npages-1;
|
||||||
var selected = -1;
|
let selected = -1;
|
||||||
var oldselected = -1;
|
let oldselected = -1;
|
||||||
var page = 0;
|
let page = 0;
|
||||||
const XOFF = 24;
|
const XOFF = 24;
|
||||||
const YOFF = 30;
|
const YOFF = 30;
|
||||||
|
|
||||||
function draw_icon(p,n,selected) {
|
let drawIcon= function(p,n,selected) {
|
||||||
var x = (n%2)*72+XOFF;
|
let x = (n%2)*72+XOFF;
|
||||||
var y = n>1?72+YOFF:YOFF;
|
let y = n>1?72+YOFF:YOFF;
|
||||||
(selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+11,y+3,x+60,y+52);
|
(selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+11,y+3,x+60,y+52);
|
||||||
g.clearRect(x+12,y+4,x+59,y+51);
|
g.clearRect(x+12,y+4,x+59,y+51);
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){}
|
try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){}
|
||||||
g.setFontAlign(0,-1,0).setFont("6x8",1);
|
g.setFontAlign(0,-1,0).setFont("6x8",1);
|
||||||
var txt = apps[p*4+n].name.replace(/([a-z])([A-Z])/g, "$1 $2").split(" ");
|
let txt = apps[p*4+n].name.replace(/([a-z])([A-Z])/g, "$1 $2").split(" ");
|
||||||
var lineY = 0;
|
let lineY = 0;
|
||||||
var line = "";
|
let line = "";
|
||||||
while (txt.length > 0){
|
while (txt.length > 0){
|
||||||
var c = txt.shift();
|
let c = txt.shift();
|
||||||
|
|
||||||
if (c.length + 1 + line.length > 13){
|
if (c.length + 1 + line.length > 13){
|
||||||
if (line.length > 0){
|
if (line.length > 0){
|
||||||
g.drawString(line.trim(),x+36,y+54+lineY*8);
|
g.drawString(line.trim(),x+36,y+54+lineY*8);
|
||||||
|
|
@ -67,29 +65,34 @@ function draw_icon(p,n,selected) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.drawString(line.trim(),x+36,y+54+lineY*8);
|
g.drawString(line.trim(),x+36,y+54+lineY*8);
|
||||||
}
|
};
|
||||||
|
|
||||||
function drawPage(p){
|
let drawPage = function(p){
|
||||||
g.reset();
|
g.reset();
|
||||||
g.clearRect(0,24,175,175);
|
g.clearRect(0,24,175,175);
|
||||||
var O = 88+YOFF/2-12*(Npages/2);
|
let O = 88+YOFF/2-12*(Npages/2);
|
||||||
for (var j=0;j<Npages;j++){
|
for (let j=0;j<Npages;j++){
|
||||||
var y = O+j*12;
|
let y = O+j*12;
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
if (j==page) g.fillCircle(XOFF/2,y,4);
|
if (j==page) g.fillCircle(XOFF/2,y,4);
|
||||||
else g.drawCircle(XOFF/2,y,4);
|
else g.drawCircle(XOFF/2,y,4);
|
||||||
}
|
}
|
||||||
for (var i=0;i<4;i++) {
|
for (let i=0;i<4;i++) {
|
||||||
if (!apps[p*4+i]) return i;
|
if (!apps[p*4+i]) return i;
|
||||||
draw_icon(p,i,selected==i && !settings.direct);
|
drawIcon(p,i,selected==i && !settings.direct);
|
||||||
}
|
}
|
||||||
g.flip();
|
g.flip();
|
||||||
}
|
};
|
||||||
|
|
||||||
Bangle.on("swipe",(dirLeftRight, dirUpDown)=>{
|
Bangle.loadWidgets();
|
||||||
|
//g.clear();
|
||||||
|
//Bangle.drawWidgets();
|
||||||
|
drawPage(0);
|
||||||
|
|
||||||
|
let swipeListenerDt = function(dirLeftRight, dirUpDown){
|
||||||
selected = 0;
|
selected = 0;
|
||||||
oldselected=-1;
|
oldselected=-1;
|
||||||
if(settings.swipeExit && dirLeftRight==1) load();
|
if(settings.swipeExit && dirLeftRight==1) returnToClock();
|
||||||
if (dirUpDown==-1||dirLeftRight==-1){
|
if (dirUpDown==-1||dirLeftRight==-1){
|
||||||
++page; if (page>maxPage) page=0;
|
++page; if (page>maxPage) page=0;
|
||||||
drawPage(page);
|
drawPage(page);
|
||||||
|
|
@ -97,24 +100,24 @@ Bangle.on("swipe",(dirLeftRight, dirUpDown)=>{
|
||||||
--page; if (page<0) page=maxPage;
|
--page; if (page<0) page=maxPage;
|
||||||
drawPage(page);
|
drawPage(page);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
function isTouched(p,n){
|
let isTouched = function(p,n){
|
||||||
if (n<0 || n>3) return false;
|
if (n<0 || n>3) return false;
|
||||||
var x1 = (n%2)*72+XOFF; var y1 = n>1?72+YOFF:YOFF;
|
let x1 = (n%2)*72+XOFF; let y1 = n>1?72+YOFF:YOFF;
|
||||||
var x2 = x1+71; var y2 = y1+81;
|
let x2 = x1+71; let y2 = y1+81;
|
||||||
return (p.x>x1 && p.y>y1 && p.x<x2 && p.y<y2);
|
return (p.x>x1 && p.y>y1 && p.x<x2 && p.y<y2);
|
||||||
}
|
};
|
||||||
|
|
||||||
Bangle.on("touch",(_,p)=>{
|
let touchListenerDt = function(_,p){
|
||||||
var i;
|
let i;
|
||||||
for (i=0;i<4;i++){
|
for (i=0;i<4;i++){
|
||||||
if((page*4+i)<Napps){
|
if((page*4+i)<Napps){
|
||||||
if (isTouched(p,i)) {
|
if (isTouched(p,i)) {
|
||||||
draw_icon(page,i,true && !settings.direct);
|
drawIcon(page,i,true && !settings.direct);
|
||||||
if (selected>=0 || settings.direct) {
|
if (selected>=0 || settings.direct) {
|
||||||
if (selected!=i && !settings.direct){
|
if (selected!=i && !settings.direct){
|
||||||
draw_icon(page,selected,false);
|
drawIcon(page,selected,false);
|
||||||
} else {
|
} else {
|
||||||
load(apps[page*4+i].src);
|
load(apps[page*4+i].src);
|
||||||
}
|
}
|
||||||
|
|
@ -125,12 +128,32 @@ Bangle.on("touch",(_,p)=>{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((i==4 || (page*4+i)>Napps) && selected>=0) {
|
if ((i==4 || (page*4+i)>Napps) && selected>=0) {
|
||||||
draw_icon(page,selected,false);
|
drawIcon(page,selected,false);
|
||||||
selected=-1;
|
selected=-1;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const returnToClock = function() {
|
||||||
|
Bangle.setUI();
|
||||||
|
setTimeout(eval, 0, s.read(".bootcde"));
|
||||||
|
};
|
||||||
|
|
||||||
|
Bangle.setUI({
|
||||||
|
mode : 'custom',
|
||||||
|
back : returnToClock,
|
||||||
|
swipe : swipeListenerDt,
|
||||||
|
touch : touchListenerDt
|
||||||
});
|
});
|
||||||
|
|
||||||
Bangle.loadWidgets();
|
// taken from Icon Launcher with minor alterations
|
||||||
g.clear();
|
var timeoutToClock;
|
||||||
Bangle.drawWidgets();
|
const updateTimeoutToClock = function(){
|
||||||
drawPage(0);
|
if (settings.timeOut!="Off"){
|
||||||
|
let time=parseInt(settings.timeOut); //the "s" will be trimmed by the parseInt
|
||||||
|
if (timeoutToClock) clearTimeout(timeoutToClock);
|
||||||
|
timeoutToClock = setTimeout(returnToClock,time*1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
updateTimeoutToClock();
|
||||||
|
|
||||||
|
} // end of app scope
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "dtlaunch",
|
"id": "dtlaunch",
|
||||||
"name": "Desktop Launcher",
|
"name": "Desktop Launcher",
|
||||||
"version": "0.16",
|
"version": "0.18",
|
||||||
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
|
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
|
||||||
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
|
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
|
|
|
||||||
|
|
@ -5,51 +5,56 @@
|
||||||
showClocks: true,
|
showClocks: true,
|
||||||
showLaunchers: true,
|
showLaunchers: true,
|
||||||
direct: false,
|
direct: false,
|
||||||
oneClickExit:false,
|
swipeExit: false,
|
||||||
swipeExit: false
|
timeOut: "Off"
|
||||||
}, require('Storage').readJSON(FILE, true) || {});
|
}, require('Storage').readJSON(FILE, true) || {});
|
||||||
|
|
||||||
function writeSettings() {
|
function writeSettings() {
|
||||||
require('Storage').writeJSON(FILE, settings);
|
require('Storage').writeJSON(FILE, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const timeOutChoices = [/*LANG*/"Off", "10s", "15s", "20s", "30s"];
|
||||||
|
|
||||||
E.showMenu({
|
E.showMenu({
|
||||||
"" : { "title" : "Desktop launcher" },
|
"" : { "title" : "Desktop launcher" },
|
||||||
"< Back" : () => back(),
|
/*LANG*/"< Back" : () => back(),
|
||||||
'Show clocks': {
|
/*LANG*/'Show clocks': {
|
||||||
value: settings.showClocks,
|
value: settings.showClocks,
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.showClocks = v;
|
settings.showClocks = v;
|
||||||
writeSettings();
|
writeSettings();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Show launchers': {
|
/*LANG*/'Show launchers': {
|
||||||
value: settings.showLaunchers,
|
value: settings.showLaunchers,
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.showLaunchers = v;
|
settings.showLaunchers = v;
|
||||||
writeSettings();
|
writeSettings();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Direct launch': {
|
/*LANG*/'Direct launch': {
|
||||||
value: settings.direct,
|
value: settings.direct,
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.direct = v;
|
settings.direct = v;
|
||||||
writeSettings();
|
writeSettings();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Swipe Exit': {
|
/*LANG*/'Swipe Exit': {
|
||||||
value: settings.swipeExit,
|
value: settings.swipeExit,
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.swipeExit = v;
|
settings.swipeExit = v;
|
||||||
writeSettings();
|
writeSettings();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'One click exit': {
|
/*LANG*/'Time Out': { // Adapted from Icon Launcher
|
||||||
value: settings.oneClickExit,
|
value: timeOutChoices.indexOf(settings.timeOut),
|
||||||
|
min: 0,
|
||||||
|
max: timeOutChoices.length-1,
|
||||||
|
format: v => timeOutChoices[v],
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.oneClickExit = v;
|
settings.timeOut = timeOutChoices[v];
|
||||||
writeSettings();
|
writeSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
0.1: New App!
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
Enton - Enhanced Anton Clock
|
||||||
|
|
||||||
|
This clock face is based on the 'Anton Clock'.
|
||||||
|
|
||||||
|
Things I changed:
|
||||||
|
|
||||||
|
- The main font for the time is now Audiowide
|
||||||
|
- Removed the written out day name and replaced it with steps and bpm
|
||||||
|
- Changed the date string to a (for me) more readable string
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwkE/4A/AH4A/AH4A/AH4Aw+cikf/mQDCAAIFBAwQDBBYgXCgEDAQIABn4JBkAFBgIKDgQwFmMD+UCmcgl/zEIMzmcQmYKBmYiCAAfxC4QrBl8wBwcgkYsGC4sAiMAF4UxiIGBn8QAgMSC48wgMRiEDBAISCiYcFC48v//yC4PzgJAGiAXIiczPgPzC4JyBmf/AYQXI+KcCj8wmYFCgEjAYQ3G+cjbQIABJIMzAoUin7XIADpSEK4rWGI4MhmRJBn8j+U/d4MimUTkUzIw5dBl4UBMgIXBAgMyLYKOBmQXHiSbCDgMyl8z+UjmJ1BHgJbHCgM/IYQABAgQJBYYYA/AH4AtaQU/mTvBBozWBd44KBkUSkLnBEo8jkcvBI0/CgMiDAIXHHYIXImUzJQJHH+Y+Bn6Z/ABQA=="))
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
Graphics.prototype.setFontAudiowide = function() {
|
||||||
|
// Actual height 33 (36 - 4)
|
||||||
|
var widths = atob("CiAsESQjJSQkHyQkDA==");
|
||||||
|
var font = atob("AAAAAAAAAAAAAAAAAAAAAPAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAADgAAAAAAHgAAAAAAfgAAAAAA/gAAAAAD/gAAAAAH/gAAAAAf/AAAAAB/8AAAAAD/4AAAAAP/gAAAAAf/AAAAAB/8AAAAAD/4AAAAAP/gAAAAAf+AAAAAB/8AAAAAH/wAAAAAP/gAAAAA/+AAAAAB/8AAAAAD/wAAAAAD/gAAAAAD+AAAAAAD4AAAAAADwAAAAAADAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAA//+AAAAB///AAAAH///wAAAP///4AAAf///8AAA////+AAA/4AP+AAB/gAD/AAB/AA9/AAD+AB+/gAD+AD+/gAD+AD+/gAD8AH+fgAD8AP8fgAD8AP4fgAD8Af4fgAD8A/wfgAD8A/gfgAD8B/gfgAD8D/AfgAD8D+AfgAD8H+AfgAD8P8AfgAD8P4AfgAD8f4AfgAD8/wAfgAD8/gAfgAD+/gA/gAD+/AA/gAB/eAB/AAB/sAD/AAB/wAH/AAA////+AAAf///8AAAP///4AAAH///wAAAD///gAAAA//+AAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//gAAAAH//gAAAAP//gAD8Af//gAD8A///gAD8B///gAD8B///gAD8B/AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+D+AfgAD//+AfgAD//+AfgAB//8AfgAA//4AfgAAf/wAfgAAP/gAfgAAB8AAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+B+A/gAD/////gAB/////AAB/////AAA////+AAAf///8AAAP///4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//4AAAAD//8AAAAD//+AAAAD//+AAAAD//+AAAAD//+AAAAD//+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//AAfgAD//wAfgAD//4AfgAD//8AfgAD//8AfgAD//+AfgAD8D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B/A/gAD8B///gAD8B///gAD8A///AAD8A///AAAAAf/+AAAAAP/4AAAAAD/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///AAAAH///wAAAf///8AAAf///8AAA////+AAB/////AAB/h+H/AAD/B+B/gAD+B+A/gAD+B+A/gAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B/A/gAD8B///gAD8B///gAD8A///AAAAAf//AAAAAf/+AAAAAH/4AAAAAB/gAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAgAD8AAABgAD8AAAHgAD8AAAfgAD8AAA/gAD8AAD/gAD8AAP/gAD8AA//gAD8AB//AAD8AH/8AAD8Af/wAAD8A//AAAD8D/+AAAD8P/4AAAD8f/gAAAD9//AAAAD//8AAAAD//wAAAAD//gAAAAD/+AAAAAD/4AAAAAD/wAAAAAD/AAAAAAD8AAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAH/4AAAAAP/8AAAH+f/+AAAf////AAA/////gAB/////gAB///A/gAD//+AfgAD//+AfgAD+D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+D+AfgAD//+AfgAD//+AfgAB///A/gAB/////gAA/////AAAP////AAAD+f/+AAAAAP/8AAAAAH/4AAAAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/AAAAAAf/wAAAAA//4AAAAB//8AAAAB//8AfgAD//+AfgAD/D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+B+A/gAD+B+A/gAD/B+B/gAB/////AAB/////AAA////+AAAf///8AAAP///4AAAH///wAAAB///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAPAAAA/AAfgAAA/AAfgAAA/AAfgAAA/AAfgAAAeAAPAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
||||||
|
var scale = 1; // size multiplier for this font
|
||||||
|
g.setFontCustom(font, 46, widths, 48+(scale<<8)+(1<<16));
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSteps() {
|
||||||
|
var steps = 0;
|
||||||
|
try{
|
||||||
|
if (WIDGETS.wpedom !== undefined) {
|
||||||
|
steps = WIDGETS.wpedom.getSteps();
|
||||||
|
} else if (WIDGETS.activepedom !== undefined) {
|
||||||
|
steps = WIDGETS.activepedom.getSteps();
|
||||||
|
} else {
|
||||||
|
steps = Bangle.getHealthStatus("day").steps;
|
||||||
|
}
|
||||||
|
} catch(ex) {
|
||||||
|
// In case we failed, we can only show 0 steps.
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.round(steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // must be inside our own scope here so that when we are unloaded everything disappears
|
||||||
|
// we also define functions using 'let fn = function() {..}' for the same reason. function decls are global
|
||||||
|
let drawTimeout;
|
||||||
|
|
||||||
|
|
||||||
|
// Actually draw the watch face
|
||||||
|
let draw = function() {
|
||||||
|
var x = g.getWidth() / 2;
|
||||||
|
var y = g.getHeight() / 2;
|
||||||
|
g.reset().clearRect(Bangle.appRect); // clear whole background (w/o widgets)
|
||||||
|
var date = new Date();
|
||||||
|
var timeStr = require("locale").time(date, 1); // Hour and minute
|
||||||
|
g.setFontAlign(0, 0).setFont("Audiowide").drawString(timeStr, x, y);
|
||||||
|
var dateStr = require("locale").date(date, 1).toUpperCase();
|
||||||
|
g.setFontAlign(0, 0).setFont("6x8", 2).drawString(dateStr, x, y+28);
|
||||||
|
g.setFontAlign(0, 0).setFont("6x8", 2);
|
||||||
|
g.drawString(getSteps(), 50, y+70);
|
||||||
|
g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), g.getWidth() -37, y + 70);
|
||||||
|
|
||||||
|
// queue next draw
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = setTimeout(function() {
|
||||||
|
drawTimeout = undefined;
|
||||||
|
draw();
|
||||||
|
}, 60000 - (Date.now() % 60000));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Show launcher when middle button pressed
|
||||||
|
Bangle.setUI({
|
||||||
|
mode : "clock",
|
||||||
|
remove : function() {
|
||||||
|
// Called to unload all of the clock app
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = undefined;
|
||||||
|
delete Graphics.prototype.setFontAnton;
|
||||||
|
}});
|
||||||
|
// Load widgets
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
draw();
|
||||||
|
setTimeout(Bangle.drawWidgets,0);
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 905 B |
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"id": "entonclk",
|
||||||
|
"name": "Enton Clock",
|
||||||
|
"version": "0.1",
|
||||||
|
"description": "A simple clock using the Audiowide font. ",
|
||||||
|
"icon": "app.png",
|
||||||
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
"type": "clock",
|
||||||
|
"tags": "clock",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"allow_emulator": true,
|
||||||
|
"readme":"README.md",
|
||||||
|
"storage": [
|
||||||
|
{"name":"entonclk.app.js","url":"app.js"},
|
||||||
|
{"name":"entonclk.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
|
|
@ -0,0 +1,2 @@
|
||||||
|
0.01: New app!
|
||||||
|
0.02: Submitted to app loader
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Gallery
|
||||||
|
|
||||||
|
A simple gallery app
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Upon opening the gallery app, you will be presented with a list of images that you can display. Tap the image to show it. Brightness will be set to full, and the screen timeout will be disabled. When you are done viewing the image, you can tap the screen to go back to the list of images. Press BTN1 to flip the image upside down.
|
||||||
|
|
||||||
|
## Adding images
|
||||||
|
|
||||||
|
1. The gallery app does not perform any scaling, and does not support panning. Therefore, you should use your favorite image editor to produce an image of the appropriate size for your watch. (240x240 for Bangle 1 or 176x176 for Bangle 2.) How you achieve this is up to you. If on a Bangle 2, I recommend adjusting the colors here to comply with the color restrictions.
|
||||||
|
|
||||||
|
2. Upload your image to the [Espruino image converter](https://www.espruino.com/Image+Converter). I recommend enabling compression and choosing one of the following color settings:
|
||||||
|
* 16 bit RGB565 for Bangle 1
|
||||||
|
* 3 bit RGB for Bangle 2
|
||||||
|
* 1 bit black/white for monochrome images that you want to respond to your system theme. (White will be rendered as your foreground color and black will be rendered as your background color.)
|
||||||
|
|
||||||
|
3. Set the output format to an image string, copy it into the [IDE](https://www.espruino.com/ide/), and set the destination to a file in storage. The file name should begin with "gal-" (without the quotes) and end with ".img" (without the quotes) to appear in the gallery. Note that the gal- prefix and .img extension will be removed in the UI. Upload the file.
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
const storage = require('Storage');
|
||||||
|
|
||||||
|
let imageFiles = storage.list(/^gal-.*\.img/).sort();
|
||||||
|
|
||||||
|
let imageMenu = { '': { 'title': 'Gallery' } };
|
||||||
|
|
||||||
|
for (let fileName of imageFiles) {
|
||||||
|
let displayName = fileName.substr(4, fileName.length - 8); // Trim off the 'gal-' and '.img' for a friendly display name
|
||||||
|
imageMenu[displayName] = eval(`() => { drawImage("${fileName}"); }`); // Unfortunately, eval is the only reasonable way to do this
|
||||||
|
}
|
||||||
|
|
||||||
|
let cachedOptions = Bangle.getOptions(); // We will change the backlight and timeouts later, and need to restore them when displaying the menu
|
||||||
|
let backlightSetting = storage.readJSON('setting.json').brightness; // LCD brightness is not included in there for some reason
|
||||||
|
|
||||||
|
let angle = 0; // Store the angle of rotation
|
||||||
|
let image; // Cache the image here because we access it in multiple places
|
||||||
|
|
||||||
|
function drawMenu() {
|
||||||
|
Bangle.removeListener('touch', drawMenu); // We no longer want touching to reload the menu
|
||||||
|
Bangle.setOptions(cachedOptions); // The drawImage function set no timeout, undo that
|
||||||
|
Bangle.setLCDBrightness(backlightSetting); // Restore backlight
|
||||||
|
image = undefined; // Delete the image from memory
|
||||||
|
|
||||||
|
E.showMenu(imageMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawImage(fileName) {
|
||||||
|
E.showMenu(); // Remove the menu to prevent it from breaking things
|
||||||
|
setTimeout(() => { Bangle.on('touch', drawMenu); }, 300); // Touch the screen to go back to the image menu (300ms timeout to allow user to lift finger)
|
||||||
|
Bangle.setOptions({ // Disable display power saving while showing the image
|
||||||
|
lockTimeout: 0,
|
||||||
|
lcdPowerTimeout: 0,
|
||||||
|
backlightTimeout: 0
|
||||||
|
});
|
||||||
|
Bangle.setLCDBrightness(1); // Full brightness
|
||||||
|
|
||||||
|
image = eval(storage.read(fileName)); // Sadly, the only reasonable way to do this
|
||||||
|
g.clear().reset().drawImage(image, 88, 88, { rotate: angle });
|
||||||
|
}
|
||||||
|
|
||||||
|
setWatch(info => {
|
||||||
|
if (image) {
|
||||||
|
if (angle == 0) angle = Math.PI;
|
||||||
|
else angle = 0;
|
||||||
|
Bangle.buzz();
|
||||||
|
|
||||||
|
g.clear().reset().drawImage(image, 88, 88, { rotate: angle })
|
||||||
|
}
|
||||||
|
}, BTN1, { repeat: true });
|
||||||
|
|
||||||
|
// We don't load the widgets because there is no reasonable way to unload them
|
||||||
|
drawMenu();
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwgIOLgf/AAX8Av4FBJgkMAos/CIfMAv4Fe4AF/Apq5EAAw"))
|
||||||
|
After Width: | Height: | Size: 249 B |
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"id": "gallery",
|
||||||
|
"name": "Gallery",
|
||||||
|
"version": "0.02",
|
||||||
|
"description": "A gallery that lets you view images uploaded with the IDE (see README)",
|
||||||
|
"readme": "README.md",
|
||||||
|
"icon": "icon.png",
|
||||||
|
"type": "app",
|
||||||
|
"tags": "tools",
|
||||||
|
"supports": [
|
||||||
|
"BANGLEJS2",
|
||||||
|
"BANGLEJS"
|
||||||
|
],
|
||||||
|
"allow_emulator": true,
|
||||||
|
"storage": [
|
||||||
|
{
|
||||||
|
"name": "gallery.app.js",
|
||||||
|
"url": "app.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gallery.img",
|
||||||
|
"url": "icon.js",
|
||||||
|
"evaluate": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
E.showMenu({
|
E.showMenu({
|
||||||
"" : { "title" : "GPS auto time" },
|
"" : { "title" : "GPS auto time" },
|
||||||
"< Back" : () => back(),
|
"< Back" : () => back(),
|
||||||
'Show Widgets': {
|
'Show Widget': {
|
||||||
value: !!settings.show,
|
value: !!settings.show,
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.show = v;
|
settings.show = v;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
delete settings;
|
delete settings;
|
||||||
|
|
||||||
Bangle.on('GPS',function(fix) {
|
Bangle.on('GPS',function(fix) {
|
||||||
if (fix.fix) {
|
if (fix.fix && fix.time) {
|
||||||
var curTime = fix.time.getTime()/1000;
|
var curTime = fix.time.getTime()/1000;
|
||||||
setTime(curTime);
|
setTime(curTime);
|
||||||
lastTimeSet = curTime;
|
lastTimeSet = curTime;
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,10 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Make selection of background activity more explicit
|
0.02: Make selection of background activity more explicit
|
||||||
|
0.03: Fix listener for accel always active
|
||||||
|
Use custom UI with swipes instead of leftright
|
||||||
|
0.04: Fix compass heading
|
||||||
|
0.05: Added adjustment for Bangle.js magnetometer heading fix
|
||||||
|
0.06: Fix waypoint menu always selecting last waypoint
|
||||||
|
Fix widget adding listeners more than once
|
||||||
|
0.07: Show checkered flag for target markers
|
||||||
|
Single waypoints are now shown in the compass view
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,12 @@ Tapping or button to switch to the next information display, swipe right for the
|
||||||
|
|
||||||
Choose either a route or a waypoint as basis for the display.
|
Choose either a route or a waypoint as basis for the display.
|
||||||
|
|
||||||
After this selection and availability of a GPS fix the compass will show a blue dot for your destination and a green one for possibly available waypoints on the way.
|
After this selection and availability of a GPS fix the compass will show a checkered flag for your destination and a green dot for possibly available waypoints on the way.
|
||||||
Waypoints are shown with name if available and distance to waypoint.
|
Waypoints are shown with name if available and distance to waypoint.
|
||||||
|
|
||||||
|
As long as no GPS signal is available the compass shows the heading from the build in magnetometer. When a GPS fix becomes available, the compass display shows the GPS course. This can be differentiated by the display of bubble levels on top and sides of the compass.
|
||||||
|
If they are on display, the source is the magnetometer and you should keep the bangle level. There is currently no tilt compensation for the compass display.
|
||||||
|
|
||||||
### Route
|
### Route
|
||||||
|
|
||||||
Routes can be created from .gpx files containing "trkpt" elements with this script: [createRoute.sh](createRoute.sh)
|
Routes can be created from .gpx files containing "trkpt" elements with this script: [createRoute.sh](createRoute.sh)
|
||||||
|
|
|
||||||
|
|
@ -239,8 +239,14 @@ function getCompassSlice(compassDataSource){
|
||||||
} else {
|
} else {
|
||||||
bpos=Math.round(bpos*increment);
|
bpos=Math.round(bpos*increment);
|
||||||
}
|
}
|
||||||
graphics.setColor(p.color);
|
if (p.color){
|
||||||
graphics.fillCircle(bpos,y+height-12,Math.floor(width*0.03));
|
graphics.setColor(p.color);
|
||||||
|
}
|
||||||
|
if (p.icon){
|
||||||
|
graphics.drawImage(p.icon, bpos,y+height-12, {rotate:0,scale:2});
|
||||||
|
} else {
|
||||||
|
graphics.fillCircle(bpos,y+height-12,Math.floor(width*0.03));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (compassDataSource.getMarkers){
|
if (compassDataSource.getMarkers){
|
||||||
|
|
@ -318,16 +324,24 @@ function triangle (x, y, width, height){
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onSwipe(dir){
|
||||||
|
if (dir < 0) {
|
||||||
|
nextScreen();
|
||||||
|
} else if (dir > 0) {
|
||||||
|
switchMenu();
|
||||||
|
} else {
|
||||||
|
nextScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setButtons(){
|
function setButtons(){
|
||||||
Bangle.setUI("leftright", (dir)=>{
|
let options = {
|
||||||
if (dir < 0) {
|
mode: "custom",
|
||||||
nextScreen();
|
swipe: onSwipe,
|
||||||
} else if (dir > 0) {
|
btn: nextScreen,
|
||||||
switchMenu();
|
touch: nextScreen
|
||||||
} else {
|
};
|
||||||
nextScreen();
|
Bangle.setUI(options);
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getApproxFileSize(name){
|
function getApproxFileSize(name){
|
||||||
|
|
@ -469,10 +483,9 @@ function showRouteSelector (){
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let c of STORAGE.list((/\.trf$/))){
|
STORAGE.list(/\.trf$/).forEach((file)=>{
|
||||||
let file = c;
|
menu[file] = ()=>{handleLoading(file);};
|
||||||
menu[file] = ()=>{handleLoading(file);};
|
});
|
||||||
}
|
|
||||||
|
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
@ -535,14 +548,14 @@ function showWaypointSelector(){
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let c in waypoints){
|
waypoints.forEach((wp,c)=>{
|
||||||
menu[waypoints[c].name] = function (){
|
menu[waypoints[c].name] = function (){
|
||||||
state.waypoint = waypoints[c];
|
state.waypoint = waypoints[c];
|
||||||
state.waypointIndex = c;
|
state.waypointIndex = c;
|
||||||
state.route = null;
|
state.route = null;
|
||||||
removeMenu();
|
removeMenu();
|
||||||
};
|
};
|
||||||
}
|
});
|
||||||
|
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
@ -588,8 +601,8 @@ function showBackgroundMenu(){
|
||||||
"title" : "Background",
|
"title" : "Background",
|
||||||
back : showMenu,
|
back : showMenu,
|
||||||
},
|
},
|
||||||
"Start" : ()=>{ E.showPrompt("Start?").then((v)=>{ if (v) {WIDGETS.gpstrek.start(true); removeMenu();} else {E.showMenu(mainmenu);}});},
|
"Start" : ()=>{ E.showPrompt("Start?").then((v)=>{ if (v) {WIDGETS.gpstrek.start(true); removeMenu();} else {showMenu();}}).catch(()=>{E.showMenu(mainmenu);});},
|
||||||
"Stop" : ()=>{ E.showPrompt("Stop?").then((v)=>{ if (v) {WIDGETS.gpstrek.stop(true); removeMenu();} else {E.showMenu(mainmenu);}});},
|
"Stop" : ()=>{ E.showPrompt("Stop?").then((v)=>{ if (v) {WIDGETS.gpstrek.stop(true); removeMenu();} else {showMenu();}}).catch(()=>{E.showMenu(mainmenu);});},
|
||||||
};
|
};
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
@ -605,7 +618,7 @@ function showMenu(){
|
||||||
"Background" : showBackgroundMenu,
|
"Background" : showBackgroundMenu,
|
||||||
"Calibration": showCalibrationMenu,
|
"Calibration": showCalibrationMenu,
|
||||||
"Reset" : ()=>{ E.showPrompt("Do Reset?").then((v)=>{ if (v) {WIDGETS.gpstrek.resetState(); removeMenu();} else {E.showMenu(mainmenu);}});},
|
"Reset" : ()=>{ E.showPrompt("Do Reset?").then((v)=>{ if (v) {WIDGETS.gpstrek.resetState(); removeMenu();} else {E.showMenu(mainmenu);}});},
|
||||||
"Slices" : {
|
"Info rows" : {
|
||||||
value : numberOfSlices,
|
value : numberOfSlices,
|
||||||
min:1,max:6,step:1,
|
min:1,max:6,step:1,
|
||||||
onchange : v => { setNumberOfSlices(v); }
|
onchange : v => { setNumberOfSlices(v); }
|
||||||
|
|
@ -670,6 +683,8 @@ function setClosestWaypoint(route, startindex, progress){
|
||||||
|
|
||||||
let screen = 1;
|
let screen = 1;
|
||||||
|
|
||||||
|
const finishIcon = atob("CggB//meZmeZ+Z5n/w==");
|
||||||
|
|
||||||
const compassSliceData = {
|
const compassSliceData = {
|
||||||
getCourseType: function(){
|
getCourseType: function(){
|
||||||
return (state.currentPos && state.currentPos.course) ? "GPS" : "MAG";
|
return (state.currentPos && state.currentPos.course) ? "GPS" : "MAG";
|
||||||
|
|
@ -684,7 +699,10 @@ const compassSliceData = {
|
||||||
points.push({bearing:bearing(state.currentPos, state.route.currentWaypoint), color:"#0f0"});
|
points.push({bearing:bearing(state.currentPos, state.route.currentWaypoint), color:"#0f0"});
|
||||||
}
|
}
|
||||||
if (state.currentPos && state.currentPos.lon && state.route){
|
if (state.currentPos && state.currentPos.lon && state.route){
|
||||||
points.push({bearing:bearing(state.currentPos, getLast(state.route)), color:"#00f"});
|
points.push({bearing:bearing(state.currentPos, getLast(state.route)), icon: finishIcon});
|
||||||
|
}
|
||||||
|
if (state.currentPos && state.currentPos.lon && state.waypoint){
|
||||||
|
points.push({bearing:bearing(state.currentPos, state.waypoint), icon: finishIcon});
|
||||||
}
|
}
|
||||||
return points;
|
return points;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "gpstrek",
|
"id": "gpstrek",
|
||||||
"name": "GPS Trekking",
|
"name": "GPS Trekking",
|
||||||
"version": "0.02",
|
"version": "0.07",
|
||||||
"description": "Helper for tracking the status/progress during hiking. Do NOT depend on this for navigation!",
|
"description": "Helper for tracking the status/progress during hiking. Do NOT depend on this for navigation!",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"screenshots": [{"url":"screen1.png"},{"url":"screen2.png"},{"url":"screen3.png"},{"url":"screen4.png"}],
|
"screenshots": [{"url":"screen1.png"},{"url":"screen2.png"},{"url":"screen3.png"},{"url":"screen4.png"}],
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.8 KiB |
|
|
@ -23,10 +23,6 @@ function onGPS(fix) {
|
||||||
if(fix.fix) state.currentPos = fix;
|
if(fix.fix) state.currentPos = fix;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bangle.on('accel', function(e) {
|
|
||||||
state.acc = e;
|
|
||||||
});
|
|
||||||
|
|
||||||
function onMag(e) {
|
function onMag(e) {
|
||||||
if (!state.compassHeading) state.compassHeading = e.heading;
|
if (!state.compassHeading) state.compassHeading = e.heading;
|
||||||
|
|
||||||
|
|
@ -73,12 +69,23 @@ function onPressure(e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onAcc (e){
|
||||||
|
state.acc = e;
|
||||||
|
}
|
||||||
|
|
||||||
function start(bg){
|
function start(bg){
|
||||||
|
Bangle.removeListener('GPS', onGPS);
|
||||||
|
Bangle.removeListener("HRM", onPulse);
|
||||||
|
Bangle.removeListener("mag", onMag);
|
||||||
|
Bangle.removeListener("step", onStep);
|
||||||
|
Bangle.removeListener("pressure", onPressure);
|
||||||
|
Bangle.removeListener('accel', onAcc);
|
||||||
Bangle.on('GPS', onGPS);
|
Bangle.on('GPS', onGPS);
|
||||||
Bangle.on("HRM", onPulse);
|
Bangle.on("HRM", onPulse);
|
||||||
Bangle.on("mag", onMag);
|
Bangle.on("mag", onMag);
|
||||||
Bangle.on("step", onStep);
|
Bangle.on("step", onStep);
|
||||||
Bangle.on("pressure", onPressure);
|
Bangle.on("pressure", onPressure);
|
||||||
|
Bangle.on('accel', onAcc);
|
||||||
|
|
||||||
Bangle.setGPSPower(1, "gpstrek");
|
Bangle.setGPSPower(1, "gpstrek");
|
||||||
Bangle.setHRMPower(1, "gpstrek");
|
Bangle.setHRMPower(1, "gpstrek");
|
||||||
|
|
@ -96,8 +103,19 @@ function stop(bg){
|
||||||
if (bg){
|
if (bg){
|
||||||
if (state.active) bgChanged = true;
|
if (state.active) bgChanged = true;
|
||||||
state.active = false;
|
state.active = false;
|
||||||
saveState();
|
} else if (!state.active) {
|
||||||
|
Bangle.setGPSPower(0, "gpstrek");
|
||||||
|
Bangle.setHRMPower(0, "gpstrek");
|
||||||
|
Bangle.setCompassPower(0, "gpstrek");
|
||||||
|
Bangle.setBarometerPower(0, "gpstrek");
|
||||||
|
Bangle.removeListener('GPS', onGPS);
|
||||||
|
Bangle.removeListener("HRM", onPulse);
|
||||||
|
Bangle.removeListener("mag", onMag);
|
||||||
|
Bangle.removeListener("step", onStep);
|
||||||
|
Bangle.removeListener("pressure", onPressure);
|
||||||
|
Bangle.removeListener('accel', onAcc);
|
||||||
}
|
}
|
||||||
|
saveState();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
0.01: New App!
|
||||||
|
0.02: Refactor code to store grocery list in separate file
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
Modified version of the Grocery App - lets you upload an image with the products you need to shop - Display a list of product and track if you already put them in your cart.
|
||||||
|
|
||||||
|
Uses this API to do the OCR: https://rapidapi.com/serendi/api/pen-to-print-handwriting-ocr
|
||||||
|
With a free account you get 100 API calls a month.
|
||||||
|
|
||||||
|

|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
var filename = 'grocery_list_aug.json';
|
||||||
|
var settings = require("Storage").readJSON(filename,1)|| { products: [] };
|
||||||
|
|
||||||
|
function updateSettings() {
|
||||||
|
require("Storage").writeJSON(filename, settings);
|
||||||
|
Bangle.buzz();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const mainMenu = settings.products.reduce(function(m, p, i){
|
||||||
|
const name = p.name;
|
||||||
|
m[name] = {
|
||||||
|
value: p.ok,
|
||||||
|
format: v => v?'[x]':'[ ]',
|
||||||
|
onchange: v => {
|
||||||
|
settings.products[i].ok = v;
|
||||||
|
updateSettings();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return m;
|
||||||
|
}, {
|
||||||
|
'': { 'title': 'Grocery list' }
|
||||||
|
});
|
||||||
|
mainMenu['< Back'] = ()=>{load();};
|
||||||
|
E.showMenu(mainMenu);
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
E.toArrayBuffer(atob("MDCEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAiAAMAADAAAwAAMAAiAAMAAAAAAAA/8zP/Mz/zM/8z/zM/8zP/Mz/zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA///MzMzMzMzMzM/////8zP//zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA///MzMzMzMzMz//////8zP//zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA///MzMzMzMzMzM/////8zP//zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA///MzMzMzMzMz//////8zP//zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA/////////////MzMzMzMzP//zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAAAA////////////////////////zAAAAARE////////////////////////zERAAARE////////////////////////zERAAERE////////////////////////zEREAERE////////////////////////zEREAAREzMzMzMzMzMzMzMzMzMzMzMzMzERAAABEREREREREREREREREREREREREREQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="))
|
||||||