Merge branch 'espruino:master' into sleeplog_v0.10_beta
|
@ -0,0 +1,352 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.8,maximum-scale=0.8, minimum-scale=0.8, shrink-to-fit=no">
|
||||
<link rel="stylesheet" href="css/spectre.min.css">
|
||||
<link rel="stylesheet" href="css/spectre-exp.min.css">
|
||||
<link rel="stylesheet" href="css/spectre-icons.min.css">
|
||||
<link rel="stylesheet" href="css/pwa.css">
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="img/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="img/favicon-16x16.png">
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<link rel="mask-icon" href="img/safari-pinned-tab.svg" color="#5755d9">
|
||||
<meta name="apple-mobile-web-app-title" content="BangleApps">
|
||||
<meta name="application-name" content="BangleApps">
|
||||
<meta name="msapplication-TileColor" content="#5755d9">
|
||||
<meta name="theme-color" content="#5755d9">
|
||||
<title>Bangle.js App Loader</title>
|
||||
</head>
|
||||
<body>
|
||||
<!--<button id="test">Test</button>
|
||||
<div id="status"></div>-->
|
||||
|
||||
<header class="navbar-primary navbar">
|
||||
<section class="navbar-section" >
|
||||
<a href="https://banglejs.com" target="_blank" class="navbar-brand mr-2" ><img src="img/banglejs-logo-sml.png" alt="Bangle.js">
|
||||
<div>App Loader</div></a>
|
||||
<!-- <a href="#" class="btn btn-link">...</a> -->
|
||||
</section>
|
||||
<section class="navbar-section">
|
||||
<button class="btn" id="connectmydevice">Connect</button>
|
||||
</section>
|
||||
<!--<section class="navbar-section">
|
||||
<div class="input-group input-inline">
|
||||
<input class="form-input" type="text" placeholder="search">
|
||||
<button class="btn btn-primary input-group-btn">Search</button>
|
||||
</div>
|
||||
</section>-->
|
||||
</header>
|
||||
|
||||
<div class="container" style="padding-top:4px">
|
||||
<p id="requireHTTPS" class="hidden">
|
||||
<b>STOP!</b> This page <b>must</b> be served over HTTPS. Please <a>reload this page via HTTPS</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<ul class="tab tab-block" id="tab-navigate">
|
||||
<li class="tab-item active" id="tab-librarycontainer">
|
||||
<a href="javascript:showTab('librarycontainer')">Library</a>
|
||||
</li>
|
||||
<li class="tab-item" id="tab-myappscontainer">
|
||||
<a href="javascript:showTab('myappscontainer')">My Apps</a>
|
||||
</li>
|
||||
<li class="tab-item" id="tab-morecontainer">
|
||||
<a href="javascript:showTab('morecontainer')">More...</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="container" id="toastcontainer">
|
||||
</div>
|
||||
|
||||
<div class="container apploader-tab" id="librarycontainer">
|
||||
<div class="dropdown-container">
|
||||
<div class="dropdown devicetype-nav">
|
||||
<a href="#" class="btn btn-link dropdown-toggle" tabindex="0">
|
||||
<span>All apps</span><i class="icon icon-caret"></i>
|
||||
</a>
|
||||
<!-- menu component -->
|
||||
<ul class="menu">
|
||||
<li class="menu-item"><a>All apps</a></li>
|
||||
<li class="menu-item"><a dt="BANGLEJS">Bangle.js 1</a></li>
|
||||
<li class="menu-item"><a dt="BANGLEJS2">Bangle.js 2</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="filter-nav">
|
||||
<label class="chip active" filterid="">Default</label>
|
||||
<label class="chip" filterid="clock">Clocks</label>
|
||||
<label class="chip" filterid="game">Games</label>
|
||||
<label class="chip" filterid="tool">Tools</label>
|
||||
<label class="chip" filterid="widget">Widgets</label>
|
||||
<label class="chip" filterid="bluetooth">Bluetooth</label>
|
||||
<label class="chip" filterid="outdoors">Outdoors</label>
|
||||
<label class="chip" filterid="favourites">Favourites</label>
|
||||
</div>
|
||||
<div class="sort-nav hidden">
|
||||
<span>Sort by:</span>
|
||||
<label class="chip active" sortid="">None</label>
|
||||
<label class="chip" sortid="created">New</label>
|
||||
<label class="chip" sortid="modified">Updated</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel" style="clear:both">
|
||||
<div class="panel-header">
|
||||
<div class="input-group" id="searchform">
|
||||
<input class="form-input" type="text" placeholder="Keywords...">
|
||||
<button class="btn btn-primary input-group-btn">Search</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body columns"><!-- apps go here --></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container apploader-tab" id="myappscontainer" style="display:none">
|
||||
<div class="panel">
|
||||
<div class="panel-header" style="text-align:right">
|
||||
<button class="btn refresh">Refresh...</button>
|
||||
<button class="btn btn-primary updateapps hidden">Update X apps</button>
|
||||
</div>
|
||||
<div class="panel-body columns"><!-- apps go here --></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container apploader-tab" id="morecontainer" style="display:none">
|
||||
<div class="hero bg-gray">
|
||||
<div class="hero-body">
|
||||
<a href="https://banglejs.com" target="_blank"><img src="img/banglejs-logo-mid.png" alt="Bangle.js"></a>
|
||||
<h2>App Loader</h2>
|
||||
<p>A tool for uploading and removing apps from <a href="https://banglejs.com" target="_blank">Bangle.js Smart Watches</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container" style="padding-top: 8px;">
|
||||
<p><b>Can't connect?</b> Check out the <a href="https://www.espruino.com/Troubleshooting+Bangle.js" target="_blank">Bangle.js Troubleshooting page</a>
|
||||
<p id="apploaderlinks"></p>
|
||||
<p>Check out <a href="https://github.com/espruino/BangleApps" target="_blank">the Source on GitHub</a>, or
|
||||
find out <a href="https://www.espruino.com/Bangle.js+App+Loader" target="_blank">how to add your own app</a></p>
|
||||
<p>Using <a href="https://espruino.com/" target="_blank">Espruino</a>, Icons from <a href="https://icons8.com/" target="_blank">icons8.com</a></p>
|
||||
|
||||
<h3>Utilities</h3>
|
||||
<p><button class="btn" id="settime">Set Bangle.js Time</button>
|
||||
<button class="btn" id="removeall" data-tooltip="Delete everything from your Bangle, leaving it blank">Remove all Apps</button>
|
||||
<button class="btn" id="reinstallall" data-tooltip="Remove and re-install every app, leaving all other data intact">Reinstall apps</button>
|
||||
<button class="btn" id="installdefault">Install default apps</button>
|
||||
<button class="btn" id="installfavourite" data-tooltip="Delete everything, install apps you've marked as favourites">Install favourite apps</button></p>
|
||||
<p><button class="btn tooltip tooltip-right" id="downloadallapps" data-tooltip="Download all Bangle.js files to a ZIP file">Backup</button>
|
||||
<button class="btn tooltip tooltip-right" id="uploadallapps" data-tooltip="Restore Bangle.js from a ZIP file">Restore</button></p>
|
||||
<h3>Settings</h3>
|
||||
<div class="form-group">
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" id="settings-pretokenise">
|
||||
<i class="form-icon"></i> Pretokenise apps before upload (smaller, faster apps)
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" id="settings-settime">
|
||||
<i class="form-icon"></i> Always update time when we connect
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<select class="form-select form-inline" id="settings-lang" style="width: 10em">
|
||||
<option value="">None (English)</option>
|
||||
</select> <span>Translations (<a href="https://github.com/espruino/BangleApps/issues/1311" target="_blank">BETA - more info</a>). Any apps that are uploaded to Bangle.js after changing this will have any text automatically translated.</span>
|
||||
</div>
|
||||
<button class="btn" id="defaultsettings">Default settings</button>
|
||||
</div>
|
||||
<div id="more-deviceinfo" style="display:none">
|
||||
<h3>Device info</h3>
|
||||
<div id="more-deviceinfo-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="floating hidden">
|
||||
<!-- Install button, hidden by default -->
|
||||
<div id="installContainer" class="hidden">
|
||||
<button id="butInstall" type="button">
|
||||
Install
|
||||
</button>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://www.puck-js.com/puck.js"></script>
|
||||
<script src="core/lib/marked.min.js"></script>
|
||||
<script src="core/lib/espruinotools.js"></script>
|
||||
<script src="core/lib/heatshrink.js"></script>
|
||||
<script src="core/js/utils.js"></script>
|
||||
<script src="loader.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js"></script> <!-- for backup.js -->
|
||||
<script src="backup.js"></script>
|
||||
<script src="core/js/ui.js"></script>
|
||||
<script src="core/js/comms.js"></script>
|
||||
<script src="core/js/appinfo.js"></script>
|
||||
<script src="core/js/index.js"></script>
|
||||
<script src="core/js/pwa.js" defer></script>
|
||||
<script>
|
||||
/*Android = {
|
||||
bangleTx : function(data) {
|
||||
console.log("TX : "+JSON.stringify(data));
|
||||
}
|
||||
};*/
|
||||
|
||||
/*document.getElementById("test").addEventListener("click", function() {
|
||||
console.log("Pressed");
|
||||
Android.bangleTx("LED1.toggle();\n");
|
||||
});*/
|
||||
|
||||
if (typeof Android!=="undefined") {
|
||||
console.log("Running in Android, overwrite Puck library");
|
||||
|
||||
var isBusy = false;
|
||||
var queue = [];
|
||||
var connection = {
|
||||
cb : function(data) {},
|
||||
write : function(data, writecb) {
|
||||
Android.bangleTx(data);
|
||||
Puck.writeProgress(data.length, data.length);
|
||||
if (writecb) setTimeout(writecb,10);
|
||||
},
|
||||
close : function() {},
|
||||
received : "",
|
||||
hadData : false
|
||||
}
|
||||
|
||||
function bangleRx(data) {
|
||||
// document.getElementById("status").innerText = "RX:"+data;
|
||||
connection.received += data;
|
||||
connection.hadData = true;
|
||||
if (connection.cb) connection.cb(data);
|
||||
}
|
||||
|
||||
function log(level, s) {
|
||||
if (Puck.log) Puck.log(level, s);
|
||||
}
|
||||
|
||||
function handleQueue() {
|
||||
if (!queue.length) return;
|
||||
var q = queue.shift();
|
||||
log(3,"Executing "+JSON.stringify(q)+" from queue");
|
||||
if (q.type == "write") Puck.write(q.data, q.callback, q.callbackNewline);
|
||||
else log(1,"Unknown queue item "+JSON.stringify(q));
|
||||
}
|
||||
|
||||
/* convenience function... Write data, call the callback with data:
|
||||
callbackNewline = false => if no new data received for ~0.2 sec
|
||||
callbackNewline = true => after a newline */
|
||||
function write(data, callback, callbackNewline) {
|
||||
let result;
|
||||
/// If there wasn't a callback function, then promisify
|
||||
if (typeof callback !== 'function') {
|
||||
callbackNewline = callback;
|
||||
|
||||
result = new Promise((resolve, reject) => callback = (value, err) => {
|
||||
if (err) reject(err);
|
||||
else resolve(value);
|
||||
});
|
||||
}
|
||||
|
||||
if (isBusy) {
|
||||
log(3, "Busy - adding Puck.write to queue");
|
||||
queue.push({type:"write", data:data, callback:callback, callbackNewline:callbackNewline});
|
||||
return result;
|
||||
}
|
||||
|
||||
var cbTimeout;
|
||||
function onWritten() {
|
||||
if (callbackNewline) {
|
||||
connection.cb = function(d) {
|
||||
var newLineIdx = connection.received.indexOf("\n");
|
||||
if (newLineIdx>=0) {
|
||||
var l = connection.received.substr(0,newLineIdx);
|
||||
connection.received = connection.received.substr(newLineIdx+1);
|
||||
connection.cb = undefined;
|
||||
if (cbTimeout) clearTimeout(cbTimeout);
|
||||
cbTimeout = undefined;
|
||||
if (callback)
|
||||
callback(l);
|
||||
isBusy = false;
|
||||
handleQueue();
|
||||
}
|
||||
};
|
||||
}
|
||||
// wait for any received data if we have a callback...
|
||||
var maxTime = 300; // 30 sec - Max time we wait in total, even if getting data
|
||||
var dataWaitTime = callbackNewline ? 100/*10 sec if waiting for newline*/ : 3/*300ms*/;
|
||||
var maxDataTime = dataWaitTime; // max time we wait after having received data
|
||||
cbTimeout = setTimeout(function timeout() {
|
||||
cbTimeout = undefined;
|
||||
if (maxTime) maxTime--;
|
||||
if (maxDataTime) maxDataTime--;
|
||||
if (connection.hadData) maxDataTime=dataWaitTime;
|
||||
if (maxDataTime && maxTime) {
|
||||
cbTimeout = setTimeout(timeout, 100);
|
||||
} else {
|
||||
connection.cb = undefined;
|
||||
if (callback)
|
||||
callback(connection.received);
|
||||
isBusy = false;
|
||||
handleQueue();
|
||||
connection.received = "";
|
||||
}
|
||||
connection.hadData = false;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
if (!connection.txInProgress) connection.received = "";
|
||||
isBusy = true;
|
||||
connection.write(data, onWritten);
|
||||
return result
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
Puck = {
|
||||
/// Are we writing debug information? 0 is no, 1 is some, 2 is more, 3 is all.
|
||||
debug : Puck.debug,
|
||||
/// Should we use flow control? Default is true
|
||||
flowControl : true,
|
||||
/// Used internally to write log information - you can replace this with your own function
|
||||
log : function(level, s) { if (level <= this.debug) console.log("<BLE> "+s)},
|
||||
/// Called with the current send progress or undefined when done - you can replace this with your own function
|
||||
writeProgress : Puck.writeProgress,
|
||||
connect : function(callback) {
|
||||
setTimeout(callback, 10);
|
||||
},
|
||||
write : write,
|
||||
eval : function(expr, cb) {
|
||||
const response = write('\x10Bluetooth.println(JSON.stringify(' + expr + '))\n', true)
|
||||
.then(function (d) {
|
||||
try {
|
||||
return JSON.parse(d);
|
||||
} catch (e) {
|
||||
log(1, "Unable to decode " + JSON.stringify(d) + ", got " + e.toString());
|
||||
return Promise.reject(d);
|
||||
}
|
||||
});
|
||||
if (cb) {
|
||||
return void response.then(cb, (err) => cb(null, err));
|
||||
} else {
|
||||
return response;
|
||||
}
|
||||
},
|
||||
isConnected : function() { return true; },
|
||||
getConnection : function() { return connection; },
|
||||
close : function() {
|
||||
if (connection)
|
||||
connection.close();
|
||||
},
|
||||
};
|
||||
// no need for header
|
||||
document.getElementsByTagName("header")[0].style="display:none";
|
||||
// force connection attempt automatically
|
||||
setTimeout(function() {
|
||||
getInstalledApps(true).catch(err => {
|
||||
showToast("Device connection failed, "+err,"error");
|
||||
if ("object"==typeof err) console.log(err.stack);
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -217,7 +217,12 @@ function showEditRepeatMenu(repeat, dow, dowChangeCallback) {
|
|||
function showCustomDaysMenu(dow, dowChangeCallback, originalRepeat, originalDow) {
|
||||
const menu = {
|
||||
"": { "title": /*LANG*/"Custom Days" },
|
||||
"< Back": () => dowChangeCallback(true, dow),
|
||||
"< Back": () => {
|
||||
// If the user unchecks all the days then we assume repeat = once
|
||||
// and we force the dow to every day.
|
||||
var repeat = dow > 0;
|
||||
dowChangeCallback(repeat, repeat ? dow : EVERY_DAY)
|
||||
}
|
||||
};
|
||||
|
||||
require("date_utils").dows(firstDayOfWeek).forEach((day, i) => {
|
||||
|
@ -315,12 +320,12 @@ function showAdvancedMenu() {
|
|||
|
||||
function enableAll(on) {
|
||||
if (alarms.filter(e => e.on == !on).length == 0) {
|
||||
E.showPrompt(on ? /*LANG*/"Nothing to Enable" : /*LANG*/"Nothing to Disable", {
|
||||
title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All",
|
||||
buttons: { /*LANG*/"Ok": true }
|
||||
}).then(() => showAdvancedMenu());
|
||||
E.showAlert(
|
||||
on ? /*LANG*/"Nothing to Enable" : /*LANG*/"Nothing to Disable",
|
||||
on ? /*LANG*/"Enable All" : /*LANG*/"Disable All"
|
||||
).then(() => showAdvancedMenu());
|
||||
} else {
|
||||
E.showPrompt(/*LANG*/"Are you sure?", { title: on ? "/*LANG*/Enable All" : /*LANG*/"Disable All" }).then((confirm) => {
|
||||
E.showPrompt(/*LANG*/"Are you sure?", { title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All" }).then((confirm) => {
|
||||
if (confirm) {
|
||||
alarms.forEach(alarm => alarm.on = on);
|
||||
saveAndReload();
|
||||
|
@ -334,7 +339,7 @@ function enableAll(on) {
|
|||
|
||||
function deleteAll() {
|
||||
if (alarms.length == 0) {
|
||||
E.showPrompt(/*LANG*/"Nothing to delete", { title: /*LANG*/"Delete All", buttons: { /*LANG*/"Ok": true } }).then(() => showAdvancedMenu());
|
||||
E.showAlert(/*LANG*/"Nothing to delete", /*LANG*/"Delete All").then(() => showAdvancedMenu());
|
||||
} else {
|
||||
E.showPrompt(/*LANG*/"Are you sure?", {
|
||||
title: /*LANG*/"Delete All"
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
{ "id": "bowserWF",
|
||||
{
|
||||
"id": "bowserWF",
|
||||
"name": "Bowser Watchface",
|
||||
"shortName":"Bowser Watchface",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Let bowser show you the time",
|
||||
"icon": "app.png",
|
||||
"tags": "",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"bowserWF.app.js","url":"app.js"},
|
||||
{"name":"bowserWF.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
],
|
||||
"data": [{"name":"bowserWF.json"}]
|
||||
}
|
||||
|
|
|
@ -21,3 +21,4 @@
|
|||
Adds some preset modes and a custom one
|
||||
Restructure the settings menu
|
||||
0.08: Allow scanning for devices in settings
|
||||
0.09: Misc Fixes and improvements (https://github.com/espruino/BangleApps/pull/1655)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
When this app is installed it overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.
|
||||
|
||||
HRM is requested it searches on Bluetooth for a heart rate monitor, connects, and sends data back using the `Bangle.on('HRM'` event as if it came from the on board monitor.
|
||||
HRM is requested it searches on Bluetooth for a heart rate monitor, connects, and sends data back using the `Bangle.on('HRM')` event as if it came from the on board monitor.
|
||||
|
||||
This means it's compatible with many Bangle.js apps including:
|
||||
|
||||
|
@ -16,19 +16,23 @@ as that requires live sensor data (rather than just BPM readings).
|
|||
|
||||
Just install the app, then install an app that uses the heart rate monitor.
|
||||
|
||||
Once installed it'll automatically try and connect to the first bluetooth
|
||||
heart rate monitor it finds.
|
||||
Once installed you will have to go into this app's settings while your heart rate monitor
|
||||
is available for bluetooth pairing and scan for devices.
|
||||
|
||||
**To disable this and return to normal HRM, uninstall the app**
|
||||
|
||||
## Compatible Heart Rate Monitors
|
||||
|
||||
This works with any heart rate monitor providing the standard Bluetooth
|
||||
Heart Rate Service (`180D`) and characteristic (`2A37`).
|
||||
Heart Rate Service (`180D`) and characteristic (`2A37`). It additionally supports
|
||||
the location (`2A38`) characteristic and the Battery Service (`180F`), reporting
|
||||
that information in the `BTHRM` event when they are available.
|
||||
|
||||
So far it has been tested on:
|
||||
|
||||
* CooSpo Bluetooth Heart Rate Monitor
|
||||
* Polar H10
|
||||
* Polar OH1
|
||||
* Wahoo TICKR X 2
|
||||
|
||||
## Internals
|
||||
|
@ -38,7 +42,6 @@ This replaces `Bangle.setHRMPower` with its own implementation.
|
|||
## TODO
|
||||
|
||||
* A widget to show connection state?
|
||||
* Specify a specific device by address?
|
||||
|
||||
## Creator
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
require('Storage').readJSON("bthrm.default.json", true) || {},
|
||||
require('Storage').readJSON("bthrm.json", true) || {}
|
||||
);
|
||||
|
||||
|
||||
var log = function(text, param){
|
||||
if (settings.debuglog){
|
||||
var logline = new Date().toISOString() + " - " + text;
|
||||
|
@ -13,39 +13,38 @@
|
|||
print(logline);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
log("Settings: ", settings);
|
||||
|
||||
|
||||
if (settings.enabled){
|
||||
|
||||
function clearCache(){
|
||||
var clearCache = function() {
|
||||
return require('Storage').erase("bthrm.cache.json");
|
||||
}
|
||||
};
|
||||
|
||||
function getCache(){
|
||||
var getCache = function() {
|
||||
var cache = require('Storage').readJSON("bthrm.cache.json", true) || {};
|
||||
if (settings.btname && settings.btname == cache.name) return cache;
|
||||
if (settings.btid && settings.btid === cache.id) return cache;
|
||||
clearCache();
|
||||
return {};
|
||||
}
|
||||
|
||||
function addNotificationHandler(characteristic){
|
||||
};
|
||||
|
||||
var addNotificationHandler = function(characteristic) {
|
||||
log("Setting notification handler: " + supportedCharacteristics[characteristic.uuid].handler);
|
||||
characteristic.on('characteristicvaluechanged', supportedCharacteristics[characteristic.uuid].handler);
|
||||
}
|
||||
|
||||
function writeCache(cache){
|
||||
characteristic.on('characteristicvaluechanged', (ev) => supportedCharacteristics[characteristic.uuid].handler(ev.target.value));
|
||||
};
|
||||
|
||||
var writeCache = function(cache) {
|
||||
var oldCache = getCache();
|
||||
if (oldCache != cache) {
|
||||
if (oldCache !== cache) {
|
||||
log("Writing cache");
|
||||
require('Storage').writeJSON("bthrm.cache.json", cache)
|
||||
require('Storage').writeJSON("bthrm.cache.json", cache);
|
||||
} else {
|
||||
log("No changes, don't write cache");
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
function characteristicsToCache(characteristics){
|
||||
var characteristicsToCache = function(characteristics) {
|
||||
log("Cache characteristics");
|
||||
var cache = getCache();
|
||||
if (!cache.characteristics) cache.characteristics = {};
|
||||
|
@ -60,9 +59,9 @@
|
|||
};
|
||||
}
|
||||
writeCache(cache);
|
||||
}
|
||||
};
|
||||
|
||||
function characteristicsFromCache(){
|
||||
var characteristicsFromCache = function() {
|
||||
log("Read cached characteristics");
|
||||
var cache = getCache();
|
||||
if (!cache.characteristics) return [];
|
||||
|
@ -81,38 +80,34 @@
|
|||
restored.push(r);
|
||||
}
|
||||
return restored;
|
||||
}
|
||||
};
|
||||
|
||||
log("Start");
|
||||
|
||||
var lastReceivedData={
|
||||
};
|
||||
|
||||
var serviceFilters = [{
|
||||
services: [ "180d" ]
|
||||
}];
|
||||
|
||||
supportedServices = [
|
||||
"0x180d", "0x180f"
|
||||
var supportedServices = [
|
||||
"0x180d", // Heart Rate
|
||||
"0x180f", // Battery
|
||||
];
|
||||
|
||||
var supportedCharacteristics = {
|
||||
"0x2a37": {
|
||||
//Heart rate measurement
|
||||
handler: function (event){
|
||||
var dv = event.target.value;
|
||||
handler: function (dv){
|
||||
var flags = dv.getUint8(0);
|
||||
|
||||
|
||||
var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit
|
||||
|
||||
|
||||
var sensorContact;
|
||||
|
||||
|
||||
if (flags & 2){
|
||||
sensorContact = (flags & 4) ? true : false;
|
||||
sensorContact = !!(flags & 4);
|
||||
}
|
||||
|
||||
|
||||
var idx = 2 + (flags&1);
|
||||
|
||||
|
||||
var energyExpended;
|
||||
if (flags & 8){
|
||||
energyExpended = dv.getUint16(idx,1);
|
||||
|
@ -121,11 +116,11 @@
|
|||
var interval;
|
||||
if (flags & 16) {
|
||||
interval = [];
|
||||
maxIntervalBytes = (dv.byteLength - idx);
|
||||
var maxIntervalBytes = (dv.byteLength - idx);
|
||||
log("Found " + (maxIntervalBytes / 2) + " rr data fields");
|
||||
for(var i = 0 ; i < maxIntervalBytes / 2; i++){
|
||||
interval[i] = dv.getUint16(idx,1); // in milliseconds
|
||||
idx += 2
|
||||
idx += 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,45 +135,44 @@
|
|||
}
|
||||
|
||||
if (settings.replace){
|
||||
var newEvent = {
|
||||
var repEvent = {
|
||||
bpm: bpm,
|
||||
confidence: (sensorContact || sensorContact === undefined)? 100 : 0,
|
||||
src: "bthrm"
|
||||
};
|
||||
|
||||
log("Emitting HRM: ", newEvent);
|
||||
Bangle.emit("HRM", newEvent);
|
||||
|
||||
log("Emitting HRM: ", repEvent);
|
||||
Bangle.emit("HRM", repEvent);
|
||||
}
|
||||
|
||||
var newEvent = {
|
||||
bpm: bpm
|
||||
};
|
||||
|
||||
|
||||
if (location) newEvent.location = location;
|
||||
if (interval) newEvent.rr = interval;
|
||||
if (energyExpended) newEvent.energy = energyExpended;
|
||||
if (battery) newEvent.battery = battery;
|
||||
if (sensorContact) newEvent.contact = sensorContact;
|
||||
|
||||
|
||||
log("Emitting BTHRM: ", newEvent);
|
||||
Bangle.emit("BTHRM", newEvent);
|
||||
}
|
||||
},
|
||||
"0x2a38": {
|
||||
//Body sensor location
|
||||
handler: function(data){
|
||||
handler: function(dv){
|
||||
if (!lastReceivedData["0x180d"]) lastReceivedData["0x180d"] = {};
|
||||
if (!lastReceivedData["0x180d"]["0x2a38"]) lastReceivedData["0x180d"]["0x2a38"] = data.target.value;
|
||||
lastReceivedData["0x180d"]["0x2a38"] = parseInt(dv.buffer, 10);
|
||||
}
|
||||
},
|
||||
"0x2a19": {
|
||||
//Battery
|
||||
handler: function (event){
|
||||
handler: function (dv){
|
||||
if (!lastReceivedData["0x180f"]) lastReceivedData["0x180f"] = {};
|
||||
if (!lastReceivedData["0x180f"]["0x2a19"]) lastReceivedData["0x180f"]["0x2a19"] = event.target.value.getUint8(0);
|
||||
lastReceivedData["0x180f"]["0x2a19"] = dv.getUint8(0);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
var device;
|
||||
|
@ -195,7 +189,7 @@
|
|||
maxInterval: 1500
|
||||
};
|
||||
|
||||
function waitingPromise(timeout) {
|
||||
var waitingPromise = function(timeout) {
|
||||
return new Promise(function(resolve){
|
||||
log("Start waiting for " + timeout);
|
||||
setTimeout(()=>{
|
||||
|
@ -203,7 +197,7 @@
|
|||
resolve();
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (settings.enabled){
|
||||
Bangle.isBTHRMOn = function(){
|
||||
|
@ -215,7 +209,6 @@
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
if (settings.replace){
|
||||
var origIsHRMOn = Bangle.isHRMOn;
|
||||
|
||||
|
@ -229,15 +222,15 @@
|
|||
};
|
||||
}
|
||||
|
||||
function clearRetryTimeout(){
|
||||
var clearRetryTimeout = function() {
|
||||
if (currentRetryTimeout){
|
||||
log("Clearing timeout " + currentRetryTimeout);
|
||||
clearTimeout(currentRetryTimeout);
|
||||
currentRetryTimeout = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function retry(){
|
||||
var retry = function() {
|
||||
log("Retry");
|
||||
|
||||
if (!currentRetryTimeout){
|
||||
|
@ -252,17 +245,17 @@
|
|||
initBt();
|
||||
}, clampedTime);
|
||||
|
||||
retryTime = Math.pow(retryTime, 1.1);
|
||||
retryTime = Math.pow(clampedTime, 1.1);
|
||||
if (retryTime > maxRetryTime){
|
||||
retryTime = maxRetryTime;
|
||||
}
|
||||
} else {
|
||||
log("Already in retry...");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var buzzing = false;
|
||||
function onDisconnect(reason) {
|
||||
var onDisconnect = function(reason) {
|
||||
log("Disconnect: " + reason);
|
||||
log("GATT: ", gatt);
|
||||
log("Characteristics: ", characteristics);
|
||||
|
@ -277,11 +270,23 @@
|
|||
if (Bangle.isBTHRMOn()){
|
||||
retry();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function createCharacteristicPromise(newCharacteristic){
|
||||
var createCharacteristicPromise = function(newCharacteristic) {
|
||||
log("Create characteristic promise: ", newCharacteristic);
|
||||
var result = Promise.resolve();
|
||||
// For values that can be read, go ahead and read them, even if we might be notified in the future
|
||||
// Allows for getting initial state of infrequently updating characteristics, like battery
|
||||
if (newCharacteristic.readValue){
|
||||
result = result.then(()=>{
|
||||
log("Reading data for " + JSON.stringify(newCharacteristic));
|
||||
return newCharacteristic.readValue().then((data)=>{
|
||||
if (supportedCharacteristics[newCharacteristic.uuid] && supportedCharacteristics[newCharacteristic.uuid].handler) {
|
||||
supportedCharacteristics[newCharacteristic.uuid].handler(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
if (newCharacteristic.properties.notify){
|
||||
result = result.then(()=>{
|
||||
log("Starting notifications for: ", newCharacteristic);
|
||||
|
@ -290,31 +295,23 @@
|
|||
log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications");
|
||||
startPromise = startPromise.then(()=>{
|
||||
log("Wait after connect");
|
||||
waitingPromise(settings.gracePeriodNotification)
|
||||
return waitingPromise(settings.gracePeriodNotification);
|
||||
});
|
||||
}
|
||||
return startPromise;
|
||||
});
|
||||
} else if (newCharacteristic.read){
|
||||
result = result.then(()=>{
|
||||
readData(newCharacteristic);
|
||||
log("Reading data for " + newCharacteristic);
|
||||
return newCharacteristic.read().then((data)=>{
|
||||
supportedCharacteristics[newCharacteristic.uuid].handler(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
return result.then(()=>log("Handled characteristic: ", newCharacteristic));
|
||||
}
|
||||
|
||||
function attachCharacteristicPromise(promise, characteristic){
|
||||
};
|
||||
|
||||
var attachCharacteristicPromise = function(promise, characteristic) {
|
||||
return promise.then(()=>{
|
||||
log("Handling characteristic:", characteristic);
|
||||
return createCharacteristicPromise(characteristic);
|
||||
});
|
||||
}
|
||||
|
||||
function createCharacteristicsPromise(newCharacteristics){
|
||||
};
|
||||
|
||||
var createCharacteristicsPromise = function(newCharacteristics) {
|
||||
log("Create characteristics promise: ", newCharacteristics);
|
||||
var result = Promise.resolve();
|
||||
for (var c of newCharacteristics){
|
||||
|
@ -324,13 +321,13 @@
|
|||
if (c.properties.notify){
|
||||
addNotificationHandler(c);
|
||||
}
|
||||
|
||||
|
||||
result = attachCharacteristicPromise(result, c);
|
||||
}
|
||||
return result.then(()=>log("Handled characteristics"));
|
||||
}
|
||||
|
||||
function createServicePromise(service){
|
||||
};
|
||||
|
||||
var createServicePromise = function(service) {
|
||||
log("Create service promise: ", service);
|
||||
var result = Promise.resolve();
|
||||
result = result.then(()=>{
|
||||
|
@ -338,15 +335,13 @@
|
|||
return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c));
|
||||
});
|
||||
return result.then(()=>log("Handled service" + service.uuid));
|
||||
}
|
||||
|
||||
function attachServicePromise(promise, service){
|
||||
return promise.then(()=>createServicePromise(service));
|
||||
}
|
||||
|
||||
var reUseCounter = 0;
|
||||
};
|
||||
|
||||
function initBt() {
|
||||
var attachServicePromise = function(promise, service) {
|
||||
return promise.then(()=>createServicePromise(service));
|
||||
};
|
||||
|
||||
var initBt = function () {
|
||||
log("initBt with blockInit: " + blockInit);
|
||||
if (blockInit){
|
||||
retry();
|
||||
|
@ -355,63 +350,58 @@
|
|||
|
||||
blockInit = true;
|
||||
|
||||
if (reUseCounter > 10){
|
||||
log("Reuse counter to high");
|
||||
gatt=undefined;
|
||||
reUseCounter = 0;
|
||||
}
|
||||
|
||||
var promise;
|
||||
|
||||
var filters;
|
||||
|
||||
if (!device){
|
||||
var filters = serviceFilters;
|
||||
if (settings.btname){
|
||||
log("Configured device name", settings.btname);
|
||||
filters = [{name: settings.btname}];
|
||||
if (settings.btid){
|
||||
log("Configured device id", settings.btid);
|
||||
filters = [{ id: settings.btid }];
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
log("Requesting device with filters", filters);
|
||||
promise = NRF.requestDevice({ filters: filters });
|
||||
|
||||
promise = NRF.requestDevice({ filters: filters, active: true });
|
||||
|
||||
if (settings.gracePeriodRequest){
|
||||
log("Add " + settings.gracePeriodRequest + "ms grace period after request");
|
||||
}
|
||||
|
||||
|
||||
promise = promise.then((d)=>{
|
||||
log("Got device: ", d);
|
||||
d.on('gattserverdisconnected', onDisconnect);
|
||||
device = d;
|
||||
});
|
||||
|
||||
|
||||
promise = promise.then(()=>{
|
||||
log("Wait after request");
|
||||
return waitingPromise(settings.gracePeriodRequest);
|
||||
});
|
||||
|
||||
} else {
|
||||
promise = Promise.resolve();
|
||||
log("Reuse device: ", device);
|
||||
}
|
||||
|
||||
|
||||
promise = promise.then(()=>{
|
||||
if (gatt){
|
||||
log("Reuse GATT: ", gatt);
|
||||
} else {
|
||||
log("GATT is new: ", gatt);
|
||||
characteristics = [];
|
||||
var cachedName = getCache().name;
|
||||
if (device.name != cachedName){
|
||||
log("Device name changed from " + cachedName + " to " + device.name + ", clearing cache");
|
||||
var cachedId = getCache().id;
|
||||
if (device.id !== cachedId){
|
||||
log("Device ID changed from " + cachedId + " to " + device.id + ", clearing cache");
|
||||
clearCache();
|
||||
}
|
||||
var newCache = getCache();
|
||||
newCache.name = device.name;
|
||||
newCache.id = device.id;
|
||||
writeCache(newCache);
|
||||
gatt = device.gatt;
|
||||
}
|
||||
|
||||
|
||||
return Promise.resolve(gatt);
|
||||
});
|
||||
|
||||
|
||||
promise = promise.then((gatt)=>{
|
||||
if (!gatt.connected){
|
||||
var connectPromise = gatt.connect(connectSettings);
|
||||
|
@ -427,16 +417,28 @@
|
|||
return Promise.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/* promise = promise.then(() => {
|
||||
log(JSON.stringify(gatt.getSecurityStatus()));
|
||||
if (gatt.getSecurityStatus()['bonded']) {
|
||||
log("Already bonded");
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
log("Start bonding");
|
||||
return gatt.startBonding()
|
||||
.then(() => console.log(gatt.getSecurityStatus()));
|
||||
}
|
||||
});*/
|
||||
|
||||
promise = promise.then(()=>{
|
||||
if (!characteristics || characteristics.length == 0){
|
||||
if (!characteristics || characteristics.length === 0){
|
||||
characteristics = characteristicsFromCache();
|
||||
}
|
||||
});
|
||||
|
||||
promise = promise.then(()=>{
|
||||
var characteristicsPromise = Promise.resolve();
|
||||
if (characteristics.length == 0){
|
||||
if (characteristics.length === 0){
|
||||
characteristicsPromise = characteristicsPromise.then(()=>{
|
||||
log("Getting services");
|
||||
return gatt.getPrimaryServices();
|
||||
|
@ -454,24 +456,22 @@
|
|||
log("Add " + settings.gracePeriodService + "ms grace period after services");
|
||||
result = result.then(()=>{
|
||||
log("Wait after services");
|
||||
return waitingPromise(settings.gracePeriodService)
|
||||
return waitingPromise(settings.gracePeriodService);
|
||||
});
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
} else {
|
||||
for (var characteristic of characteristics){
|
||||
characteristicsPromise = attachCharacteristicPromise(characteristicsPromise, characteristic, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return characteristicsPromise;
|
||||
});
|
||||
|
||||
promise = promise.then(()=>{
|
||||
|
||||
return promise.then(()=>{
|
||||
log("Connection established, waiting for notifications");
|
||||
reUseCounter = 0;
|
||||
characteristicsToCache(characteristics);
|
||||
clearRetryTimeout();
|
||||
}).catch((e) => {
|
||||
|
@ -479,7 +479,7 @@
|
|||
log("Error:", e);
|
||||
onDisconnect(e);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Bangle.setBTHRMPower = function(isOn, app) {
|
||||
// Do app power handling
|
||||
|
@ -487,7 +487,7 @@
|
|||
if (Bangle._PWR===undefined) Bangle._PWR={};
|
||||
if (Bangle._PWR.BTHRM===undefined) Bangle._PWR.BTHRM=[];
|
||||
if (isOn && !Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM.push(app);
|
||||
if (!isOn && Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM = Bangle._PWR.BTHRM.filter(a=>a!=app);
|
||||
if (!isOn && Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM = Bangle._PWR.BTHRM.filter(a=>a!==app);
|
||||
isOn = Bangle._PWR.BTHRM.length;
|
||||
// so now we know if we're really on
|
||||
if (isOn) {
|
||||
|
@ -510,7 +510,7 @@
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var origSetHRMPower = Bangle.setHRMPower;
|
||||
|
||||
if (settings.startWithHrm){
|
||||
|
@ -525,11 +525,10 @@
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
var fallbackInterval;
|
||||
|
||||
function switchInternalHrm(){
|
||||
|
||||
var switchInternalHrm = function() {
|
||||
if (settings.allowFallback && !fallbackInterval){
|
||||
log("Fallback to HRM enabled");
|
||||
origSetHRMPower(1, "bthrm_fallback");
|
||||
|
@ -542,7 +541,7 @@
|
|||
}
|
||||
}, settings.fallbackTimeout);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (settings.replace){
|
||||
log("Replace HRM event");
|
||||
|
@ -557,11 +556,11 @@
|
|||
}
|
||||
switchInternalHrm();
|
||||
}
|
||||
|
||||
|
||||
E.on("kill", ()=>{
|
||||
if (gatt && gatt.connected){
|
||||
log("Got killed, trying to disconnect");
|
||||
var promise = gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect on kill", e));
|
||||
gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect on kill", e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
var btm = g.getHeight()-1;
|
||||
var intervalInt;
|
||||
var intervalBt;
|
||||
|
||||
var BODY_LOCS = {
|
||||
0: 'Other',
|
||||
1: 'Chest',
|
||||
2: 'Wrist',
|
||||
3: 'Finger',
|
||||
4: 'Hand',
|
||||
5: 'Ear Lobe',
|
||||
6: 'Foot',
|
||||
}
|
||||
|
||||
function clear(y){
|
||||
g.reset();
|
||||
g.clearRect(0,y,g.getWidth(),y+75);
|
||||
|
@ -15,17 +24,17 @@ function draw(y, type, event) {
|
|||
g.setFontAlign(0,0);
|
||||
g.setFontVector(40).drawString(str,px,y+20);
|
||||
str = "Event: " + type;
|
||||
if (type == "HRM") {
|
||||
if (type === "HRM") {
|
||||
str += " Confidence: " + event.confidence;
|
||||
g.setFontVector(12).drawString(str,px,y+40);
|
||||
str = " Source: " + (event.src ? event.src : "internal");
|
||||
g.setFontVector(12).drawString(str,px,y+50);
|
||||
}
|
||||
if (type == "BTHRM"){
|
||||
if (type === "BTHRM"){
|
||||
if (event.battery) str += " Bat: " + (event.battery ? event.battery : "");
|
||||
g.setFontVector(12).drawString(str,px,y+40);
|
||||
str= "";
|
||||
if (event.location) str += "Loc: " + event.location.toFixed(0) + "ms";
|
||||
if (event.location) str += "Loc: " + BODY_LOCS[event.location];
|
||||
if (event.rr && event.rr.length > 0) str += " RR: " + event.rr.join(",");
|
||||
g.setFontVector(12).drawString(str,px,y+50);
|
||||
str= "";
|
||||
|
@ -45,7 +54,7 @@ function onBtHrm(e) {
|
|||
firstEventBt = false;
|
||||
}
|
||||
draw(100, "BTHRM", e);
|
||||
if (e.bpm == 0){
|
||||
if (e.bpm === 0){
|
||||
Bangle.buzz(100,0.2);
|
||||
}
|
||||
if (intervalBt){
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
"allowFallback": true,
|
||||
"warnDisconnect": false,
|
||||
"fallbackTimeout": 10,
|
||||
"custom_replace": false,
|
||||
"custom_replace": true,
|
||||
"custom_debuglog": false,
|
||||
"custom_startWithHrm": false,
|
||||
"custom_allowFallback": false,
|
||||
"custom_startWithHrm": true,
|
||||
"custom_allowFallback": true,
|
||||
"custom_warnDisconnect": false,
|
||||
"custom_fallbackTimeout": 10,
|
||||
"gracePeriodNotification": 0,
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
"id": "bthrm",
|
||||
"name": "Bluetooth Heart Rate Monitor",
|
||||
"shortName": "BT HRM",
|
||||
"version": "0.08",
|
||||
"version": "0.09",
|
||||
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
"tags": "health,bluetooth",
|
||||
"tags": "health,bluetooth,hrm,bthrm",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
require('Storage').writeJSON(FILE, s);
|
||||
readSettings();
|
||||
}
|
||||
|
||||
|
||||
function readSettings(){
|
||||
settings = Object.assign(
|
||||
require('Storage').readJSON("bthrm.default.json", true) || {},
|
||||
require('Storage').readJSON(FILE, true) || {}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
var FILE="bthrm.json";
|
||||
var settings;
|
||||
readSettings();
|
||||
|
@ -61,12 +61,13 @@
|
|||
}
|
||||
};
|
||||
|
||||
if (settings.btname){
|
||||
var name = "Clear " + settings.btname;
|
||||
if (settings.btname || settings.btid){
|
||||
var name = "Clear " + (settings.btname || settings.btid);
|
||||
mainmenu[name] = function() {
|
||||
E.showPrompt("Clear current device name?").then((r)=>{
|
||||
E.showPrompt("Clear current device?").then((r)=>{
|
||||
if (r) {
|
||||
writeSettings("btname",undefined);
|
||||
writeSettings("btid",undefined);
|
||||
}
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
|
@ -78,9 +79,7 @@
|
|||
mainmenu.Debug = function() { E.showMenu(submenu_debug); };
|
||||
return mainmenu;
|
||||
}
|
||||
|
||||
|
||||
|
||||
var submenu_debug = {
|
||||
'' : { title: "Debug"},
|
||||
'< Back': function() { E.showMenu(buildMainMenu()); },
|
||||
|
@ -103,35 +102,39 @@
|
|||
|
||||
function createMenuFromScan(){
|
||||
E.showMenu();
|
||||
E.showMessage("Scanning");
|
||||
E.showMessage("Scanning for 4 seconds");
|
||||
|
||||
var submenu_scan = {
|
||||
'' : { title: "Scan"},
|
||||
'< Back': function() { E.showMenu(buildMainMenu()); }
|
||||
};
|
||||
var packets=10;
|
||||
var scanStart=Date.now();
|
||||
NRF.setScan(function(d) {
|
||||
packets--;
|
||||
if (packets<=0 || Date.now() - scanStart > 5000){
|
||||
NRF.setScan();
|
||||
E.showMenu(submenu_scan);
|
||||
} else if (d.name){
|
||||
print("Found device", d);
|
||||
submenu_scan[d.name] = function(){
|
||||
E.showPrompt("Set "+d.name+"?").then((r)=>{
|
||||
if (r) {
|
||||
writeSettings("btname",d.name);
|
||||
}
|
||||
E.showMenu(buildMainMenu());
|
||||
NRF.findDevices(function(devices) {
|
||||
submenu_scan[''] = { title: `Scan (${devices.length} found)`};
|
||||
if (devices.length === 0) {
|
||||
E.showAlert("No devices found")
|
||||
.then(() => E.showMenu(buildMainMenu()));
|
||||
return;
|
||||
} else {
|
||||
devices.forEach((d) => {
|
||||
print("Found device", d);
|
||||
var shown = (d.name || d.id.substr(0, 17));
|
||||
submenu_scan[shown] = function () {
|
||||
E.showPrompt("Set " + shown + "?").then((r) => {
|
||||
if (r) {
|
||||
writeSettings("btid", d.id);
|
||||
// Store the name for displaying later. Will connect by ID
|
||||
if (d.name) {
|
||||
writeSettings("btname", d.name);
|
||||
}
|
||||
}
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
}, { filters: [{services: [ "180d" ]}]});
|
||||
E.showMenu(submenu_scan);
|
||||
}, { timeout: 4000, active: true, filters: [{services: [ "180d" ]}]});
|
||||
}
|
||||
|
||||
|
||||
|
||||
var submenu_custom = {
|
||||
'' : { title: "Custom mode"},
|
||||
'< Back': function() { E.showMenu(buildMainMenu()); },
|
||||
|
@ -167,7 +170,7 @@
|
|||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
var submenu_grace = {
|
||||
'' : { title: "Grace periods"},
|
||||
'< Back': function() { E.showMenu(submenu_debug); },
|
||||
|
@ -212,51 +215,6 @@
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
var submenu = {
|
||||
'' : { title: "Grace periods"},
|
||||
'< Back': function() { E.showMenu(buildMainMenu()); },
|
||||
'Request': {
|
||||
value: settings.gracePeriodRequest,
|
||||
min: 0,
|
||||
max: 3000,
|
||||
step: 100,
|
||||
format: v=>v+"ms",
|
||||
onchange: v => {
|
||||
writeSettings("gracePeriodRequest",v);
|
||||
}
|
||||
},
|
||||
'Connect': {
|
||||
value: settings.gracePeriodConnect,
|
||||
min: 0,
|
||||
max: 3000,
|
||||
step: 100,
|
||||
format: v=>v+"ms",
|
||||
onchange: v => {
|
||||
writeSettings("gracePeriodConnect",v);
|
||||
}
|
||||
},
|
||||
'Notification': {
|
||||
value: settings.gracePeriodNotification,
|
||||
min: 0,
|
||||
max: 3000,
|
||||
step: 100,
|
||||
format: v=>v+"ms",
|
||||
onchange: v => {
|
||||
writeSettings("gracePeriodNotification",v);
|
||||
}
|
||||
},
|
||||
'Service': {
|
||||
value: settings.gracePeriodService,
|
||||
min: 0,
|
||||
max: 3000,
|
||||
step: 100,
|
||||
format: v=>v+"ms",
|
||||
onchange: v => {
|
||||
writeSettings("gracePeriodService",v);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
E.showMenu(buildMainMenu());
|
||||
})
|
||||
});
|
||||
|
|
|
@ -5,4 +5,5 @@
|
|||
0.05: Included icons for information.
|
||||
0.06: Design and usability improvements.
|
||||
0.07: Improved positioning.
|
||||
0.08: Select the color of widgets correctly. Additional settings to hide colon.
|
||||
0.08: Select the color of widgets correctly. Additional settings to hide colon.
|
||||
0.09: Larger font size if colon is hidden to improve readability further.
|
|
@ -44,6 +44,16 @@ Graphics.prototype.setLargeFont = function(scale) {
|
|||
return this;
|
||||
};
|
||||
|
||||
Graphics.prototype.setXLargeFont = function(scale) {
|
||||
// Actual height 53 (55 - 3)
|
||||
this.setFontCustom(
|
||||
E.toString(require('heatshrink').decompress(atob('AHM/8AIG/+AA4sD/wQGh/4EWQA/AC8YA40HNA0BRY8/RY0P/6LFgf//4iFA4IiFj4HBEQkHCAQiDHIIZGv4HCFQY5BDAo5CAAIpDDAfACA3wLYv//hsFKYxcCMgoiBOooiBQwwiBS40AHIgA/ACS/DLYjYCBAjQEBAYQDBAgHDUAbyDZQi3CegoHEVQQZFagUfW4Y0DaAgECaIJSEFYMPbIYNDv5ACGAIrBCgJ1EFYILCAAQWCj4zDGgILCegcDEQRNDHIIiCHgZ2BEQShFIqUDFYidCh5ODg4NCn40DAgd/AYR5BDILZEAAIMDAAYVCh7aHdYhKDbQg4Dv7rGBAihFCAwIDCAgA/AB3/eoa7GAAk/dgbVGDJrvCDK67DDIjaGdYpbCdYonCcQjjDEVUBEQ4A/AEMcAYV/NAUHcYUDawd/cYUPRYSmBBgaLBToP8BgYiBSgIiCj4iCg//EQSuDW4IMDVwYiCBgIiBBgrRDCATeBaIYqCv70DCgT4CEQMfIgQZBBoRnDv/3EQIvBDIffEQMHFwReBRYUfOgX/+IiDKIeHEQRRECwUHKwIuB8AiDIoJEBCwZFCv/4HIZaBIgPAEQS2CUYQiCD4SABEQcfOwIZBEQaHBO4RcEAAI/BEQQgBSIQiDTIRZBEQZuBVYQiDHoKWCEQQICFQIiDBAQeCEQQA/AANwA40BLIJ5BO4JWCBAUPAYR5En7RBUIQECN4SYCQQIiEh6CCEQk/BoQiBgYeCBoTrCAgT0CCgIfCFYQiBg4IBGgIiDj6rBg4rCBYLRDFYIiBbYIfBLgQiBIQYiD4JCCLgf/bQIWDBYV/EQV/BYXz/5FBgIiD5//IowZBD4M/NAX/BIPgDIJoC//5GgKUDn//4f/8KLE/wTBAAI8BEQPwj4HBVwYmBDgIZDN4QZCGYKJCHQP/JoSgCBATrCh5dBKITVDG4gICAAbvDAH5SCL4QADK4J5CCAiTCCAp1BCAqCDCAgiGCAIiFCAQiFeoIiFg6/FCAgiECAXnEQgQB/kfEQYQC4F/EQYQCgIiDfoIQBg4iDCAUAEQZUCcgIiDDIIQBEQhuBBoIiENoYiFDwQiECAQiFwEBPQQNCAQKDDEYMDDoMfRh4iGUwqvEESBiBaQ5oEbgr0FNAo+EEIwA+oAHGgJoFRAMHe4L0CAALNBBAT0BfwScDCAXweAL0DWgUPQYQiDwF/QYQiC/zTB+C0FBAL0CEQYIBGgMPCgIxBg4rCJIKsCh5IBBwTPCj4WBgYLBZ4V/MAIiBBQQrBEQYtCBYQiCO4QLFCwgiDIQIiGIoMHEQpFBn5FFD4JoENwRoGDgSUCAoKfBw//DgIiCT4auCFwN/T4RRET4TaCEQKoCDIQiCGgK/DAAQICdYQACHoIqCBAoQFEwIhFAH4AFQIROEj4IGXwIIGNwIACbgIhEBAiRCVwoqDTogHEW4QZFXgIZB/z9Cv49CF4MPBwI0Ca4LlB8ATCJoP4AoINDfQPAg7PBg4cBBwUfD4MfFYILCCwgOCf4QLEwEPCwILCgJaBn4WBBYQxCIQQiD+EDCYI5CBYRQBIo4fBMQIuBC4N/NAv8AoIcBSgU/FYIIBZIYrCW4hOCXIQZCgYUBv7jEh4uBZAscewZ8CgEgUYT0EEoQIBA4gICFQQIEHYQA+KQzdDAArdCAArpCEScHaIQiEvwiGe4QiFUwQiEbgIiFYIL0DEQTkBEQrJEEQc/cYYiCg4HBDIQiCfoRoEHQLaDEQQHBbQYiBCAT8Dn/BCAoXBJYP/OgZKC/6OEEARLCEQZLEEQZLEEQjKFEQI6EEQZLDEQbsGEQLjGYYYA/JIxzEg/AfgJSDAoPgfgiDC8COFAoPnaQj6CAAR+CW4TCFA4i6CDIqhCDIfwHoYHCYIN/GgKuBJ4JDBFYUf/C5CBYIZBv/Ag4ZBg4rBBYQTBAQIcBg4FBn5UBAQUfFwIfCEQeAgYfBAQUBFAKbCAQQiCGwIiE+A2BwBFNwE/AoM/EQJoIWwKCCh4cBFYKUERYV/W46uHFYIZGaJA0B/glBGYT0JIITiEMIJvCFQQAEHYQA/ABBlEOIhdGQAIRFSgQIBgQICn4IB8EAjiBCUYglCbQYeBEoQZCTwM/CYIZD/gEBUwIzBJ4UHYAU/EwIrBh4rCAoIXCn4rBCgUDAQN/FYMfBYIXBCYJnCBYXggf8HgQLCwEPEQQuBgJOECwILDCwgiLHIUHBYJFGD4IxBgYWCn4rBBwJoFDIYNBCgPADgKHBRYfDBQN/GAIrBToTLDVwYACDILiCWAb8DAAYzBYAjTCAAI9BAARNCBAoqCBAgQDFgbYCAH4AufgQACf4T8CAAT/CfgQACBwITCAAYOBCYQioh4iEAHQA=='))),
|
||||
46,
|
||||
atob("FR4uHyopKyksJSssGA=="),
|
||||
70+(scale<<8)+(1<<16)
|
||||
);
|
||||
};
|
||||
|
||||
Graphics.prototype.setMediumFont = function(scale) {
|
||||
// Actual height 41 (42 - 2)
|
||||
this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAB/AAAAAAAP/AAAAAAD//AAAAAA///AAAAAP///AAAAB///8AAAAf///AAAAH///wAAAB///+AAAAH///gAAAAH//4AAAAAH/+AAAAAAH/wAAAAAAH8AAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAH////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gAAH+AAD+AAAD/AAH8AAAB/AAH4AAAA/gAH4AAAAfgAH4AAAAfgAPwAAAAfgAPwAAAAfgAPwAAAAfgAHwAAAAfgAH4AAAAfgAH4AAAA/gAH8AAAA/AAD+AAAD/AAD/gAAH/AAB/////+AAB/////8AAA/////4AAAf////wAAAH////gAAAB///+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAAfwAAAAAAA/gAAAAAAA/AAAAAAAB/AAAAAAAD+AAAAAAAD8AAAAAAAH8AAAAAAAH//////AAH//////AAH//////AAH//////AAH//////AAH//////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4AAA/AAAP4AAB/AAAf4AAD/AAA/4AAD/AAB/4AAH/AAD/4AAP/AAH/AAAf/AAH8AAA//AAH4AAB//AAP4AAD//AAPwAAH+/AAPwAAP8/AAPwAAf4/AAPwAA/4/AAPwAA/w/AAPwAB/g/AAPwAD/A/AAP4AH+A/AAH8AP8A/AAH/A/4A/AAD///wA/AAD///gA/AAB///AA/AAA//+AA/AAAP/8AA/AAAD/wAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAH4AAAHwAAH4AAAH4AAH4AAAH8AAH4AAAP+AAH4AAAH+AAH4A4AB/AAH4A+AA/AAH4B/AA/gAH4D/AAfgAH4H+AAfgAH4P+AAfgAH4f+AAfgAH4/+AAfgAH5/+AAfgAH5//AAfgAH7+/AA/gAH/8/gB/AAH/4f4H/AAH/wf//+AAH/gP//8AAH/AH//8AAH+AD//wAAH8AB//gAAD4AAf+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAAAAD/AAAAAAAP/AAAAAAB//AAAAAAH//AAAAAAf//AAAAAB///AAAAAH///AAAAAf/8/AAAAB//w/AAAAH/+A/AAAA//4A/AAAD//gA/AAAH/+AA/AAAH/4AA/AAAH/gAA/AAAH+AAA/AAAHwAAA/AAAHAAf///AAEAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAP/AHgAAH///AP4AAH///gP8AAH///gP8AAH///gP+AAH///gD/AAH/A/AB/AAH4A/AA/gAH4A+AAfgAH4B+AAfgAH4B+AAfgAH4B8AAfgAH4B8AAfgAH4B+AAfgAH4B+AAfgAH4B+AA/gAH4B/AA/AAH4A/gD/AAH4A/4H+AAH4Af//+AAH4AP//8AAH4AP//4AAHwAD//wAAAAAB//AAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAD////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gP4H+AAD/AfgD/AAH8A/AB/AAH8A/AA/gAH4B+AAfgAH4B+AAfgAPwB8AAfgAPwB8AAfgAPwB+AAfgAPwB+AAfgAH4B+AAfgAH4B/AA/gAH8B/AB/AAH+A/wD/AAD+A/8P+AAB8Af//+AAB4AP//8AAAwAH//4AAAAAD//gAAAAAA//AAAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAHAAPwAAAA/AAPwAAAD/AAPwAAAf/AAPwAAB//AAPwAAP//AAPwAA//8AAPwAH//wAAPwAf/+AAAPwB//4AAAPwP//AAAAPw//8AAAAP3//gAAAAP//+AAAAAP//wAAAAAP//AAAAAAP/4AAAAAAP/gAAAAAAP+AAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAH+A//gAAAf/h//4AAA//z//8AAB/////+AAD/////+AAD///+H/AAH+H/4B/AAH8B/wA/gAH4A/gAfgAH4A/gAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAH4A/gAfgAH4A/gAfgAH8B/wA/gAH/H/4B/AAD///+H/AAD/////+AAB/////+AAA//z//8AAAf/h//4AAAH+A//gAAAAAAH+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAD/8AAAAAAP/+AAAAAAf//AAcAAA///gA8AAB///wB+AAD/x/4B/AAD+AP4B/AAH8AH8A/gAH4AH8A/gAH4AD8AfgAP4AD8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAH4AD8AfgAH4AD4A/gAH8AH4B/AAD+APwD/AAD/g/wP+AAB/////+AAA/////8AAAf////4AAAP////wAAAH////AAAAA///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DxcjFyAfISAiHCAiEg=="), 54+(scale<<8)+(1<<16));
|
||||
|
@ -304,17 +314,13 @@ function drawTime(){
|
|||
g.setColor(g.theme.bg);
|
||||
g.setFontAlign(0,0);
|
||||
|
||||
var timeStr;
|
||||
if(settings.hideColon){
|
||||
var hours = date.getHours();
|
||||
hours -= hours >=12 ? 12 : 0;
|
||||
var minutes = date.getMinutes();
|
||||
minutes = minutes < 10 ? String("0") + minutes : minutes;
|
||||
timeStr = String(hours) + minutes;
|
||||
} else {
|
||||
timeStr = locale.time(date,1);
|
||||
}
|
||||
var hours = String(date.getHours());
|
||||
var minutes = date.getMinutes();
|
||||
minutes = minutes < 10 ? String("0") + minutes : minutes;
|
||||
var colon = settings.hideColon ? "" : ":";
|
||||
var timeStr = hours + colon + minutes;
|
||||
|
||||
// Set y coordinates correctly
|
||||
y += parseInt((H - y)/2) + 5;
|
||||
|
||||
var infoEntry = getInfoEntry();
|
||||
|
@ -324,7 +330,11 @@ function drawTime(){
|
|||
|
||||
// Show large or small time depending on info entry
|
||||
if(infoStr == null){
|
||||
g.setLargeFont();
|
||||
if(settings.hideColon){
|
||||
g.setXLargeFont();
|
||||
} else {
|
||||
g.setLargeFont();
|
||||
}
|
||||
} else {
|
||||
y -= 15;
|
||||
g.setMediumFont();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "bwclk",
|
||||
"name": "BW Clock",
|
||||
"version": "0.08",
|
||||
"version": "0.09",
|
||||
"description": "BW Clock.",
|
||||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
|
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.1 KiB |
|
@ -1,2 +1,4 @@
|
|||
0.01: New Clock Nifty A
|
||||
0.02: Shows the current week number (ISO8601), can be disabled via settings ""
|
||||
0.02: Shows the current week number (ISO8601), can be disabled via settings
|
||||
0.03: Call setUI before loading widgets
|
||||
Improve settings page
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
# Nifty-A Clock
|
||||
|
||||
Colors are black/white - photos have non correct camera color "blue"
|
||||
Colors are black/white - photos have non correct camera color "blue".
|
||||
|
||||
## This is the clock
|
||||
This is the clock:
|
||||
|
||||

|
||||
|
||||
## The week number (ISO8601) can be turned of in settings
|
||||
(default is **"On"**)
|
||||
The week number (ISO8601) can be turned off in settings (default is `On`)
|
||||
|
||||

|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const locale = require("locale");
|
||||
const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
|
||||
const CFG = require('Storage').readJSON("ffcniftya.json", 1) || {showWeekNum: true};
|
||||
const is12Hour = Object.assign({ "12hour": false }, require("Storage").readJSON("setting.json", true))["12hour"];
|
||||
const showWeekNum = Object.assign({ showWeekNum: true }, require('Storage').readJSON("ffcniftya.json", true))["showWeekNum"];
|
||||
|
||||
/* Clock *********************************************/
|
||||
const scale = g.getWidth() / 176;
|
||||
|
@ -17,16 +17,17 @@ const center = {
|
|||
y: Math.round(((viewport.height - widget) / 2) + widget),
|
||||
}
|
||||
|
||||
function ISO8601_week_no(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480
|
||||
var tdt = new Date(date.valueOf());
|
||||
var dayn = (date.getDay() + 6) % 7;
|
||||
tdt.setDate(tdt.getDate() - dayn + 3);
|
||||
var firstThursday = tdt.valueOf();
|
||||
tdt.setMonth(0, 1);
|
||||
if (tdt.getDay() !== 4) {
|
||||
tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7);
|
||||
}
|
||||
return 1 + Math.ceil((firstThursday - tdt) / 604800000);
|
||||
// copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480
|
||||
function ISO8601_week_no(date) {
|
||||
var tdt = new Date(date.valueOf());
|
||||
var dayn = (date.getDay() + 6) % 7;
|
||||
tdt.setDate(tdt.getDate() - dayn + 3);
|
||||
var firstThursday = tdt.valueOf();
|
||||
tdt.setMonth(0, 1);
|
||||
if (tdt.getDay() !== 4) {
|
||||
tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7);
|
||||
}
|
||||
return 1 + Math.ceil((firstThursday - tdt) / 604800000);
|
||||
}
|
||||
|
||||
function d02(value) {
|
||||
|
@ -59,7 +60,7 @@ function draw() {
|
|||
g.drawString(year, centerDatesScaleX, center.y - 62 * scale);
|
||||
g.drawString(month, centerDatesScaleX, center.y - 44 * scale);
|
||||
g.drawString(day, centerDatesScaleX, center.y - 26 * scale);
|
||||
if (CFG.showWeekNum) g.drawString(d02(ISO8601_week_no(now)), centerDatesScaleX, center.y + 15 * scale);
|
||||
if (showWeekNum) g.drawString(weekNum, centerDatesScaleX, center.y + 15 * scale);
|
||||
g.drawString(monthName, centerDatesScaleX, center.y + 48 * scale);
|
||||
g.drawString(dayName, centerDatesScaleX, center.y + 66 * scale);
|
||||
}
|
||||
|
@ -79,7 +80,6 @@ function clearTickTimer() {
|
|||
function queueNextTick() {
|
||||
clearTickTimer();
|
||||
tickTimer = setTimeout(tick, 60000 - (Date.now() % 60000));
|
||||
// tickTimer = setTimeout(tick, 3000);
|
||||
}
|
||||
|
||||
function tick() {
|
||||
|
@ -91,21 +91,16 @@ function tick() {
|
|||
|
||||
// Clear the screen once, at startup
|
||||
g.clear();
|
||||
// Start ticking
|
||||
tick();
|
||||
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower', (on) => {
|
||||
if (on) {
|
||||
tick(); // Start ticking
|
||||
tick();
|
||||
} else {
|
||||
clearTickTimer(); // stop ticking
|
||||
clearTickTimer();
|
||||
}
|
||||
});
|
||||
|
||||
// Load widgets
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "ffcniftya",
|
||||
"name": "Nifty-A Clock",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "A nifty clock with time and date",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot_nifty.png"}],
|
||||
|
|
|
@ -1,23 +1,15 @@
|
|||
(function(back) {
|
||||
var FILE = "ffcniftya.json";
|
||||
// Load settings
|
||||
var cfg = require('Storage').readJSON(FILE, 1) || { showWeekNum: true };
|
||||
(function (back) {
|
||||
const settings = Object.assign({ showWeekNum: true }, require("Storage").readJSON("ffcniftya.json", true));
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, cfg);
|
||||
}
|
||||
|
||||
// Show the menu
|
||||
E.showMenu({
|
||||
"" : { "title" : "Nifty-A Clock" },
|
||||
"< Back" : () => back(),
|
||||
'week number?': {
|
||||
value: cfg.showWeekNum,
|
||||
format: v => v?"On":"Off",
|
||||
"": { "title": "Nifty-A Clock" },
|
||||
"< Back": () => back(),
|
||||
/*LANG*/"Show Week Number": {
|
||||
value: settings.showWeekNum,
|
||||
onchange: v => {
|
||||
cfg.showWeekNum = v;
|
||||
writeSettings();
|
||||
settings.showWeekNum = v;
|
||||
require("Storage").writeJSON("ffcniftya.json", settings);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
0.01: New Clock Nifty B
|
||||
0.02: Added configuration
|
||||
0.02: Added configuration
|
||||
0.03: Call setUI before loading widgets
|
||||
Fix bug with black being unselectable
|
||||
Improve settings page
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
# Nifty Series B Clock
|
||||
|
||||
- Display Time and Date
|
||||
- Color Configuration
|
||||
|
||||
##
|
||||
- Colour Configuration
|
||||
|
||||

|
||||
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
const locale = require("locale");
|
||||
const storage = require('Storage');
|
||||
|
||||
const is12Hour = (storage.readJSON("setting.json", 1) || {})["12hour"];
|
||||
const color = (storage.readJSON("ffcniftyb.json", 1) || {})["color"] || 63488 /* red */;
|
||||
|
||||
const is12Hour = Object.assign({ "12hour": false }, require("Storage").readJSON("setting.json", true))["12hour"];
|
||||
const color = Object.assign({ color: 63488 }, require("Storage").readJSON("ffcniftyb.json", true)).color; // Default to RED
|
||||
|
||||
/* Clock *********************************************/
|
||||
const scale = g.getWidth() / 176;
|
||||
|
@ -19,7 +15,7 @@ const center = {
|
|||
};
|
||||
|
||||
function d02(value) {
|
||||
return ('0' + value).substr(-2);
|
||||
return ("0" + value).substr(-2);
|
||||
}
|
||||
|
||||
function renderEllipse(g) {
|
||||
|
@ -35,8 +31,8 @@ function renderText(g) {
|
|||
const month = d02(now.getMonth() + 1);
|
||||
const year = now.getFullYear();
|
||||
|
||||
const month2 = locale.month(now, 3);
|
||||
const day2 = locale.dow(now, 3);
|
||||
const month2 = require("locale").month(now, 3);
|
||||
const day2 = require("locale").dow(now, 3);
|
||||
|
||||
g.setFontAlign(1, 0).setFont("Vector", 90 * scale);
|
||||
g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale);
|
||||
|
@ -96,7 +92,6 @@ function startTick(run) {
|
|||
stopTick();
|
||||
run();
|
||||
ticker = setTimeout(() => startTick(run), 60000 - (Date.now() % 60000));
|
||||
// ticker = setTimeout(() => startTick(run), 3000);
|
||||
}
|
||||
|
||||
/* Init **********************************************/
|
||||
|
@ -104,7 +99,7 @@ function startTick(run) {
|
|||
g.clear();
|
||||
startTick(draw);
|
||||
|
||||
Bangle.on('lcdPower', (on) => {
|
||||
Bangle.on("lcdPower", (on) => {
|
||||
if (on) {
|
||||
startTick(draw);
|
||||
} else {
|
||||
|
@ -112,7 +107,6 @@ Bangle.on('lcdPower', (on) => {
|
|||
}
|
||||
});
|
||||
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
Bangle.setUI("clock");
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"id": "ffcniftyb",
|
||||
"name": "Nifty-B Clock",
|
||||
"version": "0.02",
|
||||
"description": "A nifty clock (series B) with time, date and color configuration",
|
||||
"version": "0.03",
|
||||
"description": "A nifty clock (series B) with time, date and colour configuration",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "clock",
|
||||
|
|
|
@ -1,49 +1,31 @@
|
|||
(function (back) {
|
||||
const storage = require('Storage');
|
||||
const SETTINGS_FILE = "ffcniftyb.json";
|
||||
const settings = Object.assign({ color: 63488 }, require("Storage").readJSON("ffcniftyb.json", true));
|
||||
|
||||
const colors = {
|
||||
65535: 'White',
|
||||
63488: 'Red',
|
||||
65504: 'Yellow',
|
||||
2047: 'Cyan',
|
||||
2016: 'Green',
|
||||
31: 'Blue',
|
||||
0: 'Black',
|
||||
65535: /*LANG*/"White",
|
||||
63488: /*LANG*/"Red",
|
||||
65504: /*LANG*/"Yellow",
|
||||
2047: /*LANG*/"Cyan",
|
||||
2016: /*LANG*/"Green",
|
||||
31: /*LANG*/"Blue",
|
||||
0: /*LANG*/"Black"
|
||||
}
|
||||
|
||||
function load(settings) {
|
||||
return Object.assign(settings, storage.readJSON(SETTINGS_FILE, 1) || {});
|
||||
}
|
||||
const menu = {};
|
||||
menu[""] = { title: "Nifty-B Clock" };
|
||||
menu["< Back"] = back;
|
||||
|
||||
function save(settings) {
|
||||
storage.write(SETTINGS_FILE, settings)
|
||||
}
|
||||
|
||||
const settings = load({
|
||||
color: 63488 /* red */,
|
||||
Object.keys(colors).forEach(color => {
|
||||
var label = colors[color];
|
||||
menu[label] = {
|
||||
value: settings.color == color,
|
||||
onchange: () => {
|
||||
settings.color = color;
|
||||
require("Storage").write("ffcniftyb.json", settings);
|
||||
setTimeout(load, 10);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const saveColor = (color) => () => {
|
||||
settings.color = color;
|
||||
save(settings);
|
||||
back();
|
||||
};
|
||||
|
||||
function showMenu(items, opt) {
|
||||
items[''] = opt || {};
|
||||
items['< Back'] = back;
|
||||
E.showMenu(items);
|
||||
}
|
||||
|
||||
showMenu(
|
||||
Object.keys(colors).reduce((menu, color) => {
|
||||
menu[colors[color]] = saveColor(color);
|
||||
return menu;
|
||||
}, {}),
|
||||
{
|
||||
title: 'Color',
|
||||
selected: Object.keys(colors).indexOf(settings.color)
|
||||
}
|
||||
);
|
||||
E.showMenu(menu);
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Logs health data to a file every 10 minutes, and provides an app to view it
|
||||
|
||||
**BETA - requires firmware 2v11**
|
||||
**BETA - requires firmware 2v11 or later**
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
0.01: Initial release
|
||||
0.02: implemented "direct launch" and "one click exit" settings
|
|
@ -0,0 +1,12 @@
|
|||
# Icon launcher
|
||||
|
||||
A launcher inspired by smartphones, with an icon-only scrollable menu.
|
||||
|
||||
This launcher shows 9 apps per screen, making it much faster to navigate versus the default launcher.
|
||||
|
||||

|
||||

|
||||
|
||||
## Technical note
|
||||
|
||||
The app uses `E.showScroller`'s code in the app but not the function itself because `E.showScroller` doesn't report the position of a press to the select function.
|
|
@ -0,0 +1,209 @@
|
|||
const s = require("Storage");
|
||||
const settings = s.readJSON("launch.json", true) || { showClocks: true, fullscreen: false,direct:false,oneClickExit:false };
|
||||
|
||||
if( settings.oneClickExit)
|
||||
setWatch(_=> load(), BTN1);
|
||||
|
||||
if (!settings.fullscreen) {
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
||||
var apps = s
|
||||
.list(/\.info$/)
|
||||
.map((app) => {
|
||||
var a = s.readJSON(app, 1);
|
||||
return (
|
||||
a && {
|
||||
name: a.name,
|
||||
type: a.type,
|
||||
icon: a.icon,
|
||||
sortorder: a.sortorder,
|
||||
src: a.src,
|
||||
}
|
||||
);
|
||||
})
|
||||
.filter(
|
||||
(app) =>
|
||||
app &&
|
||||
(app.type == "app" ||
|
||||
(app.type == "clock" && settings.showClocks) ||
|
||||
!app.type)
|
||||
);
|
||||
apps.sort((a, b) => {
|
||||
var n = (0 | a.sortorder) - (0 | b.sortorder);
|
||||
if (n) return n; // do sortorder first
|
||||
if (a.name < b.name) return -1;
|
||||
if (a.name > b.name) return 1;
|
||||
return 0;
|
||||
});
|
||||
apps.forEach((app) => {
|
||||
if (app.icon) app.icon = s.read(app.icon); // should just be a link to a memory area
|
||||
});
|
||||
|
||||
let scroll = 0;
|
||||
let selectedItem = -1;
|
||||
const R = Bangle.appRect;
|
||||
|
||||
const iconSize = 48;
|
||||
|
||||
const appsN = Math.floor(R.w / iconSize);
|
||||
const whitespace = (R.w - appsN * iconSize) / (appsN + 1);
|
||||
|
||||
const itemSize = iconSize + whitespace;
|
||||
|
||||
function drawItem(itemI, r) {
|
||||
g.clearRect(r.x, r.y, r.x + r.w - 1, r.y + r.h - 1);
|
||||
let x = 0;
|
||||
for (let i = itemI * appsN; i < appsN * (itemI + 1); i++) {
|
||||
if (!apps[i]) break;
|
||||
x += whitespace;
|
||||
if (!apps[i].icon) {
|
||||
g.setFontAlign(0,0,0).setFont("12x20:2").drawString("?", x + r.x+iconSize/2, r.y + iconSize/2);
|
||||
} else {
|
||||
g.drawImage(apps[i].icon, x + r.x, r.y);
|
||||
}
|
||||
if (selectedItem == i) {
|
||||
g.drawRect(
|
||||
x + r.x - 1,
|
||||
r.y - 1,
|
||||
x + r.x + iconSize + 1,
|
||||
r.y + iconSize + 1
|
||||
);
|
||||
}
|
||||
x += iconSize;
|
||||
}
|
||||
drawText(itemI);
|
||||
}
|
||||
|
||||
function drawItemAuto(i) {
|
||||
var y = idxToY(i);
|
||||
g.reset().setClipRect(R.x, y, R.x2, y + itemSize);
|
||||
drawItem(i, {
|
||||
x: R.x,
|
||||
y: y,
|
||||
w: R.w,
|
||||
h: itemSize
|
||||
});
|
||||
g.setClipRect(0, 0, g.getWidth() - 1, g.getHeight() - 1);
|
||||
}
|
||||
|
||||
let lastIsDown = false;
|
||||
|
||||
function drawText(i) {
|
||||
const selectedApp = apps[selectedItem];
|
||||
const idy = (selectedItem - (selectedItem % 3)) / 3;
|
||||
if (!selectedApp || i != idy) return;
|
||||
const appY = idxToY(idy) + iconSize / 2;
|
||||
g.setFontAlign(0, 0, 0);
|
||||
g.setFont("12x20");
|
||||
const rect = g.stringMetrics(selectedApp.name);
|
||||
g.clearRect(
|
||||
R.w / 2 - rect.width / 2,
|
||||
appY - rect.height / 2,
|
||||
R.w / 2 + rect.width / 2,
|
||||
appY + rect.height / 2
|
||||
);
|
||||
g.drawString(selectedApp.name, R.w / 2, appY);
|
||||
}
|
||||
|
||||
function selectItem(id, e) {
|
||||
const iconN = E.clip(Math.floor((e.x - R.x) / itemSize), 0, appsN - 1);
|
||||
const appId = id * appsN + iconN;
|
||||
if( settings.direct && apps[appId])
|
||||
{
|
||||
load(apps[appId].src);
|
||||
return;
|
||||
}
|
||||
if (appId == selectedItem && apps[appId]) {
|
||||
const app = apps[appId];
|
||||
if (!app.src || s.read(app.src) === undefined) {
|
||||
E.showMessage( /*LANG*/ "App Source\nNot found");
|
||||
} else {
|
||||
load(app.src);
|
||||
}
|
||||
}
|
||||
selectedItem = appId;
|
||||
drawItems();
|
||||
}
|
||||
|
||||
function idxToY(i) {
|
||||
return i * itemSize + R.y - (scroll & ~1);
|
||||
}
|
||||
|
||||
function YtoIdx(y) {
|
||||
return Math.floor((y + (scroll & ~1) - R.y) / itemSize);
|
||||
}
|
||||
|
||||
function drawItems() {
|
||||
g.reset().clearRect(R.x, R.y, R.x2, R.y2);
|
||||
g.setClipRect(R.x, R.y, R.x2, R.y2);
|
||||
var a = YtoIdx(R.y);
|
||||
var b = Math.min(YtoIdx(R.y2), 99);
|
||||
for (var i = a; i <= b; i++)
|
||||
drawItem(i, {
|
||||
x: R.x,
|
||||
y: idxToY(i),
|
||||
w: R.w,
|
||||
h: itemSize,
|
||||
});
|
||||
g.setClipRect(0, 0, g.getWidth() - 1, g.getHeight() - 1);
|
||||
}
|
||||
|
||||
drawItems();
|
||||
g.flip();
|
||||
|
||||
const itemsN = Math.ceil(apps.length / appsN);
|
||||
|
||||
Bangle.setUI({
|
||||
mode: "custom",
|
||||
drag: (e) => {
|
||||
let dy = e.dy;
|
||||
if (scroll + R.h - dy > itemsN * itemSize) {
|
||||
dy = scroll + R.h - itemsN * itemSize;
|
||||
}
|
||||
if (scroll - dy < 0) {
|
||||
dy = scroll;
|
||||
}
|
||||
scroll -= dy;
|
||||
scroll = E.clip(scroll, 0, itemSize * (itemsN - 1));
|
||||
g.setClipRect(R.x, R.y, R.x2, R.y2);
|
||||
g.scroll(0, dy);
|
||||
if (dy < 0) {
|
||||
g.setClipRect(R.x, R.y2 - (1 - dy), R.x2, R.y2);
|
||||
let i = YtoIdx(R.y2 - (1 - dy));
|
||||
let y = idxToY(i);
|
||||
while (y < R.y2) {
|
||||
drawItem(i, {
|
||||
x: R.x,
|
||||
y: y,
|
||||
w: R.w,
|
||||
h: itemSize,
|
||||
});
|
||||
i++;
|
||||
y += itemSize;
|
||||
}
|
||||
} else {
|
||||
// d>0
|
||||
g.setClipRect(R.x, R.y, R.x2, R.y + dy);
|
||||
let i = YtoIdx(R.y + dy);
|
||||
let y = idxToY(i);
|
||||
while (y > R.y - itemSize) {
|
||||
drawItem(i, {
|
||||
x: R.x,
|
||||
y: y,
|
||||
w: R.w,
|
||||
h: itemSize,
|
||||
});
|
||||
y -= itemSize;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
g.setClipRect(0, 0, g.getWidth() - 1, g.getHeight() - 1);
|
||||
},
|
||||
touch: (_, e) => {
|
||||
if (e.y < R.y - 4) return;
|
||||
var i = YtoIdx(e.y);
|
||||
selectItem(i, e);
|
||||
},
|
||||
});
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"id": "iconlaunch",
|
||||
"name": "Icon Launcher",
|
||||
"shortName" : "Icon launcher",
|
||||
"version": "0.02",
|
||||
"icon": "app.png",
|
||||
"description": "A launcher inspired by smartphones, with an icon-only scrollable menu.",
|
||||
"tags": "tool,system,launcher",
|
||||
"type": "launch",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{ "name": "iconlaunch.app.js", "url": "app.js" },
|
||||
{ "name": "iconlaunch.settings.js", "url": "settings.js" }
|
||||
],
|
||||
"screenshots": [{ "url": "screenshot1.png" }, { "url": "screenshot2.png" }],
|
||||
"readme": "README.md",
|
||||
"sortorder": -10
|
||||
}
|
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 4.6 KiB |
|
@ -0,0 +1,38 @@
|
|||
// make sure to enclose the function in parentheses
|
||||
(function(back) {
|
||||
let settings = Object.assign({
|
||||
showClocks: true,
|
||||
fullscreen: false
|
||||
}, require("Storage").readJSON("launch.json", true) || {});
|
||||
|
||||
let fonts = g.getFonts();
|
||||
function save(key, value) {
|
||||
settings[key] = value;
|
||||
require("Storage").write("launch.json",settings);
|
||||
}
|
||||
const appMenu = {
|
||||
"": { "title": /*LANG*/"Launcher" },
|
||||
/*LANG*/"< Back": back,
|
||||
/*LANG*/"Show Clocks": {
|
||||
value: settings.showClocks == true,
|
||||
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
|
||||
onchange: (m) => { save("showClocks", m) }
|
||||
},
|
||||
/*LANG*/"Fullscreen": {
|
||||
value: settings.fullscreen == true,
|
||||
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
|
||||
onchange: (m) => { save("fullscreen", m) }
|
||||
},
|
||||
/*LANG*/"Direct launch": {
|
||||
value: settings.direct == true,
|
||||
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
|
||||
onchange: (m) => { save("direct", m) }
|
||||
},
|
||||
/*LANG*/"One click exit": {
|
||||
value: settings.oneClickExit == true,
|
||||
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
|
||||
onchange: (m) => { save("oneClickExit", m) }
|
||||
}
|
||||
};
|
||||
E.showMenu(appMenu);
|
||||
});
|
|
@ -1 +1,2 @@
|
|||
0.01: Initial version
|
||||
0.01: Initial version
|
||||
0.02: Update for time_utils module
|
||||
|
|
|
@ -73,7 +73,7 @@ function showAlarm(alarm) {
|
|||
const settings = require("sched").getSettings();
|
||||
|
||||
let msg = "";
|
||||
msg += require("sched").formatTime(alarm.timer);
|
||||
if (alarm.timer) msg += require("time_utils").formatTime(alarm.timer);
|
||||
if (alarm.msg) {
|
||||
msg += "\n"+alarm.msg;
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ function showAlarm(alarm) {
|
|||
|
||||
if (alarm.data.hm && alarm.data.hm == true) {
|
||||
//hard mode extends auto-snooze time
|
||||
buzzCount = buzzCount * 2;
|
||||
buzzCount = buzzCount * 3;
|
||||
startHM();
|
||||
}
|
||||
|
||||
|
|
|
@ -258,7 +258,7 @@ function editTimer(idx, a) {
|
|||
a.last = 0;
|
||||
a.data.ot = a.timer;
|
||||
a.appid = "multitimer";
|
||||
a.js = "load('multitimer.alarm.js')";
|
||||
a.js = "(require('Storage').read('multitimer.alarm.js') !== undefined) ? load('multitimer.alarm.js') : load('sched.js')";
|
||||
if (idx < 0) alarms.push(a);
|
||||
else alarms[timerIdx[idx]] = a;
|
||||
require("sched").setAlarms(alarms);
|
||||
|
@ -585,7 +585,7 @@ function editAlarm(idx, a) {
|
|||
var menu = {
|
||||
"": { "title": "Alarm" },
|
||||
"< Back": () => {
|
||||
if (a.data.hm == true) a.js = "load('multitimer.alarm.js')";
|
||||
if (a.data.hm == true) a.js = "(require('Storage').read('multitimer.alarm.js') !== undefined) ? load('multitimer.alarm.js') : load('sched.js')";
|
||||
if (a.data.hm == false && a.js) delete a.js;
|
||||
if (idx >= 0) alarms[alarmIdx[idx]] = a;
|
||||
else alarms.push(a);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "multitimer",
|
||||
"name": "Multi Timer",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "Set timers and chronographs (stopwatches) and watch them count down in real time. Pause, create, edit, and delete timers and chronos, and add custom labels/messages. Also sets alarms.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [
|
||||
|
@ -19,4 +19,4 @@
|
|||
],
|
||||
"data": [{"name":"multitimer.json"}],
|
||||
"dependencies": {"scheduler":"type"}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,4 +4,5 @@
|
|||
0.04: Fixed icon and png to 48x48 pixels
|
||||
0.05: added charging icon
|
||||
0.06: Add 12h support and autocycle control
|
||||
0.07: added localization; removed deprecated code
|
||||
0.07: added localization, removed deprecated code
|
||||
0.08: removed unused font, fix autocycle, imported suncalc and trimmed, removed pedometer dependency, "tap to cycle" setting
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
"id": "rebble",
|
||||
"name": "Rebble Clock",
|
||||
"shortName": "Rebble",
|
||||
"version": "0.07",
|
||||
"version": "0.08",
|
||||
"description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion",
|
||||
"readme": "README.md",
|
||||
"icon": "rebble.png",
|
||||
"dependencies": {"mylocation":"app", "widpedom":"app"},
|
||||
"dependencies": {"mylocation":"app"},
|
||||
"screenshots": [{"url":"screenshot_rebble.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
|
@ -14,6 +14,7 @@
|
|||
"storage": [
|
||||
{"name":"rebble.app.js","url":"rebble.app.js"},
|
||||
{"name":"rebble.settings.js","url":"rebble.settings.js"},
|
||||
{"name":"rebble.img","url":"rebble.icon.js","evaluate":true}
|
||||
{"name":"rebble.img","url":"rebble.icon.js","evaluate":true},
|
||||
{"name":"suncalc","url":"suncalc.js"}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
const SETTINGS_FILE = "rebble.json";
|
||||
|
||||
// initialize with default settings...
|
||||
let localSettings = {'bg': '#0f0', 'color': 'Green', 'autoCycle': true}
|
||||
let localSettings = {'bg': '#0f0', 'color': 'Green', 'autoCycle': true, 'sideTap':0};
|
||||
//sideTap 0 = on| 1= sideBar1 | 2 = ...
|
||||
|
||||
// ...and overwrite them with any saved values
|
||||
// This way saved values are preserved if a new version adds more settings
|
||||
const storage = require('Storage')
|
||||
let settings = storage.readJSON(SETTINGS_FILE, 1) || localSettings;
|
||||
|
||||
const saved = settings || {}
|
||||
for (const key in saved) {
|
||||
localSettings[key] = saved[key]
|
||||
|
@ -21,26 +23,55 @@
|
|||
var color_options = ['Green','Orange','Cyan','Purple','Red','Blue'];
|
||||
var bg_code = ['#0f0','#ff0','#0ff','#f0f','#f00','#00f'];
|
||||
|
||||
E.showMenu({
|
||||
'': { 'title': 'Rebble Clock' },
|
||||
'< Back': back,
|
||||
'Colour': {
|
||||
value: 0 | color_options.indexOf(localSettings.color),
|
||||
min: 0, max: 5,
|
||||
format: v => color_options[v],
|
||||
onchange: v => {
|
||||
localSettings.color = color_options[v];
|
||||
localSettings.bg = bg_code[v];
|
||||
save();
|
||||
function showMenu()
|
||||
{
|
||||
const menu={
|
||||
'': { 'title': 'Rebble Clock' },
|
||||
'< Back': back,
|
||||
'Colour': {
|
||||
value: 0 | color_options.indexOf(localSettings.color),
|
||||
min: 0, max: 5,
|
||||
format: v => color_options[v],
|
||||
onchange: v => {
|
||||
localSettings.color = color_options[v];
|
||||
localSettings.bg = bg_code[v];
|
||||
save();
|
||||
},
|
||||
},
|
||||
},
|
||||
'Auto Cycle': {
|
||||
value: "autoCycle" in localSettings ? localSettings.autoCycle : true,
|
||||
format: () => (localSettings.autoCycle ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
localSettings.autoCycle = !localSettings.autoCycle;
|
||||
save();
|
||||
'Auto Cycle': {
|
||||
value: localSettings.autoCycle,
|
||||
onchange: (v) => {
|
||||
localSettings.autoCycle = v;
|
||||
save();
|
||||
showMenu();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if( !localSettings.autoCycle)
|
||||
{
|
||||
menu['Tap to Cycle']= {
|
||||
value: localSettings.sideTap,
|
||||
min: 0,
|
||||
max: 3,
|
||||
step: 1,
|
||||
format: v => NumberToSideTap(v),
|
||||
onchange: v => {
|
||||
localSettings.sideTap=v
|
||||
save();
|
||||
setTimeout(showMenu, 10);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
})
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function NumberToSideTap(Number)
|
||||
{
|
||||
if(Number==0)
|
||||
return 'on';
|
||||
return Number+"";
|
||||
}
|
||||
|
||||
showMenu();
|
||||
})
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
(c) 2011-2015, Vladimir Agafonkin
|
||||
SunCalc is a JavaScript library for calculating sun/moon position and light phases.
|
||||
https://github.com/mourner/suncalc
|
||||
|
||||
edit for banglejs
|
||||
*/
|
||||
|
||||
(function () { 'use strict';
|
||||
|
||||
// shortcuts for easier to read formulas
|
||||
|
||||
var PI = Math.PI,
|
||||
sin = Math.sin,
|
||||
cos = Math.cos,
|
||||
tan = Math.tan,
|
||||
asin = Math.asin,
|
||||
atan = Math.atan2,
|
||||
acos = Math.acos,
|
||||
rad = PI / 180;
|
||||
|
||||
// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas
|
||||
|
||||
|
||||
// date/time constants and conversions
|
||||
|
||||
var dayMs = 1000 * 60 * 60 * 24,
|
||||
J1970 = 2440588,
|
||||
J2000 = 2451545;
|
||||
|
||||
function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
|
||||
function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); }
|
||||
function toDays(date) { return toJulian(date) - J2000; }
|
||||
|
||||
|
||||
// general calculations for position
|
||||
|
||||
var e = rad * 23.4397; // obliquity of the Earth
|
||||
|
||||
function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }
|
||||
|
||||
|
||||
// general sun calculations
|
||||
|
||||
function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
|
||||
|
||||
function eclipticLongitude(M) {
|
||||
|
||||
var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
|
||||
P = rad * 102.9372; // perihelion of the Earth
|
||||
|
||||
return M + C + P + PI;
|
||||
}
|
||||
|
||||
var SunCalc = {};
|
||||
|
||||
|
||||
// sun times configuration (angle, morning name, evening name)
|
||||
|
||||
var times = SunCalc.times = [
|
||||
[-0.833, 'sunrise', 'sunset' ],
|
||||
[ -0.3, 'sunriseEnd', 'sunsetStart' ],
|
||||
[ -6, 'dawn', 'dusk' ],
|
||||
[ -12, 'nauticalDawn', 'nauticalDusk'],
|
||||
[ -18, 'nightEnd', 'night' ],
|
||||
[ 6, 'goldenHourEnd', 'goldenHour' ]
|
||||
];
|
||||
|
||||
|
||||
|
||||
// calculations for sun times
|
||||
|
||||
var J0 = 0.0009;
|
||||
|
||||
function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); }
|
||||
|
||||
function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; }
|
||||
function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); }
|
||||
|
||||
function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); }
|
||||
function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; }
|
||||
|
||||
// returns set time for the given sun altitude
|
||||
function getSetJ(h, lw, phi, dec, n, M, L) {
|
||||
|
||||
var w = hourAngle(h, phi, dec),
|
||||
a = approxTransit(w, lw, n);
|
||||
return solarTransitJ(a, M, L);
|
||||
}
|
||||
|
||||
|
||||
// calculates sun times for a given date, latitude/longitude, and, optionally,
|
||||
// the observer height (in meters) relative to the horizon
|
||||
|
||||
SunCalc.getTimes = function (date, lat, lng, height) {
|
||||
|
||||
height = height || 0;
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
|
||||
dh = observerAngle(height),
|
||||
|
||||
d = toDays(date),
|
||||
n = julianCycle(d, lw),
|
||||
ds = approxTransit(0, lw, n),
|
||||
|
||||
M = solarMeanAnomaly(ds),
|
||||
L = eclipticLongitude(M),
|
||||
dec = declination(L, 0),
|
||||
|
||||
Jnoon = solarTransitJ(ds, M, L),
|
||||
|
||||
i, len, time, h0, Jset, Jrise;
|
||||
|
||||
|
||||
var result = {
|
||||
solarNoon: fromJulian(Jnoon),
|
||||
nadir: fromJulian(Jnoon - 0.5)
|
||||
};
|
||||
|
||||
for (i = 0, len = times.length; i < len; i += 1) {
|
||||
time = times[i];
|
||||
h0 = (time[0] + dh) * rad;
|
||||
|
||||
Jset = getSetJ(h0, lw, phi, dec, n, M, L);
|
||||
Jrise = Jnoon - (Jset - Jnoon);
|
||||
|
||||
result[time[1]] = fromJulian(Jrise);
|
||||
result[time[2]] = fromJulian(Jset);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
// export as Node module / AMD module / browser variable
|
||||
if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc;
|
||||
else if (typeof define === 'function' && define.amd) define(SunCalc);
|
||||
else window.SunCalc = SunCalc;
|
||||
|
||||
|
||||
}());
|
|
@ -12,3 +12,4 @@
|
|||
0.11: Notifications fixes
|
||||
0.12: Fix for recorder not stopping at end of run. Bug introduced in 0.11
|
||||
0.13: Revert #1578 (stop duplicate entries) as with 2v12 menus it causes other boxes to be wiped (fix #1643)
|
||||
0.14: Fix Bangle.js 1 issue where after the 'overwrite track' menu, the start/stop button stopped working
|
||||
|
|
|
@ -55,6 +55,7 @@ function onStartStop() {
|
|||
prepPromises.push(
|
||||
WIDGETS["recorder"].setRecording(true).then(() => {
|
||||
isMenuDisplayed = false;
|
||||
layout.setUI(); // grab our input handling again
|
||||
layout.forgetLazyState();
|
||||
layout.render();
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "run",
|
||||
"name": "Run",
|
||||
"version":"0.13",
|
||||
"version":"0.14",
|
||||
"description": "Displays distance, time, steps, cadence, pace and more for runners.",
|
||||
"icon": "app.png",
|
||||
"tags": "run,running,fitness,outdoors,gps",
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
will then clearInterval() to get rid of this call so it can proceed
|
||||
normally.
|
||||
If active[0].js is defined, just run that code as-is and not alarm.js */
|
||||
Bangle.SCHED = setTimeout(require("Storage").read(active[0].js)!==undefined ? active[0].js : 'load("sched.js")',t);
|
||||
Bangle.SCHED = setTimeout(active[0].js||'load("sched.js")',t);
|
||||
} else { // check for new alarms at midnight (so day of week works)
|
||||
Bangle.SCHED = setTimeout('eval(require("Storage").read("sched.boot.js"))', 86400000 - (Date.now()%86400000));
|
||||
}
|
||||
|
|
|
@ -48,3 +48,4 @@
|
|||
0.43: Add some Bangle 1 colours to theme customizer
|
||||
0.44: Add "Start Week On X" option (#1780)
|
||||
UI improvements to Locale and Date & Time menu
|
||||
0.45: Add calibrate battery option
|
||||
|
|
|
@ -13,7 +13,6 @@ This is Bangle.js's settings menu
|
|||
* **LCD** Configure settings about the screen. How long it stays on, how bright it is, and when it turns on - see below.
|
||||
* **Theme** Adjust the colour scheme
|
||||
* **Utils** Utilities - including resetting settings (see below)
|
||||
* **Turn Off** Turn Bangle.js off
|
||||
|
||||
## BLE - Bluetooth Settings
|
||||
|
||||
|
@ -61,5 +60,7 @@ The exact effects depend on the app. In general the watch will not wake up by i
|
|||
* **Compact Storage** Removes deleted/old files from Storage - this will speed up your Bangle.js
|
||||
* **Rewrite Settings** Should not normally be required, but if `.boot0` has been deleted/corrupted (and so no settings are being loaded) this will fix it.
|
||||
* **Flatten Battery** Turns on all devices and draws as much power as possible, attempting to flatten the Bangle.js battery. This can still take 5+ hours.
|
||||
* **Calibrate Battery** If you're finding your battery percentage meter isn't accurate, leave your Bangle.js on charge for at least 3 hours, and then choose this menu option. It will measure the battery voltage when full and will allow Bangle.js to report a more accurate battery percentage.
|
||||
* **Reset Settings** Reset the settings (as set in this app) to defaults. Does not reset settings for other apps.
|
||||
* **Factory Reset** (not available on Bangle.js 1) - wipe **everything** and return to a factory state
|
||||
* **Turn Off** Turn Bangle.js off
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "setting",
|
||||
"name": "Settings",
|
||||
"version": "0.44",
|
||||
"version": "0.45",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"icon": "settings.png",
|
||||
"tags": "tool,system",
|
||||
|
|
|
@ -546,6 +546,18 @@ function showUtilMenu() {
|
|||
var i=1000;while (i--);
|
||||
}, 1);
|
||||
},
|
||||
/*LANG*/'Calibrate Battery': () => {
|
||||
E.showPrompt(/*LANG*/"Is the battery fully charged?",{title:/*LANG*/"Calibrate"}).then(ok => {
|
||||
if (ok) {
|
||||
var s=require("Storage").readJSON("setting.json");
|
||||
s.batFullVoltage = (analogRead(D3)+analogRead(D3)+analogRead(D3)+analogRead(D3))/4;
|
||||
require("Storage").writeJSON("setting.json",s);
|
||||
E.showAlert(/*LANG*/"Calibrated!").then(() => load("settings.app.js"));
|
||||
} else {
|
||||
E.showAlert(/*LANG*/"Please charge Bangle.js for 3 hours and try again").then(() => load("settings.app.js"));
|
||||
}
|
||||
});
|
||||
},
|
||||
/*LANG*/'Reset Settings': () => {
|
||||
E.showPrompt(/*LANG*/'Reset to Defaults?',{title:/*LANG*/"Settings"}).then((v) => {
|
||||
if (v) {
|
||||
|
|
|
@ -13,3 +13,4 @@
|
|||
1.14: Add VMG and coordinates screens
|
||||
1.43: Adds mirroring of the watch face to an Android device. See README.md
|
||||
1.49: Droidscript mirroring prog automatically uses last connection address. Auto connects when run.
|
||||
1.50: Add configuration item Wpt File Suffix. A one character suffix to append to the waypoints.json file. A number of other apps also use this file name. Using the file name suffix allows the speedalt2 waypoints to be retained if one of these other apps is installed for a different use.
|
||||
|
|
|
@ -78,6 +78,10 @@ Waypoints are used in Distance and VMG modes. Create a file waypoints.json and w
|
|||
|
||||
The [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app in the App Loader has a really nice waypoints file editor. (Must be connected to your Bangle.JS and then click on the Download icon.)
|
||||
|
||||
By default the waypoints file is called waypoints.json
|
||||
|
||||
**Note** : The waypoints.json file is used by a number of different gps apps. The setting 'Wpt File Suffix' allows one of waypoints1.json, waypoints2.json or waypoints3.json to be used instead. This allows the other apps to be used with a different set of waypoints without losing the speedalt2 waypoint set.
|
||||
|
||||
Sample waypoints.json (My sailing waypoints)
|
||||
|
||||
<pre>
|
||||
|
|
|
@ -5,8 +5,9 @@ Mike Bennett mike[at]kereru.com
|
|||
1.14 : Add VMG screen
|
||||
1.34 : Add bluetooth data stream for Droidscript
|
||||
1.43 : Keep GPS in SuperE mode while using Droiscript screen mirroring
|
||||
1.50 : Add cfg.wptSfx one char suffix to append to waypoints.json filename. Protects speedalt2 waypoints from other apps that use the same file name for waypoints.
|
||||
*/
|
||||
var v = '1.49';
|
||||
var v = '1.50';
|
||||
var vDroid = '1.50'; // Required DroidScript program version
|
||||
|
||||
/*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */
|
||||
|
@ -209,7 +210,7 @@ function nxtWp(){
|
|||
}
|
||||
|
||||
function loadWp() {
|
||||
var w = require("Storage").readJSON('waypoints.json')||[{name:"NONE"}];
|
||||
var w = require("Storage").readJSON('waypoints'+cfg.wptSfx+'.json')||[{name:"NONE"}];
|
||||
if (cfg.wp>=w.length) cfg.wp=0;
|
||||
if (cfg.wp<0) cfg.wp = w.length-1;
|
||||
savSettings();
|
||||
|
@ -718,6 +719,7 @@ cfg.primSpd = cfg.primSpd||0; // 1 = Spd in primary, 0 = Spd in secondary
|
|||
cfg.spdFilt = cfg.spdFilt==undefined?true:cfg.spdFilt;
|
||||
cfg.altFilt = cfg.altFilt==undefined?true:cfg.altFilt;
|
||||
cfg.touch = cfg.touch==undefined?true:cfg.touch;
|
||||
cfg.wptSfx = cfg.wptSfx==undefined?'':cfg.wptSfx;
|
||||
|
||||
if ( cfg.spdFilt ) var spdFilter = new KalmanFilter({R: 0.1 , Q: 1 });
|
||||
if ( cfg.altFilt ) var altFilter = new KalmanFilter({R: 0.01, Q: 2 });
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "speedalt2",
|
||||
"name": "GPS Adventure Sports II",
|
||||
"shortName":"GPS Adv Sport II",
|
||||
"version":"1.49",
|
||||
"version":"1.50",
|
||||
"description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
@ -15,5 +15,11 @@
|
|||
{"name":"speedalt2.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"speedalt2.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [{"name":"speedalt2.json"}]
|
||||
"data": [
|
||||
{"name":"speedalt2.json"},
|
||||
{"name":"waypoints.json"},
|
||||
{"name":"waypoints1.json"},
|
||||
{"name":"waypoints2.json"},
|
||||
{"name":"waypoints3.json"}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -30,6 +30,11 @@
|
|||
writeSettings();
|
||||
}
|
||||
|
||||
function setSfx(s) {
|
||||
settings.wptSfx = s;
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
|
||||
const appMenu = {
|
||||
'': {'title': 'GPS Adv Sprt II'},
|
||||
|
@ -38,6 +43,7 @@
|
|||
'Units' : function() { E.showMenu(unitsMenu); },
|
||||
'Colours' : function() { E.showMenu(colMenu); },
|
||||
'Kalman Filter' : function() { E.showMenu(kalMenu); },
|
||||
'Wpt File Suffix' : function() { E.showMenu(sfxMenu); },
|
||||
'Touch' : {
|
||||
value : settings.touch,
|
||||
format : v => v?"On":"Off",
|
||||
|
@ -69,6 +75,15 @@
|
|||
'Inverted' : function() { setColour(3); }
|
||||
};
|
||||
|
||||
const sfxMenu = {
|
||||
'': {'title': 'Wpt File Suffix'},
|
||||
'< Back': function() { E.showMenu(appMenu); },
|
||||
'Default' : function() { setSfx(''); },
|
||||
'1' : function() { setSfx('1'); },
|
||||
'2' : function() { setSfx('2'); },
|
||||
'3' : function() { setSfx('3'); }
|
||||
};
|
||||
|
||||
const kalMenu = {
|
||||
'': {'title': 'Kalman Filter'},
|
||||
'< Back': function() { E.showMenu(appMenu); },
|
||||
|
|
2
core
|
@ -1 +1 @@
|
|||
Subproject commit 147892754eaf50c8581ebfb4d8651b9ec24aa44e
|
||||
Subproject commit 404e981834f2e8df9c505a8fab12ae12fe3bd562
|
|
@ -192,7 +192,15 @@
|
|||
"Notifications": "Notifiche",
|
||||
"Scheduler": "Schedulatore",
|
||||
"Stop": "Stop",
|
||||
"Min Font": "Dimensione minima del font"
|
||||
"Min Font": "Dimensione minima del font",
|
||||
"White": "Bianco",
|
||||
"Red": "Rosso",
|
||||
"Yellow": "Giallo",
|
||||
"Cyan": "Ciano",
|
||||
"Green": "Verde",
|
||||
"Blue": "Blu",
|
||||
"Black": "Nero",
|
||||
"Show Week Number": "Mostra numero settimana"
|
||||
},
|
||||
"//2": "App-specific overrides",
|
||||
"alarm": {
|
||||
|
|
|
@ -66,6 +66,10 @@ ClockFace.prototype.tick = function() {
|
|||
};
|
||||
|
||||
ClockFace.prototype.start = function() {
|
||||
/* Some widgets want to know if we're in a clock or not (like chrono, widget clock, etc). Normally
|
||||
.CLOCK is set by Bangle.setUI('clock') but we want to load widgets so we can check appRect and *then*
|
||||
call setUI. see #1864 */
|
||||
Bangle.CLOCK = 1;
|
||||
Bangle.loadWidgets();
|
||||
if (this.init) this.init.apply(this);
|
||||
if (this._upDown) Bangle.setUI("clockupdown", d=>this._upDown.apply(this,[d]));
|
||||
|
@ -103,4 +107,4 @@ ClockFace.prototype.redraw = function() {
|
|||
this.tick();
|
||||
};
|
||||
|
||||
exports = ClockFace;
|
||||
exports = ClockFace;
|
||||
|
|
|
@ -65,6 +65,8 @@ Other functions:
|
|||
* `layout.debug(obj)` - draw outlines for objects on screen
|
||||
* `layout.clear(obj)` - clear the given object (you can also just specify `bgCol` to clear before each render)
|
||||
* `layout.forgetLazyState()` - if lazy rendering is enabled, makes the next call to `render()` perform a full re-render
|
||||
* `layout.setUI()` - (called when module initialised) This sets up input (buttons, touch, etc) with Bangle._setUI
|
||||
This can be useful if you called E.showMenu/showPrompt/etc and those grabbed input away from layour
|
||||
*/
|
||||
|
||||
|
||||
|
@ -73,11 +75,10 @@ function Layout(layout, options) {
|
|||
// Do we have >1 physical buttons?
|
||||
this.physBtns = (process.env.HWVERSION==2) ? 1 : 3;
|
||||
|
||||
options = options || {};
|
||||
this.lazy = options.lazy || false;
|
||||
this.options = options || {};
|
||||
this.lazy = this.options.lazy || false;
|
||||
|
||||
var btnList, uiSet;
|
||||
Bangle.setUI(); // remove all existing input handlers
|
||||
var btnList;
|
||||
if (process.env.HWVERSION!=2) {
|
||||
// no touchscreen, find any buttons in 'layout'
|
||||
btnList = [];
|
||||
|
@ -91,48 +92,19 @@ function Layout(layout, options) {
|
|||
this.physBtns = 0;
|
||||
this.buttons = btnList;
|
||||
this.selectedButton = -1;
|
||||
Bangle.setUI({mode:"updown", back:options.back}, dir=>{
|
||||
var s = this.selectedButton, l=this.buttons.length;
|
||||
if (dir===undefined && this.buttons[s])
|
||||
return this.buttons[s].cb();
|
||||
if (this.buttons[s]) {
|
||||
delete this.buttons[s].selected;
|
||||
this.render(this.buttons[s]);
|
||||
}
|
||||
s = (s+l+dir) % l;
|
||||
if (this.buttons[s]) {
|
||||
this.buttons[s].selected = 1;
|
||||
this.render(this.buttons[s]);
|
||||
}
|
||||
this.selectedButton = s;
|
||||
});
|
||||
uiSet = true;
|
||||
}
|
||||
}
|
||||
if (options.back && !uiSet) Bangle.setUI({mode: "custom", back: options.back});
|
||||
|
||||
if (options.btns) {
|
||||
var buttons = options.btns;
|
||||
if (this.options.btns) {
|
||||
var buttons = this.options.btns;
|
||||
this.b = buttons;
|
||||
if (this.physBtns >= buttons.length) {
|
||||
// Handler for button watch events
|
||||
function pressHandler(btn,e) {
|
||||
if (e.time-e.lastTime > 0.75 && this.b[btn].cbl)
|
||||
this.b[btn].cbl(e);
|
||||
else
|
||||
if (this.b[btn].cb) this.b[btn].cb(e);
|
||||
}
|
||||
// enough physical buttons
|
||||
let btnHeight = Math.floor(Bangle.appRect.h / this.physBtns);
|
||||
if (Bangle.btnWatches) Bangle.btnWatches.forEach(clearWatch);
|
||||
Bangle.btnWatches = [];
|
||||
if (this.physBtns > 2 && buttons.length==1)
|
||||
buttons.unshift({label:""}); // pad so if we have a button in the middle
|
||||
while (this.physBtns > buttons.length)
|
||||
buttons.push({label:""});
|
||||
if (buttons[0]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,0), BTN1, {repeat:true,edge:-1}));
|
||||
if (buttons[1]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,1), BTN2, {repeat:true,edge:-1}));
|
||||
if (buttons[2]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,2), BTN3, {repeat:true,edge:-1}));
|
||||
this._l.width = g.getWidth()-8; // text width
|
||||
this._l = {type:"h", filly:1, c: [
|
||||
this._l,
|
||||
|
@ -149,19 +121,8 @@ function Layout(layout, options) {
|
|||
if (btnList) btnList.push.apply(btnList, this._l.c[1].c);
|
||||
}
|
||||
}
|
||||
if (process.env.HWVERSION==2) {
|
||||
|
||||
// Handler for touch events
|
||||
function touchHandler(l,e) {
|
||||
if (l.cb && e.x>=l.x && e.y>=l.y && e.x<=l.x+l.w && e.y<=l.y+l.h) {
|
||||
if (e.type==2 && l.cbl) l.cbl(e); else if (l.cb) l.cb(e);
|
||||
}
|
||||
if (l.c) l.c.forEach(n => touchHandler(n,e));
|
||||
}
|
||||
Bangle.touchHandler = (_,e)=>touchHandler(this._l,e);
|
||||
Bangle.on('touch',Bangle.touchHandler);
|
||||
}
|
||||
|
||||
// Link in all buttons/touchscreen/etc
|
||||
this.setUI();
|
||||
// recurse over layout doing some fixing up if needed
|
||||
var ll = this;
|
||||
function recurser(l) {
|
||||
|
@ -175,16 +136,57 @@ function Layout(layout, options) {
|
|||
this.updateNeeded = true;
|
||||
}
|
||||
|
||||
Layout.prototype.remove = function (l) {
|
||||
if (Bangle.btnWatches) {
|
||||
Bangle.btnWatches.forEach(clearWatch);
|
||||
delete Bangle.btnWatches;
|
||||
Layout.prototype.setUI = function() {
|
||||
Bangle.setUI(); // remove all existing input handlers
|
||||
|
||||
var uiSet;
|
||||
if (this.buttons) {
|
||||
// multiple buttons so we'll jus use back/next/select
|
||||
Bangle.setUI({mode:"updown", back:this.options.back}, dir=>{
|
||||
var s = this.selectedButton, l=this.buttons.length;
|
||||
if (dir===undefined && this.buttons[s])
|
||||
return this.buttons[s].cb();
|
||||
if (this.buttons[s]) {
|
||||
delete this.buttons[s].selected;
|
||||
this.render(this.buttons[s]);
|
||||
}
|
||||
s = (s+l+dir) % l;
|
||||
if (this.buttons[s]) {
|
||||
this.buttons[s].selected = 1;
|
||||
this.render(this.buttons[s]);
|
||||
}
|
||||
this.selectedButton = s;
|
||||
});
|
||||
uiSet = true;
|
||||
}
|
||||
if (Bangle.touchHandler) {
|
||||
Bangle.removeListener("touch",Bangle.touchHandler);
|
||||
delete Bangle.touchHandler;
|
||||
if (this.options.back && !uiSet) Bangle.setUI({mode: "custom", back: this.options.back});
|
||||
// physical buttons -> actual applications
|
||||
if (this.b) {
|
||||
// Handler for button watch events
|
||||
function pressHandler(btn,e) {
|
||||
if (e.time-e.lastTime > 0.75 && this.b[btn].cbl)
|
||||
this.b[btn].cbl(e);
|
||||
else
|
||||
if (this.b[btn].cb) this.b[btn].cb(e);
|
||||
}
|
||||
if (Bangle.btnWatches) Bangle.btnWatches.forEach(clearWatch);
|
||||
Bangle.btnWatches = [];
|
||||
if (this.b[0]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,0), BTN1, {repeat:true,edge:-1}));
|
||||
if (this.b[1]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,1), BTN2, {repeat:true,edge:-1}));
|
||||
if (this.b[2]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,2), BTN3, {repeat:true,edge:-1}));
|
||||
}
|
||||
};
|
||||
// Handle touch events on new Bangle.js
|
||||
if (process.env.HWVERSION==2) {
|
||||
function touchHandler(l,e) {
|
||||
if (l.cb && e.x>=l.x && e.y>=l.y && e.x<=l.x+l.w && e.y<=l.y+l.h) {
|
||||
if (e.type==2 && l.cbl) l.cbl(e); else if (l.cb) l.cb(e);
|
||||
}
|
||||
if (l.c) l.c.forEach(n => touchHandler(n,e));
|
||||
}
|
||||
Bangle.touchHandler = (_,e)=>touchHandler(this._l,e);
|
||||
Bangle.on('touch',Bangle.touchHandler);
|
||||
}
|
||||
}
|
||||
|
||||
function prepareLazyRender(l, rectsToClear, drawList, rects, parentBg) {
|
||||
var bgCol = l.bgCol == null ? parentBg : g.toColor(l.bgCol);
|
||||
|
|
|
@ -140,7 +140,7 @@ declare const require: ((module: 'heatshrink') => {
|
|||
|
||||
declare const Bangle: {
|
||||
// functions
|
||||
buzz: () => void;
|
||||
buzz: (duration?: number, intensity?: number) => Promise<void>;
|
||||
drawWidgets: () => void;
|
||||
isCharging: () => boolean;
|
||||
// events
|
||||
|
@ -158,9 +158,9 @@ declare type Image = {
|
|||
};
|
||||
|
||||
declare type GraphicsApi = {
|
||||
reset: () => void;
|
||||
reset: () => GraphicsApi;
|
||||
flip: () => void;
|
||||
setColor: (color: string) => void; // TODO we can most likely type color more usefully than this
|
||||
setColor: (color: string) => GraphicsApi; // TODO we can most likely type color more usefully than this
|
||||
drawImage: (
|
||||
image: string | Image | ArrayBuffer,
|
||||
xOffset: number,
|
||||
|
@ -169,7 +169,7 @@ declare type GraphicsApi = {
|
|||
rotate?: number;
|
||||
scale?: number;
|
||||
}
|
||||
) => void;
|
||||
) => GraphicsApi;
|
||||
// TODO add more
|
||||
};
|
||||
|
||||
|
|