mirror of https://github.com/espruino/BangleApps
updated sleepquiet
parent
adb7e8a924
commit
59f3726deb
|
@ -6,6 +6,8 @@ Bangle.js App Loader (and Apps)
|
||||||
* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps)
|
* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps)
|
||||||
* Try the **development version** at [espruino.github.io](https://espruino.github.io/BangleApps/)
|
* Try the **development version** at [espruino.github.io](https://espruino.github.io/BangleApps/)
|
||||||
|
|
||||||
|
The release version is manually refreshed with regular intervals while the development version is continuously updated as new code is committed to this repository.
|
||||||
|
|
||||||
**All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By
|
**All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By
|
||||||
submitting code to this repository you confirm that you are happy with it being MIT licensed,
|
submitting code to this repository you confirm that you are happy with it being MIT licensed,
|
||||||
and that it is not licensed in another way that would make this impossible.
|
and that it is not licensed in another way that would make this impossible.
|
||||||
|
@ -403,7 +405,7 @@ in an iframe.
|
||||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="../../lib/interface.js"></script>
|
<script src="../../core/lib/interface.js"></script>
|
||||||
<div id="t">Loading...</div>
|
<div id="t">Loading...</div>
|
||||||
<script>
|
<script>
|
||||||
function onInit() {
|
function onInit() {
|
||||||
|
|
|
@ -1,203 +0,0 @@
|
||||||
const lintExemptions = require("./lint_exemptions.js");
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
"env": {
|
|
||||||
// TODO: "espruino": false
|
|
||||||
// TODO: "banglejs": false
|
|
||||||
// For a prototype of the above, see https://github.com/espruino/BangleApps/pull/3237
|
|
||||||
},
|
|
||||||
"extends": "eslint:recommended",
|
|
||||||
"globals": {
|
|
||||||
// Methods and Fields at https://banglejs.com/reference
|
|
||||||
"Array": "readonly",
|
|
||||||
"ArrayBuffer": "readonly",
|
|
||||||
"ArrayBufferView": "readonly",
|
|
||||||
"Bangle": "readonly",
|
|
||||||
"BluetoothDevice": "readonly",
|
|
||||||
"BluetoothRemoteGATTCharacteristic": "readonly",
|
|
||||||
"BluetoothRemoteGATTServer": "readonly",
|
|
||||||
"BluetoothRemoteGATTService": "readonly",
|
|
||||||
"Boolean": "readonly",
|
|
||||||
"console": "readonly",
|
|
||||||
"DataView": "readonly",
|
|
||||||
"Date": "readonly",
|
|
||||||
"E": "readonly",
|
|
||||||
"Error": "readonly",
|
|
||||||
"Flash": "readonly",
|
|
||||||
"Float32Array": "readonly",
|
|
||||||
"Float64Array": "readonly",
|
|
||||||
"Function": "readonly",
|
|
||||||
"Graphics": "readonly",
|
|
||||||
"I2C": "readonly",
|
|
||||||
"Int16Array": "readonly",
|
|
||||||
"Int32Array": "readonly",
|
|
||||||
"Int8Array": "readonly",
|
|
||||||
"InternalError": "readonly",
|
|
||||||
"JSON": "readonly",
|
|
||||||
"Math": "readonly",
|
|
||||||
"Modules": "readonly",
|
|
||||||
"NRF": "readonly",
|
|
||||||
"Number": "readonly",
|
|
||||||
"Object": "readonly",
|
|
||||||
"OneWire": "readonly",
|
|
||||||
"Pin": "readonly",
|
|
||||||
"process": "readonly",
|
|
||||||
"Promise": "readonly",
|
|
||||||
"ReferenceError": "readonly",
|
|
||||||
"RegExp": "readonly",
|
|
||||||
"Serial": "readonly",
|
|
||||||
"SPI": "readonly",
|
|
||||||
"StorageFile": "readonly",
|
|
||||||
"String": "readonly",
|
|
||||||
"SyntaxError": "readonly",
|
|
||||||
"TFMicroInterpreter": "readonly",
|
|
||||||
"TypeError": "readonly",
|
|
||||||
"Uint16Array": "readonly",
|
|
||||||
"Uint24Array": "readonly",
|
|
||||||
"Uint32Array": "readonly",
|
|
||||||
"Uint8Array": "readonly",
|
|
||||||
"Uint8ClampedArray": "readonly",
|
|
||||||
"Unistroke": "readonly",
|
|
||||||
"Waveform": "readonly",
|
|
||||||
// Methods and Fields at https://banglejs.com/reference
|
|
||||||
"__FILE__": "readonly",
|
|
||||||
"analogRead": "readonly",
|
|
||||||
"analogWrite": "readonly",
|
|
||||||
"arguments": "readonly",
|
|
||||||
"atob": "readonly",
|
|
||||||
"Bluetooth": "readonly",
|
|
||||||
"BTN": "readonly",
|
|
||||||
"BTN1": "readonly",
|
|
||||||
"BTN2": "readonly",
|
|
||||||
"BTN3": "readonly",
|
|
||||||
"BTN4": "readonly",
|
|
||||||
"BTN5": "readonly",
|
|
||||||
"btoa": "readonly",
|
|
||||||
"changeInterval": "readonly",
|
|
||||||
"clearInterval": "readonly",
|
|
||||||
"clearTimeout": "readonly",
|
|
||||||
"clearWatch": "readonly",
|
|
||||||
"decodeURIComponent": "readonly",
|
|
||||||
"digitalPulse": "readonly",
|
|
||||||
"digitalRead": "readonly",
|
|
||||||
"digitalWrite": "readonly",
|
|
||||||
"dump": "readonly",
|
|
||||||
"echo": "readonly",
|
|
||||||
"edit": "readonly",
|
|
||||||
"encodeURIComponent": "readonly",
|
|
||||||
"eval": "readonly",
|
|
||||||
"getPinMode": "readonly",
|
|
||||||
"getSerial": "readonly",
|
|
||||||
"getTime": "readonly",
|
|
||||||
"global": "readonly",
|
|
||||||
"HIGH": "readonly",
|
|
||||||
"I2C1": "readonly",
|
|
||||||
"Infinity": "readonly",
|
|
||||||
"isFinite": "readonly",
|
|
||||||
"isNaN": "readonly",
|
|
||||||
"LED": "readonly",
|
|
||||||
"LED1": "readonly",
|
|
||||||
"LED2": "readonly",
|
|
||||||
"load": "readonly",
|
|
||||||
"LoopbackA": "readonly",
|
|
||||||
"LoopbackB": "readonly",
|
|
||||||
"LOW": "readonly",
|
|
||||||
"NaN": "readonly",
|
|
||||||
"parseFloat": "readonly",
|
|
||||||
"parseInt": "readonly",
|
|
||||||
"peek16": "readonly",
|
|
||||||
"peek32": "readonly",
|
|
||||||
"peek8": "readonly",
|
|
||||||
"pinMode": "readonly",
|
|
||||||
"poke16": "readonly",
|
|
||||||
"poke32": "readonly",
|
|
||||||
"poke8": "readonly",
|
|
||||||
"print": "readonly",
|
|
||||||
"require": "readonly",
|
|
||||||
"reset": "readonly",
|
|
||||||
"save": "readonly",
|
|
||||||
"Serial1": "readonly",
|
|
||||||
"setBusyIndicator": "readonly",
|
|
||||||
"setInterval": "readonly",
|
|
||||||
"setSleepIndicator": "readonly",
|
|
||||||
"setTime": "readonly",
|
|
||||||
"setTimeout": "readonly",
|
|
||||||
"setWatch": "readonly",
|
|
||||||
"shiftOut": "readonly",
|
|
||||||
"SPI1": "readonly",
|
|
||||||
"Terminal": "readonly",
|
|
||||||
"trace": "readonly",
|
|
||||||
"VIBRATE": "readonly",
|
|
||||||
// Aliases and not defined at https://banglejs.com/reference
|
|
||||||
"g": "readonly",
|
|
||||||
"WIDGETS": "readonly",
|
|
||||||
"module": "readonly",
|
|
||||||
"exports": "writable",
|
|
||||||
"D0": "readonly",
|
|
||||||
"D1": "readonly",
|
|
||||||
"D2": "readonly",
|
|
||||||
"D3": "readonly",
|
|
||||||
"D4": "readonly",
|
|
||||||
"D5": "readonly",
|
|
||||||
"D6": "readonly",
|
|
||||||
"D7": "readonly",
|
|
||||||
"D8": "readonly",
|
|
||||||
"D9": "readonly",
|
|
||||||
"D10": "readonly",
|
|
||||||
"D11": "readonly",
|
|
||||||
"D12": "readonly",
|
|
||||||
"D13": "readonly",
|
|
||||||
"D14": "readonly",
|
|
||||||
"D15": "readonly",
|
|
||||||
"D16": "readonly",
|
|
||||||
"D17": "readonly",
|
|
||||||
"D18": "readonly",
|
|
||||||
"D19": "readonly",
|
|
||||||
"D20": "readonly",
|
|
||||||
"D21": "readonly",
|
|
||||||
"D22": "readonly",
|
|
||||||
"D23": "readonly",
|
|
||||||
"D24": "readonly",
|
|
||||||
"D25": "readonly",
|
|
||||||
"D26": "readonly",
|
|
||||||
"D27": "readonly",
|
|
||||||
"D28": "readonly",
|
|
||||||
"D29": "readonly",
|
|
||||||
"D30": "readonly",
|
|
||||||
"D31": "readonly",
|
|
||||||
|
|
||||||
"bleServiceOptions": "writable", // available in boot.js code that's called ad part of bootupdate
|
|
||||||
},
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": 11
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"indent": [
|
|
||||||
"off",
|
|
||||||
2,
|
|
||||||
{
|
|
||||||
"SwitchCase": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"no-constant-condition": "off",
|
|
||||||
"no-delete-var": "off",
|
|
||||||
"no-empty": ["warn", { "allowEmptyCatch": true }],
|
|
||||||
"no-global-assign": "off",
|
|
||||||
"no-inner-declarations": "off",
|
|
||||||
"no-prototype-builtins": "off",
|
|
||||||
"no-redeclare": "off",
|
|
||||||
"no-unreachable": "warn",
|
|
||||||
"no-cond-assign": "warn",
|
|
||||||
"no-useless-catch": "warn",
|
|
||||||
"no-undef": "warn",
|
|
||||||
"no-unused-vars": ["warn", { "args": "none" } ],
|
|
||||||
"no-useless-escape": "off",
|
|
||||||
"no-control-regex" : "off"
|
|
||||||
},
|
|
||||||
overrides: [
|
|
||||||
...Object.entries(lintExemptions).map(([filePath, {rules}]) => ({
|
|
||||||
files: [filePath],
|
|
||||||
rules: Object.fromEntries(rules.map(rule => [rule, "off"])),
|
|
||||||
})),
|
|
||||||
],
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
// 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() {
|
|
||||||
// queue next draw in one minute
|
|
||||||
queueDraw();
|
|
||||||
// Work out where to draw...
|
|
||||||
var x = g.getWidth()/2;
|
|
||||||
var y = g.getHeight()/2;
|
|
||||||
g.reset();
|
|
||||||
// work out locale-friendly date/time
|
|
||||||
var date = new Date();
|
|
||||||
var timeStr = require("locale").time(date,1);
|
|
||||||
var dateStr = require("locale").date(date);
|
|
||||||
// draw time
|
|
||||||
g.setFontAlign(0,0).setFont("Vector",48);
|
|
||||||
g.clearRect(0,y-15,g.getWidth(),y+25); // clear the background
|
|
||||||
g.drawString(timeStr,x,y);
|
|
||||||
// draw date
|
|
||||||
y += 35;
|
|
||||||
g.setFontAlign(0,0).setFont("6x8");
|
|
||||||
g.clearRect(0,y-4,g.getWidth(),y+4); // clear the background
|
|
||||||
g.drawString(dateStr,x,y);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear the screen once, at startup
|
|
||||||
g.clear();
|
|
||||||
// draw immediately at first, queue update
|
|
||||||
draw();
|
|
||||||
|
|
||||||
// Show launcher when middle button pressed
|
|
||||||
Bangle.setUI("clock");
|
|
||||||
// Load widgets
|
|
||||||
Bangle.loadWidgets();
|
|
||||||
Bangle.drawWidgets();
|
|
|
@ -4,6 +4,7 @@
|
||||||
"version":"0.01",
|
"version":"0.01",
|
||||||
"description": "A detailed description of my clock",
|
"description": "A detailed description of my clock",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
"supports" : ["BANGLEJS2"],
|
"supports" : ["BANGLEJS2"],
|
||||||
|
|
|
@ -9,3 +9,4 @@
|
||||||
0.09: New app screen (instead of showing settings or the alert) and some optimisations
|
0.09: New app screen (instead of showing settings or the alert) and some optimisations
|
||||||
0.10: Add software back button via setUI
|
0.10: Add software back button via setUI
|
||||||
0.11: Add setting to unlock screen
|
0.11: Add setting to unlock screen
|
||||||
|
0.12: Fix handling that dates can be given as ms since epoch.
|
||||||
|
|
|
@ -29,16 +29,13 @@ exports.loadData = function () {
|
||||||
dismissDate: new Date(1970),
|
dismissDate: new Date(1970),
|
||||||
pauseDate: new Date(1970),
|
pauseDate: new Date(1970),
|
||||||
},
|
},
|
||||||
|
|
||||||
require("Storage").readJSON("activityreminder.data.json") || {});
|
require("Storage").readJSON("activityreminder.data.json") || {});
|
||||||
|
|
||||||
if (typeof (data.stepsDate) == "string")
|
data.stepsDate = new Date(typeof data.stepsDate === 'string' ? data.stepsDate : data.stepsDate.ms);
|
||||||
data.stepsDate = new Date(data.stepsDate);
|
data.okDate = new Date(typeof data.okDate === 'string' ? data.okDate : data.okDate.ms);
|
||||||
if (typeof (data.okDate) == "string")
|
data.dismissDate = new Date(typeof data.dismissDate === 'string' ? data.dismissDate : data.dismissDate.ms);
|
||||||
data.okDate = new Date(data.okDate);
|
data.pauseDate = new Date(typeof data.pauseDate === 'string' ? data.pauseDate : data.pauseDate.ms);
|
||||||
if (typeof (data.dismissDate) == "string")
|
|
||||||
data.dismissDate = new Date(data.dismissDate);
|
|
||||||
if (typeof (data.pauseDate) == "string")
|
|
||||||
data.pauseDate = new Date(data.pauseDate);
|
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
"name": "Activity Reminder",
|
"name": "Activity Reminder",
|
||||||
"shortName":"Activity Reminder",
|
"shortName":"Activity Reminder",
|
||||||
"description": "A reminder to take short walks for the ones with a sedentary lifestyle",
|
"description": "A reminder to take short walks for the ones with a sedentary lifestyle",
|
||||||
"version":"0.11",
|
"version":"0.12",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
"tags": "tool,activity",
|
"tags": "tool,activity,health",
|
||||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
|
|
|
@ -3,3 +3,4 @@
|
||||||
0.03: Tell clock widgets to hide.
|
0.03: Tell clock widgets to hide.
|
||||||
0.04: Swipe down to see widgets, step counter now just uses getHealthStatus
|
0.04: Swipe down to see widgets, step counter now just uses getHealthStatus
|
||||||
0.05: Report latest HRM rather than HRM 10 minutes ago (fix #2395)
|
0.05: Report latest HRM rather than HRM 10 minutes ago (fix #2395)
|
||||||
|
0.06: Use watch temperature
|
|
@ -3,8 +3,7 @@
|
||||||
<img src="https://user-images.githubusercontent.com/2981891/175355586-1dfc0d66-6555-4385-b124-1605fdb71a11.jpg" width="250" />
|
<img src="https://user-images.githubusercontent.com/2981891/175355586-1dfc0d66-6555-4385-b124-1605fdb71a11.jpg" width="250" />
|
||||||
|
|
||||||
An over-engineered clock inspired by Casio watches.<br/>
|
An over-engineered clock inspired by Casio watches.<br/>
|
||||||
It has a dedicated timer, a scratchpad and can display the weather condition 4 days ahead.<br/>
|
It has a dedicated timer, a scratchpad and displays the current temperature.<br/>
|
||||||
It uses a <a target="_blank" href="https://dotgreg.github.io/advCasioBangleClock/">custom web app</a> to update its content.<br/>
|
|
||||||
Forked from the awesome Cassio Watch.<br/>
|
Forked from the awesome Cassio Watch.<br/>
|
||||||
|
|
||||||
## Todo
|
## Todo
|
||||||
|
@ -21,7 +20,7 @@ Forked from the awesome Cassio Watch.<br/>
|
||||||
- Footsteps
|
- Footsteps
|
||||||
- Battery
|
- Battery
|
||||||
- Simple Timer embedded
|
- Simple Timer embedded
|
||||||
- Weather forecast (7 days)
|
- Current temperature
|
||||||
- Scratchpad
|
- Scratchpad
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
@ -36,14 +35,6 @@ Web interface to update weather & scratchpad <br/>
|
||||||
<img src="https://user-images.githubusercontent.com/2981891/175519121-851bb209-7192-40db-a014-490c344f7597.jpg" width="250" />
|
<img src="https://user-images.githubusercontent.com/2981891/175519121-851bb209-7192-40db-a014-490c344f7597.jpg" width="250" />
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
### How to update the tasks list / weather
|
|
||||||
- you will need a <a target="_blank" href="https://openweathermap.org/price#weather">free openweathermap.org api key</a>.
|
|
||||||
- go to https://dotgreg.github.io/advCasioBangleClock/
|
|
||||||
- Alternatively you can install it on your own server/heroku/service/github pages, the web-app code is <a target="_blank" href="https://github.com/dotgreg/advCasioBangleClock/tree/master/web-app">here</a>
|
|
||||||
- fill the location and the api key (it will be saved on your browser, no need to do it each time)
|
|
||||||
- edit the scratchpad with what you want
|
|
||||||
- click on sync
|
|
||||||
- reload your clock!
|
|
||||||
|
|
||||||
### How to start/stop the timer
|
### How to start/stop the timer
|
||||||
- swipe up : add time (+5min)
|
- swipe up : add time (+5min)
|
||||||
|
|
|
@ -88,9 +88,9 @@ function drawRocket() {
|
||||||
|
|
||||||
function getTemperature(){
|
function getTemperature(){
|
||||||
try {
|
try {
|
||||||
var weatherJson = storage.readJSON('weather.json');
|
var temperature = E.getTemperature()
|
||||||
var weather = weatherJson.weather;
|
var formatted = require("locale").temp(temperature).replace(/[^\d-]/g, '');
|
||||||
return Math.round(weather.temp-273.15);
|
return formatted;
|
||||||
|
|
||||||
} catch(ex) {
|
} catch(ex) {
|
||||||
print(ex)
|
print(ex)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{ "id": "advcasio",
|
{ "id": "advcasio",
|
||||||
"name": "Advanced Casio Clock",
|
"name": "Advanced Casio Clock",
|
||||||
"shortName":"advcasio",
|
"shortName":"advcasio",
|
||||||
"version":"0.05",
|
"version":"0.06",
|
||||||
"description": "An over-engineered clock inspired by Casio watches. It has a 4 days weather, a timer using swipe and a scratchpad. Can be updated using a dedicated webapp.",
|
"description": "An over-engineered clock inspired by Casio watches. It has current temperature, a timer using swipe and a scratchpad. Can be updated using a dedicated webapp.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
@ -18,8 +18,5 @@
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"advcasio.app.js","url":"app.js"},
|
{"name":"advcasio.app.js","url":"app.js"},
|
||||||
{"name":"advcasio.img","url":"app-icon.js","evaluate":true}
|
{"name":"advcasio.img","url":"app-icon.js","evaluate":true}
|
||||||
],
|
|
||||||
"data": [
|
|
||||||
{ "name": "advcasio.data.json", "url": "data.json", "storageFile": true }
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,3 +49,5 @@
|
||||||
0.44: Add "delete timer after expiration" setting to events.
|
0.44: Add "delete timer after expiration" setting to events.
|
||||||
0.45: Fix new alarm when selectedAlarm is undefined
|
0.45: Fix new alarm when selectedAlarm is undefined
|
||||||
0.46: Show alarm groups if the Show Group setting is ON. Scroll alarms menu back to previous position when getting back to it.
|
0.46: Show alarm groups if the Show Group setting is ON. Scroll alarms menu back to previous position when getting back to it.
|
||||||
|
0.47: Fix wrap around when snoozed through midnight
|
||||||
|
0.48: Use datetimeinput for Events, if available. Scroll back when getting out of group. Menu date format setting for shorter dates on current year.
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
This app allows you to add/modify any alarms, timers and events.
|
This app allows you to add/modify any alarms, timers and events.
|
||||||
|
|
||||||
Optional: When a keyboard app is detected, you can add a message to display when any of these is triggered.
|
Optional: When a keyboard app is detected, you can add a message to display when any of these is triggered. If a datetime input app (e.g. datetime_picker) is detected, it will be used for the selection of the date+time of events.
|
||||||
|
|
||||||
It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) to handle the alarm scheduling in an efficient way that can work alongside other apps.
|
It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) to handle the alarm scheduling in an efficient way that can work alongside other apps.
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,8 @@ const iconTimerOff = "\0" + (g.theme.dark
|
||||||
|
|
||||||
// An array of alarm objects (see sched/README.md)
|
// An array of alarm objects (see sched/README.md)
|
||||||
var alarms = require("sched").getAlarms();
|
var alarms = require("sched").getAlarms();
|
||||||
|
// Fix possible wrap around in existing alarms #3281, broken alarms still needs to be saved to get fixed
|
||||||
|
alarms.forEach(e => e.t %= 86400000); // This can probably be removed in the future when we are sure there are no more broken alarms
|
||||||
|
|
||||||
function handleFirstDayOfWeek(dow) {
|
function handleFirstDayOfWeek(dow) {
|
||||||
if (firstDayOfWeek == 1) {
|
if (firstDayOfWeek == 1) {
|
||||||
|
@ -48,13 +50,17 @@ function handleFirstDayOfWeek(dow) {
|
||||||
alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
|
alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
|
||||||
|
|
||||||
function getLabel(e) {
|
function getLabel(e) {
|
||||||
const dateStr = e.date && require("locale").date(new Date(e.date), 1);
|
const dateStr = getDateText(e.date);
|
||||||
return (e.timer
|
return (e.timer
|
||||||
? require("time_utils").formatDuration(e.timer)
|
? require("time_utils").formatDuration(e.timer)
|
||||||
: (dateStr ? `${dateStr}${e.rp?"*":""} ${require("time_utils").formatTime(e.t)}` : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeRepeat(e)}` : ""))
|
: (dateStr ? `${dateStr}${e.rp?"*":""} ${require("time_utils").formatTime(e.t)}` : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeRepeat(e)}` : ""))
|
||||||
) + (e.msg ? ` ${e.msg}` : "");
|
) + (e.msg ? ` ${e.msg}` : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDateText(d) {
|
||||||
|
return d && (settings.menuDateFormat === "mmdd" ? d.substring(d.startsWith(new Date().getFullYear()) ? 5 : 0) : require("locale").date(new Date(d), 1));
|
||||||
|
}
|
||||||
|
|
||||||
function trimLabel(label, maxLength) {
|
function trimLabel(label, maxLength) {
|
||||||
if(settings.showOverflow) return label;
|
if(settings.showOverflow) return label;
|
||||||
return (label.length > maxLength
|
return (label.length > maxLength
|
||||||
|
@ -73,10 +79,10 @@ function formatAlarmProperty(msg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMainMenu(scroll, group) {
|
function showMainMenu(scroll, group, scrollback) {
|
||||||
const menu = {
|
const menu = {
|
||||||
"": { "title": group || /*LANG*/"Alarms & Timers", scroll: scroll },
|
"": { "title": group || /*LANG*/"Alarms & Timers", scroll: scroll },
|
||||||
"< Back": () => group ? showMainMenu() : load(),
|
"< Back": () => group ? showMainMenu(scrollback) : load(),
|
||||||
/*LANG*/"New...": () => showNewMenu(group)
|
/*LANG*/"New...": () => showNewMenu(group)
|
||||||
};
|
};
|
||||||
const getGroups = settings.showGroup && !group;
|
const getGroups = settings.showGroup && !group;
|
||||||
|
@ -96,7 +102,7 @@ function showMainMenu(scroll, group) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!group) {
|
if (!group) {
|
||||||
Object.keys(groups).sort().forEach(g => menu[g] = () => showMainMenu(null, g));
|
Object.keys(groups).sort().forEach(g => menu[g] = () => showMainMenu(null, g, scroller.scroll));
|
||||||
menu[/*LANG*/"Advanced"] = () => showAdvancedMenu();
|
menu[/*LANG*/"Advanced"] = () => showAdvancedMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,6 +142,8 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate, scroll, group) {
|
||||||
var title = date ? (isNew ? /*LANG*/"New Event" : /*LANG*/"Edit Event") : (isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm");
|
var title = date ? (isNew ? /*LANG*/"New Event" : /*LANG*/"Edit Event") : (isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm");
|
||||||
var keyboard = "textinput";
|
var keyboard = "textinput";
|
||||||
try {keyboard = require(keyboard);} catch(e) {keyboard = null;}
|
try {keyboard = require(keyboard);} catch(e) {keyboard = null;}
|
||||||
|
var datetimeinput;
|
||||||
|
try {datetimeinput = require("datetimeinput");} catch(e) {datetimeinput = null;}
|
||||||
|
|
||||||
const menu = {
|
const menu = {
|
||||||
"": { "title": title },
|
"": { "title": title },
|
||||||
|
@ -143,7 +151,28 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate, scroll, group) {
|
||||||
prepareAlarmForSave(alarm, alarmIndex, time, date);
|
prepareAlarmForSave(alarm, alarmIndex, time, date);
|
||||||
saveAndReload();
|
saveAndReload();
|
||||||
showMainMenu(scroll, group);
|
showMainMenu(scroll, group);
|
||||||
},
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (alarm.date && datetimeinput) {
|
||||||
|
menu[`${getDateText(date.toLocalISOString().slice(0,10))} ${require("time_utils").formatTime(time)}`] = {
|
||||||
|
value: date,
|
||||||
|
format: v => "",
|
||||||
|
onchange: v => {
|
||||||
|
setTimeout(() => {
|
||||||
|
var datetime = new Date(v.getTime());
|
||||||
|
datetime.setHours(time.h, time.m);
|
||||||
|
datetimeinput.input({datetime}).then(result => {
|
||||||
|
time.h = result.getHours();
|
||||||
|
time.m = result.getMinutes();
|
||||||
|
prepareAlarmForSave(alarm, alarmIndex, time, result, true);
|
||||||
|
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate, scroll, group);
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
Object.assign(menu, {
|
||||||
/*LANG*/"Hour": {
|
/*LANG*/"Hour": {
|
||||||
value: time.h,
|
value: time.h,
|
||||||
format: v => ("0" + v).substr(-2),
|
format: v => ("0" + v).substr(-2),
|
||||||
|
@ -177,7 +206,11 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate, scroll, group) {
|
||||||
min: new Date().getFullYear(),
|
min: new Date().getFullYear(),
|
||||||
max: 2100,
|
max: 2100,
|
||||||
onchange: v => date.setFullYear(v)
|
onchange: v => date.setFullYear(v)
|
||||||
},
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(menu, {
|
||||||
/*LANG*/"Message": {
|
/*LANG*/"Message": {
|
||||||
value: alarm.msg,
|
value: alarm.msg,
|
||||||
format: formatAlarmProperty,
|
format: formatAlarmProperty,
|
||||||
|
@ -239,7 +272,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate, scroll, group) {
|
||||||
saveAndReload();
|
saveAndReload();
|
||||||
showMainMenu(scroll, group);
|
showMainMenu(scroll, group);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
if (!keyboard) delete menu[/*LANG*/"Message"];
|
if (!keyboard) delete menu[/*LANG*/"Message"];
|
||||||
if (!keyboard || !settings.showGroup) delete menu[/*LANG*/"Group"];
|
if (!keyboard || !settings.showGroup) delete menu[/*LANG*/"Group"];
|
||||||
|
@ -497,7 +530,7 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
|
||||||
|
|
||||||
function prepareTimerForSave(timer, timerIndex, time, temp) {
|
function prepareTimerForSave(timer, timerIndex, time, temp) {
|
||||||
timer.timer = require("time_utils").encodeTime(time);
|
timer.timer = require("time_utils").encodeTime(time);
|
||||||
timer.t = require("time_utils").getCurrentTimeMillis() + timer.timer;
|
timer.t = (require("time_utils").getCurrentTimeMillis() + timer.timer) % 86400000;
|
||||||
timer.last = 0;
|
timer.last = 0;
|
||||||
|
|
||||||
if (!temp) {
|
if (!temp) {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "alarm",
|
"id": "alarm",
|
||||||
"name": "Alarms & Timers",
|
"name": "Alarms & Timers",
|
||||||
"shortName": "Alarms",
|
"shortName": "Alarms",
|
||||||
"version": "0.46",
|
"version": "0.48",
|
||||||
"description": "Set alarms and timers on your Bangle",
|
"description": "Set alarms and timers on your Bangle",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool,alarm",
|
"tags": "tool,alarm",
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
0.01: Release
|
0.01: Release
|
||||||
0.02: Rename app
|
0.02: Rename app
|
||||||
0.03: Add type "clock"
|
0.03: Add type "clock"
|
||||||
0.04: changed update cylce, when locked
|
0.04: Changed update cylce, when locked
|
||||||
|
0.05: Fix support for dark theme + support widgets +
|
||||||
|
add settings for widgets, order of drawing and hour hand length
|
||||||
|
0.06: Fix issue showing widgets when app is fast-loaded into from launcher with widgets disabled
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
# Analog Clock
|
# Dark Analog Clock
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* second hand
|
* second hand (only on unlocked screen)
|
||||||
* date
|
* date
|
||||||
* battery percantage
|
* battery percentage (showing charge status with color)
|
||||||
* no widgets
|
* turned off or swipeable widgets (choose in settings)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
* whether to load widgets, or not; if widgets are loaded, they are swipeable from the top; if not, NO ACTIONS of widgets are available
|
||||||
|
* date and battery can be printed both below hands (as if hands were physical) and above (more readable)
|
||||||
|
* hour hand can be made slighly shorter to improve readability when minute hand is behind a number
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
@ -1,5 +1,30 @@
|
||||||
|
const defaultSettings = {
|
||||||
|
loadWidgets : false,
|
||||||
|
textAboveHands : false,
|
||||||
|
shortHrHand : false
|
||||||
|
};
|
||||||
|
const settings = Object.assign(defaultSettings, require('Storage').readJSON('andark.json',1)||{});
|
||||||
|
|
||||||
const c={"x":g.getWidth()/2,"y":g.getHeight()/2};
|
const c={"x":g.getWidth()/2,"y":g.getHeight()/2};
|
||||||
let zahlpos=[];
|
|
||||||
|
const zahlpos=(function() {
|
||||||
|
let z=[];
|
||||||
|
let sk=1;
|
||||||
|
for(let i=-10;i<50;i+=5){
|
||||||
|
let win=i*2*Math.PI/60;
|
||||||
|
let xsk =c.x+2+Math.cos(win)*(c.x-10),
|
||||||
|
ysk =c.y+2+Math.sin(win)*(c.x-10);
|
||||||
|
if(sk==3){xsk-=10;}
|
||||||
|
if(sk==6){ysk-=10;}
|
||||||
|
if(sk==9){xsk+=10;}
|
||||||
|
if(sk==12){ysk+=10;}
|
||||||
|
if(sk==10){xsk+=3;}
|
||||||
|
z.push([sk,xsk,ysk]);
|
||||||
|
sk+=1;
|
||||||
|
}
|
||||||
|
return z;
|
||||||
|
})();
|
||||||
|
|
||||||
let unlock = false;
|
let unlock = false;
|
||||||
|
|
||||||
function zeiger(len,dia,tim){
|
function zeiger(len,dia,tim){
|
||||||
|
@ -11,12 +36,8 @@ function zeiger(len,dia,tim){
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw(){
|
function drawHands(d) {
|
||||||
const d=new Date();
|
|
||||||
let m=d.getMinutes(), h=d.getHours(), s=d.getSeconds();
|
let m=d.getMinutes(), h=d.getHours(), s=d.getSeconds();
|
||||||
//draw black rectangle in the middle to clear screen from scale and hands
|
|
||||||
g.setColor(0,0,0);
|
|
||||||
g.fillRect(10,10,2*c.x-10,2*c.x-10);
|
|
||||||
g.setColor(1,1,1);
|
g.setColor(1,1,1);
|
||||||
|
|
||||||
if(h>12){
|
if(h>12){
|
||||||
|
@ -29,15 +50,8 @@ function draw(){
|
||||||
m=2*Math.PI/60*(m)-Math.PI/2;
|
m=2*Math.PI/60*(m)-Math.PI/2;
|
||||||
|
|
||||||
s=2*Math.PI/60*s-Math.PI/2;
|
s=2*Math.PI/60*s-Math.PI/2;
|
||||||
g.setFontAlign(0,0);
|
|
||||||
g.setFont("Vector",10);
|
|
||||||
let dateStr = " "+require("locale").date(d)+" ";
|
|
||||||
g.drawString(dateStr, c.x, c.y+20, true);
|
|
||||||
// g.drawString(d.getDate(),1.4*c.x,c.y,true);
|
|
||||||
g.drawString(Math.round(E.getBattery()/5)*5+"%",c.x,c.y+40,true);
|
|
||||||
drawlet();
|
|
||||||
//g.setColor(1,0,0);
|
//g.setColor(1,0,0);
|
||||||
const hz = zeiger(100,5,h);
|
const hz = zeiger(settings.shortHrHand?88:100,5,h);
|
||||||
g.fillPoly(hz,true);
|
g.fillPoly(hz,true);
|
||||||
//g.setColor(1,1,1);
|
//g.setColor(1,1,1);
|
||||||
const minz = zeiger(150,5,m);
|
const minz = zeiger(150,5,m);
|
||||||
|
@ -47,12 +61,53 @@ function draw(){
|
||||||
g.fillPoly(sekz,true);
|
g.fillPoly(sekz,true);
|
||||||
}
|
}
|
||||||
g.fillCircle(c.x,c.y,4);
|
g.fillCircle(c.x,c.y,4);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drawText(d) {
|
||||||
|
g.setFont("Vector",10);
|
||||||
|
g.setBgColor(0,0,0);
|
||||||
|
g.setColor(1,1,1);
|
||||||
|
let dateStr = require("locale").date(d);
|
||||||
|
g.drawString(dateStr, c.x, c.y+20, true);
|
||||||
|
let batStr = Math.round(E.getBattery()/5)*5+"%";
|
||||||
|
if (Bangle.isCharging()) {
|
||||||
|
g.setBgColor(1,0,0);
|
||||||
|
}
|
||||||
|
g.drawString(batStr, c.x, c.y+40, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawNumbers() {
|
||||||
|
//draws the numbers on the screen
|
||||||
|
g.setFont("Vector",20);
|
||||||
|
g.setColor(1,1,1);
|
||||||
|
g.setBgColor(0,0,0);
|
||||||
|
for(let i = 0;i<12;i++){
|
||||||
|
g.drawString(zahlpos[i][0],zahlpos[i][1],zahlpos[i][2],true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw(){
|
||||||
|
// draw black rectangle in the middle to clear screen from scale and hands
|
||||||
|
g.setColor(0,0,0);
|
||||||
|
g.fillRect(10,10,2*c.x-10,2*c.x-10);
|
||||||
|
// prepare for drawing the text
|
||||||
|
g.setFontAlign(0,0);
|
||||||
|
// do drawing
|
||||||
|
drawNumbers();
|
||||||
|
const d=new Date();
|
||||||
|
if (settings.textAboveHands) {
|
||||||
|
drawHands(d); drawText(d);
|
||||||
|
} else {
|
||||||
|
drawText(d); drawHands(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//draws the scale once the app is startet
|
//draws the scale once the app is startet
|
||||||
function drawScale(){
|
function drawScale(){
|
||||||
|
// clear the screen
|
||||||
|
g.setBgColor(0,0,0);
|
||||||
|
g.clear();
|
||||||
|
// draw the ticks of the scale
|
||||||
for(let i=-14;i<47;i++){
|
for(let i=-14;i<47;i++){
|
||||||
const win=i*2*Math.PI/60;
|
const win=i*2*Math.PI/60;
|
||||||
let d=2;
|
let d=2;
|
||||||
|
@ -64,40 +119,22 @@ function drawScale(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//draws the numbers on the screen
|
//// main running sequence ////
|
||||||
|
|
||||||
function drawlet(){
|
// Show launcher when middle button pressed, and widgets that we're clock
|
||||||
g.setFont("Vector",20);
|
Bangle.setUI("clock");
|
||||||
for(let i = 0;i<12;i++){
|
// Load widgets if needed, and make them show swipeable
|
||||||
g.drawString(zahlpos[i][0],zahlpos[i][1],zahlpos[i][2]);
|
if (settings.loadWidgets) {
|
||||||
}
|
Bangle.loadWidgets();
|
||||||
}
|
require("widget_utils").swipeOn();
|
||||||
//calcultes the Position of the numbers when app starts and saves them in an array
|
} else if (global.WIDGETS) require("widget_utils").hide();
|
||||||
function setlet(){
|
|
||||||
let sk=1;
|
|
||||||
for(let i=-10;i<50;i+=5){
|
|
||||||
let win=i*2*Math.PI/60;
|
|
||||||
let xsk =c.x+2+Math.cos(win)*(c.x-10),
|
|
||||||
ysk =c.y+2+Math.sin(win)*(c.x-10);
|
|
||||||
if(sk==3){xsk-=10;}
|
|
||||||
if(sk==6){ysk-=10;}
|
|
||||||
if(sk==9){xsk+=10;}
|
|
||||||
if(sk==12){ysk+=10;}
|
|
||||||
if(sk==10){xsk+=3;}
|
|
||||||
zahlpos.push([sk,xsk,ysk]);
|
|
||||||
sk+=1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setlet();
|
|
||||||
// Clear the screen once, at startup
|
// Clear the screen once, at startup
|
||||||
g.setBgColor(0,0,0);
|
|
||||||
g.clear();
|
|
||||||
drawScale();
|
drawScale();
|
||||||
draw();
|
draw();
|
||||||
|
|
||||||
let secondInterval = setInterval(draw, 1000);
|
let secondInterval = setInterval(draw, 1000);
|
||||||
// Stop updates when LCD is off, restart when on
|
|
||||||
|
|
||||||
|
// Stop updates when LCD is off, restart when on
|
||||||
Bangle.on('lcdPower',on=>{
|
Bangle.on('lcdPower',on=>{
|
||||||
if (secondInterval) clearInterval(secondInterval);
|
if (secondInterval) clearInterval(secondInterval);
|
||||||
secondInterval = undefined;
|
secondInterval = undefined;
|
||||||
|
@ -107,18 +144,9 @@ Bangle.on('lcdPower',on=>{
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Bangle.on('lock',on=>{
|
Bangle.on('lock',on=>{
|
||||||
|
unlock = !on;
|
||||||
if (secondInterval) clearInterval(secondInterval);
|
if (secondInterval) clearInterval(secondInterval);
|
||||||
secondInterval = undefined;
|
secondInterval = setInterval(draw, unlock ? 1000 : 60000);
|
||||||
if (!on) {
|
|
||||||
secondInterval = setInterval(draw, 1000);
|
|
||||||
unlock = true;
|
|
||||||
draw(); // draw immediately
|
draw(); // draw immediately
|
||||||
}else{
|
|
||||||
secondInterval = setInterval(draw, 60000);
|
|
||||||
unlock = false;
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
Bangle.on('charging',on=>{draw();});
|
||||||
// Show launcher when middle button pressed
|
|
||||||
Bangle.setUI("clock");
|
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
{ "id": "andark",
|
{ "id": "andark",
|
||||||
"name": "Analog Dark",
|
"name": "Analog Dark",
|
||||||
"shortName":"AnDark",
|
"shortName":"AnDark",
|
||||||
"version":"0.04",
|
"version":"0.06",
|
||||||
"description": "analog clock face without disturbing widgets",
|
"description": "analog clock face without disturbing widgets",
|
||||||
"icon": "andark_icon.png",
|
"icon": "andark_icon.png",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
"supports" : ["BANGLEJS2"],
|
"supports" : ["BANGLEJS2"],
|
||||||
|
"screenshots": [{"url":"andark_screen.png"}],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"andark.app.js","url":"app.js"},
|
{"name":"andark.app.js","url":"app.js"},
|
||||||
|
{"name":"andark.settings.js","url":"settings.js"},
|
||||||
{"name":"andark.img","url":"app_icon.js","evaluate":true}
|
{"name":"andark.img","url":"app_icon.js","evaluate":true}
|
||||||
]
|
],
|
||||||
|
"data": [{"name":"andark.json"}]
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,3 +34,6 @@
|
||||||
0.32: Added support for loyalty cards from gadgetbridge
|
0.32: Added support for loyalty cards from gadgetbridge
|
||||||
0.33: Fix alarms created in Gadgetbridge not repeating
|
0.33: Fix alarms created in Gadgetbridge not repeating
|
||||||
0.34: Implement API for activity tracks fetching (Recorder app logs).
|
0.34: Implement API for activity tracks fetching (Recorder app logs).
|
||||||
|
0.35: Implement API to enable/disable acceleration data tracking.
|
||||||
|
0.36: Move from wrapper function to {} and let - faster execution at boot
|
||||||
|
Allow `calendar-` to take an array of items to remove
|
|
@ -44,6 +44,10 @@ The boot code also provides some useful functions:
|
||||||
* `id` - a custom (string) ID
|
* `id` - a custom (string) ID
|
||||||
* `timeout` - a timeout for the request in milliseconds (default 30000ms)
|
* `timeout` - a timeout for the request in milliseconds (default 30000ms)
|
||||||
* `xpath` an xPath query to run on the request (but right now the URL requested must be XML - HTML is rarely XML compliant)
|
* `xpath` an xPath query to run on the request (but right now the URL requested must be XML - HTML is rarely XML compliant)
|
||||||
|
* `return` for xpath, if not specified, one result is returned. If `return:"array"` an array of results is returned.
|
||||||
|
* `method` HTTP method (default is `get`) - `get/post/head/put/patch/delete`
|
||||||
|
* `body` the body of the HTTP request
|
||||||
|
* `headers` an object of headers, eg `{HeaderOne : "headercontents"}`
|
||||||
|
|
||||||
eg:
|
eg:
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
(function() {
|
/* global GB */
|
||||||
function gbSend(message) {
|
{
|
||||||
|
let gbSend = function(message) {
|
||||||
Bluetooth.println("");
|
Bluetooth.println("");
|
||||||
Bluetooth.println(JSON.stringify(message));
|
Bluetooth.println(JSON.stringify(message));
|
||||||
}
|
}
|
||||||
var lastMsg; // for music messages - may not be needed now...
|
let lastMsg; // for music messages - may not be needed now...
|
||||||
var actInterval; // Realtime activity reporting interval when `act` is true
|
let actInterval; // Realtime activity reporting interval when `act` is true
|
||||||
var actHRMHandler; // For Realtime activity reporting
|
let actHRMHandler; // For Realtime activity reporting
|
||||||
var gpsState = {}; // keep information on GPS via Gadgetbridge
|
let gpsState = {}; // keep information on GPS via Gadgetbridge
|
||||||
|
|
||||||
// this settings var is deleted after this executes to save memory
|
// this settings var is deleted after this executes to save memory
|
||||||
var settings = require("Storage").readJSON("android.settings.json",1)||{};
|
let settings = require("Storage").readJSON("android.settings.json",1)||{};
|
||||||
//default alarm settings
|
//default alarm settings
|
||||||
if (settings.rp == undefined) settings.rp = true;
|
if (settings.rp == undefined) settings.rp = true;
|
||||||
if (settings.as == undefined) settings.as = true;
|
if (settings.as == undefined) settings.as = true;
|
||||||
if (settings.vibrate == undefined) settings.vibrate = "..";
|
if (settings.vibrate == undefined) settings.vibrate = "..";
|
||||||
require('Storage').writeJSON("android.settings.json", settings);
|
require('Storage').writeJSON("android.settings.json", settings);
|
||||||
var _GB = global.GB;
|
let _GB = global.GB;
|
||||||
let fetchRecInterval;
|
let fetchRecInterval;
|
||||||
global.GB = (event) => {
|
global.GB = (event) => {
|
||||||
// feed a copy to other handlers if there were any
|
// feed a copy to other handlers if there were any
|
||||||
|
@ -121,6 +122,9 @@
|
||||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||||
//if any of those happen we are out of sync!
|
//if any of those happen we are out of sync!
|
||||||
if (!cal || !Array.isArray(cal)) cal = [];
|
if (!cal || !Array.isArray(cal)) cal = [];
|
||||||
|
if (Array.isArray(event.id))
|
||||||
|
cal = cal.filter(e=>!event.id.includes(e.id));
|
||||||
|
else
|
||||||
cal = cal.filter(e=>e.id!=event.id);
|
cal = cal.filter(e=>e.id!=event.id);
|
||||||
require("Storage").writeJSON("android.calendar.json", cal);
|
require("Storage").writeJSON("android.calendar.json", cal);
|
||||||
},
|
},
|
||||||
|
@ -292,6 +296,10 @@
|
||||||
// we receive all, just override what we have
|
// we receive all, just override what we have
|
||||||
if (Array.isArray(event.d))
|
if (Array.isArray(event.d))
|
||||||
require("Storage").writeJSON("android.cards.json", event.d);
|
require("Storage").writeJSON("android.cards.json", event.d);
|
||||||
|
},
|
||||||
|
"accelsender": function () {
|
||||||
|
require("Storage").writeJSON("accelsender.json", {enabled: event.enable, interval: event.interval});
|
||||||
|
load();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var h = HANDLERS[event.t];
|
var h = HANDLERS[event.t];
|
||||||
|
@ -332,7 +340,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
// Battery monitor
|
// Battery monitor
|
||||||
function sendBattery() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); }
|
let sendBattery = function() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); }
|
||||||
Bangle.on("charging", sendBattery);
|
Bangle.on("charging", sendBattery);
|
||||||
NRF.on("connect", () => setTimeout(function() {
|
NRF.on("connect", () => setTimeout(function() {
|
||||||
sendBattery();
|
sendBattery();
|
||||||
|
@ -427,4 +435,4 @@
|
||||||
|
|
||||||
// remove settings object so it's not taking up RAM
|
// remove settings object so it's not taking up RAM
|
||||||
delete settings;
|
delete settings;
|
||||||
})();
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "android",
|
"id": "android",
|
||||||
"name": "Android Integration",
|
"name": "Android Integration",
|
||||||
"shortName": "Android",
|
"shortName": "Android",
|
||||||
"version": "0.34",
|
"version": "0.36",
|
||||||
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool,system,messages,notifications,gadgetbridge",
|
"tags": "tool,system,messages,notifications,gadgetbridge",
|
||||||
|
|
|
@ -1,153 +0,0 @@
|
||||||
let result = true;
|
|
||||||
|
|
||||||
function assertTrue(condition, text) {
|
|
||||||
if (!condition) {
|
|
||||||
result = false;
|
|
||||||
print("FAILURE: " + text);
|
|
||||||
} else print("OK: " + text);
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertFalse(condition, text) {
|
|
||||||
assertTrue(!condition, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertUndefinedOrEmpty(array, text) {
|
|
||||||
assertTrue(!array || array.length == 0, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertNotEmpty(array, text) {
|
|
||||||
assertTrue(array && array.length > 0, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
let internalOn = () => {
|
|
||||||
return getPinMode((process.env.HWVERSION==2)?D30:D26) == "input";
|
|
||||||
};
|
|
||||||
|
|
||||||
let sec = {
|
|
||||||
connected: false
|
|
||||||
};
|
|
||||||
|
|
||||||
NRF.getSecurityStatus = () => sec;
|
|
||||||
// add an empty starting point to make the asserts work
|
|
||||||
Bangle._PWR={};
|
|
||||||
|
|
||||||
let teststeps = [];
|
|
||||||
|
|
||||||
teststeps.push(()=>{
|
|
||||||
print("Not connected, should use internal GPS");
|
|
||||||
assertTrue(!NRF.getSecurityStatus().connected, "Not connected");
|
|
||||||
|
|
||||||
assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS");
|
|
||||||
assertFalse(Bangle.isGPSOn(), "isGPSOn");
|
|
||||||
|
|
||||||
assertTrue(Bangle.setGPSPower(1, "test"), "Switch GPS on");
|
|
||||||
|
|
||||||
assertNotEmpty(Bangle._PWR.GPS, "GPS");
|
|
||||||
assertTrue(Bangle.isGPSOn(), "isGPSOn");
|
|
||||||
assertTrue(internalOn(), "Internal GPS on");
|
|
||||||
|
|
||||||
assertFalse(Bangle.setGPSPower(0, "test"), "Switch GPS off");
|
|
||||||
|
|
||||||
assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS");
|
|
||||||
assertFalse(Bangle.isGPSOn(), "isGPSOn");
|
|
||||||
assertFalse(internalOn(), "Internal GPS off");
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
teststeps.push(()=>{
|
|
||||||
print("Connected, should use GB GPS");
|
|
||||||
sec.connected = true;
|
|
||||||
|
|
||||||
assertTrue(NRF.getSecurityStatus().connected, "Connected");
|
|
||||||
|
|
||||||
assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS");
|
|
||||||
assertFalse(Bangle.isGPSOn(), "isGPSOn");
|
|
||||||
assertFalse(internalOn(), "Internal GPS off");
|
|
||||||
|
|
||||||
|
|
||||||
print("Internal GPS stays on until the first GadgetBridge event arrives");
|
|
||||||
assertTrue(Bangle.setGPSPower(1, "test"), "Switch GPS on");
|
|
||||||
|
|
||||||
assertNotEmpty(Bangle._PWR.GPS, "GPS");
|
|
||||||
assertTrue(Bangle.isGPSOn(), "isGPSOn");
|
|
||||||
assertTrue(internalOn(), "Internal GPS on");
|
|
||||||
|
|
||||||
print("Send minimal GadgetBridge GPS event to trigger switch");
|
|
||||||
GB({t:"gps"});
|
|
||||||
});
|
|
||||||
|
|
||||||
teststeps.push(()=>{
|
|
||||||
print("GPS should be on, internal off");
|
|
||||||
|
|
||||||
assertNotEmpty(Bangle._PWR.GPS, "GPS");
|
|
||||||
assertTrue(Bangle.isGPSOn(), "isGPSOn");
|
|
||||||
assertFalse(internalOn(), "Internal GPS off");
|
|
||||||
});
|
|
||||||
|
|
||||||
teststeps.push(()=>{
|
|
||||||
print("Switching GPS off turns both GadgetBridge as well as internal off");
|
|
||||||
assertFalse(Bangle.setGPSPower(0, "test"), "Switch GPS off");
|
|
||||||
|
|
||||||
assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS");
|
|
||||||
assertFalse(Bangle.isGPSOn(), "isGPSOn");
|
|
||||||
assertFalse(internalOn(), "Internal GPS off");
|
|
||||||
});
|
|
||||||
|
|
||||||
teststeps.push(()=>{
|
|
||||||
print("Wait for all timeouts to run out");
|
|
||||||
return 12000;
|
|
||||||
});
|
|
||||||
|
|
||||||
teststeps.push(()=>{
|
|
||||||
print("Check auto switch when no GPS event arrives");
|
|
||||||
|
|
||||||
assertTrue(Bangle.setGPSPower(1, "test"), "Switch GPS on");
|
|
||||||
|
|
||||||
assertNotEmpty(Bangle._PWR.GPS, "GPS");
|
|
||||||
assertTrue(Bangle.isGPSOn(), "isGPSOn");
|
|
||||||
assertTrue(internalOn(), "Internal GPS on");
|
|
||||||
|
|
||||||
print("Send minimal GadgetBridge GPS event to trigger switch");
|
|
||||||
GB({t:"gps"});
|
|
||||||
|
|
||||||
print("Internal should be switched off now");
|
|
||||||
|
|
||||||
assertNotEmpty(Bangle._PWR.GPS, "GPS");
|
|
||||||
assertTrue(Bangle.isGPSOn(), "isGPSOn");
|
|
||||||
assertFalse(internalOn(), "Internal GPS off");
|
|
||||||
|
|
||||||
//wait on next test
|
|
||||||
return 12000;
|
|
||||||
});
|
|
||||||
|
|
||||||
teststeps.push(()=>{
|
|
||||||
print("Check state and disable GPS, internal should be on");
|
|
||||||
|
|
||||||
assertNotEmpty(Bangle._PWR.GPS, "GPS");
|
|
||||||
assertTrue(Bangle.isGPSOn(), "isGPSOn");
|
|
||||||
assertTrue(internalOn(), "Internal GPS on");
|
|
||||||
|
|
||||||
assertFalse(Bangle.setGPSPower(0, "test"), "Switch GPS off");
|
|
||||||
});
|
|
||||||
|
|
||||||
teststeps.push(()=>{
|
|
||||||
print("Result Overall is " + (result ? "OK" : "FAIL"));
|
|
||||||
});
|
|
||||||
|
|
||||||
let wrap = (functions) => {
|
|
||||||
if (functions.length > 0) {
|
|
||||||
setTimeout(()=>{
|
|
||||||
let waitingTime = functions.shift()();
|
|
||||||
if (waitingTime){
|
|
||||||
print("WAITING: ", waitingTime);
|
|
||||||
setTimeout(()=>{wrap(functions);}, waitingTime);
|
|
||||||
} else
|
|
||||||
wrap(functions);
|
|
||||||
},0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
setTimeout(()=>{
|
|
||||||
wrap(teststeps);
|
|
||||||
}, 5000);
|
|
||||||
|
|
|
@ -6,3 +6,4 @@
|
||||||
0.06: Fix azimuth (bug #2651), only show degrees
|
0.06: Fix azimuth (bug #2651), only show degrees
|
||||||
0.07: Minor code improvements
|
0.07: Minor code improvements
|
||||||
0.08: Minor code improvements
|
0.08: Minor code improvements
|
||||||
|
0.09: Fix: Handle when the moon rise/set do not occur on the current day
|
||||||
|
|
|
@ -180,8 +180,8 @@ function drawMoonTimesPage(gps, title) {
|
||||||
const moonColor = g.theme.dark ? {r: 1, g: 1, b: 1} : {r: 0, g: 0, b: 0};
|
const moonColor = g.theme.dark ? {r: 1, g: 1, b: 1} : {r: 0, g: 0, b: 0};
|
||||||
|
|
||||||
const pageData = {
|
const pageData = {
|
||||||
Rise: dateToTimeString(times.rise),
|
Rise: times.rise ? dateToTimeString(times.rise) : "Not today",
|
||||||
Set: dateToTimeString(times.set),
|
Set: times.set ? dateToTimeString(times.set) : "Not today",
|
||||||
};
|
};
|
||||||
|
|
||||||
drawData(title, pageData, null, g.getHeight()/2 - Object.keys(pageData).length/2*20 + 5);
|
drawData(title, pageData, null, g.getHeight()/2 - Object.keys(pageData).length/2*20 + 5);
|
||||||
|
@ -240,7 +240,7 @@ function sunIndexPageMenu(gps) {
|
||||||
"title": "-- Sun --",
|
"title": "-- Sun --",
|
||||||
},
|
},
|
||||||
"Current Pos": () => {
|
"Current Pos": () => {
|
||||||
m = E.showMenu();
|
E.showMenu();
|
||||||
drawSunShowPage(gps, "Current Pos", new Date());
|
drawSunShowPage(gps, "Current Pos", new Date());
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -248,13 +248,13 @@ function sunIndexPageMenu(gps) {
|
||||||
Object.keys(sunTimes).sort().reduce((menu, key) => {
|
Object.keys(sunTimes).sort().reduce((menu, key) => {
|
||||||
const title = titlizeKey(key);
|
const title = titlizeKey(key);
|
||||||
menu[title] = () => {
|
menu[title] = () => {
|
||||||
m = E.showMenu();
|
E.showMenu();
|
||||||
drawSunShowPage(gps, key, sunTimes[key]);
|
drawSunShowPage(gps, key, sunTimes[key]);
|
||||||
};
|
};
|
||||||
return menu;
|
return menu;
|
||||||
}, sunMenu);
|
}, sunMenu);
|
||||||
|
|
||||||
sunMenu["< Back"] = () => m = indexPageMenu(gps);
|
sunMenu["< Back"] = () => indexPageMenu(gps);
|
||||||
|
|
||||||
return E.showMenu(sunMenu);
|
return E.showMenu(sunMenu);
|
||||||
}
|
}
|
||||||
|
@ -266,18 +266,18 @@ function moonIndexPageMenu(gps) {
|
||||||
"title": "-- Moon --",
|
"title": "-- Moon --",
|
||||||
},
|
},
|
||||||
"Times": () => {
|
"Times": () => {
|
||||||
m = E.showMenu();
|
E.showMenu();
|
||||||
drawMoonTimesPage(gps, /*LANG*/"Times");
|
drawMoonTimesPage(gps, /*LANG*/"Times");
|
||||||
},
|
},
|
||||||
"Position": () => {
|
"Position": () => {
|
||||||
m = E.showMenu();
|
E.showMenu();
|
||||||
drawMoonPositionPage(gps, /*LANG*/"Position");
|
drawMoonPositionPage(gps, /*LANG*/"Position");
|
||||||
},
|
},
|
||||||
"Illumination": () => {
|
"Illumination": () => {
|
||||||
m = E.showMenu();
|
E.showMenu();
|
||||||
drawMoonIlluminationPage(gps, /*LANG*/"Illumination");
|
drawMoonIlluminationPage(gps, /*LANG*/"Illumination");
|
||||||
},
|
},
|
||||||
"< Back": () => m = indexPageMenu(gps),
|
"< Back": () => indexPageMenu(gps),
|
||||||
};
|
};
|
||||||
|
|
||||||
return E.showMenu(moonMenu);
|
return E.showMenu(moonMenu);
|
||||||
|
@ -289,10 +289,10 @@ function indexPageMenu(gps) {
|
||||||
"title": /*LANG*/"Select",
|
"title": /*LANG*/"Select",
|
||||||
},
|
},
|
||||||
/*LANG*/"Sun": () => {
|
/*LANG*/"Sun": () => {
|
||||||
m = sunIndexPageMenu(gps);
|
sunIndexPageMenu(gps);
|
||||||
},
|
},
|
||||||
/*LANG*/"Moon": () => {
|
/*LANG*/"Moon": () => {
|
||||||
m = moonIndexPageMenu(gps);
|
moonIndexPageMenu(gps);
|
||||||
},
|
},
|
||||||
"< Back": () => { load(); }
|
"< Back": () => { load(); }
|
||||||
};
|
};
|
||||||
|
@ -300,9 +300,9 @@ function indexPageMenu(gps) {
|
||||||
return E.showMenu(menu);
|
return E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCenterStringX(str) {
|
//function getCenterStringX(str) {
|
||||||
return (g.getWidth() - g.stringWidth(str)) / 2;
|
// return (g.getWidth() - g.stringWidth(str)) / 2;
|
||||||
}
|
//}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
let location = require("Storage").readJSON("mylocation.json",1)||{"lat":51.5072,"lon":0.1276,"location":"London"};
|
let location = require("Storage").readJSON("mylocation.json",1)||{"lat":51.5072,"lon":0.1276,"location":"London"};
|
||||||
|
@ -311,5 +311,4 @@ function init() {
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
}
|
}
|
||||||
|
|
||||||
let m;
|
|
||||||
init();
|
init();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "astrocalc",
|
"id": "astrocalc",
|
||||||
"name": "Astrocalc",
|
"name": "Astrocalc",
|
||||||
"version": "0.08",
|
"version": "0.09",
|
||||||
"description": "Calculates interesting information on the sun like sunset and sunrise and moon cycles for the current day based on your location from MyLocation app",
|
"description": "Calculates interesting information on the sun like sunset and sunrise and moon cycles for the current day based on your location from MyLocation app",
|
||||||
"icon": "astrocalc.png",
|
"icon": "astrocalc.png",
|
||||||
"tags": "app,sun,moon,cycles,tool,outdoors",
|
"tags": "app,sun,moon,cycles,tool,outdoors",
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
|
0.02: Add black- and whitelist for apps. Configure the timout time.
|
||||||
|
|
|
@ -6,11 +6,11 @@ Sets a timeout to load the clock face. The timeout is stopped and started again
|
||||||
|
|
||||||
Install with app loader and Auto Reset will run in background. If you don't interact with the watch it will time out to the clock face after 10 minutes.
|
Install with app loader and Auto Reset will run in background. If you don't interact with the watch it will time out to the clock face after 10 minutes.
|
||||||
|
|
||||||
|
Through the settings apps can be black-/whitelisted and the timeout length can be configured.
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- Add settings page
|
- per app specific timeout lengths?
|
||||||
- set how many minutes the timeout should count down.
|
|
||||||
- whitelist/blacklist for apps.
|
|
||||||
|
|
||||||
## Requests
|
## Requests
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,29 @@
|
||||||
{
|
{
|
||||||
|
const DEFAULTS = {
|
||||||
|
mode: 0,
|
||||||
|
apps: [],
|
||||||
|
timeout: 10
|
||||||
|
};
|
||||||
|
const settings = require("Storage").readJSON("autoreset.json", 1) || DEFAULTS;
|
||||||
|
|
||||||
|
// Check if the back button should be enabled for the current app.
|
||||||
|
// app is the src file of the app.
|
||||||
|
// Derivative of the backswipe app's logic.
|
||||||
|
function enabledForApp(app) {
|
||||||
|
if (Bangle.CLOCK==1) return false;
|
||||||
|
if (!settings) return true;
|
||||||
|
let isListed = settings.apps.filter((a) => a.files.includes(app)).length > 0;
|
||||||
|
return settings.mode===0?!isListed:isListed;
|
||||||
|
}
|
||||||
|
|
||||||
let timeoutAutoreset;
|
let timeoutAutoreset;
|
||||||
let resetTimeoutAutoreset = (force)=>{
|
const resetTimeoutAutoreset = (force)=>{
|
||||||
if (timeoutAutoreset) clearTimeout(timeoutAutoreset);
|
if (timeoutAutoreset) clearTimeout(timeoutAutoreset);
|
||||||
setTimeout(()=>{ // Short outer timeout to make sure we have time to leave clock face before checking `Bangle.CLOCK!=1`.
|
setTimeout(()=>{ // Short outer timeout to make sure we have time to leave clock face before checking `Bangle.CLOCK!=1`.
|
||||||
if (Bangle.CLOCK!=1) { // Only add timeout if not already on clock face.
|
if (enabledForApp(global.__FILE__)) {
|
||||||
timeoutAutoreset = setTimeout(()=>{
|
timeoutAutoreset = setTimeout(()=>{
|
||||||
if (Bangle.CLOCK!=1) Bangle.showClock();
|
if (Bangle.CLOCK!=1) Bangle.showClock();
|
||||||
}, 10*60*1000);
|
}, settings.timeout*60*1000);
|
||||||
}
|
}
|
||||||
},200);
|
},200);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{ "id": "autoreset",
|
{ "id": "autoreset",
|
||||||
"name": "Auto Reset",
|
"name": "Auto Reset",
|
||||||
"version":"0.01",
|
"version":"0.02",
|
||||||
"description": "Sets a timeout to load the clock face. The timeout is stopped and started again upon user input.",
|
"description": "Sets a timeout to load the clock face. The timeout is stopped and started again upon user input.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "bootloader",
|
"type": "bootloader",
|
||||||
|
@ -8,6 +8,10 @@
|
||||||
"supports" : ["BANGLEJS2"],
|
"supports" : ["BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"autoreset.boot.js","url":"boot.js"}
|
{"name":"autoreset.boot.js","url":"boot.js"},
|
||||||
|
{"name":"autoreset.settings.js","url":"settings.js"}
|
||||||
|
],
|
||||||
|
"data":[
|
||||||
|
{"name":"autoreset.json"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Don't fire if the app uses swipes already.
|
0.02: Don't fire if the app uses swipes already.
|
||||||
0.03: Only count defined handlers in the handler array.
|
0.03: Only count defined handlers in the handler array.
|
||||||
|
0.04: Fix messages auto opened by `messagegui` could not be blacklisted. Needs
|
||||||
|
a refresh by deselecting and reselecting the "Messages" app throught Back Swipe
|
||||||
|
settings.
|
||||||
|
|
|
@ -47,9 +47,9 @@
|
||||||
function enabledForApp(app) {
|
function enabledForApp(app) {
|
||||||
if (!settings) return true;
|
if (!settings) return true;
|
||||||
if (settings.mode === 0) {
|
if (settings.mode === 0) {
|
||||||
return !(settings.apps.filter((a) => a.src === app).length > 0);
|
return !(settings.apps.filter((a) => (a.src===app)||(a.files&&a.files.includes(app))).length > 0); // The `a.src===app` and `a.files&&...` checks are for backwards compatibility. Otherwise only `a.files.includes(app)` is needed.
|
||||||
} else if (settings.mode === 1) {
|
} else if (settings.mode === 1) {
|
||||||
return settings.apps.filter((a) => a.src === app).length > 0;
|
return settings.apps.filter((a) => (a.src===app)||(a.files&&a.files.includes(app))).length > 0;
|
||||||
} else {
|
} else {
|
||||||
return settings.mode === 2 ? true : false;
|
return settings.mode === 2 ? true : false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{ "id": "backswipe",
|
{ "id": "backswipe",
|
||||||
"name": "Back Swipe",
|
"name": "Back Swipe",
|
||||||
"shortName":"BackSwipe",
|
"shortName":"BackSwipe",
|
||||||
"version":"0.03",
|
"version":"0.04",
|
||||||
"description": "Service that allows you to use an app's back button using left to right swipe gesture",
|
"description": "Service that allows you to use an app's back button using left to right swipe gesture",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "back,gesture,swipe",
|
"tags": "back,gesture,swipe",
|
||||||
|
|
|
@ -26,7 +26,8 @@
|
||||||
return appInfo && {
|
return appInfo && {
|
||||||
'name': appInfo.name,
|
'name': appInfo.name,
|
||||||
'sortorder': appInfo.sortorder,
|
'sortorder': appInfo.sortorder,
|
||||||
'src': appInfo.src
|
'src': appInfo.src,
|
||||||
|
'files': appInfo.files
|
||||||
};
|
};
|
||||||
}).filter(app => app && !!app.src);
|
}).filter(app => app && !!app.src);
|
||||||
apps.sort((a, b) => {
|
apps.sort((a, b) => {
|
||||||
|
|
|
@ -6,3 +6,4 @@
|
||||||
0.06: Tell clock widgets to hide.
|
0.06: Tell clock widgets to hide.
|
||||||
0.07: Better battery graphic - now has green, yellow and red sections; battery status reflected in the bar across the middle of the screen; current battery state checked only once every 15 minutes, leading to longer-lasting battery charge
|
0.07: Better battery graphic - now has green, yellow and red sections; battery status reflected in the bar across the middle of the screen; current battery state checked only once every 15 minutes, leading to longer-lasting battery charge
|
||||||
0.08: Minor code improvements
|
0.08: Minor code improvements
|
||||||
|
0.09: Something was changing the value of the "width" variable, which caused the battery usage feature to malfunction. The "width" variable has been renamed - the cause remains a mystery.
|
||||||
|
|
|
@ -12,7 +12,7 @@ Graphics.prototype.setFontOpenSans = function(scale) {
|
||||||
|
|
||||||
var drawTimeout;
|
var drawTimeout;
|
||||||
var lastBattCheck = 0;
|
var lastBattCheck = 0;
|
||||||
var width = 0;
|
var batteryUsedWidth = 0;
|
||||||
|
|
||||||
function queueDraw(millis_now) {
|
function queueDraw(millis_now) {
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
@ -32,8 +32,8 @@ function draw() {
|
||||||
|
|
||||||
if ((date.getTime() >= lastBattCheck + 15*60000) || Bangle.isCharging()) {
|
if ((date.getTime() >= lastBattCheck + 15*60000) || Bangle.isCharging()) {
|
||||||
lastBattCheck = date.getTime();
|
lastBattCheck = date.getTime();
|
||||||
width = E.getBattery();
|
batteryUsedWidth = E.getBattery();
|
||||||
width += width/2;
|
batteryUsedWidth += batteryUsedWidth/2;
|
||||||
}
|
}
|
||||||
|
|
||||||
g.reset();
|
g.reset();
|
||||||
|
@ -58,7 +58,7 @@ function draw() {
|
||||||
g.fillRect(167,163,170,167);
|
g.fillRect(167,163,170,167);
|
||||||
if (Bangle.isCharging()) {
|
if (Bangle.isCharging()) {
|
||||||
g.setColor(1,1,0);
|
g.setColor(1,1,0);
|
||||||
g.fillRect(12,162,12+width,168);
|
g.fillRect(12,162,12+batteryUsedWidth,168);
|
||||||
} else {
|
} else {
|
||||||
g.setColor(1,0,0);
|
g.setColor(1,0,0);
|
||||||
g.fillRect(12,162,57,168);
|
g.fillRect(12,162,57,168);
|
||||||
|
@ -67,16 +67,16 @@ function draw() {
|
||||||
g.setColor(0,1,0);
|
g.setColor(0,1,0);
|
||||||
g.fillRect(73,162,162,168);
|
g.fillRect(73,162,162,168);
|
||||||
}
|
}
|
||||||
if (width < 150) {
|
if (batteryUsedWidth < 150) {
|
||||||
g.setColor(g.theme.bg);
|
g.setColor(g.theme.bg);
|
||||||
g.fillRect(12+width+1,162,162,168);
|
g.fillRect(12+batteryUsedWidth+1,162,162,168);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Bangle.isCharging()) {
|
if (Bangle.isCharging()) {
|
||||||
g.setColor(1,1,0);
|
g.setColor(1,1,0);
|
||||||
} else if (width <= 45) {
|
} else if (batteryUsedWidth <= 45) {
|
||||||
g.setColor(1,0,0);
|
g.setColor(1,0,0);
|
||||||
} else if (width <= 60) {
|
} else if (batteryUsedWidth <= 60) {
|
||||||
g.setColor(1,1,0);
|
g.setColor(1,1,0);
|
||||||
} else {
|
} else {
|
||||||
g.setColor(0, 1, 0);
|
g.setColor(0, 1, 0);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{ "id": "bigdclock",
|
{ "id": "bigdclock",
|
||||||
"name": "Big digit clock containing just the essentials",
|
"name": "Big digit clock containing just the essentials",
|
||||||
"shortName":"Big digit clk",
|
"shortName":"Big digit clk",
|
||||||
"version": "0.08",
|
"version": "0.09",
|
||||||
"description": "A clock containing just the essentials, made as easy to read as possible for those of us that need glasses. It contains the time, the day-of-week, the day-of-month, and the current battery state-of-charge.",
|
"description": "A clock containing just the essentials, made as easy to read as possible for those of us that need glasses. It contains the time, the day-of-week, the day-of-month, and the current battery state-of-charge.",
|
||||||
"icon": "bigdclock.png",
|
"icon": "bigdclock.png",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
|
|
@ -3,3 +3,10 @@
|
||||||
0.03: Added setting for fullscreen option
|
0.03: Added setting for fullscreen option
|
||||||
0.04: Added settings to hide unused squares and show date
|
0.04: Added settings to hide unused squares and show date
|
||||||
0.05: Minor code improvements
|
0.05: Minor code improvements
|
||||||
|
0.06: Added setting to show battery and added artwork to date
|
||||||
|
0.07: Removed percentage from battery and cleaned up logic
|
||||||
|
0.08: Changed month to day and text color to black on date
|
||||||
|
0.09: Changed day color back to white
|
||||||
|
0.10: Add blinking when charging
|
||||||
|
0.11: Changed battery to buzz instead of blink and fixed battery counter
|
||||||
|
0.12: Got rid of battery counter
|
|
@ -1,13 +1,50 @@
|
||||||
var settings = Object.assign({
|
var settings = Object.assign({
|
||||||
fullscreen: false,
|
fullscreen: true,
|
||||||
hidesq: false,
|
hidesq: false,
|
||||||
showdate: false,
|
showdate: true,
|
||||||
|
showbat: true,
|
||||||
}, require('Storage').readJSON("binaryclk.json", true) || {});
|
}, require('Storage').readJSON("binaryclk.json", true) || {});
|
||||||
|
|
||||||
function draw() {
|
var gap = 4;
|
||||||
|
var mgn = 24;
|
||||||
|
var sq = 33;
|
||||||
|
|
||||||
|
if (settings.fullscreen) {
|
||||||
|
gap = 8;
|
||||||
|
mgn = 0;
|
||||||
|
sq = 34;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pos = sq + gap;
|
||||||
|
|
||||||
|
function drawbat() {
|
||||||
|
var bat = E.getBattery();
|
||||||
|
if (bat < 20) {
|
||||||
|
g.setColor('#FF0000');
|
||||||
|
} else if (bat < 40) {
|
||||||
|
g.setColor('#FFA500');
|
||||||
|
} else {
|
||||||
|
g.setColor('#00FF00');
|
||||||
|
}
|
||||||
|
g.fillRect(Math.floor(mgn/2) + gap + 2 * pos, mgn + gap, Math.floor(mgn/2) + gap + 2 * pos + Math.floor(bat * sq / 100), mgn + gap + sq);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawbatrect() {
|
||||||
|
if (g.theme.dark) {
|
||||||
|
g.setColor(-1);
|
||||||
|
} else {
|
||||||
|
g.setColor(1);
|
||||||
|
}
|
||||||
|
g.drawRect(Math.floor(mgn/2) + gap + 2 * pos, mgn + gap, Math.floor(mgn/2) + gap + 2 * pos + sq, mgn + gap + sq);
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
let i = 0;
|
||||||
var dt = new Date();
|
var dt = new Date();
|
||||||
var h = dt.getHours(), m = dt.getMinutes(), d = dt.getDate();
|
var h = dt.getHours();
|
||||||
|
var m = dt.getMinutes();
|
||||||
|
var d = dt.getDate();
|
||||||
|
var day = dt.toString().substring(0,3);
|
||||||
const t = [];
|
const t = [];
|
||||||
|
|
||||||
t[0] = Math.floor(h/10);
|
t[0] = Math.floor(h/10);
|
||||||
|
@ -18,18 +55,6 @@ function draw() {
|
||||||
g.reset();
|
g.reset();
|
||||||
g.clearRect(Bangle.appRect);
|
g.clearRect(Bangle.appRect);
|
||||||
|
|
||||||
let i = 0;
|
|
||||||
var gap = 8;
|
|
||||||
var mgn = 20;
|
|
||||||
|
|
||||||
if (settings.fullscreen) {
|
|
||||||
gap = 12;
|
|
||||||
mgn = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sq = 29;
|
|
||||||
var pos = sq + gap;
|
|
||||||
|
|
||||||
for (let r = 3; r >= 0; r--) {
|
for (let r = 3; r >= 0; r--) {
|
||||||
for (let c = 0; c < 4; c++) {
|
for (let c = 0; c < 4; c++) {
|
||||||
if (t[c] & Math.pow(2, r)) {
|
if (t[c] & Math.pow(2, r)) {
|
||||||
|
@ -47,25 +72,43 @@ function draw() {
|
||||||
if (settings.hidesq) {
|
if (settings.hidesq) {
|
||||||
c1sqhide = 2;
|
c1sqhide = 2;
|
||||||
c3sqhide = 1;
|
c3sqhide = 1;
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.hidesq) {
|
|
||||||
g.clearRect(Math.floor(mgn/2), mgn, Math.floor(mgn/2) + pos, mgn + c1sqhide * pos);
|
g.clearRect(Math.floor(mgn/2), mgn, Math.floor(mgn/2) + pos, mgn + c1sqhide * pos);
|
||||||
g.clearRect(Math.floor(mgn/2) + 2 * pos + gap, mgn, Math.floor(mgn/2) + 3 * pos, mgn + c3sqhide * pos);
|
g.clearRect(Math.floor(mgn/2) + 2 * pos + gap, mgn, Math.floor(mgn/2) + 3 * pos, mgn + c3sqhide * pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.showdate) {
|
if (settings.showdate) {
|
||||||
g.setFontAlign(0, 0);
|
g.setColor(-1).fillRect(Math.floor(mgn/2) + gap, mgn + gap, Math.floor(mgn/2) + gap + sq, mgn + gap + sq);
|
||||||
|
g.setColor('#FF0000').fillRect(Math.floor(mgn/2) + gap, mgn + gap, Math.floor(mgn/2) + gap + sq, mgn + gap + 12);
|
||||||
|
g.setFontAlign(0, -1);
|
||||||
|
g.setFont("Vector",12);
|
||||||
|
g.setColor(-1).drawString(day, Math.ceil(mgn/2) + gap + Math.ceil(sq/2) + 1, mgn + gap + 1);
|
||||||
|
g.setFontAlign(0, 1);
|
||||||
g.setFont("Vector",20);
|
g.setFont("Vector",20);
|
||||||
|
g.setColor(1).drawString(d, Math.ceil(mgn/2) + gap + Math.ceil(sq/2) + 1, mgn + gap + sq + 2);
|
||||||
|
if (g.theme.dark) {
|
||||||
|
g.setColor(-1);
|
||||||
|
} else {
|
||||||
|
g.setColor(1);
|
||||||
|
g.drawLine(Math.floor(mgn/2) + gap, mgn + gap + 13, Math.floor(mgn/2) + gap + sq, mgn + gap + 13);
|
||||||
|
}
|
||||||
g.drawRect(Math.floor(mgn/2) + gap, mgn + gap, Math.floor(mgn/2) + gap + sq, mgn + gap + sq);
|
g.drawRect(Math.floor(mgn/2) + gap, mgn + gap, Math.floor(mgn/2) + gap + sq, mgn + gap + sq);
|
||||||
g.drawString(d, Math.ceil(mgn/2) + gap + Math.ceil(sq/2) + 1, mgn + gap + Math.ceil(sq/2) + 1);
|
}
|
||||||
|
|
||||||
|
if (settings.showbat) {
|
||||||
|
drawbat();
|
||||||
|
drawbatrect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g.clear();
|
g.clear();
|
||||||
draw();
|
draw();
|
||||||
/*var secondInterval =*/ setInterval(draw, 60000);
|
setInterval(draw, 60000);
|
||||||
Bangle.setUI("clock");
|
Bangle.setUI("clock");
|
||||||
if (!settings.fullscreen) {
|
if (!settings.fullscreen) {
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bangle.on('charging', function(charging) {
|
||||||
|
if(charging) Bangle.buzz();
|
||||||
|
});
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"id": "binaryclk",
|
"id": "binaryclk",
|
||||||
"name": "Bin Clock",
|
"name": "Bin Clock",
|
||||||
"version": "0.05",
|
"version": "0.12",
|
||||||
"description": "Clock face to show binary time in 24 hour format",
|
"description": "Binary clock with date and battery",
|
||||||
"icon": "app-icon.png",
|
"icon": "app-icon.png",
|
||||||
"screenshots": [{"url":"screenshot.png"}],
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 2.1 KiB |
|
@ -4,6 +4,7 @@
|
||||||
fullscreen: false,
|
fullscreen: false,
|
||||||
hidesq: false,
|
hidesq: false,
|
||||||
showdate: false,
|
showdate: false,
|
||||||
|
showbat: false,
|
||||||
}, require('Storage').readJSON(FILE, true) || {});
|
}, require('Storage').readJSON(FILE, true) || {});
|
||||||
|
|
||||||
function writeSettings() {
|
function writeSettings() {
|
||||||
|
@ -34,5 +35,12 @@
|
||||||
writeSettings();
|
writeSettings();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'Show Battery': {
|
||||||
|
value: settings.showbat,
|
||||||
|
onchange: v => {
|
||||||
|
settings.showbat = v;
|
||||||
|
writeSettings();
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
|
@ -69,3 +69,6 @@
|
||||||
0.58: "Make Connectable" temporarily bypasses the whitelist
|
0.58: "Make Connectable" temporarily bypasses the whitelist
|
||||||
0.59: Whitelist: Try to resolve peer addresses using NRF.resolveAddress() - for 2v19 or 2v18 cutting edge builds
|
0.59: Whitelist: Try to resolve peer addresses using NRF.resolveAddress() - for 2v19 or 2v18 cutting edge builds
|
||||||
0.60: Minor code improvements
|
0.60: Minor code improvements
|
||||||
|
0.61: Instead of breaking execution with an Exception when updating boot, just use if..else (fix 'Uncaught undefined')
|
||||||
|
0.62: Handle setting for configuring BLE privacy
|
||||||
|
0.63: Only set BLE `display:1` if we have a passkey
|
||||||
|
|
|
@ -19,7 +19,7 @@ if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't chang
|
||||||
let CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT);
|
let CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT);
|
||||||
boot += `if(E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
|
boot += `if(E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
|
||||||
}
|
}
|
||||||
boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`;
|
boot += `{eval(require('Storage').read('bootupdate.js'));print("Storage Updated!")}else{\n`;
|
||||||
boot += `E.setFlags({pretokenise:1});\n`;
|
boot += `E.setFlags({pretokenise:1});\n`;
|
||||||
boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`;
|
boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`;
|
||||||
bootPost += `NRF.setServices(bleServices,bleServiceOptions);delete bleServices,bleServiceOptions;\n`; // executed after other boot code
|
bootPost += `NRF.setServices(bleServices,bleServiceOptions);delete bleServices,bleServiceOptions;\n`; // executed after other boot code
|
||||||
|
@ -78,7 +78,12 @@ if (global.save) boot += `global.save = function() { throw new Error("You can't
|
||||||
// Apply any settings-specific stuff
|
// Apply any settings-specific stuff
|
||||||
if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`;
|
if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`;
|
||||||
if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`;
|
if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\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.bleprivacy || (s.passkey!==undefined && s.passkey.length==6)) {
|
||||||
|
let passkey = s.passkey ? `passkey:${E.toJS(s.passkey.toString())},display:1,mitm:1,` : "";
|
||||||
|
let privacy = s.bleprivacy ? `privacy:${E.toJS(s.bleprivacy)},` : "";
|
||||||
|
boot+=`NRF.setSecurity({${passkey}${privacy}});\n`;
|
||||||
|
}
|
||||||
|
if (s.blename === false) boot+=`NRF.setAdvertising({},{showName:false});\n`;
|
||||||
if (s.whitelist && !s.whitelist_disabled) boot+=`NRF.on('connect', function(addr) { if (!NRF.ignoreWhitelist) { let whitelist = (require('Storage').readJSON('setting.json',1)||{}).whitelist; if (NRF.resolveAddress !== undefined) { let resolvedAddr = NRF.resolveAddress(addr); if (resolvedAddr !== undefined) addr = resolvedAddr + " (resolved)"; } if (!whitelist.includes(addr)) NRF.disconnect(); }});\n`;
|
if (s.whitelist && !s.whitelist_disabled) boot+=`NRF.on('connect', function(addr) { if (!NRF.ignoreWhitelist) { let whitelist = (require('Storage').readJSON('setting.json',1)||{}).whitelist; if (NRF.resolveAddress !== undefined) { let resolvedAddr = NRF.resolveAddress(addr); if (resolvedAddr !== undefined) addr = resolvedAddr + " (resolved)"; } if (!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
|
||||||
// ================================================== FIXING OLDER FIRMWARES
|
// ================================================== FIXING OLDER FIRMWARES
|
||||||
|
@ -122,6 +127,7 @@ let bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
|
||||||
return a==b ? 0 : (a>b ? 1 : -1);
|
return a==b ? 0 : (a>b ? 1 : -1);
|
||||||
});
|
});
|
||||||
// precalculate file size
|
// precalculate file size
|
||||||
|
bootPost += "}";
|
||||||
let fileSize = boot.length + bootPost.length;
|
let fileSize = boot.length + bootPost.length;
|
||||||
bootFiles.forEach(bootFile=>{
|
bootFiles.forEach(bootFile=>{
|
||||||
// match the size of data we're adding below in bootFiles.forEach
|
// match the size of data we're adding below in bootFiles.forEach
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "boot",
|
"id": "boot",
|
||||||
"name": "Bootloader",
|
"name": "Bootloader",
|
||||||
"version": "0.60",
|
"version": "0.63",
|
||||||
"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",
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
0.01: Initial release.
|
0.01: Initial release.
|
||||||
0.02: Handle the case where other apps have set bleAdvert to an array
|
0.02: Handle the case where other apps have set bleAdvert to an array
|
||||||
|
0.03: Use the bleAdvert module
|
||||||
|
|
|
@ -1,26 +1,8 @@
|
||||||
(() => {
|
(() => {
|
||||||
function advertiseBattery() {
|
function advertiseBattery() {
|
||||||
if(Array.isArray(Bangle.bleAdvert)){
|
require("ble_advert").set(0x180F, [E.getBattery()]);
|
||||||
// ensure we're in the cycle
|
|
||||||
var found = false;
|
|
||||||
for(var ad in Bangle.bleAdvert){
|
|
||||||
if(ad[0x180F]){
|
|
||||||
ad[0x180F] = [E.getBattery()];
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!found)
|
|
||||||
Bangle.bleAdvert.push({ 0x180F: [E.getBattery()] });
|
|
||||||
}else{
|
|
||||||
// simple object
|
|
||||||
Bangle.bleAdvert[0x180F] = [E.getBattery()];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NRF.setAdvertising(Bangle.bleAdvert);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Bangle.bleAdvert) Bangle.bleAdvert = {};
|
|
||||||
setInterval(advertiseBattery, 60 * 1000);
|
setInterval(advertiseBattery, 60 * 1000);
|
||||||
advertiseBattery();
|
advertiseBattery();
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "bootgattbat",
|
"id": "bootgattbat",
|
||||||
"name": "BLE GATT Battery Service",
|
"name": "BLE GATT Battery Service",
|
||||||
"shortName": "BLE Battery Service",
|
"shortName": "BLE Battery Service",
|
||||||
"version": "0.02",
|
"version": "0.03",
|
||||||
"description": "Adds the GATT Battery Service to advertise the percentage of battery currently remaining over Bluetooth.\n",
|
"description": "Adds the GATT Battery Service to advertise the percentage of battery currently remaining over Bluetooth.\n",
|
||||||
"icon": "bluetooth.png",
|
"icon": "bluetooth.png",
|
||||||
"type": "bootloader",
|
"type": "bootloader",
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
0.01: Initial release.
|
0.01: Initial release.
|
||||||
0.02: Added compatibility to OpenTracks and added HRM Location
|
0.02: Added compatibility to OpenTracks and added HRM Location
|
||||||
|
0.03: Allow setting to keep BLE connected
|
||||||
|
0.04: Use the bleAdvert module
|
||||||
|
|
|
@ -4,18 +4,13 @@
|
||||||
* This function prepares BLE heart rate Advertisement.
|
* This function prepares BLE heart rate Advertisement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
NRF.setAdvertising(
|
require("ble_advert").set(0x180d, undefined, {
|
||||||
{
|
|
||||||
0x180d: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// We need custom Advertisement settings for Apps like OpenTracks
|
// We need custom Advertisement settings for Apps like OpenTracks
|
||||||
connectable: true,
|
connectable: true,
|
||||||
discoverable: true,
|
discoverable: true,
|
||||||
scannable: true,
|
scannable: true,
|
||||||
whenConnected: true,
|
whenConnected: true,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
NRF.setServices({
|
NRF.setServices({
|
||||||
0x180D: { // heart_rate
|
0x180D: { // heart_rate
|
||||||
|
@ -28,8 +23,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const keepConnected = (require("Storage").readJSON("gatthrm.settings.json", 1) || {}).keepConnected;
|
||||||
|
|
||||||
function updateBLEHeartRate(hrm) {
|
function updateBLEHeartRate(hrm) {
|
||||||
/*
|
/*
|
||||||
* Send updated heart rate measurement via BLE
|
* Send updated heart rate measurement via BLE
|
||||||
|
@ -52,6 +49,7 @@
|
||||||
/*
|
/*
|
||||||
* BLE has to restart after service setup.
|
* BLE has to restart after service setup.
|
||||||
*/
|
*/
|
||||||
|
if(!keepConnected)
|
||||||
NRF.disconnect();
|
NRF.disconnect();
|
||||||
}
|
}
|
||||||
else if (error.message.includes("UUID 0x2a37")) {
|
else if (error.message.includes("UUID 0x2a37")) {
|
||||||
|
@ -66,5 +64,5 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
setupHRMAdvertising();
|
setupHRMAdvertising();
|
||||||
Bangle.on("HRM", function (hrm) { updateBLEHeartRate(hrm); });
|
Bangle.on("HRM", updateBLEHeartRate);
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "bootgatthrm",
|
"id": "bootgatthrm",
|
||||||
"name": "BLE GATT HRM Service",
|
"name": "BLE GATT HRM Service",
|
||||||
"shortName": "BLE HRM Service",
|
"shortName": "BLE HRM Service",
|
||||||
"version": "0.02",
|
"version": "0.04",
|
||||||
"description": "Adds the GATT HRM Service to advertise the measured HRM over Bluetooth.\n",
|
"description": "Adds the GATT HRM Service to advertise the measured HRM over Bluetooth.\n",
|
||||||
"icon": "bluetooth.png",
|
"icon": "bluetooth.png",
|
||||||
"type": "bootloader",
|
"type": "bootloader",
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
0.01: New app!
|
0.01: New app!
|
||||||
0.02: Advertise accelerometer data and sensor location
|
0.02: Advertise accelerometer data and sensor location
|
||||||
|
0.03: Use the bleAdvert module
|
||||||
|
0.04: Actually use the ble_advert module
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
var _a;
|
|
||||||
{
|
{
|
||||||
var __assign = Object.assign;
|
var __assign = Object.assign;
|
||||||
var Layout_1 = require("Layout");
|
var Layout_1 = require("Layout");
|
||||||
|
@ -441,8 +440,6 @@ var _a;
|
||||||
NRF.setServices(ad, {
|
NRF.setServices(ad, {
|
||||||
uart: false,
|
uart: false,
|
||||||
});
|
});
|
||||||
var bangle2 = Bangle;
|
|
||||||
var cycle = Array.isArray(bangle2.bleAdvert) ? bangle2.bleAdvert : [];
|
|
||||||
for (var id in ad) {
|
for (var id in ad) {
|
||||||
var serv = ad[id];
|
var serv = ad[id];
|
||||||
var value = void 0;
|
var value = void 0;
|
||||||
|
@ -450,11 +447,7 @@ var _a;
|
||||||
value = serv[ch].value;
|
value = serv[ch].value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
cycle.push((_a = {}, _a[id] = value || [], _a));
|
require("ble_advert").set(id, value || []);
|
||||||
}
|
}
|
||||||
bangle2.bleAdvert = cycle;
|
|
||||||
NRF.setAdvertising(cycle, {
|
|
||||||
interval: 100,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
// @ts-ignore helper
|
// @ts-expect-error helper
|
||||||
|
|
||||||
const __assign = Object.assign;
|
const __assign = Object.assign;
|
||||||
|
|
||||||
const Layout = require("Layout");
|
const Layout = require("Layout");
|
||||||
|
@ -666,6 +667,8 @@ const getBleAdvert = <T>(map: (s: BleServ) => T, all = false) => {
|
||||||
|
|
||||||
// done via advertise in setServices()
|
// done via advertise in setServices()
|
||||||
//const updateBleAdvert = () => {
|
//const updateBleAdvert = () => {
|
||||||
|
// require("ble_advert").set(...)
|
||||||
|
//
|
||||||
// let bleAdvert: ReturnType<typeof getBleAdvert<undefined>>;
|
// let bleAdvert: ReturnType<typeof getBleAdvert<undefined>>;
|
||||||
//
|
//
|
||||||
// if (!(bleAdvert = (Bangle as any).bleAdvert)) {
|
// if (!(bleAdvert = (Bangle as any).bleAdvert)) {
|
||||||
|
@ -764,12 +767,6 @@ enableSensors();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
type BleAdvert = { [key: string]: number[] };
|
|
||||||
const bangle2 = Bangle as {
|
|
||||||
bleAdvert?: BleAdvert | BleAdvert[];
|
|
||||||
};
|
|
||||||
const cycle = Array.isArray(bangle2.bleAdvert) ? bangle2.bleAdvert : [];
|
|
||||||
|
|
||||||
for(const id in ad){
|
for(const id in ad){
|
||||||
const serv = ad[id as BleServ];
|
const serv = ad[id as BleServ];
|
||||||
let value;
|
let value;
|
||||||
|
@ -780,16 +777,7 @@ enableSensors();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
cycle.push({ [id]: value || [] });
|
require("ble_advert").set(id, value || []);
|
||||||
}
|
}
|
||||||
|
|
||||||
bangle2.bleAdvert = cycle;
|
|
||||||
|
|
||||||
NRF.setAdvertising(
|
|
||||||
cycle,
|
|
||||||
{
|
|
||||||
interval: 100,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "btadv",
|
"id": "btadv",
|
||||||
"name": "btadv",
|
"name": "btadv",
|
||||||
"shortName": "btadv",
|
"shortName": "btadv",
|
||||||
"version": "0.02",
|
"version": "0.04",
|
||||||
"description": "Advertise & export live heart rate, accel, pressure, GPS & mag data over bluetooth",
|
"description": "Advertise & export live heart rate, accel, pressure, GPS & mag data over bluetooth",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"tags": "health,tool,sensors,bluetooth",
|
"tags": "health,tool,sensors,bluetooth",
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Fix double-button press if you press the next button within 30s (#3243)
|
0.02: Fix double-button press if you press the next button within 30s (#3243)
|
||||||
|
0.03: Cope with identical duplicate buttons (fix #3260)
|
||||||
|
Set 'n' for buttons in Bangle.btHomeData correctly (avoids adding extra buttons on end of advertising)
|
||||||
|
0.04: Fix duplicate button on edit->save
|
||||||
|
0.05: Use the bleAdvert module
|
||||||
|
|
|
@ -5,20 +5,26 @@ function showMenu() {
|
||||||
var settings = require("Storage").readJSON("bthome.json",1)||{};
|
var settings = require("Storage").readJSON("bthome.json",1)||{};
|
||||||
if (!(settings.buttons instanceof Array))
|
if (!(settings.buttons instanceof Array))
|
||||||
settings.buttons = [];
|
settings.buttons = [];
|
||||||
var menu = { "": {title:"BTHome", back:load} };
|
var menu = [];
|
||||||
|
menu[""] = {title:"BTHome", back:load };
|
||||||
settings.buttons.forEach((button,idx) => {
|
settings.buttons.forEach((button,idx) => {
|
||||||
var img = require("icons").getIcon(button.icon);
|
var img = require("icons").getIcon(button.icon);
|
||||||
menu[/*LANG*/"\0"+img+" "+button.name] = function() {
|
menu.push({
|
||||||
|
title : /*LANG*/"\0"+img+" "+button.name,
|
||||||
|
onchange : function() {
|
||||||
Bangle.btHome([{type:"button_event",v:button.v,n:button.n}],{event:true});
|
Bangle.btHome([{type:"button_event",v:button.v,n:button.n}],{event:true});
|
||||||
E.showMenu();
|
E.showMenu();
|
||||||
E.showMessage("Sending Event");
|
E.showMessage("Sending Event");
|
||||||
Bangle.buzz();
|
Bangle.buzz();
|
||||||
setTimeout(showMenu, 500);
|
setTimeout(showMenu, 500);
|
||||||
};
|
}
|
||||||
});
|
});
|
||||||
menu[/*LANG*/"Settings"] = function() {
|
});
|
||||||
|
menu.push({
|
||||||
|
title : /*LANG*/"Settings",
|
||||||
|
onchange : function() {
|
||||||
eval(require("Storage").read("bthome.settings.js"))(()=>showMenu());
|
eval(require("Storage").read("bthome.settings.js"))(()=>showMenu());
|
||||||
};
|
}});
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
// Ensure we have the bleAdvert global (to play well with other stuff)
|
|
||||||
if (!Bangle.bleAdvert) Bangle.bleAdvert = {};
|
|
||||||
Bangle.btHomeData = [];
|
Bangle.btHomeData = [];
|
||||||
{
|
{
|
||||||
require("BTHome").packetId = 0|(Math.random()*256); // random packet id so new packets show up
|
require("BTHome").packetId = 0|(Math.random()*256); // random packet id so new packets show up
|
||||||
|
@ -13,7 +11,7 @@ Bangle.btHomeData = [];
|
||||||
if (settings.buttons instanceof Array) {
|
if (settings.buttons instanceof Array) {
|
||||||
let n = settings.buttons.reduce((n,b)=>b.n>n?b.n:n,-1);
|
let n = settings.buttons.reduce((n,b)=>b.n>n?b.n:n,-1);
|
||||||
for (var i=0;i<=n;i++)
|
for (var i=0;i<=n;i++)
|
||||||
Bangle.btHomeData.push({type:"button_event",v:"none",n:n});
|
Bangle.btHomeData.push({type:"button_event",v:"none",n:i});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,20 +37,6 @@ Bangle.btHome = function(extras, options) {
|
||||||
if (bat) bat.v = E.getBattery();
|
if (bat) bat.v = E.getBattery();
|
||||||
var advert = require("BTHome").getAdvertisement(Bangle.btHomeData)[0xFCD2];
|
var advert = require("BTHome").getAdvertisement(Bangle.btHomeData)[0xFCD2];
|
||||||
// Add to the list of available advertising
|
// Add to the list of available advertising
|
||||||
if(Array.isArray(Bangle.bleAdvert)){
|
|
||||||
var found = false;
|
|
||||||
for(var ad in Bangle.bleAdvert){
|
|
||||||
if(ad[0xFCD2]){
|
|
||||||
ad[0xFCD2] = advert;
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!found)
|
|
||||||
Bangle.bleAdvert.push({ 0xFCD2: advert });
|
|
||||||
} else {
|
|
||||||
Bangle.bleAdvert[0xFCD2] = advert;
|
|
||||||
}
|
|
||||||
var advOptions = {};
|
var advOptions = {};
|
||||||
var updateTimeout = 10*60*1000; // update every 10 minutes
|
var updateTimeout = 10*60*1000; // update every 10 minutes
|
||||||
if (options.event) { // if it's an event...
|
if (options.event) { // if it's an event...
|
||||||
|
@ -60,7 +44,7 @@ Bangle.btHome = function(extras, options) {
|
||||||
advOptions.whenConnected = true;
|
advOptions.whenConnected = true;
|
||||||
updateTimeout = 30000; // slow down in 30 seconds
|
updateTimeout = 30000; // slow down in 30 seconds
|
||||||
}
|
}
|
||||||
NRF.setAdvertising(Bangle.bleAdvert, advOptions);
|
require("ble_advert").set(0xFCD2, advert, advOptions);
|
||||||
if (Bangle.btHomeTimeout) clearTimeout(Bangle.btHomeTimeout);
|
if (Bangle.btHomeTimeout) clearTimeout(Bangle.btHomeTimeout);
|
||||||
Bangle.btHomeTimeout = setTimeout(function() {
|
Bangle.btHomeTimeout = setTimeout(function() {
|
||||||
delete Bangle.btHomeTimeout;
|
delete Bangle.btHomeTimeout;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{ "id": "bthome",
|
{ "id": "bthome",
|
||||||
"name": "BTHome",
|
"name": "BTHome",
|
||||||
"shortName":"BTHome",
|
"shortName":"BTHome",
|
||||||
"version":"0.02",
|
"version":"0.05",
|
||||||
"description": "Allow your Bangle to advertise with BTHome and send events to Home Assistant via Bluetooth",
|
"description": "Allow your Bangle to advertise with BTHome and send events to Home Assistant via Bluetooth",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
(function(back) {
|
(function(back) {
|
||||||
var settings = require("Storage").readJSON("bthome.json",1)||{};
|
var settings;
|
||||||
|
|
||||||
|
function loadSettings() {
|
||||||
|
settings = require("Storage").readJSON("bthome.json",1)||{};
|
||||||
if (!(settings.buttons instanceof Array))
|
if (!(settings.buttons instanceof Array))
|
||||||
settings.buttons = [];
|
settings.buttons = [];
|
||||||
|
}
|
||||||
|
|
||||||
function saveSettings() {
|
function saveSettings() {
|
||||||
require("Storage").writeJSON("bthome.json",settings)
|
require("Storage").writeJSON("bthome.json",settings)
|
||||||
|
@ -15,7 +19,10 @@
|
||||||
}
|
}
|
||||||
var actions = ["press","double_press","triple_press","long_press","long_double_press","long_triple_press"];
|
var actions = ["press","double_press","triple_press","long_press","long_double_press","long_triple_press"];
|
||||||
var menu = {
|
var menu = {
|
||||||
"":{title:isNew ? /*LANG*/"New Button" : /*LANG*/"Edit Button", back:showMenu},
|
"":{title:isNew ? /*LANG*/"New Button" : /*LANG*/"Edit Button", back: () => {
|
||||||
|
loadSettings(); // revert changes
|
||||||
|
showMenu();
|
||||||
|
}},
|
||||||
/*LANG*/"Icon" : {
|
/*LANG*/"Icon" : {
|
||||||
value : "\0"+require("icons").getIcon(button.icon),
|
value : "\0"+require("icons").getIcon(button.icon),
|
||||||
onchange : () => {
|
onchange : () => {
|
||||||
|
@ -49,7 +56,7 @@
|
||||||
onchange : v => button.n=v
|
onchange : v => button.n=v
|
||||||
},
|
},
|
||||||
/*LANG*/"Save" : () => {
|
/*LANG*/"Save" : () => {
|
||||||
settings.buttons.push(button);
|
if (isNew) settings.buttons.push(button);
|
||||||
saveSettings();
|
saveSettings();
|
||||||
showMenu();
|
showMenu();
|
||||||
}
|
}
|
||||||
|
@ -67,25 +74,34 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMenu() {
|
function showMenu() {
|
||||||
var menu = { "": {title:"BTHome", back:back},
|
var menu = [];
|
||||||
/*LANG*/"Show Battery" : {
|
menu[""] = {title:"BTHome", back:back};
|
||||||
|
menu.push({
|
||||||
|
title : /*LANG*/"Show Battery",
|
||||||
value : !!settings.showBattery,
|
value : !!settings.showBattery,
|
||||||
onchange : v=>{
|
onchange : v=>{
|
||||||
settings.showBattery = v;
|
settings.showBattery = v;
|
||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
};
|
|
||||||
settings.buttons.forEach((button,idx) => {
|
settings.buttons.forEach((button,idx) => {
|
||||||
var img = require("icons").getIcon(button.icon);
|
var img = require("icons").getIcon(button.icon);
|
||||||
menu[/*LANG*/"Button"+(img ? " \0"+img : (idx+1))] = function() {
|
menu.push({
|
||||||
|
title : /*LANG*/"Button"+(img ? " \0"+img : (idx+1)),
|
||||||
|
onchange : function() {
|
||||||
showButtonMenu(button, false);
|
showButtonMenu(button, false);
|
||||||
};
|
}
|
||||||
});
|
});
|
||||||
menu[/*LANG*/"Add Button"] = function() {
|
});
|
||||||
|
menu.push({
|
||||||
|
title : /*LANG*/"Add Button",
|
||||||
|
onchange : function() {
|
||||||
showButtonMenu(undefined, true);
|
showButtonMenu(undefined, true);
|
||||||
};
|
}
|
||||||
|
});
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadSettings();
|
||||||
showMenu();
|
showMenu();
|
||||||
})
|
})
|
|
@ -1,2 +1,3 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Handle the case where other apps have set bleAdvert to an array
|
0.02: Handle the case where other apps have set bleAdvert to an array
|
||||||
|
0.03: Use the ble_advert module
|
||||||
|
|
|
@ -38,21 +38,7 @@ function onTemperature(p) {
|
||||||
pressure100&255,(pressure100>>8)&255,pressure100>>16
|
pressure100&255,(pressure100>>8)&255,pressure100>>16
|
||||||
];
|
];
|
||||||
|
|
||||||
if(Array.isArray(Bangle.bleAdvert)){
|
require("ble_advert").set(0xFCD2, advert);
|
||||||
var found = false;
|
|
||||||
for(var ad in Bangle.bleAdvert){
|
|
||||||
if(ad[0xFCD2]){
|
|
||||||
ad[0xFCD2] = advert;
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!found)
|
|
||||||
Bangle.bleAdvert.push({ 0xFCD2: advert });
|
|
||||||
}else{
|
|
||||||
Bangle.bleAdvert[0xFCD2] = advert;
|
|
||||||
}
|
|
||||||
NRF.setAdvertising(Bangle.bleAdvert);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the temperature in the most accurate way with pressure sensor
|
// Gets the temperature in the most accurate way with pressure sensor
|
||||||
|
@ -60,7 +46,6 @@ function drawTemperature() {
|
||||||
Bangle.getPressure().then(p =>{if (p) onTemperature(p);});
|
Bangle.getPressure().then(p =>{if (p) onTemperature(p);});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Bangle.bleAdvert) Bangle.bleAdvert = {};
|
|
||||||
setInterval(function() {
|
setInterval(function() {
|
||||||
drawTemperature();
|
drawTemperature();
|
||||||
}, 10000); // update every 10s
|
}, 10000); // update every 10s
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{ "id": "bthometemp",
|
{ "id": "bthometemp",
|
||||||
"name": "BTHome Temperature and Pressure",
|
"name": "BTHome Temperature and Pressure",
|
||||||
"shortName":"BTHome T",
|
"shortName":"BTHome T",
|
||||||
"version":"0.02",
|
"version":"0.03",
|
||||||
"description": "Displays temperature and pressure, and advertises them over bluetooth for Home Assistant using BTHome.io standard",
|
"description": "Displays temperature and pressure, and advertises them over bluetooth for Home Assistant using BTHome.io standard",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "bthome,bluetooth,temperature",
|
"tags": "bthome,bluetooth,temperature",
|
||||||
|
|
|
@ -42,3 +42,8 @@
|
||||||
Add debug option for disabling active scanning
|
Add debug option for disabling active scanning
|
||||||
0.17: New GUI based on layout library
|
0.17: New GUI based on layout library
|
||||||
0.18: Minor code improvements
|
0.18: Minor code improvements
|
||||||
|
0.19: Move caching of characteristics into settings app
|
||||||
|
Changed default of active scanning to false
|
||||||
|
Fix setHRMPower method not returning new state
|
||||||
|
Only buzz for disconnect after switching on if there already was an actual connection
|
||||||
|
Fix recorder not switching BTHRM on and off
|
||||||
|
|
|
@ -21,6 +21,10 @@ Once installed you will have to go into this app's settings while your heart rat
|
||||||
|
|
||||||
**To disable this and return to normal HRM, uninstall the app or change the settings**
|
**To disable this and return to normal HRM, uninstall the app or change the settings**
|
||||||
|
|
||||||
|
The characteristics of your selected sensor are cached in the settings. That means if your sensor changes, e.g. by firmware updates or similar, you will need to re-scan in the settings to update the cache of characteristics. This is done to take some complexity (and time) out of the boot process.
|
||||||
|
|
||||||
|
Scanning in the settings will do 10 retries and then give up on adding the sensor. Usually that works fine, if it does not for you just try multiple times. Currently saved sensor information is only replaced on a successful pairing. There are additional options in the Debug entry of the menu that can help with specific sensor oddities. Bonding and active scanning can help with connecting, but can also prevent some sensors from working. The "Grace Periods" just add some additional time at certain steps in the connection process which can help with stability or reconnect speed of some finicky sensors. Defaults should be fine for most.
|
||||||
|
|
||||||
### Modes
|
### Modes
|
||||||
|
|
||||||
* Off - Internal HRM is used, no attempt on connecting to BT HRM.
|
* Off - Internal HRM is used, no attempt on connecting to BT HRM.
|
||||||
|
@ -57,3 +61,7 @@ This replaces `Bangle.setHRMPower` with its own implementation.
|
||||||
## Creator
|
## Creator
|
||||||
|
|
||||||
Gordon Williams
|
Gordon Williams
|
||||||
|
|
||||||
|
## Contributer
|
||||||
|
|
||||||
|
[halemmerich](https://github.com/halemmerich)
|
||||||
|
|
|
@ -57,7 +57,7 @@ var layout = new Layout( {
|
||||||
{ type:undefined, height:8 } //dummy to protect debug output
|
{ type:undefined, height:8 } //dummy to protect debug output
|
||||||
]
|
]
|
||||||
}, {
|
}, {
|
||||||
lazy:true
|
lazy:false
|
||||||
});
|
});
|
||||||
|
|
||||||
var int,agg,bt;
|
var int,agg,bt;
|
||||||
|
@ -106,8 +106,7 @@ function draw(){
|
||||||
layout.btContact.label = "--";
|
layout.btContact.label = "--";
|
||||||
layout.btEnergy.label = "--";
|
layout.btEnergy.label = "--";
|
||||||
}
|
}
|
||||||
|
layout.clear();
|
||||||
layout.update();
|
|
||||||
layout.render();
|
layout.render();
|
||||||
let first = true;
|
let first = true;
|
||||||
for (let c of layout.l.c){
|
for (let c of layout.l.c){
|
||||||
|
@ -122,26 +121,29 @@ function draw(){
|
||||||
|
|
||||||
|
|
||||||
// This can get called for the boot code to show what's happening
|
// This can get called for the boot code to show what's happening
|
||||||
function showStatusInfo(txt) {
|
global.showStatusInfo = function(txt) {
|
||||||
var R = Bangle.appRect;
|
var R = Bangle.appRect;
|
||||||
g.reset().clearRect(R.x,R.y2-8,R.x2,R.y2).setFont("6x8");
|
g.reset().clearRect(R.x,R.y2-8,R.x2,R.y2).setFont("6x8");
|
||||||
txt = g.wrapString(txt, R.w)[0];
|
txt = g.wrapString(txt, R.w)[0];
|
||||||
g.setFontAlign(0,1).drawString(txt, (R.x+R.x2)/2, R.y2);
|
g.setFontAlign(0,1).drawString(txt, (R.x+R.x2)/2, R.y2);
|
||||||
}
|
};
|
||||||
|
|
||||||
function onBtHrm(e) {
|
function onBtHrm(e) {
|
||||||
bt = e;
|
bt = e;
|
||||||
bt.time = Date.now();
|
bt.time = Date.now();
|
||||||
|
draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onInt(e) {
|
function onInt(e) {
|
||||||
int = e;
|
int = e;
|
||||||
int.time = Date.now();
|
int.time = Date.now();
|
||||||
|
draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onAgg(e) {
|
function onAgg(e) {
|
||||||
agg = e;
|
agg = e;
|
||||||
agg.time = Date.now();
|
agg.time = Date.now();
|
||||||
|
draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
var settings = require('Storage').readJSON("bthrm.json", true) || {};
|
var settings = require('Storage').readJSON("bthrm.json", true) || {};
|
||||||
|
@ -162,7 +164,6 @@ Bangle.drawWidgets();
|
||||||
if (Bangle.setBTHRMPower){
|
if (Bangle.setBTHRMPower){
|
||||||
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
||||||
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2);
|
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2);
|
||||||
setInterval(draw, 1000);
|
|
||||||
} else {
|
} else {
|
||||||
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
||||||
g.drawString("BTHRM disabled",g.getWidth()/2,g.getHeight()/2);
|
g.drawString("BTHRM disabled",g.getWidth()/2,g.getHeight()/2);
|
||||||
|
|
|
@ -15,8 +15,7 @@
|
||||||
"custom_fallbackTimeout": 10,
|
"custom_fallbackTimeout": 10,
|
||||||
"gracePeriodNotification": 0,
|
"gracePeriodNotification": 0,
|
||||||
"gracePeriodConnect": 0,
|
"gracePeriodConnect": 0,
|
||||||
"gracePeriodService": 0,
|
|
||||||
"gracePeriodRequest": 0,
|
"gracePeriodRequest": 0,
|
||||||
"bonding": false,
|
"bonding": false,
|
||||||
"active": true
|
"active": false
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
exports.enable = () => {
|
exports.enable = () => {
|
||||||
var settings = Object.assign(
|
let settings = Object.assign(
|
||||||
require('Storage').readJSON("bthrm.default.json", true) || {},
|
require('Storage').readJSON("bthrm.default.json", true) || {},
|
||||||
require('Storage').readJSON("bthrm.json", true) || {}
|
require('Storage').readJSON("bthrm.json", true) || {}
|
||||||
);
|
);
|
||||||
|
|
||||||
var log = function(text, param){
|
let log = function(text, param){
|
||||||
if (global.showStatusInfo)
|
if (global.showStatusInfo)
|
||||||
global.showStatusInfo(text);
|
global.showStatusInfo(text);
|
||||||
if (settings.debuglog){
|
if (settings.debuglog){
|
||||||
var logline = new Date().toISOString() + " - " + text;
|
let logline = new Date().toISOString() + " - " + text;
|
||||||
if (param) logline += ": " + JSON.stringify(param);
|
if (param) logline += ": " + JSON.stringify(param);
|
||||||
print(logline);
|
print(logline);
|
||||||
}
|
}
|
||||||
|
@ -16,60 +16,33 @@ exports.enable = () => {
|
||||||
|
|
||||||
log("Settings: ", settings);
|
log("Settings: ", settings);
|
||||||
|
|
||||||
if (settings.enabled){
|
//this is for compatibility with 0.18 and older
|
||||||
|
let oldCache = require('Storage').readJSON("bthrm.cache.json", true);
|
||||||
|
if(oldCache){
|
||||||
|
settings.cache = oldCache;
|
||||||
|
require('Storage').writeJSON("bthrm.json", settings);
|
||||||
|
require('Storage').erase("bthrm.cache.json");
|
||||||
|
}
|
||||||
|
|
||||||
var clearCache = function() {
|
if (settings.enabled && settings.cache){
|
||||||
return require('Storage').erase("bthrm.cache.json");
|
|
||||||
};
|
|
||||||
|
|
||||||
var getCache = function() {
|
log("Start");
|
||||||
var cache = require('Storage').readJSON("bthrm.cache.json", true) || {};
|
|
||||||
if (settings.btid && settings.btid === cache.id) return cache;
|
|
||||||
clearCache();
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
|
|
||||||
var addNotificationHandler = function(characteristic) {
|
let addNotificationHandler = function(characteristic) {
|
||||||
log("Setting notification handler"/*supportedCharacteristics[characteristic.uuid].handler*/);
|
log("Setting notification handler"/*supportedCharacteristics[characteristic.uuid].handler*/);
|
||||||
characteristic.on('characteristicvaluechanged', (ev) => supportedCharacteristics[characteristic.uuid].handler(ev.target.value));
|
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) {
|
let characteristicsFromCache = function(device) {
|
||||||
log("Cache characteristics");
|
let service = { device : device }; // fake a BluetoothRemoteGATTService
|
||||||
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");
|
log("Read cached characteristics");
|
||||||
var cache = getCache();
|
let cache = settings.cache;
|
||||||
if (!cache.characteristics) return [];
|
if (!cache.characteristics) return [];
|
||||||
var restored = [];
|
let restored = [];
|
||||||
for (var c in cache.characteristics){
|
for (let c in cache.characteristics){
|
||||||
var cached = cache.characteristics[c];
|
let cached = cache.characteristics[c];
|
||||||
var r = new BluetoothRemoteGATTCharacteristic();
|
let r = new BluetoothRemoteGATTCharacteristic();
|
||||||
log("Restoring characteristic ", cached);
|
log("Restoring characteristic ", cached);
|
||||||
r.handle_value = cached.handle;
|
r.handle_value = cached.handle;
|
||||||
r.uuid = cached.uuid;
|
r.uuid = cached.uuid;
|
||||||
|
@ -84,26 +57,14 @@ exports.enable = () => {
|
||||||
return restored;
|
return restored;
|
||||||
};
|
};
|
||||||
|
|
||||||
log("Start");
|
let supportedCharacteristics = {
|
||||||
|
|
||||||
var lastReceivedData={
|
|
||||||
};
|
|
||||||
|
|
||||||
var supportedServices = [
|
|
||||||
"0x180d", // Heart Rate
|
|
||||||
"0x180f", // Battery
|
|
||||||
];
|
|
||||||
|
|
||||||
var bpmTimeout;
|
|
||||||
|
|
||||||
var supportedCharacteristics = {
|
|
||||||
"0x2a37": {
|
"0x2a37": {
|
||||||
//Heart rate measurement
|
//Heart rate measurement
|
||||||
active: false,
|
active: false,
|
||||||
handler: function (dv){
|
handler: function (dv){
|
||||||
var flags = dv.getUint8(0);
|
let flags = dv.getUint8(0);
|
||||||
|
|
||||||
var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit
|
let bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit
|
||||||
supportedCharacteristics["0x2a37"].active = bpm > 0;
|
supportedCharacteristics["0x2a37"].active = bpm > 0;
|
||||||
log("BTHRM BPM " + supportedCharacteristics["0x2a37"].active);
|
log("BTHRM BPM " + supportedCharacteristics["0x2a37"].active);
|
||||||
switchFallback();
|
switchFallback();
|
||||||
|
@ -114,42 +75,42 @@ exports.enable = () => {
|
||||||
startFallback();
|
startFallback();
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
var sensorContact;
|
let sensorContact;
|
||||||
|
|
||||||
if (flags & 2){
|
if (flags & 2){
|
||||||
sensorContact = !!(flags & 4);
|
sensorContact = !!(flags & 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
var idx = 2 + (flags&1);
|
let idx = 2 + (flags&1);
|
||||||
|
|
||||||
var energyExpended;
|
let energyExpended;
|
||||||
if (flags & 8){
|
if (flags & 8){
|
||||||
energyExpended = dv.getUint16(idx,1);
|
energyExpended = dv.getUint16(idx,1);
|
||||||
idx += 2;
|
idx += 2;
|
||||||
}
|
}
|
||||||
var interval;
|
let interval;
|
||||||
if (flags & 16) {
|
if (flags & 16) {
|
||||||
interval = [];
|
interval = [];
|
||||||
var maxIntervalBytes = (dv.byteLength - idx);
|
let maxIntervalBytes = (dv.byteLength - idx);
|
||||||
log("Found " + (maxIntervalBytes / 2) + " rr data fields");
|
log("Found " + (maxIntervalBytes / 2) + " rr data fields");
|
||||||
for(var i = 0 ; i < maxIntervalBytes / 2; i++){
|
for(let i = 0 ; i < maxIntervalBytes / 2; i++){
|
||||||
interval[i] = dv.getUint16(idx,1); // in milliseconds
|
interval[i] = dv.getUint16(idx,1); // in milliseconds
|
||||||
idx += 2;
|
idx += 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var location;
|
let location;
|
||||||
if (lastReceivedData && lastReceivedData["0x180d"] && lastReceivedData["0x180d"]["0x2a38"]){
|
if (lastReceivedData && lastReceivedData["0x180d"] && lastReceivedData["0x180d"]["0x2a38"]){
|
||||||
location = lastReceivedData["0x180d"]["0x2a38"];
|
location = lastReceivedData["0x180d"]["0x2a38"];
|
||||||
}
|
}
|
||||||
|
|
||||||
var battery;
|
let battery;
|
||||||
if (lastReceivedData && lastReceivedData["0x180f"] && lastReceivedData["0x180f"]["0x2a19"]){
|
if (lastReceivedData && lastReceivedData["0x180f"] && lastReceivedData["0x180f"]["0x2a19"]){
|
||||||
battery = lastReceivedData["0x180f"]["0x2a19"];
|
battery = lastReceivedData["0x180f"]["0x2a19"];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.replace && bpm > 0){
|
if (settings.replace && bpm > 0){
|
||||||
var repEvent = {
|
let repEvent = {
|
||||||
bpm: bpm,
|
bpm: bpm,
|
||||||
confidence: (sensorContact || sensorContact === undefined)? 100 : 0,
|
confidence: (sensorContact || sensorContact === undefined)? 100 : 0,
|
||||||
src: "bthrm"
|
src: "bthrm"
|
||||||
|
@ -159,7 +120,7 @@ exports.enable = () => {
|
||||||
Bangle.emit("HRM_R", repEvent);
|
Bangle.emit("HRM_R", repEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
var newEvent = {
|
let newEvent = {
|
||||||
bpm: bpm
|
bpm: bpm
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -177,6 +138,7 @@ exports.enable = () => {
|
||||||
//Body sensor location
|
//Body sensor location
|
||||||
handler: function(dv){
|
handler: function(dv){
|
||||||
if (!lastReceivedData["0x180d"]) lastReceivedData["0x180d"] = {};
|
if (!lastReceivedData["0x180d"]) lastReceivedData["0x180d"] = {};
|
||||||
|
log("Got location", dv);
|
||||||
lastReceivedData["0x180d"]["0x2a38"] = parseInt(dv.buffer, 10);
|
lastReceivedData["0x180d"]["0x2a38"] = parseInt(dv.buffer, 10);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -184,26 +146,27 @@ exports.enable = () => {
|
||||||
//Battery
|
//Battery
|
||||||
handler: function (dv){
|
handler: function (dv){
|
||||||
if (!lastReceivedData["0x180f"]) lastReceivedData["0x180f"] = {};
|
if (!lastReceivedData["0x180f"]) lastReceivedData["0x180f"] = {};
|
||||||
|
log("Got battery", dv);
|
||||||
lastReceivedData["0x180f"]["0x2a19"] = dv.getUint8(0);
|
lastReceivedData["0x180f"]["0x2a19"] = dv.getUint8(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var device;
|
let lastReceivedData={
|
||||||
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) {
|
let bpmTimeout;
|
||||||
|
|
||||||
|
let device;
|
||||||
|
let gatt;
|
||||||
|
let characteristics = [];
|
||||||
|
let blockInit = false;
|
||||||
|
let currentRetryTimeout;
|
||||||
|
let initialRetryTime = 40;
|
||||||
|
let maxRetryTime = 60000;
|
||||||
|
let retryTime = initialRetryTime;
|
||||||
|
|
||||||
|
let waitingPromise = function(timeout) {
|
||||||
return new Promise(function(resolve){
|
return new Promise(function(resolve){
|
||||||
log("Start waiting for " + timeout);
|
log("Start waiting for " + timeout);
|
||||||
setTimeout(()=>{
|
setTimeout(()=>{
|
||||||
|
@ -240,7 +203,7 @@ exports.enable = () => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var clearRetryTimeout = function(resetTime) {
|
let clearRetryTimeout = function(resetTime) {
|
||||||
if (currentRetryTimeout){
|
if (currentRetryTimeout){
|
||||||
log("Clearing timeout " + currentRetryTimeout);
|
log("Clearing timeout " + currentRetryTimeout);
|
||||||
clearTimeout(currentRetryTimeout);
|
clearTimeout(currentRetryTimeout);
|
||||||
|
@ -252,12 +215,12 @@ exports.enable = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var retry = function() {
|
let retry = function() {
|
||||||
log("Retry");
|
log("Retry");
|
||||||
|
|
||||||
if (!currentRetryTimeout && !powerdownRequested){
|
if (!currentRetryTimeout && !powerdownRequested){
|
||||||
|
|
||||||
var clampedTime = retryTime < 100 ? 100 : retryTime;
|
let clampedTime = retryTime < 100 ? 100 : retryTime;
|
||||||
|
|
||||||
log("Set timeout for retry as " + clampedTime);
|
log("Set timeout for retry as " + clampedTime);
|
||||||
clearRetryTimeout();
|
clearRetryTimeout();
|
||||||
|
@ -276,20 +239,21 @@ exports.enable = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var buzzing = false;
|
let initialDisconnects = true;
|
||||||
var onDisconnect = function(reason) {
|
let buzzing = false;
|
||||||
|
let onDisconnect = function(reason) {
|
||||||
log("Disconnect: " + reason);
|
log("Disconnect: " + reason);
|
||||||
log("GATT", gatt);
|
log("GATT", gatt);
|
||||||
log("Characteristics", characteristics);
|
log("Characteristics", characteristics);
|
||||||
|
|
||||||
var retryTimeResetNeeded = true;
|
let retryTimeResetNeeded = true;
|
||||||
retryTimeResetNeeded &= reason != "Connection Timeout";
|
retryTimeResetNeeded &= reason != "Connection Timeout";
|
||||||
retryTimeResetNeeded &= reason != "No device found matching filters";
|
retryTimeResetNeeded &= reason != "No device found matching filters";
|
||||||
clearRetryTimeout(retryTimeResetNeeded);
|
clearRetryTimeout(retryTimeResetNeeded);
|
||||||
supportedCharacteristics["0x2a37"].active = false;
|
supportedCharacteristics["0x2a37"].active = false;
|
||||||
if (!powerdownRequested) startFallback();
|
if (!powerdownRequested) startFallback();
|
||||||
blockInit = false;
|
blockInit = false;
|
||||||
if (settings.warnDisconnect && !buzzing){
|
if (settings.warnDisconnect && !buzzing && !initialDisconnects){
|
||||||
buzzing = true;
|
buzzing = true;
|
||||||
Bangle.buzz(500,0.3).then(()=>waitingPromise(4500)).then(()=>{buzzing = false;});
|
Bangle.buzz(500,0.3).then(()=>waitingPromise(4500)).then(()=>{buzzing = false;});
|
||||||
}
|
}
|
||||||
|
@ -298,9 +262,9 @@ exports.enable = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var createCharacteristicPromise = function(newCharacteristic) {
|
let createCharacteristicPromise = function(newCharacteristic) {
|
||||||
log("Create characteristic promise", newCharacteristic);
|
log("Create characteristic promise", newCharacteristic);
|
||||||
var result = Promise.resolve();
|
let result = Promise.resolve();
|
||||||
// For values that can be read, go ahead and read them, even if we might be notified in the future
|
// 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
|
// Allows for getting initial state of infrequently updating characteristics, like battery
|
||||||
if (newCharacteristic.readValue){
|
if (newCharacteristic.readValue){
|
||||||
|
@ -316,58 +280,29 @@ exports.enable = () => {
|
||||||
if (newCharacteristic.properties.notify){
|
if (newCharacteristic.properties.notify){
|
||||||
result = result.then(()=>{
|
result = result.then(()=>{
|
||||||
log("Starting notifications", newCharacteristic);
|
log("Starting notifications", newCharacteristic);
|
||||||
var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic));
|
let startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic));
|
||||||
|
|
||||||
|
if (settings.gracePeriodNotification){
|
||||||
log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications");
|
log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications");
|
||||||
startPromise = startPromise.then(()=>{
|
startPromise = startPromise.then(()=>{
|
||||||
log("Wait after connect");
|
log("Wait after connect");
|
||||||
return waitingPromise(settings.gracePeriodNotification);
|
return waitingPromise(settings.gracePeriodNotification);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
return startPromise;
|
return startPromise;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return result.then(()=>log("Handled characteristic", newCharacteristic));
|
return result.then(()=>log("Handled characteristic", newCharacteristic));
|
||||||
};
|
};
|
||||||
|
|
||||||
var attachCharacteristicPromise = function(promise, characteristic) {
|
let attachCharacteristicPromise = function(promise, characteristic) {
|
||||||
return promise.then(()=>{
|
return promise.then(()=>{
|
||||||
log("Handling characteristic:", characteristic);
|
log("Handling characteristic:", characteristic);
|
||||||
return createCharacteristicPromise(characteristic);
|
return createCharacteristicPromise(characteristic);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var createCharacteristicsPromise = function(newCharacteristics) {
|
let initBt = function () {
|
||||||
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);
|
log("initBt with blockInit: " + blockInit);
|
||||||
if (blockInit && !powerdownRequested){
|
if (blockInit && !powerdownRequested){
|
||||||
retry();
|
retry();
|
||||||
|
@ -376,8 +311,8 @@ exports.enable = () => {
|
||||||
|
|
||||||
blockInit = true;
|
blockInit = true;
|
||||||
|
|
||||||
var promise;
|
let promise;
|
||||||
var filters;
|
let filters;
|
||||||
|
|
||||||
if (!device){
|
if (!device){
|
||||||
if (settings.btid){
|
if (settings.btid){
|
||||||
|
@ -397,6 +332,10 @@ exports.enable = () => {
|
||||||
|
|
||||||
if (settings.gracePeriodRequest){
|
if (settings.gracePeriodRequest){
|
||||||
log("Add " + settings.gracePeriodRequest + "ms grace period after request");
|
log("Add " + settings.gracePeriodRequest + "ms grace period after request");
|
||||||
|
promise = promise.then((d)=>{
|
||||||
|
log("Wait after request");
|
||||||
|
return waitingPromise(settings.gracePeriodRequest).then(()=>Promise.resolve(d));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
promise = promise.then((d)=>{
|
promise = promise.then((d)=>{
|
||||||
|
@ -404,108 +343,50 @@ exports.enable = () => {
|
||||||
d.on('gattserverdisconnected', onDisconnect);
|
d.on('gattserverdisconnected', onDisconnect);
|
||||||
device = d;
|
device = d;
|
||||||
});
|
});
|
||||||
|
|
||||||
promise = promise.then(()=>{
|
|
||||||
log("Wait after request");
|
|
||||||
return waitingPromise(settings.gracePeriodRequest);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
promise = Promise.resolve();
|
promise = Promise.resolve();
|
||||||
log("Reuse device", device);
|
log("Reuse device", device);
|
||||||
}
|
}
|
||||||
|
|
||||||
promise = promise.then(()=>{
|
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;
|
gatt = device.gatt;
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve(gatt);
|
return Promise.resolve(gatt);
|
||||||
});
|
});
|
||||||
|
|
||||||
promise = promise.then((gatt)=>{
|
promise = promise.then((gatt)=>{
|
||||||
if (!gatt.connected){
|
if (!gatt.connected){
|
||||||
log("Connecting...");
|
log("Connecting...");
|
||||||
var connectPromise = gatt.connect(connectSettings).then(function() {
|
let connectPromise = gatt.connect().then(function() {
|
||||||
log("Connected.");
|
log("Connected.");
|
||||||
});
|
});
|
||||||
|
if (settings.gracePeriodConnect){
|
||||||
log("Add " + settings.gracePeriodConnect + "ms grace period after connecting");
|
log("Add " + settings.gracePeriodConnect + "ms grace period after connecting");
|
||||||
connectPromise = connectPromise.then(()=>{
|
connectPromise = connectPromise.then(()=>{
|
||||||
log("Wait after connect");
|
log("Wait after connect");
|
||||||
return waitingPromise(settings.gracePeriodConnect);
|
return waitingPromise(settings.gracePeriodConnect);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
return connectPromise;
|
return connectPromise;
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (settings.bonding){
|
|
||||||
promise = promise.then(()=>{
|
promise = promise.then(()=>{
|
||||||
log(JSON.stringify(gatt.getSecurityStatus()));
|
if (!characteristics || characteristics.length == 0){
|
||||||
if (gatt.getSecurityStatus()['bonded']) {
|
|
||||||
log("Already bonded");
|
|
||||||
return Promise.resolve();
|
|
||||||
} else {
|
|
||||||
log("Start bonding");
|
|
||||||
return gatt.startBonding()
|
|
||||||
.then(() => log("Security status" + gatt.getSecurityStatus()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
promise = promise.then(()=>{
|
|
||||||
if (!characteristics || characteristics.length === 0){
|
|
||||||
characteristics = characteristicsFromCache(device);
|
characteristics = characteristicsFromCache(device);
|
||||||
}
|
}
|
||||||
});
|
let characteristicsPromise = Promise.resolve();
|
||||||
|
for (let characteristic of characteristics){
|
||||||
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);
|
characteristicsPromise = attachCharacteristicPromise(characteristicsPromise, characteristic, true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return characteristicsPromise;
|
return characteristicsPromise;
|
||||||
});
|
});
|
||||||
|
|
||||||
return promise.then(()=>{
|
return promise.then(()=>{
|
||||||
log("Connection established, waiting for notifications");
|
log("Connection established, waiting for notifications");
|
||||||
characteristicsToCache(characteristics);
|
initialDisconnects = false;
|
||||||
clearRetryTimeout(true);
|
clearRetryTimeout(true);
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
characteristics = [];
|
characteristics = [];
|
||||||
|
@ -514,7 +395,7 @@ exports.enable = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var powerdownRequested = false;
|
let powerdownRequested = false;
|
||||||
|
|
||||||
Bangle.setBTHRMPower = function(isOn, app) {
|
Bangle.setBTHRMPower = function(isOn, app) {
|
||||||
// Do app power handling
|
// Do app power handling
|
||||||
|
@ -526,6 +407,7 @@ exports.enable = () => {
|
||||||
isOn = Bangle._PWR.BTHRM.length;
|
isOn = Bangle._PWR.BTHRM.length;
|
||||||
// so now we know if we're really on
|
// so now we know if we're really on
|
||||||
if (isOn) {
|
if (isOn) {
|
||||||
|
initialDisconnects = true;
|
||||||
powerdownRequested = false;
|
powerdownRequested = false;
|
||||||
switchFallback();
|
switchFallback();
|
||||||
if (!Bangle.isBTHRMConnected()) initBt();
|
if (!Bangle.isBTHRMConnected()) initBt();
|
||||||
|
@ -598,17 +480,18 @@ exports.enable = () => {
|
||||||
Bangle.setBTHRMPower(0);
|
Bangle.setBTHRMPower(0);
|
||||||
if (!isOn) stopFallback();
|
if (!isOn) stopFallback();
|
||||||
}
|
}
|
||||||
|
return Bangle.isBTHRMOn() || Bangle.isHRMOn();
|
||||||
}
|
}
|
||||||
if ((settings.enabled && !settings.replace) || !settings.enabled){
|
if ((settings.enabled && !settings.replace) || !settings.enabled){
|
||||||
Bangle.origSetHRMPower(isOn, app);
|
return Bangle.origSetHRMPower(isOn, app);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var fallbackActive = false;
|
let fallbackActive = false;
|
||||||
var inSwitch = false;
|
let inSwitch = false;
|
||||||
|
|
||||||
var stopFallback = function(){
|
let stopFallback = function(){
|
||||||
if (fallbackActive){
|
if (fallbackActive){
|
||||||
Bangle.origSetHRMPower(0, "bthrm_fallback");
|
Bangle.origSetHRMPower(0, "bthrm_fallback");
|
||||||
fallbackActive = false;
|
fallbackActive = false;
|
||||||
|
@ -616,7 +499,7 @@ exports.enable = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var startFallback = function(){
|
let startFallback = function(){
|
||||||
if (!fallbackActive && settings.allowFallback) {
|
if (!fallbackActive && settings.allowFallback) {
|
||||||
fallbackActive = true;
|
fallbackActive = true;
|
||||||
Bangle.origSetHRMPower(1, "bthrm_fallback");
|
Bangle.origSetHRMPower(1, "bthrm_fallback");
|
||||||
|
@ -624,7 +507,7 @@ exports.enable = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var switchFallback = function() {
|
let switchFallback = function() {
|
||||||
log("Check falling back to HRM");
|
log("Check falling back to HRM");
|
||||||
if (!inSwitch){
|
if (!inSwitch){
|
||||||
inSwitch = true;
|
inSwitch = true;
|
||||||
|
@ -640,8 +523,8 @@ exports.enable = () => {
|
||||||
if (settings.replace){
|
if (settings.replace){
|
||||||
log("Replace HRM event");
|
log("Replace HRM event");
|
||||||
if (Bangle._PWR && Bangle._PWR.HRM){
|
if (Bangle._PWR && Bangle._PWR.HRM){
|
||||||
for (var i = 0; i < Bangle._PWR.HRM.length; i++){
|
for (let i = 0; i < Bangle._PWR.HRM.length; i++){
|
||||||
var app = Bangle._PWR.HRM[i];
|
let app = Bangle._PWR.HRM[i];
|
||||||
log("Moving app " + app);
|
log("Moving app " + app);
|
||||||
Bangle.origSetHRMPower(0, app);
|
Bangle.origSetHRMPower(0, app);
|
||||||
Bangle.setBTHRMPower(1, app);
|
Bangle.setBTHRMPower(1, app);
|
||||||
|
|
|
@ -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.18",
|
"version": "0.19",
|
||||||
"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",
|
||||||
"screenshots": [{"url":"screen.png"}],
|
"screenshots": [{"url":"screen.png"}],
|
||||||
|
|
|
@ -26,11 +26,11 @@
|
||||||
},
|
},
|
||||||
start : () => {
|
start : () => {
|
||||||
Bangle.on('BTHRM', onHRM);
|
Bangle.on('BTHRM', onHRM);
|
||||||
if (Bangle.setBTRHMPower) Bangle.setBTHRMPower(1,"recorder");
|
if (Bangle.setBTHRMPower) Bangle.setBTHRMPower(1,"recorder");
|
||||||
},
|
},
|
||||||
stop : () => {
|
stop : () => {
|
||||||
Bangle.removeListener('BTHRM', onHRM);
|
Bangle.removeListener('BTHRM', onHRM);
|
||||||
if (Bangle.setBTRHMPower) Bangle.setBTHRMPower(0,"recorder");
|
if (Bangle.setBTHRMPower) Bangle.setBTHRMPower(0,"recorder");
|
||||||
},
|
},
|
||||||
draw : (x,y) => g.setColor((Bangle.isBTHRMActive && Bangle.isBTHRMActive())?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
|
draw : (x,y) => g.setColor((Bangle.isBTHRMActive && Bangle.isBTHRMActive())?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
(function(back) {
|
(function(back) {
|
||||||
function writeSettings(key, value) {
|
function writeSettings(key, value) {
|
||||||
var s = require('Storage').readJSON(FILE, true) || {};
|
let s = require('Storage').readJSON(FILE, true) || {};
|
||||||
s[key] = value;
|
s[key] = value;
|
||||||
require('Storage').writeJSON(FILE, s);
|
require('Storage').writeJSON(FILE, s);
|
||||||
readSettings();
|
readSettings();
|
||||||
|
@ -13,10 +13,14 @@
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var FILE="bthrm.json";
|
let FILE="bthrm.json";
|
||||||
var settings;
|
let settings;
|
||||||
readSettings();
|
readSettings();
|
||||||
|
|
||||||
|
let log = ()=>{};
|
||||||
|
if (settings.debuglog)
|
||||||
|
log = print;
|
||||||
|
|
||||||
function applyCustomSettings(){
|
function applyCustomSettings(){
|
||||||
writeSettings("enabled",true);
|
writeSettings("enabled",true);
|
||||||
writeSettings("replace",settings.custom_replace);
|
writeSettings("replace",settings.custom_replace);
|
||||||
|
@ -26,7 +30,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildMainMenu(){
|
function buildMainMenu(){
|
||||||
var mainmenu = {
|
let mainmenu = {
|
||||||
'': { 'title': 'Bluetooth HRM' },
|
'': { 'title': 'Bluetooth HRM' },
|
||||||
'< Back': back,
|
'< Back': back,
|
||||||
'Mode': {
|
'Mode': {
|
||||||
|
@ -63,12 +67,13 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
if (settings.btname || settings.btid){
|
if (settings.btname || settings.btid){
|
||||||
var name = "Clear " + (settings.btname || settings.btid);
|
let name = "Clear " + (settings.btname || settings.btid);
|
||||||
mainmenu[name] = function() {
|
mainmenu[name] = function() {
|
||||||
E.showPrompt("Clear current device?").then((r)=>{
|
E.showPrompt("Clear current device?").then((r)=>{
|
||||||
if (r) {
|
if (r) {
|
||||||
writeSettings("btname",undefined);
|
writeSettings("btname",undefined);
|
||||||
writeSettings("btid",undefined);
|
writeSettings("btid",undefined);
|
||||||
|
writeSettings("cache", undefined);
|
||||||
}
|
}
|
||||||
E.showMenu(buildMainMenu());
|
E.showMenu(buildMainMenu());
|
||||||
});
|
});
|
||||||
|
@ -81,7 +86,7 @@
|
||||||
return mainmenu;
|
return mainmenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
var submenu_debug = {
|
let submenu_debug = {
|
||||||
'' : { title: "Debug"},
|
'' : { title: "Debug"},
|
||||||
'< Back': function() { E.showMenu(buildMainMenu()); },
|
'< Back': function() { E.showMenu(buildMainMenu()); },
|
||||||
'Alert on disconnect': {
|
'Alert on disconnect': {
|
||||||
|
@ -111,11 +116,135 @@
|
||||||
'Grace periods': function() { E.showMenu(submenu_grace); }
|
'Grace periods': function() { E.showMenu(submenu_grace); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let supportedServices = [
|
||||||
|
"0x180d", // Heart Rate
|
||||||
|
"0x180f", // Battery
|
||||||
|
];
|
||||||
|
|
||||||
|
let supportedCharacteristics = [
|
||||||
|
"0x2a37", // Heart Rate
|
||||||
|
"0x2a38", // Body sensor location
|
||||||
|
"0x2a19", // Battery
|
||||||
|
];
|
||||||
|
|
||||||
|
var characteristicsToCache = function(characteristics) {
|
||||||
|
log("Cache characteristics");
|
||||||
|
let cache = {};
|
||||||
|
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.uuid);
|
||||||
|
cache.characteristics[c.uuid] = {
|
||||||
|
"handle": c.handle_value,
|
||||||
|
"uuid": c.uuid,
|
||||||
|
"notify": c.properties.notify,
|
||||||
|
"read": c.properties.read
|
||||||
|
};
|
||||||
|
}
|
||||||
|
writeSettings("cache", cache);
|
||||||
|
};
|
||||||
|
|
||||||
|
let createCharacteristicPromise = function(newCharacteristic) {
|
||||||
|
log("Create characteristic promise", newCharacteristic.uuid);
|
||||||
|
return Promise.resolve().then(()=>log("Handled characteristic", newCharacteristic.uuid));
|
||||||
|
};
|
||||||
|
|
||||||
|
let attachCharacteristicPromise = function(promise, characteristic) {
|
||||||
|
return promise.then(()=>{
|
||||||
|
log("Handling characteristic:", characteristic.uuid);
|
||||||
|
return createCharacteristicPromise(characteristic);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let characteristics;
|
||||||
|
|
||||||
|
let createCharacteristicsPromise = function(newCharacteristics) {
|
||||||
|
log("Create characteristics promise ", newCharacteristics.length);
|
||||||
|
let result = Promise.resolve();
|
||||||
|
for (let c of newCharacteristics){
|
||||||
|
if (!supportedCharacteristics.includes(c.uuid)) continue;
|
||||||
|
log("Supporting characteristic", c.uuid);
|
||||||
|
characteristics.push(c);
|
||||||
|
|
||||||
|
result = attachCharacteristicPromise(result, c);
|
||||||
|
}
|
||||||
|
return result.then(()=>log("Handled characteristics"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let createServicePromise = function(service) {
|
||||||
|
log("Create service promise", service.uuid);
|
||||||
|
let 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));
|
||||||
|
};
|
||||||
|
|
||||||
|
let attachServicePromise = function(promise, service) {
|
||||||
|
return promise.then(()=>createServicePromise(service));
|
||||||
|
};
|
||||||
|
|
||||||
|
function cacheDevice(deviceId){
|
||||||
|
let promise;
|
||||||
|
let filters;
|
||||||
|
let gatt;
|
||||||
|
characteristics = [];
|
||||||
|
filters = [{ id: deviceId }];
|
||||||
|
|
||||||
|
log("Requesting device with filters", filters);
|
||||||
|
promise = NRF.requestDevice({ filters: filters, active: settings.active });
|
||||||
|
|
||||||
|
promise = promise.then((d)=>{
|
||||||
|
log("Got device", d);
|
||||||
|
gatt = d.gatt;
|
||||||
|
log("Connecting...");
|
||||||
|
return gatt.connect().then(function() {
|
||||||
|
log("Connected.");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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(() => log("Security status after bonding" + gatt.getSecurityStatus()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
promise = promise.then(()=>{
|
||||||
|
log("Getting services");
|
||||||
|
return gatt.getPrimaryServices();
|
||||||
|
});
|
||||||
|
|
||||||
|
promise = promise.then((services)=>{
|
||||||
|
log("Got services", services.length);
|
||||||
|
let result = Promise.resolve();
|
||||||
|
for (let service of services){
|
||||||
|
if (!(supportedServices.includes(service.uuid))) continue;
|
||||||
|
log("Supporting service", service.uuid);
|
||||||
|
result = attachServicePromise(result, service);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise.then(()=>{
|
||||||
|
log("Connection established, saving cache");
|
||||||
|
characteristicsToCache(characteristics);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function createMenuFromScan(){
|
function createMenuFromScan(){
|
||||||
E.showMenu();
|
E.showMenu();
|
||||||
E.showMessage("Scanning for 4 seconds");
|
E.showMessage("Scanning for 4 seconds");
|
||||||
|
|
||||||
var submenu_scan = {
|
let submenu_scan = {
|
||||||
'< Back': function() { E.showMenu(buildMainMenu()); }
|
'< Back': function() { E.showMenu(buildMainMenu()); }
|
||||||
};
|
};
|
||||||
NRF.findDevices(function(devices) {
|
NRF.findDevices(function(devices) {
|
||||||
|
@ -126,27 +255,50 @@
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
devices.forEach((d) => {
|
devices.forEach((d) => {
|
||||||
print("Found device", d);
|
log("Found device", d);
|
||||||
var shown = (d.name || d.id.substr(0, 17));
|
let shown = (d.name || d.id.substr(0, 17));
|
||||||
submenu_scan[shown] = function () {
|
submenu_scan[shown] = function () {
|
||||||
E.showPrompt("Set " + shown + "?").then((r) => {
|
E.showPrompt("Connect to\n" + shown + "?", {title: "Pairing"}).then((r) => {
|
||||||
if (r) {
|
if (r) {
|
||||||
|
E.showMessage("Connecting...", {img:require("Storage").read("bthrm.img")});
|
||||||
|
let count = 0;
|
||||||
|
const successHandler = ()=>{
|
||||||
|
E.showPrompt("Success!", {
|
||||||
|
img:require("Storage").read("bthrm.img"),
|
||||||
|
buttons: { "OK":true }
|
||||||
|
}).then(()=>{
|
||||||
writeSettings("btid", d.id);
|
writeSettings("btid", d.id);
|
||||||
// Store the name for displaying later. Will connect by ID
|
// Store the name for displaying later. Will connect by ID
|
||||||
if (d.name) {
|
if (d.name) {
|
||||||
writeSettings("btname", d.name);
|
writeSettings("btname", d.name);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
E.showMenu(buildMainMenu());
|
E.showMenu(buildMainMenu());
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
const errorHandler = (e)=>{
|
||||||
|
count++;
|
||||||
|
log("ERROR", e);
|
||||||
|
if (count <= 10){
|
||||||
|
E.showMessage("Error during caching\nRetry " + count + "/10", e);
|
||||||
|
return cacheDevice(d.id).then(successHandler).catch(errorHandler);
|
||||||
|
} else {
|
||||||
|
E.showAlert("Error during caching", e).then(()=>{
|
||||||
|
E.showMenu(buildMainMenu());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return cacheDevice(d.id).then(successHandler).catch(errorHandler);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
E.showMenu(submenu_scan);
|
E.showMenu(submenu_scan);
|
||||||
}, { timeout: 4000, active: true, filters: [{services: [ "180d" ]}]});
|
}, { timeout: 4000, active: true, filters: [{services: [ "180d" ]}]});
|
||||||
}
|
}
|
||||||
|
|
||||||
var submenu_custom = {
|
let submenu_custom = {
|
||||||
'' : { title: "Custom mode"},
|
'' : { title: "Custom mode"},
|
||||||
'< Back': function() { E.showMenu(buildMainMenu()); },
|
'< Back': function() { E.showMenu(buildMainMenu()); },
|
||||||
'Replace HRM': {
|
'Replace HRM': {
|
||||||
|
@ -183,7 +335,7 @@
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
var submenu_grace = {
|
let submenu_grace = {
|
||||||
'' : { title: "Grace periods"},
|
'' : { title: "Grace periods"},
|
||||||
'< Back': function() { E.showMenu(submenu_debug); },
|
'< Back': function() { E.showMenu(submenu_debug); },
|
||||||
'Request': {
|
'Request': {
|
||||||
|
@ -215,16 +367,6 @@
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
writeSettings("gracePeriodNotification",v);
|
writeSettings("gracePeriodNotification",v);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
'Service': {
|
|
||||||
value: settings.gracePeriodService,
|
|
||||||
min: 0,
|
|
||||||
max: 3000,
|
|
||||||
step: 100,
|
|
||||||
format: v=>v+"ms",
|
|
||||||
onchange: v => {
|
|
||||||
writeSettings("gracePeriodService",v);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
{
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"commonjs": true,
|
|
||||||
"es6": true
|
|
||||||
},
|
|
||||||
"extends": "eslint:recommended",
|
|
||||||
"globals": {
|
|
||||||
"Atomics": "readonly",
|
|
||||||
"SharedArrayBuffer": "readonly"
|
|
||||||
},
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": 2018
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"indent": [
|
|
||||||
"error",
|
|
||||||
2,
|
|
||||||
{ "SwitchCase": 1 }
|
|
||||||
],
|
|
||||||
"linebreak-style": [
|
|
||||||
"error",
|
|
||||||
"windows"
|
|
||||||
],
|
|
||||||
"quotes": [
|
|
||||||
"error",
|
|
||||||
"double"
|
|
||||||
]
|
|
||||||
/*,
|
|
||||||
"semi": [
|
|
||||||
"error",
|
|
||||||
"always"
|
|
||||||
]*/
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,3 +2,4 @@
|
||||||
0.02: Hiding widgets while showing the code
|
0.02: Hiding widgets while showing the code
|
||||||
0.03: Added option to use max brightness when showing code
|
0.03: Added option to use max brightness when showing code
|
||||||
0.04: Minor code improvements
|
0.04: Minor code improvements
|
||||||
|
0.05: Add EAN & UPC codes
|
||||||
|
|
|
@ -82,16 +82,17 @@ function printSquareCode(binary, size) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function printLinearCode(binary) {
|
function printLinearCode(binary) {
|
||||||
|
var padding = 5;
|
||||||
var yFrom = 15;
|
var yFrom = 15;
|
||||||
var yTo = 28;
|
var yTo = 28;
|
||||||
var width = g.getWidth()/binary.length;
|
var width = (g.getWidth()-(2*padding))/binary.length;
|
||||||
for(var b = 0; b < binary.length; b++){
|
for(var b = 0; b < binary.length; b++){
|
||||||
var x = b * width;
|
var x = b * width;
|
||||||
if(binary[b] === "1"){
|
if(binary[b] === "1"){
|
||||||
g.setColor(BLACK).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)});
|
g.setColor(BLACK).fillRect({x:x+padding, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)});
|
||||||
}
|
}
|
||||||
else if(binary[b]){
|
else if(binary[b]){
|
||||||
g.setColor(WHITE).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)});
|
g.setColor(WHITE).fillRect({x:x+padding, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,6 +133,42 @@ function showCode(card) {
|
||||||
printLinearCode(code.encode().data);
|
printLinearCode(code.encode().data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "EAN_8": {
|
||||||
|
g.setFont("Vector:20");
|
||||||
|
g.setFontAlign(0,1).setColor(BLACK);
|
||||||
|
g.drawString(card.value, g.getWidth()/2, g.getHeight());
|
||||||
|
const EAN8 = require("cards.EAN8.js");
|
||||||
|
let code = new EAN8(card.value, {});
|
||||||
|
printLinearCode(code.encode().data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "EAN_13": {
|
||||||
|
g.setFont("Vector:20");
|
||||||
|
g.setFontAlign(0,1).setColor(BLACK);
|
||||||
|
g.drawString(card.value, g.getWidth()/2, g.getHeight());
|
||||||
|
const EAN13 = require("cards.EAN13.js");
|
||||||
|
let code = new EAN13(card.value, {});
|
||||||
|
printLinearCode(code.encode().data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "UPC_A": {
|
||||||
|
g.setFont("Vector:20");
|
||||||
|
g.setFontAlign(0,1).setColor(BLACK);
|
||||||
|
g.drawString(card.value, g.getWidth()/2, g.getHeight());
|
||||||
|
const UPC = require("cards.UPC.js");
|
||||||
|
let code = new UPC.UPC(card.value, {});
|
||||||
|
printLinearCode(code.encode().data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "UPC_E": {
|
||||||
|
g.setFont("Vector:20");
|
||||||
|
g.setFontAlign(0,1).setColor(BLACK);
|
||||||
|
g.drawString(card.value, g.getWidth()/2, g.getHeight());
|
||||||
|
const UPCE = require("cards.UPCE.js");
|
||||||
|
let code = new UPCE(card.value, {});
|
||||||
|
printLinearCode(code.encode().data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
g.clear(true);
|
g.clear(true);
|
||||||
g.setFont("Vector:30");
|
g.setFont("Vector:30");
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
{
|
{
|
||||||
"id": "cards",
|
"id": "cards",
|
||||||
"name": "Cards",
|
"name": "Cards",
|
||||||
"version": "0.04",
|
"version": "0.05",
|
||||||
"description": "Display loyalty cards",
|
"description": "Display loyalty cards",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_card1.png"}, {"url":"screenshot_cards_card2.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}],
|
"screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_card1.png"}, {"url":"screenshot_cards_card2.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}],
|
||||||
"tags": "cards",
|
"tags": "cards",
|
||||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
|
"allow_emulator": true,
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"cards.app.js","url":"app.js"},
|
{"name":"cards.app.js","url":"app.js"},
|
||||||
|
@ -15,6 +16,12 @@
|
||||||
{"name":"cards.qrcode.js","url":"qrcode.js"},
|
{"name":"cards.qrcode.js","url":"qrcode.js"},
|
||||||
{"name":"cards.codabar.js","url":"codabar.js"},
|
{"name":"cards.codabar.js","url":"codabar.js"},
|
||||||
{"name":"cards.code39.js","url":"code39.js"},
|
{"name":"cards.code39.js","url":"code39.js"},
|
||||||
|
{"name":"cards.EAN.js","url":"EAN.js"},
|
||||||
|
{"name":"cards.EAN8.js","url":"EAN8.js"},
|
||||||
|
{"name":"cards.EAN13.js","url":"EAN13.js"},
|
||||||
|
{"name":"cards.UPC.js","url":"UPC.js"},
|
||||||
|
{"name":"cards.UPCE.js","url":"UPCE.js"},
|
||||||
|
{"name":"cards.encode.js","url":"encode.js"},
|
||||||
{"name":"cards.img","url":"app-icon.js","evaluate":true}
|
{"name":"cards.img","url":"app-icon.js","evaluate":true}
|
||||||
],
|
],
|
||||||
"data": [{"name":"cards.settings.json"}]
|
"data": [{"name":"cards.settings.json"}]
|
||||||
|
|
|
@ -3,3 +3,5 @@
|
||||||
0.03: Added threshold
|
0.03: Added threshold
|
||||||
0.04: Added notification
|
0.04: Added notification
|
||||||
0.05: Fixed boot
|
0.05: Fixed boot
|
||||||
|
0.06: Allow tap to silence notification/buzzing
|
||||||
|
0.07: Fix notification-tap silencing and notification length
|
||||||
|
|
|
@ -6,6 +6,8 @@ The first stage of charging Li-ion ends at ~80% capacity when the charge voltage
|
||||||
|
|
||||||
This app has no UI and no configuration. To disable the app, you have to uninstall it.
|
This app has no UI and no configuration. To disable the app, you have to uninstall it.
|
||||||
|
|
||||||
|
Tap the charged notification to prevent buzzing for this charging session.
|
||||||
|
|
||||||
New in v0.03: before the very first buzz, the average value after the peak is written to chargent.json and used as threshold for future charges. This reduces the time spent in the second charge stage.
|
New in v0.03: before the very first buzz, the average value after the peak is written to chargent.json and used as threshold for future charges. This reduces the time spent in the second charge stage.
|
||||||
|
|
||||||
Side notes
|
Side notes
|
||||||
|
|
|
@ -24,7 +24,8 @@
|
||||||
lim = sum / cnt;
|
lim = sum / cnt;
|
||||||
require('Storage').writeJSON('chargent.json', {limit: lim});
|
require('Storage').writeJSON('chargent.json', {limit: lim});
|
||||||
}
|
}
|
||||||
require('notify').show({id: 'chargent', title: 'Fully charged'});
|
const onHide = () => { if(id) id = clearInterval(id) };
|
||||||
|
require('notify').show({id: 'chargent', title: 'Charged', onHide });
|
||||||
// TODO ? customizable
|
// TODO ? customizable
|
||||||
Bangle.buzz(500);
|
Bangle.buzz(500);
|
||||||
setTimeout(() => Bangle.buzz(500), 1000);
|
setTimeout(() => Bangle.buzz(500), 1000);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{ "id": "chargent",
|
{ "id": "chargent",
|
||||||
"name": "Charge Gently",
|
"name": "Charge Gently",
|
||||||
"version": "0.05",
|
"version": "0.07",
|
||||||
"description": "When charging, reminds you to disconnect the watch to prolong battery life.",
|
"description": "When charging, reminds you to disconnect the watch to prolong battery life.",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"type": "bootloader",
|
"type": "bootloader",
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: added settings options to change date format
|
0.02: added settings options to change date format
|
||||||
|
0.03: Remove un-needed font requirement, now outputs transparent image
|
||||||
|
0.04: Fix image after 0.03 regression
|
||||||
|
0.05: Remove duplicated day in calendar when format setting is 'dd MMM'
|
|
@ -1,13 +1,11 @@
|
||||||
(function() {
|
(function() {
|
||||||
require("Font4x8Numeric").add(Graphics);
|
|
||||||
|
|
||||||
var settings = require("Storage").readJSON("clkinfocal.json",1)||{};
|
var settings = require("Storage").readJSON("clkinfocal.json",1)||{};
|
||||||
settings.fmt = settings.fmt||"DDD";
|
settings.fmt = settings.fmt||"DDD";
|
||||||
|
|
||||||
var getDateString = function(dt) {
|
var getDateString = function(dt) {
|
||||||
switch(settings.fmt) {
|
switch(settings.fmt) {
|
||||||
case "dd MMM":
|
case "dd MMM":
|
||||||
return '' + dt.getDate() + ' ' + require("locale").month(dt,1).toUpperCase();
|
return require("locale").month(dt,1).toUpperCase();
|
||||||
case "DDD dd":
|
case "DDD dd":
|
||||||
return require("locale").dow(dt,1).toUpperCase() + ' ' + dt.getDate();
|
return require("locale").dow(dt,1).toUpperCase() + ' ' + dt.getDate();
|
||||||
default: // DDD
|
default: // DDD
|
||||||
|
@ -22,6 +20,7 @@
|
||||||
get : () => {
|
get : () => {
|
||||||
let d = new Date();
|
let d = new Date();
|
||||||
let g = Graphics.createArrayBuffer(24,24,1,{msb:true});
|
let g = Graphics.createArrayBuffer(24,24,1,{msb:true});
|
||||||
|
g.transparent = 0;
|
||||||
g.drawImage(atob("FhgBDADAMAMP/////////////////////8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAP///////"),1,0);
|
g.drawImage(atob("FhgBDADAMAMP/////////////////////8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAP///////"),1,0);
|
||||||
g.setFont("6x15").setFontAlign(0,0).drawString(d.getDate(),11,17);
|
g.setFont("6x15").setFontAlign(0,0).drawString(d.getDate(),11,17);
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{ "id": "clkinfocal",
|
{ "id": "clkinfocal",
|
||||||
"name": "Calendar Clockinfo",
|
"name": "Calendar Clockinfo",
|
||||||
"version":"0.02",
|
"version":"0.05",
|
||||||
"description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays the day of the month in the icon, and the weekday. There is also a settings menu to select the format of the text",
|
"description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays the day of the month in the icon, and the weekday. There is also a settings menu to select the format of the text",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot.png"}],
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
var info = {
|
var info = {
|
||||||
name: "Gps",
|
name: "GPS",
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: "gridref",
|
name: "gridref",
|
||||||
|
|
|
@ -71,7 +71,7 @@ OsGridRef.latLongToOsGrid = function(point) {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function to_map_ref(digits, easting, northing) {
|
function to_map_ref(digits, easting, northing) {
|
||||||
if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); // eslint-disable-line comma-spacing
|
if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`);
|
||||||
|
|
||||||
let e = easting;
|
let e = easting;
|
||||||
let n = northing;
|
let n = northing;
|
||||||
|
|
|
@ -7,3 +7,7 @@
|
||||||
0.06: When >1 clockinfo, swiping one back tries to ensure they don't display the same thing
|
0.06: When >1 clockinfo, swiping one back tries to ensure they don't display the same thing
|
||||||
0.07: Developer tweak: clkinfo load errors are emitted
|
0.07: Developer tweak: clkinfo load errors are emitted
|
||||||
0.08: Pass options to show(), hide() and run(), and add focus() and blur() item methods
|
0.08: Pass options to show(), hide() and run(), and add focus() and blur() item methods
|
||||||
|
0.09: Save clkinfo settings on kill and remove
|
||||||
|
0.10: Fix focus bug when changing focus between two clock infos
|
||||||
|
0.11: Prepend swipe listener if possible
|
||||||
|
0.12: Add drawFilledImage to allow drawing icons with a separately coloured middle
|
|
@ -224,6 +224,13 @@ exports.addInteractive = function(menu, options) {
|
||||||
options.menuB = b;
|
options.menuB = b;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const save = () => {
|
||||||
|
// save the currently showing clock_info
|
||||||
|
const settings = exports.loadSettings();
|
||||||
|
settings.apps[appName] = {a:options.menuA, b:options.menuB};
|
||||||
|
require("Storage").writeJSON("clock_info.json",settings);
|
||||||
|
};
|
||||||
|
E.on("kill", save);
|
||||||
|
|
||||||
if (options.menuA===undefined) options.menuA = 0;
|
if (options.menuA===undefined) options.menuA = 0;
|
||||||
if (options.menuB===undefined) options.menuB = Math.min(exports.loadCount, menu[options.menuA].items.length)-1;
|
if (options.menuB===undefined) options.menuB = Math.min(exports.loadCount, menu[options.menuA].items.length)-1;
|
||||||
|
@ -276,17 +283,13 @@ exports.addInteractive = function(menu, options) {
|
||||||
oldMenuItem.removeAllListeners("draw");
|
oldMenuItem.removeAllListeners("draw");
|
||||||
menuShowItem(menu[options.menuA].items[options.menuB]);
|
menuShowItem(menu[options.menuA].items[options.menuB]);
|
||||||
}
|
}
|
||||||
// save the currently showing clock_info
|
|
||||||
let settings = exports.loadSettings();
|
|
||||||
settings.apps[appName] = {a:options.menuA,b:options.menuB};
|
|
||||||
require("Storage").writeJSON("clock_info.json",settings);
|
|
||||||
// On 2v18+ firmware we can stop other event handlers from being executed since we handled this
|
// On 2v18+ firmware we can stop other event handlers from being executed since we handled this
|
||||||
E.stopEventPropagation&&E.stopEventPropagation();
|
E.stopEventPropagation&&E.stopEventPropagation();
|
||||||
}
|
}
|
||||||
Bangle.on("swipe",swipeHandler);
|
if (Bangle.prependListener) {Bangle.prependListener("swipe",swipeHandler);} else {Bangle.on("swipe",swipeHandler);}
|
||||||
const blur = () => {
|
const blur = () => {
|
||||||
options.focus=false;
|
options.focus=false;
|
||||||
delete Bangle.CLKINFO_FOCUS;
|
Bangle.CLKINFO_FOCUS--;
|
||||||
const itm = menu[options.menuA].items[options.menuB];
|
const itm = menu[options.menuA].items[options.menuB];
|
||||||
let redraw = true;
|
let redraw = true;
|
||||||
if (itm.blur && itm.blur(options) === false)
|
if (itm.blur && itm.blur(options) === false)
|
||||||
|
@ -295,7 +298,7 @@ exports.addInteractive = function(menu, options) {
|
||||||
};
|
};
|
||||||
const focus = () => {
|
const focus = () => {
|
||||||
let redraw = true;
|
let redraw = true;
|
||||||
Bangle.CLKINFO_FOCUS=true;
|
Bangle.CLKINFO_FOCUS = (0 | Bangle.CLKINFO_FOCUS) + 1;
|
||||||
if (!options.focus) {
|
if (!options.focus) {
|
||||||
options.focus=true;
|
options.focus=true;
|
||||||
const itm = menu[options.menuA].items[options.menuB];
|
const itm = menu[options.menuA].items[options.menuB];
|
||||||
|
@ -333,10 +336,12 @@ exports.addInteractive = function(menu, options) {
|
||||||
menuShowItem(menu[options.menuA].items[options.menuB]);
|
menuShowItem(menu[options.menuA].items[options.menuB]);
|
||||||
// return an object with info that can be used to remove the info
|
// return an object with info that can be used to remove the info
|
||||||
options.remove = function() {
|
options.remove = function() {
|
||||||
|
save();
|
||||||
|
E.removeListener("kill", save);
|
||||||
Bangle.removeListener("swipe",swipeHandler);
|
Bangle.removeListener("swipe",swipeHandler);
|
||||||
if (touchHandler) Bangle.removeListener("touch",touchHandler);
|
if (touchHandler) Bangle.removeListener("touch",touchHandler);
|
||||||
if (lockHandler) Bangle.removeListener("lock", lockHandler);
|
if (lockHandler) Bangle.removeListener("lock", lockHandler);
|
||||||
delete Bangle.CLKINFO_FOCUS;
|
Bangle.CLKINFO_FOCUS--;
|
||||||
menuHideItem(menu[options.menuA].items[options.menuB]);
|
menuHideItem(menu[options.menuA].items[options.menuB]);
|
||||||
exports.loadCount--;
|
exports.loadCount--;
|
||||||
delete exports.clockInfos[options.index];
|
delete exports.clockInfos[options.index];
|
||||||
|
@ -366,6 +371,46 @@ exports.addInteractive = function(menu, options) {
|
||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* clockinfos usually return a 24x24 image. This draws that image but
|
||||||
|
recolors it such that it is transparent, with the middle of the image as background
|
||||||
|
and the image itself as foreground. options is passed to g.drawImage */
|
||||||
|
exports.drawFilledImage = function(img,x,y,options) {
|
||||||
|
if (!img) return;
|
||||||
|
if (!g.floodFill/*2v18+*/) return g.drawImage(img,x,y,options);
|
||||||
|
let gfx = exports.imgGfx;
|
||||||
|
if (!gfx) {
|
||||||
|
gfx = exports.imgGfx = Graphics.createArrayBuffer(26, 26, 2, {msb:true});
|
||||||
|
gfx.transparent = 3;
|
||||||
|
gfx.palette = new Uint16Array([g.theme.bg, g.theme.fg, g.toColor("#888"), g.toColor("#888")]);
|
||||||
|
}
|
||||||
|
/* img is (usually) a black and white transparent image. But we really would like the bits in
|
||||||
|
the middle of it to be white. So what we do is we draw a slightly bigger rectangle in white,
|
||||||
|
draw the image, and then flood-fill the rectangle back to the background color. floodFill
|
||||||
|
was only added in 2v18 so we have to check for it and fallback if not. */
|
||||||
|
gfx.clear(1).setColor(1).drawImage(img, 1,1).floodFill(0,0,3);
|
||||||
|
var scale = (options && options.scale) || 1;
|
||||||
|
return g.drawImage(gfx, x-scale,y-scale,options);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* clockinfos usually return a 24x24 image. This creates a 26x26 gfx of the image but
|
||||||
|
recolors it such that it is transparent, with the middle and border of the image as background
|
||||||
|
and the image itself as foreground. options is passed to g.drawImage */
|
||||||
|
exports.drawBorderedImage = function(img,x,y,options) {
|
||||||
|
if (!img) return;
|
||||||
|
if (!g.floodFill/*2v18+*/) return g.drawImage(img,x,y,options);
|
||||||
|
let gfx = exports.imgGfxB;
|
||||||
|
if (!gfx) {
|
||||||
|
gfx = exports.imgGfxB = Graphics.createArrayBuffer(28, 28, 2, {msb:true});
|
||||||
|
gfx.transparent = 3;
|
||||||
|
gfx.palette = new Uint16Array([g.theme.bg, g.theme.fg, g.theme.bg/*border*/, g.toColor("#888")]);
|
||||||
|
}
|
||||||
|
gfx.clear(1).setColor(2).drawImage(img, 1,1).drawImage(img, 3,1).drawImage(img, 1,3).drawImage(img, 3,3); // border
|
||||||
|
gfx.setColor(1).drawImage(img, 2,2); // main image
|
||||||
|
gfx.floodFill(27,27,3); // flood fill edge to transparent
|
||||||
|
var o = ((options && options.scale) || 1)*2;
|
||||||
|
return g.drawImage(gfx, x-o,y-o,options);
|
||||||
|
};
|
||||||
|
|
||||||
// Code for testing (plots all elements from first list)
|
// Code for testing (plots all elements from first list)
|
||||||
/*
|
/*
|
||||||
g.clear();
|
g.clear();
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
{ "id": "clock_info",
|
{ "id": "clock_info",
|
||||||
"name": "Clock Info Module",
|
"name": "Clock Info Module",
|
||||||
"shortName": "Clock Info",
|
"shortName": "Clock Info",
|
||||||
"version":"0.08",
|
"version":"0.12",
|
||||||
"description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)",
|
"description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"tags": "clkinfo",
|
"tags": "clkinfo,clockinfo",
|
||||||
"supports" : ["BANGLEJS2"],
|
"supports" : ["BANGLEJS2"],
|
||||||
"provides_modules" : ["clock_info"],
|
"provides_modules" : ["clock_info"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
|
|
|
@ -6,3 +6,5 @@
|
||||||
0.06: Tell clock widgets to hide.
|
0.06: Tell clock widgets to hide.
|
||||||
0.07: Convert Yes/No On/Off in settings to checkboxes
|
0.07: Convert Yes/No On/Off in settings to checkboxes
|
||||||
0.08: Fixed typo in settings.js for DRAGDOWN to make option work
|
0.08: Fixed typo in settings.js for DRAGDOWN to make option work
|
||||||
|
0.09: You can now back out of the calendar using the button
|
||||||
|
0.10: Fix linter warnings
|
||||||
|
|
|
@ -7,23 +7,24 @@ I know that it seems redundant because there already **is** a *time&cal*-app, bu
|
||||||
|:--:|:-|
|
|:--:|:-|
|
||||||
||locked: triggers only one minimal update/min|
|
||locked: triggers only one minimal update/min|
|
||||||
||unlocked: smaller clock, but with seconds|
|
||unlocked: smaller clock, but with seconds|
|
||||||
||swipe up for big calendar, (up down to scroll, left/right to exit)|
|
||swipe up for big calendar<br>⬆️/⬇️ to scroll<br> ⬅️/➡️ to exit|
|
||||||
|
|
||||||
## Configurable Features
|
## Configurable Features
|
||||||
- Number of calendar rows (weeks)
|
- Number of calendar rows (weeks)
|
||||||
- Buzz on connect/disconnect (I know, this should be an extra widget, but for now, it is included)
|
- Buzz on connect/disconnect (feel free to disable and use a widget)
|
||||||
- Clock Mode (24h/12h). (No am/pm indicator)
|
- Clock Mode (24h/12h). (No am/pm indicator)
|
||||||
- First day of the week
|
- First day of the week
|
||||||
- Red Saturday/Sunday
|
- Red Saturday/Sunday
|
||||||
- Swipe/Drag gestures to launch features or apps.
|
- Swipe/Drag gestures to launch features or apps.
|
||||||
|
|
||||||
## Auto detects your message/music apps:
|
## Integrated swipe launcher: (Configure in Settings)
|
||||||
- swiping down will search your files for an app with the string "message" in its filename and launch it. (configurable)
|
- ⬇️ (down) will search your files for an app with the string "**message**"
|
||||||
- swiping right will search your files for an app with the string "music" in its filename and launch it. (configurable)
|
- ➡️ (right) will search your files for an app with the string "**music**"
|
||||||
|
- ⬅️ (left) will search your files for an app with the string "**agenda**"
|
||||||
|
- ⬆️ (up) will show the **internal full calendar**
|
||||||
|
|
||||||
## Feedback
|
## Feedback
|
||||||
The clock works for me in a 24h/MondayFirst/WeekendFree environment but is not well-tested with other settings.
|
If something isn't working, please tell me: https://github.com/Stuff-etc/BangleApps/issues (I moved my github repo)
|
||||||
So if something isn't working, please tell me: https://github.com/foostuff/BangleApps/issues
|
|
||||||
|
|
||||||
## Planned features:
|
## Planned features:
|
||||||
- Internal lightweight music control, because switching apps has a loading time.
|
- Internal lightweight music control, because switching apps has a loading time.
|
||||||
|
|
|
@ -24,15 +24,25 @@ const DEBUG = false;
|
||||||
var state = "watch";
|
var state = "watch";
|
||||||
var monthOffset = 0;
|
var monthOffset = 0;
|
||||||
|
|
||||||
|
// FIXME: These variables should maybe be defined inside relevant functions below. The linter complained they were not defined (i.e. they were added to global scope if I understand correctly).
|
||||||
|
let dayInterval;
|
||||||
|
let secondInterval;
|
||||||
|
let minuteInterval;
|
||||||
|
let newmonth;
|
||||||
|
let bottomrightY;
|
||||||
|
let bottomrightX;
|
||||||
|
let rMonth;
|
||||||
|
let dimSeconds;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Calendar features
|
* Calendar features
|
||||||
*/
|
*/
|
||||||
function drawFullCalendar(monthOffset) {
|
function drawFullCalendar(monthOffset) {
|
||||||
addMonths = function (_d, _am) {
|
const addMonths = function (_d, _am) {
|
||||||
var ay = 0, m = _d.getMonth(), y = _d.getFullYear();
|
let ay = 0, m = _d.getMonth(), y = _d.getFullYear();
|
||||||
while ((m + _am) > 11) { ay++; _am -= 12; }
|
while ((m + _am) > 11) { ay++; _am -= 12; }
|
||||||
while ((m + _am) < 0) { ay--; _am += 12; }
|
while ((m + _am) < 0) { ay--; _am += 12; }
|
||||||
n = new Date(_d.getTime());
|
let n = new Date(_d.getTime());
|
||||||
n.setMonth(m + _am);
|
n.setMonth(m + _am);
|
||||||
n.setFullYear(y + ay);
|
n.setFullYear(y + ay);
|
||||||
return n;
|
return n;
|
||||||
|
@ -45,10 +55,10 @@ function drawFullCalendar(monthOffset) {
|
||||||
if (typeof dayInterval !== "undefined") clearTimeout(dayInterval);
|
if (typeof dayInterval !== "undefined") clearTimeout(dayInterval);
|
||||||
if (typeof secondInterval !== "undefined") clearTimeout(secondInterval);
|
if (typeof secondInterval !== "undefined") clearTimeout(secondInterval);
|
||||||
if (typeof minuteInterval !== "undefined") clearTimeout(minuteInterval);
|
if (typeof minuteInterval !== "undefined") clearTimeout(minuteInterval);
|
||||||
d = addMonths(Date(), monthOffset);
|
var d = addMonths(Date(), monthOffset);
|
||||||
tdy = Date().getDate() + "." + Date().getMonth();
|
let tdy = Date().getDate() + "." + Date().getMonth();
|
||||||
newmonth = false;
|
newmonth = false;
|
||||||
c_y = 0;
|
let c_y = 0;
|
||||||
g.reset();
|
g.reset();
|
||||||
g.setBgColor(0);
|
g.setBgColor(0);
|
||||||
g.clear();
|
g.clear();
|
||||||
|
@ -60,8 +70,8 @@ function drawFullCalendar(monthOffset) {
|
||||||
rD.setDate(rD.getDate() - dow);
|
rD.setDate(rD.getDate() - dow);
|
||||||
var rDate = rD.getDate();
|
var rDate = rD.getDate();
|
||||||
bottomrightY = c_y - 3;
|
bottomrightY = c_y - 3;
|
||||||
clrsun = s.REDSUN ? '#f00' : '#fff';
|
let clrsun = s.REDSUN ? '#f00' : '#fff';
|
||||||
clrsat = s.REDSUN ? '#f00' : '#fff';
|
let clrsat = s.REDSUN ? '#f00' : '#fff';
|
||||||
var fg = [clrsun, '#fff', '#fff', '#fff', '#fff', '#fff', clrsat];
|
var fg = [clrsun, '#fff', '#fff', '#fff', '#fff', '#fff', clrsat];
|
||||||
for (var y = 1; y <= 11; y++) {
|
for (var y = 1; y <= 11; y++) {
|
||||||
bottomrightY += CELL_H;
|
bottomrightY += CELL_H;
|
||||||
|
@ -90,7 +100,7 @@ function caldrawMonth(rDate, c, m, rD) {
|
||||||
g.setColor(c);
|
g.setColor(c);
|
||||||
g.setFont("Vector", 18);
|
g.setFont("Vector", 18);
|
||||||
g.setFontAlign(-1, 1, 1);
|
g.setFontAlign(-1, 1, 1);
|
||||||
drawyear = ((rMonth % 11) == 0) ? String(rD.getFullYear()).substr(-2) : "";
|
let drawyear = ((rMonth % 11) == 0) ? String(rD.getFullYear()).substr(-2) : "";
|
||||||
g.drawString(m + drawyear, bottomrightX, bottomrightY - CELL_H, 1);
|
g.drawString(m + drawyear, bottomrightX, bottomrightY - CELL_H, 1);
|
||||||
newmonth = false;
|
newmonth = false;
|
||||||
}
|
}
|
||||||
|
@ -124,7 +134,7 @@ function drawMinutes() {
|
||||||
var d = new Date();
|
var d = new Date();
|
||||||
var hours = s.MODE24 ? d.getHours().toString().padStart(2, ' ') : ((d.getHours() + 24) % 12 || 12).toString().padStart(2, ' ');
|
var hours = s.MODE24 ? d.getHours().toString().padStart(2, ' ') : ((d.getHours() + 24) % 12 || 12).toString().padStart(2, ' ');
|
||||||
var minutes = d.getMinutes().toString().padStart(2, '0');
|
var minutes = d.getMinutes().toString().padStart(2, '0');
|
||||||
var textColor = NRF.getSecurityStatus().connected ? '#99f' : '#fff';
|
var textColor = NRF.getSecurityStatus().connected ? '#fff' : '#f00';
|
||||||
var size = 50;
|
var size = 50;
|
||||||
var clock_x = (w - 20) / 2;
|
var clock_x = (w - 20) / 2;
|
||||||
if (dimSeconds) {
|
if (dimSeconds) {
|
||||||
|
@ -156,7 +166,7 @@ function drawSeconds() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawWatch() {
|
function drawWatch() {
|
||||||
if (DEBUG) console.log("CALENDAR");
|
if (DEBUG) console.log("DRAWWATCH");
|
||||||
monthOffset = 0;
|
monthOffset = 0;
|
||||||
state = "watch";
|
state = "watch";
|
||||||
var d = new Date();
|
var d = new Date();
|
||||||
|
@ -197,6 +207,7 @@ function drawWatch() {
|
||||||
if (DEBUG) console.log("Next Day:" + (nextday / 3600));
|
if (DEBUG) console.log("Next Day:" + (nextday / 3600));
|
||||||
if (typeof dayInterval !== "undefined") clearTimeout(dayInterval);
|
if (typeof dayInterval !== "undefined") clearTimeout(dayInterval);
|
||||||
dayInterval = setTimeout(drawWatch, nextday * 1000);
|
dayInterval = setTimeout(drawWatch, nextday * 1000);
|
||||||
|
if (DEBUG) console.log("ended DRAWWATCH. next refresh in " + nextday + "s");
|
||||||
}
|
}
|
||||||
|
|
||||||
function BTevent() {
|
function BTevent() {
|
||||||
|
@ -211,8 +222,12 @@ function action(a) {
|
||||||
g.reset();
|
g.reset();
|
||||||
if (typeof secondInterval !== "undefined") clearTimeout(secondInterval);
|
if (typeof secondInterval !== "undefined") clearTimeout(secondInterval);
|
||||||
if (DEBUG) console.log("action:" + a);
|
if (DEBUG) console.log("action:" + a);
|
||||||
|
state = "unknown";
|
||||||
|
console.log("state -> unknown");
|
||||||
|
let l;
|
||||||
switch (a) {
|
switch (a) {
|
||||||
case "[ignore]":
|
case "[ignore]":
|
||||||
|
drawWatch();
|
||||||
break;
|
break;
|
||||||
case "[calend.]":
|
case "[calend.]":
|
||||||
drawFullCalendar();
|
drawFullCalendar();
|
||||||
|
@ -229,6 +244,12 @@ function action(a) {
|
||||||
load(l[0]);
|
load(l[0]);
|
||||||
} else E.showAlert("Message app not found", "Not found").then(drawWatch);
|
} else E.showAlert("Message app not found", "Not found").then(drawWatch);
|
||||||
break;
|
break;
|
||||||
|
case "[AI:agenda]":
|
||||||
|
l = require("Storage").list(RegExp("agenda.*app.js"));
|
||||||
|
if (l.length > 0) {
|
||||||
|
load(l[0]);
|
||||||
|
} else E.showAlert("Agenda app not found", "Not found").then(drawWatch);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
l = require("Storage").list(RegExp(a + ".app.js"));
|
l = require("Storage").list(RegExp(a + ".app.js"));
|
||||||
if (l.length > 0) {
|
if (l.length > 0) {
|
||||||
|
@ -276,7 +297,6 @@ function input(dir) {
|
||||||
drawWatch();
|
drawWatch();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,3 +329,10 @@ NRF.on('disconnect', BTevent);
|
||||||
dimSeconds = Bangle.isLocked();
|
dimSeconds = Bangle.isLocked();
|
||||||
drawWatch();
|
drawWatch();
|
||||||
|
|
||||||
|
setWatch(function() {
|
||||||
|
if (state == "watch") {
|
||||||
|
Bangle.showLauncher()
|
||||||
|
} else if (state == "calendar") {
|
||||||
|
drawWatch();
|
||||||
|
}
|
||||||
|
}, BTN1, {repeat:true, edge:"falling"});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "clockcal",
|
"id": "clockcal",
|
||||||
"name": "Clock & Calendar",
|
"name": "Clock & Calendar",
|
||||||
"version": "0.08",
|
"version": "0.10",
|
||||||
"description": "Clock with Calendar",
|
"description": "Clock with Calendar",
|
||||||
"readme":"README.md",
|
"readme":"README.md",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
(function (back) {
|
(function (back) {
|
||||||
var FILE = "clockcal.json";
|
var FILE = "clockcal.json";
|
||||||
defaults={
|
const defaults={
|
||||||
CAL_ROWS: 4, //number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets.
|
CAL_ROWS: 4, //number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets.
|
||||||
BUZZ_ON_BT: true, //2x slow buzz on disconnect, 2x fast buzz on connect. Will be extra widget eventually
|
BUZZ_ON_BT: true, //2x slow buzz on disconnect, 2x fast buzz on connect. Will be extra widget eventually
|
||||||
MODE24: true, //24h mode vs 12h mode
|
MODE24: true, //24h mode vs 12h mode
|
||||||
|
@ -9,19 +9,19 @@
|
||||||
REDSAT: true, // Use red color for saturday?
|
REDSAT: true, // Use red color for saturday?
|
||||||
DRAGDOWN: "[AI:messg]",
|
DRAGDOWN: "[AI:messg]",
|
||||||
DRAGRIGHT: "[AI:music]",
|
DRAGRIGHT: "[AI:music]",
|
||||||
DRAGLEFT: "[ignore]",
|
DRAGLEFT: "[AI:agenda]",
|
||||||
DRAGUP: "[calend.]"
|
DRAGUP: "[calend.]"
|
||||||
};
|
};
|
||||||
settings = Object.assign(defaults, require('Storage').readJSON(FILE, true) || {});
|
let settings = Object.assign(defaults, require('Storage').readJSON(FILE, true) || {});
|
||||||
|
|
||||||
actions = ["[ignore]","[calend.]","[AI:music]","[AI:messg]"];
|
let actions = ["[ignore]","[calend.]","[AI:music]","[AI:messg]","[AI:agenda]"];
|
||||||
require("Storage").list(RegExp(".app.js")).forEach(element => actions.push(element.replace(".app.js","")));
|
require("Storage").list(RegExp(".app.js")).forEach(element => actions.push(element.replace(".app.js","")));
|
||||||
|
|
||||||
function writeSettings() {
|
function writeSettings() {
|
||||||
require('Storage').writeJSON(FILE, settings);
|
require('Storage').writeJSON(FILE, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
menu = {
|
const menu = {
|
||||||
"": { "title": "Clock & Calendar" },
|
"": { "title": "Clock & Calendar" },
|
||||||
"< Back": () => back(),
|
"< Back": () => back(),
|
||||||
'Buzz(dis)conn.?': {
|
'Buzz(dis)conn.?': {
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
0.01: 1st ver,RGB565 and RGB888 colors in a common UI/UX
|
0.01: 1st ver,RGB565 and RGB888 colors in a common UI/UX
|
||||||
0.02: Minor code improvements
|
0.02: Minor code improvements
|
||||||
|
0.03: Minor code improvements
|
||||||
|
|
|
@ -35,22 +35,22 @@ var v_model=process.env.BOARD;
|
||||||
var v_color_text='#FB0E01';
|
var v_color_text='#FB0E01';
|
||||||
var v_color_statictxt='#e56e06'; //orange RGB format rrggbb
|
var v_color_statictxt='#e56e06'; //orange RGB format rrggbb
|
||||||
//RGB565 requires only 16 (5+6+5) bits/2 bytes
|
//RGB565 requires only 16 (5+6+5) bits/2 bytes
|
||||||
var a_colors_str= Array('White RGB565 0x','Orange','DarkGreen','Yellow',
|
var a_colors_str= ['White RGB565 0x','Orange','DarkGreen','Yellow',
|
||||||
'Maroon','Blue','green','Purple',
|
'Maroon','Blue','green','Purple',
|
||||||
'cyan','olive','DarkCyan','DarkGrey',
|
'cyan','olive','DarkCyan','DarkGrey',
|
||||||
'Navy','Red','Magenta','GreenYellow',
|
'Navy','Red','Magenta','GreenYellow',
|
||||||
'Blush RGB888','pure red','Orange','Grey green',
|
'Blush RGB888','pure red','Orange','Grey green',
|
||||||
'D. grey','Almond','Amber','Bone',
|
'D. grey','Almond','Amber','Bone',
|
||||||
'Canary','Aero blue','Camel','Baby pink',
|
'Canary','Aero blue','Camel','Baby pink',
|
||||||
'Y.Corn','Cultured','Eigengrau','Citrine');
|
'Y.Corn','Cultured','Eigengrau','Citrine'];
|
||||||
var a_colors= Array(0xFFFF,0xFD20,0x03E0,0xFFE0,
|
var a_colors= [0xFFFF,0xFD20,0x03E0,0xFFE0,
|
||||||
0x7800,0x001F,0x07E0,0x780F,
|
0x7800,0x001F,0x07E0,0x780F,
|
||||||
0x07FF,0x7BE0,0x03EF,0x7BEF,
|
0x07FF,0x7BE0,0x03EF,0x7BEF,
|
||||||
0x000F,0xF800,0xF81F,0xAFE5,
|
0x000F,0xF800,0xF81F,0xAFE5,
|
||||||
'#DE5D83','#FB0E01','#E56E06','#7E795C',
|
'#DE5D83','#FB0E01','#E56E06','#7E795C',
|
||||||
'#404040','#EFDECD','#FFBF00','#E3DAC9',
|
'#404040','#EFDECD','#FFBF00','#E3DAC9',
|
||||||
'#FFFF99','#C0E8D5','#C19A6B','#F4C2C2',
|
'#FFFF99','#C0E8D5','#C19A6B','#F4C2C2',
|
||||||
'#FBEC5D','#F5F5F5','#16161D','#E4D00A');
|
'#FBEC5D','#F5F5F5','#16161D','#E4D00A'];
|
||||||
var v_color_lines=0xFFFF; //White hex format
|
var v_color_lines=0xFFFF; //White hex format
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "color_catalog",
|
"id": "color_catalog",
|
||||||
"name": "Colors Catalog",
|
"name": "Colors Catalog",
|
||||||
"shortName": "Colors Catalog",
|
"shortName": "Colors Catalog",
|
||||||
"version": "0.02",
|
"version": "0.03",
|
||||||
"description": "Displays RGB565 and RGB888 colors, its name and code in screen.",
|
"description": "Displays RGB565 and RGB888 colors, its name and code in screen.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "Color,input,buttons,touch,UI",
|
"tags": "Color,input,buttons,touch,UI",
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Minor code improvements
|
0.02: Minor code improvements
|
||||||
0.03: Minor code improvements
|
0.03: Minor code improvements
|
||||||
|
0.04: Allow calling contacts from the app, Refactoring
|
||||||
|
|
|
@ -2,108 +2,102 @@
|
||||||
|
|
||||||
var Layout = require("Layout");
|
var Layout = require("Layout");
|
||||||
|
|
||||||
//const W = g.getWidth();
|
|
||||||
//const H = g.getHeight();
|
|
||||||
|
|
||||||
var wp = require('Storage').readJSON("contacts.json", true) || [];
|
var wp = require('Storage').readJSON("contacts.json", true) || [];
|
||||||
// Use this with corrupted contacts
|
|
||||||
//var wp = [];
|
|
||||||
|
|
||||||
var key; /* Shared between functions, typically wp name */
|
function writeContacts() {
|
||||||
|
|
||||||
function writeContact() {
|
|
||||||
require('Storage').writeJSON("contacts.json", wp);
|
require('Storage').writeJSON("contacts.json", wp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function callNumber (number) {
|
||||||
|
Bluetooth.println(JSON.stringify({
|
||||||
|
t:"intent",
|
||||||
|
target:"activity",
|
||||||
|
action:"android.intent.action.CALL",
|
||||||
|
flags:["FLAG_ACTIVITY_NEW_TASK"],
|
||||||
|
categories:["android.intent.category.DEFAULT"],
|
||||||
|
data: 'tel:' + number,
|
||||||
|
}))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
function mainMenu() {
|
function mainMenu() {
|
||||||
var menu = {
|
var menu = {
|
||||||
"< Back" : Bangle.load
|
"< Back" : Bangle.load
|
||||||
};
|
};
|
||||||
if (Object.keys(wp).length==0) Object.assign(menu, {"NO Contacts":""});
|
if (!wp.length) {
|
||||||
else for (let id in wp) {
|
menu['No Contacts'] = () => {};
|
||||||
let i = id;
|
} else {
|
||||||
menu[wp[id]["name"]]=()=>{ decode(i); };
|
for (const e of wp) {
|
||||||
|
const closureE = e;
|
||||||
|
menu[e.name] = () => showContact(closureE);
|
||||||
}
|
}
|
||||||
menu["Add"]=addCard;
|
}
|
||||||
menu["Remove"]=removeCard;
|
menu["Add"] = addContact;
|
||||||
|
menu["Remove"] = removeContact;
|
||||||
g.clear();
|
g.clear();
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
function decode(pin) {
|
function showContact(i) {
|
||||||
var i = wp[pin];
|
|
||||||
var l = i["name"] + "\n" + i["number"];
|
|
||||||
var la = new Layout ({
|
|
||||||
type:"v", c: [
|
|
||||||
{type:"txt", font:"10%", pad:1, fillx:1, filly:1, label: l},
|
|
||||||
{type:"btn", font:"10%", pad:1, fillx:1, filly:1, label:"OK", cb:l=>{mainMenu();}}
|
|
||||||
], lazy:true});
|
|
||||||
g.clear();
|
g.clear();
|
||||||
la.render();
|
(new Layout ({
|
||||||
|
type:"v",
|
||||||
|
c: [
|
||||||
|
{type:"txt", font:"10%", pad:1, fillx:1, filly:1, label: i["name"] + "\n" + i["number"]},
|
||||||
|
{type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "Call", cb: l => callNumber(i['number'])},
|
||||||
|
{type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "Back to list", cb: mainMenu}
|
||||||
|
],
|
||||||
|
lazy:true
|
||||||
|
})).render();
|
||||||
}
|
}
|
||||||
|
|
||||||
function showNumpad(text, key_, callback) {
|
function showNumpad() {
|
||||||
key = key_;
|
return new Promise((resolve, reject) => {
|
||||||
|
let number = ''
|
||||||
E.showMenu();
|
E.showMenu();
|
||||||
function addDigit(digit) {
|
function addDigit(digit) {
|
||||||
key+=digit;
|
number += digit;
|
||||||
if (1) {
|
Bangle.buzz(20);
|
||||||
l = text[key.length];
|
update();
|
||||||
switch (l) {
|
|
||||||
case '.': case ' ': case "'":
|
|
||||||
key+=l;
|
|
||||||
break;
|
|
||||||
case 'd': case 'D': default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
function removeDigit() {
|
||||||
|
number = number.slice(0, -1);
|
||||||
Bangle.buzz(20);
|
Bangle.buzz(20);
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
function update() {
|
function update() {
|
||||||
g.reset();
|
g.reset();
|
||||||
g.clearRect(0,0,g.getWidth(),23);
|
g.clearRect(0,0,g.getWidth(),23);
|
||||||
s = key + text.substr(key.length, 999);
|
g.setFont("Vector:24").setFontAlign(1,0).drawString(number, g.getWidth(),12);
|
||||||
g.setFont("Vector:24").setFontAlign(1,0).drawString(s,g.getWidth(),12);
|
|
||||||
}
|
}
|
||||||
const ds="12%";
|
const ds="12%";
|
||||||
|
const digitBtn = (digit) => ({type:"btn", font:ds, width:58, label:digit, cb:l=>{addDigit(digit);}});
|
||||||
var numPad = new Layout ({
|
var numPad = new Layout ({
|
||||||
type:"v", c: [{
|
type:"v", c: [{
|
||||||
type:"v", c: [
|
type:"v", c: [
|
||||||
{type:"", height:24},
|
{type:"", height:24},
|
||||||
|
{type:"h",filly:1, c: [digitBtn("1"), digitBtn("2"), digitBtn("3")]},
|
||||||
|
{type:"h",filly:1, c: [digitBtn("4"), digitBtn("5"), digitBtn("6")]},
|
||||||
|
{type:"h",filly:1, c: [digitBtn("7"), digitBtn("8"), digitBtn("9")]},
|
||||||
{type:"h",filly:1, c: [
|
{type:"h",filly:1, c: [
|
||||||
{type:"btn", font:ds, width:58, label:"7", cb:l=>{addDigit("7");}},
|
{type:"btn", font:ds, width:58, label:"C", cb: removeDigit},
|
||||||
{type:"btn", font:ds, width:58, label:"8", cb:l=>{addDigit("8");}},
|
digitBtn('0'),
|
||||||
{type:"btn", font:ds, width:58, label:"9", cb:l=>{addDigit("9");}}
|
{type:"btn", font:ds, width:58, id:"OK", label:"OK", cb: l => resolve(number)}
|
||||||
]},
|
|
||||||
{type:"h",filly:1, c: [
|
|
||||||
{type:"btn", font:ds, width:58, label:"4", cb:l=>{addDigit("4");}},
|
|
||||||
{type:"btn", font:ds, width:58, label:"5", cb:l=>{addDigit("5");}},
|
|
||||||
{type:"btn", font:ds, width:58, label:"6", cb:l=>{addDigit("6");}}
|
|
||||||
]},
|
|
||||||
{type:"h",filly:1, c: [
|
|
||||||
{type:"btn", font:ds, width:58, label:"1", cb:l=>{addDigit("1");}},
|
|
||||||
{type:"btn", font:ds, width:58, label:"2", cb:l=>{addDigit("2");}},
|
|
||||||
{type:"btn", font:ds, width:58, label:"3", cb:l=>{addDigit("3");}}
|
|
||||||
]},
|
|
||||||
{type:"h",filly:1, c: [
|
|
||||||
{type:"btn", font:ds, width:58, label:"0", cb:l=>{addDigit("0");}},
|
|
||||||
{type:"btn", font:ds, width:58, label:"C", cb:l=>{key=key.slice(0,-1); update();}},
|
|
||||||
{type:"btn", font:ds, width:58, id:"OK", label:"OK", cb:callback}
|
|
||||||
]}
|
]}
|
||||||
]}
|
]}
|
||||||
], lazy:true});
|
], lazy:true});
|
||||||
g.clear();
|
g.clear();
|
||||||
numPad.render();
|
numPad.render();
|
||||||
update();
|
update();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeCard() {
|
function removeContact() {
|
||||||
var menu = {
|
var menu = {
|
||||||
"" : {title : "Select Contact"},
|
"" : {title : "Select Contact"},
|
||||||
"< Back" : mainMenu
|
"< Back" : mainMenu
|
||||||
};
|
};
|
||||||
if (Object.keys(wp).length==0) Object.assign(menu, {"No Contacts":""});
|
if (wp.length===0) Object.assign(menu, {"No Contacts":""});
|
||||||
else {
|
else {
|
||||||
wp.forEach((val, card) => {
|
wp.forEach((val, card) => {
|
||||||
const name = wp[card].name;
|
const name = wp[card].name;
|
||||||
|
@ -116,7 +110,7 @@ function removeCard() {
|
||||||
{type:"h", c: [
|
{type:"h", c: [
|
||||||
{type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: "YES", cb:l=>{
|
{type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: "YES", cb:l=>{
|
||||||
wp.splice(card, 1);
|
wp.splice(card, 1);
|
||||||
writeContact();
|
writeContacts();
|
||||||
mainMenu();
|
mainMenu();
|
||||||
}},
|
}},
|
||||||
{type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: " NO", cb:l=>{mainMenu();}}
|
{type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: " NO", cb:l=>{mainMenu();}}
|
||||||
|
@ -130,40 +124,28 @@ function removeCard() {
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
function askPosition(callback) {
|
|
||||||
showNumpad("dddDDDddd", "", function() {
|
|
||||||
callback(key, "");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createContact(lat, name) {
|
function addNewContact(name) {
|
||||||
let n = {};
|
|
||||||
n["name"] = name;
|
|
||||||
n["number"] = lat;
|
|
||||||
wp.push(n);
|
|
||||||
print("add -- contacts", wp);
|
|
||||||
writeContact();
|
|
||||||
}
|
|
||||||
|
|
||||||
function addCardName2(key) {
|
|
||||||
g.clear();
|
g.clear();
|
||||||
askPosition(function(lat, lon) {
|
showNumpad().then((number) => {
|
||||||
print("position -- ", lat, lon);
|
wp.push({name: name, number: number});
|
||||||
createContact(lat, result);
|
writeContacts();
|
||||||
mainMenu();
|
mainMenu();
|
||||||
});
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addCardName(key) {
|
function tryAddContact(name) {
|
||||||
result = key;
|
if (wp.filter((e) => e.name === name).length) {
|
||||||
if (wp[result]!=undefined) {
|
|
||||||
E.showMenu();
|
E.showMenu();
|
||||||
var alreadyExists = new Layout (
|
var alreadyExists = new Layout (
|
||||||
{type:"v", c: [
|
{type:"v", c: [
|
||||||
{type:"txt", font:Math.min(15,100/result.length)+"%", pad:1, fillx:1, filly:1, label:result},
|
{type:"txt", font:Math.min(15,100/name.length)+"%", pad:1, fillx:1, filly:1, label:name},
|
||||||
{type:"txt", font:"12%", pad:1, fillx:1, filly:1, label:"already exists."},
|
{type:"txt", font:"12%", pad:1, fillx:1, filly:1, label:"already exists."},
|
||||||
{type:"h", c: [
|
{type:"h", c: [
|
||||||
{type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "REPLACE", cb:l=>{ addCardName2(key); }},
|
{type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "REPLACE", cb:l=>{ addNewContact(name); }},
|
||||||
{type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "CANCEL", cb:l=>{mainMenu();}}
|
{type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "CANCEL", cb:l=>{mainMenu();}}
|
||||||
]}
|
]}
|
||||||
], lazy:true});
|
], lazy:true});
|
||||||
|
@ -171,13 +153,13 @@ function addCardName(key) {
|
||||||
alreadyExists.render();
|
alreadyExists.render();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
addCardName2(key);
|
addNewContact(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addCard() {
|
function addContact() {
|
||||||
require("textinput").input({text:""}).then(result => {
|
require("textinput").input({text:""}).then(name => {
|
||||||
if (result != "") {
|
if (name !== "") {
|
||||||
addCardName(result);
|
tryAddContact(name);
|
||||||
} else
|
} else
|
||||||
mainMenu();
|
mainMenu();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{ "id": "contacts",
|
{ "id": "contacts",
|
||||||
"name": "Contacts",
|
"name": "Contacts",
|
||||||
"version": "0.03",
|
"version": "0.04",
|
||||||
"description": "Provides means of storing user contacts, viewing/editing them on device and from the App loader",
|
"description": "Provides means of storing user contacts, viewing/editing them on device and from the App loader",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool",
|
"tags": "tool",
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue