Merge branch 'master' into master
52
apps.json
|
|
@ -77,7 +77,7 @@
|
||||||
{
|
{
|
||||||
"id": "messages",
|
"id": "messages",
|
||||||
"name": "Messages",
|
"name": "Messages",
|
||||||
"version": "0.14",
|
"version": "0.15",
|
||||||
"description": "App to display notifications from iOS and Gadgetbridge",
|
"description": "App to display notifications from iOS and Gadgetbridge",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
|
|
@ -4487,7 +4487,7 @@
|
||||||
"name": "LCARS Clock",
|
"name": "LCARS Clock",
|
||||||
"shortName":"LCARS",
|
"shortName":"LCARS",
|
||||||
"icon": "lcars.png",
|
"icon": "lcars.png",
|
||||||
"version":"0.07",
|
"version":"0.08",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"supports": ["BANGLEJS2"],
|
"supports": ["BANGLEJS2"],
|
||||||
"description": "Library Computer Access Retrieval System (LCARS) clock.",
|
"description": "Library Computer Access Retrieval System (LCARS) clock.",
|
||||||
|
|
@ -4652,7 +4652,7 @@
|
||||||
"id": "sensible",
|
"id": "sensible",
|
||||||
"name": "SensiBLE",
|
"name": "SensiBLE",
|
||||||
"shortName": "SensiBLE",
|
"shortName": "SensiBLE",
|
||||||
"version": "0.04",
|
"version": "0.05",
|
||||||
"description": "Collect, display and advertise real-time sensor data.",
|
"description": "Collect, display and advertise real-time sensor data.",
|
||||||
"icon": "sensible.png",
|
"icon": "sensible.png",
|
||||||
"screenshots": [
|
"screenshots": [
|
||||||
|
|
@ -5029,9 +5029,10 @@
|
||||||
{ "id": "circlesclock",
|
{ "id": "circlesclock",
|
||||||
"name": "Circles clock",
|
"name": "Circles clock",
|
||||||
"shortName":"Circles clock",
|
"shortName":"Circles clock",
|
||||||
"version":"0.02",
|
"version":"0.03",
|
||||||
"description": "A clock with circles for different data at the bottom in a probably familiar style",
|
"description": "A clock with circles for different data at the bottom in a probably familiar style",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
"dependencies": {"widpedom":"app"},
|
"dependencies": {"widpedom":"app"},
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
|
|
@ -5064,18 +5065,35 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ "id": "andark",
|
{ "id": "andark",
|
||||||
"name": "Analog Dark",
|
"name": "Analog Dark",
|
||||||
"shortName":"AnDark",
|
"shortName":"AnDark",
|
||||||
"version":"0.04",
|
"version":"0.04",
|
||||||
"description": "analog clock face without disturbing widgets",
|
"description": "analog clock face without disturbing widgets",
|
||||||
"icon": "andark_icon.png",
|
"icon": "app_icon.png",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
"supports" : ["BANGLEJS2"],
|
"supports" : ["BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"andark.app.js","url":"app.js"},
|
{"name":"andark.app.js","url":"app.js"},
|
||||||
{"name":"andark.img","url":"app_icon.js ","evaluate":true}
|
{"name":"andark.img","url":"app_icon.js ","evaluate":true}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "diract",
|
||||||
|
"name": "DirAct",
|
||||||
|
"shortName": "DirAct",
|
||||||
|
"version": "0.01",
|
||||||
|
"description": "Proximity interaction detection.",
|
||||||
|
"icon": "diract.png",
|
||||||
|
"type": "app",
|
||||||
|
"tags": "tool,sensors",
|
||||||
|
"supports" : [ "BANGLEJS2" ],
|
||||||
|
"allow_emulator": false,
|
||||||
|
"readme": "README.md",
|
||||||
|
"storage": [
|
||||||
|
{ "name": "diract.app.js", "url": "diract.js" },
|
||||||
|
{ "name": "diract.img", "url": "diract-icon.js", "evaluate": true }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
0.01: New clock
|
0.01: New clock
|
||||||
0.02: Fix icon & add battery warn functionality
|
0.02: Fix icon & add battery warn functionality
|
||||||
|
0.03: Theming support & minor fixes
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ It shows besides time, date and day of week the following information:
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
* Show weather information
|
* Show weather information
|
||||||
|
* Configure which information to show in each circle
|
||||||
|
* Configure visibility of widgets
|
||||||
|
|
||||||
## Creator
|
## Creator
|
||||||
Marco ([myxor](https://github.com/myxor))
|
Marco ([myxor](https://github.com/myxor))
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,23 @@ const powerIcon = heatshrink.decompress(atob("h0OwYQNsAED7AEDmwEDtu2AgUbtuABwXbB
|
||||||
const powerIconGreen = heatshrink.decompress(atob("h0OwYQNkAEDpAEDiQEDkmSAgUJkmABwVJBIUEyVAAoYOCgEBFIgODABI"));
|
const powerIconGreen = heatshrink.decompress(atob("h0OwYQNkAEDpAEDiQEDkmSAgUJkmABwVJBIUEyVAAoYOCgEBFIgODABI"));
|
||||||
const powerIconRed = heatshrink.decompress(atob("h0OwYQNoAEDyAEDkgEDpIFDiVJBweSAgUJkmAAoYZDgQpEBwYAJA"));
|
const powerIconRed = heatshrink.decompress(atob("h0OwYQNoAEDyAEDkgEDpIFDiVJBweSAgUJkmAAoYZDgQpEBwYAJA"));
|
||||||
|
|
||||||
const SETTINGS_FILE = "circlesclock.json";
|
|
||||||
let settings;
|
let settings;
|
||||||
|
|
||||||
function loadSettings() {
|
function loadSettings() {
|
||||||
settings = require("Storage").readJSON(SETTINGS_FILE, 1) || {
|
settings = require("Storage").readJSON("circlesclock.json", 1) || {
|
||||||
'maxHR': 200,
|
'maxHR': 200,
|
||||||
'stepGoal': 10000,
|
'stepGoal': 10000,
|
||||||
'batteryWarn': 30
|
'batteryWarn': 30
|
||||||
};
|
};
|
||||||
|
// Load step goal from pedometer widget as fallback
|
||||||
|
if (settings.stepGoal == undefined) {
|
||||||
|
const d = require('Storage').readJSON("wpedom.json", 1) || {};
|
||||||
|
settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const colorFg = '#fff';
|
const colorFg = g.theme.dark ? '#fff' : '#000';
|
||||||
const colorBg = '#000';
|
const colorBg = g.theme.dark ? '#000' : '#fff';
|
||||||
const colorGrey = '#808080';
|
const colorGrey = '#808080';
|
||||||
const colorRed = '#ff0000';
|
const colorRed = '#ff0000';
|
||||||
const colorGreen = '#00ff00';
|
const colorGreen = '#00ff00';
|
||||||
|
|
@ -73,7 +77,7 @@ function drawSteps() {
|
||||||
g.setColor(colorGrey);
|
g.setColor(colorGrey);
|
||||||
g.fillCircle(w1, h3, radiusOuter);
|
g.fillCircle(w1, h3, radiusOuter);
|
||||||
|
|
||||||
const stepGoal = settings.stepGoal;
|
const stepGoal = settings.stepGoal || 10000;
|
||||||
if (stepGoal > 0) {
|
if (stepGoal > 0) {
|
||||||
let percent = steps / stepGoal;
|
let percent = steps / stepGoal;
|
||||||
if (stepGoal < steps) percent = 1;
|
if (stepGoal < steps) percent = 1;
|
||||||
|
|
@ -97,8 +101,9 @@ function drawHeartRate() {
|
||||||
g.setColor(colorGrey);
|
g.setColor(colorGrey);
|
||||||
g.fillCircle(w2, h3, radiusOuter);
|
g.fillCircle(w2, h3, radiusOuter);
|
||||||
|
|
||||||
if (hrtValue != undefined) {
|
if (hrtValue != undefined && hrtValue > 0) {
|
||||||
const percent = hrtValue / settings.maxHR;
|
const minHR = 40;
|
||||||
|
const percent = (hrtValue - minHR) / (settings.maxHR - minHR);
|
||||||
drawGauge(w2, h3, percent, colorRed);
|
drawGauge(w2, h3, percent, colorRed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,25 +161,26 @@ function radians(a) {
|
||||||
return a * Math.PI / 180;
|
return a * Math.PI / 180;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function drawGauge(cx, cy, percent, color) {
|
function drawGauge(cx, cy, percent, color) {
|
||||||
let offset = 30;
|
let offset = 30;
|
||||||
let end = 300;
|
let end = 300;
|
||||||
var i = 0;
|
var i = 0;
|
||||||
var r = radiusInner + 3;
|
var r = radiusInner + 3;
|
||||||
|
|
||||||
|
if (percent <= 0) return;
|
||||||
if (percent > 1) percent = 1;
|
if (percent > 1) percent = 1;
|
||||||
|
|
||||||
var startrot = -offset;
|
var startrot = -offset;
|
||||||
var endrot = startrot - ((end - offset) * percent);
|
var endrot = startrot - ((end - offset) * percent) - 15;
|
||||||
|
|
||||||
g.setColor(color);
|
g.setColor(color);
|
||||||
|
|
||||||
|
const size = 4;
|
||||||
// draw gauge
|
// draw gauge
|
||||||
for (i = startrot; i > endrot; i -= 4) {
|
for (i = startrot; i > endrot - size; i -= size) {
|
||||||
x = cx + r * Math.sin(radians(i));
|
x = cx + r * Math.sin(radians(i));
|
||||||
y = cy + r * Math.cos(radians(i));
|
y = cy + r * Math.cos(radians(i));
|
||||||
g.fillCircle(x, y, 4);
|
g.fillCircle(x, y, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -201,6 +207,10 @@ function getSteps() {
|
||||||
Bangle.on('lock', function(isLocked) {
|
Bangle.on('lock', function(isLocked) {
|
||||||
if (!isLocked) {
|
if (!isLocked) {
|
||||||
Bangle.setHRMPower(1, "watch");
|
Bangle.setHRMPower(1, "watch");
|
||||||
|
if (hrtValue == undefined) {
|
||||||
|
hrtValue = '...';
|
||||||
|
drawHeartRate();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Bangle.setHRMPower(0, "watch");
|
Bangle.setHRMPower(0, "watch");
|
||||||
}
|
}
|
||||||
|
|
@ -218,6 +228,10 @@ Bangle.on('HRM', function(hrm) {
|
||||||
//}
|
//}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Bangle.on('charging', function(charging) {
|
||||||
|
drawBattery();
|
||||||
|
});
|
||||||
|
|
||||||
g.clear();
|
g.clear();
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
/*
|
/*
|
||||||
|
|
@ -225,9 +239,11 @@ Bangle.loadWidgets();
|
||||||
* so we will blank out the draw() functions of each widget and change the
|
* so we will blank out the draw() functions of each widget and change the
|
||||||
* area to the top bar doesn't get cleared.
|
* area to the top bar doesn't get cleared.
|
||||||
*/
|
*/
|
||||||
for (let wd of WIDGETS) {
|
if (typeof WIDGETS === "object") {
|
||||||
wd.draw = () => {};
|
for (let wd of WIDGETS) {
|
||||||
wd.area = "";
|
wd.draw = () => {};
|
||||||
|
wd.area = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
loadSettings();
|
loadSettings();
|
||||||
setInterval(draw, 60000);
|
setInterval(draw, 60000);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: New App!
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
# DirAct
|
||||||
|
|
||||||
|
[DirAct](https://www.reelyactive.com/diract/) implementation for the Bangle.js.
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Real-time interactions will be recognised by [Pareto Anywhere](https://www.reelyactive.com/pareto/anywhere/) open source middleware and any other program which observes the [DirAct open standard](https://reelyactive.github.io/diract/).
|
||||||
|
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
Currently implements DirAct real-time functionality.
|
||||||
|
|
||||||
|
|
||||||
|
## Controls
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
|
||||||
|
## Requests
|
||||||
|
|
||||||
|
[Contact reelyActive](https://www.reelyactive.com/contact/) for support/updates.
|
||||||
|
|
||||||
|
|
||||||
|
## Creator
|
||||||
|
|
||||||
|
Developed by [jeffyactive](https://github.com/jeffyactive) of [reelyActive](https://www.reelyactive.com). DirAct is jointly developed by reelyActive and Code Blue Consulting.
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwkE/4AImUQgMjBpIAI+UQFAMBn4XRmJBDiYXRFwQwCC9HzF93/kAXDgSPWj4XRMAZGSDAUhiTWSAH4Ag+URAAUvRBkSAofxgUzmchX4khSw3ygIID+UiFwMiF4bIBGowIBiYvEmUjF4kxEwgABmU/mMCC4cBiUhiAXDkET+cjC4cgj5IE+MAI4MAC4RGCHQIXEgRREF44kCCIIeCDoIIBMAYkDHQJeDEwJBBn/xgYGBn8hC5cS+YoBmEfFoP/IoMxgYXJmETJIISBj/zgBOBDgITCSgYXDBoYUCDQIXBiYXNBIIXBA4IXHI44XEJIJHIC5JHEVwRkBO5qKBbYqPGgMikTXDmKPDEAPyAIJJCX4cAAAQXDIoQtBl6IEGIIXJFoYmCSAQ4BSYIXJRYJWBDQIACkM/eYQXJdYcSC4fzG4J2CC5MwK4I+CAAR4BLwQXJBwowDiQfDC5HzLAIXFGwoXILAQALC5IANC/4X/C+w"))
|
||||||
|
|
@ -0,0 +1,548 @@
|
||||||
|
/**
|
||||||
|
* Copyright reelyActive 2017-2021
|
||||||
|
* We believe in an open Internet of Things
|
||||||
|
*
|
||||||
|
* DirAct is jointly developed by reelyActive and Code Blue Consulting
|
||||||
|
*/
|
||||||
|
|
||||||
|
// User-configurable constants
|
||||||
|
const INSTANCE_ID = [ 0x00, 0x00, 0x00, 0x01 ];
|
||||||
|
const NAMESPACE_FILTER_ID = [ 0xc0, 0xde, 0xb1, 0x0e, 0x1d,
|
||||||
|
0xd1, 0xe0, 0x1b, 0xed, 0x0c ];
|
||||||
|
const EXCITER_INSTANCE_IDS = new Uint32Array([ 0xe8c17e45 ]);
|
||||||
|
const RESETTER_INSTANCE_IDS = new Uint32Array([ 0x4e5e77e4 ]);
|
||||||
|
const PROXIMITY_RSSI_THRESHOLD = -65;
|
||||||
|
const PROXIMITY_LED_RSSI_THRESHOLD = -65;
|
||||||
|
const PROXIMITY_TABLE_SIZE = 8;
|
||||||
|
const DIGEST_TABLE_SIZE = 32;
|
||||||
|
const OBSERVE_PERIOD_MILLISECONDS = 400;
|
||||||
|
const BROADCAST_PERIOD_MILLISECONDS = 3600;
|
||||||
|
const BROADCAST_DIGEST_PAGE_MILLISECONDS = 400;
|
||||||
|
const PROXIMITY_PACKET_INTERVAL_MILLISECONDS = 400;
|
||||||
|
const DIGEST_PACKET_INTERVAL_MILLISECONDS = 100;
|
||||||
|
const DIGEST_TIME_CYCLE_THRESHOLD = 86400;
|
||||||
|
const EXCITER_HOLDOFF_SECONDS = 60;
|
||||||
|
const BLINK_ON_PROXIMITY = true;
|
||||||
|
const BLINK_ON_DISTANCING = true;
|
||||||
|
const BLINK_ON_DIGEST = true;
|
||||||
|
const BLINK_ON_RESET = true;
|
||||||
|
|
||||||
|
|
||||||
|
// Eddystone protocol constants
|
||||||
|
const EDDYSTONE_UUID = 'feaa';
|
||||||
|
const EDDYSTONE_UID_FRAME = 0x00;
|
||||||
|
const EDDYSTONE_NAMESPACE_OFFSET = 2;
|
||||||
|
const EDDYSTONE_NAMESPACE_LENGTH = 10;
|
||||||
|
const EDDYSTONE_INSTANCE_OFFSET = 14;
|
||||||
|
|
||||||
|
|
||||||
|
// DirAct constants
|
||||||
|
const DIRACT_MANUFACTURER_ID = 0x0583; // Code Blue Consulting
|
||||||
|
const DIRACT_PROXIMITY_FRAME = 0x01;
|
||||||
|
const DIRACT_DIGEST_FRAME = 0x11;
|
||||||
|
const DIRACT_DEFAULT_COUNT_LENGTH = 0x07;
|
||||||
|
const DIRACT_INSTANCE_LENGTH = 4;
|
||||||
|
const DIRACT_INSTANCE_OFFSET = 2;
|
||||||
|
const MAX_NUMBER_STRONGEST = 3;
|
||||||
|
const MAX_BATTERY_VOLTAGE = 3.3;
|
||||||
|
const MIN_BATTERY_VOLTAGE = 3.0;
|
||||||
|
const MAX_RSSI_TO_ENCODE = -28;
|
||||||
|
const MIN_RSSI_TO_ENCODE = -92;
|
||||||
|
const MAX_ACCELERATION_TO_ENCODE = 2;
|
||||||
|
const MAX_ACCELERATION_MAGNITUDE = 0x1f;
|
||||||
|
const INVALID_ACCELERATION_CODE = 0x20;
|
||||||
|
const SCAN_OPTIONS = {
|
||||||
|
filters: [
|
||||||
|
{ manufacturerData: { 0x0583: {} } },
|
||||||
|
{ services: [ EDDYSTONE_UUID ] }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Other constants
|
||||||
|
const BITS_PER_BYTE = 8;
|
||||||
|
const DUMMY_INSTANCE_ID = 0;
|
||||||
|
const DUMMY_RSSI = MIN_RSSI_TO_ENCODE;
|
||||||
|
|
||||||
|
|
||||||
|
// Global variables
|
||||||
|
let proximityInstances = new Uint32Array(PROXIMITY_TABLE_SIZE);
|
||||||
|
let proximityRssis = new Int8Array(PROXIMITY_TABLE_SIZE);
|
||||||
|
let digestInstances = new Uint32Array(DIGEST_TABLE_SIZE);
|
||||||
|
let digestCounts = new Uint16Array(DIGEST_TABLE_SIZE);
|
||||||
|
let digestTime = new Uint8Array([ 0, 0, 0 ]);
|
||||||
|
let numberOfDigestPages = 0;
|
||||||
|
let sensorData = [ 0x82, 0x08, 0x3f ];
|
||||||
|
let cyclicCount = 0;
|
||||||
|
let lastDigestTime = Math.round(getTime());
|
||||||
|
let lastResetTime = Math.round(getTime());
|
||||||
|
let isExciterPresent = false;
|
||||||
|
let isResetterPresent = false;
|
||||||
|
let isProximityDetected = false;
|
||||||
|
let menu = {
|
||||||
|
"": { "title": "-- DirAct --" }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate observer mode, scanning for devices in proximity.
|
||||||
|
*/
|
||||||
|
function observe() {
|
||||||
|
proximityInstances.fill(DUMMY_INSTANCE_ID); // Reset proximity
|
||||||
|
proximityRssis.fill(DUMMY_RSSI); // table data
|
||||||
|
isExciterPresent = false;
|
||||||
|
isResetterPresent = false;
|
||||||
|
|
||||||
|
NRF.setScan(handleDiscoveredDevice, SCAN_OPTIONS); // Start scanning
|
||||||
|
setTimeout(broadcast, OBSERVE_PERIOD_MILLISECONDS); // ...until period end
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile the scan results and initiate broadcaster mode, advertising either
|
||||||
|
* proximity or digest packets, in consequence.
|
||||||
|
*/
|
||||||
|
function broadcast() {
|
||||||
|
NRF.setScan(); // Stop scanning
|
||||||
|
|
||||||
|
let sortedProximityIndices = getSortedIndices(proximityRssis);
|
||||||
|
|
||||||
|
updateDigestTable(sortedProximityIndices);
|
||||||
|
updateSensorData();
|
||||||
|
|
||||||
|
let currentTime = Math.round(getTime());
|
||||||
|
let isExcited = isExciterPresent &&
|
||||||
|
((currentTime - lastDigestTime) > EXCITER_HOLDOFF_SECONDS);
|
||||||
|
|
||||||
|
if(isResetterPresent) {
|
||||||
|
if(BLINK_ON_RESET) {
|
||||||
|
Bangle.setLCDPower(true);
|
||||||
|
}
|
||||||
|
lastResetTime = currentTime;
|
||||||
|
resetDigest();
|
||||||
|
broadcastProximity(sortedProximityIndices);
|
||||||
|
}
|
||||||
|
else if(isExcited) {
|
||||||
|
let sortedDigestIndices = getSortedIndices(digestCounts);
|
||||||
|
compileDigest();
|
||||||
|
broadcastDigest(sortedDigestIndices, 0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
broadcastProximity(sortedProximityIndices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate broadcaster mode advertising proximity packets.
|
||||||
|
* @param {TypedArray} sortedIndices The sorted proximity table indices.
|
||||||
|
*/
|
||||||
|
function broadcastProximity(sortedIndices) {
|
||||||
|
let advertisingOptions = {
|
||||||
|
interval: PROXIMITY_PACKET_INTERVAL_MILLISECONDS,
|
||||||
|
showName: false,
|
||||||
|
manufacturer: DIRACT_MANUFACTURER_ID
|
||||||
|
};
|
||||||
|
|
||||||
|
advertisingOptions.manufacturerData = compileProximityData(sortedIndices);
|
||||||
|
NRF.setAdvertising({}, advertisingOptions); // Start advertising
|
||||||
|
setTimeout(observe, BROADCAST_PERIOD_MILLISECONDS); // ...until period end
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate broadcaster mode advertising digest packets.
|
||||||
|
* @param {TypedArray} sortedIndices The sorted digest table indices.
|
||||||
|
* @param {Number} pageNumber The page number to broadcast.
|
||||||
|
*/
|
||||||
|
function broadcastDigest(sortedIndices, pageNumber) {
|
||||||
|
let isLastPage = (pageNumber === (numberOfDigestPages - 1));
|
||||||
|
let advertisingOptions = {
|
||||||
|
interval: DIGEST_PACKET_INTERVAL_MILLISECONDS,
|
||||||
|
showName: false,
|
||||||
|
manufacturer: DIRACT_MANUFACTURER_ID
|
||||||
|
};
|
||||||
|
|
||||||
|
advertisingOptions.manufacturerData = compileDigestData(sortedIndices,
|
||||||
|
pageNumber);
|
||||||
|
NRF.setAdvertising({}, advertisingOptions); // Start advertising
|
||||||
|
|
||||||
|
if(isLastPage) {
|
||||||
|
setTimeout(observe, BROADCAST_DIGEST_PAGE_MILLISECONDS);
|
||||||
|
if(getTime() > DIGEST_TIME_CYCLE_THRESHOLD) {
|
||||||
|
lastResetTime = Math.round(getTime());
|
||||||
|
resetDigest();
|
||||||
|
}
|
||||||
|
if(BLINK_ON_DIGEST) {
|
||||||
|
Bangle.setLCDPower(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setTimeout(broadcastDigest, BROADCAST_DIGEST_PAGE_MILLISECONDS,
|
||||||
|
sortedIndices, ++pageNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the given device discovered on scan and process further if
|
||||||
|
* Eddystone-UID or DirAct.
|
||||||
|
* @param {BluetoothDevice} device The discovered device.
|
||||||
|
*/
|
||||||
|
function handleDiscoveredDevice(device) {
|
||||||
|
let isEddystone = (device.hasOwnProperty('services') &&
|
||||||
|
device.hasOwnProperty('serviceData') &&
|
||||||
|
(device.services[0] === EDDYSTONE_UUID));
|
||||||
|
let isManufacturer = (device.hasOwnProperty('manufacturer') &&
|
||||||
|
device.manufacturer === DIRACT_MANUFACTURER_ID);
|
||||||
|
|
||||||
|
if(isEddystone) {
|
||||||
|
let isEddystoneUID = (device.serviceData[EDDYSTONE_UUID][0] ===
|
||||||
|
EDDYSTONE_UID_FRAME);
|
||||||
|
if(isEddystoneUID) {
|
||||||
|
handleEddystoneUidDevice(device.serviceData[EDDYSTONE_UUID], device.rssi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(isManufacturer) {
|
||||||
|
let isDirAct = ((device.manufacturerData[0] === DIRACT_PROXIMITY_FRAME) ||
|
||||||
|
(device.manufacturerData[0] === DIRACT_DIGEST_FRAME));
|
||||||
|
if(isDirAct) {
|
||||||
|
handleDirActDevice(device.manufacturerData, device.rssi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the given Eddystone-UID device, adding to the devices in range if
|
||||||
|
* it meets the filter criteria.
|
||||||
|
* @param {Array} serviceData The Eddystone service data.
|
||||||
|
* @param {Number} rssi The received signal strength.
|
||||||
|
*/
|
||||||
|
function handleEddystoneUidDevice(serviceData, rssi) {
|
||||||
|
for(let cByte = 0; cByte < EDDYSTONE_NAMESPACE_LENGTH; cByte++) {
|
||||||
|
let namespaceIndex = EDDYSTONE_NAMESPACE_OFFSET + cByte;
|
||||||
|
if(serviceData[namespaceIndex] !== NAMESPACE_FILTER_ID[cByte]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let instanceId = 0;
|
||||||
|
let bitShift = (DIRACT_INSTANCE_LENGTH - 1) * BITS_PER_BYTE;
|
||||||
|
|
||||||
|
for(let cByte = 0; cByte < DIRACT_INSTANCE_LENGTH; cByte++) {
|
||||||
|
let instanceByte = serviceData[EDDYSTONE_INSTANCE_OFFSET + cByte];
|
||||||
|
instanceId += instanceByte << bitShift;
|
||||||
|
bitShift -= BITS_PER_BYTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
let unsignedInstanceId = new Uint32Array([instanceId])[0];
|
||||||
|
|
||||||
|
if(EXCITER_INSTANCE_IDS.indexOf(unsignedInstanceId) >= 0) {
|
||||||
|
isExciterPresent = true;
|
||||||
|
}
|
||||||
|
else if(RESETTER_INSTANCE_IDS.indexOf(unsignedInstanceId) >= 0) {
|
||||||
|
isResetterPresent = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
updateProximityTable(instanceId, rssi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the given DirAct device, adding to the devices in range if
|
||||||
|
* it meets the filter criteria.
|
||||||
|
* @param {Array} manufacturerData The DirAct manufacturer data.
|
||||||
|
* @param {Number} rssi The received signal strength.
|
||||||
|
*/
|
||||||
|
function handleDirActDevice(manufacturerData, rssi) {
|
||||||
|
let instanceId = 0;
|
||||||
|
let bitShift = (DIRACT_INSTANCE_LENGTH - 1) * BITS_PER_BYTE;
|
||||||
|
|
||||||
|
for(let cByte = DIRACT_INSTANCE_OFFSET;
|
||||||
|
cByte < DIRACT_INSTANCE_OFFSET + DIRACT_INSTANCE_LENGTH; cByte++) {
|
||||||
|
let instanceByte = manufacturerData[cByte];
|
||||||
|
instanceId += instanceByte << bitShift;
|
||||||
|
bitShift -= BITS_PER_BYTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProximityTable(instanceId, rssi);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the proximity table with the given instance's RSSI. If the instance
|
||||||
|
* already exists, combine RSSI values in a weighted average.
|
||||||
|
* @param {String} instanceId The DirAct 4-byte instance id as a 32-bit integer.
|
||||||
|
* @param {Number} rssi The received signal strength.
|
||||||
|
*/
|
||||||
|
function updateProximityTable(instanceId, rssi) {
|
||||||
|
let instanceIndex = proximityInstances.indexOf(instanceId);
|
||||||
|
let isNewInstance = (instanceIndex < 0);
|
||||||
|
|
||||||
|
if(isNewInstance) {
|
||||||
|
let nextIndex = proximityInstances.indexOf(DUMMY_INSTANCE_ID);
|
||||||
|
if(nextIndex >= 0) {
|
||||||
|
proximityInstances[nextIndex] = instanceId;
|
||||||
|
proximityRssis[nextIndex] = rssi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
proximityRssis[instanceIndex] = (proximityRssis[instanceIndex] + rssi) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the digest table based on the proximity table and its sorted indices.
|
||||||
|
* @param {TypedArray} sortedIndices The sorted proximity table indices.
|
||||||
|
*/
|
||||||
|
function updateDigestTable(sortedIndices) {
|
||||||
|
for(let cInstance = 0; cInstance < PROXIMITY_TABLE_SIZE; cInstance++) {
|
||||||
|
let proximityIndex = sortedIndices[cInstance];
|
||||||
|
|
||||||
|
if(proximityRssis[proximityIndex] >= PROXIMITY_RSSI_THRESHOLD) {
|
||||||
|
let instanceId = proximityInstances[proximityIndex];
|
||||||
|
let instanceIndex = digestInstances.indexOf(instanceId);
|
||||||
|
let isNewInstance = (instanceIndex < 0);
|
||||||
|
|
||||||
|
if(isNewInstance) {
|
||||||
|
let nextIndex = digestInstances.indexOf(DUMMY_INSTANCE_ID);
|
||||||
|
if(nextIndex >= 0) {
|
||||||
|
digestInstances[nextIndex] = instanceId;
|
||||||
|
digestCounts[nextIndex] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(digestCounts[instanceIndex] < 65535) {
|
||||||
|
digestCounts[instanceIndex]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cInstance = PROXIMITY_TABLE_SIZE; // Break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compile the digest from the digest table.
|
||||||
|
*/
|
||||||
|
function compileDigest() {
|
||||||
|
let numberOfEntries = digestCounts.findIndex(count => count === 0);
|
||||||
|
let currentTime = Math.round(getTime());
|
||||||
|
let elapsedTime = currentTime - lastResetTime;
|
||||||
|
if(numberOfEntries < 0) {
|
||||||
|
numberOfEntries = DIGEST_TABLE_SIZE;
|
||||||
|
}
|
||||||
|
digestTime[0] = (elapsedTime >> 16) & 0xff;
|
||||||
|
digestTime[1] = (elapsedTime >> 8) & 0xff;
|
||||||
|
digestTime[2] = elapsedTime & 0xff;
|
||||||
|
numberOfDigestPages = Math.max(1, Math.min(8, Math.ceil(numberOfEntries/3)));
|
||||||
|
lastDigestTime = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Clear the digest table.
|
||||||
|
*/
|
||||||
|
function resetDigest() {
|
||||||
|
digestInstances.fill(DUMMY_INSTANCE_ID);
|
||||||
|
digestCounts.fill(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile the DirAct proximity data.
|
||||||
|
* @param {TypedArray} sortedIndices The sorted proximity table indices.
|
||||||
|
*/
|
||||||
|
function compileProximityData(sortedIndices) {
|
||||||
|
let data = [
|
||||||
|
DIRACT_PROXIMITY_FRAME, DIRACT_DEFAULT_COUNT_LENGTH,
|
||||||
|
INSTANCE_ID[0], INSTANCE_ID[1], INSTANCE_ID[2], INSTANCE_ID[3],
|
||||||
|
sensorData[0], sensorData[1], sensorData[2]
|
||||||
|
];
|
||||||
|
let isNewProximityDetected = false;
|
||||||
|
|
||||||
|
for(let cInstance = 0; cInstance < MAX_NUMBER_STRONGEST; cInstance++) {
|
||||||
|
let index = sortedIndices[cInstance];
|
||||||
|
|
||||||
|
if(proximityRssis[index] >= PROXIMITY_RSSI_THRESHOLD) {
|
||||||
|
let instanceId = proximityInstances[index];
|
||||||
|
data.push((instanceId >> 24) & 0xff, (instanceId >> 16) & 0xff,
|
||||||
|
(instanceId >> 8) & 0xff, instanceId & 0xff,
|
||||||
|
encodeRssi(proximityRssis[index]));
|
||||||
|
if(proximityRssis[index] >= PROXIMITY_LED_RSSI_THRESHOLD) {
|
||||||
|
isNewProximityDetected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cInstance = PROXIMITY_TABLE_SIZE; // Break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cyclicCount = (cyclicCount + 1) % 8;
|
||||||
|
|
||||||
|
data[1] = (cyclicCount << 5) + (data.length - 2);
|
||||||
|
|
||||||
|
if(isProximityDetected && !isNewProximityDetected && BLINK_ON_DISTANCING) {
|
||||||
|
Bangle.setLCDPower(true);
|
||||||
|
}
|
||||||
|
else if(isNewProximityDetected && BLINK_ON_PROXIMITY) {
|
||||||
|
Bangle.setLCDPower(true);
|
||||||
|
}
|
||||||
|
isProximityDetected = isNewProximityDetected;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile the DirAct digest data.
|
||||||
|
* @param {TypedArray} sortedIndices The sorted digest table indices.
|
||||||
|
* @param {Number} digestPage The page of the digest to compile.
|
||||||
|
*/
|
||||||
|
function compileDigestData(sortedIndices, digestPage) {
|
||||||
|
let isLastPage = (digestPage === (numberOfDigestPages - 1));
|
||||||
|
|
||||||
|
let digestStatus = digestTime[0] & 0x7f;
|
||||||
|
if(isLastPage) {
|
||||||
|
digestStatus |= 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = [
|
||||||
|
DIRACT_DIGEST_FRAME, DIRACT_DEFAULT_COUNT_LENGTH,
|
||||||
|
INSTANCE_ID[0], INSTANCE_ID[1], INSTANCE_ID[2], INSTANCE_ID[3],
|
||||||
|
digestStatus, digestTime[1], digestTime[2]
|
||||||
|
];
|
||||||
|
let pageIndex = digestPage * 3;
|
||||||
|
|
||||||
|
for(let cInstance = pageIndex; cInstance < (pageIndex + 3); cInstance++) {
|
||||||
|
let index = sortedIndices[cInstance];
|
||||||
|
|
||||||
|
if(digestCounts[index] > 0) {
|
||||||
|
let instanceId = digestInstances[index];
|
||||||
|
let encodedCount = digestCounts[index];
|
||||||
|
if(encodedCount > 127) {
|
||||||
|
encodedCount = 0x80 | (Math.min((encodedCount >> 8), 0x7f) & 0x7f);
|
||||||
|
}
|
||||||
|
data.push((instanceId >> 24) & 0xff, (instanceId >> 16) & 0xff,
|
||||||
|
(instanceId >> 8) & 0xff, instanceId & 0xff,
|
||||||
|
encodedCount);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cInstance = pageIndex + 3; // Break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data[1] = (digestPage << 5) + (data.length - 2);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode the given RSSI.
|
||||||
|
* @param {Number} rssi The given RSSI.
|
||||||
|
* @return {Number} The encoded RSSI.
|
||||||
|
*/
|
||||||
|
function encodeRssi(rssi) {
|
||||||
|
rssi = Math.round(rssi);
|
||||||
|
|
||||||
|
if(rssi >= MAX_RSSI_TO_ENCODE) {
|
||||||
|
return 0x3f;
|
||||||
|
}
|
||||||
|
if(rssi <= MIN_RSSI_TO_ENCODE) {
|
||||||
|
return 0x00;
|
||||||
|
}
|
||||||
|
return rssi - MIN_RSSI_TO_ENCODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode the battery percentage.
|
||||||
|
* @return {Number} The battery percentage.
|
||||||
|
*/
|
||||||
|
function encodeBatteryPercentage() {
|
||||||
|
let voltage = NRF.getBattery();
|
||||||
|
|
||||||
|
if(voltage <= MIN_BATTERY_VOLTAGE) {
|
||||||
|
return 0x00;
|
||||||
|
}
|
||||||
|
if(voltage >= MAX_BATTERY_VOLTAGE) {
|
||||||
|
return 0x3f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.round(0x3f * (voltage - MIN_BATTERY_VOLTAGE) /
|
||||||
|
(MAX_BATTERY_VOLTAGE - MIN_BATTERY_VOLTAGE));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode the acceleration.
|
||||||
|
* @return {Array} The encoded acceleration [ x, y, z ].
|
||||||
|
*/
|
||||||
|
function encodeAcceleration() {
|
||||||
|
let encodedAcceleration = { x: INVALID_ACCELERATION_CODE,
|
||||||
|
y: INVALID_ACCELERATION_CODE,
|
||||||
|
z: INVALID_ACCELERATION_CODE };
|
||||||
|
|
||||||
|
let acceleration = { x: Bangle.getAccel().x,
|
||||||
|
y: Bangle.getAccel().y,
|
||||||
|
z: Bangle.getAccel().z };
|
||||||
|
|
||||||
|
for(let axis in acceleration) {
|
||||||
|
let magnitude = acceleration[axis];
|
||||||
|
let encodedMagnitude = Math.min(MAX_ACCELERATION_MAGNITUDE,
|
||||||
|
Math.round(MAX_ACCELERATION_MAGNITUDE *
|
||||||
|
(Math.abs(magnitude) /
|
||||||
|
MAX_ACCELERATION_TO_ENCODE)));
|
||||||
|
if(magnitude < 0) {
|
||||||
|
encodedMagnitude = 0x3f - encodedMagnitude;
|
||||||
|
}
|
||||||
|
encodedAcceleration[axis] = encodedMagnitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
return encodedAcceleration;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the sensor data (battery & acceleration) for the advertising packet.
|
||||||
|
*/
|
||||||
|
function updateSensorData() {
|
||||||
|
|
||||||
|
// Update the battery measurement each time the cyclic count resets
|
||||||
|
if(cyclicCount === 0) {
|
||||||
|
encodedBattery = encodeBatteryPercentage();
|
||||||
|
}
|
||||||
|
|
||||||
|
encodedAcceleration = encodeAcceleration();
|
||||||
|
|
||||||
|
sensorData[0] = ((encodedAcceleration.x << 2) & 0xfc) |
|
||||||
|
((encodedAcceleration.y >> 4) & 0x3f);
|
||||||
|
sensorData[1] = ((encodedAcceleration.y << 4) & 0xf0) |
|
||||||
|
((encodedAcceleration.z >> 2) & 0x0f);
|
||||||
|
sensorData[2] = ((encodedAcceleration.z << 6) & 0xc0) |
|
||||||
|
(encodedBattery & 0x3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the sorted order of the indices of the given array.
|
||||||
|
* @return {Uint8Array} The array of indices sorted in descending order.
|
||||||
|
*/
|
||||||
|
function getSortedIndices(unsortedArray) {
|
||||||
|
let sortedIndices = new Uint8Array(unsortedArray.length);
|
||||||
|
|
||||||
|
sortedIndices.forEach((value, index) => sortedIndices[index] = index);
|
||||||
|
sortedIndices.sort((a, b) => unsortedArray[b] - unsortedArray[a]);
|
||||||
|
|
||||||
|
return sortedIndices;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// On start: begin DirAct operation and display the menu
|
||||||
|
g.clear();
|
||||||
|
E.showMenu(menu);
|
||||||
|
observe();
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
|
|
@ -4,4 +4,5 @@
|
||||||
0.04: Inluded LCARS Logo.
|
0.04: Inluded LCARS Logo.
|
||||||
0.05: Additional icons for (1) charging and (2) bat < 30%.
|
0.05: Additional icons for (1) charging and (2) bat < 30%.
|
||||||
0.06: Fix - Alarm disabled, if clock was closed.
|
0.06: Fix - Alarm disabled, if clock was closed.
|
||||||
0.07: Added settings to adjust data that is shown for each row.
|
0.07: Added settings to adjust data that is shown for each row.
|
||||||
|
0.08: Support for multiple screens. 24h graph for steps + HRM. Fullscreen Mode.
|
||||||
|
|
@ -1,18 +1,33 @@
|
||||||
# LCARS clock
|
# LCARS clock
|
||||||
|
|
||||||
A simple LCARS inspired clock.
|
A simple LCARS inspired clock.
|
||||||
Note: To display the steps, its necessary to install
|
Note: To display the steps, the health app is required. If this app is not installed, the data will not be shown.
|
||||||
the [Pedometer widget](https://banglejs.com/apps/#pedometer%20widget).
|
To contribute you can open a PR at this [GitHub Repo]( https://github.com/peerdavid/BangleApps)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
* LCARS Style watch face
|
* LCARS Style watch face.
|
||||||
* Shows satate (charging, out of battery etc.)
|
* Full screen mode - widgets are still loaded.
|
||||||
* SHows data that can be configured (steps, HRM, temperature etc.)
|
* Supports multiple screens with different data.
|
||||||
* Swipe left/right to activate an alarm
|
* [Screen 1] Date + Time + Lock status.
|
||||||
|
* [Screen 1] Shows randomly images of real planets.
|
||||||
|
* [Screen 1] Shows different states such as (charging, out of battery, GPS on etc.)
|
||||||
|
* [Screen 1] Swipe up/down to activate an alarm.
|
||||||
|
* [Screen 1] Shows 3 customizable datapoints on the first screen.
|
||||||
|
* [Screen 1] The lower orange line indicates the battery level.
|
||||||
|
* [Screen 2] Display month graphs for steps + hrm on the second screen.
|
||||||
|
|
||||||
|
|
||||||
|
## Multiple screens support
|
||||||
|
Access different screens via swipe left/ right
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
## Icons
|
## Icons
|
||||||
<div>Icons made by <a href="https://www.flaticon.com/authors/smashicons" title="Smashicons">Smashicons</a>, <a href="https://www.freepik.com" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a></div>
|
<div>Icons made by <a href="https://www.flaticon.com/authors/smashicons" title="Smashicons">Smashicons</a>, <a href="https://www.freepik.com" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a></div>
|
||||||
|
|
||||||
|
|
||||||
## Creator
|
## Contributors
|
||||||
Made by [David Peer](https://github.com/peerdavid)
|
- Creator: [David Peer](https://github.com/peerdavid).
|
||||||
|
- Improvements: [Adam Schmalhofer](https://github.com/adamschmalhofer).
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 795 B |
|
After Width: | Height: | Size: 791 B |
|
Before Width: | Height: | Size: 8.5 KiB |
|
|
@ -15,85 +15,96 @@ let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
|
||||||
for (const key in saved_settings) {
|
for (const key in saved_settings) {
|
||||||
settings[key] = saved_settings[key]
|
settings[key] = saved_settings[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Colors to use
|
||||||
|
*/
|
||||||
|
let cBlue = "#0094FF";
|
||||||
|
let cOrange = "#FF9900";
|
||||||
|
let cPurple = "#FF00DC";
|
||||||
|
let cWhite = "#FFFFFF";
|
||||||
|
let cBlack = "#000000";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Global lcars variables
|
||||||
|
*/
|
||||||
|
let lcarsViewPos = 0;
|
||||||
|
let drag;
|
||||||
let hrmValue = 0;
|
let hrmValue = 0;
|
||||||
|
var connected = NRF.getSecurityStatus().connected;
|
||||||
|
var plotWeek = false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Requirements and globals
|
* Requirements and globals
|
||||||
*/
|
*/
|
||||||
const locale = require('locale');
|
const locale = require('locale');
|
||||||
|
|
||||||
var backgroundImage = {
|
var bgLeft = {
|
||||||
width : 176, height : 151, bpp : 3,
|
width : 27, height : 176, bpp : 3,
|
||||||
transparent : 2,
|
transparent : 0,
|
||||||
buffer : require("heatshrink").decompress(atob("AAdx48cATsAg4daIAX3799ATv2wEFDrUAgNHQDyDghaAeQcJKG86D4gRKGgAA4jxKFuBB5iaDF6BB5ZwyD6QAYCC4CD/Qf6Dzg/gQf8H/iD/n//wCD9gP///wQfpBKQf6D4h5BB/yD8jl/IIIABjiD5n4/DAAWAQe8B//8QYfHj//PAaDzHwICCAAP4gYCBQep6DIIYFBRgKD1j/+gB9BQYYKBn/gQen/+BBFQAUH/iDzGoZBHJoOAQeRBDj5BHj6PB0WKlACDJQIAofYZBFBAZBBAGMHPQZB8QYZAEIIcDIOiDI/hB3QZBBFjlx44CDuBBpg4DCIJEfIIPnz15AQeAQeH8gIDBGoJBCnnz54CDZ1UHPQMHIIUAIIKD3II6MBQYQCCQeI1B+BBC/BKCBASGCQeK5B/xBC4BKEn/gAoKDyj//45BFj/xZYSDzgF/IAP+JQrLCQecAgKDBF4cHQYKJDQecAn6EBAAiJEQeZBB/jICAAMcvwMDQevgQwR0CIIiDzgP/BA1/4CD3nAHGhyD3ABqD0ABiD/Qf4ADjiD/gEnQYuQQf6D7gaDFzxB5gFzQYnz4BB5hyDFATfkEoIdagEBQYoCcgEHDrReBhKDhwEBQbYABjiD/AH4A/AH4AGiFx48cATsAg4daIIWSpMkATuQEbkAgJfbQckJQDyDhZxQA1gRKFpBA4gEQQYtwIPMSQYtAIPKADQfqADAQRA5Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf4A/AH4A/AH4A/AFkcuPHAQdAIPOSpMkAQaD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf4A/AH4A/AH4A/AGUcuPHAQdwIPOSpMkAQaD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf4AciSDFoCD/QfcCQYtIIPMAQYoC6gEJQYgC6gEBQf7HCQf4ABiiD9"))
|
buffer : require("heatshrink").decompress(atob("AAUM2XLlgCCwAJBBAuy4EAmQIF5cggAIGlmwgYIG2XIF42wF4ImGF4ImHJoQmGJoQdJhZNHNY47CgRNGBIJZHHgRiGBIRQ/KH5QCAFCh/eX5Q/KAwdCAGVbtu27YCCoAJBkuWrNlAQRGCiwRDAQPQBIMJCIYCBsAJBgomEtu0WoQmEy1YBIMBHYttIwQ7FyxQ/KHFlFAQ7F2weCHYplKChRTCCg5TCHw5TMAD0GzVp0wCCBBGaBIMaBAtpwECBA2mwEJBAugDgMmCIwJBF5EABAtoeQQvGCYQdPJoI7LMQzTCLJKAGzAJBO4xQ/KGQA8UP7y/KH5QnAHih/eX5Q/GQ4JCGRJlKCgxTDBAwgCCg5TCHwxTCNA4A=="))
|
||||||
}
|
};
|
||||||
|
|
||||||
|
var bgRight = {
|
||||||
|
width : 27, height : 176, bpp : 3,
|
||||||
|
transparent : 0,
|
||||||
|
buffer : require("heatshrink").decompress(atob("lmy5YCDBIUyBAmy5AJBhYUG2EAhgIFAQMAgQIGCgQABCg4ABEAwUNFI2AKZHAKZEgGRZTGOIUDQxJxGKH5Q/agwAnUP7y/KH4yGeVYAJrdt23bAQVABIMly1ZsoCCMgUWCIYCB6AJBhIRDAQNgBIMFEwlt2i1CEwmWrAJBgI7FtpGCHYuWKH5QxEwpQDlo7F0A7IqBZBEwo7BCIwCBJo53CJoxiCJpIAdgOmzVpAQR/CgAIEAQJ2CBAoCBBIMmCg1oD4QLGFQUCCjQ+CKYw+CKY4JCKYwoCGRMaGREJDoroCgwdFzBlLKH5QvAHih/eX5Q/KE4A8UP7y/KH5QGDpg7HJoxZCCIx3CJowmCF4yACJox/CgAA="))
|
||||||
|
};
|
||||||
|
|
||||||
var iconEarth = {
|
var iconEarth = {
|
||||||
text: "EARTH",
|
|
||||||
width : 50, height : 50, bpp : 3,
|
width : 50, height : 50, bpp : 3,
|
||||||
buffer : require("heatshrink").decompress(atob("AFtx48ECBsDwU5k/yhARLjgjBjlzAQMQEZcIkOP/fn31IEZgCBnlz58cEpM4geugEgwU/8+WNZJHDuHHvgmBCQ8goEOnVgJoMnyV58mACItHI4X8uAFBuVHnnz4BuGxk4////Egz3IkmWvPgNw8f/prB//BghTC+AjE7848eMjNnzySBwUJkmf/BuGuPDAQIjBiPHhhTCSQnjMo0ITANJn44Dg8MuFBggCCiFBcAJ0Bv5xEh+ITo2OhHkyf/OIQdBWwVHhgjBNwUE+fP/5EEgePMoYLBhMgyVJk/+BQQdC688I4XxOIc8v//NAvr+QEBj/5NwKVBy1/QYUciPBhk1EAJrC+KeC489QYaMBgU/8BNB9+ChEjz1Jkn/QYMBDQIgCcYTCCiP/nlzJQmenMAgV4//uy/9wRaB/1J8iVCcAfHjt9TYYICnhKCgRKBw159/v//r927OIeeoASBDQccvv3791KYVDBYPLJQeCnPnz//AAP6ocEjEkXgMgJQtz79fLAP8KYkccAcJ8Gf/f/xu/cAMQ4eP5MlyQRCMolx40YsOGBAPfnnzU4KVDpKMBvz8Dh0/8me7IICgkxJQXPIgZTD58sEgcJk+eNoONnFBhk4/5uB/pcDg5KD+4mEv4CBXISVDhEn31/8/+mH7x//JQK5CAAMB4JBCnnxJQf/+fJEgkAa4L+CAQOOjMn/1bXIRxDJQXx58f//Hhlz/88EgsChMgz/Zs/+nfkyV/8huDOI6SD498NwoACi1Z8+S/Plz17/+QCI7jC+ZxBmfPnojIAAMDcYWSp//2wRJEwq2GABECjMgNYwAmA="))
|
buffer : require("heatshrink").decompress(atob("AFtx48ECBsDwU5k/yhARLjgjBjlzAQMQEZcIkOP/fn31IEZgCBnlz58cEpM4geugEgwU/8+WNZJHDuHHvgmBCQ8goEOnVgJoMnyV58mACItHI4X8uAFBuVHnnz4BuGxk4////Egz3IkmWvPgNw8f/prB//BghTC+AjE7848eMjNnzySBwUJkmf/BuGuPDAQIjBiPHhhTCSQnjMo0ITANJn44Dg8MuFBggCCiFBcAJ0Bv5xEh+ITo2OhHkyf/OIQdBWwVHhgjBNwUE+fP/5EEgePMoYLBhMgyVJk/+BQQdC688I4XxOIc8v//NAvr+QEBj/5NwKVBy1/QYUciPBhk1EAJrC+KeC489QYaMBgU/8BNB9+ChEjz1Jkn/QYMBDQIgCcYTCCiP/nlzJQmenMAgV4//uy/9wRaB/1J8iVCcAfHjt9TYYICnhKCgRKBw159/v//r927OIeeoASBDQccvv3791KYVDBYPLJQeCnPnz//AAP6ocEjEkXgMgJQtz79fLAP8KYkccAcJ8Gf/f/xu/cAMQ4eP5MlyQRCMolx40YsOGBAPfnnzU4KVDpKMBvz8Dh0/8me7IICgkxJQXPIgZTD58sEgcJk+eNoONnFBhk4/5uB/pcDg5KD+4mEv4CBXISVDhEn31/8/+mH7x//JQK5CAAMB4JBCnnxJQf/+fJEgkAa4L+CAQOOjMn/1bXIRxDJQXx58f//Hhlz/88EgsChMgz/Zs/+nfkyV/8huDOI6SD498NwoACi1Z8+S/Plz17/+QCI7jC+ZxBmfPnojIAAMDcYWSp//2wRJEwq2GABECjMgNYwAmA="))
|
||||||
}
|
};
|
||||||
|
|
||||||
var iconSaturn = {
|
var iconSaturn = {
|
||||||
text: "SATURN",
|
|
||||||
width : 50, height : 50, bpp : 3,
|
width : 50, height : 50, bpp : 3,
|
||||||
transparent : 1,
|
transparent : 1,
|
||||||
buffer : require("heatshrink").decompress(atob("AH4A/AEkQuPHCJ0ChEAwARNjAjBjgjOhs06Q2OEYVx4ARMhEggUMkANIDoIgBoEEgEBNxJEC6ZrBAAMwNxAjDNYcHNxIjB7dtEwIHBwRoKj158+cuPEjlwCRAjC23bpu0wRNDAAsHEYWeEwaSJ6YjCAQUNSRQjEzxQBWZMNEYlsmg2JWAIjCz95SoJuJggjDtuw6dMG5JKCz998wFBJRVNEYW0yaVBJRNhJQN9+4pCzhKJmBKC4YpB/fINxIgCzFxSoQ3J4ENm3CAQPb98wbpEcAQMYWwKYBNxMDXgc2/fv3g2IEAOAgAjBjy5CEhEMfYICBgfPnjdLjj+CgMHiC3JknDhhoINw4jCAB0IJQIANR4QjPAH4A/AFA"))
|
buffer : require("heatshrink").decompress(atob("AH4A/AEkQuPHCJ0ChEAwARNjAjBjgjOhs06Q2OEYVx4ARMhEggUMkANIDoIgBoEEgEBNxJEC6ZrBAAMwNxAjDNYcHNxIjB7dtEwIHBwRoKj158+cuPEjlwCRAjC23bpu0wRNDAAsHEYWeEwaSJ6YjCAQUNSRQjEzxQBWZMNEYlsmg2JWAIjCz95SoJuJggjDtuw6dMG5JKCz998wFBJRVNEYW0yaVBJRNhJQN9+4pCzhKJmBKC4YpB/fINxIgCzFxSoQ3J4ENm3CAQPb98wbpEcAQMYWwKYBNxMDXgc2/fv3g2IEAOAgAjBjy5CEhEMfYICBgfPnjdLjj+CgMHiC3JknDhhoINw4jCAB0IJQIANR4QjPAH4A/AFA"))
|
||||||
}
|
};
|
||||||
|
|
||||||
var iconMoon = {
|
var iconMoon = {
|
||||||
text: "MOON",
|
|
||||||
width : 50, height : 50, bpp : 3,
|
width : 50, height : 50, bpp : 3,
|
||||||
transparent : 1,
|
transparent : 1,
|
||||||
buffer : require("heatshrink").decompress(atob("AH4AQjlx44CCCZsg8eOkHDwAQKEYgmPhEgEQM48AOIgMHEYoCB4ATI8UAmH/x04JoRuJsImHuBKLn37EwZuIgEQOI8cEpXj/yYBhE8+YNGgkYoJxITBUPnAaC///nC+FjBuIOJZEB8YeCh/8AoYACoMEEAnEjhQDPQJKJ/DCDAoi5DoLdHAoMQgLjFWYPOnngh02IwXzwDjEgPGEYS8BI4MBYoSVG4fP/nghkAgZrDkngJQqSG4gvBg4sBQgkImHihEAWwP8ZBMBEYl5/+cSoVAGQIUFh04weJn///0gj/OEw5KEz45BzhuCTYQAEgePB4IACAoJuBnAQEa4XHjxKB//xFgWHJQsCRgMDEonipwjENwUBDQNx8+evvn/hTDLw3igE+EgZxB8UOXIvEJQUfEYOfv53DEQkgga5BJQvzx84cAj+CDoNh8/eEYJKDuCSEcocnEon+/7xEgFBIIcfB4Mf/IICXI2DgDdBAAn758gCIq5Dv4zBvJuIOIfjEgvP/ARHgwdCB4P3AoTdFAAk4EYk8SQgAFTALaDSQwAGh08//vnDmBABYmEEZYAzA=="))
|
buffer : require("heatshrink").decompress(atob("AH4AQjlx44CCCZsg8eOkHDwAQKEYgmPhEgEQM48AOIgMHEYoCB4ATI8UAmH/x04JoRuJsImHuBKLn37EwZuIgEQOI8cEpXj/yYBhE8+YNGgkYoJxITBUPnAaC///nC+FjBuIOJZEB8YeCh/8AoYACoMEEAnEjhQDPQJKJ/DCDAoi5DoLdHAoMQgLjFWYPOnngh02IwXzwDjEgPGEYS8BI4MBYoSVG4fP/nghkAgZrDkngJQqSG4gvBg4sBQgkImHihEAWwP8ZBMBEYl5/+cSoVAGQIUFh04weJn///0gj/OEw5KEz45BzhuCTYQAEgePB4IACAoJuBnAQEa4XHjxKB//xFgWHJQsCRgMDEonipwjENwUBDQNx8+evvn/hTDLw3igE+EgZxB8UOXIvEJQUfEYOfv53DEQkgga5BJQvzx84cAj+CDoNh8/eEYJKDuCSEcocnEon+/7xEgFBIIcfB4Mf/IICXI2DgDdBAAn758gCIq5Dv4zBvJuIOIfjEgvP/ARHgwdCB4P3AoTdFAAk4EYk8SQgAFTALaDSQwAGh08//vnDmBABYmEEZYAzA=="))
|
||||||
}
|
};
|
||||||
|
|
||||||
var iconMars = {
|
var iconMars = {
|
||||||
text: "MARS",
|
|
||||||
width : 50, height : 50, bpp : 3,
|
width : 50, height : 50, bpp : 3,
|
||||||
transparent : 1,
|
transparent : 1,
|
||||||
buffer : require("heatshrink").decompress(atob("AH4ATjlwCJ+Dh0wwAQMg0cuPHjFhCZkDps0yVJkmQCBMEjFx42atOmzQmLhMkEYQCCCREQoOGEYmmzB0IEY4CBkARGoJKBEYQCEzgSGkGSpAjDyYCCphuGiFhJQgCD8ASFgRHGAQKbB6BuHJRGeOIsINxEk6dNmARDgMEjQjHAQPnVQojIyZKB6YSDNwK5FAQt54BuDXJIjBEwK5EgxKKXgq5BJRdgXIojJAQJKMcAM0EwM2JUApDoCVFExa7FkGCgAmIkAREEwUEjAmHCIgABhEggQmFpACBCIojBEwRQCzVhwkQU4YADgQmBwQCCI4IFBCAojFAQojGJQQjDAQgRGEZICBEo4gFyUIkilFJQUYEAZrBAQMYNw5KDSQSbCNwwABgOGEwgCBsPACQ5xGwdNnARJcAVh48evvnCJK8Chs+/fv33gCRcB48cuPHCBYA/ADAA=="))
|
buffer : require("heatshrink").decompress(atob("AH4ATjlwCJ+Dh0wwAQMg0cuPHjFhCZkDps0yVJkmQCBMEjFx42atOmzQmLhMkEYQCCCREQoOGEYmmzB0IEY4CBkARGoJKBEYQCEzgSGkGSpAjDyYCCphuGiFhJQgCD8ASFgRHGAQKbB6BuHJRGeOIsINxEk6dNmARDgMEjQjHAQPnVQojIyZKB6YSDNwK5FAQt54BuDXJIjBEwK5EgxKKXgq5BJRdgXIojJAQJKMcAM0EwM2JUApDoCVFExa7FkGCgAmIkAREEwUEjAmHCIgABhEggQmFpACBCIojBEwRQCzVhwkQU4YADgQmBwQCCI4IFBCAojFAQojGJQQjDAQgRGEZICBEo4gFyUIkilFJQUYEAZrBAQMYNw5KDSQSbCNwwABgOGEwgCBsPACQ5xGwdNnARJcAVh48evvnCJK8Chs+/fv33gCRcB48cuPHCBYA/ADAA=="))
|
||||||
}
|
};
|
||||||
|
|
||||||
var iconSatellite = {
|
var iconSatellite = {
|
||||||
text: "GPS ON",
|
|
||||||
width : 50, height : 50, bpp : 3,
|
width : 50, height : 50, bpp : 3,
|
||||||
transparent : 2,
|
transparent : 2,
|
||||||
buffer : require("heatshrink").decompress(atob("pMkyQC/ATGXhIRPyNl0gmPjlwCJ9ly1aCJ1c+fHJR1Hy1ZJR1I+fPnlx6QRLpe+/JKBr5KMuYjBJQMdCJce/fvJQW0CJUlEYQCBSpvvJQbXJjl0NwnzNxGQwEOnHhgF78+WqQyIrFx48cAQXz4ShJgAABh0+8cP//9LJEhg4jDuP3//0LhGQgYlBgeAn///5cIy8MuAmDCIP/9I4HkmCEYMOgHfCQWkCI0cuBuDgF/CIP+CI1Ny1IkeAgHANwIAB/QRFrj7BhkxEwQRC/4RFpbXDgSVBg4RCSorXDI4MJAQMfCIP8cwImDn37fwN58+kwHgLgSVFub7CI4NyBAJKDLgkuEYX78+evKtCLg0jEYRKC58JMoRcFkwjDJQTFDl65EkojEAQMdcwn/+gFC3YjEJQLXEpYRDWwQmEdI6SHAQO0CJUkx4jDF4gCIJQgRMXIjCEARIjCCJ2XEYPKCJqJBJQIROcAUpCJ0kybaDARtdCKAC2kAA="))
|
buffer : require("heatshrink").decompress(atob("pMkyQC/ATGXhIRPyNl0gmPjlwCJ9ly1aCJ1c+fHJR1Hy1ZJR1I+fPnlx6QRLpe+/JKBr5KMuYjBJQMdCJce/fvJQW0CJUlEYQCBSpvvJQbXJjl0NwnzNxGQwEOnHhgF78+WqQyIrFx48cAQXz4ShJgAABh0+8cP//9LJEhg4jDuP3//0LhGQgYlBgeAn///5cIy8MuAmDCIP/9I4HkmCEYMOgHfCQWkCI0cuBuDgF/CIP+CI1Ny1IkeAgHANwIAB/QRFrj7BhkxEwQRC/4RFpbXDgSVBg4RCSorXDI4MJAQMfCIP8cwImDn37fwN58+kwHgLgSVFub7CI4NyBAJKDLgkuEYX78+evKtCLg0jEYRKC58JMoRcFkwjDJQTFDl65EkojEAQMdcwn/+gFC3YjEJQLXEpYRDWwQmEdI6SHAQO0CJUkx4jDF4gCIJQgRMXIjCEARIjCCJ2XEYPKCJqJBJQIROcAUpCJ0kybaDARtdCKAC2kAA="))
|
||||||
}
|
};
|
||||||
|
|
||||||
var iconAlarm = {
|
|
||||||
text: "TIMER",
|
|
||||||
width : 50, height : 50, bpp : 3,
|
|
||||||
transparent : 1,
|
|
||||||
buffer : require("heatshrink").decompress(atob("kmSpICEp//BAwCJn/+CJ8k//5CKAABCJs8uPH//x48EI5YjCAARNKEYUcv//jgFBExEnEYoAC+QmHIgIgC/gpCuPBCI2fIgU4AQXjA4P8CIuTEYZKBAolwHApXBEAWP//jxwpBAALaFDoYCIiQmDDIP4EAT+CEwnJEwYjLAQLaFEYomDKALmDNwoCIOIZuD8AkFgCYDHAQjMAQTdDNwOAEg0Dx0/cYeREZtxQYOTHgJuHOIvkXJy8DNwIACJQ8Ah4NDAAfxEZARHOIIkHg4jQAQb1CQ4KVJgEOnDIBSoIjNAQPBcAaVJcAKVBcDGOcD7OBMQM48BuH8f//JKCnhKNggRBkmfTQJxBEwhuD/gRCyVHJRlyCIVJXgYmB8ZQBAoIKBXIQmCOIt/NxAUCOIImCIgIpCBAJuDAQZEE/huIAQWTDgImBTYQGC8gRFcYpKFCI8kDwQAFCJBfBEAX/+IjBiQRIEw4jJAQc8v//NYwCIOgJrIJpA1OcwbaFAQWQA="))
|
|
||||||
}
|
|
||||||
|
|
||||||
var iconCharging = {
|
var iconCharging = {
|
||||||
text: "CHARGE",
|
|
||||||
width : 50, height : 50, bpp : 3,
|
width : 50, height : 50, bpp : 3,
|
||||||
transparent : 5,
|
transparent : 5,
|
||||||
buffer : require("heatshrink").decompress(atob("23btugAwUBtoICARG0h048eODQYCJ6P/AAUCCJfbo4SDxYRLtEcuPHjlwgoRJ7RnIloUHoYjDAQfAExEAwUIkACEkSAIEYwCBhZKH6EIJI0CJRFHEY0BJRWBSgf//0AJRYSE4BKLj4SE8BKLv4RD/hK/JS2AXY0gXwRKG4cMmACCJQMAg8csEFJQsBAwfasEAm379u0gFbcBfHzgFBz1xMQZKBjY/D0E2+BOChu26yVEEYdww+cgAFCg+cgIfB6RKF4HbgEIkGChEAthfCJQ0eEAIjBBAMxk6GCJQtgtyVBwRKBAQMbHAJKGXIIFCgACBhl54qVG2E+EAJKBJoWAm0WJQ6SCXgdxFgMLJQvYjeAEAUwFIUitEtJQ14NwUHgEwKYZKGwOwNYX7XgWCg3CJQ5rB4MevPnAoPDJRJrCgEG/ECAoNsJRUwoEesIIBiJKI3CVDti/CJRKVDiJHBSo0YsOGjED8AjBcAcIgdhcAXAPIUAcAYIBcA4dBAQUG8BrBgBuCgOwcBEeXIK2BBAIFBgRqBGoYAChq8CcYUE4FbUYOACQsHzgjDgwFBCIImBAQsDtwYD7cAloRI22B86YBw5QBgoRJ7dAgYEDCJaeBJoMcsARMAQNoJIIRE6A"))
|
buffer : require("heatshrink").decompress(atob("23btugAwUBtoICARG0h048eODQYCJ6P/AAUCCJfbo4SDxYRLtEcuPHjlwgoRJ7RnIloUHoYjDAQfAExEAwUIkACEkSAIEYwCBhZKH6EIJI0CJRFHEY0BJRWBSgf//0AJRYSE4BKLj4SE8BKLv4RD/hK/JS2AXY0gXwRKG4cMmACCJQMAg8csEFJQsBAwfasEAm379u0gFbcBfHzgFBz1xMQZKBjY/D0E2+BOChu26yVEEYdww+cgAFCg+cgIfB6RKF4HbgEIkGChEAthfCJQ0eEAIjBBAMxk6GCJQtgtyVBwRKBAQMbHAJKGXIIFCgACBhl54qVG2E+EAJKBJoWAm0WJQ6SCXgdxFgMLJQvYjeAEAUwFIUitEtJQ14NwUHgEwKYZKGwOwNYX7XgWCg3CJQ5rB4MevPnAoPDJRJrCgEG/ECAoNsJRUwoEesIIBiJKI3CVDti/CJRKVDiJHBSo0YsOGjED8AjBcAcIgdhcAXAPIUAcAYIBcA4dBAQUG8BrBgBuCgOwcBEeXIK2BBAIFBgRqBGoYAChq8CcYUE4FbUYOACQsHzgjDgwFBCIImBAQsDtwYD7cAloRI22B86YBw5QBgoRJ7dAgYEDCJaeBJoMcsARMAQNoJIIRE6A"))
|
||||||
}
|
};
|
||||||
|
|
||||||
var iconNoBattery = {
|
var iconNoBattery = {
|
||||||
text: "NO BAT",
|
text: "NO BAT",
|
||||||
width : 50, height : 50, bpp : 3,
|
width : 50, height : 50, bpp : 3,
|
||||||
transparent : 1,
|
transparent : 1,
|
||||||
buffer : require("heatshrink").decompress(atob("kmSpIC/AWMyoQIFsmECJFJhMmA4QXByVICIwODAQ4RRFIQGD5JVLkIGDzJqMyAGDph8MiRKGyApEAoZKFyYIDQwMkSQNkQZABBhIIOOJRuEL5gRIAUKACVQMhmUSNYNDQYJTBBwYFByGTkOE5FJWYNMknCAQKYCiaSCpmGochDoSYBhMwTAZrChILBhmEzKPBF4ImBTAREBDoMmEwJVDoYjBycJFgWEJQRuLJQ1kmQCCjJlCBYbjCagaDBwyDBmBuBF4TjJAUQKINBChCDQxZBcZIIQF4NIgEAgKSDiQmEVQKMBoARBAAMCSQLLBVoxqKL4gaCChVCNwoRKOIo4CJIgABBoSMHpIRFgDdJOIJUBCAUJRgJuEAQb+DIIgRIAX4C/ASOQA"))
|
buffer : require("heatshrink").decompress(atob("kmSpIC/AWMyoQIFsmECJFJhMmA4QXByVICIwODAQ4RRFIQGD5JVLkIGDzJqMyAGDph8MiRKGyApEAoZKFyYIDQwMkSQNkQZABBhIIOOJRuEL5gRIAUKACVQMhmUSNYNDQYJTBBwYFByGTkOE5FJWYNMknCAQKYCiaSCpmGochDoSYBhMwTAZrChILBhmEzKPBF4ImBTAREBDoMmEwJVDoYjBycJFgWEJQRuLJQ1kmQCCjJlCBYbjCagaDBwyDBmBuBF4TjJAUQKINBChCDQxZBcZIIQF4NIgEAgKSDiQmEVQKMBoARBAAMCSQLLBVoxqKL4gaCChVCNwoRKOIo4CJIgABBoSMHpIRFgDdJOIJUBCAUJRgJuEAQb+DIIgRIAX4C/ASOQA"))
|
||||||
}
|
};
|
||||||
|
|
||||||
// Font to use:
|
// Font to use:
|
||||||
// <link href="https://fonts.googleapis.com/css2?family=Antonio:wght@400;700&display=swap" rel="stylesheet">
|
// <link href="https://fonts.googleapis.com/css2?family=Antonio:wght@400;700&display=swap" rel="stylesheet">
|
||||||
Graphics.prototype.setFontAntonioSmall = function(scale) {
|
Graphics.prototype.setFontAntonioMedium = function(scale) {
|
||||||
// Actual height 18 (17 - 0)
|
// Actual height 20 (19 - 0)
|
||||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAf4Mf/sYAMAAAAAAfgAfAAAAAfgAeAAAAAAiAAj8H/4fyEAv8f/gfiAAgAAAAD54H98eOPHn8Hz8AhwAAAP8Af+AYGAYCAf+AP8MAB8AHwA+AD4AfAAcf4A/8AwMAwMA/8Af4AAAAAwGD8f/8f8MY/cfz4PD8AHMAAAfAAeAAAAAAAAP/+f//YADAAAQABYADf//P/+AAAAAANAAPAAfwAfgAPAANAAAAAAEAAEAA/AA/AAEAAEAAAAAAZAAfAAYAAAAIAAIAAIAAIAAAAAAAAAMAAMAAAAAAAAEAB8Af4H+AfwAcAAAAAP/4f/8YAMf/8f/8H/wAAAAAAEAAMAAf/8f/8f/8AAAAAAAAAHgcfh8cH8YPMf8MPwEAAAAAAOB4eB8YYMY4Mf/8Pn4AAAAAgAHwA/wPwwf/8f/8AAwAAgAAAf54f58ZwMZwMY/8Qf4AAAAAAP/4f/8YYMYYMff8HP4AAAQAAYAAYD8Y/8f/AfgAcAAAAAAAAPv4f/8YYMY8Mf/8Pn4AAAAAAP94f98YGMcMMf/8H/wAAAAAABgwBgwAAAAAABgABg/Bg8AAAAEAAOAAbAA7gAxgBwwASAAbAAbAAbAAbAASAAAAAxwA5gAbAAPAAOAAAAPAAfHcYPcf8Af4AHgAAAAAAAB/gH/wOA4Y/MZ/sbAsbBkb/MZ/sOBsH/AAAAAAMAP8f/4fwwf4wH/8AH8AAMAAAf/8f/8YYMYYMf/8P/4ADgAAAP/4f/8YAMYAMfj8Pj4AAAAAAf/8f/8YAMYAMf/8P/4B/AAAAf/8f/8YMMYMMYIMAAAAAAf/8f/8YYAYYAYYAAAAAAAP/4f/8YAMYIMfP8Pv8AAAAAAf/8f/8AMAAMAf/8f/8f/8AAAAAAf/8f/8AAAAAAAD4AB8AAMf/8f/4f/gAAAAAAf/8f/8A+AD/gfj4eA8QAEAAAf/8f/8AAMAAMAAMAAAf/8f/8f8AB/wAB8AP8P/Af/8f/8AAAAAAf/8f/8HwAA+AAPwf/8f/8AAAAAAP/4f/8YAMYAMf/8P/4AAAAAAf/8f/8YGAYGAf8AP8ABAAAAAf/w//4wAYwAc//+f/yAAAAAAf/8f/8YMAYMAf/8f/8DA8CAAPj4fz8Y4MeeMfP8HD4YAAYAAf/8f/8YAAQAAAAAf/4f/8AAMAAMf/8f/4AAAYAAf4AP/4AP8AP8f/4fwAQAAYAAf8AP/8AD8D/8f8Af8AD/8AD8f/8f8AAAAQAEeB8P/4B/AP/4fA8QAEYAAfAAP4AB/8H/8fwAcAAAAMYD8Y/8f/MfwMcAMAAAf/+f//YADYADAAAAAAfAAf8AB/wAH8AAMQACYADf//f//AAAAA"), 32, atob("BAUHCAcTCAQFBQgGBAYFBggICAgICAgICAgEBQYGBggNCAgICAcHCAkECAgGCwkICAgIBwYICAwHBwYGBgY="), 18+(scale<<8)+(1<<16));
|
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAA//mP/5gAAAAAAAAAAAAA/gAMAAAAAA/gAPAAAEIIBP+H/8D+IYBP+H/8D+IABCAAwIAfnwP8+PHh448eP3+B4fAAAAAAAH/AD/4AwGAMBgD/4Af8GAAPgAPgAfgAfAAfAA+AAOP/AH/4BgGAYBgH/4A/8AAAAAAAAAQAA/B+f4/+GMPhjv/4/h8Dg/gAcYwAAPwADgAAAAAAAAB//8///sAAaAACAAAMAAb//+f//AAAAAAAbAAGwAA4AA/wADgABsAAbAAAAAAAgAAMAAPwAD8AAMAADAAAAAAAAAAHAAB/AAOAAAAAAAAMAADAAAwAAMAACAAAAAAAAAABgAAYAAAAAAAAA4AD+AP+A/4A/gAOAAAAAAAAAH//j//8wADMAAz//8f/+AAAAAAAMAADAABgAA//+P//gAAAAAAAAAAAAAfgfP4fzAfswfDP/gx/gMAAAHgPj4D8wMDMHAz//8f3+AAEAAAAADwAH8APzA/AwP//j//4AAwAAAD/Hw/x+MwBjOAYz/+Mf/AAAAAAAH//j//8wYDMGAz9/8fP+AAcDAAAwAAMAfjB/4z/wP+AD4AAwAAAAOB/f4///MHAzBwM///H9/gAAAAAAH/Pj/78wGDMBgz//8f/+AAAAAAADhwA4cAAAAAAAAAAAAAADh/A4fgAAAAOAAHwABsAA7gAccAGDAAAAANgADYAA2AANgADYAA2AAAAAAAABgwAccADuAAbAAHwAA4AAAAHwAD8c4/POMHAD/wAfwAAAAAAAAD/wD//B4B4Y/HMf8zMBMyATMwczP+M4BzHwcgf+AA+AAAAAAD4A/+P/8D+DA/4wH/+AB/4AAeAAAAAAA//+P//jBgYwYGP//j//4PH4AAAAAAAf/+P//zgAcwADP4fz+P4Ph8AAAAAAA//+P//jAAYwAGPADj//4P/4AAAAAAA//+P//jBgYwYGMGBgAAAAAAP//j//4wYAMGADBgAAAAAAAA//w///PAHzAQM4MHP7/x+/8AAAAAAD//4//+AGAABgAAYAP//j//4AAAAAAAAAA//+P//gAAAAAAAAAAAHwAB+AABgAAY//+P//AAAAAAAAAAD//4//+APgAf+Afj8PgPjAAYAAAAAAD//4//+AABgAAYAAGAAAAAAA//+P//j/gAD/wAB/gAP4B/4P/AD//4//+AAAAAAAAAAP//j//4P4AAfwAA/g//+P//gAAAAAAAAAA//g//+PAHjAAY4AOP//h//wAAAAAAD//4//+MDADAwA4cAP/AB/gAAAAAAAA//g//+PAHjAAc4APv//5//yAAAAAAD//4//+MGADBgA48AP//h+f4AAAAAAB+Pw/z+MOBjBwY/P+Hx/AAHgwAAMAAD//4//+MAADAAAAAAP//D//4AAOAABgAA4//+P//AAAAwAAP8AD//AA/+AAfgP/4//gPwAAAAA+AAP/4Af/4AD+A//j/wA/wAD/+AA/4B/+P/+D+AAAAAMADj8P4P/4A/4B//w+A+MABgAAA4AAPwAB/gAB/+A//j/gA+AAMAAAAAYwB+MH/jf+Y/8GPwBjAAAAAAP//7//+wABsAAYAAAAAAPAAD/gAH/gAD/gAD4AACAAADAAGwABv//7//+AAAA=="), 32, atob("BQUHCAgVCQQFBQkHBQcFBwgICAgICAgICAgFBQcHBwgPCQkJCQcHCQoFCQkHDQoJCQkJCAYJCQ0ICAcGBwY="), 20+(scale<<8)+(1<<16));
|
||||||
}
|
};
|
||||||
|
|
||||||
Graphics.prototype.setFontAntonioLarge = function(scale) {
|
Graphics.prototype.setFontAntonioLarge = function(scale) {
|
||||||
// Actual height 34 (34 - 1)
|
// Actual height 39 (39 - 1)
|
||||||
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAADwAAAAAeAAAAADwAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAD+AAAAH/wAAAP/+AAAf/+AAA//8AAB//4AAD//wAAD//gAAAf/AAAAD+AAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAB////gA/////AP////8D/////wfAAAA+DwAAADweAAAAeDwAAADwf////+D/////wP////8Af///+AAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAOAAAAADwAAAAAeAAAAAHgAAAAB/////wf////+D/////wf////+D/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AAPwH/4AP+B//AH/wf/4D/+D4AB/9weAAf4ODwAP8BweAP/AOD///gBwP//wAOA//4ABwB/4AAOAAAAAAAAAAAAAAAAAAAAB8AA/gA/gAH/AP8AA/8D/gAH/wfAHAA+DwA4ADweAHgAeDwB8ADwf7/+H+D/////gP/9//8A//H/+AA/AH/AAAAAAAAAAAAAAAAAABwAAAAD+AAAAD/wAAAH/+AAAH/5wAAH/wOAAP/gBwAP/gAOAD/////wf////+D/////wf////+AAAABwAAAAAOAAAAABwAAAAAAAAAAAAAAAAAAeAD//4D/Af//Af8D//4D/wf//Af+DwPAADweB4AAeDwPAADweB///+DwP///weA///8DwD//+AAAA/8AAAAAAAAAAAAAAAAAAAAAA////AA/////AP////8D/////wfgPAB+DwB4ADweAOAAeDwBwADwf+PAA+D/x///wP+H//8A/wf//AAAA//gAAAAAAAAAAAAADgAAAAAeAAAAADwAAAAAeAAAD+DwAAP/weAA//+DwA///weB///8Dx//8AAf//wAAD//gAAAf/AAAAD/AAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAD/wf/wB//v//AP////8D/////weAPwAeDwA8ADwcAHAAeDwB8ADwf////+D/////wP/9//8A//H//AA/AD/AAAAAAAAAAAAAAAAAAAAAD//gfAA///D/AP//8f8D///j/weAA8A+DwADgDweAAcAeDwAHgDwf////+B/////gP////8Af///+AAP//4AAAAAAAAAAAAAAAAAAAAAAD4AfAAAfAD4AAD4AfAAAfAD4AAD4AfAAAAAAAAAAAAAA=="), 46, atob("Cg4QEBAQEBAQEBAQCQ=="), 39+(scale<<8)+(1<<16));
|
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAAPgAAAAAB8AAAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAD8AAAAAH/gAAAAP/8AAAAf//gAAA///AAAB//+AAAD//8AAAH//4AAAP//wAAAB//gAAAAP/AAAAAB+AAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH///AAAf////8AP/////4B//////Af/////8D8AAAAfgeAAAAA8DwAAAAHgeAAAAA8D//////gf/////8B//////AP/////wAf////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAHgAAAAAA8AAAAAAPgAAAAAB4AAAAAAf/////gP/////8B//////gP/////8B//////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAD/+AAP8A//wAP/gP/+AH/8D//wD//gfgAA//8DwAAf+HgeAAP/A8DwAH/gHgfgP/wA8D///4AHgP//+AA8A///AAHgB//AAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4AA/gAD/AAH/gA/4AA/+AP/AAH/4D/4AA//gfgA4AB8DwAPAAHgeAB4AA8DwAPgAHgfAD+AB8D//////gP/////4B//5//+AD/+H//gAH/AH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP4AAAAAP/AAAAAP/4AAAAP//AAAAP/x4AAAf/wPAAAf/gB4AAf/AAPAAP/AAB4AB//////gP/////8B//////gP/////8AAAAAPAAAAAAB4AAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//wD/AB///Af+AP//4D/4B///Af/gP//4B/8B4D4AAPgPAeAAA8B4DwAAHgPAfAAB8B4D////gPAf///4B4B////APAD///gAAAD//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///AAAP////4AH/////wB//////Af/////8D8APAA/geADwAB8DwAeAAHgeADwAA8D4AeAAPgf/j+AH8B/8f///gP/h///4Af8H//+AAPgP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAAAAPAAAAAAB4AAAABgPAAAA/8B4AAB//gPAAD//8B4AH///gPAH///8B4P//+AAPH//wAAB///gAAAP//AAAAB/+AAAAAP+AAAAAB+AAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/4A/+AAf/w//+AP//v//4B//////Af/////8D4AfwAPgeAB8AA8DwAHAAHgeAB8AA8D4Af4APgf/////8B//////AP//v//4A//4//8AA/4A/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/+AAAAD//+D/gB///4f+AP///j/4D///8f/gfAAHgB8DwAA8AHgeAAHgA8DwAA8AHgfgAHgB8D//////gP/////4A/////+AD/////gAD////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAfgAAB+AD8AAAPwAfgAAB+AD8AAAPwAfgAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DBATExMTExMTExMTCw=="), 45+(scale<<8)+(1<<16));
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Draw watch face
|
* Draw watch face
|
||||||
|
|
@ -108,117 +119,299 @@ function queueDraw() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function printData(key, y){
|
function printData(key, y, c){
|
||||||
g.setFontAlign(-1,-1,0);
|
g.setFontAlign(-1,-1,0);
|
||||||
|
var text = "ERR";
|
||||||
|
var value = "NOT FOUND";
|
||||||
|
|
||||||
if(key == "Battery"){
|
if(key == "Battery"){
|
||||||
var bat = E.getBattery();
|
text = "BAT";
|
||||||
g.drawString("BAT:", 30, y);
|
value = E.getBattery() + "%";
|
||||||
g.drawString(bat+ "%", 68, y);
|
|
||||||
|
|
||||||
} else if(key == "Steps"){
|
} else if(key == "Steps"){
|
||||||
var steps = getSteps();
|
text = "STEP";
|
||||||
g.drawString("STEP:", 30, y);
|
value = getSteps();
|
||||||
g.drawString(steps, 68, y);
|
|
||||||
|
|
||||||
} else if(key == "Temp."){
|
} else if(key == "Temp."){
|
||||||
var temperature = Math.floor(E.getTemperature());
|
text = "TEMP";
|
||||||
g.drawString("TEMP:", 30, y);
|
value = Math.floor(E.getTemperature()) + "C";
|
||||||
g.drawString(temperature + "C", 69, y);
|
|
||||||
|
|
||||||
} else if(key == "HRM"){
|
} else if(key == "HRM"){
|
||||||
g.drawString("HRM:", 30, y);
|
text = "HRM";
|
||||||
g.drawString(hrmValue, 69, y);
|
value = hrmValue;
|
||||||
|
|
||||||
|
} else if (key == "VREF"){
|
||||||
|
text = "VREF";
|
||||||
|
value = E.getAnalogVRef().toFixed(2) + "V";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
g.setColor(c);
|
||||||
|
g.fillRect(133, y-2, 165 ,y+18);
|
||||||
|
g.fillCircle(161, y+8, 10);
|
||||||
|
g.setColor(cBlack);
|
||||||
|
g.drawString(text, 135, y);
|
||||||
|
|
||||||
|
g.setColor(c);
|
||||||
|
g.setFontAlign(1,-1,0);
|
||||||
|
g.drawString(value, 130, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawHorizontalBgLine(color, x1, x2, y, h){
|
||||||
|
g.setColor(color);
|
||||||
|
|
||||||
|
for(var i=0; i<h; i++){
|
||||||
|
g.drawLine(x1, y+i, x2,y+i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function drawLock(){
|
||||||
|
if(lcarsViewPos != 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g.setFontAntonioMedium();
|
||||||
|
g.setColor(cOrange);
|
||||||
|
g.clearRect(120, 10, g.getWidth(), 75);
|
||||||
|
g.drawString("LCARS", 128, 13);
|
||||||
|
if(connected){
|
||||||
|
g.drawString("CONN", 128, 33);
|
||||||
} else {
|
} else {
|
||||||
g.drawString("NOT FOUND", 30, y);
|
g.drawString("NOCON", 128, 33);
|
||||||
|
}
|
||||||
|
if(Bangle.isLocked()){
|
||||||
|
g.setColor(cPurple);
|
||||||
|
g.drawString("LOCK", 128, 53);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawState(){
|
||||||
|
if(lcarsViewPos != 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g.clearRect(20, 93, 77, 170);
|
||||||
|
g.setColor(cWhite);
|
||||||
|
var bat = E.getBattery();
|
||||||
|
var current = new Date();
|
||||||
|
var hours = current.getHours();
|
||||||
|
|
||||||
|
if(!isAlarmEnabled()){
|
||||||
|
var iconImg =
|
||||||
|
Bangle.isCharging() ? iconCharging :
|
||||||
|
bat < 30 ? iconNoBattery :
|
||||||
|
Bangle.isGPSOn() ? iconSatellite :
|
||||||
|
hours % 4 == 0 ? iconSaturn :
|
||||||
|
hours % 4 == 1 ? iconMars :
|
||||||
|
hours % 4 == 2 ? iconMoon :
|
||||||
|
iconEarth;
|
||||||
|
g.drawImage(iconImg, 29, 104);
|
||||||
|
} else {
|
||||||
|
// Alarm within symbol
|
||||||
|
g.setFontAntonioMedium();
|
||||||
|
g.setFontAlign(0, 0, 0);
|
||||||
|
g.setColor(cOrange);
|
||||||
|
g.drawString("ALARM", 29+25, 107);
|
||||||
|
g.setColor(cWhite);
|
||||||
|
g.setFontAntonioLarge();
|
||||||
|
g.drawString(getAlarmMinutes(), 29+25, 107+35);
|
||||||
|
}
|
||||||
|
|
||||||
|
g.setFontAlign(-1, -1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function drawPosition0(){
|
||||||
|
// Draw background image
|
||||||
|
g.drawImage(bgLeft, 0, 0);
|
||||||
|
drawHorizontalBgLine(cBlue, 25, 120, 0, 4);
|
||||||
|
drawHorizontalBgLine(cBlue, 130, 176, 0, 4);
|
||||||
|
drawHorizontalBgLine(cPurple, 20, 70, 80, 4);
|
||||||
|
drawHorizontalBgLine(cPurple, 80, 176, 80, 4);
|
||||||
|
drawHorizontalBgLine(cOrange, 35, 110, 87, 4);
|
||||||
|
drawHorizontalBgLine(cOrange, 120, 176, 87, 4);
|
||||||
|
|
||||||
|
// The last line is a battery indicator too
|
||||||
|
var bat = E.getBattery() / 100.0;
|
||||||
|
var batX2 = parseInt((172 - 35) * bat + 35);
|
||||||
|
drawHorizontalBgLine(cOrange, 35, batX2, 171, 5);
|
||||||
|
drawHorizontalBgLine(cPurple, batX2+10, 172, 171, 5);
|
||||||
|
|
||||||
|
// Draw logo
|
||||||
|
drawLock();
|
||||||
|
|
||||||
|
// Write time
|
||||||
|
g.setFontAlign(-1, -1, 0);
|
||||||
|
g.setColor(cWhite);
|
||||||
|
var currentDate = new Date();
|
||||||
|
var timeStr = locale.time(currentDate,1);
|
||||||
|
g.setFontAntonioLarge();
|
||||||
|
g.drawString(timeStr, 28, 10);
|
||||||
|
|
||||||
|
// Write date
|
||||||
|
g.setColor(cWhite);
|
||||||
|
g.setFontAntonioMedium();
|
||||||
|
var dayStr = locale.dow(currentDate, true).toUpperCase();
|
||||||
|
dayStr += " " + currentDate.getDate();
|
||||||
|
dayStr += " " + currentDate.getFullYear();
|
||||||
|
g.drawString(dayStr, 29, 56);
|
||||||
|
|
||||||
|
// Draw data
|
||||||
|
g.setFontAlign(-1, -1, 0);
|
||||||
|
g.setColor(cWhite);
|
||||||
|
printData(settings.dataRow1, 97, cOrange);
|
||||||
|
printData(settings.dataRow2, 122, cPurple);
|
||||||
|
printData(settings.dataRow3, 147, cBlue);
|
||||||
|
|
||||||
|
// Draw state
|
||||||
|
drawState();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawPosition1(){
|
||||||
|
// Draw background image
|
||||||
|
g.drawImage(bgRight, 149, 0);
|
||||||
|
drawHorizontalBgLine(cBlue, 0, 140, 0, 4);
|
||||||
|
drawHorizontalBgLine(cPurple, 0, 80, 80, 4);
|
||||||
|
drawHorizontalBgLine(cPurple, 90, 150, 80, 4);
|
||||||
|
drawHorizontalBgLine(cOrange, 0, 50, 87, 4);
|
||||||
|
drawHorizontalBgLine(cOrange, 60, 140, 87, 4);
|
||||||
|
drawHorizontalBgLine(cOrange, 0, 150, 171, 5);
|
||||||
|
|
||||||
|
// Draw steps bars
|
||||||
|
g.setColor(cWhite);
|
||||||
|
let health;
|
||||||
|
|
||||||
|
try {
|
||||||
|
health = require("health");
|
||||||
|
} catch(ex) {
|
||||||
|
g.setFontAntonioMedium();
|
||||||
|
g.drawString("MODULE HEALTH", 20, 110);
|
||||||
|
g.drawString("REQUIRED.", 20, 130);
|
||||||
|
g.drawString("MODULE HEALTH", 20, 20);
|
||||||
|
g.drawString("REQUIRED.", 20, 40);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plot HRM graph
|
||||||
|
if(plotWeek){
|
||||||
|
var data = new Uint16Array(32);
|
||||||
|
var cnt = new Uint8Array(32);
|
||||||
|
health.readDailySummaries(new Date(), h=>{
|
||||||
|
data[h.day]+=h.bpm;
|
||||||
|
if (h.bpm) cnt[h.day]++;
|
||||||
|
});
|
||||||
|
require("graph").drawBar(g, data, {
|
||||||
|
axes : true,
|
||||||
|
minx: 1,
|
||||||
|
gridx : 5,
|
||||||
|
gridy : 100,
|
||||||
|
width : 140,
|
||||||
|
height : 50,
|
||||||
|
x: 5,
|
||||||
|
y: 25
|
||||||
|
});
|
||||||
|
|
||||||
|
// Plot step graph
|
||||||
|
var data = new Uint16Array(32);
|
||||||
|
health.readDailySummaries(new Date(), h=>data[h.day]+=h.steps/1000);
|
||||||
|
var gridY = parseInt(Math.max.apply(Math, data)/2);
|
||||||
|
gridY = gridY <= 0 ? 1 : gridY;
|
||||||
|
require("graph").drawBar(g, data, {
|
||||||
|
axes : true,
|
||||||
|
minx: 1,
|
||||||
|
gridx : 5,
|
||||||
|
gridy : gridY,
|
||||||
|
width : 140,
|
||||||
|
height : 50,
|
||||||
|
x: 5,
|
||||||
|
y: 115
|
||||||
|
});
|
||||||
|
|
||||||
|
g.setFontAlign(1, 1, 0);
|
||||||
|
g.setFontAntonioMedium();
|
||||||
|
g.setColor(cWhite);
|
||||||
|
g.drawString("WEEK HRM", 154, 27);
|
||||||
|
g.drawString("WEEK STEPS [K]", 154, 115);
|
||||||
|
|
||||||
|
// Plot day
|
||||||
|
} else {
|
||||||
|
var data = new Uint16Array(24);
|
||||||
|
var cnt = new Uint8Array(24);
|
||||||
|
health.readDay(new Date(), h=>{
|
||||||
|
data[h.hr]+=h.bpm;
|
||||||
|
if (h.bpm) cnt[h.hr]++;
|
||||||
|
});
|
||||||
|
require("graph").drawBar(g, data, {
|
||||||
|
axes : true,
|
||||||
|
minx: 1,
|
||||||
|
gridx : 4,
|
||||||
|
gridy : 100,
|
||||||
|
width : 140,
|
||||||
|
height : 50,
|
||||||
|
x: 5,
|
||||||
|
y: 25
|
||||||
|
});
|
||||||
|
|
||||||
|
// Plot step graph
|
||||||
|
var data = new Uint16Array(24);
|
||||||
|
health.readDay(new Date(), h=>data[h.hr]+=h.steps);
|
||||||
|
var gridY = parseInt(Math.max.apply(Math, data)/1000)*1000;
|
||||||
|
gridY = gridY <= 0 ? 1000 : gridY;
|
||||||
|
require("graph").drawBar(g, data, {
|
||||||
|
axes : true,
|
||||||
|
minx: 1,
|
||||||
|
gridx : 4,
|
||||||
|
gridy : gridY,
|
||||||
|
width : 140,
|
||||||
|
height : 50,
|
||||||
|
x: 5,
|
||||||
|
y: 115
|
||||||
|
});
|
||||||
|
|
||||||
|
g.setFontAlign(1, 1, 0);
|
||||||
|
g.setFontAntonioMedium();
|
||||||
|
g.setColor(cWhite);
|
||||||
|
g.drawString("DAY HRM", 154, 27);
|
||||||
|
g.drawString("DAY STEPS", 154, 115);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw(){
|
function draw(){
|
||||||
|
|
||||||
// First handle alarm to show this correctly afterwards
|
// First handle alarm to show this correctly afterwards
|
||||||
handleAlarm();
|
handleAlarm();
|
||||||
|
|
||||||
// Next draw the watch face
|
// Next draw the watch face
|
||||||
g.reset();
|
g.reset();
|
||||||
g.clearRect(0, 24, g.getWidth(), g.getHeight());
|
g.clearRect(0, 0, g.getWidth(), g.getHeight());
|
||||||
|
|
||||||
// Draw background image
|
// Draw current lcars position
|
||||||
g.drawImage(backgroundImage, 0, 24);
|
if(lcarsViewPos == 0){
|
||||||
|
drawPosition0();
|
||||||
// Draw symbol
|
} else if (lcarsViewPos == 1) {
|
||||||
var bat = E.getBattery();
|
drawPosition1();
|
||||||
var timeInMinutes = getCurrentTimeInMinutes();
|
|
||||||
|
|
||||||
var iconImg =
|
|
||||||
isAlarmEnabled() ? iconAlarm :
|
|
||||||
Bangle.isCharging() ? iconCharging :
|
|
||||||
bat < 30 ? iconNoBattery :
|
|
||||||
Bangle.isGPSOn() ? iconSatellite :
|
|
||||||
timeInMinutes % 4 == 0 ? iconSaturn :
|
|
||||||
timeInMinutes % 4 == 1 ? iconMars :
|
|
||||||
timeInMinutes % 4 == 2 ? iconMoon :
|
|
||||||
iconEarth;
|
|
||||||
g.drawImage(iconImg, 115, 115);
|
|
||||||
|
|
||||||
// Alarm within symbol
|
|
||||||
g.setFontAlign(0,0,0);
|
|
||||||
g.setFontAntonioSmall();
|
|
||||||
g.drawString(iconImg.text, 115+25, 105);
|
|
||||||
if(isAlarmEnabled() > 0){
|
|
||||||
g.drawString(getAlarmMinutes(), 115+25, 115+25);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write time
|
|
||||||
var currentDate = new Date();
|
|
||||||
var timeStr = locale.time(currentDate,1);
|
|
||||||
g.setFontAlign(0,0,0);
|
|
||||||
g.setFontAntonioLarge();
|
|
||||||
g.drawString(timeStr, 60, 55);
|
|
||||||
|
|
||||||
// Write date
|
|
||||||
g.setFontAlign(-1,-1, 0);
|
|
||||||
g.setFontAntonioSmall();
|
|
||||||
|
|
||||||
var dayName = locale.dow(currentDate, true).toUpperCase();
|
|
||||||
var day = currentDate.getDate();
|
|
||||||
g.drawString(day, 100, 35);
|
|
||||||
g.drawString(dayName, 100, 55);
|
|
||||||
|
|
||||||
// Draw battery
|
|
||||||
printData(settings.dataRow1, 98);
|
|
||||||
printData(settings.dataRow2, 121);
|
|
||||||
printData(settings.dataRow3, 144);
|
|
||||||
|
|
||||||
// Queue draw in one minute
|
// Queue draw in one minute
|
||||||
queueDraw();
|
queueDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Step counter via widget
|
* Step counter via widget
|
||||||
*/
|
*/
|
||||||
function getSteps() {
|
function getSteps() {
|
||||||
if (stepsWidget() !== undefined)
|
var steps = 0
|
||||||
return stepsWidget().getSteps();
|
try {
|
||||||
return "???";
|
health = require("health");
|
||||||
}
|
} catch(ex) {
|
||||||
|
return steps;
|
||||||
function stepsWidget() {
|
|
||||||
if (WIDGETS.activepedom !== undefined) {
|
|
||||||
return WIDGETS.activepedom;
|
|
||||||
} else if (WIDGETS.wpedom !== undefined) {
|
|
||||||
return WIDGETS.wpedom;
|
|
||||||
}
|
}
|
||||||
return undefined;
|
|
||||||
|
health.readDay(new Date(), h=>steps+=h.steps);
|
||||||
|
return steps;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* HRM Listener
|
|
||||||
*/
|
|
||||||
Bangle.on('HRM', function (hrm) {
|
|
||||||
hrmValue = hrm.bpm;
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Handle alarm
|
* Handle alarm
|
||||||
|
|
@ -228,7 +421,7 @@ function getCurrentTimeInMinutes(){
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAlarmEnabled(){
|
function isAlarmEnabled(){
|
||||||
return settings.alarm > 0;
|
return settings.alarm >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAlarmMinutes(){
|
function getAlarmMinutes(){
|
||||||
|
|
@ -253,65 +446,130 @@ function handleAlarm(){
|
||||||
.then(() => new Promise(resolve => setTimeout(resolve, t)))
|
.then(() => new Promise(resolve => setTimeout(resolve, t)))
|
||||||
.then(() => Bangle.buzz(t, 1))
|
.then(() => Bangle.buzz(t, 1))
|
||||||
.then(() => new Promise(resolve => setTimeout(resolve, t)))
|
.then(() => new Promise(resolve => setTimeout(resolve, t)))
|
||||||
.then(() => Bangle.buzz(t, 1));
|
.then(() => Bangle.buzz(t, 1))
|
||||||
|
.then(() => new Promise(resolve => setTimeout(resolve, 5E3)))
|
||||||
// Update alarm state to disabled
|
.then(() => {
|
||||||
settings.alarm = -1;
|
// Update alarm state to disabled
|
||||||
Storage.writeJSON(SETTINGS_FILE, settings);
|
settings.alarm = -1;
|
||||||
|
Storage.writeJSON(SETTINGS_FILE, settings);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Swipe to set an alarm
|
* Listeners
|
||||||
*/
|
|
||||||
Bangle.on('swipe',function(dir) {
|
|
||||||
// Increase alarm
|
|
||||||
if(dir == -1){
|
|
||||||
if(isAlarmEnabled()){
|
|
||||||
settings.alarm += 5;
|
|
||||||
} else {
|
|
||||||
settings.alarm = getCurrentTimeInMinutes() + 5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrease alarm
|
|
||||||
if(dir == +1){
|
|
||||||
if(isAlarmEnabled() && (settings.alarm-5 > getCurrentTimeInMinutes())){
|
|
||||||
settings.alarm -= 5;
|
|
||||||
} else {
|
|
||||||
settings.alarm = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update UI
|
|
||||||
draw();
|
|
||||||
|
|
||||||
// Update alarm state
|
|
||||||
Storage.writeJSON(SETTINGS_FILE, settings);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Stop updates when LCD is off, restart when on
|
|
||||||
*/
|
*/
|
||||||
Bangle.on('lcdPower',on=>{
|
Bangle.on('lcdPower',on=>{
|
||||||
if (on) {
|
if (on) {
|
||||||
draw(); // draw immediately, queue redraw
|
// Whenever we connect to Gadgetbridge, reading data from
|
||||||
|
// health failed. Therefore, we update and read data from
|
||||||
|
// health iff the connection state did not change.
|
||||||
|
if(connected == NRF.getSecurityStatus().connected) {
|
||||||
|
draw();
|
||||||
|
} else {
|
||||||
|
connected = NRF.getSecurityStatus().connected
|
||||||
|
drawLock();
|
||||||
|
}
|
||||||
} else { // stop draw timer
|
} else { // stop draw timer
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
drawTimeout = undefined;
|
drawTimeout = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connected = NRF.getSecurityStatus().connected
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Bangle.on('lock', function(isLocked) {
|
||||||
|
drawLock();
|
||||||
|
});
|
||||||
|
|
||||||
|
Bangle.on('charging',function(charging) {
|
||||||
|
drawState();
|
||||||
|
});
|
||||||
|
|
||||||
|
Bangle.on('HRM', function (hrm) {
|
||||||
|
hrmValue = hrm.bpm;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function increaseAlarm(){
|
||||||
|
if(isAlarmEnabled()){
|
||||||
|
settings.alarm += 5;
|
||||||
|
} else {
|
||||||
|
settings.alarm = getCurrentTimeInMinutes() + 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage.writeJSON(SETTINGS_FILE, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function decreaseAlarm(){
|
||||||
|
if(isAlarmEnabled() && (settings.alarm-5 > getCurrentTimeInMinutes())){
|
||||||
|
settings.alarm -= 5;
|
||||||
|
} else {
|
||||||
|
settings.alarm = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage.writeJSON(SETTINGS_FILE, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Thanks to the app "gbmusic" for this code to detect swipes in all 4 directions.
|
||||||
|
Bangle.on("drag", e => {
|
||||||
|
if (!drag) { // start dragging
|
||||||
|
drag = {x: e.x, y: e.y};
|
||||||
|
} else if (!e.b) { // released
|
||||||
|
const dx = e.x-drag.x, dy = e.y-drag.y;
|
||||||
|
drag = null;
|
||||||
|
|
||||||
|
// Horizontal swipe
|
||||||
|
if (Math.abs(dx)>Math.abs(dy)+10) {
|
||||||
|
if(dx > 0){
|
||||||
|
lcarsViewPos = 0;
|
||||||
|
} else {
|
||||||
|
lcarsViewPos = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertical swipe
|
||||||
|
} else if (Math.abs(dy)>Math.abs(dx)+10) {
|
||||||
|
if(lcarsViewPos == 0){
|
||||||
|
if(dy > 0){
|
||||||
|
decreaseAlarm();
|
||||||
|
} else {
|
||||||
|
increaseAlarm();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only update the state and return to
|
||||||
|
// avoid a full draw as this is much faster.
|
||||||
|
drawState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(lcarsViewPos == 1){
|
||||||
|
plotWeek = dy < 0 ? true : false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
draw();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Lets start widgets, listen for btn etc.
|
||||||
|
*/
|
||||||
// Show launcher when middle button pressed
|
// Show launcher when middle button pressed
|
||||||
Bangle.setUI("clock");
|
Bangle.setUI("clock");
|
||||||
|
|
||||||
// Load widgets - needed by draw
|
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
|
/*
|
||||||
|
* we are not drawing the widgets as we are taking over the whole screen
|
||||||
|
* so we will blank out the draw() functions of each widget and change the
|
||||||
|
* area to the top bar doesn't get cleared.
|
||||||
|
*/
|
||||||
|
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
||||||
|
|
||||||
// Clear the screen once, at startup and draw clock
|
// Clear the screen once, at startup and draw clock
|
||||||
g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
|
g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
|
||||||
draw();
|
draw();
|
||||||
|
|
||||||
// After drawing the watch face, we can draw the widgets
|
// After drawing the watch face, we can draw the widgets
|
||||||
Bangle.drawWidgets();
|
// Bangle.drawWidgets();
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
require("heatshrink").decompress(atob("mEwgeevPnAQsc+fPngCE+/fvoCEvAbIA4/AgFzEZwRBjwjNvBUBEZ3eCIMOEZtwCIMBEZuARYU5EZecTocHEZf0CIcBEbvgaggjKTwIAEbQpoHAAiSEeoYQHJQr1CCBJKEIgcBI4xKFaIdt3AOFgfuAYMeEYLRBj1pLQ4ICuYjBAgPbtoRHhu3AYN5VoMGzVpI49502AgPPVoM27dsK48N23cgE5CgOmzVoCI4LBzCSB8EP2wjJgILBAYMAhIjBsAjJzVwg47C7YRJEYhfBEZXmEZ53CI4q2BEAiVCkwjCNYaMGboQjDkBfDCAbdB04EBgyPDC4YAD/dt2wRCHIM5njXCCAcHboOmCIQ0B5/nfYT6DFIIjBeAcOvM8+EAjitFEYJEBAANzEYOeeowjCFgUDzwjB+YrDgAgBEYWcA4Mc+YjCvAQCgftEANuDIYOBEYXPNwIAIg4OCCgXkCBEOEZDvBEAhEB4AjF/inB8+OJQOOvILBoAjGU4IFDAQYjGbQIdCAQt4EY0DEZACDEYceEZACDC4bLBEZwCO"))
|
require("heatshrink").decompress(atob("mEwgeevPnAQsc+fPngCE+/fvoCEvAbIA4/AgFzEZwRBjwjNvBUBEZ3eCIMOEZtwCIMBEZuARYU5EZecTocHEZf0CIcBEbvgaggjKTwIAEbQpoHAAiSEeoYQHJQr1CCBJKEIgcBI4xKFaIdt3AOFgfuAYMeEYLRBj1pLQ4ICuYjBAgPbtoRHhu3AYN5VoMGzVpI49502AgPPVoM27dsK48N23cgE5CgOmzVoCI4LBzCSB8EP2wjJgILBAYMAhIjBsAjJzVwg47C7YRJEYhfBEZXmEZ53CI4q2BEAiVCkwjCNYaMGboQjDkBfDCAbdB04EBgyPDC4YAD/dt2wRCHIM5njXCCAcHboOmCIQ0B5/nfYT6DFIIjBeAcOvM8+EAjitFEYJEBAANzEYOeeowjCFgUDzwjB+YrDgAgBEYWcA4Mc+YjCvAQCgftEANuDIYOBEYXPNwIAIg4OCCgXkCBEOEZDvBEAhEB4AjF/inB8+OJQOOvILBoAjGU4IFDAQYjGbQIdCAQt4EY0DEZACDEYceEZACDC4bLBEZwCO"))
|
||||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
|
@ -18,14 +18,14 @@
|
||||||
storage.write(SETTINGS_FILE, settings)
|
storage.write(SETTINGS_FILE, settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
var data_options = ['Battery', 'Steps', 'Temp.', "HRM"];
|
var data_options = ["Battery", "Steps", "Temp.", "HRM", "VREF"];
|
||||||
|
|
||||||
E.showMenu({
|
E.showMenu({
|
||||||
'': { 'title': 'LCARS Clock' },
|
'': { 'title': 'LCARS Clock' },
|
||||||
'< Back': back,
|
'< Back': back,
|
||||||
'Row 1': {
|
'Row 1': {
|
||||||
value: 0 | data_options.indexOf(settings.dataRow1),
|
value: 0 | data_options.indexOf(settings.dataRow1),
|
||||||
min: 0, max: 3,
|
min: 0, max: 4,
|
||||||
format: v => data_options[v],
|
format: v => data_options[v],
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.dataRow1 = data_options[v];
|
settings.dataRow1 = data_options[v];
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
},
|
},
|
||||||
'Row 2': {
|
'Row 2': {
|
||||||
value: 0 | data_options.indexOf(settings.dataRow2),
|
value: 0 | data_options.indexOf(settings.dataRow2),
|
||||||
min: 0, max: 3,
|
min: 0, max: 4,
|
||||||
format: v => data_options[v],
|
format: v => data_options[v],
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.dataRow2 = data_options[v];
|
settings.dataRow2 = data_options[v];
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
},
|
},
|
||||||
'Row 3': {
|
'Row 3': {
|
||||||
value: 0 | data_options.indexOf(settings.dataRow3),
|
value: 0 | data_options.indexOf(settings.dataRow3),
|
||||||
min: 0, max: 3,
|
min: 0, max: 4,
|
||||||
format: v => data_options[v],
|
format: v => data_options[v],
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.dataRow3 = data_options[v];
|
settings.dataRow3 = data_options[v];
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
|
@ -21,3 +21,4 @@
|
||||||
Add 'Delete All' option to message options
|
Add 'Delete All' option to message options
|
||||||
Now update correctly when 'require("messages").clearAll()' is called
|
Now update correctly when 'require("messages").clearAll()' is called
|
||||||
0.14: Hide widget when all unread notifications are dismissed from phone
|
0.14: Hide widget when all unread notifications are dismissed from phone
|
||||||
|
0.15: Don't buzz when Quiet Mode is active
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ var MESSAGES = require("Storage").readJSON("messages.json",1)||[];
|
||||||
if (!Array.isArray(MESSAGES)) MESSAGES=[];
|
if (!Array.isArray(MESSAGES)) MESSAGES=[];
|
||||||
var onMessagesModified = function(msg) {
|
var onMessagesModified = function(msg) {
|
||||||
// TODO: if new, show this new one
|
// TODO: if new, show this new one
|
||||||
if (msg && msg.new) {
|
if (msg && msg.new && !((require('Storage').readJSON('setting.json', 1) || {}).quiet)) {
|
||||||
if (WIDGETS["messages"]) WIDGETS["messages"].buzz();
|
if (WIDGETS["messages"]) WIDGETS["messages"].buzz();
|
||||||
else Bangle.buzz();
|
else Bangle.buzz();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,8 @@ exports.pushMessage = function(event) {
|
||||||
// otherwise load messages/show widget
|
// otherwise load messages/show widget
|
||||||
var loadMessages = Bangle.CLOCK || event.important;
|
var loadMessages = Bangle.CLOCK || event.important;
|
||||||
// first, buzz
|
// first, buzz
|
||||||
if (loadMessages && global.WIDGETS && WIDGETS.messages)
|
var quiet = (require('Storage').readJSON('setting.json',1)||{}).quiet;
|
||||||
|
if (!quiet && loadMessages && global.WIDGETS && WIDGETS.messages)
|
||||||
WIDGETS.messages.buzz();
|
WIDGETS.messages.buzz();
|
||||||
// after a delay load the app, to ensure we have all the messages
|
// after a delay load the app, to ensure we have all the messages
|
||||||
if (exports.messageTimeout) clearTimeout(exports.messageTimeout);
|
if (exports.messageTimeout) clearTimeout(exports.messageTimeout);
|
||||||
|
|
@ -51,7 +52,7 @@ exports.pushMessage = function(event) {
|
||||||
exports.messageTimeout = undefined;
|
exports.messageTimeout = undefined;
|
||||||
// if we're in a clock or it's important, go straight to messages app
|
// if we're in a clock or it's important, go straight to messages app
|
||||||
if (loadMessages) return load("messages.app.js");
|
if (loadMessages) return load("messages.app.js");
|
||||||
if (!global.WIDGETS || !WIDGETS.messages) return Bangle.buzz(); // no widgets - just buzz to let someone know
|
if (!quiet && (!global.WIDGETS || !WIDGETS.messages)) return Bangle.buzz(); // no widgets - just buzz to let someone know
|
||||||
WIDGETS.messages.show();
|
WIDGETS.messages.show();
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ WIDGETS["messages"]={area:"tl",width:0,draw:function() {
|
||||||
WIDGETS["messages"].width=0;
|
WIDGETS["messages"].width=0;
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
},buzz:function() {
|
},buzz:function() {
|
||||||
|
if ((require('Storage').readJSON('setting.json',1)||{}).quiet) return; // never buzz during Quiet Mode
|
||||||
let v = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrate || ".";
|
let v = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrate || ".";
|
||||||
function b() {
|
function b() {
|
||||||
var c = v[0];
|
var c = v[0];
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,4 @@
|
||||||
0.02: Corrected variable initialisation
|
0.02: Corrected variable initialisation
|
||||||
0.03: Advertise app name, added screenshots
|
0.03: Advertise app name, added screenshots
|
||||||
0.04: Advertise bar, GPS, HRM and mag services
|
0.04: Advertise bar, GPS, HRM and mag services
|
||||||
|
0.05: Refactored for efficiency, corrected sensor value inaccuracies
|
||||||
|
|
@ -124,27 +124,16 @@ function transmitUpdatedSensorData() {
|
||||||
isNewMagData = false;
|
isNewMagData = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
NRF.setAdvertising(data, { showName: false, interval: 200 });
|
let interval = 1000 / data.length;
|
||||||
|
NRF.setAdvertising(data, { showName: false, interval: interval });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Encode the bar service data to fit in a Bluetooth PDU
|
// Encode the bar service data to fit in a Bluetooth PDU
|
||||||
function encodeBarServiceData() {
|
function encodeBarServiceData() {
|
||||||
let tEncoded = Math.round(bar.temperature * 100);
|
let t = toByteArray(Math.round(bar.temperature * 100), 2, true);
|
||||||
let pEncoded = Math.round(bar.pressure * 100);
|
let p = toByteArray(Math.round(bar.pressure * 1000), 4, false);
|
||||||
let eEncoded = Math.round(bar.altitude * 100);
|
let e = toByteArray(Math.round(bar.altitude * 100), 3, true);
|
||||||
|
|
||||||
if(bar.temperature < 0) {
|
|
||||||
tEncoded += 0x10000;
|
|
||||||
}
|
|
||||||
if(bar.altitude < 0) {
|
|
||||||
eEncoded += 0x1000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
let t = [ tEncoded & 0xff, (tEncoded >> 8) & 0xff ];
|
|
||||||
let p = [ pEncoded & 0xff, (pEncoded >> 8) & 0xff, (pEncoded >> 16) & 0xff,
|
|
||||||
(pEncoded >> 24) & 0xff ];
|
|
||||||
let e = [ eEncoded & 0xff, (eEncoded >> 8) & 0xff, (eEncoded >> 16) & 0xff ];
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
0x02, 0x01, 0x06, // Flags
|
0x02, 0x01, 0x06, // Flags
|
||||||
|
|
@ -157,52 +146,26 @@ function encodeBarServiceData() {
|
||||||
|
|
||||||
// Encode the GPS service data using the Location and Speed characteristic
|
// Encode the GPS service data using the Location and Speed characteristic
|
||||||
function encodeGpsServiceData() {
|
function encodeGpsServiceData() {
|
||||||
let latEncoded = Math.round(gps.lat * 10000000);
|
let s = toByteArray(Math.round(1000 * gps.speed / 36), 2, false);
|
||||||
let lonEncoded = Math.round(gps.lon * 10000000);
|
let lat = toByteArray(Math.round(gps.lat * 10000000), 4, true);
|
||||||
let hEncoded = Math.round(gps.course * 100);
|
let lon = toByteArray(Math.round(gps.lon * 10000000), 4, true);
|
||||||
let sEncoded = Math.round(1000 * gps.speed / 36);
|
let e = toByteArray(Math.round(gps.alt * 100), 3, true);
|
||||||
|
let h = toByteArray(Math.round(gps.course * 100), 2, false);
|
||||||
if(gps.lat < 0) {
|
|
||||||
latEncoded += 0x100000000;
|
|
||||||
}
|
|
||||||
if(gps.lon < 0) {
|
|
||||||
lonEncoded += 0x100000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
let s = [ sEncoded & 0xff, (sEncoded >> 8) & 0xff ];
|
|
||||||
let lat = [ latEncoded & 0xff, (latEncoded >> 8) & 0xff,
|
|
||||||
(latEncoded >> 16) & 0xff, (latEncoded >> 24) & 0xff ];
|
|
||||||
let lon = [ lonEncoded & 0xff, (lonEncoded >> 8) & 0xff,
|
|
||||||
(lonEncoded >> 16) & 0xff, (lonEncoded >> 24) & 0xff ];
|
|
||||||
let h = [ hEncoded & 0xff, (hEncoded >> 8) & 0xff ];
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
0x02, 0x01, 0x06, // Flags
|
0x02, 0x01, 0x06, // Flags
|
||||||
0x11, 0x16, 0x67, 0x2a, 0x95, 0x02, s[0], s[1], lat[0], lat[1], lat[2],
|
0x14, 0x16, 0x67, 0x2a, 0x9d, 0x02, s[0], s[1], lat[0], lat[1], lat[2],
|
||||||
lat[3], lon[0], lon[1], lon[2], lon[3], h[0], h[1] // Location and Speed
|
lat[3], lon[0], lon[1], lon[2], lon[3], e[0], e[1], e[2], h[0], h[1]
|
||||||
|
// Location and Speed
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Encode the mag service data using the magnetic flux density 3D characteristic
|
// Encode the mag service data using the magnetic flux density 3D characteristic
|
||||||
function encodeMagServiceData() {
|
function encodeMagServiceData() {
|
||||||
let xEncoded = mag.x; // TODO: units???
|
let x = toByteArray(mag.x, 2, true);
|
||||||
let yEncoded = mag.y;
|
let y = toByteArray(mag.y, 2, true);
|
||||||
let zEncoded = mag.z;
|
let z = toByteArray(mag.z, 2, true);
|
||||||
|
|
||||||
if(xEncoded < 0) {
|
|
||||||
xEncoded += 0x10000;
|
|
||||||
}
|
|
||||||
if(yEncoded < 0) {
|
|
||||||
yEncoded += 0x10000;
|
|
||||||
}
|
|
||||||
if(yEncoded < 0) {
|
|
||||||
yEncoded += 0x10000;
|
|
||||||
}
|
|
||||||
|
|
||||||
let x = [ xEncoded & 0xff, (xEncoded >> 8) & 0xff ];
|
|
||||||
let y = [ yEncoded & 0xff, (yEncoded >> 8) & 0xff ];
|
|
||||||
let z = [ zEncoded & 0xff, (zEncoded >> 8) & 0xff ];
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
0x02, 0x01, 0x06, // Flags
|
0x02, 0x01, 0x06, // Flags
|
||||||
|
|
@ -211,6 +174,22 @@ function encodeMagServiceData() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Convert the given value to a little endian byte array
|
||||||
|
function toByteArray(value, numberOfBytes, isSigned) {
|
||||||
|
let byteArray = new Array(numberOfBytes);
|
||||||
|
|
||||||
|
if(isSigned && (value < 0)) {
|
||||||
|
value += 1 << (numberOfBytes * 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let index = 0; index < numberOfBytes; index++) {
|
||||||
|
byteArray[index] = (value >> (index * 8)) & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
return byteArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Update acceleration
|
// Update acceleration
|
||||||
Bangle.on('accel', function(newAcc) {
|
Bangle.on('accel', function(newAcc) {
|
||||||
acc = newAcc;
|
acc = newAcc;
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,4 @@
|
||||||
0.05: Make Bluetooth widget thinner, and when on a bright theme use light grey for disabled color
|
0.05: Make Bluetooth widget thinner, and when on a bright theme use light grey for disabled color
|
||||||
0.06: Tweaking colors for dark/light themes and low bpp screens
|
0.06: Tweaking colors for dark/light themes and low bpp screens
|
||||||
0.07: Memory usage improvements
|
0.07: Memory usage improvements
|
||||||
|
0.08: Disable LCD on, on bluetooth status change
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ WIDGETS["bluetooth"]={area:"tr",width:15,draw:function() {
|
||||||
g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),2+this.x,2+this.y);
|
g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),2+this.x,2+this.y);
|
||||||
},changed:function() {
|
},changed:function() {
|
||||||
WIDGETS["bluetooth"].draw();
|
WIDGETS["bluetooth"].draw();
|
||||||
Bangle.setLCDPower(1); // turn screen on
|
|
||||||
}};
|
}};
|
||||||
NRF.on('connect',WIDGETS["bluetooth"].changed);
|
NRF.on('connect',WIDGETS["bluetooth"].changed);
|
||||||
NRF.on('disconnect',WIDGETS["bluetooth"].changed);
|
NRF.on('disconnect',WIDGETS["bluetooth"].changed);
|
||||||
|
|
|
||||||