Merge branch 'espruino:master' into master

pull/1886/head
pidajo 2022-05-26 18:28:42 +02:00 committed by GitHub
commit 16ce6105d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
107 changed files with 1810 additions and 763 deletions

View File

@ -256,6 +256,7 @@ and which gives information about the app for the Launcher.
// 'clock' - a clock - required for clocks to automatically start // 'clock' - a clock - required for clocks to automatically start
// 'widget' - a widget // 'widget' - a widget
// 'bootloader' - an app that at startup (app.boot.js) but doesn't have a launcher entry for 'app.js' // 'bootloader' - an app that at startup (app.boot.js) but doesn't have a launcher entry for 'app.js'
// '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' // 'launch' - replacement 'Launcher'
// 'textinput' - provides a 'textinput' library that allows text to be input on the Bangle // 'textinput' - provides a 'textinput' library that allows text to be input on the Bangle

352
android.html Normal file
View File

@ -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>&nbsp;&nbsp;<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>

View File

@ -27,3 +27,5 @@
0.25: Fix redrawing selected Alarm/Timer entry inside edit submenu 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.26: Add support for Monday as first day of the week (#1780)
0.27: New UI! 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

View File

@ -37,8 +37,8 @@ function handleFirstDayOfWeek(dow) {
return dow; return dow;
} }
// Check the first day of week and update the dow field accordingly. // Check the first day of week and update the dow field accordingly (alarms only!)
alarms.forEach(alarm => alarm.dow = handleFirstDayOfWeek(alarm.dow)); alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
function showMainMenu() { function showMainMenu() {
const menu = { const menu = {
@ -50,7 +50,7 @@ function showMainMenu() {
alarms.forEach((e, index) => { alarms.forEach((e, index) => {
var label = e.timer var label = e.timer
? require("time_utils").formatDuration(e.timer) ? require("time_utils").formatDuration(e.timer)
: require("time_utils").formatTime(e.t) + (e.dow > 0 ? (" " + decodeDOW(e)) : ""); : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : "");
menu[label] = { menu[label] = {
value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff), value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff),
onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index) onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index)
@ -111,8 +111,8 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
}, },
/*LANG*/"Repeat": { /*LANG*/"Repeat": {
value: decodeDOW(alarm), value: decodeDOW(alarm),
onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.dow, dow => { onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, alarm.dow, (repeat, dow) => {
alarm.rp = dow > 0; alarm.rp = repeat;
alarm.dow = dow; alarm.dow = dow;
alarm.t = require("time_utils").encodeTime(time); alarm.t = require("time_utils").encodeTime(time);
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex); setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex);
@ -158,14 +158,14 @@ function saveAlarm(alarm, alarmIndex, time) {
} }
function saveAndReload() { function saveAndReload() {
// Before saving revert the dow to the standard format // Before saving revert the dow to the standard format (alarms only!)
alarms.forEach(a => a.dow = handleFirstDayOfWeek(a.dow, firstDayOfWeek)); alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
require("sched").setAlarms(alarms); require("sched").setAlarms(alarms);
require("sched").reload(); require("sched").reload();
// Fix after save // Fix after save
alarms.forEach(a => a.dow = handleFirstDayOfWeek(a.dow, firstDayOfWeek)); alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
} }
function decodeDOW(alarm) { function decodeDOW(alarm) {
@ -178,42 +178,51 @@ function decodeDOW(alarm) {
: "Once" : "Once"
} }
function showEditRepeatMenu(dow, dowChangeCallback) { function showEditRepeatMenu(repeat, dow, dowChangeCallback) {
var originalRepeat = repeat;
var originalDow = dow; var originalDow = dow;
var isCustom = dow > 0 && dow != WORKDAYS && dow != WEEKEND && dow != EVERY_DAY; var isCustom = repeat && dow != WORKDAYS && dow != WEEKEND && dow != EVERY_DAY;
const menu = { const menu = {
"": { "title": /*LANG*/"Repeat Alarm" }, "": { "title": /*LANG*/"Repeat Alarm" },
"< Back": () => dowChangeCallback(dow), "< Back": () => dowChangeCallback(repeat, dow),
/*LANG*/"Once": { // No days set: the alarm will fire once /*LANG*/"Once": {
value: dow == 0, // The alarm will fire once. Internally it will be saved
onchange: () => dowChangeCallback(0) // 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": { /*LANG*/"Workdays": {
value: dow == WORKDAYS, value: repeat && dow == WORKDAYS,
onchange: () => dowChangeCallback(WORKDAYS) onchange: () => dowChangeCallback(true, WORKDAYS)
}, },
/*LANG*/"Weekends": { /*LANG*/"Weekends": {
value: dow == WEEKEND, value: repeat && dow == WEEKEND,
onchange: () => dowChangeCallback(WEEKEND) onchange: () => dowChangeCallback(true, WEEKEND)
}, },
/*LANG*/"Every Day": { /*LANG*/"Every Day": {
value: dow == EVERY_DAY, value: repeat && dow == EVERY_DAY,
onchange: () => dowChangeCallback(EVERY_DAY) onchange: () => dowChangeCallback(true, EVERY_DAY)
}, },
/*LANG*/"Custom": { /*LANG*/"Custom": {
value: isCustom ? decodeDOW({ rp: true, dow: dow }) : false, value: isCustom ? decodeDOW({ rp: true, dow: dow }) : false,
onchange: () => setTimeout(showCustomDaysMenu, 10, isCustom ? dow : EVERY_DAY, dowChangeCallback, originalDow) onchange: () => setTimeout(showCustomDaysMenu, 10, isCustom ? dow : EVERY_DAY, dowChangeCallback, originalRepeat, originalDow)
} }
}; };
E.showMenu(menu); E.showMenu(menu);
} }
function showCustomDaysMenu(dow, dowChangeCallback, originalDow) { function showCustomDaysMenu(dow, dowChangeCallback, originalRepeat, originalDow) {
const menu = { const menu = {
"": { "title": /*LANG*/"Custom Days" }, "": { "title": /*LANG*/"Custom Days" },
"< Back": () => dowChangeCallback(dow), "< Back": () => {
// If the user unchecks all the days then we assume repeat = once
// and we force the dow to every day.
var repeat = dow > 0;
dowChangeCallback(repeat, repeat ? dow : EVERY_DAY)
}
}; };
require("date_utils").dows(firstDayOfWeek).forEach((day, i) => { require("date_utils").dows(firstDayOfWeek).forEach((day, i) => {
@ -223,7 +232,7 @@ function showCustomDaysMenu(dow, dowChangeCallback, originalDow) {
}; };
}); });
menu[/*LANG*/"Cancel"] = () => setTimeout(showEditRepeatMenu, 10, originalDow, dowChangeCallback) menu[/*LANG*/"Cancel"] = () => setTimeout(showEditRepeatMenu, 10, originalRepeat, originalDow, dowChangeCallback)
E.showMenu(menu); E.showMenu(menu);
} }
@ -311,12 +320,12 @@ function showAdvancedMenu() {
function enableAll(on) { function enableAll(on) {
if (alarms.filter(e => e.on == !on).length == 0) { if (alarms.filter(e => e.on == !on).length == 0) {
E.showPrompt(on ? /*LANG*/"Nothing to Enable" : /*LANG*/"Nothing to Disable", { E.showAlert(
title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All", on ? /*LANG*/"Nothing to Enable" : /*LANG*/"Nothing to Disable",
buttons: { /*LANG*/"Ok": true } on ? /*LANG*/"Enable All" : /*LANG*/"Disable All"
}).then(() => showAdvancedMenu()); ).then(() => showAdvancedMenu());
} else { } else {
E.showPrompt(/*LANG*/"Are you sure?", { title: on ? "/*LANG*/Enable All" : /*LANG*/"Disable All" }).then((confirm) => { E.showPrompt(/*LANG*/"Are you sure?", { title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All" }).then((confirm) => {
if (confirm) { if (confirm) {
alarms.forEach(alarm => alarm.on = on); alarms.forEach(alarm => alarm.on = on);
saveAndReload(); saveAndReload();
@ -330,7 +339,7 @@ function enableAll(on) {
function deleteAll() { function deleteAll() {
if (alarms.length == 0) { if (alarms.length == 0) {
E.showPrompt(/*LANG*/"Nothing to delete", { title: /*LANG*/"Delete All", buttons: { /*LANG*/"Ok": true } }).then(() => showAdvancedMenu()); E.showAlert(/*LANG*/"Nothing to delete", /*LANG*/"Delete All").then(() => showAdvancedMenu());
} else { } else {
E.showPrompt(/*LANG*/"Are you sure?", { E.showPrompt(/*LANG*/"Are you sure?", {
title: /*LANG*/"Delete All" title: /*LANG*/"Delete All"

View File

@ -2,7 +2,7 @@
"id": "alarm", "id": "alarm",
"name": "Alarms & Timers", "name": "Alarms & Timers",
"shortName": "Alarms", "shortName": "Alarms",
"version": "0.27", "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",

View File

@ -8,3 +8,4 @@
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.09: Alarm vibration, repeat, and auto-snooze now handled by sched
0.10: Fix SMS bug

View File

@ -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

View File

@ -2,7 +2,7 @@
"id": "android", "id": "android",
"name": "Android Integration", "name": "Android Integration",
"shortName": "Android", "shortName": "Android",
"version": "0.09", "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",

View File

@ -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"}]
} }

View File

@ -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)

View File

@ -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

View File

@ -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));
} }
}); });
} }

View File

@ -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){

View File

@ -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,

View File

@ -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": [

View File

@ -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());
}) });

View File

@ -6,3 +6,4 @@
0.06: Design and usability improvements. 0.06: Design and usability improvements.
0.07: Improved positioning. 0.07: Improved positioning.
0.08: Select the color of widgets correctly. Additional settings to hide colon. 0.08: Select the color of widgets correctly. Additional settings to hide colon.
0.09: Larger font size if colon is hidden to improve readability further.

View File

@ -44,6 +44,16 @@ Graphics.prototype.setLargeFont = function(scale) {
return this; return this;
}; };
Graphics.prototype.setXLargeFont = function(scale) {
// Actual height 53 (55 - 3)
this.setFontCustom(
E.toString(require('heatshrink').decompress(atob('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'))),
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)
this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAB/AAAAAAAP/AAAAAAD//AAAAAA///AAAAAP///AAAAB///8AAAAf///AAAAH///wAAAB///+AAAAH///gAAAAH//4AAAAAH/+AAAAAAH/wAAAAAAH8AAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAH////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gAAH+AAD+AAAD/AAH8AAAB/AAH4AAAA/gAH4AAAAfgAH4AAAAfgAPwAAAAfgAPwAAAAfgAPwAAAAfgAHwAAAAfgAH4AAAAfgAH4AAAA/gAH8AAAA/AAD+AAAD/AAD/gAAH/AAB/////+AAB/////8AAA/////4AAAf////wAAAH////gAAAB///+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAAfwAAAAAAA/gAAAAAAA/AAAAAAAB/AAAAAAAD+AAAAAAAD8AAAAAAAH8AAAAAAAH//////AAH//////AAH//////AAH//////AAH//////AAH//////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4AAA/AAAP4AAB/AAAf4AAD/AAA/4AAD/AAB/4AAH/AAD/4AAP/AAH/AAAf/AAH8AAA//AAH4AAB//AAP4AAD//AAPwAAH+/AAPwAAP8/AAPwAAf4/AAPwAA/4/AAPwAA/w/AAPwAB/g/AAPwAD/A/AAP4AH+A/AAH8AP8A/AAH/A/4A/AAD///wA/AAD///gA/AAB///AA/AAA//+AA/AAAP/8AA/AAAD/wAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAH4AAAHwAAH4AAAH4AAH4AAAH8AAH4AAAP+AAH4AAAH+AAH4A4AB/AAH4A+AA/AAH4B/AA/gAH4D/AAfgAH4H+AAfgAH4P+AAfgAH4f+AAfgAH4/+AAfgAH5/+AAfgAH5//AAfgAH7+/AA/gAH/8/gB/AAH/4f4H/AAH/wf//+AAH/gP//8AAH/AH//8AAH+AD//wAAH8AB//gAAD4AAf+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAAAAD/AAAAAAAP/AAAAAAB//AAAAAAH//AAAAAAf//AAAAAB///AAAAAH///AAAAAf/8/AAAAB//w/AAAAH/+A/AAAA//4A/AAAD//gA/AAAH/+AA/AAAH/4AA/AAAH/gAA/AAAH+AAA/AAAHwAAA/AAAHAAf///AAEAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAP/AHgAAH///AP4AAH///gP8AAH///gP8AAH///gP+AAH///gD/AAH/A/AB/AAH4A/AA/gAH4A+AAfgAH4B+AAfgAH4B+AAfgAH4B8AAfgAH4B8AAfgAH4B+AAfgAH4B+AAfgAH4B+AA/gAH4B/AA/AAH4A/gD/AAH4A/4H+AAH4Af//+AAH4AP//8AAH4AP//4AAHwAD//wAAAAAB//AAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAD////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gP4H+AAD/AfgD/AAH8A/AB/AAH8A/AA/gAH4B+AAfgAH4B+AAfgAPwB8AAfgAPwB8AAfgAPwB+AAfgAPwB+AAfgAH4B+AAfgAH4B/AA/gAH8B/AB/AAH+A/wD/AAD+A/8P+AAB8Af//+AAB4AP//8AAAwAH//4AAAAAD//gAAAAAA//AAAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAHAAPwAAAA/AAPwAAAD/AAPwAAAf/AAPwAAB//AAPwAAP//AAPwAA//8AAPwAH//wAAPwAf/+AAAPwB//4AAAPwP//AAAAPw//8AAAAP3//gAAAAP//+AAAAAP//wAAAAAP//AAAAAAP/4AAAAAAP/gAAAAAAP+AAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAH+A//gAAAf/h//4AAA//z//8AAB/////+AAD/////+AAD///+H/AAH+H/4B/AAH8B/wA/gAH4A/gAfgAH4A/gAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAH4A/gAfgAH4A/gAfgAH8B/wA/gAH/H/4B/AAD///+H/AAD/////+AAB/////+AAA//z//8AAAf/h//4AAAH+A//gAAAAAAH+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAD/8AAAAAAP/+AAAAAAf//AAcAAA///gA8AAB///wB+AAD/x/4B/AAD+AP4B/AAH8AH8A/gAH4AH8A/gAH4AD8AfgAP4AD8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAH4AD8AfgAH4AD4A/gAH8AH4B/AAD+APwD/AAD/g/wP+AAB/////+AAA/////8AAAf////4AAAP////wAAAH////AAAAA///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DxcjFyAfISAiHCAiEg=="), 54+(scale<<8)+(1<<16)); this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAB/AAAAAAAP/AAAAAAD//AAAAAA///AAAAAP///AAAAB///8AAAAf///AAAAH///wAAAB///+AAAAH///gAAAAH//4AAAAAH/+AAAAAAH/wAAAAAAH8AAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAH////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gAAH+AAD+AAAD/AAH8AAAB/AAH4AAAA/gAH4AAAAfgAH4AAAAfgAPwAAAAfgAPwAAAAfgAPwAAAAfgAHwAAAAfgAH4AAAAfgAH4AAAA/gAH8AAAA/AAD+AAAD/AAD/gAAH/AAB/////+AAB/////8AAA/////4AAAf////wAAAH////gAAAB///+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAAfwAAAAAAA/gAAAAAAA/AAAAAAAB/AAAAAAAD+AAAAAAAD8AAAAAAAH8AAAAAAAH//////AAH//////AAH//////AAH//////AAH//////AAH//////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4AAA/AAAP4AAB/AAAf4AAD/AAA/4AAD/AAB/4AAH/AAD/4AAP/AAH/AAAf/AAH8AAA//AAH4AAB//AAP4AAD//AAPwAAH+/AAPwAAP8/AAPwAAf4/AAPwAA/4/AAPwAA/w/AAPwAB/g/AAPwAD/A/AAP4AH+A/AAH8AP8A/AAH/A/4A/AAD///wA/AAD///gA/AAB///AA/AAA//+AA/AAAP/8AA/AAAD/wAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAH4AAAHwAAH4AAAH4AAH4AAAH8AAH4AAAP+AAH4AAAH+AAH4A4AB/AAH4A+AA/AAH4B/AA/gAH4D/AAfgAH4H+AAfgAH4P+AAfgAH4f+AAfgAH4/+AAfgAH5/+AAfgAH5//AAfgAH7+/AA/gAH/8/gB/AAH/4f4H/AAH/wf//+AAH/gP//8AAH/AH//8AAH+AD//wAAH8AB//gAAD4AAf+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAAAAD/AAAAAAAP/AAAAAAB//AAAAAAH//AAAAAAf//AAAAAB///AAAAAH///AAAAAf/8/AAAAB//w/AAAAH/+A/AAAA//4A/AAAD//gA/AAAH/+AA/AAAH/4AA/AAAH/gAA/AAAH+AAA/AAAHwAAA/AAAHAAf///AAEAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAP/AHgAAH///AP4AAH///gP8AAH///gP8AAH///gP+AAH///gD/AAH/A/AB/AAH4A/AA/gAH4A+AAfgAH4B+AAfgAH4B+AAfgAH4B8AAfgAH4B8AAfgAH4B+AAfgAH4B+AAfgAH4B+AA/gAH4B/AA/AAH4A/gD/AAH4A/4H+AAH4Af//+AAH4AP//8AAH4AP//4AAHwAD//wAAAAAB//AAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAD////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gP4H+AAD/AfgD/AAH8A/AB/AAH8A/AA/gAH4B+AAfgAH4B+AAfgAPwB8AAfgAPwB8AAfgAPwB+AAfgAPwB+AAfgAH4B+AAfgAH4B/AA/gAH8B/AB/AAH+A/wD/AAD+A/8P+AAB8Af//+AAB4AP//8AAAwAH//4AAAAAD//gAAAAAA//AAAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAHAAPwAAAA/AAPwAAAD/AAPwAAAf/AAPwAAB//AAPwAAP//AAPwAA//8AAPwAH//wAAPwAf/+AAAPwB//4AAAPwP//AAAAPw//8AAAAP3//gAAAAP//+AAAAAP//wAAAAAP//AAAAAAP/4AAAAAAP/gAAAAAAP+AAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAH+A//gAAAf/h//4AAA//z//8AAB/////+AAD/////+AAD///+H/AAH+H/4B/AAH8B/wA/gAH4A/gAfgAH4A/gAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAH4A/gAfgAH4A/gAfgAH8B/wA/gAH/H/4B/AAD///+H/AAD/////+AAB/////+AAA//z//8AAAf/h//4AAAH+A//gAAAAAAH+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAD/8AAAAAAP/+AAAAAAf//AAcAAA///gA8AAB///wB+AAD/x/4B/AAD+AP4B/AAH8AH8A/gAH4AH8A/gAH4AD8AfgAP4AD8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAH4AD8AfgAH4AD4A/gAH8AH4B/AAD+APwD/AAD/g/wP+AAB/////+AAA/////8AAAf////4AAAP////wAAAH////AAAAA///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DxcjFyAfISAiHCAiEg=="), 54+(scale<<8)+(1<<16));
@ -304,17 +314,13 @@ function drawTime(){
g.setColor(g.theme.bg); g.setColor(g.theme.bg);
g.setFontAlign(0,0); g.setFontAlign(0,0);
var timeStr; var hours = String(date.getHours());
if(settings.hideColon){ var minutes = date.getMinutes();
var hours = date.getHours(); minutes = minutes < 10 ? String("0") + minutes : minutes;
hours -= hours >=12 ? 12 : 0; var colon = settings.hideColon ? "" : ":";
var minutes = date.getMinutes(); var timeStr = hours + colon + minutes;
minutes = minutes < 10 ? String("0") + minutes : minutes;
timeStr = String(hours) + minutes;
} else {
timeStr = locale.time(date,1);
}
// Set y coordinates correctly
y += parseInt((H - y)/2) + 5; y += parseInt((H - y)/2) + 5;
var infoEntry = getInfoEntry(); var infoEntry = getInfoEntry();
@ -324,7 +330,11 @@ 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){
g.setLargeFont(); if(settings.hideColon){
g.setXLargeFont();
} else {
g.setLargeFont();
}
} else { } else {
y -= 15; y -= 15;
g.setMediumFont(); g.setMediumFont();

View File

@ -1,7 +1,7 @@
{ {
"id": "bwclk", "id": "bwclk",
"name": "BW Clock", "name": "BW Clock",
"version": "0.08", "version": "0.09",
"description": "BW Clock.", "description": "BW Clock.",
"readme": "README.md", "readme": "README.md",
"icon": "app.png", "icon": "app.png",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -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

View File

@ -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();

View File

@ -21,5 +21,6 @@
"circle2colorizeIcon": true, "circle2colorizeIcon": true,
"circle3colorizeIcon": true, "circle3colorizeIcon": true,
"circle4colorizeIcon": false, "circle4colorizeIcon": false,
"hrmValidity": 60 "hrmValidity": 60,
"updateInterval": 60
} }

View File

@ -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"}],

View File

@ -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;
}, },

View File

@ -1,2 +1,4 @@
0.01: New Clock Nifty A 0.01: New Clock Nifty A
0.02: Shows the current week number (ISO8601), can be disabled via settings "" 0.02: Shows the current week number (ISO8601), can be disabled via settings
0.03: Call setUI before loading widgets
Improve settings page

View File

@ -1,13 +1,12 @@
# Nifty-A Clock # Nifty-A Clock
Colors are black/white - photos have non correct camera color "blue" Colors are black/white - photos have non correct camera color "blue".
## This is the clock This is the clock:
![](screenshot_nifty.png) ![](screenshot_nifty.png)
## The week number (ISO8601) can be turned of in settings The week number (ISO8601) can be turned off in settings (default is `On`)
(default is **"On"**)
![](screenshot_settings_nifty.png) ![](screenshot_settings_nifty.png)

View File

@ -1,6 +1,6 @@
const locale = require("locale"); const locale = require("locale");
const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; const is12Hour = Object.assign({ "12hour": false }, require("Storage").readJSON("setting.json", true))["12hour"];
const CFG = require('Storage').readJSON("ffcniftya.json", 1) || {showWeekNum: true}; const showWeekNum = Object.assign({ showWeekNum: true }, require('Storage').readJSON("ffcniftya.json", true))["showWeekNum"];
/* Clock *********************************************/ /* Clock *********************************************/
const scale = g.getWidth() / 176; const scale = g.getWidth() / 176;
@ -17,16 +17,17 @@ const center = {
y: Math.round(((viewport.height - widget) / 2) + widget), y: Math.round(((viewport.height - widget) / 2) + widget),
} }
function ISO8601_week_no(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480 // copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480
var tdt = new Date(date.valueOf()); function ISO8601_week_no(date) {
var dayn = (date.getDay() + 6) % 7; var tdt = new Date(date.valueOf());
tdt.setDate(tdt.getDate() - dayn + 3); var dayn = (date.getDay() + 6) % 7;
var firstThursday = tdt.valueOf(); tdt.setDate(tdt.getDate() - dayn + 3);
tdt.setMonth(0, 1); var firstThursday = tdt.valueOf();
if (tdt.getDay() !== 4) { tdt.setMonth(0, 1);
tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7); if (tdt.getDay() !== 4) {
} tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7);
return 1 + Math.ceil((firstThursday - tdt) / 604800000); }
return 1 + Math.ceil((firstThursday - tdt) / 604800000);
} }
function d02(value) { function d02(value) {
@ -59,7 +60,7 @@ function draw() {
g.drawString(year, centerDatesScaleX, center.y - 62 * scale); g.drawString(year, centerDatesScaleX, center.y - 62 * scale);
g.drawString(month, centerDatesScaleX, center.y - 44 * scale); g.drawString(month, centerDatesScaleX, center.y - 44 * scale);
g.drawString(day, centerDatesScaleX, center.y - 26 * scale); g.drawString(day, centerDatesScaleX, center.y - 26 * scale);
if (CFG.showWeekNum) g.drawString(d02(ISO8601_week_no(now)), centerDatesScaleX, center.y + 15 * scale); if (showWeekNum) g.drawString(weekNum, centerDatesScaleX, center.y + 15 * scale);
g.drawString(monthName, centerDatesScaleX, center.y + 48 * scale); g.drawString(monthName, centerDatesScaleX, center.y + 48 * scale);
g.drawString(dayName, centerDatesScaleX, center.y + 66 * scale); g.drawString(dayName, centerDatesScaleX, center.y + 66 * scale);
} }
@ -79,7 +80,6 @@ function clearTickTimer() {
function queueNextTick() { function queueNextTick() {
clearTickTimer(); clearTickTimer();
tickTimer = setTimeout(tick, 60000 - (Date.now() % 60000)); tickTimer = setTimeout(tick, 60000 - (Date.now() % 60000));
// tickTimer = setTimeout(tick, 3000);
} }
function tick() { function tick() {
@ -91,21 +91,16 @@ function tick() {
// Clear the screen once, at startup // Clear the screen once, at startup
g.clear(); g.clear();
// Start ticking
tick(); tick();
// Stop updates when LCD is off, restart when on
Bangle.on('lcdPower', (on) => { Bangle.on('lcdPower', (on) => {
if (on) { if (on) {
tick(); // Start ticking tick();
} else { } else {
clearTickTimer(); // stop ticking clearTickTimer();
} }
}); });
// Load widgets Bangle.setUI("clock");
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
// Show launcher when middle button pressed
Bangle.setUI("clock");

View File

@ -1,7 +1,7 @@
{ {
"id": "ffcniftya", "id": "ffcniftya",
"name": "Nifty-A Clock", "name": "Nifty-A Clock",
"version": "0.02", "version": "0.03",
"description": "A nifty clock with time and date", "description": "A nifty clock with time and date",
"icon": "app.png", "icon": "app.png",
"screenshots": [{"url":"screenshot_nifty.png"}], "screenshots": [{"url":"screenshot_nifty.png"}],

View File

@ -1,22 +1,14 @@
(function(back) { (function (back) {
var FILE = "ffcniftya.json"; const settings = Object.assign({ showWeekNum: true }, require("Storage").readJSON("ffcniftya.json", true));
// Load settings
var cfg = require('Storage').readJSON(FILE, 1) || { showWeekNum: true };
function writeSettings() {
require('Storage').writeJSON(FILE, cfg);
}
// Show the menu
E.showMenu({ E.showMenu({
"" : { "title" : "Nifty-A Clock" }, "": { "title": "Nifty-A Clock" },
"< Back" : () => back(), "< Back": () => back(),
'week number?': { /*LANG*/"Show Week Number": {
value: cfg.showWeekNum, value: settings.showWeekNum,
format: v => v?"On":"Off",
onchange: v => { onchange: v => {
cfg.showWeekNum = v; settings.showWeekNum = v;
writeSettings(); require("Storage").writeJSON("ffcniftya.json", settings);
} }
} }
}); });

View File

@ -1,2 +1,5 @@
0.01: New Clock Nifty B 0.01: New Clock Nifty B
0.02: Added configuration 0.02: Added configuration
0.03: Call setUI before loading widgets
Fix bug with black being unselectable
Improve settings page

View File

@ -1,9 +1,6 @@
# Nifty Series B Clock # Nifty Series B Clock
- Display Time and Date - Display Time and Date
- Color Configuration - Colour Configuration
##
![](screenshot.png) ![](screenshot.png)

View File

@ -1,9 +1,5 @@
const locale = require("locale"); const is12Hour = Object.assign({ "12hour": false }, require("Storage").readJSON("setting.json", true))["12hour"];
const storage = require('Storage'); const color = Object.assign({ color: 63488 }, require("Storage").readJSON("ffcniftyb.json", true)).color; // Default to RED
const is12Hour = (storage.readJSON("setting.json", 1) || {})["12hour"];
const color = (storage.readJSON("ffcniftyb.json", 1) || {})["color"] || 63488 /* red */;
/* Clock *********************************************/ /* Clock *********************************************/
const scale = g.getWidth() / 176; const scale = g.getWidth() / 176;
@ -19,7 +15,7 @@ const center = {
}; };
function d02(value) { function d02(value) {
return ('0' + value).substr(-2); return ("0" + value).substr(-2);
} }
function renderEllipse(g) { function renderEllipse(g) {
@ -35,8 +31,8 @@ function renderText(g) {
const month = d02(now.getMonth() + 1); const month = d02(now.getMonth() + 1);
const year = now.getFullYear(); const year = now.getFullYear();
const month2 = locale.month(now, 3); const month2 = require("locale").month(now, 3);
const day2 = locale.dow(now, 3); const day2 = require("locale").dow(now, 3);
g.setFontAlign(1, 0).setFont("Vector", 90 * scale); g.setFontAlign(1, 0).setFont("Vector", 90 * scale);
g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale); g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale);
@ -96,7 +92,6 @@ function startTick(run) {
stopTick(); stopTick();
run(); run();
ticker = setTimeout(() => startTick(run), 60000 - (Date.now() % 60000)); ticker = setTimeout(() => startTick(run), 60000 - (Date.now() % 60000));
// ticker = setTimeout(() => startTick(run), 3000);
} }
/* Init **********************************************/ /* Init **********************************************/
@ -104,7 +99,7 @@ function startTick(run) {
g.clear(); g.clear();
startTick(draw); startTick(draw);
Bangle.on('lcdPower', (on) => { Bangle.on("lcdPower", (on) => {
if (on) { if (on) {
startTick(draw); startTick(draw);
} else { } else {
@ -112,7 +107,6 @@ Bangle.on('lcdPower', (on) => {
} }
}); });
Bangle.setUI("clock");
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
Bangle.setUI("clock");

View File

@ -1,8 +1,8 @@
{ {
"id": "ffcniftyb", "id": "ffcniftyb",
"name": "Nifty-B Clock", "name": "Nifty-B Clock",
"version": "0.02", "version": "0.03",
"description": "A nifty clock (series B) with time, date and color configuration", "description": "A nifty clock (series B) with time, date and colour configuration",
"icon": "app.png", "icon": "app.png",
"screenshots": [{"url":"screenshot.png"}], "screenshots": [{"url":"screenshot.png"}],
"type": "clock", "type": "clock",

View File

@ -1,49 +1,31 @@
(function (back) { (function (back) {
const storage = require('Storage'); const settings = Object.assign({ color: 63488 }, require("Storage").readJSON("ffcniftyb.json", true));
const SETTINGS_FILE = "ffcniftyb.json";
const colors = { const colors = {
65535: 'White', 65535: /*LANG*/"White",
63488: 'Red', 63488: /*LANG*/"Red",
65504: 'Yellow', 65504: /*LANG*/"Yellow",
2047: 'Cyan', 2047: /*LANG*/"Cyan",
2016: 'Green', 2016: /*LANG*/"Green",
31: 'Blue', 31: /*LANG*/"Blue",
0: 'Black', 0: /*LANG*/"Black"
} }
function load(settings) { const menu = {};
return Object.assign(settings, storage.readJSON(SETTINGS_FILE, 1) || {}); menu[""] = { title: "Nifty-B Clock" };
} menu["< Back"] = back;
function save(settings) { Object.keys(colors).forEach(color => {
storage.write(SETTINGS_FILE, settings) var label = colors[color];
} menu[label] = {
value: settings.color == color,
const settings = load({ onchange: () => {
color: 63488 /* red */, settings.color = color;
require("Storage").write("ffcniftyb.json", settings);
setTimeout(load, 10);
}
};
}); });
const saveColor = (color) => () => { E.showMenu(menu);
settings.color = color;
save(settings);
back();
};
function showMenu(items, opt) {
items[''] = opt || {};
items['< Back'] = back;
E.showMenu(items);
}
showMenu(
Object.keys(colors).reduce((menu, color) => {
menu[colors[color]] = saveColor(color);
return menu;
}, {}),
{
title: 'Color',
selected: Object.keys(colors).indexOf(settings.color)
}
);
}); });

View File

@ -2,7 +2,7 @@
Logs health data to a file every 10 minutes, and provides an app to view it Logs health data to a file every 10 minutes, and provides an app to view it
**BETA - requires firmware 2v11** **BETA - requires firmware 2v11 or later**
## Usage ## Usage

View File

@ -0,0 +1,2 @@
0.01: Initial release
0.02: implemented "direct launch" and "one click exit" settings

12
apps/iconlaunch/README.md Normal file
View File

@ -0,0 +1,12 @@
# Icon launcher
A launcher inspired by smartphones, with an icon-only scrollable menu.
This launcher shows 9 apps per screen, making it much faster to navigate versus the default launcher.
![A screenshot](screenshot1.png)
![Another screenshot](screenshot2.png)
## Technical note
The app uses `E.showScroller`'s code in the app but not the function itself because `E.showScroller` doesn't report the position of a press to the select function.

209
apps/iconlaunch/app.js Normal file
View File

@ -0,0 +1,209 @@
const s = require("Storage");
const settings = s.readJSON("launch.json", true) || { showClocks: true, fullscreen: false,direct:false,oneClickExit:false };
if( settings.oneClickExit)
setWatch(_=> load(), BTN1);
if (!settings.fullscreen) {
Bangle.loadWidgets();
Bangle.drawWidgets();
}
var apps = s
.list(/\.info$/)
.map((app) => {
var a = s.readJSON(app, 1);
return (
a && {
name: a.name,
type: a.type,
icon: a.icon,
sortorder: a.sortorder,
src: a.src,
}
);
})
.filter(
(app) =>
app &&
(app.type == "app" ||
(app.type == "clock" && settings.showClocks) ||
!app.type)
);
apps.sort((a, b) => {
var n = (0 | a.sortorder) - (0 | b.sortorder);
if (n) return n; // do sortorder first
if (a.name < b.name) return -1;
if (a.name > b.name) return 1;
return 0;
});
apps.forEach((app) => {
if (app.icon) app.icon = s.read(app.icon); // should just be a link to a memory area
});
let scroll = 0;
let selectedItem = -1;
const R = Bangle.appRect;
const iconSize = 48;
const appsN = Math.floor(R.w / iconSize);
const whitespace = (R.w - appsN * iconSize) / (appsN + 1);
const itemSize = iconSize + whitespace;
function drawItem(itemI, r) {
g.clearRect(r.x, r.y, r.x + r.w - 1, r.y + r.h - 1);
let x = 0;
for (let i = itemI * appsN; i < appsN * (itemI + 1); i++) {
if (!apps[i]) break;
x += whitespace;
if (!apps[i].icon) {
g.setFontAlign(0,0,0).setFont("12x20:2").drawString("?", x + r.x+iconSize/2, r.y + iconSize/2);
} else {
g.drawImage(apps[i].icon, x + r.x, r.y);
}
if (selectedItem == i) {
g.drawRect(
x + r.x - 1,
r.y - 1,
x + r.x + iconSize + 1,
r.y + iconSize + 1
);
}
x += iconSize;
}
drawText(itemI);
}
function drawItemAuto(i) {
var y = idxToY(i);
g.reset().setClipRect(R.x, y, R.x2, y + itemSize);
drawItem(i, {
x: R.x,
y: y,
w: R.w,
h: itemSize
});
g.setClipRect(0, 0, g.getWidth() - 1, g.getHeight() - 1);
}
let lastIsDown = false;
function drawText(i) {
const selectedApp = apps[selectedItem];
const idy = (selectedItem - (selectedItem % 3)) / 3;
if (!selectedApp || i != idy) return;
const appY = idxToY(idy) + iconSize / 2;
g.setFontAlign(0, 0, 0);
g.setFont("12x20");
const rect = g.stringMetrics(selectedApp.name);
g.clearRect(
R.w / 2 - rect.width / 2,
appY - rect.height / 2,
R.w / 2 + rect.width / 2,
appY + rect.height / 2
);
g.drawString(selectedApp.name, R.w / 2, appY);
}
function selectItem(id, e) {
const iconN = E.clip(Math.floor((e.x - R.x) / itemSize), 0, appsN - 1);
const appId = id * appsN + iconN;
if( settings.direct && apps[appId])
{
load(apps[appId].src);
return;
}
if (appId == selectedItem && apps[appId]) {
const app = apps[appId];
if (!app.src || s.read(app.src) === undefined) {
E.showMessage( /*LANG*/ "App Source\nNot found");
} else {
load(app.src);
}
}
selectedItem = appId;
drawItems();
}
function idxToY(i) {
return i * itemSize + R.y - (scroll & ~1);
}
function YtoIdx(y) {
return Math.floor((y + (scroll & ~1) - R.y) / itemSize);
}
function drawItems() {
g.reset().clearRect(R.x, R.y, R.x2, R.y2);
g.setClipRect(R.x, R.y, R.x2, R.y2);
var a = YtoIdx(R.y);
var b = Math.min(YtoIdx(R.y2), 99);
for (var i = a; i <= b; i++)
drawItem(i, {
x: R.x,
y: idxToY(i),
w: R.w,
h: itemSize,
});
g.setClipRect(0, 0, g.getWidth() - 1, g.getHeight() - 1);
}
drawItems();
g.flip();
const itemsN = Math.ceil(apps.length / appsN);
Bangle.setUI({
mode: "custom",
drag: (e) => {
let dy = e.dy;
if (scroll + R.h - dy > itemsN * itemSize) {
dy = scroll + R.h - itemsN * itemSize;
}
if (scroll - dy < 0) {
dy = scroll;
}
scroll -= dy;
scroll = E.clip(scroll, 0, itemSize * (itemsN - 1));
g.setClipRect(R.x, R.y, R.x2, R.y2);
g.scroll(0, dy);
if (dy < 0) {
g.setClipRect(R.x, R.y2 - (1 - dy), R.x2, R.y2);
let i = YtoIdx(R.y2 - (1 - dy));
let y = idxToY(i);
while (y < R.y2) {
drawItem(i, {
x: R.x,
y: y,
w: R.w,
h: itemSize,
});
i++;
y += itemSize;
}
} else {
// d>0
g.setClipRect(R.x, R.y, R.x2, R.y + dy);
let i = YtoIdx(R.y + dy);
let y = idxToY(i);
while (y > R.y - itemSize) {
drawItem(i, {
x: R.x,
y: y,
w: R.w,
h: itemSize,
});
y -= itemSize;
i--;
}
}
g.setClipRect(0, 0, g.getWidth() - 1, g.getHeight() - 1);
},
touch: (_, e) => {
if (e.y < R.y - 4) return;
var i = YtoIdx(e.y);
selectItem(i, e);
},
});

BIN
apps/iconlaunch/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,18 @@
{
"id": "iconlaunch",
"name": "Icon Launcher",
"shortName" : "Icon launcher",
"version": "0.02",
"icon": "app.png",
"description": "A launcher inspired by smartphones, with an icon-only scrollable menu.",
"tags": "tool,system,launcher",
"type": "launch",
"supports": ["BANGLEJS2"],
"storage": [
{ "name": "iconlaunch.app.js", "url": "app.js" },
{ "name": "iconlaunch.settings.js", "url": "settings.js" }
],
"screenshots": [{ "url": "screenshot1.png" }, { "url": "screenshot2.png" }],
"readme": "README.md",
"sortorder": -10
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -0,0 +1,38 @@
// make sure to enclose the function in parentheses
(function(back) {
let settings = Object.assign({
showClocks: true,
fullscreen: false
}, require("Storage").readJSON("launch.json", true) || {});
let fonts = g.getFonts();
function save(key, value) {
settings[key] = value;
require("Storage").write("launch.json",settings);
}
const appMenu = {
"": { "title": /*LANG*/"Launcher" },
/*LANG*/"< Back": back,
/*LANG*/"Show Clocks": {
value: settings.showClocks == true,
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
onchange: (m) => { save("showClocks", m) }
},
/*LANG*/"Fullscreen": {
value: settings.fullscreen == true,
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
onchange: (m) => { save("fullscreen", m) }
},
/*LANG*/"Direct launch": {
value: settings.direct == true,
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
onchange: (m) => { save("direct", m) }
},
/*LANG*/"One click exit": {
value: settings.oneClickExit == true,
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
onchange: (m) => { save("oneClickExit", m) }
}
};
E.showMenu(appMenu);
});

View File

@ -11,6 +11,8 @@ const speedUnits = { // how many kph per X?
"kmh": 1, "kmh": 1,
"kph": 1, "kph": 1,
"km/h": 1, "km/h": 1,
"kmt": 1,
"km/tim": 1,
"mph": 1.60934, "mph": 1.60934,
"kts": 1.852 "kts": 1.852
}; };

View File

@ -50,4 +50,4 @@
0.35: Reset graphics colors before rendering a message (possibly fix #1752) 0.35: Reset graphics colors before rendering a message (possibly fix #1752)
0.36: Ensure a new message plus an almost immediate deletion of that message doesn't load the messages app (fix #1362) 0.36: Ensure a new message plus an almost immediate deletion of that message doesn't load the messages app (fix #1362)
0.37: Now use the setUI 'back' icon in the top left rather than specific buttons/menu items 0.37: Now use the setUI 'back' icon in the top left rather than specific buttons/menu items
0.38: Add telegram foss handling

View File

@ -67,100 +67,6 @@ function saveMessages() {
require("Storage").writeJSON("messages.json",MESSAGES) require("Storage").writeJSON("messages.json",MESSAGES)
} }
function getNotificationImage() {
return atob("HBKBAD///8H///iP//8cf//j4//8f5//j/x/8//j/H//H4//4PB//EYj/44HH/Hw+P4//8fH//44///xH///g////A==");
}
function getFBIcon() {
return atob("GBiBAAAAAAAAAAAYAAD/AAP/wAf/4A/48A/g8B/g+B/j+B/n+D/n/D8A/B8A+B+B+B/n+A/n8A/n8Afn4APnwADnAAAAAAAAAAAAAA==");
}
function getPosImage() {
return atob("GRSBAAAAAYAAAcAAAeAAAfAAAfAAAfAAAfAAAfAAAfBgAfA4AfAeAfAPgfAD4fAA+fAAP/AAD/AAA/AAAPAAADAAAA==");
}
function getNegImage() {
return atob("FhaBADAAMeAB78AP/4B/fwP4/h/B/P4D//AH/4AP/AAf4AB/gAP/AB/+AP/8B/P4P4fx/A/v4B//AD94AHjAAMA=");
}
/*
* icons should be 24x24px with 1bpp colors and 'Transparency to Color'
* http://www.espruino.com/Image+Converter
*/
function getMessageImage(msg) {
if (msg.img) return atob(msg.img);
var s = (msg.src||"").toLowerCase();
if (s=="alarm" || s =="alarmclockreceiver") return atob("GBjBAP////8AAAAAAAACAEAHAOAefng5/5wTgcgHAOAOGHAMGDAYGBgYGBgYGBgYGBgYDhgYBxgMATAOAHAHAOADgcAB/4AAfgAAAAAAAAA=");
if (s=="bibel") return atob("GBgBAAAAA//wD//4D//4H//4H/f4H/f4H+P4H4D4H4D4H/f4H/f4H/f4H/f4H/f4H//4H//4H//4GAAAEAAAEAAACAAAB//4AAAA");
if (s=="calendar") return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA==");
if (s=="corona-warn") return atob("GBgBAAAAABwAAP+AAf/gA//wB/PwD/PgDzvAHzuAP8EAP8AAPAAAPMAAP8AAH8AAHzsADzuAB/PAB/PgA//wAP/gAH+AAAwAAAAA");
if (s=="discord") return atob("GBgBAAAAAAAAAAAAAIEABwDgDP8wH//4H//4P//8P//8P//8Pjx8fhh+fzz+f//+f//+e//ePH48HwD4AgBAAAAAAAAAAAAAAAAA");
if (s=="facebook") return getFBIcon();
if (s=="gmail") return getNotificationImage();
if (s=="google home") return atob("GBiCAAAAAAAAAAAAAAAAAAAAAoAAAAAACqAAAAAAKqwAAAAAqroAAAACquqAAAAKq+qgAAAqr/qoAACqv/6qAAKq//+qgA6r///qsAqr///6sAqv///6sAqv///6sAqv///6sA6v///6sA6v///qsA6qqqqqsA6qqqqqsA6qqqqqsAP7///vwAAAAAAAAAAAAAAAAA==");
if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA=");
if (s=="home assistant") return atob("FhaBAAAAAADAAAeAAD8AAf4AD/3AfP8D7fwft/D/P8ec572zbzbNsOEhw+AfD8D8P4fw/z/D/P8P8/w/z/AAAAA=");
if (s=="instagram") return atob("GBiBAAAAAAAAAAAAAAAAAAP/wAYAYAwAMAgAkAh+EAjDEAiBEAiBEAiBEAiBEAjDEAh+EAgAEAwAMAYAYAP/wAAAAAAAAAAAAAAAAA==");
if (s=="kalender") return atob("GBgBBgBgBQCgff++RQCiRgBiQAACf//+QAACQAACR//iRJkiRIEiR//iRNsiRIEiRJkiR//iRIEiRIEiR//iQAACQAACf//+AAAA");
if (s=="lieferando") return atob("GBgBABgAAH5wAP9wAf/4A//4B//4D//4H//4P/88fV8+fV4//V4//Vw/HVw4HVw4HBg4HBg4HBg4HDg4Hjw4Hj84Hj44Hj44Hj44");
if (s=="mail") return getNotificationImage();
if (s=="messenger") return getFBIcon();
if (s=="nina") return atob("GBgBAAAABAAQCAAICAAIEAAEEgAkJAgSJBwSKRxKSj4pUn8lVP+VVP+VUgAlSgApKQBKJAASJAASEgAkEAAECAAICAAIBAAQAAAA");
if (s=="outlook mail") return atob("HBwBAAAAAAAAAAAIAAAfwAAP/gAB/+AAP/5/A//v/D/+/8P/7/g+Pv8Dye/gPd74w5znHDnOB8Oc4Pw8nv/Dwe/8Pj7/w//v/D/+/8P/7/gf/gAA/+AAAfwAAACAAAAAAAAAAAA=");
if (s=="phone") return atob("FxeBABgAAPgAAfAAB/AAD+AAH+AAP8AAP4AAfgAA/AAA+AAA+AAA+AAB+AAB+AAB+OAB//AB//gB//gA//AA/8AAf4AAPAA=");
if (s=="post & dhl") return atob("GBgBAPgAE/5wMwZ8NgN8NgP4NgP4HgP4HgPwDwfgD//AB/+AAf8AAAAABs7AHcdgG4MwAAAAGESAFESAEkSAEnyAEkSAFESAGETw");
if (s=="signal") return atob("GBgBAAAAAGwAAQGAAhggCP8QE//AB//oJ//kL//wD//0D//wT//wD//wL//0J//kB//oA//ICf8ABfxgBYBAADoABMAABAAAAAAA");
if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA==");
if (s=="slack") return atob("GBiBAAAAAAAAAABAAAHvAAHvAADvAAAPAB/PMB/veD/veB/mcAAAABzH8B3v+B3v+B3n8AHgAAHuAAHvAAHvAADGAAAAAAAAAAAAAA==");
if (s=="sms message") return getNotificationImage();
if (s=="snapchat") return atob("GBgBAAAAAAAAAH4AAf+AAf+AA//AA//AA//AA//AA//AH//4D//wB//gA//AB//gD//wH//4f//+P//8D//wAf+AAH4AAAAAAAAA");
if (s=="teams") return atob("GBgBAAAAAAAAAAQAAB4AAD8IAA8cP/M+f/scf/gIeDgAfvvefvvffvvffvvffvvff/vff/veP/PeAA/cAH/AAD+AAD8AAAQAAAAA");
if (s=="telegram") return atob("GBiBAAAAAAAAAAAAAAAAAwAAHwAA/wAD/wAf3gD/Pgf+fh/4/v/z/P/H/D8P/Acf/AM//AF/+AF/+AH/+ADz+ADh+ADAcAAAMAAAAA==");
if (s=="threema") return atob("GBjB/4Yx//8AAAAAAAAAAAAAfgAB/4AD/8AH/+AH/+AP//AP2/APw/APw/AHw+AH/+AH/8AH/4AH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=");
if (s=="to do") return atob("GBgBAAAAAAAAAAAwAAB4AAD8AAH+AAP/DAf/Hg//Px/+f7/8///4///wf//gP//AH/+AD/8AB/4AA/wAAfgAAPAAAGAAAAAAAAAA");
if (s=="twitch") return atob("GBgBH//+P//+P//+eAAGeAAGeAAGeDGGeDOGeDOGeDOGeDOGeDOGeDOGeAAOeAAOeAAcf4/4f5/wf7/gf//Af/+AA/AAA+AAAcAA");
if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA");
if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA==");
if (s=="wordfeud") return atob("GBgCWqqqqqqlf//////9v//////+v/////++v/////++v8///Lu+v8///L++v8///P/+v8v//P/+v9v//P/+v+fx/P/+v+Pk+P/+v/PN+f/+v/POuv/+v/Ofdv/+v/NvM//+v/I/Y//+v/k/k//+v/i/w//+v/7/6//+v//////+v//////+f//////9Wqqqqqql");
if (s=="youtube") return atob("GBgBAAAAAAAAAAAAAAAAAf8AH//4P//4P//8P//8P5/8P4/8f4P8f4P8P4/8P5/8P//8P//8P//4H//4Af8AAAAAAAAAAAAAAAAA");
if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A=");
return getNotificationImage();
}
function getMessageImageCol(msg,def) {
return {
// generic colors, using B2-safe colors
"alarm": "#fff",
"mail": "#ff0",
"music": "#f0f",
"phone": "#0f0",
"sms message": "#0ff",
// brands, according to https://www.schemecolor.com/?s (picking one for multicolored logos)
// all dithered on B2, but we only use the color for the icons. (Could maybe pick the closest 3-bit color for B2?)
"bibel": "#54342c",
"discord": "#738adb",
"facebook": "#4267b2",
"gmail": "#ea4335",
"google home": "#fbbc05",
"hangouts": "#1ba261",
"home assistant": "#fff", // ha-blue is #41bdf5, but that's the background
"instagram": "#dd2a7b",
"liferando": "#ee5c00",
"messenger": "#0078ff",
"nina": "#e57004",
"outlook mail": "#0072c6",
"post & dhl": "#f2c101",
"signal": "#00f",
"skype": "#00aff0",
"slack": "#e51670",
"snapchat": "#ff0",
"teams": "#464eb8",
"telegram": "#0088cc",
"threema": "#000",
"to do": "#3999e5",
"twitch": "#6441A4",
"twitter": "#1da1f2",
"whatsapp": "#4fce5d",
"wordfeud": "#e7d3c7",
"youtube": "#f00",
}[(msg.src||"").toLowerCase()]||(def !== undefined?def:g.theme.fg);
}
function showMapMessage(msg) { function showMapMessage(msg) {
active = "map"; active = "map";
var m; var m;
@ -387,7 +293,7 @@ function showMessage(msgid) {
var buttons = [ var buttons = [
]; ];
if (msg.positive) { if (msg.positive) {
buttons.push({type:"btn", src:getPosImage(), cb:()=>{ buttons.push({type:"btn", src:atob("GRSBAAAAAYAAAcAAAeAAAfAAAfAAAfAAAfAAAfAAAfBgAfA4AfAeAfAPgfAD4fAA+fAAP/AAD/AAA/AAAPAAADAAAA=="), cb:()=>{
msg.new = false; saveMessages(); msg.new = false; saveMessages();
cancelReloadTimeout(); // don't auto-reload to clock now cancelReloadTimeout(); // don't auto-reload to clock now
Bangle.messageResponse(msg,true); Bangle.messageResponse(msg,true);
@ -396,7 +302,7 @@ function showMessage(msgid) {
} }
if (msg.negative) { if (msg.negative) {
if (buttons.length) buttons.push({width:32}); // nasty hack... if (buttons.length) buttons.push({width:32}); // nasty hack...
buttons.push({type:"btn", src:getNegImage(), cb:()=>{ buttons.push({type:"btn", src:atob("FhaBADAAMeAB78AP/4B/fwP4/h/B/P4D//AH/4AP/AAf4AB/gAP/AB/+AP/8B/P4P4fx/A/v4B//AD94AHjAAMA="), cb:()=>{
msg.new = false; saveMessages(); msg.new = false; saveMessages();
cancelReloadTimeout(); // don't auto-reload to clock now cancelReloadTimeout(); // don't auto-reload to clock now
Bangle.messageResponse(msg,false); Bangle.messageResponse(msg,false);
@ -411,7 +317,7 @@ function showMessage(msgid) {
{type:"txt", font:fontSmall, label:msg.src||/*LANG*/"Message", bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:2, halign:1 }, {type:"txt", font:fontSmall, label:msg.src||/*LANG*/"Message", bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:2, halign:1 },
title?{type:"txt", font:titleFont, label:title, bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:2 }:{}, title?{type:"txt", font:titleFont, label:title, bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:2 }:{},
]}, ]},
{ type:"btn", src:getMessageImage(msg), col:getMessageImageCol(msg), pad: 3, cb:()=>{ { type:"btn", src:require("messages").getMessageImage(msg), col:require("messages").getMessageImageCol(msg), pad: 3, cb:()=>{
cancelReloadTimeout(); // don't auto-reload to clock now cancelReloadTimeout(); // don't auto-reload to clock now
showMessageSettings(msg); showMessageSettings(msg);
}}, }},
@ -467,14 +373,14 @@ function checkMessages(options) {
g.clearRect(r.x,r.y,r.x+r.w, r.y+r.h); g.clearRect(r.x,r.y,r.x+r.w, r.y+r.h);
if (!msg) return; if (!msg) return;
var x = r.x+2, title = msg.title, body = msg.body; var x = r.x+2, title = msg.title, body = msg.body;
var img = getMessageImage(msg); var img = require("messages").getMessageImage(msg);
if (msg.id=="music") { if (msg.id=="music") {
title = msg.artist || /*LANG*/"Music"; title = msg.artist || /*LANG*/"Music";
body = msg.track; body = msg.track;
} }
if (img) { if (img) {
var fg = g.getColor(); var fg = g.getColor();
g.setColor(getMessageImageCol(msg,fg)).drawImage(img, x+24, r.y+24, {rotate:0}) // force centering g.setColor(require("messages").getMessageImageCol(msg,fg)).drawImage(img, x+24, r.y+24, {rotate:0}) // force centering
.setColor(fg); // only color the icon .setColor(fg); // only color the icon
x += 50; x += 50;
} }

View File

@ -104,3 +104,84 @@ exports.clearAll = function(event) {
if (global.WIDGETS && WIDGETS.messages) if (global.WIDGETS && WIDGETS.messages)
WIDGETS.messages.hide(); WIDGETS.messages.hide();
} }
exports.getMessageImage = function(msg) {
/*
* icons should be 24x24px with 1bpp colors and 'Transparency to Color'
* http://www.espruino.com/Image+Converter
*/
if (msg.img) return atob(msg.img);
var s = (msg.src||"").toLowerCase();
if (s=="alarm" || s =="alarmclockreceiver") return atob("GBjBAP////8AAAAAAAACAEAHAOAefng5/5wTgcgHAOAOGHAMGDAYGBgYGBgYGBgYGBgYDhgYBxgMATAOAHAHAOADgcAB/4AAfgAAAAAAAAA=");
if (s=="bibel") return atob("GBgBAAAAA//wD//4D//4H//4H/f4H/f4H+P4H4D4H4D4H/f4H/f4H/f4H/f4H/f4H//4H//4H//4GAAAEAAAEAAACAAAB//4AAAA");
if (s=="calendar") return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA==");
if (s=="corona-warn") return atob("GBgBAAAAABwAAP+AAf/gA//wB/PwD/PgDzvAHzuAP8EAP8AAPAAAPMAAP8AAH8AAHzsADzuAB/PAB/PgA//wAP/gAH+AAAwAAAAA");
if (s=="discord") return atob("GBgBAAAAAAAAAAAAAIEABwDgDP8wH//4H//4P//8P//8P//8Pjx8fhh+fzz+f//+f//+e//ePH48HwD4AgBAAAAAAAAAAAAAAAAA");
if (s=="facebook" || s=="messenger") return atob("GBiBAAAAAAAAAAAYAAD/AAP/wAf/4A/48A/g8B/g+B/j+B/n+D/n/D8A/B8A+B+B+B/n+A/n8A/n8Afn4APnwADnAAAAAAAAAAAAAA==");
if (s=="google home") return atob("GBiCAAAAAAAAAAAAAAAAAAAAAoAAAAAACqAAAAAAKqwAAAAAqroAAAACquqAAAAKq+qgAAAqr/qoAACqv/6qAAKq//+qgA6r///qsAqr///6sAqv///6sAqv///6sAqv///6sA6v///6sA6v///qsA6qqqqqsA6qqqqqsA6qqqqqsAP7///vwAAAAAAAAAAAAAAAAA==");
if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA=");
if (s=="home assistant") return atob("FhaBAAAAAADAAAeAAD8AAf4AD/3AfP8D7fwft/D/P8ec572zbzbNsOEhw+AfD8D8P4fw/z/D/P8P8/w/z/AAAAA=");
if (s=="instagram") return atob("GBiBAAAAAAAAAAAAAAAAAAP/wAYAYAwAMAgAkAh+EAjDEAiBEAiBEAiBEAiBEAjDEAh+EAgAEAwAMAYAYAP/wAAAAAAAAAAAAAAAAA==");
if (s=="kalender") return atob("GBgBBgBgBQCgff++RQCiRgBiQAACf//+QAACQAACR//iRJkiRIEiR//iRNsiRIEiRJkiR//iRIEiRIEiR//iQAACQAACf//+AAAA");
if (s=="lieferando") return atob("GBgBABgAAH5wAP9wAf/4A//4B//4D//4H//4P/88fV8+fV4//V4//Vw/HVw4HVw4HBg4HBg4HBg4HDg4Hjw4Hj84Hj44Hj44Hj44");
if (s=="nina") return atob("GBgBAAAABAAQCAAICAAIEAAEEgAkJAgSJBwSKRxKSj4pUn8lVP+VVP+VUgAlSgApKQBKJAASJAASEgAkEAAECAAICAAIBAAQAAAA");
if (s=="outlook mail") return atob("HBwBAAAAAAAAAAAIAAAfwAAP/gAB/+AAP/5/A//v/D/+/8P/7/g+Pv8Dye/gPd74w5znHDnOB8Oc4Pw8nv/Dwe/8Pj7/w//v/D/+/8P/7/gf/gAA/+AAAfwAAACAAAAAAAAAAAA=");
if (s=="phone") return atob("FxeBABgAAPgAAfAAB/AAD+AAH+AAP8AAP4AAfgAA/AAA+AAA+AAA+AAB+AAB+AAB+OAB//AB//gB//gA//AA/8AAf4AAPAA=");
if (s=="post & dhl") return atob("GBgBAPgAE/5wMwZ8NgN8NgP4NgP4HgP4HgPwDwfgD//AB/+AAf8AAAAABs7AHcdgG4MwAAAAGESAFESAEkSAEnyAEkSAFESAGETw");
if (s=="signal") return atob("GBgBAAAAAGwAAQGAAhggCP8QE//AB//oJ//kL//wD//0D//wT//wD//wL//0J//kB//oA//ICf8ABfxgBYBAADoABMAABAAAAAAA");
if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA==");
if (s=="slack") return atob("GBiBAAAAAAAAAABAAAHvAAHvAADvAAAPAB/PMB/veD/veB/mcAAAABzH8B3v+B3v+B3n8AHgAAHuAAHvAAHvAADGAAAAAAAAAAAAAA==");
if (s=="snapchat") return atob("GBgBAAAAAAAAAH4AAf+AAf+AA//AA//AA//AA//AA//AH//4D//wB//gA//AB//gD//wH//4f//+P//8D//wAf+AAH4AAAAAAAAA");
if (s=="teams") return atob("GBgBAAAAAAAAAAQAAB4AAD8IAA8cP/M+f/scf/gIeDgAfvvefvvffvvffvvffvvff/vff/veP/PeAA/cAH/AAD+AAD8AAAQAAAAA");
if (s=="telegram" || s=="telegram foss") return atob("GBiBAAAAAAAAAAAAAAAAAwAAHwAA/wAD/wAf3gD/Pgf+fh/4/v/z/P/H/D8P/Acf/AM//AF/+AF/+AH/+ADz+ADh+ADAcAAAMAAAAA==");
if (s=="threema") return atob("GBjB/4Yx//8AAAAAAAAAAAAAfgAB/4AD/8AH/+AH/+AP//AP2/APw/APw/AHw+AH/+AH/8AH/4AH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=");
if (s=="to do") return atob("GBgBAAAAAAAAAAAwAAB4AAD8AAH+AAP/DAf/Hg//Px/+f7/8///4///wf//gP//AH/+AD/8AB/4AA/wAAfgAAPAAAGAAAAAAAAAA");
if (s=="twitch") return atob("GBgBH//+P//+P//+eAAGeAAGeAAGeDGGeDOGeDOGeDOGeDOGeDOGeDOGeAAOeAAOeAAcf4/4f5/wf7/gf//Af/+AA/AAA+AAAcAA");
if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA");
if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA==");
if (s=="wordfeud") return atob("GBgCWqqqqqqlf//////9v//////+v/////++v/////++v8///Lu+v8///L++v8///P/+v8v//P/+v9v//P/+v+fx/P/+v+Pk+P/+v/PN+f/+v/POuv/+v/Ofdv/+v/NvM//+v/I/Y//+v/k/k//+v/i/w//+v/7/6//+v//////+v//////+f//////9Wqqqqqql");
if (s=="youtube") return atob("GBgBAAAAAAAAAAAAAAAAAf8AH//4P//4P//8P//8P5/8P4/8f4P8f4P8P4/8P5/8P//8P//8P//4H//4Af8AAAAAAAAAAAAAAAAA");
if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A=");
// if (s=="sms message" || s=="mail" || s=="gmail") // .. default icon (below)
return atob("HBKBAD///8H///iP//8cf//j4//8f5//j/x/8//j/H//H4//4PB//EYj/44HH/Hw+P4//8fH//44///xH///g////A==");
};
exports.getMessageImageCol = function(msg,def) {
return {
// generic colors, using B2-safe colors
"alarm": "#fff",
"mail": "#ff0",
"music": "#f0f",
"phone": "#0f0",
"sms message": "#0ff",
// brands, according to https://www.schemecolor.com/?s (picking one for multicolored logos)
// all dithered on B2, but we only use the color for the icons. (Could maybe pick the closest 3-bit color for B2?)
"bibel": "#54342c",
"discord": "#738adb",
"facebook": "#4267b2",
"gmail": "#ea4335",
"google home": "#fbbc05",
"hangouts": "#1ba261",
"home assistant": "#fff", // ha-blue is #41bdf5, but that's the background
"instagram": "#dd2a7b",
"liferando": "#ee5c00",
"messenger": "#0078ff",
"nina": "#e57004",
"outlook mail": "#0072c6",
"post & dhl": "#f2c101",
"signal": "#00f",
"skype": "#00aff0",
"slack": "#e51670",
"snapchat": "#ff0",
"teams": "#464eb8",
"telegram": "#0088cc",
"telegram foss": "#0088cc",
"threema": "#000",
"to do": "#3999e5",
"twitch": "#6441A4",
"twitter": "#1da1f2",
"whatsapp": "#4fce5d",
"wordfeud": "#e7d3c7",
"youtube": "#f00",
}[(msg.src||"").toLowerCase()]||(def !== undefined?def:g.theme.fg);
};

View File

@ -1,7 +1,7 @@
{ {
"id": "messages", "id": "messages",
"name": "Messages", "name": "Messages",
"version": "0.37", "version": "0.38",
"description": "App to display notifications from iOS and Gadgetbridge/Android", "description": "App to display notifications from iOS and Gadgetbridge/Android",
"icon": "app.png", "icon": "app.png",
"type": "app", "type": "app",

View File

@ -1 +1,2 @@
0.01: Initial version 0.01: Initial version
0.02: Update for time_utils module

View File

@ -73,7 +73,7 @@ function showAlarm(alarm) {
const settings = require("sched").getSettings(); const settings = require("sched").getSettings();
let msg = ""; let msg = "";
msg += require("sched").formatTime(alarm.timer); if (alarm.timer) msg += require("time_utils").formatTime(alarm.timer);
if (alarm.msg) { if (alarm.msg) {
msg += "\n"+alarm.msg; msg += "\n"+alarm.msg;
} }
@ -86,7 +86,7 @@ function showAlarm(alarm) {
if (alarm.data.hm && alarm.data.hm == true) { if (alarm.data.hm && alarm.data.hm == true) {
//hard mode extends auto-snooze time //hard mode extends auto-snooze time
buzzCount = buzzCount * 2; buzzCount = buzzCount * 3;
startHM(); startHM();
} }

View File

@ -258,7 +258,7 @@ function editTimer(idx, a) {
a.last = 0; a.last = 0;
a.data.ot = a.timer; a.data.ot = a.timer;
a.appid = "multitimer"; a.appid = "multitimer";
a.js = "load('multitimer.alarm.js')"; a.js = "(require('Storage').read('multitimer.alarm.js') !== undefined) ? load('multitimer.alarm.js') : load('sched.js')";
if (idx < 0) alarms.push(a); if (idx < 0) alarms.push(a);
else alarms[timerIdx[idx]] = a; else alarms[timerIdx[idx]] = a;
require("sched").setAlarms(alarms); require("sched").setAlarms(alarms);
@ -585,7 +585,7 @@ function editAlarm(idx, a) {
var menu = { var menu = {
"": { "title": "Alarm" }, "": { "title": "Alarm" },
"< Back": () => { "< Back": () => {
if (a.data.hm == true) a.js = "load('multitimer.alarm.js')"; if (a.data.hm == true) a.js = "(require('Storage').read('multitimer.alarm.js') !== undefined) ? load('multitimer.alarm.js') : load('sched.js')";
if (a.data.hm == false && a.js) delete a.js; if (a.data.hm == false && a.js) delete a.js;
if (idx >= 0) alarms[alarmIdx[idx]] = a; if (idx >= 0) alarms[alarmIdx[idx]] = a;
else alarms.push(a); else alarms.push(a);

View File

@ -1,7 +1,7 @@
{ {
"id": "multitimer", "id": "multitimer",
"name": "Multi Timer", "name": "Multi Timer",
"version": "0.01", "version": "0.02",
"description": "Set timers and chronographs (stopwatches) and watch them count down in real time. Pause, create, edit, and delete timers and chronos, and add custom labels/messages. Also sets alarms.", "description": "Set timers and chronographs (stopwatches) and watch them count down in real time. Pause, create, edit, and delete timers and chronos, and add custom labels/messages. Also sets alarms.",
"icon": "app.png", "icon": "app.png",
"screenshots": [ "screenshots": [

View File

@ -4,3 +4,4 @@
0.04: Fixed issue selecting Frankfurt not saved 0.04: Fixed issue selecting Frankfurt not saved
0.05: Fixed issue with back option 0.05: Fixed issue with back option
0.06: renamed source files to match standard 0.06: renamed source files to match standard
0.07: Move mylocation app into 'Settings -> Apps'

View File

@ -2,6 +2,8 @@
*Sets and stores GPS lat and lon of your preferred city* *Sets and stores GPS lat and lon of your preferred city*
To access, go to `Settings -> Apps -> My Location`
* Select one of the preset Cities or setup through the GPS * Select one of the preset Cities or setup through the GPS
* Other Apps can read this information to do calculations based on location * Other Apps can read this information to do calculations based on location
* When the City shows ??? it means the location has been set through the GPS * When the City shows ??? it means the location has been set through the GPS

View File

@ -1 +0,0 @@
require("heatshrink").decompress(atob("mEw4UA///gH4AYPO/QPDgNVqtADY/1BYNfBQ0PBQIAB+ALFmoLDrgLF6oLDq4KEgYKDBYPABYcNBYlVuAuIGAwuEAANUBYYKFHgg6Bq4ZCr4DBHgQLBvWq2te1WlBYZGBBYOr1Wq1qSDBYNqBIILDKgQLLgoLHqBqDBfJHLBZBrOgKPCBYiPCU4NaBYe1WYrABBQLCCfgYGCrwVBa4kAirvKNgIAErgLDKgIAEKQQ8EAAY6DBZhIDIww8GHQg8GHQgwGFwowEFwx5EOog8GHQ0AlWpBYNq1AKFWIILBAYOgBYbICytWAgQKCgTgDcwYXGAAgvGAAY8EEgYWGBgoVEA=="))

View File

@ -2,16 +2,15 @@
"name": "My Location", "name": "My Location",
"shortName":"My Location", "shortName":"My Location",
"icon": "app.png", "icon": "app.png",
"type": "app", "type": "settings",
"screenshots": [{"url":"screenshot_1.png"}], "screenshots": [{"url":"screenshot_1.png"}],
"version":"0.06", "version":"0.07",
"description": "Sets and stores the lat and long of your preferred City or it can be set from the GPS. mylocation.json can be used by other apps that need your main location lat and lon. See README", "description": "Sets and stores the lat and long of your preferred City or it can be set from the GPS. mylocation.json can be used by other apps that need your main location lat and lon. See README",
"readme": "README.md", "readme": "README.md",
"tags": "tool,utility", "tags": "tool,utility",
"supports": ["BANGLEJS", "BANGLEJS2"], "supports": ["BANGLEJS", "BANGLEJS2"],
"storage": [ "storage": [
{"name":"mylocation.app.js","url":"app.js"}, {"name":"mylocation.settings.js","url":"settings.js"}
{"name":"mylocation.img","url":"icon.js","evaluate": true }
], ],
"data": [ "data": [
{"name":"mylocation.json"} {"name":"mylocation.json"}

View File

@ -1,5 +1,4 @@
Bangle.loadWidgets(); (function(back) {
Bangle.drawWidgets();
const SETTINGS_FILE = "mylocation.json"; const SETTINGS_FILE = "mylocation.json";
let settings; let settings;
@ -18,7 +17,7 @@ function loadSettings() {
} }
} }
function save() { function saveSettings() {
settings = s; settings = s;
require('Storage').write(SETTINGS_FILE, settings); require('Storage').write(SETTINGS_FILE, settings);
} }
@ -34,29 +33,29 @@ function setFromGPS() {
//console.log("fix from GPS"); //console.log("fix from GPS");
s = {'lat': gps.lat, 'lon': gps.lon, 'location': '???' }; s = {'lat': gps.lat, 'lon': gps.lon, 'location': '???' };
Bangle.buzz(1500); // buzz on first position Bangle.buzz(1500); // buzz on first position
Bangle.setGPSPower(0); Bangle.setGPSPower(0, "mylocation");
save(); saveSettings();
Bangle.setUI("updown", ()=>{ load(); }); Bangle.setUI("updown", ()=>{ load(); });
E.showPrompt("Location has been saved from the GPS fix",{ E.showPrompt(/*LANG*/"Location has been saved from the GPS fix",{
title:"Location Saved", title:/*LANG*/"Location Saved",
buttons : {"OK":1} buttons : {/*LANG*/"OK":1}
}).then(function(v) { }).then(function(v) {
load(); // load default clock load(); // load default clock
}); });
}); });
Bangle.setGPSPower(1); Bangle.setGPSPower(1, "mylocation");
E.showMessage("Waiting for GPS fix. Place watch in the open. Could take 10 minutes. Long press to abort", "GPS Running"); E.showMessage(/*LANG*/"Waiting for GPS fix. Place watch in the open. Could take 10 minutes. Long press to abort", "GPS Running");
Bangle.setUI("updown", undefined); Bangle.setUI("updown", undefined);
} }
function showMainMenu() { function showMainMenu() {
//console.log("showMainMenu"); //console.log("showMainMenu");
const mainmenu = { const mainmenu = {
'': { 'title': 'My Location' }, '': { 'title': /*LANG*/'My Location' },
'< Back': ()=>{ load(); }, '< Back': ()=>{ back(); },
'City': { /*LANG*/'City': {
value: 0 | locations.indexOf(s.location), value: 0 | locations.indexOf(s.location),
min: 0, max: locations.length - 1, min: 0, max: locations.length - 1,
format: v => locations[v], format: v => locations[v],
@ -65,14 +64,15 @@ function showMainMenu() {
s.location = locations[v]; s.location = locations[v];
s.lat = lats[v]; s.lat = lats[v];
s.lon = lons[v]; s.lon = lons[v];
save(); saveSettings();
} }
} }
}, },
'Set From GPS': ()=>{ setFromGPS(); } /*LANG*/'Set From GPS': ()=>{ setFromGPS(); }
}; };
return E.showMenu(mainmenu); return E.showMenu(mainmenu);
} }
loadSettings(); loadSettings();
showMainMenu(); showMainMenu();
})

View File

@ -1 +1,2 @@
0.01: New face :) 0.01: New face :)
0.02: Color image compressed

View File

@ -1,7 +1,17 @@
# About this Watchface # Poketch Clock
Based on the electronic device from the Pokemon game. A clock based on the Poketch electronic device found in Sinnoh
# Features ![](https://user-images.githubusercontent.com/44651387/157491789-1b608c11-8af2-4519-a90f-41b8a58a9a14.png)
## Features
Has a dark mode Has a dark mode
## Requests
If you have any issues or would like to suggest a feature, click here to send a message -> [here](https://github.com/elykittytee/BangleApps/issues/new?title=Poketch%20Clock%20Bug).
## Creator
Eleanor Tayam

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@
"id": "pokeclk", "id": "pokeclk",
"name": "Poketch Clock", "name": "Poketch Clock",
"shortName":"Poketch Clock", "shortName":"Poketch Clock",
"version": "0.01", "version": "0.02",
"description": "A clock based on the Poketch electronic device found in Sinnoh", "description": "A clock based on the Poketch electronic device found in Sinnoh",
"icon": "app.png", "icon": "app.png",
"type": "clock", "type": "clock",

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

1
apps/r2d2clk/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New App!

11
apps/r2d2clk/README.md Normal file
View File

@ -0,0 +1,11 @@
# R2D2 Clock
A clock with R2D2's shiny metal face on it. :)
![](screenshot.png)
## Creator
Made by [Noah Howard](https://github.com/nh-99)
Based on [Interlaced Clock](https://github.com/espruino/BangleApps/tree/master/apps/intclock)

1
apps/r2d2clk/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("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"))

67
apps/r2d2clk/app.js Normal file
View File

@ -0,0 +1,67 @@
Graphics.prototype.setFontUndo = function(scale) {
// Actual height 19 (20 - 2)
this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKqKAqooCqigKqKAAAACgAAKAAAoAACgAAAAAAAAACgAAKAAAoAACgAAAAAAKCgAoKAKqqAqqoCqqgKqqAKCgAoKAKqqAqqoCqqgKqqAKCgAoKAAAAAKgoAqCgKqKAqooKiioqKKiooqKiioKKqAoqoCgqAKCoAAAACoCgKgKAiCoCIKgKioAqKgACoAAKgACoAAKgACoqAKioCoIgKgiAoCoCgKgAAAAKqgAqqAKqqAqqoKiioqKKiooqKiioKAKAoAoCgCgKAKAAAACgAAKAAAoAACgAAAAAAKqgAqqAKqqAqqoCgCgKAKAAAACgCgKAKAqqoCqqgCqoAKqgAAAACigAKKAAqoACqgAqoACqgAKqAAqoAAqoACqgAKKAAooAAAAAAoAACgAAKAAAoAAqqACqoAKqgAqqAAKAAAoAACgAAKAAAAAAACoAAKgAAqAACoAAAAAoAACgAAKAAAoAACgAAKAAAoAACgAAKAAAoAACgAAKAAAAAAACgAAKAAAoAACgAAAAAAoAACgAAqAACoAAqAACoAAqAACoAAqAACoAAqAACoAAqAACoAAKAAAoAAAAAACqoAKqgCqqgKqqAoAoCgCgKAKAoAoCqqgKqqAKqgAqqAAAAAqqoCqqgKqqAqqoAAAAKCqAoKoCiqgKKqAoooCiigKKKAoooCqigKqKAKgoAqCgAAAAoAoCgCgKAKAoAoCiigKKKAoooCiigKqqAqqoAqqACqoAAAACqAAKoAAqoACqgAAKAAAoAACgAAKAAqqoCqqgKqqAqqoAAAAKoKAqgoCqigKqKAoooCiigKKKAoooCiqgKKqAoKgCgqAAAAAKqgAqqAKqqAqqoCiigKKKAoooCiigKKqAoqoCgqAKCoAAAACgAAKAAAoAACgAAKAAAoAACgAAKAAAqqoCqqgCqqAKqoAAAACqoAKqgCqqgKqqAoooCiigKKKAoooCqqgKqqAKqgAqqAAAAAKgAAqAAKqAAqoACigAKKAAooACigAKqqAqqoAqqgCqqAAAACgCgKAKAoAoCgCgAAAAoAqCgCoKAKgoAqAAAAAKAAAoAAKoAAqgAKqgAqqAKgqAqCoCgCgKAKAAAAAoKACgoAKCgAoKACgoAKCgAoKACgoAKCgAoKACgoAKCgAAAAKAKAoAoCoKgKgqAKqgAqqAAqgACqAACgAAKAAAAACgAAKAAAoAACgAAKKKAoooCiigKKKAqoACqgACoAAKgAAAAACqqAKqoCqqgKqqAoAACgAAKCoAoKgCiqgKKqAoooCiigKqqAqqoAqqACqoAAAAAqqgCqqAqqoCqqgKKAAooACigAKKAAqqoCqqgCqqAKqoAAAAKqqAqqoCqqgKqqAoooCiigKKKAoooCqqgKqqAKCgAoKAAAAAKqgAqqAKqqAqqoCgCgKAKAoAoCgCgKAKAoAoCgCgKAKAAAACqqgKqqAqqoCqqgKAKAoAoCoKgKgqAKqgAqqAAqgACqAAAAACqoAKqgCqqgKqqAoooCiigKKKAoooCgCgKAKAoAoCgCgAAAAKqoAqqgKqqAqqoCigAKKAAooACigAKAAAoAACgAAKAAAAAAAqqACqoAqqoCqqgKAKAoAoCiigKKKAoqoCiqgKKqAoqoAAAAKqqAqqoCqqgKqqAAoAACgAAKAAAoACqqgKqqAqqoCqqgAAAAoAoCgCgKAKAoAoCqqgKqqAqqoCqqgKAKAoAoCgCgKAKAAAAAAKAAAoAACoAAKgAAKAAAoAACgAAKAqqoCqqgKqoAqqgAAAAKqqAqqoCqqgKqqACqAAKoACqoAKqgCoKgKgqAoAoCgCgAAAAqqgCqqAKqqAqqoAACgAAKAAAoAACgAAKAAAoAACgAAKAAAACqqgKqqAqqoCqqgCoAAKgAAKgAAqAAKgAAqAAKqqAqqoCqqgKqqAAAACqqgKqqAqqoCqqgCoAAKgAAKgAAqAAqqoCqqgKqqAqqoAAAACqoAKqgCqqgKqqAoAoCgCgKAKAoAoCqqgKqqAKqgAqqAAAAAqqoCqqgKqqAqqoCigAKKAAooACigAKqAAqoAAqAACoAAAAAAqqACqoAqqoCqqgKAKAoAoCgKgKAqAqqoCqqgCqqAKqoAAAAKqqAqqoCqqgKqqAooACigAKKgAoqACqqgKqqAKioAqKgAAAAKgoAqCgKqKAqooCiigKKKAoooCiigKKqAoqoCgqAKCoAAAACgAAKAAAoAACgAAKqqAqqoCqqgKqqAoAACgAAKAAAoAAAAAAKqoAqqgCqqgKqqAAAoAACgAAKAAAoCqqgKqqAqqgCqqAAAAAqqACqoAKqoAqqgAAKgAAqAACoAAKgKqoAqqgCqoAKqgAAAACqqgKqqAqqoCqqgACoAAKgACoAAKgAAKgAAqAKqqAqqoCqqgKqqAAAACoKgKgqAqqoCqqgAqgACqAAKoAAqgAqqoCqqgKgqAqCoAAAAKoAAqgACqgAKqAAAqoACqgAKqAAqoCqgAKqAAqgACqAAAAAAoCoCgKgKCqAoKoCiqgKKqAqooCqigKoKAqgoCoCgKgKAAAACqqgKqqAqqoCqqgKAKAoAoAAAAKAAAoAACoAAKgAAKgAAqAAAqAACoAACoAAKgAAKgAAqAAAqAACoAACgAAKAAAACgCgKAKAqqoCqqgKqqAqqoAAA"), 32, atob("DQULDw0RDQUHBw0NBQ0FEQ0FDQ0NDQ0NDQ0FBQsNCw0RDQ0NDQ0NDQ0NDQ0NDw0NDQ0NDQ0NDQ8NDQ0HEQc="), 22+(scale<<8)+(1<<16));
return this;
};
var IMAGEWIDTH = 117;
var IMAGEHEIGHT = 60;
var r2d2 = require("heatshrink").decompress(atob("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"));
// timeout used to update every minute
var drawTimeout;
// schedule a draw for the next minute
function queueDraw() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
}
function draw() {
//reset().clearRect(0,24,g.getWidth(),g.getHeight()-IMAGEHEIGHT);
// draw r2d2
g.drawImage(r2d2, (g.getWidth()/2)-(IMAGEWIDTH/2), g.getHeight()-IMAGEHEIGHT);
var x = g.getWidth()/2;
var y = g.getHeight()/2 - 30;
g.reset();
// work out locale-friendly date/time
var date = new Date();
var timeStr = require("locale").time(date,1).trim();
var dateStr = require("locale").date(date).toUpperCase();
// draw time
g.setFontAlign(0,0).setFont("Undo:3");
g.clearRect(0,y-30,g.getWidth(),y+30); // clear the background
g.drawString(timeStr,x,y);
// draw date
y += 40;
g.setFontAlign(0,0).setFont("Undo");
g.clearRect(0,y-10,g.getWidth(),y+20); // clear the background
g.drawString(dateStr,x,y);
// queue draw in one minute
queueDraw();
}
// Clear the screen once, at startup
g.clear();
// draw immediately at first, queue update
draw();
// Stop updates when LCD is off, restart when on
Bangle.on('lcdPower',on=>{
if (on) {
draw(); // draw immediately, queue redraw
} else { // stop draw timer
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
}
});
// Show launcher when middle button pressed
Bangle.setUI("clock");
// Load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();

BIN
apps/r2d2clk/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,16 @@
{ "id": "r2d2clk",
"name": "R2D2 Clock",
"shortName":"R2D2 Clock",
"version":"0.01",
"description": "A clock with R2D2's shiny metal face on it. :)",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"r2d2clk.app.js","url":"app.js"},
{"name":"r2d2clk.img","url":"app-icon.js","evaluate":true}
]
}

BIN
apps/r2d2clk/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -4,4 +4,5 @@
0.04: Fixed icon and png to 48x48 pixels 0.04: Fixed icon and png to 48x48 pixels
0.05: added charging icon 0.05: added charging icon
0.06: Add 12h support and autocycle control 0.06: Add 12h support and autocycle control
0.07: added localization; removed deprecated code 0.07: added localization, removed deprecated code
0.08: removed unused font, fix autocycle, imported suncalc and trimmed, removed pedometer dependency, "tap to cycle" setting

BIN
apps/rebble/KdamThmor.ttf Normal file

Binary file not shown.

View File

@ -2,11 +2,11 @@
"id": "rebble", "id": "rebble",
"name": "Rebble Clock", "name": "Rebble Clock",
"shortName": "Rebble", "shortName": "Rebble",
"version": "0.07", "version": "0.08",
"description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion", "description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion",
"readme": "README.md", "readme": "README.md",
"icon": "rebble.png", "icon": "rebble.png",
"dependencies": {"mylocation":"app", "widpedom":"app"}, "dependencies": {"mylocation":"app"},
"screenshots": [{"url":"screenshot_rebble.png"}], "screenshots": [{"url":"screenshot_rebble.png"}],
"type": "clock", "type": "clock",
"tags": "clock", "tags": "clock",
@ -14,6 +14,7 @@
"storage": [ "storage": [
{"name":"rebble.app.js","url":"rebble.app.js"}, {"name":"rebble.app.js","url":"rebble.app.js"},
{"name":"rebble.settings.js","url":"rebble.settings.js"}, {"name":"rebble.settings.js","url":"rebble.settings.js"},
{"name":"rebble.img","url":"rebble.icon.js","evaluate":true} {"name":"rebble.img","url":"rebble.icon.js","evaluate":true},
{"name":"suncalc","url":"suncalc.js"}
] ]
} }

File diff suppressed because one or more lines are too long

View File

@ -2,12 +2,14 @@
const SETTINGS_FILE = "rebble.json"; const SETTINGS_FILE = "rebble.json";
// initialize with default settings... // initialize with default settings...
let localSettings = {'bg': '#0f0', 'color': 'Green', 'autoCycle': true} let localSettings = {'bg': '#0f0', 'color': 'Green', 'autoCycle': true, 'sideTap':0};
//sideTap 0 = on| 1= sideBar1 | 2 = ...
// ...and overwrite them with any saved values // ...and overwrite them with any saved values
// This way saved values are preserved if a new version adds more settings // This way saved values are preserved if a new version adds more settings
const storage = require('Storage') const storage = require('Storage')
let settings = storage.readJSON(SETTINGS_FILE, 1) || localSettings; let settings = storage.readJSON(SETTINGS_FILE, 1) || localSettings;
const saved = settings || {} const saved = settings || {}
for (const key in saved) { for (const key in saved) {
localSettings[key] = saved[key] localSettings[key] = saved[key]
@ -21,26 +23,55 @@
var color_options = ['Green','Orange','Cyan','Purple','Red','Blue']; var color_options = ['Green','Orange','Cyan','Purple','Red','Blue'];
var bg_code = ['#0f0','#ff0','#0ff','#f0f','#f00','#00f']; var bg_code = ['#0f0','#ff0','#0ff','#f0f','#f00','#00f'];
E.showMenu({ function showMenu()
'': { 'title': 'Rebble Clock' }, {
'< Back': back, const menu={
'Colour': { '': { 'title': 'Rebble Clock' },
value: 0 | color_options.indexOf(localSettings.color), '< Back': back,
min: 0, max: 5, 'Colour': {
format: v => color_options[v], value: 0 | color_options.indexOf(localSettings.color),
onchange: v => { min: 0, max: 5,
localSettings.color = color_options[v]; format: v => color_options[v],
localSettings.bg = bg_code[v]; onchange: v => {
save(); localSettings.color = color_options[v];
localSettings.bg = bg_code[v];
save();
},
}, },
}, 'Auto Cycle': {
'Auto Cycle': { value: localSettings.autoCycle,
value: "autoCycle" in localSettings ? localSettings.autoCycle : true, onchange: (v) => {
format: () => (localSettings.autoCycle ? 'Yes' : 'No'), localSettings.autoCycle = v;
onchange: () => { save();
localSettings.autoCycle = !localSettings.autoCycle; showMenu();
save(); }
} }
};
if( !localSettings.autoCycle)
{
menu['Tap to Cycle']= {
value: localSettings.sideTap,
min: 0,
max: 3,
step: 1,
format: v => NumberToSideTap(v),
onchange: v => {
localSettings.sideTap=v
save();
setTimeout(showMenu, 10);
}
};
} }
}); E.showMenu(menu);
}
function NumberToSideTap(Number)
{
if(Number==0)
return 'on';
return Number+"";
}
showMenu();
}) })

143
apps/rebble/suncalc.js Normal file
View File

@ -0,0 +1,143 @@
/*
(c) 2011-2015, Vladimir Agafonkin
SunCalc is a JavaScript library for calculating sun/moon position and light phases.
https://github.com/mourner/suncalc
edit for banglejs
*/
(function () { 'use strict';
// shortcuts for easier to read formulas
var PI = Math.PI,
sin = Math.sin,
cos = Math.cos,
tan = Math.tan,
asin = Math.asin,
atan = Math.atan2,
acos = Math.acos,
rad = PI / 180;
// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas
// date/time constants and conversions
var dayMs = 1000 * 60 * 60 * 24,
J1970 = 2440588,
J2000 = 2451545;
function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); }
function toDays(date) { return toJulian(date) - J2000; }
// general calculations for position
var e = rad * 23.4397; // obliquity of the Earth
function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }
// general sun calculations
function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
function eclipticLongitude(M) {
var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
P = rad * 102.9372; // perihelion of the Earth
return M + C + P + PI;
}
var SunCalc = {};
// sun times configuration (angle, morning name, evening name)
var times = SunCalc.times = [
[-0.833, 'sunrise', 'sunset' ],
[ -0.3, 'sunriseEnd', 'sunsetStart' ],
[ -6, 'dawn', 'dusk' ],
[ -12, 'nauticalDawn', 'nauticalDusk'],
[ -18, 'nightEnd', 'night' ],
[ 6, 'goldenHourEnd', 'goldenHour' ]
];
// calculations for sun times
var J0 = 0.0009;
function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); }
function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; }
function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); }
function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); }
function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; }
// returns set time for the given sun altitude
function getSetJ(h, lw, phi, dec, n, M, L) {
var w = hourAngle(h, phi, dec),
a = approxTransit(w, lw, n);
return solarTransitJ(a, M, L);
}
// calculates sun times for a given date, latitude/longitude, and, optionally,
// the observer height (in meters) relative to the horizon
SunCalc.getTimes = function (date, lat, lng, height) {
height = height || 0;
var lw = rad * -lng,
phi = rad * lat,
dh = observerAngle(height),
d = toDays(date),
n = julianCycle(d, lw),
ds = approxTransit(0, lw, n),
M = solarMeanAnomaly(ds),
L = eclipticLongitude(M),
dec = declination(L, 0),
Jnoon = solarTransitJ(ds, M, L),
i, len, time, h0, Jset, Jrise;
var result = {
solarNoon: fromJulian(Jnoon),
nadir: fromJulian(Jnoon - 0.5)
};
for (i = 0, len = times.length; i < len; i += 1) {
time = times[i];
h0 = (time[0] + dh) * rad;
Jset = getSetJ(h0, lw, phi, dec, n, M, L);
Jrise = Jnoon - (Jset - Jnoon);
result[time[1]] = fromJulian(Jrise);
result[time[2]] = fromJulian(Jset);
}
return result;
};
// export as Node module / AMD module / browser variable
if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc;
else if (typeof define === 'function' && define.amd) define(SunCalc);
else window.SunCalc = SunCalc;
}());

View File

@ -6,7 +6,7 @@
"icon": "rndmclk.png", "icon": "rndmclk.png",
"type": "widget", "type": "widget",
"tags": "widget,clock", "tags": "widget,clock",
"supports": ["BANGLEJS"], "supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md", "readme": "README.md",
"storage": [ "storage": [
{"name":"rndmclk.wid.js","url":"widget.js"} {"name":"rndmclk.wid.js","url":"widget.js"}

View File

@ -12,3 +12,4 @@
0.11: Notifications fixes 0.11: Notifications fixes
0.12: Fix for recorder not stopping at end of run. Bug introduced in 0.11 0.12: Fix for recorder not stopping at end of run. Bug introduced in 0.11
0.13: Revert #1578 (stop duplicate entries) as with 2v12 menus it causes other boxes to be wiped (fix #1643) 0.13: Revert #1578 (stop duplicate entries) as with 2v12 menus it causes other boxes to be wiped (fix #1643)
0.14: Fix Bangle.js 1 issue where after the 'overwrite track' menu, the start/stop button stopped working

View File

@ -55,6 +55,7 @@ function onStartStop() {
prepPromises.push( prepPromises.push(
WIDGETS["recorder"].setRecording(true).then(() => { WIDGETS["recorder"].setRecording(true).then(() => {
isMenuDisplayed = false; isMenuDisplayed = false;
layout.setUI(); // grab our input handling again
layout.forgetLazyState(); layout.forgetLazyState();
layout.render(); layout.render();
}) })

View File

@ -1,6 +1,6 @@
{ "id": "run", { "id": "run",
"name": "Run", "name": "Run",
"version":"0.13", "version":"0.14",
"description": "Displays distance, time, steps, cadence, pace and more for runners.", "description": "Displays distance, time, steps, cadence, pace and more for runners.",
"icon": "app.png", "icon": "app.png",
"tags": "run,running,fitness,outdoors,gps", "tags": "run,running,fitness,outdoors,gps",

View File

@ -6,6 +6,7 @@
0.06: Refactor some methods to library 0.06: Refactor some methods to library
0.07: Update settings 0.07: Update settings
Correct `decodeTime(t)` to return a more likely expected time Correct `decodeTime(t)` to return a more likely expected time
0.08: add day of week check to getActiveAlarms() 0.08: Add day of week check to getActiveAlarms()
0.09: Move some functions to new time_utils module 0.09: Move some functions to new time_utils module
0.10: Default to sched.js if custom js not found 0.10: Default to sched.js if custom js not found
0.11: Fix default dow

View File

@ -8,12 +8,12 @@
var time = new Date(); var time = new Date();
var currentTime = (time.getHours()*3600000)+(time.getMinutes()*60000)+(time.getSeconds()*1000); var currentTime = (time.getHours()*3600000)+(time.getMinutes()*60000)+(time.getSeconds()*1000);
var d = time.getDate(); var d = time.getDate();
var active = alarms.filter( var active = alarms.filter(a =>
a=>a.on && // enabled a.on // enabled
a.last!=d && // not already fired today && (a.last != d) // not already fired today
a.t+60000>currentTime && // is not in the past by >1 minute && (a.t + 60000 > currentTime) // is not in the past by >1 minute
(a.dow>>time.getDay())&1 && // is allowed on this day of the week && (a.dow >> time.getDay() & 1) // is allowed on this day of the week
(!a.date || a.date==time.toISOString().substr(0,10)) // is allowed on this date && (!a.date || a.date == time.toISOString().substr(0, 10)) // is allowed on this date
); );
if (active.length) { if (active.length) {
active = active.sort((a,b)=>a.t-b.t); // sort by time active = active.sort((a,b)=>a.t-b.t); // sort by time
@ -24,7 +24,7 @@
will then clearInterval() to get rid of this call so it can proceed will then clearInterval() to get rid of this call so it can proceed
normally. normally.
If active[0].js is defined, just run that code as-is and not alarm.js */ If active[0].js is defined, just run that code as-is and not alarm.js */
Bangle.SCHED = setTimeout(require("Storage").read(active[0].js)!==undefined ? active[0].js : 'load("sched.js")',t); Bangle.SCHED = setTimeout(active[0].js||'load("sched.js")',t);
} else { // check for new alarms at midnight (so day of week works) } else { // check for new alarms at midnight (so day of week works)
Bangle.SCHED = setTimeout('eval(require("Storage").read("sched.boot.js"))', 86400000 - (Date.now()%86400000)); Bangle.SCHED = setTimeout('eval(require("Storage").read("sched.boot.js"))', 86400000 - (Date.now()%86400000));
} }

View File

@ -11,16 +11,19 @@ exports.getAlarm = function(id) {
return exports.getAlarms().find(a=>a.id==id); return exports.getAlarms().find(a=>a.id==id);
}; };
// Given a list of alarms from getAlarms, return a list of active alarms for the given time (or current time if time not specified) // Given a list of alarms from getAlarms, return a list of active alarms for the given time (or current time if time not specified)
exports.getActiveAlarms = function(alarms, time) { exports.getActiveAlarms = function (alarms, time) {
if (!time) time = new Date(); if (!time) time = new Date();
var currentTime = (time.getHours()*3600000)+(time.getMinutes()*60000)+(time.getSeconds()*1000) // get current time 10s in future to ensure we alarm if we've started the app a tad early
+10000;// get current time - 10s in future to ensure we alarm if we've started the app a tad early var currentTime = (time.getHours() * 3600000) + (time.getMinutes() * 60000) + (time.getSeconds() * 1000) + 10000;
return alarms.filter(a=>a.on return alarms
&&(a.t<currentTime) .filter(a =>
&&(a.last!=time.getDate()) a.on // enabled
&& (!a.date || a.date==time.toISOString().substr(0,10)) && (a.last != time.getDate()) // not already fired today
&& a.dow >> (time).getDay() & 1) && (a.t < currentTime)
.sort((a,b)=>a.t-b.t); && (a.dow >> time.getDay() & 1) // is allowed on this day of the week
&& (!a.date || a.date == time.toISOString().substr(0, 10)) // is allowed on this date
)
.sort((a, b) => a.t - b.t);
} }
// Set an alarm object based on ID. Leave 'alarm' undefined to remove it // Set an alarm object based on ID. Leave 'alarm' undefined to remove it
exports.setAlarm = function(id, alarm) { exports.setAlarm = function(id, alarm) {
@ -66,7 +69,7 @@ exports.newDefaultAlarm = function () {
on: true, on: true,
rp: settings.defaultRepeat, rp: settings.defaultRepeat,
as: settings.defaultAutoSnooze, as: settings.defaultAutoSnooze,
dow: settings.defaultRepeat ? 0b1111111 : 0b0000000, dow: 0b1111111,
last: 0, last: 0,
vibrate: settings.defaultAlarmPattern, vibrate: settings.defaultAlarmPattern,
}; };

View File

@ -1,7 +1,7 @@
{ {
"id": "sched", "id": "sched",
"name": "Scheduler", "name": "Scheduler",
"version": "0.10", "version": "0.11",
"description": "Scheduling library for alarms and timers", "description": "Scheduling library for alarms and timers",
"icon": "app.png", "icon": "app.png",
"type": "scheduler", "type": "scheduler",

View File

@ -48,3 +48,5 @@
0.43: Add some Bangle 1 colours to theme customizer 0.43: Add some Bangle 1 colours to theme customizer
0.44: Add "Start Week On X" option (#1780) 0.44: Add "Start Week On X" option (#1780)
UI improvements to Locale and Date & Time menu UI improvements to Locale and Date & Time menu
0.45: Add calibrate battery option
0.46: Fix regression after making 'calibrate battery' only for Bangle.js 2

View File

@ -13,7 +13,6 @@ This is Bangle.js's settings menu
* **LCD** Configure settings about the screen. How long it stays on, how bright it is, and when it turns on - see below. * **LCD** Configure settings about the screen. How long it stays on, how bright it is, and when it turns on - see below.
* **Theme** Adjust the colour scheme * **Theme** Adjust the colour scheme
* **Utils** Utilities - including resetting settings (see below) * **Utils** Utilities - including resetting settings (see below)
* **Turn Off** Turn Bangle.js off
## BLE - Bluetooth Settings ## BLE - Bluetooth Settings
@ -61,5 +60,7 @@ The exact effects depend on the app. In general the watch will not wake up by i
* **Compact Storage** Removes deleted/old files from Storage - this will speed up your Bangle.js * **Compact Storage** Removes deleted/old files from Storage - this will speed up your Bangle.js
* **Rewrite Settings** Should not normally be required, but if `.boot0` has been deleted/corrupted (and so no settings are being loaded) this will fix it. * **Rewrite Settings** Should not normally be required, but if `.boot0` has been deleted/corrupted (and so no settings are being loaded) this will fix it.
* **Flatten Battery** Turns on all devices and draws as much power as possible, attempting to flatten the Bangle.js battery. This can still take 5+ hours. * **Flatten Battery** Turns on all devices and draws as much power as possible, attempting to flatten the Bangle.js battery. This can still take 5+ hours.
* **Calibrate Battery** If you're finding your battery percentage meter isn't accurate, leave your Bangle.js on charge for at least 3 hours, and then choose this menu option. It will measure the battery voltage when full and will allow Bangle.js to report a more accurate battery percentage.
* **Reset Settings** Reset the settings (as set in this app) to defaults. Does not reset settings for other apps. * **Reset Settings** Reset the settings (as set in this app) to defaults. Does not reset settings for other apps.
* **Factory Reset** (not available on Bangle.js 1) - wipe **everything** and return to a factory state * **Factory Reset** (not available on Bangle.js 1) - wipe **everything** and return to a factory state
* **Turn Off** Turn Bangle.js off

View File

@ -1,7 +1,7 @@
{ {
"id": "setting", "id": "setting",
"name": "Settings", "name": "Settings",
"version": "0.44", "version": "0.46",
"description": "A menu for setting up Bangle.js", "description": "A menu for setting up Bangle.js",
"icon": "settings.png", "icon": "settings.png",
"tags": "tool,system", "tags": "tool,system",

View File

@ -545,8 +545,22 @@ function showUtilMenu() {
setInterval(function() { setInterval(function() {
var i=1000;while (i--); var i=1000;while (i--);
}, 1); }, 1);
}, }
/*LANG*/'Reset Settings': () => { };
if (BANGLEJS2)
menu[/*LANG*/'Calibrate Battery'] = () => {
E.showPrompt(/*LANG*/"Is the battery fully charged?",{title:/*LANG*/"Calibrate"}).then(ok => {
if (ok) {
var s=require("Storage").readJSON("setting.json");
s.batFullVoltage = (analogRead(D3)+analogRead(D3)+analogRead(D3)+analogRead(D3))/4;
require("Storage").writeJSON("setting.json",s);
E.showAlert(/*LANG*/"Calibrated!").then(() => load("settings.app.js"));
} else {
E.showAlert(/*LANG*/"Please charge Bangle.js for 3 hours and try again").then(() => load("settings.app.js"));
}
});
};
menu[/*LANG*/'Reset Settings'] = () => {
E.showPrompt(/*LANG*/'Reset to Defaults?',{title:/*LANG*/"Settings"}).then((v) => { E.showPrompt(/*LANG*/'Reset to Defaults?',{title:/*LANG*/"Settings"}).then((v) => {
if (v) { if (v) {
E.showMessage('Resetting'); E.showMessage('Resetting');
@ -554,9 +568,9 @@ function showUtilMenu() {
setTimeout(showMainMenu, 50); setTimeout(showMainMenu, 50);
} else showUtilMenu(); } else showUtilMenu();
}); });
}, };
/*LANG*/'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() } menu[/*LANG*/'Turn Off'] = ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() };
};
if (Bangle.factoryReset) { if (Bangle.factoryReset) {
menu[/*LANG*/'Factory Reset'] = ()=>{ menu[/*LANG*/'Factory Reset'] = ()=>{
E.showPrompt(/*LANG*/'This will remove everything!',{title:/*LANG*/"Factory Reset"}).then((v) => { E.showPrompt(/*LANG*/'This will remove everything!',{title:/*LANG*/"Factory Reset"}).then((v) => {

View File

@ -13,3 +13,4 @@
1.14: Add VMG and coordinates screens 1.14: Add VMG and coordinates screens
1.43: Adds mirroring of the watch face to an Android device. See README.md 1.43: Adds mirroring of the watch face to an Android device. See README.md
1.49: Droidscript mirroring prog automatically uses last connection address. Auto connects when run. 1.49: Droidscript mirroring prog automatically uses last connection address. Auto connects when run.
1.50: Add configuration item Wpt File Suffix. A one character suffix to append to the waypoints.json file. A number of other apps also use this file name. Using the file name suffix allows the speedalt2 waypoints to be retained if one of these other apps is installed for a different use.

View File

@ -78,6 +78,10 @@ Waypoints are used in Distance and VMG modes. Create a file waypoints.json and w
The [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app in the App Loader has a really nice waypoints file editor. (Must be connected to your Bangle.JS and then click on the Download icon.) The [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app in the App Loader has a really nice waypoints file editor. (Must be connected to your Bangle.JS and then click on the Download icon.)
By default the waypoints file is called waypoints.json
**Note** : The waypoints.json file is used by a number of different gps apps. The setting 'Wpt File Suffix' allows one of waypoints1.json, waypoints2.json or waypoints3.json to be used instead. This allows the other apps to be used with a different set of waypoints without losing the speedalt2 waypoint set.
Sample waypoints.json (My sailing waypoints) Sample waypoints.json (My sailing waypoints)
<pre> <pre>

View File

@ -5,8 +5,9 @@ Mike Bennett mike[at]kereru.com
1.14 : Add VMG screen 1.14 : Add VMG screen
1.34 : Add bluetooth data stream for Droidscript 1.34 : Add bluetooth data stream for Droidscript
1.43 : Keep GPS in SuperE mode while using Droiscript screen mirroring 1.43 : Keep GPS in SuperE mode while using Droiscript screen mirroring
1.50 : Add cfg.wptSfx one char suffix to append to waypoints.json filename. Protects speedalt2 waypoints from other apps that use the same file name for waypoints.
*/ */
var v = '1.49'; var v = '1.50';
var vDroid = '1.50'; // Required DroidScript program version var vDroid = '1.50'; // Required DroidScript program version
/*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */
@ -209,7 +210,7 @@ function nxtWp(){
} }
function loadWp() { function loadWp() {
var w = require("Storage").readJSON('waypoints.json')||[{name:"NONE"}]; var w = require("Storage").readJSON('waypoints'+cfg.wptSfx+'.json')||[{name:"NONE"}];
if (cfg.wp>=w.length) cfg.wp=0; if (cfg.wp>=w.length) cfg.wp=0;
if (cfg.wp<0) cfg.wp = w.length-1; if (cfg.wp<0) cfg.wp = w.length-1;
savSettings(); savSettings();
@ -718,6 +719,7 @@ cfg.primSpd = cfg.primSpd||0; // 1 = Spd in primary, 0 = Spd in secondary
cfg.spdFilt = cfg.spdFilt==undefined?true:cfg.spdFilt; cfg.spdFilt = cfg.spdFilt==undefined?true:cfg.spdFilt;
cfg.altFilt = cfg.altFilt==undefined?true:cfg.altFilt; cfg.altFilt = cfg.altFilt==undefined?true:cfg.altFilt;
cfg.touch = cfg.touch==undefined?true:cfg.touch; cfg.touch = cfg.touch==undefined?true:cfg.touch;
cfg.wptSfx = cfg.wptSfx==undefined?'':cfg.wptSfx;
if ( cfg.spdFilt ) var spdFilter = new KalmanFilter({R: 0.1 , Q: 1 }); if ( cfg.spdFilt ) var spdFilter = new KalmanFilter({R: 0.1 , Q: 1 });
if ( cfg.altFilt ) var altFilter = new KalmanFilter({R: 0.01, Q: 2 }); if ( cfg.altFilt ) var altFilter = new KalmanFilter({R: 0.01, Q: 2 });

View File

@ -2,7 +2,7 @@
"id": "speedalt2", "id": "speedalt2",
"name": "GPS Adventure Sports II", "name": "GPS Adventure Sports II",
"shortName":"GPS Adv Sport II", "shortName":"GPS Adv Sport II",
"version":"1.49", "version":"1.50",
"description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.",
"icon": "app.png", "icon": "app.png",
"type": "app", "type": "app",
@ -15,5 +15,11 @@
{"name":"speedalt2.img","url":"app-icon.js","evaluate":true}, {"name":"speedalt2.img","url":"app-icon.js","evaluate":true},
{"name":"speedalt2.settings.js","url":"settings.js"} {"name":"speedalt2.settings.js","url":"settings.js"}
], ],
"data": [{"name":"speedalt2.json"}] "data": [
{"name":"speedalt2.json"},
{"name":"waypoints.json"},
{"name":"waypoints1.json"},
{"name":"waypoints2.json"},
{"name":"waypoints3.json"}
]
} }

View File

@ -30,6 +30,11 @@
writeSettings(); writeSettings();
} }
function setSfx(s) {
settings.wptSfx = s;
writeSettings();
}
const appMenu = { const appMenu = {
'': {'title': 'GPS Adv Sprt II'}, '': {'title': 'GPS Adv Sprt II'},
@ -38,6 +43,7 @@
'Units' : function() { E.showMenu(unitsMenu); }, 'Units' : function() { E.showMenu(unitsMenu); },
'Colours' : function() { E.showMenu(colMenu); }, 'Colours' : function() { E.showMenu(colMenu); },
'Kalman Filter' : function() { E.showMenu(kalMenu); }, 'Kalman Filter' : function() { E.showMenu(kalMenu); },
'Wpt File Suffix' : function() { E.showMenu(sfxMenu); },
'Touch' : { 'Touch' : {
value : settings.touch, value : settings.touch,
format : v => v?"On":"Off", format : v => v?"On":"Off",
@ -69,6 +75,15 @@
'Inverted' : function() { setColour(3); } 'Inverted' : function() { setColour(3); }
}; };
const sfxMenu = {
'': {'title': 'Wpt File Suffix'},
'< Back': function() { E.showMenu(appMenu); },
'Default' : function() { setSfx(''); },
'1' : function() { setSfx('1'); },
'2' : function() { setSfx('2'); },
'3' : function() { setSfx('3'); }
};
const kalMenu = { const kalMenu = {
'': {'title': 'Kalman Filter'}, '': {'title': 'Kalman Filter'},
'< Back': function() { E.showMenu(appMenu); }, '< Back': function() { E.showMenu(appMenu); },

View File

@ -3,3 +3,4 @@
0.03: Add Banglejs 1 compatibility 0.03: Add Banglejs 1 compatibility
0.04: Fix settings bug 0.04: Fix settings bug
0.05: Add altitude display (only Bangle.js 2) 0.05: Add altitude display (only Bangle.js 2)
0.06: Add power related settings to control the HR and pressure(altitude) sensor from the watchface

View File

@ -8,3 +8,8 @@ It can display :
- hrm - hrm
- motion - motion
- steps - steps
"Power saving" setting control the HR and pressure (altitude) sensors.
If "Off" they will always be on.
If "On" the sensors will be turned on every "Power on interval" minutes for 45 secondes

View File

@ -3,15 +3,14 @@ var fontColor = g.theme.dark ? "#0f0" : "#000";
var heartRate = 0; var heartRate = 0;
var altitude = -9001; var altitude = -9001;
// handling the differents versions of the Banglejs smartwatch // handling the differents versions of the Banglejs smartwatch screen sizes
if (process.env.HWVERSION == 1){ if (process.env.HWVERSION == 1){
var paddingY = 3; var paddingY = 3;
var font6x8At4Size = 48; var font6x8At4Size = 48;
var font6x8At2Size = 27; var font6x8At2Size = 27;
var font6x8FirstTextSize = 6; var font6x8FirstTextSize = 6;
var font6x8DefaultTextSize = 3; var font6x8DefaultTextSize = 3;
} } else{
else{
var paddingY = 2; var paddingY = 2;
var font6x8At4Size = 32; var font6x8At4Size = 32;
var font6x8At2Size = 18; var font6x8At2Size = 18;
@ -66,7 +65,7 @@ function drawDate(now, pos){
drawLine(locale_date, pos); drawLine(locale_date, pos);
} }
function drawInput(now, pos){ function drawInput(pos){
clearField(pos); clearField(pos);
drawLine(">", pos); drawLine(">", pos);
} }
@ -129,16 +128,52 @@ function draw(){
drawStepCount(curPos); drawStepCount(curPos);
curPos++; curPos++;
} }
drawInput(now, curPos); drawInput(curPos);
} }
function turnOnServices(){
if(settings.showHRM){
Bangle.setHRMPower(true, "terminalclock");
}
if(settings.showAltitude && process.env.HWVERSION != 1){
Bangle.setBarometerPower(true, "terminalclock");
}
if(settings.powerSaving){
setTimeout(function () {
turnOffServices();
}, 45000);
}
}
function turnOffServices(){
if(settings.showHRM){
Bangle.setHRMPower(false, "terminalclock");
}
if(settings.showAltitude && process.env.HWVERSION != 1){
Bangle.setBarometerPower(false, "terminalclock");
}
}
var unlockDrawIntervalID = -1;
Bangle.on('lock', function(on){
if(!on){ // unclock
if(settings.powerSaving){
turnOnServices();
}
unlockDrawIntervalID = setInterval(draw, 1000); // every second
}
if(on && unlockDrawIntervalID != -1){ // lock
clearInterval(unlockDrawIntervalID);
}
});
Bangle.on('HRM',function(hrmInfo) { Bangle.on('HRM',function(hrmInfo) {
if(hrmInfo.confidence >= settings.HRMinConfidence) if(hrmInfo.confidence >= settings.HRMinConfidence)
heartRate = hrmInfo.bpm; heartRate = hrmInfo.bpm;
}); });
var MEDIANLENGTH = 20; var MEDIANLENGTH = 20; // technical
var avr = [], median; var avr = [], median; // technical
Bangle.on('pressure', function(e) { Bangle.on('pressure', function(e) {
while (avr.length>MEDIANLENGTH) avr.pop(); while (avr.length>MEDIANLENGTH) avr.pop();
avr.unshift(e.altitude); avr.unshift(e.altitude);
@ -161,18 +196,20 @@ var settings = Object.assign({
showActivity: true, showActivity: true,
showStepCount: true, showStepCount: true,
showAltitude: process.env.HWVERSION != 1 ? true : false, showAltitude: process.env.HWVERSION != 1 ? true : false,
powerSaving: true,
PowerOnInterval: 15,
}, require('Storage').readJSON("terminalclock.json", true) || {}); }, require('Storage').readJSON("terminalclock.json", true) || {});
if(settings.showAltitude && process.env.HWVERSION != 1){ // turn the services before drawing anything
Bangle.setBarometerPower(true, "app"); turnOnServices();
if(settings.powerSaving){
setInterval(turnOnServices, settings.PowerOnInterval*60000); // every PowerOnInterval min
} }
// Show launcher when middle button pressed // Show launcher when middle button pressed
Bangle.setUI("clock"); Bangle.setUI("clock");
// Load widgets // Load and draw widgets
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
// draw immediately at first // draw immediately at first
draw(); draw();
setInterval(draw, 10000); // every 10 seconds
var secondInterval = setInterval(draw, 10000);

View File

@ -3,7 +3,7 @@
"name": "Terminal Clock", "name": "Terminal Clock",
"shortName":"Terminal Clock", "shortName":"Terminal Clock",
"description": "A terminal cli like clock displaying multiple sensor data", "description": "A terminal cli like clock displaying multiple sensor data",
"version":"0.05", "version":"0.06",
"icon": "app.png", "icon": "app.png",
"type": "clock", "type": "clock",
"tags": "clock", "tags": "clock",

View File

@ -8,6 +8,8 @@
showHRM: true, showHRM: true,
showActivity: true, showActivity: true,
showStepCount: true, showStepCount: true,
powerSaving: true,
PowerOnInterval: 15,
}, require('Storage').readJSON(FILE, true) || {}); }, require('Storage').readJSON(FILE, true) || {});
function writeSettings() { function writeSettings() {
@ -65,6 +67,25 @@
settings.showStepCount = v; settings.showStepCount = v;
writeSettings(); writeSettings();
} }
},
'Power saving': {
value: settings.powerSaving,
format: v => v?"On":"Off",
onchange: v => {
settings.powerSaving = v;
writeSettings();
}
},
'Power on interval': {
value: settings.PowerOnInterval,
min: 3, max: 60,
onchange: v => {
settings.PowerOnInterval = v;
writeSettings();
},
format: x => {
return x + " min";
}
} }
} }
if (process.env.HWVERSION == 1) { if (process.env.HWVERSION == 1) {

Some files were not shown because too many files have changed in this diff Show More