Merge branch 'espruino:master' into master
|
@ -11,3 +11,4 @@ tests/Layout/bin/tmp.*
|
||||||
tests/Layout/testresult.bmp
|
tests/Layout/testresult.bmp
|
||||||
apps.local.json
|
apps.local.json
|
||||||
_site
|
_site
|
||||||
|
.jekyll-cache
|
||||||
|
|
21
README.md
|
@ -226,10 +226,8 @@ and which gives information about the app for the Launcher.
|
||||||
"name":"Short Name", // for Bangle.js menu
|
"name":"Short Name", // for Bangle.js menu
|
||||||
"icon":"*myappid", // for Bangle.js menu
|
"icon":"*myappid", // for Bangle.js menu
|
||||||
"src":"-myappid", // source file
|
"src":"-myappid", // source file
|
||||||
"type":"widget/clock/app/bootloader", // optional, default "app"
|
"type":"widget/clock/app/bootloader/...", // optional, default "app"
|
||||||
// if this is 'widget' then it's not displayed in the menu
|
// see 'type' in 'metadata.json format' below for more options/info
|
||||||
// if it's 'clock' then it'll be loaded by default at boot time
|
|
||||||
// if this is 'bootloader' then it's code that is run at boot time, but is not in a menu
|
|
||||||
"version":"1.23",
|
"version":"1.23",
|
||||||
// added by BangleApps loader on upload based on metadata.json
|
// added by BangleApps loader on upload based on metadata.json
|
||||||
"files:"file1,file2,file3",
|
"files:"file1,file2,file3",
|
||||||
|
@ -252,17 +250,24 @@ and which gives information about the app for the Launcher.
|
||||||
"version": "0v01", // the version of this app
|
"version": "0v01", // the version of this app
|
||||||
"description": "...", // long description (can contain markdown)
|
"description": "...", // long description (can contain markdown)
|
||||||
"icon": "icon.png", // icon in apps/
|
"icon": "icon.png", // icon in apps/
|
||||||
"screenshots" : [ { url:"screenshot.png" } ], // optional screenshot for app
|
"screenshots" : [ { "url":"screenshot.png" } ], // optional screenshot for app
|
||||||
"type":"...", // optional(if app) -
|
"type":"...", // optional(if app) -
|
||||||
// 'app' - an application
|
// 'app' - an application
|
||||||
// 'clock' - a clock - required for clocks to automatically start
|
// 'clock' - a clock - required for clocks to automatically start
|
||||||
// 'widget' - a widget
|
// 'widget' - a widget
|
||||||
// 'launch' - replacement launcher app
|
// 'bootloader' - an app that at startup (app.boot.js) but doesn't have a launcher entry for 'app.js'
|
||||||
// 'bootloader' - code that runs at startup only
|
// 'settings' - apps that appear in Settings->Apps (with appname.settings.js) but that have no 'app.js'
|
||||||
// 'RAM' - code that runs and doesn't upload anything to storage
|
// 'RAM' - code that runs and doesn't upload anything to storage
|
||||||
|
// 'launch' - replacement 'Launcher'
|
||||||
|
// 'textinput' - provides a 'textinput' library that allows text to be input on the Bangle
|
||||||
|
// 'scheduler' - provides 'sched' library and boot code for scheduling alarms/timers
|
||||||
|
// (currently only 'sched' app)
|
||||||
|
// 'notify' - provides 'notify' library for showing notifications
|
||||||
|
// 'locale' - provides 'locale' library for language-specific date/distance/etc
|
||||||
|
// (a version of 'locale' is included in the firmware)
|
||||||
"tags": "", // comma separated tag list for searching
|
"tags": "", // comma separated tag list for searching
|
||||||
"supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
|
"supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
|
||||||
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on
|
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on (see "type" above)
|
||||||
"dependencies" : { "messages":"app" } // optional, depend on a specific app ID
|
"dependencies" : { "messages":"app" } // optional, depend on a specific app ID
|
||||||
// for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
|
// for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
|
||||||
"readme": "README.md", // if supplied, a link to a markdown-style text file
|
"readme": "README.md", // if supplied, a link to a markdown-style text file
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
theme: jekyll-theme-minimal
|
theme: jekyll-theme-slate
|
|
@ -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>
|
|
@ -1,2 +1,5 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Fix the settings bug and some tweaking
|
0.02: Fix the settings bug and some tweaking
|
||||||
|
0.03: Do not alarm while charging
|
||||||
|
0.04: Obey system quiet mode
|
||||||
|
0.05: Battery optimisation, add the pause option, bug fixes
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
# Activity reminder
|
# Activity reminder
|
||||||
|
|
||||||
A reminder to take short walks for the ones with a sedentary lifestyle.
|
A reminder to take short walks for the ones with a sedentary lifestyle.
|
||||||
The alert will popup only if you didn't take your short walk yet
|
The alert will popup only if you didn't take your short walk yet.
|
||||||
|
|
||||||
Different settings can be personalized:
|
Different settings can be personalized:
|
||||||
- Enable : Enable/Disable the app
|
- Enable : Enable/Disable the app
|
||||||
- Start hour: Hour to start the reminder
|
- Start hour: Hour to start the reminder
|
||||||
- End hour: Hour to end the reminder
|
- End hour: Hour to end the reminder
|
||||||
- Max inactivity: Maximum inactivity time to allow before the alert. From 15 to 60 min
|
- Max inactivity: Maximum inactivity time to allow before the alert. From 15 to 120 min
|
||||||
- Dismiss delay: Delay added before the next alert if the alert is dismissed. From 5 to 15 min
|
- Dismiss delay: Delay added before the next alert if the alert is dismissed. From 5 to 60 min
|
||||||
|
- Pause delay: Same as Dismiss delay but longer (usefull for meetings and such). From 30 to 240 min
|
||||||
- Min steps: Minimal amount of steps to count as an activity
|
- Min steps: Minimal amount of steps to count as an activity
|
||||||
|
|
||||||
|
|
|
@ -1,37 +1,42 @@
|
||||||
function drawAlert(){
|
function drawAlert() {
|
||||||
E.showPrompt("Inactivity detected",{
|
E.showPrompt("Inactivity detected", {
|
||||||
title:"Activity reminder",
|
title: "Activity reminder",
|
||||||
buttons : {"Ok": true,"Dismiss": false}
|
buttons: { "Ok": 1, "Dismiss": 2, "Pause": 3 }
|
||||||
}).then(function(v) {
|
}).then(function (v) {
|
||||||
if(v == true){
|
if (v == 1) {
|
||||||
stepsArray = stepsArray.slice(0, activityreminder.maxInnactivityMin - 3);
|
activityreminder_data.okDate = new Date();
|
||||||
require("activityreminder").saveStepsArray(stepsArray);
|
}
|
||||||
}
|
if (v == 2) {
|
||||||
if(v == false){
|
activityreminder_data.dismissDate = new Date();
|
||||||
stepsArray = stepsArray.slice(0, activityreminder.maxInnactivityMin - activityreminder.dismissDelayMin);
|
}
|
||||||
require("activityreminder").saveStepsArray(stepsArray);
|
if (v == 3) {
|
||||||
}
|
activityreminder_data.pauseDate = new Date();
|
||||||
|
}
|
||||||
|
activityreminder.saveData(activityreminder_data);
|
||||||
load();
|
load();
|
||||||
});
|
});
|
||||||
|
|
||||||
Bangle.buzz(400);
|
// Obey system quiet mode:
|
||||||
|
if (!(storage.readJSON('setting.json', 1) || {}).quiet) {
|
||||||
|
Bangle.buzz(400);
|
||||||
|
}
|
||||||
setTimeout(load, 20000);
|
setTimeout(load, 20000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function run(){
|
function run() {
|
||||||
if(stepsArray.length == activityreminder.maxInnactivityMin){
|
if (activityreminder.mustAlert(activityreminder_data, activityreminder_settings)) {
|
||||||
if (stepsArray[0] - stepsArray[stepsArray.length-1] < activityreminder.minSteps){
|
drawAlert();
|
||||||
drawAlert();
|
} else {
|
||||||
}
|
eval(storage.read("activityreminder.settings.js"))(() => load());
|
||||||
}else{
|
}
|
||||||
eval(require("Storage").read("activityreminder.settings.js"))(()=>load());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const activityreminder = require("activityreminder");
|
||||||
|
const storage = require("Storage");
|
||||||
g.clear();
|
g.clear();
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
activityreminder = require("activityreminder").loadSettings();
|
const activityreminder_settings = activityreminder.loadSettings();
|
||||||
stepsArray = require("activityreminder").loadStepsArray();
|
const activityreminder_data = activityreminder.loadData();
|
||||||
run();
|
run();
|
||||||
|
|
|
@ -1,29 +1,45 @@
|
||||||
function run(){
|
function run() {
|
||||||
var now = new Date();
|
if (isNotWorn()) return;
|
||||||
var h = now.getHours();
|
let now = new Date();
|
||||||
if(h >= activityreminder.startHour && h < activityreminder.endHour){
|
let h = now.getHours();
|
||||||
var health = Bangle.getHealthStatus("day");
|
let health = Bangle.getHealthStatus("day");
|
||||||
stepsArray.unshift(health.steps);
|
|
||||||
stepsArray = stepsArray.slice(0, activityreminder.maxInnactivityMin);
|
if (h >= activityreminder_settings.startHour && h < activityreminder_settings.endHour) {
|
||||||
require("activityreminder").saveStepsArray(stepsArray);
|
if (health.steps - activityreminder_data.stepsOnDate >= activityreminder_settings.minSteps // more steps made than needed
|
||||||
}
|
|| health.steps < activityreminder_data.stepsOnDate) { // new day or reboot of the watch
|
||||||
else{
|
activityreminder_data.stepsOnDate = health.steps;
|
||||||
if(stepsArray != []){
|
activityreminder_data.stepsDate = now;
|
||||||
stepsArray = [];
|
activityreminder.saveData(activityreminder_data);
|
||||||
require("activityreminder").saveStepsArray(stepsArray);
|
/* todo in a futur release
|
||||||
|
add settimer to trigger like 10 secs after the stepsDate + minSteps
|
||||||
|
cancel all other timers of this app
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if(stepsArray.length >= activityreminder.maxInnactivityMin){
|
if(activityreminder.mustAlert(activityreminder_data, activityreminder_settings)){
|
||||||
if (stepsArray[0] - stepsArray[stepsArray.length-1] < activityreminder.minSteps){
|
|
||||||
load('activityreminder.app.js');
|
load('activityreminder.app.js');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isNotWorn() {
|
||||||
|
// todo in a futur release check temperature and mouvement in a futur release
|
||||||
|
return Bangle.isCharging();
|
||||||
|
}
|
||||||
|
|
||||||
activityreminder = require("activityreminder").loadSettings();
|
const activityreminder = require("activityreminder");
|
||||||
if(activityreminder.enabled) {
|
const activityreminder_settings = activityreminder.loadSettings();
|
||||||
stepsArray = require("activityreminder").loadStepsArray();
|
if (activityreminder_settings.enabled) {
|
||||||
|
const activityreminder_data = activityreminder.loadData();
|
||||||
|
if(activityreminder_data.firstLoad){
|
||||||
|
activityreminder_data.firstLoad =false;
|
||||||
|
activityreminder.saveData(activityreminder_data);
|
||||||
|
}
|
||||||
setInterval(run, 60000);
|
setInterval(run, 60000);
|
||||||
|
/* todo in a futur release
|
||||||
|
increase setInterval time to something that is still sensible (5 mins ?)
|
||||||
|
add settimer to trigger like 10 secs after the stepsDate + minSteps
|
||||||
|
cancel all other timers of this app
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,57 @@
|
||||||
exports.loadSettings = function() {
|
const storage = require("Storage");
|
||||||
|
|
||||||
|
exports.loadSettings = function () {
|
||||||
return Object.assign({
|
return Object.assign({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
startHour: 9,
|
startHour: 9,
|
||||||
endHour: 20,
|
endHour: 20,
|
||||||
maxInnactivityMin: 30,
|
maxInnactivityMin: 30,
|
||||||
dismissDelayMin: 15,
|
dismissDelayMin: 15,
|
||||||
|
pauseDelayMin: 120,
|
||||||
minSteps: 50
|
minSteps: 50
|
||||||
}, require("Storage").readJSON("activityreminder.s.json", true) || {});
|
}, storage.readJSON("activityreminder.s.json", true) || {});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.writeSettings = function(settings){
|
exports.writeSettings = function (settings) {
|
||||||
require("Storage").writeJSON("activityreminder.s.json", settings);
|
storage.writeJSON("activityreminder.s.json", settings);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.saveStepsArray = function(stepsArray) {
|
exports.saveData = function (data) {
|
||||||
require("Storage").writeJSON("activityreminder.sa.json", stepsArray);
|
storage.writeJSON("activityreminder.data.json", data);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.loadStepsArray = function(){
|
exports.loadData = function () {
|
||||||
return require("Storage").readJSON("activityreminder.sa.json") || [];
|
let health = Bangle.getHealthStatus("day");
|
||||||
|
const data = Object.assign({
|
||||||
|
firstLoad: true,
|
||||||
|
stepsDate: new Date(),
|
||||||
|
stepsOnDate: health.steps,
|
||||||
|
okDate: new Date(1970),
|
||||||
|
dismissDate: new Date(1970),
|
||||||
|
pauseDate: new Date(1970),
|
||||||
|
},
|
||||||
|
storage.readJSON("activityreminder.data.json") || {});
|
||||||
|
|
||||||
|
if(typeof(data.stepsDate) == "string")
|
||||||
|
data.stepsDate = new Date(data.stepsDate);
|
||||||
|
if(typeof(data.okDate) == "string")
|
||||||
|
data.okDate = new Date(data.okDate);
|
||||||
|
if(typeof(data.dismissDate) == "string")
|
||||||
|
data.dismissDate = new Date(data.dismissDate);
|
||||||
|
if(typeof(data.pauseDate) == "string")
|
||||||
|
data.pauseDate = new Date(data.pauseDate);
|
||||||
|
|
||||||
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.mustAlert = function(activityreminder_data, activityreminder_settings) {
|
||||||
|
let now = new Date();
|
||||||
|
if ((now - activityreminder_data.stepsDate) / 60000 > activityreminder_settings.maxInnactivityMin) { // inactivity detected
|
||||||
|
if ((now - activityreminder_data.okDate) / 60000 > 3 && // last alert anwsered with ok was more than 3 min ago
|
||||||
|
(now - activityreminder_data.dismissDate) / 60000 > activityreminder_settings.dismissDelayMin && // last alert was more than dismissDelayMin ago
|
||||||
|
(now - activityreminder_data.pauseDate) / 60000 > activityreminder_settings.pauseDelayMin) { // last alert was more than pauseDelayMin ago
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "Activity Reminder",
|
"name": "Activity Reminder",
|
||||||
"shortName":"Activity Reminder",
|
"shortName":"Activity Reminder",
|
||||||
"description": "A reminder to take short walks for the ones with a sedentary lifestyle",
|
"description": "A reminder to take short walks for the ones with a sedentary lifestyle",
|
||||||
"version":"0.02",
|
"version":"0.05",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
"tags": "tool,activity",
|
"tags": "tool,activity",
|
||||||
|
@ -18,6 +18,6 @@
|
||||||
],
|
],
|
||||||
"data": [
|
"data": [
|
||||||
{"name": "activityreminder.s.json"},
|
{"name": "activityreminder.s.json"},
|
||||||
{"name": "activityreminder.sa.json"}
|
{"name": "activityreminder.data.json"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,64 +1,76 @@
|
||||||
(function(back) {
|
(function (back) {
|
||||||
// Load settings
|
// Load settings
|
||||||
var settings = require("activityreminder").loadSettings();
|
const activityreminder = require("activityreminder");
|
||||||
|
const settings = activityreminder.loadSettings();
|
||||||
|
|
||||||
// Show the menu
|
// Show the menu
|
||||||
E.showMenu({
|
E.showMenu({
|
||||||
"" : { "title" : "Activity Reminder" },
|
"": { "title": "Activity Reminder" },
|
||||||
"< Back" : () => back(),
|
"< Back": () => back(),
|
||||||
'Enable': {
|
'Enable': {
|
||||||
value: settings.enabled,
|
value: settings.enabled,
|
||||||
format: v => v?"Yes":"No",
|
format: v => v ? "Yes" : "No",
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.enabled = v;
|
settings.enabled = v;
|
||||||
require("activityreminder").writeSettings(settings);
|
activityreminder.writeSettings(settings);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
'Start hour': {
|
||||||
|
value: settings.startHour,
|
||||||
|
min: 0, max: 24,
|
||||||
|
onchange: v => {
|
||||||
|
settings.startHour = v;
|
||||||
|
activityreminder.writeSettings(settings);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'End hour': {
|
||||||
|
value: settings.endHour,
|
||||||
|
min: 0, max: 24,
|
||||||
|
onchange: v => {
|
||||||
|
settings.endHour = v;
|
||||||
|
activityreminder.writeSettings(settings);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Max inactivity': {
|
||||||
|
value: settings.maxInnactivityMin,
|
||||||
|
min: 15, max: 120,
|
||||||
|
onchange: v => {
|
||||||
|
settings.maxInnactivityMin = v;
|
||||||
|
activityreminder.writeSettings(settings);
|
||||||
},
|
},
|
||||||
'Start hour': {
|
format: x => {
|
||||||
value: settings.startHour,
|
return x + " min";
|
||||||
min: 0, max: 24,
|
}
|
||||||
onchange: v => {
|
},
|
||||||
settings.startHour = v;
|
'Dismiss delay': {
|
||||||
require("activityreminder").writeSettings(settings);
|
value: settings.dismissDelayMin,
|
||||||
}
|
min: 5, max: 60,
|
||||||
},
|
onchange: v => {
|
||||||
'End hour': {
|
settings.dismissDelayMin = v;
|
||||||
value: settings.endHour,
|
activityreminder.writeSettings(settings);
|
||||||
min: 0, max: 24,
|
},
|
||||||
onchange: v => {
|
format: x => {
|
||||||
settings.endHour = v;
|
return x + " min";
|
||||||
require("activityreminder").writeSettings(settings);
|
}
|
||||||
}
|
},
|
||||||
},
|
'Pause delay': {
|
||||||
'Max inactivity': {
|
value: settings.pauseDelayMin,
|
||||||
value: settings.maxInnactivityMin,
|
min: 30, max: 240,
|
||||||
min: 15, max: 120,
|
onchange: v => {
|
||||||
onchange: v => {
|
settings.pauseDelayMin = v;
|
||||||
settings.maxInnactivityMin = v;
|
activityreminder.writeSettings(settings);
|
||||||
require("activityreminder").writeSettings(settings);
|
},
|
||||||
},
|
format: x => {
|
||||||
format: x => {
|
return x + " min";
|
||||||
return x + " min";
|
}
|
||||||
}
|
},
|
||||||
},
|
'Min steps': {
|
||||||
'Dismiss delay': {
|
value: settings.minSteps,
|
||||||
value: settings.dismissDelayMin,
|
min: 10, max: 500,
|
||||||
min: 5, max: 15,
|
onchange: v => {
|
||||||
onchange: v => {
|
settings.minSteps = v;
|
||||||
settings.dismissDelayMin = v;
|
activityreminder.writeSettings(settings);
|
||||||
require("activityreminder").writeSettings(settings);
|
}
|
||||||
},
|
}
|
||||||
format: x => {
|
|
||||||
return x + " min";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'Min steps': {
|
|
||||||
value: settings.minSteps,
|
|
||||||
min: 10, max: 500,
|
|
||||||
onchange: v => {
|
|
||||||
settings.minSteps = v;
|
|
||||||
require("activityreminder").writeSettings(settings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
|
@ -24,3 +24,8 @@
|
||||||
0.23: Fix regression with Days of Week (#1735)
|
0.23: Fix regression with Days of Week (#1735)
|
||||||
0.24: Automatically save the alarm/timer when the user returns to the main menu using the back arrow
|
0.24: Automatically save the alarm/timer when the user returns to the main menu using the back arrow
|
||||||
Add "Enable All", "Disable All" and "Remove All" actions
|
Add "Enable All", "Disable All" and "Remove All" actions
|
||||||
|
0.25: Fix redrawing selected Alarm/Timer entry inside edit submenu
|
||||||
|
0.26: Add support for Monday as first day of the week (#1780)
|
||||||
|
0.27: New UI!
|
||||||
|
0.28: Fix bug with alarms not firing when configured to fire only once
|
||||||
|
0.29: Fix wrong 'dow' handling in new timer if first day of week is Monday
|
||||||
|
|
|
@ -1,7 +1,31 @@
|
||||||
Alarms & Timers
|
# Alarms & Timers
|
||||||
===============
|
|
||||||
|
|
||||||
This app allows you to add/modify any alarms and timers.
|
This app allows you to add/modify any alarms and timers.
|
||||||
|
|
||||||
It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched)
|
It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) to handle the alarm scheduling in an efficient way that can work alongside other apps.
|
||||||
to handle the alarm scheduling in an efficient way that can work alongside other apps.
|
|
||||||
|
## Menu overview
|
||||||
|
|
||||||
|
- `New...`
|
||||||
|
- `New Alarm` → Configure a new alarm
|
||||||
|
- `Repeat` → Select when the alarm will fire. You can select a predefined option (_Once_, _Every Day_, _Workdays_ or _Weekends_ or you can configure the days freely)
|
||||||
|
- `New Timer` → Configure a new timer
|
||||||
|
- `Advanced`
|
||||||
|
- `Scheduler settings` → Open the [Scheduler](https://github.com/espruino/BangleApps/tree/master/apps/sched) settings page, see its [README](https://github.com/espruino/BangleApps/blob/master/apps/sched/README.md) for details
|
||||||
|
- `Enable All` → Enable _all_ disabled alarms & timers
|
||||||
|
- `Disable All` → Disable _all_ enabled alarms & timers
|
||||||
|
- `Delete All` → Delete _all_ alarms & timers
|
||||||
|
|
||||||
|
## Creator
|
||||||
|
|
||||||
|
- [Gordon Williams](https://github.com/gfwilliams)
|
||||||
|
|
||||||
|
## Main Contributors
|
||||||
|
|
||||||
|
- [Alessandro Cocco](https://github.com/alessandrococco) - New UI, full rewrite, new features
|
||||||
|
- [Sabin Iacob](https://github.com/m0n5t3r) - Auto snooze support
|
||||||
|
- [storm64](https://github.com/storm64) - Fix redrawing in submenus
|
||||||
|
|
||||||
|
## Attributions
|
||||||
|
|
||||||
|
All icons used in this app are from [icons8](https://icons8.com).
|
||||||
|
|
|
@ -1,240 +1,358 @@
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
// 0 = Sunday (default), 1 = Monday
|
||||||
|
const firstDayOfWeek = (require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0;
|
||||||
|
const WORKDAYS = 62
|
||||||
|
const WEEKEND = firstDayOfWeek ? 192 : 65;
|
||||||
|
const EVERY_DAY = firstDayOfWeek ? 254 : 127;
|
||||||
|
|
||||||
|
const iconAlarmOn = "\0" + atob("GBiBAAAAAAAAAAYAYA4AcBx+ODn/nAP/wAf/4A/n8A/n8B/n+B/n+B/n+B/n+B/h+B/4+A/+8A//8Af/4AP/wAH/gAB+AAAAAAAAAA==");
|
||||||
|
const iconAlarmOff = "\0" + (g.theme.dark
|
||||||
|
? atob("GBjBAP////8AAAAAAAAGAGAOAHAcfjg5/5wD/8AH/+AP5/AP5/Af5/gf5/gf5wAf5gAf4Hgf+f4P+bYP8wMH84cD84cB8wMAebYAAf4AAHg=")
|
||||||
|
: atob("GBjBAP//AAAAAAAAAAAGAGAOAHAcfjg5/5wD/8AH/+AP5/AP5/Af5/gf5/gf5wAf5gAf4Hgf+f4P+bYP8wMH84cD84cB8wMAebYAAf4AAHg="));
|
||||||
|
|
||||||
|
const iconTimerOn = "\0" + (g.theme.dark
|
||||||
|
? atob("GBjBAP////8AAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5wAAwwABgYABgYABgYAH/+AH/+AAAAAAAAAAAAA=")
|
||||||
|
: atob("GBjBAP//AAAAAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5wAAwwABgYABgYABgYAH/+AH/+AAAAAAAAAAAAA="));
|
||||||
|
const iconTimerOff = "\0" + (g.theme.dark
|
||||||
|
? atob("GBjBAP////8AAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5HgAwf4BgbYBgwMBg4cH84cH8wMAAbYAAf4AAHg=")
|
||||||
|
: atob("GBjBAP//AAAAAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5HgAwf4BgbYBgwMBg4cH84cH8wMAAbYAAf4AAHg="));
|
||||||
|
|
||||||
// An array of alarm objects (see sched/README.md)
|
// An array of alarm objects (see sched/README.md)
|
||||||
let alarms = require("sched").getAlarms();
|
var alarms = require("sched").getAlarms();
|
||||||
|
|
||||||
function getCurrentTime() {
|
function handleFirstDayOfWeek(dow) {
|
||||||
let time = new Date();
|
if (firstDayOfWeek == 1) {
|
||||||
return (
|
if ((dow & 1) == 1) {
|
||||||
time.getHours() * 3600000 +
|
// In the scheduler API Sunday is 1.
|
||||||
time.getMinutes() * 60000 +
|
// Here the week starts on Monday and Sunday is ON so
|
||||||
time.getSeconds() * 1000
|
// when I read the dow I need to move Sunday to 128...
|
||||||
);
|
dow += 127;
|
||||||
|
} else if ((dow & 128) == 128) {
|
||||||
|
// ... and then when I write the dow I need to move Sunday back to 1.
|
||||||
|
dow -= 127;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dow;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveAndReload() {
|
// Check the first day of week and update the dow field accordingly (alarms only!)
|
||||||
require("sched").setAlarms(alarms);
|
alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
|
||||||
require("sched").reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
function showMainMenu() {
|
function showMainMenu() {
|
||||||
// Timer img "\0"+atob("DhKBAP////MDDAwwMGGBzgPwB4AeAPwHOBhgwMMzDez////w")
|
|
||||||
// Alarm img "\0"+atob("FBSBAABgA4YcMPDGP8Zn/mx/48//PP/zD/8A//AP/wD/8A//AP/wH/+D//w//8AAAADwAAYA")
|
|
||||||
const menu = {
|
const menu = {
|
||||||
'': { 'title': /*LANG*/'Alarms&Timers' },
|
"": { "title": /*LANG*/"Alarms & Timers" },
|
||||||
/*LANG*/'< Back' : ()=>{load();},
|
"< Back": () => load(),
|
||||||
/*LANG*/'New Alarm': ()=>editAlarm(-1),
|
/*LANG*/"New...": () => showNewMenu()
|
||||||
/*LANG*/'New Timer': ()=>editTimer(-1)
|
|
||||||
};
|
};
|
||||||
alarms.forEach((alarm,idx)=>{
|
|
||||||
var type,txt; // a leading space is currently required (JS error in Espruino 2v12)
|
alarms.forEach((e, index) => {
|
||||||
if (alarm.timer) {
|
var label = e.timer
|
||||||
type = /*LANG*/"Timer";
|
? require("time_utils").formatDuration(e.timer)
|
||||||
txt = " "+require("sched").formatTime(alarm.timer);
|
: require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : "");
|
||||||
} else {
|
menu[label] = {
|
||||||
type = /*LANG*/"Alarm";
|
value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff),
|
||||||
txt = " "+require("sched").formatTime(alarm.t);
|
onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index)
|
||||||
}
|
|
||||||
if (alarm.rp) txt += "\0"+atob("FBaBAAABgAAcAAHn//////wAHsABzAAYwAAMAADAAAAAAwAAMAADGAAzgAN4AD//////54AAOAABgAA=");
|
|
||||||
// rename duplicate alarms
|
|
||||||
if (menu[type+txt]) {
|
|
||||||
var n = 2;
|
|
||||||
while (menu[type+" "+n+txt]) n++;
|
|
||||||
txt = type+" "+n+txt;
|
|
||||||
} else txt = type+txt;
|
|
||||||
// add to menu
|
|
||||||
menu[txt] = {
|
|
||||||
value : "\0"+atob(alarm.on?"EhKBAH//v/////////////5//x//j//H+eP+Mf/A//h//z//////////3//g":"EhKBAH//v//8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA///3//g"),
|
|
||||||
onchange : function() {
|
|
||||||
if (alarm.timer) editTimer(idx, alarm);
|
|
||||||
else editAlarm(idx, alarm);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
if (alarms.some(e => !e.on)) {
|
menu[/*LANG*/"Advanced"] = () => showAdvancedMenu();
|
||||||
menu[/*LANG*/"Enable All"] = () => enableAll(true);
|
|
||||||
}
|
|
||||||
if (alarms.some(e => e.on)) {
|
|
||||||
menu[/*LANG*/"Disable All"] = () => enableAll(false);
|
|
||||||
}
|
|
||||||
if (alarms.length > 0) {
|
|
||||||
menu[/*LANG*/"Delete All"] = () => deleteAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (WIDGETS["alarm"]) WIDGETS["alarm"].reload();
|
|
||||||
return E.showMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
function editDOW(dow, onchange) {
|
|
||||||
const menu = {
|
|
||||||
'': { 'title': /*LANG*/'Days of Week' },
|
|
||||||
/*LANG*/'< Back' : () => onchange(dow)
|
|
||||||
};
|
|
||||||
for (let i = 0; i < 7; i++) (i => {
|
|
||||||
let dayOfWeek = require("locale").dow({ getDay: () => i });
|
|
||||||
menu[dayOfWeek] = {
|
|
||||||
value: !!(dow&(1<<i)),
|
|
||||||
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
|
|
||||||
onchange: v => v ? dow |= 1<<i : dow &= ~(1<<i),
|
|
||||||
};
|
|
||||||
})(i);
|
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
function editAlarm(alarmIndex, alarm) {
|
function showNewMenu() {
|
||||||
let newAlarm = alarmIndex < 0;
|
E.showMenu({
|
||||||
let a = require("sched").newDefaultAlarm();
|
"": { "title": /*LANG*/"New..." },
|
||||||
if (!newAlarm) Object.assign(a, alarms[alarmIndex]);
|
"< Back": () => showMainMenu(),
|
||||||
if (alarm) Object.assign(a,alarm);
|
/*LANG*/"Alarm": () => showEditAlarmMenu(undefined, undefined),
|
||||||
let t = require("sched").decodeTime(a.t);
|
/*LANG*/"Timer": () => showEditTimerMenu(undefined, undefined)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showEditAlarmMenu(selectedAlarm, alarmIndex) {
|
||||||
|
var isNew = alarmIndex === undefined;
|
||||||
|
|
||||||
|
var alarm = require("sched").newDefaultAlarm();
|
||||||
|
alarm.dow = handleFirstDayOfWeek(alarm.dow);
|
||||||
|
|
||||||
|
if (selectedAlarm) {
|
||||||
|
Object.assign(alarm, selectedAlarm);
|
||||||
|
}
|
||||||
|
|
||||||
|
var time = require("time_utils").decodeTime(alarm.t);
|
||||||
|
|
||||||
const menu = {
|
const menu = {
|
||||||
'': { 'title': /*LANG*/'Alarm' },
|
"": { "title": isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm" },
|
||||||
/*LANG*/'< Back': () => {
|
"< Back": () => {
|
||||||
saveAlarm(newAlarm, alarmIndex, a, t);
|
saveAlarm(alarm, alarmIndex, time);
|
||||||
showMainMenu();
|
showMainMenu();
|
||||||
},
|
},
|
||||||
/*LANG*/'Hours': {
|
/*LANG*/"Hour": {
|
||||||
value: t.hrs, min : 0, max : 23, wrap : true,
|
value: time.h,
|
||||||
onchange: v => t.hrs=v
|
format: v => ("0" + v).substr(-2),
|
||||||
|
min: 0,
|
||||||
|
max: 23,
|
||||||
|
wrap: true,
|
||||||
|
onchange: v => time.h = v
|
||||||
},
|
},
|
||||||
/*LANG*/'Minutes': {
|
/*LANG*/"Minute": {
|
||||||
value: t.mins, min : 0, max : 59, wrap : true,
|
value: time.m,
|
||||||
onchange: v => t.mins=v
|
format: v => ("0" + v).substr(-2),
|
||||||
|
min: 0,
|
||||||
|
max: 59,
|
||||||
|
wrap: true,
|
||||||
|
onchange: v => time.m = v
|
||||||
},
|
},
|
||||||
/*LANG*/'Enabled': {
|
/*LANG*/"Enabled": {
|
||||||
value: a.on,
|
value: alarm.on,
|
||||||
format: v => v ? /*LANG*/"On" : /*LANG*/"Off",
|
onchange: v => alarm.on = v
|
||||||
onchange: v=>a.on=v
|
|
||||||
},
|
},
|
||||||
/*LANG*/'Repeat': {
|
/*LANG*/"Repeat": {
|
||||||
value: a.rp,
|
value: decodeDOW(alarm),
|
||||||
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
|
onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, alarm.dow, (repeat, dow) => {
|
||||||
onchange: v => a.rp = v
|
alarm.rp = repeat;
|
||||||
},
|
alarm.dow = dow;
|
||||||
/*LANG*/'Days': {
|
alarm.t = require("time_utils").encodeTime(time);
|
||||||
value: "SMTWTFS".split("").map((d,n)=>a.dow&(1<<n)?d:".").join(""),
|
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex);
|
||||||
onchange: () => editDOW(a.dow, d => {
|
|
||||||
a.dow = d;
|
|
||||||
a.t = require("sched").encodeTime(t);
|
|
||||||
editAlarm(alarmIndex, a);
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ),
|
/*LANG*/"Vibrate": require("buzz_menu").pattern(alarm.vibrate, v => alarm.vibrate = v),
|
||||||
/*LANG*/'Auto Snooze': {
|
/*LANG*/"Auto Snooze": {
|
||||||
value: a.as,
|
value: alarm.as,
|
||||||
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
|
onchange: v => alarm.as = v
|
||||||
onchange: v => a.as = v
|
},
|
||||||
|
/*LANG*/"Cancel": () => showMainMenu()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isNew) {
|
||||||
|
menu[/*LANG*/"Delete"] = () => {
|
||||||
|
E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Alarm" }).then((confirm) => {
|
||||||
|
if (confirm) {
|
||||||
|
alarms.splice(alarmIndex, 1);
|
||||||
|
saveAndReload();
|
||||||
|
showMainMenu();
|
||||||
|
} else {
|
||||||
|
alarm.t = require("time_utils").encodeTime(time);
|
||||||
|
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
E.showMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveAlarm(alarm, alarmIndex, time) {
|
||||||
|
alarm.t = require("time_utils").encodeTime(time);
|
||||||
|
alarm.last = alarm.t < require("time_utils").getCurrentTimeMillis() ? new Date().getDate() : 0;
|
||||||
|
|
||||||
|
if (alarmIndex === undefined) {
|
||||||
|
alarms.push(alarm);
|
||||||
|
} else {
|
||||||
|
alarms[alarmIndex] = alarm;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveAndReload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveAndReload() {
|
||||||
|
// Before saving revert the dow to the standard format (alarms only!)
|
||||||
|
alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
|
||||||
|
|
||||||
|
require("sched").setAlarms(alarms);
|
||||||
|
require("sched").reload();
|
||||||
|
|
||||||
|
// Fix after save
|
||||||
|
alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeDOW(alarm) {
|
||||||
|
return alarm.rp
|
||||||
|
? require("date_utils")
|
||||||
|
.dows(firstDayOfWeek, 2)
|
||||||
|
.map((day, index) => alarm.dow & (1 << (index + firstDayOfWeek)) ? day : "_")
|
||||||
|
.join("")
|
||||||
|
.toLowerCase()
|
||||||
|
: "Once"
|
||||||
|
}
|
||||||
|
|
||||||
|
function showEditRepeatMenu(repeat, dow, dowChangeCallback) {
|
||||||
|
var originalRepeat = repeat;
|
||||||
|
var originalDow = dow;
|
||||||
|
var isCustom = repeat && dow != WORKDAYS && dow != WEEKEND && dow != EVERY_DAY;
|
||||||
|
|
||||||
|
const menu = {
|
||||||
|
"": { "title": /*LANG*/"Repeat Alarm" },
|
||||||
|
"< Back": () => dowChangeCallback(repeat, dow),
|
||||||
|
/*LANG*/"Once": {
|
||||||
|
// The alarm will fire once. Internally it will be saved
|
||||||
|
// as "fire every days" BUT the repeat flag is false so
|
||||||
|
// we avoid messing up with the scheduler.
|
||||||
|
value: !repeat,
|
||||||
|
onchange: () => dowChangeCallback(false, EVERY_DAY)
|
||||||
|
},
|
||||||
|
/*LANG*/"Workdays": {
|
||||||
|
value: repeat && dow == WORKDAYS,
|
||||||
|
onchange: () => dowChangeCallback(true, WORKDAYS)
|
||||||
|
},
|
||||||
|
/*LANG*/"Weekends": {
|
||||||
|
value: repeat && dow == WEEKEND,
|
||||||
|
onchange: () => dowChangeCallback(true, WEEKEND)
|
||||||
|
},
|
||||||
|
/*LANG*/"Every Day": {
|
||||||
|
value: repeat && dow == EVERY_DAY,
|
||||||
|
onchange: () => dowChangeCallback(true, EVERY_DAY)
|
||||||
|
},
|
||||||
|
/*LANG*/"Custom": {
|
||||||
|
value: isCustom ? decodeDOW({ rp: true, dow: dow }) : false,
|
||||||
|
onchange: () => setTimeout(showCustomDaysMenu, 10, isCustom ? dow : EVERY_DAY, dowChangeCallback, originalRepeat, originalDow)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
menu[/*LANG*/"Cancel"] = () => showMainMenu();
|
E.showMenu(menu);
|
||||||
|
|
||||||
if (!newAlarm) {
|
|
||||||
menu[/*LANG*/"Delete"] = function () {
|
|
||||||
alarms.splice(alarmIndex, 1);
|
|
||||||
saveAndReload();
|
|
||||||
showMainMenu();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.showMenu(menu);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveAlarm(newAlarm, alarmIndex, a, t) {
|
function showCustomDaysMenu(dow, dowChangeCallback, originalRepeat, originalDow) {
|
||||||
a.t = require("sched").encodeTime(t);
|
|
||||||
a.last = (a.t < getCurrentTime()) ? (new Date()).getDate() : 0;
|
|
||||||
|
|
||||||
if (newAlarm) {
|
|
||||||
alarms.push(a);
|
|
||||||
} else {
|
|
||||||
alarms[alarmIndex] = a;
|
|
||||||
}
|
|
||||||
|
|
||||||
saveAndReload();
|
|
||||||
}
|
|
||||||
|
|
||||||
function editTimer(alarmIndex, alarm) {
|
|
||||||
let newAlarm = alarmIndex < 0;
|
|
||||||
let a = require("sched").newDefaultTimer();
|
|
||||||
if (!newAlarm) Object.assign(a, alarms[alarmIndex]);
|
|
||||||
if (alarm) Object.assign(a,alarm);
|
|
||||||
let t = require("sched").decodeTime(a.timer);
|
|
||||||
|
|
||||||
const menu = {
|
const menu = {
|
||||||
'': { 'title': /*LANG*/'Timer' },
|
"": { "title": /*LANG*/"Custom Days" },
|
||||||
/*LANG*/'< Back': () => {
|
"< Back": () => {
|
||||||
saveTimer(newAlarm, alarmIndex, a, t);
|
// If the user unchecks all the days then we assume repeat = once
|
||||||
showMainMenu();
|
// and we force the dow to every day.
|
||||||
},
|
var repeat = dow > 0;
|
||||||
/*LANG*/'Hours': {
|
dowChangeCallback(repeat, repeat ? dow : EVERY_DAY)
|
||||||
value: t.hrs, min : 0, max : 23, wrap : true,
|
}
|
||||||
onchange: v => t.hrs=v
|
|
||||||
},
|
|
||||||
/*LANG*/'Minutes': {
|
|
||||||
value: t.mins, min : 0, max : 59, wrap : true,
|
|
||||||
onchange: v => t.mins=v
|
|
||||||
},
|
|
||||||
/*LANG*/'Enabled': {
|
|
||||||
value: a.on,
|
|
||||||
format: v => v ? /*LANG*/"On" : /*LANG*/"Off",
|
|
||||||
onchange: v => a.on = v
|
|
||||||
},
|
|
||||||
/*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
menu[/*LANG*/"Cancel"] = () => showMainMenu();
|
require("date_utils").dows(firstDayOfWeek).forEach((day, i) => {
|
||||||
|
menu[day] = {
|
||||||
if (!newAlarm) {
|
value: !!(dow & (1 << (i + firstDayOfWeek))),
|
||||||
menu[/*LANG*/"Delete"] = function() {
|
onchange: v => v ? (dow |= 1 << (i + firstDayOfWeek)) : (dow &= ~(1 << (i + firstDayOfWeek)))
|
||||||
alarms.splice(alarmIndex,1);
|
|
||||||
saveAndReload();
|
|
||||||
showMainMenu();
|
|
||||||
};
|
};
|
||||||
}
|
});
|
||||||
return E.showMenu(menu);
|
|
||||||
|
menu[/*LANG*/"Cancel"] = () => setTimeout(showEditRepeatMenu, 10, originalRepeat, originalDow, dowChangeCallback)
|
||||||
|
|
||||||
|
E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveTimer(newAlarm, alarmIndex, a, t) {
|
function showEditTimerMenu(selectedTimer, timerIndex) {
|
||||||
a.timer = require("sched").encodeTime(t);
|
var isNew = timerIndex === undefined;
|
||||||
a.t = getCurrentTime() + a.timer;
|
|
||||||
a.last = 0;
|
|
||||||
|
|
||||||
if (newAlarm) {
|
var timer = require("sched").newDefaultTimer();
|
||||||
alarms.push(a);
|
|
||||||
|
if (selectedTimer) {
|
||||||
|
Object.assign(timer, selectedTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
var time = require("time_utils").decodeTime(timer.timer);
|
||||||
|
|
||||||
|
const menu = {
|
||||||
|
"": { "title": isNew ? /*LANG*/"New Timer" : /*LANG*/"Edit Timer" },
|
||||||
|
"< Back": () => {
|
||||||
|
saveTimer(timer, timerIndex, time);
|
||||||
|
showMainMenu();
|
||||||
|
},
|
||||||
|
/*LANG*/"Hours": {
|
||||||
|
value: time.h,
|
||||||
|
min: 0,
|
||||||
|
max: 23,
|
||||||
|
wrap: true,
|
||||||
|
onchange: v => time.h = v
|
||||||
|
},
|
||||||
|
/*LANG*/"Minutes": {
|
||||||
|
value: time.m,
|
||||||
|
min: 0,
|
||||||
|
max: 59,
|
||||||
|
wrap: true,
|
||||||
|
onchange: v => time.m = v
|
||||||
|
},
|
||||||
|
/*LANG*/"Enabled": {
|
||||||
|
value: timer.on,
|
||||||
|
onchange: v => timer.on = v
|
||||||
|
},
|
||||||
|
/*LANG*/"Vibrate": require("buzz_menu").pattern(timer.vibrate, v => timer.vibrate = v),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isNew) {
|
||||||
|
menu[/*LANG*/"Delete"] = () => {
|
||||||
|
E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Timer" }).then((confirm) => {
|
||||||
|
if (confirm) {
|
||||||
|
alarms.splice(timerIndex, 1);
|
||||||
|
saveAndReload();
|
||||||
|
showMainMenu();
|
||||||
|
} else {
|
||||||
|
timer.timer = require("time_utils").encodeTime(time);
|
||||||
|
setTimeout(showEditTimerMenu, 10, timer, timerIndex)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
E.showMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveTimer(timer, timerIndex, time) {
|
||||||
|
timer.timer = require("time_utils").encodeTime(time);
|
||||||
|
timer.t = require("time_utils").getCurrentTimeMillis() + timer.timer;
|
||||||
|
timer.last = 0;
|
||||||
|
|
||||||
|
if (timerIndex === undefined) {
|
||||||
|
alarms.push(timer);
|
||||||
} else {
|
} else {
|
||||||
alarms[alarmIndex] = a;
|
alarms[timerIndex] = timer;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveAndReload();
|
saveAndReload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showAdvancedMenu() {
|
||||||
|
E.showMenu({
|
||||||
|
"": { "title": /*LANG*/"Advanced" },
|
||||||
|
"< Back": () => showMainMenu(),
|
||||||
|
/*LANG*/"Scheduler Settings": () => eval(require("Storage").read("sched.settings.js"))(() => showAdvancedMenu()),
|
||||||
|
/*LANG*/"Enable All": () => enableAll(true),
|
||||||
|
/*LANG*/"Disable All": () => enableAll(false),
|
||||||
|
/*LANG*/"Delete All": () => deleteAll()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function enableAll(on) {
|
function enableAll(on) {
|
||||||
E.showPrompt(/*LANG*/"Are you sure?", {
|
if (alarms.filter(e => e.on == !on).length == 0) {
|
||||||
title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All"
|
E.showAlert(
|
||||||
}).then((confirm) => {
|
on ? /*LANG*/"Nothing to Enable" : /*LANG*/"Nothing to Disable",
|
||||||
if (confirm) {
|
on ? /*LANG*/"Enable All" : /*LANG*/"Disable All"
|
||||||
alarms.forEach(alarm => alarm.on = on);
|
).then(() => showAdvancedMenu());
|
||||||
saveAndReload();
|
} else {
|
||||||
}
|
E.showPrompt(/*LANG*/"Are you sure?", { title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All" }).then((confirm) => {
|
||||||
|
if (confirm) {
|
||||||
showMainMenu();
|
alarms.forEach(alarm => alarm.on = on);
|
||||||
});
|
saveAndReload();
|
||||||
|
showMainMenu();
|
||||||
|
} else {
|
||||||
|
showAdvancedMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteAll() {
|
function deleteAll() {
|
||||||
E.showPrompt(/*LANG*/"Are you sure?", {
|
if (alarms.length == 0) {
|
||||||
title: /*LANG*/"Delete All"
|
E.showAlert(/*LANG*/"Nothing to delete", /*LANG*/"Delete All").then(() => showAdvancedMenu());
|
||||||
}).then((confirm) => {
|
} else {
|
||||||
if (confirm) {
|
E.showPrompt(/*LANG*/"Are you sure?", {
|
||||||
alarms = [];
|
title: /*LANG*/"Delete All"
|
||||||
saveAndReload();
|
}).then((confirm) => {
|
||||||
}
|
if (confirm) {
|
||||||
|
alarms = [];
|
||||||
showMainMenu();
|
saveAndReload();
|
||||||
});
|
showMainMenu();
|
||||||
|
} else {
|
||||||
|
showAdvancedMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showMainMenu();
|
showMainMenu();
|
||||||
|
|
|
@ -2,16 +2,29 @@
|
||||||
"id": "alarm",
|
"id": "alarm",
|
||||||
"name": "Alarms & Timers",
|
"name": "Alarms & Timers",
|
||||||
"shortName": "Alarms",
|
"shortName": "Alarms",
|
||||||
"version": "0.24",
|
"version": "0.29",
|
||||||
"description": "Set alarms and timers on your Bangle",
|
"description": "Set alarms and timers on your Bangle",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool,alarm,widget",
|
"tags": "tool,alarm,widget",
|
||||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
"supports": [ "BANGLEJS", "BANGLEJS2" ],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"dependencies": {"scheduler":"type"},
|
"dependencies": { "scheduler":"type" },
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"alarm.app.js","url":"app.js"},
|
{ "name": "alarm.app.js", "url": "app.js" },
|
||||||
{"name":"alarm.img","url":"app-icon.js","evaluate":true},
|
{ "name": "alarm.img", "url": "app-icon.js", "evaluate": true },
|
||||||
{"name":"alarm.wid.js","url":"widget.js"}
|
{ "name": "alarm.wid.js", "url": "widget.js" }
|
||||||
|
],
|
||||||
|
"screenshots": [
|
||||||
|
{ "url": "screenshot-1.png" },
|
||||||
|
{ "url": "screenshot-2.png" },
|
||||||
|
{ "url": "screenshot-3.png" },
|
||||||
|
{ "url": "screenshot-4.png" },
|
||||||
|
{ "url": "screenshot-5.png" },
|
||||||
|
{ "url": "screenshot-6.png" },
|
||||||
|
{ "url": "screenshot-7.png" },
|
||||||
|
{ "url": "screenshot-8.png" },
|
||||||
|
{ "url": "screenshot-9.png" },
|
||||||
|
{ "url": "screenshot-10.png" },
|
||||||
|
{ "url": "screenshot-11.png" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 2.2 KiB |
|
@ -7,3 +7,5 @@
|
||||||
0.06: Option to keep messages after a disconnect (default false) (fix #1186)
|
0.06: Option to keep messages after a disconnect (default false) (fix #1186)
|
||||||
0.07: Include charging state in battery updates to phone
|
0.07: Include charging state in battery updates to phone
|
||||||
0.08: Handling of alarms
|
0.08: Handling of alarms
|
||||||
|
0.09: Alarm vibration, repeat, and auto-snooze now handled by sched
|
||||||
|
0.10: Fix SMS bug
|
||||||
|
|
|
@ -21,7 +21,6 @@ of Gadgetbridge - making your phone make noise so you can find it.
|
||||||
* `Keep Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js
|
* `Keep Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js
|
||||||
keep any messages it has received, or should it delete them?
|
keep any messages it has received, or should it delete them?
|
||||||
* `Messages` - launches the messages app, showing a list of messages
|
* `Messages` - launches the messages app, showing a list of messages
|
||||||
* `Alarms` - opens a submenu where you can set default settings for alarms such as vibration pattern, repeat, and auto snooze
|
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
Bluetooth.println("");
|
Bluetooth.println("");
|
||||||
Bluetooth.println(JSON.stringify(message));
|
Bluetooth.println(JSON.stringify(message));
|
||||||
}
|
}
|
||||||
|
var lastMsg;
|
||||||
|
|
||||||
var settings = require("Storage").readJSON("android.settings.json",1)||{};
|
var settings = require("Storage").readJSON("android.settings.json",1)||{};
|
||||||
//default alarm settings
|
//default alarm settings
|
||||||
|
@ -18,7 +19,17 @@
|
||||||
/* TODO: Call handling, fitness */
|
/* TODO: Call handling, fitness */
|
||||||
var HANDLERS = {
|
var HANDLERS = {
|
||||||
// {t:"notify",id:int, src,title,subject,body,sender,tel:string} add
|
// {t:"notify",id:int, src,title,subject,body,sender,tel:string} add
|
||||||
"notify" : function() { Object.assign(event,{t:"add",positive:true, negative:true});require("messages").pushMessage(event); },
|
"notify" : function() {
|
||||||
|
Object.assign(event,{t:"add",positive:true, negative:true});
|
||||||
|
// Detect a weird GadgetBridge bug and fix it
|
||||||
|
// For some reason SMS messages send two GB notifications, with different sets of info
|
||||||
|
if (lastMsg && event.body == lastMsg.body && lastMsg.src == undefined && event.src == "Messages") {
|
||||||
|
// Mutate the other message
|
||||||
|
event.id = lastMsg.id;
|
||||||
|
}
|
||||||
|
lastMsg = event;
|
||||||
|
require("messages").pushMessage(event);
|
||||||
|
},
|
||||||
// {t:"notify~",id:int, title:string} // modified
|
// {t:"notify~",id:int, title:string} // modified
|
||||||
"notify~" : function() { event.t="modify";require("messages").pushMessage(event); },
|
"notify~" : function() { event.t="modify";require("messages").pushMessage(event); },
|
||||||
// {t:"notify-",id:int} // remove
|
// {t:"notify-",id:int} // remove
|
||||||
|
@ -67,17 +78,13 @@
|
||||||
var dow = event.d[j].rep;
|
var dow = event.d[j].rep;
|
||||||
if (!dow) dow = 127; //if no DOW selected, set alarm to all DOW
|
if (!dow) dow = 127; //if no DOW selected, set alarm to all DOW
|
||||||
var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0;
|
var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0;
|
||||||
var a = {
|
var a = require("sched").newDefaultAlarm();
|
||||||
id : "gb"+j,
|
a.id = "gb"+j;
|
||||||
appid : "gbalarms",
|
a.appid = "gbalarms";
|
||||||
on : true,
|
a.on = true;
|
||||||
t : event.d[j].h * 3600000 + event.d[j].m * 60000,
|
a.t = event.d[j].h * 3600000 + event.d[j].m * 60000;
|
||||||
dow : ((dow&63)<<1) | (dow>>6), // Gadgetbridge sends DOW in a different format
|
a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format
|
||||||
last : last,
|
a.last = last;
|
||||||
rp : settings.rp,
|
|
||||||
as : settings.as,
|
|
||||||
vibrate : settings.vibrate
|
|
||||||
};
|
|
||||||
alarms.push(a);
|
alarms.push(a);
|
||||||
}
|
}
|
||||||
sched.setAlarms(alarms);
|
sched.setAlarms(alarms);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "android",
|
"id": "android",
|
||||||
"name": "Android Integration",
|
"name": "Android Integration",
|
||||||
"shortName": "Android",
|
"shortName": "Android",
|
||||||
"version": "0.08",
|
"version": "0.10",
|
||||||
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool,system,messages,notifications,gadgetbridge",
|
"tags": "tool,system,messages,notifications,gadgetbridge",
|
||||||
|
|
|
@ -25,27 +25,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/*LANG*/"Messages" : ()=>load("messages.app.js"),
|
/*LANG*/"Messages" : ()=>load("messages.app.js"),
|
||||||
/*LANG*/"Alarms" : () => E.showMenu({
|
|
||||||
"" : { "title" : /*LANG*/"Alarms" },
|
|
||||||
"< Back" : ()=>E.showMenu(mainmenu),
|
|
||||||
/*LANG*/"Vibrate": require("buzz_menu").pattern(settings.vibrate, v => {settings.vibrate = v; updateSettings();}),
|
|
||||||
/*LANG*/"Repeat": {
|
|
||||||
value: settings.rp,
|
|
||||||
format : v=>v?/*LANG*/"Yes":/*LANG*/"No",
|
|
||||||
onchange: v => {
|
|
||||||
settings.rp = v;
|
|
||||||
updateSettings();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/*LANG*/"Auto snooze": {
|
|
||||||
value: settings.as,
|
|
||||||
format : v=>v?/*LANG*/"Yes":/*LANG*/"No",
|
|
||||||
onchange: v => {
|
|
||||||
settings.as = v;
|
|
||||||
updateSettings();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
E.showMenu(mainmenu);
|
E.showMenu(mainmenu);
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,3 +7,5 @@
|
||||||
0.07: Update to use Bangle.setUI instead of setWatch
|
0.07: Update to use Bangle.setUI instead of setWatch
|
||||||
0.08: Use theme colors, Layout library
|
0.08: Use theme colors, Layout library
|
||||||
0.09: Fix time/date disappearing after fullscreen notification
|
0.09: Fix time/date disappearing after fullscreen notification
|
||||||
|
0.10: Use ClockFace library
|
||||||
|
0.11: Use ClockFace.is12Hour
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
* A simple digital clock showing seconds as a bar
|
* A simple digital clock showing seconds as a bar
|
||||||
**/
|
**/
|
||||||
// Check settings for what type our clock should be
|
// Check settings for what type our clock should be
|
||||||
const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
|
|
||||||
let locale = require("locale");
|
let locale = require("locale");
|
||||||
{ // add some more info to locale
|
{ // add some more info to locale
|
||||||
let date = new Date();
|
let date = new Date();
|
||||||
|
@ -11,13 +10,9 @@ let locale = require("locale");
|
||||||
date.setMonth(1, 3); // februari: months are zero-indexed
|
date.setMonth(1, 3); // februari: months are zero-indexed
|
||||||
const localized = locale.date(date, true);
|
const localized = locale.date(date, true);
|
||||||
locale.dayFirst = /3.*2/.test(localized);
|
locale.dayFirst = /3.*2/.test(localized);
|
||||||
|
locale.hasMeridian = (locale.meridian(date)!=="");
|
||||||
locale.hasMeridian = false;
|
|
||||||
if (typeof locale.meridian==="function") { // function does not exist if languages app is not installed
|
|
||||||
locale.hasMeridian = (locale.meridian(date)!=="");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Bangle.loadWidgets();
|
|
||||||
function renderBar(l) {
|
function renderBar(l) {
|
||||||
if (!this.fraction) {
|
if (!this.fraction) {
|
||||||
// zero-size fillRect stills draws one line of pixels, we don't want that
|
// zero-size fillRect stills draws one line of pixels, we don't want that
|
||||||
|
@ -27,35 +22,9 @@ function renderBar(l) {
|
||||||
g.fillRect(l.x, l.y, l.x+width-1, l.y+l.height-1);
|
g.fillRect(l.x, l.y, l.x+width-1, l.y+l.height-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Layout = require("Layout");
|
|
||||||
const layout = new Layout({
|
|
||||||
type: "v", c: [
|
|
||||||
{
|
|
||||||
type: "h", c: [
|
|
||||||
{id: "time", label: "88:88", type: "txt", font: "6x8:5", bgCol: g.theme.bg}, // size updated below
|
|
||||||
{id: "ampm", label: " ", type: "txt", font: "6x8:2", bgCol: g.theme.bg},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{id: "bar", type: "custom", fraction: 0, fillx: 1, height: 6, col: g.theme.fg2, render: renderBar},
|
|
||||||
{height: 40},
|
|
||||||
{id: "date", type: "txt", font: "10%", valign: 1},
|
|
||||||
],
|
|
||||||
}, {lazy: true});
|
|
||||||
// adjustments based on screen size and whether we display am/pm
|
|
||||||
let thickness; // bar thickness, same as time font "pixel block" size
|
|
||||||
if (is12Hour) {
|
|
||||||
// Maximum font size = (<screen width> - <ampm: 2chars * (2*6)px>) / (5chars * 6px)
|
|
||||||
thickness = Math.floor((g.getWidth()-24)/(5*6));
|
|
||||||
} else {
|
|
||||||
layout.ampm.label = "";
|
|
||||||
thickness = Math.floor(g.getWidth()/(5*6));
|
|
||||||
}
|
|
||||||
layout.bar.height = thickness+1;
|
|
||||||
layout.time.font = "6x8:"+thickness;
|
|
||||||
layout.update();
|
|
||||||
|
|
||||||
function timeText(date) {
|
function timeText(date) {
|
||||||
if (!is12Hour) {
|
if (!clock.is12Hour) {
|
||||||
return locale.time(date, true);
|
return locale.time(date, true);
|
||||||
}
|
}
|
||||||
const date12 = new Date(date.getTime());
|
const date12 = new Date(date.getTime());
|
||||||
|
@ -68,7 +37,7 @@ function timeText(date) {
|
||||||
return locale.time(date12, true);
|
return locale.time(date12, true);
|
||||||
}
|
}
|
||||||
function ampmText(date) {
|
function ampmText(date) {
|
||||||
return (is12Hour && locale.hasMeridian)? locale.meridian(date) : "";
|
return (clock.is12Hour && locale.hasMeridian) ? locale.meridian(date) : "";
|
||||||
}
|
}
|
||||||
function dateText(date) {
|
function dateText(date) {
|
||||||
const dayName = locale.dow(date, true),
|
const dayName = locale.dow(date, true),
|
||||||
|
@ -78,31 +47,48 @@ function dateText(date) {
|
||||||
return `${dayName} ${dayMonth}`;
|
return `${dayName} ${dayMonth}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
draw = function draw(force) {
|
|
||||||
if (!Bangle.isLCDOn()) {return;} // no drawing, also no new update scheduled
|
|
||||||
const date = new Date();
|
|
||||||
layout.time.label = timeText(date);
|
|
||||||
layout.ampm.label = ampmText(date);
|
|
||||||
layout.date.label = dateText(date);
|
|
||||||
const SECONDS_PER_MINUTE = 60;
|
|
||||||
layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE;
|
|
||||||
if (force) {
|
|
||||||
Bangle.drawWidgets();
|
|
||||||
layout.forgetLazyState();
|
|
||||||
}
|
|
||||||
layout.render();
|
|
||||||
// schedule update at start of next second
|
|
||||||
const millis = date.getMilliseconds();
|
|
||||||
setTimeout(draw, 1000-millis);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Show launcher when button pressed
|
const ClockFace = require("ClockFace"),
|
||||||
Bangle.setUI("clock");
|
clock = new ClockFace({
|
||||||
Bangle.on("lcdPower", function(on) {
|
precision:1,
|
||||||
if (on) {
|
init: function() {
|
||||||
draw(true);
|
const Layout = require("Layout");
|
||||||
}
|
this.layout = new Layout({
|
||||||
});
|
type: "v", c: [
|
||||||
g.reset().clear();
|
{
|
||||||
Bangle.drawWidgets();
|
type: "h", c: [
|
||||||
draw();
|
{id: "time", label: "88:88", type: "txt", font: "6x8:5", col:g.theme.fg, bgCol: g.theme.bg}, // size updated below
|
||||||
|
{id: "ampm", label: " ", type: "txt", font: "6x8:2", col:g.theme.fg, bgCol: g.theme.bg},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{id: "bar", type: "custom", fraction: 0, fillx: 1, height: 6, col: g.theme.fg2, render: renderBar},
|
||||||
|
{height: 40},
|
||||||
|
{id: "date", type: "txt", font: "10%", valign: 1},
|
||||||
|
],
|
||||||
|
}, {lazy: true});
|
||||||
|
// adjustments based on screen size and whether we display am/pm
|
||||||
|
let thickness; // bar thickness, same as time font "pixel block" size
|
||||||
|
if (this.is12Hour) {
|
||||||
|
// Maximum font size = (<screen width> - <ampm: 2chars * (2*6)px>) / (5chars * 6px)
|
||||||
|
thickness = Math.floor((Bangle.appRect.w-24)/(5*6));
|
||||||
|
} else {
|
||||||
|
this.layout.ampm.label = "";
|
||||||
|
thickness = Math.floor(Bangle.appRect.w/(5*6));
|
||||||
|
}
|
||||||
|
this.layout.bar.height = thickness+1;
|
||||||
|
this.layout.time.font = "6x8:"+thickness;
|
||||||
|
this.layout.update();
|
||||||
|
},
|
||||||
|
update: function(date, c) {
|
||||||
|
if (c.m) this.layout.time.label = timeText(date);
|
||||||
|
if (c.h) this.layout.ampm.label = ampmText(date);
|
||||||
|
if (c.d) this.layout.date.label = dateText(date);
|
||||||
|
const SECONDS_PER_MINUTE = 60;
|
||||||
|
if (c.s) this.layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE;
|
||||||
|
this.layout.render();
|
||||||
|
},
|
||||||
|
resume: function() {
|
||||||
|
this.layout.forgetLazyState();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
clock.start();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "barclock",
|
"id": "barclock",
|
||||||
"name": "Bar Clock",
|
"name": "Bar Clock",
|
||||||
"version": "0.09",
|
"version": "0.11",
|
||||||
"description": "A simple digital clock showing seconds as a bar",
|
"description": "A simple digital clock showing seconds as a bar",
|
||||||
"icon": "clock-bar.png",
|
"icon": "clock-bar.png",
|
||||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}],
|
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}],
|
||||||
|
|
|
@ -51,3 +51,4 @@
|
||||||
0.45: Fix 0.44 regression (auto-add semi-colon between each boot code chunk)
|
0.45: Fix 0.44 regression (auto-add semi-colon between each boot code chunk)
|
||||||
0.46: Fix no clock found error on Bangle.js 2
|
0.46: Fix no clock found error on Bangle.js 2
|
||||||
0.47: Add polyfill for setUI with an object as an argument (fix regression for 2v12 devices after Layout module changed)
|
0.47: Add polyfill for setUI with an object as an argument (fix regression for 2v12 devices after Layout module changed)
|
||||||
|
0.48: Workaround for BTHRM issues on Bangle.js 1 (write .boot files in chunks)
|
||||||
|
|
|
@ -197,8 +197,18 @@ bootFiles.forEach(bootFile=>{
|
||||||
require('Storage').write('.boot0',"//"+bootFile+"\n",fileOffset);
|
require('Storage').write('.boot0',"//"+bootFile+"\n",fileOffset);
|
||||||
fileOffset+=2+bootFile.length+1;
|
fileOffset+=2+bootFile.length+1;
|
||||||
var bf = require('Storage').read(bootFile);
|
var bf = require('Storage').read(bootFile);
|
||||||
require('Storage').write('.boot0',bf,fileOffset);
|
// we can't just write 'bf' in one go because at least in 2v13 and earlier
|
||||||
fileOffset+=bf.length;
|
// Espruino wants to read the whole file into RAM first, and on Bangle.js 1
|
||||||
|
// it can be too big (especially BTHRM).
|
||||||
|
var bflen = bf.length;
|
||||||
|
var bfoffset = 0;
|
||||||
|
while (bflen) {
|
||||||
|
var bfchunk = Math.min(bflen, 2048);
|
||||||
|
require('Storage').write('.boot0',bf.substr(bfoffset, bfchunk),fileOffset);
|
||||||
|
fileOffset+=bfchunk;
|
||||||
|
bfoffset+=bfchunk;
|
||||||
|
bflen-=bfchunk;
|
||||||
|
}
|
||||||
require('Storage').write('.boot0',";\n",fileOffset);
|
require('Storage').write('.boot0',";\n",fileOffset);
|
||||||
fileOffset+=2;
|
fileOffset+=2;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "boot",
|
"id": "boot",
|
||||||
"name": "Bootloader",
|
"name": "Bootloader",
|
||||||
"version": "0.47",
|
"version": "0.48",
|
||||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||||
"icon": "bootloader.png",
|
"icon": "bootloader.png",
|
||||||
"type": "bootloader",
|
"type": "bootloader",
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
0.01: New App
|
0.01: New App
|
||||||
0.02: app keeps track of statistics now
|
0.02: app keeps track of statistics now
|
||||||
|
0.03: Fix bug in valid word detection
|
||||||
|
|
|
@ -110,7 +110,12 @@ class Wordle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addGuess(w) {
|
addGuess(w) {
|
||||||
if ((this.words.indexOf(w.toLowerCase())%5)!=0) {
|
let idx = -1;
|
||||||
|
do{
|
||||||
|
idx = this.words.indexOf(w.toLowerCase(), idx+1);
|
||||||
|
}
|
||||||
|
while(idx !== -1 && idx%5 !== 0);
|
||||||
|
if(idx%5 !== 0) {
|
||||||
E.showAlert(w+"\nis not a word", "Invalid word").then(function() {
|
E.showAlert(w+"\nis not a word", "Invalid word").then(function() {
|
||||||
layout = getKeyLayout("");
|
layout = getKeyLayout("");
|
||||||
wordle.render(true);
|
wordle.render(true);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "Bordle",
|
"name": "Bordle",
|
||||||
"shortName":"Bordle",
|
"shortName":"Bordle",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"version":"0.02",
|
"version":"0.03",
|
||||||
"description": "Bangle version of a popular word search game",
|
"description": "Bangle version of a popular word search game",
|
||||||
"supports" : ["BANGLEJS2"],
|
"supports" : ["BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
{ "id": "bowserWF",
|
{
|
||||||
|
"id": "bowserWF",
|
||||||
"name": "Bowser Watchface",
|
"name": "Bowser Watchface",
|
||||||
"shortName":"Bowser Watchface",
|
"shortName":"Bowser Watchface",
|
||||||
"version":"0.01",
|
"version":"0.02",
|
||||||
"description": "Let bowser show you the time",
|
"description": "Let bowser show you the time",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "",
|
"type": "clock",
|
||||||
|
"tags": "clock",
|
||||||
"supports" : ["BANGLEJS2"],
|
"supports" : ["BANGLEJS2"],
|
||||||
|
"allow_emulator": true,
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"bowserWF.app.js","url":"app.js"},
|
{"name":"bowserWF.app.js","url":"app.js"},
|
||||||
{"name":"bowserWF.img","url":"app-icon.js","evaluate":true}
|
{"name":"bowserWF.img","url":"app-icon.js","evaluate":true}
|
||||||
]
|
],
|
||||||
|
"data": [{"name":"bowserWF.json"}]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwcCkGSpEgwQCChICFkgCBgkQoMEyFJAoICByVBkgLBkkSpIaDEwWShEkFgcIBAIdCEYQCBAoQdBAoYsBC4Q7BpICBEYQCDF4Q7CEYYCCEYUSKYYUDyRlCJQQIBNYYvBMoQCBkgjBFgxxCL4REDFgaPEHYgmCIgosCNYZEEDoZ0CNwY7CIIYgDEYtB9+e/dg/4AB2EJkYEB/mC/fn33Ivvz598v4MB/0BgoRCyVHvmW7Mg2EA8uD/EAh/IkGP/8AgVLtkA5El+FJvoRBgmf4Mkh0HkEQo9kyEfkeQofsgf4kmPCIP+h/gwULkkCncEu/ZsmRI4cEv0H8ESpdgEwMjwXI9kTCIOANYkSEYOCncF+UAjuR/ED+FBg/3/f8RgNgiVPkYdBtkT/Egv0Il+AoMfI4PgyX7vkW799F4Nl//4//woH/+0Ztvx7Fs335sk//5EB/IRBhACB77CBpEkgEIgGQoDRBgEggVBgDdBgGAgPv317ku+5cj334t+OSoI+B8gCBtlx7dkuFfgvx4N8yPbvgOB8ACBR4MA9mf4Egz3IgeChEDwDOBx/AjuCoN8y/JgkX4ME2FBjuQn65BgMtwELkGOEYOO4Mh2EJh+Sh/jOIMd+3fskRcwMTEwOWo98gCSBwFJkm2pfgx3II4PBk++/aABhEfwEInpZBvkX7MkJQMl2FHfANBjgCBlmQhHsgwjB33IkeyBAOChMcEwM9+/ZsBHBboMJtv2hd9+FHZANBVoM7kGC/fv2FJ9+GEYOAh//+UIaIMBkkQpEAHwIIBoMgiFJBANJEAMIkGShEkwQIChIIBhIIBhIaCkmQpIFCgmSEwYpDEYwCCpAICBwUEiQdFEwIICyAIDHwQ7CEYYpCEYWSpA7FDocSEwojBCgIaDIgYCBNwR0BNYYjFEwZTDLgQjGOgYvBEYQ7ENYlJFgQCCDohuGTYpBFkhoCSoQICEYIA="))
|
After Width: | Height: | Size: 4.0 KiB |
|
@ -0,0 +1,14 @@
|
||||||
|
{ "id": "bradbury",
|
||||||
|
"name": "Bradbury Watch",
|
||||||
|
"shortName":"Bradbury",
|
||||||
|
"icon": "app.png",
|
||||||
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "A watch face based on the classic Seiko model worn by one of my favorite authors. I didn't follow the original lcd layout exactly, opting for larger font for more easily readable time, and adding date, battery level, and step count; read from the device. Tapping the screen toggles visibility of widgets.",
|
||||||
|
"type": "clock",
|
||||||
|
"supports":["BANGLEJS2"],
|
||||||
|
"storage": [
|
||||||
|
{"name":"bradbury.app.js","url":"app.js"},
|
||||||
|
{"name":"bradbury.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
}
|
After Width: | Height: | Size: 7.4 KiB |
|
@ -21,3 +21,4 @@
|
||||||
Adds some preset modes and a custom one
|
Adds some preset modes and a custom one
|
||||||
Restructure the settings menu
|
Restructure the settings menu
|
||||||
0.08: Allow scanning for devices in settings
|
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.
|
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:
|
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.
|
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
|
Once installed you will have to go into this app's settings while your heart rate monitor
|
||||||
heart rate monitor it finds.
|
is available for bluetooth pairing and scan for devices.
|
||||||
|
|
||||||
**To disable this and return to normal HRM, uninstall the app**
|
**To disable this and return to normal HRM, uninstall the app**
|
||||||
|
|
||||||
## Compatible Heart Rate Monitors
|
## Compatible Heart Rate Monitors
|
||||||
|
|
||||||
This works with any heart rate monitor providing the standard Bluetooth
|
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:
|
So far it has been tested on:
|
||||||
|
|
||||||
* CooSpo Bluetooth Heart Rate Monitor
|
* CooSpo Bluetooth Heart Rate Monitor
|
||||||
|
* Polar H10
|
||||||
|
* Polar OH1
|
||||||
* Wahoo TICKR X 2
|
* Wahoo TICKR X 2
|
||||||
|
|
||||||
## Internals
|
## Internals
|
||||||
|
@ -38,7 +42,6 @@ This replaces `Bangle.setHRMPower` with its own implementation.
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
* A widget to show connection state?
|
* A widget to show connection state?
|
||||||
* Specify a specific device by address?
|
|
||||||
|
|
||||||
## Creator
|
## Creator
|
||||||
|
|
||||||
|
|
|
@ -18,34 +18,33 @@
|
||||||
|
|
||||||
if (settings.enabled){
|
if (settings.enabled){
|
||||||
|
|
||||||
function clearCache(){
|
var clearCache = function() {
|
||||||
return require('Storage').erase("bthrm.cache.json");
|
return require('Storage').erase("bthrm.cache.json");
|
||||||
}
|
};
|
||||||
|
|
||||||
function getCache(){
|
var getCache = function() {
|
||||||
var cache = require('Storage').readJSON("bthrm.cache.json", true) || {};
|
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();
|
clearCache();
|
||||||
return {};
|
return {};
|
||||||
}
|
};
|
||||||
|
|
||||||
function addNotificationHandler(characteristic){
|
var addNotificationHandler = function(characteristic) {
|
||||||
log("Setting notification handler: " + supportedCharacteristics[characteristic.uuid].handler);
|
log("Setting notification handler: " + supportedCharacteristics[characteristic.uuid].handler);
|
||||||
characteristic.on('characteristicvaluechanged', supportedCharacteristics[characteristic.uuid].handler);
|
characteristic.on('characteristicvaluechanged', (ev) => supportedCharacteristics[characteristic.uuid].handler(ev.target.value));
|
||||||
}
|
};
|
||||||
|
|
||||||
function writeCache(cache){
|
var writeCache = function(cache) {
|
||||||
var oldCache = getCache();
|
var oldCache = getCache();
|
||||||
if (oldCache != cache) {
|
if (oldCache !== cache) {
|
||||||
log("Writing cache");
|
log("Writing cache");
|
||||||
require('Storage').writeJSON("bthrm.cache.json", cache)
|
require('Storage').writeJSON("bthrm.cache.json", cache);
|
||||||
} else {
|
} else {
|
||||||
log("No changes, don't write cache");
|
log("No changes, don't write cache");
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
var characteristicsToCache = function(characteristics) {
|
||||||
|
|
||||||
function characteristicsToCache(characteristics){
|
|
||||||
log("Cache characteristics");
|
log("Cache characteristics");
|
||||||
var cache = getCache();
|
var cache = getCache();
|
||||||
if (!cache.characteristics) cache.characteristics = {};
|
if (!cache.characteristics) cache.characteristics = {};
|
||||||
|
@ -60,9 +59,9 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
writeCache(cache);
|
writeCache(cache);
|
||||||
}
|
};
|
||||||
|
|
||||||
function characteristicsFromCache(){
|
var characteristicsFromCache = function() {
|
||||||
log("Read cached characteristics");
|
log("Read cached characteristics");
|
||||||
var cache = getCache();
|
var cache = getCache();
|
||||||
if (!cache.characteristics) return [];
|
if (!cache.characteristics) return [];
|
||||||
|
@ -81,26 +80,22 @@
|
||||||
restored.push(r);
|
restored.push(r);
|
||||||
}
|
}
|
||||||
return restored;
|
return restored;
|
||||||
}
|
};
|
||||||
|
|
||||||
log("Start");
|
log("Start");
|
||||||
|
|
||||||
var lastReceivedData={
|
var lastReceivedData={
|
||||||
};
|
};
|
||||||
|
|
||||||
var serviceFilters = [{
|
var supportedServices = [
|
||||||
services: [ "180d" ]
|
"0x180d", // Heart Rate
|
||||||
}];
|
"0x180f", // Battery
|
||||||
|
|
||||||
supportedServices = [
|
|
||||||
"0x180d", "0x180f"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
var supportedCharacteristics = {
|
var supportedCharacteristics = {
|
||||||
"0x2a37": {
|
"0x2a37": {
|
||||||
//Heart rate measurement
|
//Heart rate measurement
|
||||||
handler: function (event){
|
handler: function (dv){
|
||||||
var dv = event.target.value;
|
|
||||||
var flags = dv.getUint8(0);
|
var flags = dv.getUint8(0);
|
||||||
|
|
||||||
var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit
|
var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit
|
||||||
|
@ -108,7 +103,7 @@
|
||||||
var sensorContact;
|
var sensorContact;
|
||||||
|
|
||||||
if (flags & 2){
|
if (flags & 2){
|
||||||
sensorContact = (flags & 4) ? true : false;
|
sensorContact = !!(flags & 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
var idx = 2 + (flags&1);
|
var idx = 2 + (flags&1);
|
||||||
|
@ -121,11 +116,11 @@
|
||||||
var interval;
|
var interval;
|
||||||
if (flags & 16) {
|
if (flags & 16) {
|
||||||
interval = [];
|
interval = [];
|
||||||
maxIntervalBytes = (dv.byteLength - idx);
|
var maxIntervalBytes = (dv.byteLength - idx);
|
||||||
log("Found " + (maxIntervalBytes / 2) + " rr data fields");
|
log("Found " + (maxIntervalBytes / 2) + " rr data fields");
|
||||||
for(var i = 0 ; i < maxIntervalBytes / 2; i++){
|
for(var i = 0 ; i < maxIntervalBytes / 2; i++){
|
||||||
interval[i] = dv.getUint16(idx,1); // in milliseconds
|
interval[i] = dv.getUint16(idx,1); // in milliseconds
|
||||||
idx += 2
|
idx += 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,14 +135,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.replace){
|
if (settings.replace){
|
||||||
var newEvent = {
|
var repEvent = {
|
||||||
bpm: bpm,
|
bpm: bpm,
|
||||||
confidence: (sensorContact || sensorContact === undefined)? 100 : 0,
|
confidence: (sensorContact || sensorContact === undefined)? 100 : 0,
|
||||||
src: "bthrm"
|
src: "bthrm"
|
||||||
};
|
};
|
||||||
|
|
||||||
log("Emitting HRM: ", newEvent);
|
log("Emitting HRM: ", repEvent);
|
||||||
Bangle.emit("HRM", newEvent);
|
Bangle.emit("HRM", repEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
var newEvent = {
|
var newEvent = {
|
||||||
|
@ -166,19 +161,18 @@
|
||||||
},
|
},
|
||||||
"0x2a38": {
|
"0x2a38": {
|
||||||
//Body sensor location
|
//Body sensor location
|
||||||
handler: function(data){
|
handler: function(dv){
|
||||||
if (!lastReceivedData["0x180d"]) lastReceivedData["0x180d"] = {};
|
if (!lastReceivedData["0x180d"]) lastReceivedData["0x180d"] = {};
|
||||||
if (!lastReceivedData["0x180d"]["0x2a38"]) lastReceivedData["0x180d"]["0x2a38"] = data.target.value;
|
lastReceivedData["0x180d"]["0x2a38"] = parseInt(dv.buffer, 10);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"0x2a19": {
|
"0x2a19": {
|
||||||
//Battery
|
//Battery
|
||||||
handler: function (event){
|
handler: function (dv){
|
||||||
if (!lastReceivedData["0x180f"]) lastReceivedData["0x180f"] = {};
|
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;
|
var device;
|
||||||
|
@ -195,7 +189,7 @@
|
||||||
maxInterval: 1500
|
maxInterval: 1500
|
||||||
};
|
};
|
||||||
|
|
||||||
function waitingPromise(timeout) {
|
var waitingPromise = function(timeout) {
|
||||||
return new Promise(function(resolve){
|
return new Promise(function(resolve){
|
||||||
log("Start waiting for " + timeout);
|
log("Start waiting for " + timeout);
|
||||||
setTimeout(()=>{
|
setTimeout(()=>{
|
||||||
|
@ -203,7 +197,7 @@
|
||||||
resolve();
|
resolve();
|
||||||
}, timeout);
|
}, timeout);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
if (settings.enabled){
|
if (settings.enabled){
|
||||||
Bangle.isBTHRMOn = function(){
|
Bangle.isBTHRMOn = function(){
|
||||||
|
@ -215,7 +209,6 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (settings.replace){
|
if (settings.replace){
|
||||||
var origIsHRMOn = Bangle.isHRMOn;
|
var origIsHRMOn = Bangle.isHRMOn;
|
||||||
|
|
||||||
|
@ -229,15 +222,15 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearRetryTimeout(){
|
var clearRetryTimeout = function() {
|
||||||
if (currentRetryTimeout){
|
if (currentRetryTimeout){
|
||||||
log("Clearing timeout " + currentRetryTimeout);
|
log("Clearing timeout " + currentRetryTimeout);
|
||||||
clearTimeout(currentRetryTimeout);
|
clearTimeout(currentRetryTimeout);
|
||||||
currentRetryTimeout = undefined;
|
currentRetryTimeout = undefined;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
function retry(){
|
var retry = function() {
|
||||||
log("Retry");
|
log("Retry");
|
||||||
|
|
||||||
if (!currentRetryTimeout){
|
if (!currentRetryTimeout){
|
||||||
|
@ -252,17 +245,17 @@
|
||||||
initBt();
|
initBt();
|
||||||
}, clampedTime);
|
}, clampedTime);
|
||||||
|
|
||||||
retryTime = Math.pow(retryTime, 1.1);
|
retryTime = Math.pow(clampedTime, 1.1);
|
||||||
if (retryTime > maxRetryTime){
|
if (retryTime > maxRetryTime){
|
||||||
retryTime = maxRetryTime;
|
retryTime = maxRetryTime;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log("Already in retry...");
|
log("Already in retry...");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
var buzzing = false;
|
var buzzing = false;
|
||||||
function onDisconnect(reason) {
|
var onDisconnect = function(reason) {
|
||||||
log("Disconnect: " + reason);
|
log("Disconnect: " + reason);
|
||||||
log("GATT: ", gatt);
|
log("GATT: ", gatt);
|
||||||
log("Characteristics: ", characteristics);
|
log("Characteristics: ", characteristics);
|
||||||
|
@ -277,11 +270,23 @@
|
||||||
if (Bangle.isBTHRMOn()){
|
if (Bangle.isBTHRMOn()){
|
||||||
retry();
|
retry();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
function createCharacteristicPromise(newCharacteristic){
|
var createCharacteristicPromise = function(newCharacteristic) {
|
||||||
log("Create characteristic promise: ", newCharacteristic);
|
log("Create characteristic promise: ", newCharacteristic);
|
||||||
var result = Promise.resolve();
|
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){
|
if (newCharacteristic.properties.notify){
|
||||||
result = result.then(()=>{
|
result = result.then(()=>{
|
||||||
log("Starting notifications for: ", newCharacteristic);
|
log("Starting notifications for: ", newCharacteristic);
|
||||||
|
@ -290,31 +295,23 @@
|
||||||
log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications");
|
log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications");
|
||||||
startPromise = startPromise.then(()=>{
|
startPromise = startPromise.then(()=>{
|
||||||
log("Wait after connect");
|
log("Wait after connect");
|
||||||
waitingPromise(settings.gracePeriodNotification)
|
return waitingPromise(settings.gracePeriodNotification);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return startPromise;
|
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));
|
return result.then(()=>log("Handled characteristic: ", newCharacteristic));
|
||||||
}
|
};
|
||||||
|
|
||||||
function attachCharacteristicPromise(promise, characteristic){
|
var attachCharacteristicPromise = function(promise, characteristic) {
|
||||||
return promise.then(()=>{
|
return promise.then(()=>{
|
||||||
log("Handling characteristic:", characteristic);
|
log("Handling characteristic:", characteristic);
|
||||||
return createCharacteristicPromise(characteristic);
|
return createCharacteristicPromise(characteristic);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
function createCharacteristicsPromise(newCharacteristics){
|
var createCharacteristicsPromise = function(newCharacteristics) {
|
||||||
log("Create characteristics promise: ", newCharacteristics);
|
log("Create characteristics promise: ", newCharacteristics);
|
||||||
var result = Promise.resolve();
|
var result = Promise.resolve();
|
||||||
for (var c of newCharacteristics){
|
for (var c of newCharacteristics){
|
||||||
|
@ -328,9 +325,9 @@
|
||||||
result = attachCharacteristicPromise(result, c);
|
result = attachCharacteristicPromise(result, c);
|
||||||
}
|
}
|
||||||
return result.then(()=>log("Handled characteristics"));
|
return result.then(()=>log("Handled characteristics"));
|
||||||
}
|
};
|
||||||
|
|
||||||
function createServicePromise(service){
|
var createServicePromise = function(service) {
|
||||||
log("Create service promise: ", service);
|
log("Create service promise: ", service);
|
||||||
var result = Promise.resolve();
|
var result = Promise.resolve();
|
||||||
result = result.then(()=>{
|
result = result.then(()=>{
|
||||||
|
@ -338,15 +335,13 @@
|
||||||
return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c));
|
return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c));
|
||||||
});
|
});
|
||||||
return result.then(()=>log("Handled service" + service.uuid));
|
return result.then(()=>log("Handled service" + service.uuid));
|
||||||
}
|
};
|
||||||
|
|
||||||
function attachServicePromise(promise, service){
|
var attachServicePromise = function(promise, service) {
|
||||||
return promise.then(()=>createServicePromise(service));
|
return promise.then(()=>createServicePromise(service));
|
||||||
}
|
};
|
||||||
|
|
||||||
var reUseCounter = 0;
|
var initBt = function () {
|
||||||
|
|
||||||
function initBt() {
|
|
||||||
log("initBt with blockInit: " + blockInit);
|
log("initBt with blockInit: " + blockInit);
|
||||||
if (blockInit){
|
if (blockInit){
|
||||||
retry();
|
retry();
|
||||||
|
@ -355,22 +350,18 @@
|
||||||
|
|
||||||
blockInit = true;
|
blockInit = true;
|
||||||
|
|
||||||
if (reUseCounter > 10){
|
|
||||||
log("Reuse counter to high");
|
|
||||||
gatt=undefined;
|
|
||||||
reUseCounter = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var promise;
|
var promise;
|
||||||
|
var filters;
|
||||||
|
|
||||||
if (!device){
|
if (!device){
|
||||||
var filters = serviceFilters;
|
if (settings.btid){
|
||||||
if (settings.btname){
|
log("Configured device id", settings.btid);
|
||||||
log("Configured device name", settings.btname);
|
filters = [{ id: settings.btid }];
|
||||||
filters = [{name: settings.btname}];
|
} else {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
log("Requesting device with filters", filters);
|
log("Requesting device with filters", filters);
|
||||||
promise = NRF.requestDevice({ filters: filters });
|
promise = NRF.requestDevice({ filters: filters, active: true });
|
||||||
|
|
||||||
if (settings.gracePeriodRequest){
|
if (settings.gracePeriodRequest){
|
||||||
log("Add " + settings.gracePeriodRequest + "ms grace period after request");
|
log("Add " + settings.gracePeriodRequest + "ms grace period after request");
|
||||||
|
@ -386,7 +377,6 @@
|
||||||
log("Wait after request");
|
log("Wait after request");
|
||||||
return waitingPromise(settings.gracePeriodRequest);
|
return waitingPromise(settings.gracePeriodRequest);
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
promise = Promise.resolve();
|
promise = Promise.resolve();
|
||||||
log("Reuse device: ", device);
|
log("Reuse device: ", device);
|
||||||
|
@ -398,13 +388,13 @@
|
||||||
} else {
|
} else {
|
||||||
log("GATT is new: ", gatt);
|
log("GATT is new: ", gatt);
|
||||||
characteristics = [];
|
characteristics = [];
|
||||||
var cachedName = getCache().name;
|
var cachedId = getCache().id;
|
||||||
if (device.name != cachedName){
|
if (device.id !== cachedId){
|
||||||
log("Device name changed from " + cachedName + " to " + device.name + ", clearing cache");
|
log("Device ID changed from " + cachedId + " to " + device.id + ", clearing cache");
|
||||||
clearCache();
|
clearCache();
|
||||||
}
|
}
|
||||||
var newCache = getCache();
|
var newCache = getCache();
|
||||||
newCache.name = device.name;
|
newCache.id = device.id;
|
||||||
writeCache(newCache);
|
writeCache(newCache);
|
||||||
gatt = device.gatt;
|
gatt = device.gatt;
|
||||||
}
|
}
|
||||||
|
@ -428,15 +418,27 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* 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(()=>{
|
promise = promise.then(()=>{
|
||||||
if (!characteristics || characteristics.length == 0){
|
if (!characteristics || characteristics.length === 0){
|
||||||
characteristics = characteristicsFromCache();
|
characteristics = characteristicsFromCache();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
promise = promise.then(()=>{
|
promise = promise.then(()=>{
|
||||||
var characteristicsPromise = Promise.resolve();
|
var characteristicsPromise = Promise.resolve();
|
||||||
if (characteristics.length == 0){
|
if (characteristics.length === 0){
|
||||||
characteristicsPromise = characteristicsPromise.then(()=>{
|
characteristicsPromise = characteristicsPromise.then(()=>{
|
||||||
log("Getting services");
|
log("Getting services");
|
||||||
return gatt.getPrimaryServices();
|
return gatt.getPrimaryServices();
|
||||||
|
@ -454,12 +456,11 @@
|
||||||
log("Add " + settings.gracePeriodService + "ms grace period after services");
|
log("Add " + settings.gracePeriodService + "ms grace period after services");
|
||||||
result = result.then(()=>{
|
result = result.then(()=>{
|
||||||
log("Wait after services");
|
log("Wait after services");
|
||||||
return waitingPromise(settings.gracePeriodService)
|
return waitingPromise(settings.gracePeriodService);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
for (var characteristic of characteristics){
|
for (var characteristic of characteristics){
|
||||||
characteristicsPromise = attachCharacteristicPromise(characteristicsPromise, characteristic, true);
|
characteristicsPromise = attachCharacteristicPromise(characteristicsPromise, characteristic, true);
|
||||||
|
@ -469,9 +470,8 @@
|
||||||
return characteristicsPromise;
|
return characteristicsPromise;
|
||||||
});
|
});
|
||||||
|
|
||||||
promise = promise.then(()=>{
|
return promise.then(()=>{
|
||||||
log("Connection established, waiting for notifications");
|
log("Connection established, waiting for notifications");
|
||||||
reUseCounter = 0;
|
|
||||||
characteristicsToCache(characteristics);
|
characteristicsToCache(characteristics);
|
||||||
clearRetryTimeout();
|
clearRetryTimeout();
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
|
@ -479,7 +479,7 @@
|
||||||
log("Error:", e);
|
log("Error:", e);
|
||||||
onDisconnect(e);
|
onDisconnect(e);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
Bangle.setBTHRMPower = function(isOn, app) {
|
Bangle.setBTHRMPower = function(isOn, app) {
|
||||||
// Do app power handling
|
// Do app power handling
|
||||||
|
@ -487,7 +487,7 @@
|
||||||
if (Bangle._PWR===undefined) Bangle._PWR={};
|
if (Bangle._PWR===undefined) Bangle._PWR={};
|
||||||
if (Bangle._PWR.BTHRM===undefined) Bangle._PWR.BTHRM=[];
|
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.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;
|
isOn = Bangle._PWR.BTHRM.length;
|
||||||
// so now we know if we're really on
|
// so now we know if we're really on
|
||||||
if (isOn) {
|
if (isOn) {
|
||||||
|
@ -526,10 +526,9 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var fallbackInterval;
|
var fallbackInterval;
|
||||||
|
|
||||||
function switchInternalHrm(){
|
var switchInternalHrm = function() {
|
||||||
if (settings.allowFallback && !fallbackInterval){
|
if (settings.allowFallback && !fallbackInterval){
|
||||||
log("Fallback to HRM enabled");
|
log("Fallback to HRM enabled");
|
||||||
origSetHRMPower(1, "bthrm_fallback");
|
origSetHRMPower(1, "bthrm_fallback");
|
||||||
|
@ -542,7 +541,7 @@
|
||||||
}
|
}
|
||||||
}, settings.fallbackTimeout);
|
}, settings.fallbackTimeout);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (settings.replace){
|
if (settings.replace){
|
||||||
log("Replace HRM event");
|
log("Replace HRM event");
|
||||||
|
@ -561,7 +560,7 @@
|
||||||
E.on("kill", ()=>{
|
E.on("kill", ()=>{
|
||||||
if (gatt && gatt.connected){
|
if (gatt && gatt.connected){
|
||||||
log("Got killed, trying to disconnect");
|
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 intervalInt;
|
||||||
var intervalBt;
|
var intervalBt;
|
||||||
|
|
||||||
|
var BODY_LOCS = {
|
||||||
|
0: 'Other',
|
||||||
|
1: 'Chest',
|
||||||
|
2: 'Wrist',
|
||||||
|
3: 'Finger',
|
||||||
|
4: 'Hand',
|
||||||
|
5: 'Ear Lobe',
|
||||||
|
6: 'Foot',
|
||||||
|
}
|
||||||
|
|
||||||
function clear(y){
|
function clear(y){
|
||||||
g.reset();
|
g.reset();
|
||||||
g.clearRect(0,y,g.getWidth(),y+75);
|
g.clearRect(0,y,g.getWidth(),y+75);
|
||||||
|
@ -15,17 +24,17 @@ function draw(y, type, event) {
|
||||||
g.setFontAlign(0,0);
|
g.setFontAlign(0,0);
|
||||||
g.setFontVector(40).drawString(str,px,y+20);
|
g.setFontVector(40).drawString(str,px,y+20);
|
||||||
str = "Event: " + type;
|
str = "Event: " + type;
|
||||||
if (type == "HRM") {
|
if (type === "HRM") {
|
||||||
str += " Confidence: " + event.confidence;
|
str += " Confidence: " + event.confidence;
|
||||||
g.setFontVector(12).drawString(str,px,y+40);
|
g.setFontVector(12).drawString(str,px,y+40);
|
||||||
str = " Source: " + (event.src ? event.src : "internal");
|
str = " Source: " + (event.src ? event.src : "internal");
|
||||||
g.setFontVector(12).drawString(str,px,y+50);
|
g.setFontVector(12).drawString(str,px,y+50);
|
||||||
}
|
}
|
||||||
if (type == "BTHRM"){
|
if (type === "BTHRM"){
|
||||||
if (event.battery) str += " Bat: " + (event.battery ? event.battery : "");
|
if (event.battery) str += " Bat: " + (event.battery ? event.battery : "");
|
||||||
g.setFontVector(12).drawString(str,px,y+40);
|
g.setFontVector(12).drawString(str,px,y+40);
|
||||||
str= "";
|
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(",");
|
if (event.rr && event.rr.length > 0) str += " RR: " + event.rr.join(",");
|
||||||
g.setFontVector(12).drawString(str,px,y+50);
|
g.setFontVector(12).drawString(str,px,y+50);
|
||||||
str= "";
|
str= "";
|
||||||
|
@ -45,7 +54,7 @@ function onBtHrm(e) {
|
||||||
firstEventBt = false;
|
firstEventBt = false;
|
||||||
}
|
}
|
||||||
draw(100, "BTHRM", e);
|
draw(100, "BTHRM", e);
|
||||||
if (e.bpm == 0){
|
if (e.bpm === 0){
|
||||||
Bangle.buzz(100,0.2);
|
Bangle.buzz(100,0.2);
|
||||||
}
|
}
|
||||||
if (intervalBt){
|
if (intervalBt){
|
||||||
|
|
|
@ -7,10 +7,10 @@
|
||||||
"allowFallback": true,
|
"allowFallback": true,
|
||||||
"warnDisconnect": false,
|
"warnDisconnect": false,
|
||||||
"fallbackTimeout": 10,
|
"fallbackTimeout": 10,
|
||||||
"custom_replace": false,
|
"custom_replace": true,
|
||||||
"custom_debuglog": false,
|
"custom_debuglog": false,
|
||||||
"custom_startWithHrm": false,
|
"custom_startWithHrm": true,
|
||||||
"custom_allowFallback": false,
|
"custom_allowFallback": true,
|
||||||
"custom_warnDisconnect": false,
|
"custom_warnDisconnect": false,
|
||||||
"custom_fallbackTimeout": 10,
|
"custom_fallbackTimeout": 10,
|
||||||
"gracePeriodNotification": 0,
|
"gracePeriodNotification": 0,
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
"id": "bthrm",
|
"id": "bthrm",
|
||||||
"name": "Bluetooth Heart Rate Monitor",
|
"name": "Bluetooth Heart Rate Monitor",
|
||||||
"shortName": "BT HRM",
|
"shortName": "BT HRM",
|
||||||
"version": "0.08",
|
"version": "0.09",
|
||||||
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
"tags": "health,bluetooth",
|
"tags": "health,bluetooth,hrm,bthrm",
|
||||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
|
|
|
@ -61,12 +61,13 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (settings.btname){
|
if (settings.btname || settings.btid){
|
||||||
var name = "Clear " + settings.btname;
|
var name = "Clear " + (settings.btname || settings.btid);
|
||||||
mainmenu[name] = function() {
|
mainmenu[name] = function() {
|
||||||
E.showPrompt("Clear current device name?").then((r)=>{
|
E.showPrompt("Clear current device?").then((r)=>{
|
||||||
if (r) {
|
if (r) {
|
||||||
writeSettings("btname",undefined);
|
writeSettings("btname",undefined);
|
||||||
|
writeSettings("btid",undefined);
|
||||||
}
|
}
|
||||||
E.showMenu(buildMainMenu());
|
E.showMenu(buildMainMenu());
|
||||||
});
|
});
|
||||||
|
@ -79,8 +80,6 @@
|
||||||
return mainmenu;
|
return mainmenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var submenu_debug = {
|
var submenu_debug = {
|
||||||
'' : { title: "Debug"},
|
'' : { title: "Debug"},
|
||||||
'< Back': function() { E.showMenu(buildMainMenu()); },
|
'< Back': function() { E.showMenu(buildMainMenu()); },
|
||||||
|
@ -103,35 +102,39 @@
|
||||||
|
|
||||||
function createMenuFromScan(){
|
function createMenuFromScan(){
|
||||||
E.showMenu();
|
E.showMenu();
|
||||||
E.showMessage("Scanning");
|
E.showMessage("Scanning for 4 seconds");
|
||||||
|
|
||||||
var submenu_scan = {
|
var submenu_scan = {
|
||||||
'' : { title: "Scan"},
|
|
||||||
'< Back': function() { E.showMenu(buildMainMenu()); }
|
'< Back': function() { E.showMenu(buildMainMenu()); }
|
||||||
};
|
};
|
||||||
var packets=10;
|
NRF.findDevices(function(devices) {
|
||||||
var scanStart=Date.now();
|
submenu_scan[''] = { title: `Scan (${devices.length} found)`};
|
||||||
NRF.setScan(function(d) {
|
if (devices.length === 0) {
|
||||||
packets--;
|
E.showAlert("No devices found")
|
||||||
if (packets<=0 || Date.now() - scanStart > 5000){
|
.then(() => E.showMenu(buildMainMenu()));
|
||||||
NRF.setScan();
|
return;
|
||||||
E.showMenu(submenu_scan);
|
} else {
|
||||||
} else if (d.name){
|
devices.forEach((d) => {
|
||||||
print("Found device", d);
|
print("Found device", d);
|
||||||
submenu_scan[d.name] = function(){
|
var shown = (d.name || d.id.substr(0, 17));
|
||||||
E.showPrompt("Set "+d.name+"?").then((r)=>{
|
submenu_scan[shown] = function () {
|
||||||
if (r) {
|
E.showPrompt("Set " + shown + "?").then((r) => {
|
||||||
writeSettings("btname",d.name);
|
if (r) {
|
||||||
}
|
writeSettings("btid", d.id);
|
||||||
E.showMenu(buildMainMenu());
|
// 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 = {
|
var submenu_custom = {
|
||||||
'' : { title: "Custom mode"},
|
'' : { title: "Custom mode"},
|
||||||
'< Back': function() { E.showMenu(buildMainMenu()); },
|
'< Back': function() { E.showMenu(buildMainMenu()); },
|
||||||
|
@ -213,50 +216,5 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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());
|
E.showMenu(buildMainMenu());
|
||||||
})
|
});
|
||||||
|
|
|
@ -4,3 +4,6 @@
|
||||||
0.04: Steps can be hidden now such that the time is even larger.
|
0.04: Steps can be hidden now such that the time is even larger.
|
||||||
0.05: Included icons for information.
|
0.05: Included icons for information.
|
||||||
0.06: Design and usability improvements.
|
0.06: Design and usability improvements.
|
||||||
|
0.07: Improved positioning.
|
||||||
|
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.
|
|
@ -8,6 +8,7 @@
|
||||||
- Enable / disable lock icon in the settings.
|
- Enable / disable lock icon in the settings.
|
||||||
- If the "sched" app is installed tab top / bottom of the screen to set the timer.
|
- If the "sched" app is installed tab top / bottom of the screen to set the timer.
|
||||||
- The design is adapted to the theme of your bangle.
|
- The design is adapted to the theme of your bangle.
|
||||||
|
- The colon (e.g. 7:35 = 735) can be hidden now in the settings.
|
||||||
|
|
||||||
## Thanks to
|
## Thanks to
|
||||||
<a href="https://www.flaticon.com/free-icons/" title="Icons">Icons created by Flaticon</a>
|
<a href="https://www.flaticon.com/free-icons/" title="Icons">Icons created by Flaticon</a>
|
||||||
|
|
|
@ -18,6 +18,7 @@ const H = g.getHeight();
|
||||||
let settings = {
|
let settings = {
|
||||||
fullscreen: false,
|
fullscreen: false,
|
||||||
showLock: true,
|
showLock: true,
|
||||||
|
hideColon: false,
|
||||||
showInfo: 0,
|
showInfo: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -33,11 +34,25 @@ for (const key in saved_settings) {
|
||||||
|
|
||||||
// Manrope font
|
// Manrope font
|
||||||
Graphics.prototype.setLargeFont = function(scale) {
|
Graphics.prototype.setLargeFont = function(scale) {
|
||||||
// Actual height 49 (50 - 2)
|
// Actual height 48 (49 - 2)
|
||||||
this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAAAfwAAAAAAAAf/AAAAAAAAf/8AAAAAAAf//wAAAAAAP///AAAAAAP///8AAAAAP////wAAAAP////4AAAAP////8AAAAH////8AAAAH////8AAAAB////8AAAAAH///+AAAAAAf//+AAAAAAB//+AAAAAAAH/+AAAAAAAAf+AAAAAAAAB/AAAAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///gAAAAAD////4AAAAA/////4AAAAH/////4AAAA//////wAAAH//////gAAA///////AAAH//////+AAA///////4AAD/4AAAH/wAAP+AAAAP/AAB/wAAAAf8AAH/AAAAA/4AAf4AAAAB/gAB/gAAAAH+AAP8AAAAAf4AA/wAAAAB/gAD/AAAAAH+AAP8AAAAAf4AAf4AAAAB/gAB/gAAAAH+AAH+AAAAA/4AAf8AAAAH/AAB/4AAAA/8AAD/4AAAH/wAAP/8AAH/+AAAf//////4AAA///////AAAB//////4AAAD//////AAAAH/////4AAAAP////+AAAAAP////gAAAAAD///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAAAAAB/wAAAAAAAAH/AAAAAAAAA/4AAAAAAAAH/gAAAAAAAAf8AAAAAAAAD/gAAAAAAAAP+AAAAAAAAB///////8AAH///////wAAf///////AAB///////8AAH///////wAAf///////AAB///////8AAH///////wAAP///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAfwAAAH8AAAD/AAAB/wAAAf8AAAP/AAAD/wAAB/8AAAf/AAAP/wAAD/8AAB//AAAf/wAAH/8AAD//AAA//gAAf/8AAD/wAAB//wAAf+AAAP//AAB/wAAB//8AAH+AAAP//wAAf4AAB///AAD/AAAP/v8AAP8AAB/8/wAA/wAAP/j/AAD/AAB/8P8AAH+AAH/g/wAAf4AA/8D/AAB/wAH/gP8AAH/AA/+A/wAAf/AP/wD/AAA//D/+AP8AAD////wA/wAAH///+AD/AAAP///wAP8AAAf//+AA/wAAA///wAD/AAAB//+AAP8AAAB//gAA/wAAAB/4AAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAD+AAAAHwAAAf4AAAAfwAAB/gAAAB/gAAH+AAAAP/AAAf4AAAA/8AAB/gAAAD/4AAH+ADAAH/wAAf4AeAAP/AAB/gD+AAP8AAH+Af+AA/4AAf4D/4AB/gAB/gP/AAH+AAH+B/8AAf4AAf4P/wAB/gAB/h//AAH+AAH+P/8AAf4AAf5//wAB/gAB/v//gAP+AAH+//+AA/4AAf//f8AH/AAB//5/8B/8AAH//D////gAAf/4P///+AAB//Af///wAAH/4A///+AAAf/AB///wAAB/4AD//+AAAH/AAH//gAAAP4AAD/4AAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAA/+AAAAAAAAP/4AAAAAAAH//gAAAAAAB//+AAAAAAAf//4AAAAAAH///gAAAAAB///+AAAAAAf///4AAAAAH//9/gAAAAD///H+AAAAA///wf4AAAAP//8B/gAAAD///AH+AAAA///wAf4AAAH//8AB/gAAAf//AAH+AAAB//gAAf4AAAH/4AAB/gAAAf+AAAH+AAAB/gAf///8AAH4AB////wAAeAAH////AABgAAf///8AAAAAB////wAAAAAH////AAAAAAf///8AAAAAB////wAAAAAH////AAAAAAAAf4AAAAAAAAB/gAAAAAAAAH+AAAAAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAeAAAAAf//AB+AAAH///+AP8AAAf///4A/4AAB////gD/wAAH////Af/gAAf///8B/+AAB////wB/8AAH///+AB/wAAf4Af4AD/gAB/gB/AAP+AAH+AP8AAf4AAf4A/wAB/gAB/gD+AAH+AAH+AP4AAf4AAf4A/gAB/gAB/gD/AAH+AAH+AP8AAf4AAf4A/wAD/gAB/gD/gAf8AAH+AH/AD/wAAf4Af/Af+AAB/gB////4AAH+AD////AAAf4AH///8AAB/gAP///gAAH+AA///8AAAAAAA///AAAAAAAB//4AAAAAAAB/+AAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///gAAAAAB////4AAAAAf////4AAAAH/////4AAAA//////wAAAH//////gAAA///////AAAH//////+AAAf//////4AAD/4D/wH/wAAP+AP8AP/AAB/wB/gAf8AAH/AH8AA/4AAf4A/wAB/gAB/gD/AAH+AAH8AP4AAf4AA/wA/gAB/gAD/AD+AAH+AAH8AP8AAf4AAf4A/wAB/gAB/gD/AAP+AAH+AP+AB/wAAf8Af8AP/AAA/4B/8B/8AAD/gH////gAAP8AP///8AAAfgAf///wAAA8AB///+AAADgAD///wAAAAAAD//+AAAAAAAH//gAAAAAAAH/4AAAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAAAH+AAAAAAAAAf4AAAAAAAAB/gAAAAAAAAH+AAAAAAAAAf4AAAAADgAB/gAAAAA+AAH+AAAAAf4AAf4AAAAH/gAB/gAAAD/+AAH+AAAA//4AAf4AAAf//gAB/gAAH//+AAH+AAD///wAAf4AA///8AAB/gAf//+AAAH+AH///gAAAf4D///wAAAB/g///8AAAAH+f//+AAAAAf////gAAAAB////wAAAAAH///8AAAAAAf//+AAAAAAB///gAAAAAAH//wAAAAAAAf/8AAAAAAAB/+AAAAAAAAH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAB/wB//wAAAAf/wf//gAAAD//z///AAAAf/////+AAAD//////8AAAf//////4AAD///////gAAP////w//AAB/+f/8Af8AAH/Af/gA/4AAf4A/8AD/gAB/gB/wAH+AAP8AH+AAf4AA/wAf4AB/gAD/AB/gAH+AAP8AH+AAf4AA/wAf4AB/gAB/gB/wAH+AAH+AP/AAf4AAf8A/+AD/gAB/8f/8Af8AAD////4H/wAAP//////+AAAf//////4AAA///////AAAD//////8AAAH//z///gAAAH/+H//4AAAAH/gH//AAAAAAAAH/wAAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAAP/wAAAAAAAD//wAAAAAAAf//wAAAAAAH///gABgAAA////AAPAAAD///+AB+AAAf///4AP4AAD////wB/wAAP/AP/AH/AAB/4Af+AP+AAH/AA/4A/4AAf4AB/gB/gAB/gAH+AH+AAP8AAP4Af4AA/wAA/gB/gAD/AAD+AH+AAP8AAP4Af4AA/4AB/gB/gAB/gAH+AH+AAH+AAfwA/4AAf8AD/AH/AAB/4Af4A/8AAD/4H/gP/wAAP//////+AAAf//////wAAA///////AAAB//////4AAAD//////AAAAH/////wAAAAH////+AAAAAH////AAAAAAAf/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf4AP8AAAAAB/gA/wAAAAAH+AD/AAAAAAf4AP8AAAAAB/gA/wAAAAAH+AD/AAAAAAf4AP8AAAAAB/gA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("ExwqHCYlJyYoIicoFg=="), 64+(scale<<8)+(1<<16));
|
this.setFontCustom(
|
||||||
|
E.toString(require('heatshrink').decompress(atob('AFcH+AHFh/gA4sf4AHFn+AA4t/E43+AwsB/gHFgf4PH4AMgJ9Ngf/Pot//6bF/59F///PokfA4J9DEgIABEwYkB/7DDEgIlFCoRMDEgQsEDoRLEEgpoBA4JhGOIsHZ40PdwwA/L4SjHNAgGCP4cHA4wWDA4aVCA4gGDA4SNBe4IiBA4MPHYRBBEwScCA4d/EQUBaoRKDA4UBLQYECgb+EAgMHYYcHa4MPHoLBCBgMfYgcfBgM/PIc/BgN/A4YECIIQEDHwkDHwQHDGwQHENQUHA4d/QIQnCRIJJCSgYTCA4hqCA4hqCA4hiCA4ZCEA4RFBGYbrFAHxDGSohdDcgagFAAjPCEzicDToU/A4jPCAwbQCBwgrBgIHEFYKrDWoa7DaggA/AC0PAYV+AYSBCgKpCg4DDVIUfAYZ9BToIDDPoKVBAYfARoQDDXgMPFwTIBdYSYCv4LCv7zCXgYKCXAK8CHoUPXgY9Cn/vEYMPEwX/z46Bj4mBgf+n77CDwX4v54EIIIzCOgX/4I+CAQI9BHYQCCQ4I7CRASDBHYQHCv/Aj4+BGYIeBGAI+Bj/8AIIRBQIZjCRIiWBXgYHCPQgHBBgJ6DA4IEBPQaKBGYQ+BbgiCCAGZFDIIUBaAZBCgYHCQAQTBA4SACUwS8DDYQHBQAbVCQAYwBA4SABgYEBPoQCBFgU/CQWACgRDCHwKVCIYX+aYRDCHwMPAgY+Cn4EDHwX/AgY+B8bEFj/HA4RGCn+f94MBv45Cv+fA4J6C//+j5gBGIMBFoJWBQoRMB8E//4DBHIJcBv4HBEwJUCA4ImCj5MBA4KZCPYQHBZgRBCE4LICvwaCXAYA5PgQAEMIQAEUwQADQAJlCAARlBWYIACT4JtDAAMPA4IWESgg8CAwI+EEoPhHwYlCgY+DEoP4g4+DEoPAh4+CEoReBHwUfLYU/CwgMBXARqBHYQCCGoIjBgI+CgZSCHwcHAYY+Ch4lBJ4IbCjhACPwqUBPwqFCPwhQBIQZ+DOAKVFXooHCXop9DFAi8EFAT0GPoYAygwFEgOATISLDwBWDTQc/A4L6CTQKkCVQX+BYIHBDwX+BYIHBVQX8B4KqD+/wA4aBBj/AgK8CQIIJBA4a/BBIMBAgL/BAgUDYgL/BAII7BAQXgAII7BAQXAYQQxBYARrCMwQ0BAgV/HwYECHwgEBgY+EA4MPGwI8BA4UfGwI8BgYHBPofAQYOHPoeAR4QmBHwQHCEwI+CA4RVBHwQHCaggnBDwQHEHoIAEEQIA6v5NFfgSECBwZtEf4IHFOYQHEj4HGDwYHCDwPgv/jA4UHXQS8E/ED/AHDZ4MPSYKlCv+AYwIHDDwL7EgL7DAgTzCEwIpCeYTZBg4CBeYIJBAgICBFgIJBAgICBeYIEDHII0BAgg+EgI5CMocHGwJBCA4MfGwMD/h/BwF/PoQHC451CJIMDSgIjBA4PAA4QmBA4IhBA4JVBgEMA4bUDV4QeCAAf/HoIAENIIApOoIAEW4QAEW4QAEW4QAEWQRSFNIcDfYQMDny8DO4Q7BAQQjCewh+EHwcPToQ+Dv//ewkHUoI+En68DeIS0EHwMf/46CeYYlCHwQ0BKIY+BGgJ4Dh/nGgZZCAwKPEHYLpFDoKuFGgj4JgY0EHwQ0EYhIA6MAkf+BRBLIa5BQAJSCBgP4R4iVB/YHERoIACA4QGDE4SFBAoV/A4MH/ggBWIL7C8EfVoL4DwBHBFYIHBfYIRBAgT7CDgQEBgP4BgUBEIMDDgIMBgYMBg/gBgS5Ch/ABgUPFIMf4EHA4IEBHwUPCgJGCIIM/CgLgCAQJlBFIQFB44HBEIUBQYc/EIIHDAAIuBA4oeBRoSfBLAIHC/gHBEwIXC+AHBZghHBDwQADj4WCAHEPAwpWBKYYOCLwIHELYJUBghlDA4UcQogHBvgeDD4K0DDwIHBWgQeB4CyBh68CUAMf8DeCdIYHDdIfAfYjxCAgj2BAgbHCvwJCIIYCBBIMDHIX4BgUHFwMD+AMCA4Q0BAgg5CHwxICAQY5BdgQHBEgMDIYV/DgR1CA4PwP4KvDRgIACEYIHFWggABMQQHEZwd/Dwq1DHoTFEdooA/ACrBBcAZmC8DTCAATGBaYR+DwDTCRwbYDAASLBCIIGCFgQRBAG4='))),
|
||||||
|
46,
|
||||||
|
atob("EhooGyUkJiUnISYnFQ=="),
|
||||||
|
63+(scale<<8)+(1<<16)
|
||||||
|
);
|
||||||
return this;
|
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) {
|
Graphics.prototype.setMediumFont = function(scale) {
|
||||||
// Actual height 41 (42 - 2)
|
// Actual height 41 (42 - 2)
|
||||||
|
@ -259,11 +274,12 @@ function draw() {
|
||||||
|
|
||||||
function drawDate(){
|
function drawDate(){
|
||||||
// Draw background
|
// Draw background
|
||||||
var y = H/5*2 + (settings.fullscreen ? 0 : 8);
|
var y = H/5*2;
|
||||||
g.reset().clearRect(0,0,W,W);
|
g.reset().clearRect(0,0,W,W);
|
||||||
|
|
||||||
// Draw date
|
// Draw date
|
||||||
y -= settings.fullscreen ? 8 : 0;
|
y = parseInt(y/2);
|
||||||
|
y += settings.fullscreen ? 2 : 15;
|
||||||
var date = new Date();
|
var date = new Date();
|
||||||
var dateStr = date.getDate();
|
var dateStr = date.getDate();
|
||||||
dateStr = ("0" + dateStr).substr(-2);
|
dateStr = ("0" + dateStr).substr(-2);
|
||||||
|
@ -276,14 +292,14 @@ function drawDate(){
|
||||||
var dayW = Math.max(g.stringWidth(dayStr), g.stringWidth(monthStr));
|
var dayW = Math.max(g.stringWidth(dayStr), g.stringWidth(monthStr));
|
||||||
var fullDateW = dateW + 10 + dayW;
|
var fullDateW = dateW + 10 + dayW;
|
||||||
|
|
||||||
g.setFontAlign(-1,1);
|
g.setFontAlign(-1,0);
|
||||||
g.setMediumFont();
|
g.setMediumFont();
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
g.drawString(dateStr, W/2 - fullDateW / 2, y+5);
|
g.drawString(dateStr, W/2 - fullDateW / 2, y+1);
|
||||||
|
|
||||||
g.setSmallFont();
|
g.setSmallFont();
|
||||||
g.drawString(monthStr, W/2 - fullDateW/2 + 10 + dateW, y+3);
|
g.drawString(dayStr, W/2 - fullDateW/2 + 10 + dateW, y-12);
|
||||||
g.drawString(dayStr, W/2 - fullDateW/2 + 10 + dateW, y-23);
|
g.drawString(monthStr, W/2 - fullDateW/2 + 10 + dateW, y+11);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -296,9 +312,16 @@ function drawTime(){
|
||||||
|
|
||||||
// Draw time
|
// Draw time
|
||||||
g.setColor(g.theme.bg);
|
g.setColor(g.theme.bg);
|
||||||
g.setFontAlign(0,-1);
|
g.setFontAlign(0,0);
|
||||||
var timeStr = locale.time(date,1);
|
|
||||||
y += settings.fullscreen ? 14 : 10;
|
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();
|
var infoEntry = getInfoEntry();
|
||||||
var infoStr = infoEntry[0];
|
var infoStr = infoEntry[0];
|
||||||
|
@ -307,9 +330,13 @@ function drawTime(){
|
||||||
|
|
||||||
// Show large or small time depending on info entry
|
// Show large or small time depending on info entry
|
||||||
if(infoStr == null){
|
if(infoStr == null){
|
||||||
y += 10;
|
if(settings.hideColon){
|
||||||
g.setLargeFont();
|
g.setXLargeFont();
|
||||||
|
} else {
|
||||||
|
g.setLargeFont();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
y -= 15;
|
||||||
g.setMediumFont();
|
g.setMediumFont();
|
||||||
}
|
}
|
||||||
g.drawString(timeStr, W/2, y);
|
g.drawString(timeStr, W/2, y);
|
||||||
|
@ -319,7 +346,7 @@ function drawTime(){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
y += H/5*2-5;
|
y += 35;
|
||||||
g.setFontAlign(0,0);
|
g.setFontAlign(0,0);
|
||||||
g.setSmallFont();
|
g.setSmallFont();
|
||||||
var imgWidth = 0;
|
var imgWidth = 0;
|
||||||
|
@ -370,17 +397,6 @@ function queueDraw() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Load clock, widgets and listen for events
|
|
||||||
*/
|
|
||||||
Bangle.loadWidgets();
|
|
||||||
|
|
||||||
// Clear the screen once, at startup and set the correct theme.
|
|
||||||
var bgOrig = g.theme.bg
|
|
||||||
var fgOrig = g.theme.fg
|
|
||||||
g.setTheme({bg:fgOrig,fg:bgOrig}).clear();
|
|
||||||
draw();
|
|
||||||
|
|
||||||
// Stop updates when LCD is off, restart when on
|
// Stop updates when LCD is off, restart when on
|
||||||
Bangle.on('lcdPower',on=>{
|
Bangle.on('lcdPower',on=>{
|
||||||
if (on) {
|
if (on) {
|
||||||
|
@ -446,5 +462,17 @@ E.on("kill", function(){
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Draw clock the first time
|
||||||
|
*/
|
||||||
|
// The upper part is inverse i.e. light if dark and dark if light theme
|
||||||
|
// is enabled. In order to draw the widgets correctly, we invert the
|
||||||
|
// dark/light theme as well as the colors.
|
||||||
|
g.setTheme({bg:g.theme.fg,fg:g.theme.bg, dark:!g.theme.dark}).clear();
|
||||||
|
|
||||||
|
// Load widgets and draw clock the first time
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
draw();
|
||||||
|
|
||||||
// Show launcher when middle button pressed
|
// Show launcher when middle button pressed
|
||||||
Bangle.setUI("clock");
|
Bangle.setUI("clock");
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "bwclk",
|
"id": "bwclk",
|
||||||
"name": "BW Clock",
|
"name": "BW Clock",
|
||||||
"version": "0.06",
|
"version": "0.09",
|
||||||
"description": "BW Clock.",
|
"description": "BW Clock.",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
|
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.1 KiB |
|
@ -6,6 +6,7 @@
|
||||||
let settings = {
|
let settings = {
|
||||||
fullscreen: false,
|
fullscreen: false,
|
||||||
showLock: true,
|
showLock: true,
|
||||||
|
hideColon: false,
|
||||||
};
|
};
|
||||||
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
|
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
|
||||||
for (const key in saved_settings) {
|
for (const key in saved_settings) {
|
||||||
|
@ -35,6 +36,14 @@
|
||||||
settings.showLock = !settings.showLock;
|
settings.showLock = !settings.showLock;
|
||||||
save();
|
save();
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
'Hide Colon': {
|
||||||
|
value: settings.hideColon,
|
||||||
|
format: () => (settings.hideColon ? 'Yes' : 'No'),
|
||||||
|
onchange: () => {
|
||||||
|
settings.hideColon = !settings.hideColon;
|
||||||
|
save();
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,3 +5,5 @@
|
||||||
0.05: Update calendar weekend colors for start on Sunday
|
0.05: Update calendar weekend colors for start on Sunday
|
||||||
0.06: Use larger font for dates
|
0.06: Use larger font for dates
|
||||||
0.07: Fix off-by-one-error on previous month
|
0.07: Fix off-by-one-error on previous month
|
||||||
|
0.08: Do not register as watch, manually start clock on button
|
||||||
|
read start of week from system settings
|
||||||
|
|
|
@ -18,8 +18,7 @@ const blue = "#0000ff";
|
||||||
const yellow = "#ffff00";
|
const yellow = "#ffff00";
|
||||||
|
|
||||||
let settings = require('Storage').readJSON("calendar.json", true) || {};
|
let settings = require('Storage').readJSON("calendar.json", true) || {};
|
||||||
if (settings.startOnSun === undefined)
|
let startOnSun = ((require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0) === 0;
|
||||||
settings.startOnSun = false;
|
|
||||||
if (settings.ndColors === undefined)
|
if (settings.ndColors === undefined)
|
||||||
if (process.env.HWVERSION == 2) {
|
if (process.env.HWVERSION == 2) {
|
||||||
settings.ndColors = true;
|
settings.ndColors = true;
|
||||||
|
@ -50,14 +49,14 @@ function getDowLbls(locale) {
|
||||||
case "de_AT":
|
case "de_AT":
|
||||||
case "de_CH":
|
case "de_CH":
|
||||||
case "de_DE":
|
case "de_DE":
|
||||||
if (settings.startOnSun) {
|
if (startOnSun) {
|
||||||
dowLbls = ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"];
|
dowLbls = ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"];
|
||||||
} else {
|
} else {
|
||||||
dowLbls = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
|
dowLbls = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "nl_NL":
|
case "nl_NL":
|
||||||
if (settings.startOnSun) {
|
if (startOnSun) {
|
||||||
dowLbls = ["zo", "ma", "di", "wo", "do", "vr", "za"];
|
dowLbls = ["zo", "ma", "di", "wo", "do", "vr", "za"];
|
||||||
} else {
|
} else {
|
||||||
dowLbls = ["ma", "di", "wo", "do", "vr", "za", "zo"];
|
dowLbls = ["ma", "di", "wo", "do", "vr", "za", "zo"];
|
||||||
|
@ -66,14 +65,14 @@ function getDowLbls(locale) {
|
||||||
case "fr_BE":
|
case "fr_BE":
|
||||||
case "fr_CH":
|
case "fr_CH":
|
||||||
case "fr_FR":
|
case "fr_FR":
|
||||||
if (settings.startOnSun) {
|
if (startOnSun) {
|
||||||
dowLbls = ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"];
|
dowLbls = ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"];
|
||||||
} else {
|
} else {
|
||||||
dowLbls = ["Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"];
|
dowLbls = ["Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "sv_SE":
|
case "sv_SE":
|
||||||
if (settings.startOnSun) {
|
if (startOnSun) {
|
||||||
dowLbls = ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"];
|
dowLbls = ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"];
|
||||||
} else {
|
} else {
|
||||||
dowLbls = ["Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"];
|
dowLbls = ["Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"];
|
||||||
|
@ -81,21 +80,21 @@ function getDowLbls(locale) {
|
||||||
break;
|
break;
|
||||||
case "it_CH":
|
case "it_CH":
|
||||||
case "it_IT":
|
case "it_IT":
|
||||||
if (settings.startOnSun) {
|
if (startOnSun) {
|
||||||
dowLbls = ["Do", "Lu", "Ma", "Me", "Gi", "Ve", "Sa"];
|
dowLbls = ["Do", "Lu", "Ma", "Me", "Gi", "Ve", "Sa"];
|
||||||
} else {
|
} else {
|
||||||
dowLbls = ["Lu", "Ma", "Me", "Gi", "Ve", "Sa", "Do"];
|
dowLbls = ["Lu", "Ma", "Me", "Gi", "Ve", "Sa", "Do"];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "oc_FR":
|
case "oc_FR":
|
||||||
if (settings.startOnSun) {
|
if (startOnSun) {
|
||||||
dowLbls = ["dg", "dl", "dm", "dc", "dj", "dv", "ds"];
|
dowLbls = ["dg", "dl", "dm", "dc", "dj", "dv", "ds"];
|
||||||
} else {
|
} else {
|
||||||
dowLbls = ["dl", "dm", "dc", "dj", "dv", "ds", "dg"];
|
dowLbls = ["dl", "dm", "dc", "dj", "dv", "ds", "dg"];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (settings.startOnSun) {
|
if (startOnSun) {
|
||||||
dowLbls = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
dowLbls = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
||||||
} else {
|
} else {
|
||||||
dowLbls = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
|
dowLbls = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
|
||||||
|
@ -110,7 +109,7 @@ function drawCalendar(date) {
|
||||||
g.clearRect(0, 0, maxX, maxY);
|
g.clearRect(0, 0, maxX, maxY);
|
||||||
g.setBgColor(bgColorMonth);
|
g.setBgColor(bgColorMonth);
|
||||||
g.clearRect(0, 0, maxX, headerH);
|
g.clearRect(0, 0, maxX, headerH);
|
||||||
if (settings.startOnSun){
|
if (startOnSun){
|
||||||
g.setBgColor(bgColorWeekend);
|
g.setBgColor(bgColorWeekend);
|
||||||
g.clearRect(0, headerH + rowH, colW, maxY);
|
g.clearRect(0, headerH + rowH, colW, maxY);
|
||||||
g.setBgColor(bgColorDow);
|
g.setBgColor(bgColorDow);
|
||||||
|
@ -150,7 +149,7 @@ function drawCalendar(date) {
|
||||||
});
|
});
|
||||||
|
|
||||||
date.setDate(1);
|
date.setDate(1);
|
||||||
const dow = date.getDay() + (settings.startOnSun ? 1 : 0);
|
const dow = date.getDay() + (startOnSun ? 1 : 0);
|
||||||
const dowNorm = dow === 0 ? 7 : dow;
|
const dowNorm = dow === 0 ? 7 : dow;
|
||||||
|
|
||||||
const monthMaxDayMap = {
|
const monthMaxDayMap = {
|
||||||
|
@ -242,5 +241,5 @@ Bangle.on("touch", area => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show launcher when button pressed
|
// Show launcher when button pressed
|
||||||
Bangle.setUI("clock"); // TODO: ideally don't set 'clock' mode
|
setWatch(() => load(), process.env.HWVERSION === 2 ? BTN : BTN3, { repeat: false, edge: "falling" });
|
||||||
// No space for widgets!
|
// No space for widgets!
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "calendar",
|
"id": "calendar",
|
||||||
"name": "Calendar",
|
"name": "Calendar",
|
||||||
"version": "0.07",
|
"version": "0.08",
|
||||||
"description": "Simple calendar",
|
"description": "Simple calendar",
|
||||||
"icon": "calendar.png",
|
"icon": "calendar.png",
|
||||||
"screenshots": [{"url":"screenshot_calendar.png"}],
|
"screenshots": [{"url":"screenshot_calendar.png"}],
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
(function (back) {
|
(function (back) {
|
||||||
var FILE = "calendar.json";
|
var FILE = "calendar.json";
|
||||||
var settings = require('Storage').readJSON(FILE, true) || {};
|
var settings = require('Storage').readJSON(FILE, true) || {};
|
||||||
if (settings.startOnSun === undefined)
|
|
||||||
settings.startOnSun = false;
|
|
||||||
if (settings.ndColors === undefined)
|
if (settings.ndColors === undefined)
|
||||||
if (process.env.HWVERSION == 2) {
|
if (process.env.HWVERSION == 2) {
|
||||||
settings.ndColors = true;
|
settings.ndColors = true;
|
||||||
|
@ -17,14 +15,6 @@
|
||||||
E.showMenu({
|
E.showMenu({
|
||||||
"": { "title": "Calendar" },
|
"": { "title": "Calendar" },
|
||||||
"< Back": () => back(),
|
"< Back": () => back(),
|
||||||
'Start Sunday': {
|
|
||||||
value: settings.startOnSun,
|
|
||||||
format: v => v ? "Yes" : "No",
|
|
||||||
onchange: v => {
|
|
||||||
settings.startOnSun = v;
|
|
||||||
writeSettings();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'B2 Colors': {
|
'B2 Colors': {
|
||||||
value: settings.ndColors,
|
value: settings.ndColors,
|
||||||
format: v => v ? "Yes" : "No",
|
format: v => v ? "Yes" : "No",
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Banglejs - Touchscreen calibration
|
||||||
|
A simple calibration app for the touchscreen
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Once lauched touch the cross that appear on the screen to make
|
||||||
|
another spawn elsewhere.
|
||||||
|
|
||||||
|
each new touch on the screen will help to calibrate the offset
|
||||||
|
of your finger on the screen. After five or more input, press
|
||||||
|
the button to save the calibration and close the application.
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwkB/4AJ+EPBhQXg+BBDCyJaGGR5zIDBoQEL4QYOLYR3GBIouJR5AYBGBILBU5QMGFwgiFX4wwIEI4XGGBAgHd44+HD44XHNw4XWM5IIHCIoXWV5IXICQgXvLxAAKCYYXh5nMC6n8C4PPC5MAAA8PC4ZxBACAXOI653hU5zvJABASEC5PwHI4XcMBIXICIoXXJBAXHCAwXXJBAXHB5AfGC4ygJEAwXGQ5BoIQxoiDBYgXECwIuIBgb5ECIQJFGBQmCC4QHEDBwAFCxoYICx5ZELZoZJFiIXpA="))
|
|
@ -0,0 +1,85 @@
|
||||||
|
class BanglejsApp {
|
||||||
|
constructor() {
|
||||||
|
this.x = 0;
|
||||||
|
this.y = 0;
|
||||||
|
this.settings = {
|
||||||
|
xoffset: 0,
|
||||||
|
yoffset: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
load_settings() {
|
||||||
|
let settings = require('Storage').readJSON('calibration.json', true) || {active: false};
|
||||||
|
|
||||||
|
// do nothing if the calibration is deactivated
|
||||||
|
if (settings.active === true) {
|
||||||
|
// cancel the calibration offset
|
||||||
|
Bangle.on('touch', function(button, xy) {
|
||||||
|
xy.x += settings.xoffset;
|
||||||
|
xy.y += settings.yoffset;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!settings.xoffset) settings.xoffset = 0;
|
||||||
|
if (!settings.yoffset) settings.yoffset = 0;
|
||||||
|
|
||||||
|
console.log('loaded settings:');
|
||||||
|
console.log(settings);
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
save_settings() {
|
||||||
|
this.settings.active = true;
|
||||||
|
this.settings.reload = false;
|
||||||
|
require('Storage').writeJSON('calibration.json', this.settings);
|
||||||
|
|
||||||
|
console.log('saved settings:');
|
||||||
|
console.log(this.settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
explain() {
|
||||||
|
/*
|
||||||
|
* TODO:
|
||||||
|
* Present how to use the application
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
drawTarget() {
|
||||||
|
this.x = 16 + Math.floor(Math.random() * (g.getWidth() - 32));
|
||||||
|
this.y = 40 + Math.floor(Math.random() * (g.getHeight() - 80));
|
||||||
|
|
||||||
|
g.clearRect(0, 24, g.getWidth(), g.getHeight() - 24);
|
||||||
|
g.drawLine(this.x, this.y - 5, this.x, this.y + 5);
|
||||||
|
g.drawLine(this.x - 5, this.y, this.x + 5, this.y);
|
||||||
|
g.setFont('Vector', 10);
|
||||||
|
g.drawString('current offset: ' + this.settings.xoffset + ', ' + this.settings.yoffset, 0, 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
setOffset(xy) {
|
||||||
|
this.settings.xoffset = Math.round((this.settings.xoffset + (this.x - Math.floor((this.x + xy.x)/2)))/2);
|
||||||
|
this.settings.yoffset = Math.round((this.settings.yoffset + (this.y - Math.floor((this.y + xy.y)/2)))/2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
E.srand(Date.now());
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
calibration = new BanglejsApp();
|
||||||
|
calibration.load_settings();
|
||||||
|
|
||||||
|
let modes = {
|
||||||
|
mode : 'custom',
|
||||||
|
btn : function(n) {
|
||||||
|
calibration.save_settings(this.settings);
|
||||||
|
load();
|
||||||
|
},
|
||||||
|
touch : function(btn, xy) {
|
||||||
|
calibration.setOffset(xy);
|
||||||
|
calibration.drawTarget();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Bangle.setUI(modes);
|
||||||
|
calibration.drawTarget();
|
|
@ -0,0 +1,14 @@
|
||||||
|
let cal_settings = require('Storage').readJSON("calibration.json", true) || {active: false};
|
||||||
|
Bangle.on('touch', function(button, xy) {
|
||||||
|
// do nothing if the calibration is deactivated
|
||||||
|
if (cal_settings.active === false) return;
|
||||||
|
|
||||||
|
// reload the calibration offset at each touch event /!\ bad for the flash memory
|
||||||
|
if (cal_settings.reload === true) {
|
||||||
|
cal_settings = require('Storage').readJSON("calibration.json", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply the calibration offset
|
||||||
|
xy.x += cal_settings.xoffset;
|
||||||
|
xy.y += cal_settings.yoffset;
|
||||||
|
});
|
After Width: | Height: | Size: 451 B |
|
@ -0,0 +1,17 @@
|
||||||
|
{ "id": "calibration",
|
||||||
|
"name": "Touchscreen Calibration",
|
||||||
|
"shortName":"Calibration",
|
||||||
|
"icon": "calibration.png",
|
||||||
|
"version":"1.00",
|
||||||
|
"description": "A simple calibration app for the touchscreen",
|
||||||
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
|
"readme": "README.md",
|
||||||
|
"tags": "tool",
|
||||||
|
"storage": [
|
||||||
|
{"name":"calibration.app.js","url":"app.js"},
|
||||||
|
{"name":"calibration.boot.js","url":"boot.js"},
|
||||||
|
{"name":"calibration.settings.js","url":"settings.js"},
|
||||||
|
{"name":"calibration.img","url":"app-icon.js","evaluate":true}
|
||||||
|
],
|
||||||
|
"data": [{"name":"calibration.json"}]
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
(function(back) {
|
||||||
|
var FILE = "calibration.json";
|
||||||
|
var settings = Object.assign({
|
||||||
|
active: true,
|
||||||
|
}, require('Storage').readJSON(FILE, true) || {});
|
||||||
|
|
||||||
|
function writeSettings() {
|
||||||
|
require('Storage').writeJSON(FILE, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
E.showMenu({
|
||||||
|
"" : { "title" : "Calibration" },
|
||||||
|
"< Back" : () => back(),
|
||||||
|
'Active': {
|
||||||
|
value: !!settings.active,
|
||||||
|
format: v => v? "On":"Off",
|
||||||
|
onchange: v => {
|
||||||
|
settings.active = v;
|
||||||
|
writeSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
|
@ -23,3 +23,4 @@
|
||||||
0.11: New color option: foreground color
|
0.11: New color option: foreground color
|
||||||
Improve performance, reduce memory usage
|
Improve performance, reduce memory usage
|
||||||
Small optical adjustments
|
Small optical adjustments
|
||||||
|
0.12: Allow configuration of update interval
|
||||||
|
|
|
@ -848,8 +848,8 @@ Bangle.loadWidgets();
|
||||||
|
|
||||||
// schedule a draw for the next minute
|
// schedule a draw for the next minute
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
// draw every 60 seconds
|
// draw in interval
|
||||||
setInterval(draw,60000);
|
setInterval(draw, settings.updateInterval * 1000);
|
||||||
}, 60000 - (Date.now() % 60000));
|
}, 60000 - (Date.now() % 60000));
|
||||||
|
|
||||||
draw();
|
draw();
|
||||||
|
|
|
@ -21,5 +21,6 @@
|
||||||
"circle2colorizeIcon": true,
|
"circle2colorizeIcon": true,
|
||||||
"circle3colorizeIcon": true,
|
"circle3colorizeIcon": true,
|
||||||
"circle4colorizeIcon": false,
|
"circle4colorizeIcon": false,
|
||||||
"hrmValidity": 60
|
"hrmValidity": 60,
|
||||||
|
"updateInterval": 60
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{ "id": "circlesclock",
|
{ "id": "circlesclock",
|
||||||
"name": "Circles clock",
|
"name": "Circles clock",
|
||||||
"shortName":"Circles clock",
|
"shortName":"Circles clock",
|
||||||
"version":"0.11",
|
"version":"0.12",
|
||||||
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
|
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],
|
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],
|
||||||
|
|
|
@ -58,6 +58,16 @@
|
||||||
min: 0, max: 2,
|
min: 0, max: 2,
|
||||||
format: v => weatherData[v],
|
format: v => weatherData[v],
|
||||||
onchange: x => save('weatherCircleData', weatherData[x]),
|
onchange: x => save('weatherCircleData', weatherData[x]),
|
||||||
|
},
|
||||||
|
/*LANG*/'update interval': {
|
||||||
|
value: settings.updateInterval,
|
||||||
|
min: 0,
|
||||||
|
max : 3600,
|
||||||
|
step: 30,
|
||||||
|
format: x => {
|
||||||
|
return x + 's';
|
||||||
|
},
|
||||||
|
onchange: x => save('updateInterval', x),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
|
@ -100,7 +110,7 @@
|
||||||
/*LANG*/'valid period': {
|
/*LANG*/'valid period': {
|
||||||
value: settings.hrmValidity,
|
value: settings.hrmValidity,
|
||||||
min: 10,
|
min: 10,
|
||||||
max : 600,
|
max : 1800,
|
||||||
step: 10,
|
step: 10,
|
||||||
format: x => {
|
format: x => {
|
||||||
return x + "s";
|
return x + "s";
|
||||||
|
@ -117,9 +127,9 @@
|
||||||
/*LANG*/'< Back': ()=>showMainMenu(),
|
/*LANG*/'< Back': ()=>showMainMenu(),
|
||||||
/*LANG*/'goal': {
|
/*LANG*/'goal': {
|
||||||
value: settings.stepGoal,
|
value: settings.stepGoal,
|
||||||
min: 2000,
|
min: 1000,
|
||||||
max : 50000,
|
max : 50000,
|
||||||
step: 2000,
|
step: 500,
|
||||||
format: x => {
|
format: x => {
|
||||||
return x;
|
return x;
|
||||||
},
|
},
|
||||||
|
@ -127,9 +137,9 @@
|
||||||
},
|
},
|
||||||
/*LANG*/'distance goal': {
|
/*LANG*/'distance goal': {
|
||||||
value: settings.stepDistanceGoal,
|
value: settings.stepDistanceGoal,
|
||||||
min: 2000,
|
min: 1000,
|
||||||
max : 30000,
|
max : 50000,
|
||||||
step: 1000,
|
step: 500,
|
||||||
format: x => {
|
format: x => {
|
||||||
return x;
|
return x;
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: App created
|
|
@ -0,0 +1 @@
|
||||||
|
E.toArrayBuffer(atob("ICABAAAAAAAAAAAAAAAAAAHAAAAP8AAAfn4AA/APwA+DwfAPg8HwD+AH8Az4HzAMPnwwDAfgMAwBgDAMCYAwDA2YMAwhmDAMIZAwDCGDMA2BgzAMgYAwDAGAMA8BgPADwYPAAPGPgAB9ngAAH/gAAAfgAAABgAAAAAAAAAAAAAAAAAA="))
|
|
@ -0,0 +1,108 @@
|
||||||
|
var init_message = true;
|
||||||
|
var acc_data;
|
||||||
|
var die_roll = 1;
|
||||||
|
var selected_die = 0;
|
||||||
|
var roll = 0;
|
||||||
|
const dices = [4, 6, 10, 12, 20];
|
||||||
|
|
||||||
|
g.setFontAlign(0,0);
|
||||||
|
|
||||||
|
Bangle.on('touch', function(button, xy) {
|
||||||
|
// Change die if not rolling
|
||||||
|
if(roll < 1){
|
||||||
|
if(selected_die <= 3){
|
||||||
|
selected_die++;
|
||||||
|
}else{
|
||||||
|
selected_die = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Disable initial message
|
||||||
|
init_message = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
function rect(){
|
||||||
|
x1 = g.getWidth()/2 - 35;
|
||||||
|
x2 = g.getWidth()/2 + 35;
|
||||||
|
y1 = g.getHeight()/2 - 35;
|
||||||
|
y2 = g.getHeight()/2 + 35;
|
||||||
|
g.drawRect(x1, y1, x2, y2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pentagon(){
|
||||||
|
x1 = g.getWidth()/2;
|
||||||
|
y1 = g.getHeight()/2 - 50;
|
||||||
|
x2 = g.getWidth()/2 - 50;
|
||||||
|
y2 = g.getHeight()/2 - 10;
|
||||||
|
x3 = g.getWidth()/2 - 30;
|
||||||
|
y3 = g.getHeight()/2 + 30;
|
||||||
|
x4 = g.getWidth()/2 + 30;
|
||||||
|
y4 = g.getHeight()/2 + 30;
|
||||||
|
x5 = g.getWidth()/2 + 50;
|
||||||
|
y5 = g.getHeight()/2 - 10;
|
||||||
|
g.drawPoly([x1, y1, x2, y2, x3, y3, x4, y4, x5, y5], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function triangle(){
|
||||||
|
x1 = g.getWidth()/2;
|
||||||
|
y1 = g.getHeight()/2 - 57;
|
||||||
|
x2 = g.getWidth()/2 - 50;
|
||||||
|
y2 = g.getHeight()/2 + 23;
|
||||||
|
x3 = g.getWidth()/2 + 50;
|
||||||
|
y3 = g.getHeight()/2 + 23;
|
||||||
|
g.drawPoly([x1, y1, x2, y2, x3, y3], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawDie(variant) {
|
||||||
|
if(variant == 1){
|
||||||
|
//Rect, 6
|
||||||
|
rect();
|
||||||
|
}else if(variant == 3){
|
||||||
|
//Pentagon, 12
|
||||||
|
pentagon();
|
||||||
|
}else{
|
||||||
|
//Triangle, 4, 10, 20
|
||||||
|
triangle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initMessage(){
|
||||||
|
g.setFont("6x8", 2);
|
||||||
|
g.drawString("Dice-n-Roll", g.getWidth()/2, 20);
|
||||||
|
g.drawString("Shake to roll", g.getWidth()/2, 60);
|
||||||
|
g.drawString("Tap to change", g.getWidth()/2, 80);
|
||||||
|
g.drawString("Tap to start", g.getWidth()/2, 150);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rollDie(){
|
||||||
|
acc_data = Bangle.getAccel();
|
||||||
|
if(acc_data.diff > 0.3){
|
||||||
|
roll = 3;
|
||||||
|
}
|
||||||
|
//Mange the die "roll" by chaning the number a few times
|
||||||
|
if(roll > 0){
|
||||||
|
g.drawString("Rolling!", g.getWidth()/2, 150);
|
||||||
|
die_roll = Math.abs(E.hwRand()) % dices[selected_die] + 1;
|
||||||
|
roll--;
|
||||||
|
}
|
||||||
|
//Draw dice graphics
|
||||||
|
drawDie(selected_die);
|
||||||
|
//Draw dice number
|
||||||
|
g.setFontAlign(0,0);
|
||||||
|
g.setFont("Vector", 45);
|
||||||
|
g.drawString(die_roll, g.getWidth()/2, g.getHeight()/2);
|
||||||
|
//Draw selected die in right corner
|
||||||
|
g.setFont("6x8", 2);
|
||||||
|
g.drawString(dices[selected_die], g.getWidth()-15, 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
g.clear();
|
||||||
|
if(init_message){
|
||||||
|
initMessage();
|
||||||
|
}else{
|
||||||
|
rollDie();
|
||||||
|
}
|
||||||
|
Bangle.setLCDPower(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var interval = setInterval(main, 300);
|
After Width: | Height: | Size: 637 B |
After Width: | Height: | Size: 2.4 KiB |
|
@ -0,0 +1,14 @@
|
||||||
|
{ "id": "diceroll",
|
||||||
|
"name": "Dice-n-Roll",
|
||||||
|
"shortName":"Dice-n-Roll",
|
||||||
|
"icon": "app.png",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "A dice app with a few different dice.",
|
||||||
|
"screenshots": [{"url":"diceroll_screenshot.png"}],
|
||||||
|
"tags": "game",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"storage": [
|
||||||
|
{"name":"diceroll.app.js","url":"app.js"},
|
||||||
|
{"name":"diceroll.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
# dinoClock
|
||||||
|
|
||||||
|
Watchface with T-Rex Dinosaur from Chrome.
|
||||||
|
It displays current temperature and weather.
|
||||||
|
|
||||||
|
**Warning**: Element position and styles can change in the future.
|
||||||
|
|
||||||
|
Based on the [Weather Clock](https://github.com/espruino/BangleApps/tree/master/apps/weatherClock).
|
||||||
|
|
||||||
|
# Requirements
|
||||||
|
|
||||||
|
**This clock requires Gadgetbridge and the weather app in order to get weather data!**
|
||||||
|
|
||||||
|
See the [Bangle.js Gadgetbridge documentation](https://www.espruino.com/Gadgetbridge) for instructions on setting up Gadgetbridge and weather.
|
||||||
|
|
||||||
|

|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
const storage = require('Storage');
|
||||||
|
const locale = require("locale");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// add modifiied 4x5 numeric font
|
||||||
|
(function(graphics) {
|
||||||
|
graphics.prototype.setFont4x5NumPretty = function() {
|
||||||
|
this.setFontCustom(atob("IQAQDJgH4/An4QXr0Fa/BwnwdrcH63BCHwfr8Ha/"),45,atob("AwIEBAQEBAQEBAQEBA=="),5);
|
||||||
|
};
|
||||||
|
})(Graphics);
|
||||||
|
|
||||||
|
// add font for days of the week
|
||||||
|
(function(graphics) {
|
||||||
|
graphics.prototype.setFontDoW = function() {
|
||||||
|
this.setFontCustom(atob("///////ADgB//////+AHAD//////gAAAH//////4D8B+A///////4AcAOAH//////4AcAOAAAAAB//////wA4AcAP//////wAAAAAAAA//////4AcAP//////wA4Af//////gAAAH//////5z85+c/OfnOAA4AcAOAH//////4AcAOAAAAAB//////wcAOAHB//////wAAAAAAAA///////ODnBzg5wc4AAAAD//////84OcH//8/+fAAAAAAAAAAAAA/z/5/8/OfnPz/5/8/wAAAD//////84OcH//////AAAAAAAAAAAAA/z/5/8/OfnPz/5/8/wAAAD//////gBwA///////AAAAAAAAAAAAA"),48,24,13);
|
||||||
|
};
|
||||||
|
})(Graphics);
|
||||||
|
|
||||||
|
|
||||||
|
const SUN = 1;
|
||||||
|
const PART_SUN = 2;
|
||||||
|
const CLOUD = 3;
|
||||||
|
const SNOW = 4;
|
||||||
|
const RAIN = 5;
|
||||||
|
const STORM = 6;
|
||||||
|
const ERR = 7;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Choose weather icon based on weather const
|
||||||
|
Weather icons from https://icons8.com/icon/set/weather/ios-glyphs
|
||||||
|
Error icon from https://icons8.com/icon/set/error-cloud/ios-glyphs
|
||||||
|
**/
|
||||||
|
function weatherIcon(weather) {
|
||||||
|
switch (weather) {
|
||||||
|
case SUN:
|
||||||
|
return atob("Hh4BAAAAAAAMAAAAMAAAAMAAAAMAABgMBgBwADgA4AHAAY/GAAB/gAAD/wAAH/4AAP/8AAP/8AfP/8+fP/8+AP/8AAP/8AAH/4AAD/wAAB/gAAY/GAA4AHABwADgBgMBgAAMAAAAMAAAAMAAAAMAAAAAAAA=");
|
||||||
|
case PART_SUN:
|
||||||
|
return atob("Hh4BAAAAAAAAAAAMAAAAMAAAEMIAAOAcAAGAYAAAeAAAA/AAAB/gAA5/gAA5/g+AB+D/gA4H/wAR//wGD//4OD//4EH//4AH//4Af//+Af//+A////A////A////A///+Af//+AH//4AAAAAAAAAAAAAAAA=");
|
||||||
|
case CLOUD:
|
||||||
|
return atob("Hh4BAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAf+AAA//AAB//gAf//gB///wB///wD///wD///wP///8f///+f///+////////////////////f///+f///+P///8D///wAAAAAAAAAAAAAAAAAAAAAAAAAA=");
|
||||||
|
case SNOW:
|
||||||
|
return atob("Hh4BAAAAAAAAAAAAAAAAAHwAAAf8AAA/+AAH/+AAf//AAf8/AA/8/AB/gHgH/wP4H/wP4P/gH8P/8/8P/8/8P///4H///4B///gAAAAAAMAAAAMAAAB/gGAA/AfgA/AfgB/gfgAMAfgAMAGAAAAAAAAAAAA=");
|
||||||
|
case RAIN:
|
||||||
|
return atob("Hh4BAAAAAAAAAAAAAAAAAHwAAAf8AAA/+AAH/+AAf//AAf//AA///AB///gH///4H///4P///8P///8P///8P///4H///4B///gAAAAAAAAAABgBgABgBgABhhhgABgBgABgBgAAAAAAAAAAAAAAAAAAAAA=");
|
||||||
|
case STORM:
|
||||||
|
return atob("Hh4BAAAAAAAAAAAAAAAAAHwAAAf8AAA/+AAH/+AAf//AAf//AA///AB///gH///4H/x/4P/g/8P/k/8P/E/8P/M/4H+MP4B+cHgAAfgAAA/gABg/AABgHAABgGBgAAGBgAAEBgAAEAAAAAAAAAAAAAAAAAA=");
|
||||||
|
case ERR:
|
||||||
|
default:
|
||||||
|
return atob("Hh4BAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAf+AAA//AAB//gAf//gB///wB/z/wD/z/wD/z/wP/z/8f/z/+f/z/+//z//////////////z//f/z/+f///+P///8D///wAAAAAAAAAAAAAAAAAAAAAAAAAA=");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Choose weather icon to display based on condition.
|
||||||
|
Based on function from the Bangle weather app so it should handle all of the conditions
|
||||||
|
sent from gadget bridge.
|
||||||
|
*/
|
||||||
|
function chooseIcon(condition) {
|
||||||
|
condition = condition.toLowerCase();
|
||||||
|
if (condition.includes("thunderstorm")) return weatherIcon(STORM);
|
||||||
|
if (condition.includes("freezing")||condition.includes("snow")||
|
||||||
|
condition.includes("sleet")) {
|
||||||
|
return weatherIcon(SNOW);
|
||||||
|
}
|
||||||
|
if (condition.includes("drizzle")||
|
||||||
|
condition.includes("shower")) {
|
||||||
|
return weatherIcon(RAIN);
|
||||||
|
}
|
||||||
|
if (condition.includes("rain")) return weatherIcon(RAIN);
|
||||||
|
if (condition.includes("clear")) return weatherIcon(SUN);
|
||||||
|
if (condition.includes("few clouds")) return weatherIcon(PART_SUN);
|
||||||
|
if (condition.includes("scattered clouds")) return weatherIcon(CLOUD);
|
||||||
|
if (condition.includes("clouds")) return weatherIcon(CLOUD);
|
||||||
|
if (condition.includes("mist") ||
|
||||||
|
condition.includes("smoke") ||
|
||||||
|
condition.includes("haze") ||
|
||||||
|
condition.includes("sand") ||
|
||||||
|
condition.includes("dust") ||
|
||||||
|
condition.includes("fog") ||
|
||||||
|
condition.includes("ash") ||
|
||||||
|
condition.includes("squalls") ||
|
||||||
|
condition.includes("tornado")) {
|
||||||
|
return weatherIcon(CLOUD);
|
||||||
|
}
|
||||||
|
return weatherIcon(CLOUD);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Choose weather icon to display based on weather conditition code
|
||||||
|
* https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
|
||||||
|
*/
|
||||||
|
function chooseIconByCode(code) {
|
||||||
|
const codeGroup = Math.round(code / 100);
|
||||||
|
switch (codeGroup) {
|
||||||
|
case 2: return weatherIcon(STORM);
|
||||||
|
case 3: return weatherIcon(RAIN);
|
||||||
|
case 5: return weatherIcon(RAIN);
|
||||||
|
case 6: return weatherIcon(SNOW);
|
||||||
|
case 7: return weatherIcon(CLOUD);
|
||||||
|
case 8:
|
||||||
|
switch (code) {
|
||||||
|
case 800: return weatherIcon(SUN);
|
||||||
|
case 801: return weatherIcon(PART_SUN);
|
||||||
|
default: return weatherIcon(CLOUD);
|
||||||
|
}
|
||||||
|
default: return weatherIcon(CLOUD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get weather stored in json file by weather app.
|
||||||
|
*/
|
||||||
|
function getWeather() {
|
||||||
|
let jsonWeather = storage.readJSON('weather.json');
|
||||||
|
return jsonWeather;
|
||||||
|
}
|
||||||
|
|
||||||
|
// timeout used to update every minute
|
||||||
|
var drawTimeout;
|
||||||
|
|
||||||
|
// schedule a draw for the next minute
|
||||||
|
function queueDraw() {
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = setTimeout(function() {
|
||||||
|
drawTimeout = undefined;
|
||||||
|
draw();
|
||||||
|
},60000-(Date.now()%60000));
|
||||||
|
}
|
||||||
|
|
||||||
|
// only draw the first time
|
||||||
|
function drawBg() {
|
||||||
|
var bgImg = require("heatshrink").decompress(atob("2E7wINKn///+AEaIVUgIUB//wCs/5CtRXrCvMD8AVTg4LFCv4VZ/iSLCrwWMCrMOAQMPCp7cBCojjFCo/xFgIVQgeHCopABCpcH44Vuh/AQQX/wAV7+F/Cq/nCsw/CCqyvRCvgODCqfAgEDCp4QCSIIVQgIOBDQgGDABX/NgIECCp8HCrM/CgP4CqKaCCqSfCCqq1BCqBuB54VqgYVG/gCECp0BwgCDCp8HgYCDCo/wCo0MgHAjACBj7rDABS1Bv4lBv4rPAAsPCo3+gbbPJAIVFiAXMFZ2AUQsAuAQHiOAgJeEA"));
|
||||||
|
g.reset();
|
||||||
|
g.drawImage(bgImg,0,101);
|
||||||
|
}
|
||||||
|
|
||||||
|
function square(x,y,w,e) {
|
||||||
|
g.setColor("#000").fillRect(x,y,x+w,y+w);
|
||||||
|
g.setColor("#fff").fillRect(x+e,y+e,x+w-e,y+w-e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
var d = new Date();
|
||||||
|
var h = d.getHours(), m = d.getMinutes();
|
||||||
|
h = ("0"+h).substr(-2);
|
||||||
|
m = ("0"+m).substr(-2);
|
||||||
|
|
||||||
|
var day = d.getDate(), mon = d.getMonth(), dow = d.getDay();
|
||||||
|
day = ("0"+day).substr(-2);
|
||||||
|
mon = ("0"+(mon+1)).substr(-2);
|
||||||
|
dow = ((dow+6)%7).toString();
|
||||||
|
date = day+"."+mon;
|
||||||
|
|
||||||
|
var weatherJson = getWeather();
|
||||||
|
var wIcon;
|
||||||
|
var temp;
|
||||||
|
if(weatherJson && weatherJson.weather){
|
||||||
|
var currentWeather = weatherJson.weather;
|
||||||
|
temp = locale.temp(currentWeather.temp-273.15).match(/^(\D*\d*)(.*)$/);
|
||||||
|
const code = currentWeather.code||-1;
|
||||||
|
if (code > 0) {
|
||||||
|
wIcon = chooseIconByCode(code);
|
||||||
|
} else {
|
||||||
|
wIcon = chooseIcon(currentWeather.txt);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
temp = "";
|
||||||
|
wIcon = weatherIcon(ERR);
|
||||||
|
}
|
||||||
|
g.reset();
|
||||||
|
g.clearRect(22,35,153,75);
|
||||||
|
g.setFont("4x5NumPretty",8);
|
||||||
|
g.fillRect(84,42,92,49);
|
||||||
|
g.fillRect(84,60,92,67);
|
||||||
|
g.drawString(h,22,35);
|
||||||
|
g.drawString(m,98,35);
|
||||||
|
|
||||||
|
g.clearRect(22,95,22+4*2*4+2*4,95+2*5);
|
||||||
|
g.setFont("4x5NumPretty",2);
|
||||||
|
g.drawString(date,22,95);
|
||||||
|
|
||||||
|
g.clearRect(22,79,22+24,79+13);
|
||||||
|
g.setFont("DoW");
|
||||||
|
g.drawString(dow,22,79);
|
||||||
|
|
||||||
|
g.drawImage(wIcon,126,81);
|
||||||
|
|
||||||
|
g.clearRect(108,114,176,114+4*5);
|
||||||
|
if (temp != "") {
|
||||||
|
var tempWidth;
|
||||||
|
const mid=126+15;
|
||||||
|
if (temp[1][0]=="-") {
|
||||||
|
// do not account for - when aligning
|
||||||
|
const minusWidth=3*4;
|
||||||
|
tempWidth = minusWidth+(temp[1].length-1)*4*4;
|
||||||
|
x = mid-Math.round((tempWidth-minusWidth)/2)-minusWidth;
|
||||||
|
} else {
|
||||||
|
tempWidth = temp[1].length*4*4;
|
||||||
|
x = mid-Math.round(tempWidth/2);
|
||||||
|
}
|
||||||
|
g.setFont("4x5NumPretty",4);
|
||||||
|
g.drawString(temp[1],x,114);
|
||||||
|
square(x+tempWidth,114,6,2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// queue draw in one minute
|
||||||
|
queueDraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
g.clear();
|
||||||
|
drawBg();
|
||||||
|
Bangle.setUI("clock"); // Show launcher when middle button pressed
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
draw();
|
||||||
|
|
After Width: | Height: | Size: 8.4 KiB |
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwgJC/AAVh/E/hgFC/O/AoMB8EZwc8AoUYgYFBgFgjAXDAowXBAo8B/ARBn4FGAAsBmAFE2ADBhwFEj4VEn+AgPvAontgfwv+ABIMCMwIVCgf4FIWAAoN3sAFCwERoEB0MHwF3gEF0MPwFEAoW/4ALD/4tCg/hAoYhB/5ZDwF+Aok0gEIkEf/4AB8eMBoM2bkw="))
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"id": "dinoClock",
|
||||||
|
"name": "Dino Clock",
|
||||||
|
"description": "Clock with dino from Chrome",
|
||||||
|
"screenshots": [{"url":"screens/screen1.png"}],
|
||||||
|
"icon": "app.png",
|
||||||
|
"version": "0.01",
|
||||||
|
"type": "clock",
|
||||||
|
"tags": "clock, weather, dino, trex, chrome",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"allow_emulator": true,
|
||||||
|
"readme": "README.md",
|
||||||
|
"storage": [
|
||||||
|
{"name":"dinoClock.app.js","url":"app.js"},
|
||||||
|
{"name":"dinoClock.img","url":"icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
}
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -11,3 +11,4 @@
|
||||||
0.11: Fix bangle.js 1 white icons not displaying
|
0.11: Fix bangle.js 1 white icons not displaying
|
||||||
0.12: On Bangle 2 change to swiping up/down to move between pages as to match page indicator. Swiping from left to right now loads the clock.
|
0.12: On Bangle 2 change to swiping up/down to move between pages as to match page indicator. Swiping from left to right now loads the clock.
|
||||||
0.13: Added swipeExit setting so that left-right to exit is an option
|
0.13: Added swipeExit setting so that left-right to exit is an option
|
||||||
|
0.14: Don't move pages when doing exit swipe.
|
||||||
|
|
|
@ -29,6 +29,6 @@ Bangle 2:
|
||||||
|
|
||||||
**Touch** - icon to select, scond touch launches app
|
**Touch** - icon to select, scond touch launches app
|
||||||
|
|
||||||
**Swipe Left** - move to next page of app icons
|
**Swipe Left/Up** - move to next page of app icons
|
||||||
|
|
||||||
**Swipe Right** - move to previous page of app icons
|
**Swipe Right/Down** - move to previous page of app icons
|
||||||
|
|
|
@ -93,7 +93,7 @@ Bangle.on("swipe",(dirLeftRight, dirUpDown)=>{
|
||||||
if (dirUpDown==-1||dirLeftRight==-1){
|
if (dirUpDown==-1||dirLeftRight==-1){
|
||||||
++page; if (page>maxPage) page=0;
|
++page; if (page>maxPage) page=0;
|
||||||
drawPage(page);
|
drawPage(page);
|
||||||
} else if (dirUpDown==1||dirLeftRight==1){
|
} else if (dirUpDown==1||(dirLeftRight==1 && !settings.swipeExit)){
|
||||||
--page; if (page<0) page=maxPage;
|
--page; if (page<0) page=maxPage;
|
||||||
drawPage(page);
|
drawPage(page);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "dtlaunch",
|
"id": "dtlaunch",
|
||||||
"name": "Desktop Launcher",
|
"name": "Desktop Launcher",
|
||||||
"version": "0.13",
|
"version": "0.14",
|
||||||
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
|
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
|
||||||
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
|
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: New App!
|
|
@ -0,0 +1,33 @@
|
||||||
|
# F9 Lander
|
||||||
|
|
||||||
|
Land a Falcon 9 booster on a drone ship.
|
||||||
|
|
||||||
|
## Game play
|
||||||
|
|
||||||
|
Attempt to land your Falcon 9 booster on a drone ship before running out of fuel.
|
||||||
|
A successful landing requires:
|
||||||
|
* setting down on the ship
|
||||||
|
* the booster has to be mostly vertical
|
||||||
|
* the landing speed cannot be too high
|
||||||
|
|
||||||
|
## Controls
|
||||||
|
|
||||||
|
The angle of the booster is controlled by tilting the watch side-to-side. The
|
||||||
|
throttle level is controlled by tilting the watch forward and back:
|
||||||
|
* screen horizontal (face up) means no throttle
|
||||||
|
* screen vertical corresponds to full throttle
|
||||||
|
|
||||||
|
The fuel burn rate is proportional to the throttle level.
|
||||||
|
|
||||||
|
## Creators
|
||||||
|
Liam Kl. B.
|
||||||
|
|
||||||
|
Marko Kl. B.
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwcA/4AD/P8yVJkgCCye27dt2wRE//kCIuSuwRIBwgCCpwRQpIRRnYRQkmdCIvPCJICBEZ4RG/IRP/15CJ/z5IRPz4RM/gQB/n+BxICCn/z/P/BxQCDz7mIAX4Cq31/CJ+ebpiYE/IR/CNP/5IROnn//4jP5DFQ5sJCKAjPk3oCMMk4QRQAX4Ckn7jBAA/5CK8nCJPJNHA"))
|
|
@ -0,0 +1,150 @@
|
||||||
|
const falcon9 = Graphics.createImage(`
|
||||||
|
xxxxx
|
||||||
|
xxxxx xxxxx
|
||||||
|
x x
|
||||||
|
x x
|
||||||
|
xxxxx
|
||||||
|
xxxxx
|
||||||
|
xxxxx
|
||||||
|
xxxxx
|
||||||
|
xxxxx
|
||||||
|
xxxxx
|
||||||
|
xxxxx
|
||||||
|
xxxxx
|
||||||
|
xxxxx
|
||||||
|
xxxxx
|
||||||
|
xxxxx
|
||||||
|
xxxxx
|
||||||
|
xxxxx
|
||||||
|
xxxxx
|
||||||
|
xxxxxxxxx
|
||||||
|
xx xxxxx xx
|
||||||
|
xx xx`);
|
||||||
|
|
||||||
|
const droneShip = Graphics.createImage(`
|
||||||
|
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
`);
|
||||||
|
|
||||||
|
const droneX = Math.floor(Math.random()*(g.getWidth()-droneShip.width-40) + 20)
|
||||||
|
const cloudOffs = Math.floor(Math.random()*g.getWidth()/2);
|
||||||
|
|
||||||
|
const oceanHeight = g.getHeight()*0.1;
|
||||||
|
|
||||||
|
const targetY = g.getHeight()-oceanHeight-falcon9.height/2;
|
||||||
|
|
||||||
|
var booster = { x : g.getWidth()/4 + Math.random()*g.getWidth()/2,
|
||||||
|
y : 20,
|
||||||
|
vx : 0,
|
||||||
|
vy : 0,
|
||||||
|
mass : 100,
|
||||||
|
fuel : 100 };
|
||||||
|
|
||||||
|
var exploded = false;
|
||||||
|
var nExplosions = 0;
|
||||||
|
var landed = false;
|
||||||
|
|
||||||
|
const gravity = 4;
|
||||||
|
const dt = 0.1;
|
||||||
|
const fuelBurnRate = 20*(176/g.getHeight());
|
||||||
|
const maxV = 12;
|
||||||
|
|
||||||
|
function flameImageGen (throttle) {
|
||||||
|
var str = " xxx \n xxx \n";
|
||||||
|
str += "xxxxx\n".repeat(throttle);
|
||||||
|
str += " xxx \n x \n";
|
||||||
|
return Graphics.createImage(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawFalcon(x, y, throttle, angle) {
|
||||||
|
g.setColor(1, 1, 1).drawImage(falcon9, x, y, {rotate:angle});
|
||||||
|
if (throttle>0) {
|
||||||
|
var flameImg = flameImageGen(throttle);
|
||||||
|
var r = falcon9.height/2 + flameImg.height/2-1;
|
||||||
|
var xoffs = -Math.sin(angle)*r;
|
||||||
|
var yoffs = Math.cos(angle)*r;
|
||||||
|
if (Math.random()>0.7) g.setColor(1, 0.5, 0);
|
||||||
|
else g.setColor(1, 1, 0);
|
||||||
|
g.drawImage(flameImg, x+xoffs, y+yoffs, {rotate:angle});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawBG() {
|
||||||
|
g.setBgColor(0.2, 0.2, 1).clear();
|
||||||
|
g.setColor(0, 0, 1).fillRect(0, g.getHeight()-oceanHeight, g.getWidth()-1, g.getHeight()-1);
|
||||||
|
g.setColor(0.5, 0.5, 1).fillCircle(cloudOffs+34, 30, 15).fillCircle(cloudOffs+60, 35, 20).fillCircle(cloudOffs+75, 20, 10);
|
||||||
|
g.setColor(1, 1, 0).fillCircle(g.getWidth(), 0, 20);
|
||||||
|
g.setColor(1, 1, 1).drawImage(droneShip, droneX, g.getHeight()-oceanHeight-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showFuel() {
|
||||||
|
g.setColor(0, 0, 0).setFont("4x6:2").setFontAlign(-1, -1, 0).drawString("Fuel: "+Math.abs(booster.fuel).toFixed(0), 4, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderScreen(input) {
|
||||||
|
drawBG();
|
||||||
|
showFuel();
|
||||||
|
drawFalcon(booster.x, booster.y, Math.floor(input.throttle*12), input.angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInputs() {
|
||||||
|
var accel = Bangle.getAccel();
|
||||||
|
var a = Math.PI/2 + Math.atan2(accel.y, accel.x);
|
||||||
|
var t = (1+accel.z);
|
||||||
|
if (t > 1) t = 1;
|
||||||
|
if (t < 0) t = 0;
|
||||||
|
if (booster.fuel<=0) t = 0;
|
||||||
|
return {throttle: t, angle: a};
|
||||||
|
}
|
||||||
|
|
||||||
|
function epilogue(str) {
|
||||||
|
g.setFont("Vector", 24).setFontAlign(0, 0, 0).setColor(0, 0, 0).drawString(str, g.getWidth()/2, g.getHeight()/2).flip();
|
||||||
|
g.setFont("Vector", 16).drawString("<= again exit =>", g.getWidth()/2, g.getHeight()/2+20);
|
||||||
|
clearInterval(stepInterval);
|
||||||
|
Bangle.on("swipe", (d) => { if (d>0) load(); else load('f9lander.app.js'); });
|
||||||
|
}
|
||||||
|
|
||||||
|
function gameStep() {
|
||||||
|
if (exploded) {
|
||||||
|
if (nExplosions++ < 15) {
|
||||||
|
var r = Math.random()*25;
|
||||||
|
var x = Math.random()*30 - 15;
|
||||||
|
var y = Math.random()*30 - 15;
|
||||||
|
g.setColor(1, Math.random()*0.5+0.5, 0).fillCircle(booster.x+x, booster.y+y, r);
|
||||||
|
if (nExplosions==1) Bangle.buzz(600);
|
||||||
|
}
|
||||||
|
else epilogue("You crashed!");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var input = getInputs();
|
||||||
|
if (booster.y >= targetY) {
|
||||||
|
// console.log(booster.x + " " + booster.y + " " + booster.vy + " " + droneX + " " + input.angle);
|
||||||
|
if (Math.abs(booster.x-droneX-droneShip.width/2)<droneShip.width/2 && Math.abs(input.angle)<Math.PI/8 && booster.vy<maxV) {
|
||||||
|
renderScreen({angle:0, throttle:0});
|
||||||
|
epilogue("You landed!");
|
||||||
|
}
|
||||||
|
else exploded = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
booster.x += booster.vx*dt;
|
||||||
|
booster.y += booster.vy*dt;
|
||||||
|
booster.vy += gravity*dt;
|
||||||
|
booster.fuel -= input.throttle*dt*fuelBurnRate;
|
||||||
|
booster.vy += -Math.cos(input.angle)*input.throttle*gravity*3*dt;
|
||||||
|
booster.vx += Math.sin(input.angle)*input.throttle*gravity*3*dt;
|
||||||
|
renderScreen(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stepInterval;
|
||||||
|
Bangle.setLCDTimeout(0);
|
||||||
|
renderScreen({angle:0, throttle:0});
|
||||||
|
g.setFont("Vector", 24).setFontAlign(0, 0, 0).setColor(0, 0, 0).drawString("Swipe to start", g.getWidth()/2, g.getHeight()/2);
|
||||||
|
Bangle.on("swipe", () => {
|
||||||
|
stepInterval = setInterval(gameStep, Math.floor(1000*dt));
|
||||||
|
Bangle.removeListener("swipe");
|
||||||
|
});
|
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 722 B |
After Width: | Height: | Size: 1.3 KiB |