Merge branch 'espruino:master' into master
|
@ -2,3 +2,4 @@ apps/animclk/V29.LBM.js
|
|||
apps/banglerun/rollup.config.js
|
||||
apps/schoolCalendar/fullcalendar/main.js
|
||||
apps/authentiwatch/qr_packed.js
|
||||
*.test.js
|
||||
|
|
|
@ -43,3 +43,7 @@ charge.
|
|||
|
||||
This app is based in the work done by [jeffmer](https://github.com/jeffmer/JeffsBangleAppsDev)
|
||||
|
||||
|
||||
Written by: [Hugh Barney](https://github.com/hughbarney) For support
|
||||
and discussion please post in the [Bangle JS
|
||||
Forum](http://forum.espruino.com/microcosms/1424/)
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: Display pressure as number and hand
|
||||
0.02: Use theme color
|
||||
0.03: workaround for some firmwares that return 'undefined' for first call to barometer
|
||||
|
|
|
@ -110,9 +110,13 @@ drawScaleLabels();
|
|||
drawIcons();
|
||||
|
||||
try {
|
||||
Bangle.getPressure().then(data => {
|
||||
drawHand(Math.round(data.pressure));
|
||||
});
|
||||
function baroHandler(data) {
|
||||
if (data===undefined) // workaround for https://github.com/espruino/BangleApps/issues/1429
|
||||
setTimeout(() => Bangle.getPressure().then(baroHandler), 500);
|
||||
else
|
||||
drawHand(Math.round(data.pressure));
|
||||
}
|
||||
Bangle.getPressure().then(baroHandler);
|
||||
} catch(e) {
|
||||
print(e.message);
|
||||
print("barometer not supporter, show a demo value");
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "barometer",
|
||||
"name": "Barometer",
|
||||
"shortName":"Barometer",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "A simple barometer that displays the current air pressure",
|
||||
"icon": "barometer.png",
|
||||
"tags": "tool,outdoors",
|
||||
|
|
|
@ -45,3 +45,4 @@
|
|||
0.39: Fix passkey support (fix https://github.com/espruino/Espruino/issues/2035)
|
||||
0.40: Bootloader now rebuilds for new firmware versions
|
||||
0.41: Add Keyboard and Mouse Bluetooth HID option
|
||||
0.42: Sort *.boot.js files lexically and by optional numeric priority, e.g. appname.<priority>.boot.js
|
||||
|
|
|
@ -195,7 +195,19 @@ if (!Bangle.appRect) { // added in 2v11 - polyfill for older firmwares
|
|||
|
||||
// Append *.boot.js files
|
||||
// These could change bleServices/bleServiceOptions if needed
|
||||
require('Storage').list(/\.boot\.js/).forEach(bootFile=>{
|
||||
var getPriority = /.*\.(\d+)\.boot\.js$/;
|
||||
require('Storage').list(/\.boot\.js/).sort((a,b)=>{
|
||||
var aPriority = a.match(getPriority);
|
||||
var bPriority = b.match(getPriority);
|
||||
if (aPriority && bPriority){
|
||||
return parseInt(aPriority[1]) - parseInt(bPriority[1]);
|
||||
} else if (aPriority && !bPriority){
|
||||
return -1;
|
||||
} else if (!aPriority && bPriority){
|
||||
return 1;
|
||||
}
|
||||
return a > b;
|
||||
}).forEach(bootFile=>{
|
||||
// we add a semicolon so if the file is wrapped in (function(){ ... }()
|
||||
// with no semicolon we don't end up with (function(){ ... }()(function(){ ... }()
|
||||
// which would cause an error!
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "boot",
|
||||
"name": "Bootloader",
|
||||
"version": "0.41",
|
||||
"version": "0.42",
|
||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||
"icon": "bootloader.png",
|
||||
"type": "bootloader",
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
# Bowser Watchface
|
||||
|
||||
Show your evil character.
|
||||
With style!
|
||||
|
||||
Bowser jumps once every minute to advance the clock. hehe
|
|
@ -0,0 +1 @@
|
|||
E.toArrayBuffer(atob("Ly+EARERERERERERERERER//8RERERERERERERERERERERERERERERzP//d3EREREREREREREREREREREREREcz//3dxERERERERERERERERERERERERHMz/93dxERERERERERERERERERERERERd8zHd3f/8REREREREREREREREREREREXfMx3d3//ERERERERERERERERERERERF3d3d3d//3cRHBEREREREREREREREREXd3d3d3f/93fMDMERERERERERERERERF3d3d3d3//d3zAzBERERERERERERERERd3d3fMd3f//8zMwREREREREREREREREXd3d8zMd3f/zMzMERERERERERERERERF3d3fMzHd3/8zMzBERERERERER93d3//d3d3x3/Mx3fMzP8REREREREXzP/3d//3d3d8d3fMzMz/8RERERERERF8z/93f/93d3fHd3zMzM//ERERER//93zM//93//d3d3z/d/9////xEREREcz/d3zM/3d///d3d8zHERH/ERERERERHM/3d8zP93f//3d3fMxxER/xERERER/3zMd3d3d3d3f///d3zM8REREREREREf93d3d3d3d3d3d///d3zMERERERERERH/d3d3d3d3d3d3f//3d8zBERERERERERd3d3f///d3d3d3//93fMzP8RERERERH//3d3zP/3d//////3d3d8wRERERERER//93d8z/93f/////93d3fMEREREREREc//d3fMx3f//MzMd3d3d3dxERERERERHMx3d3d3d//8zMAPd3zMzHERERERERH/d3d3d3d3f3fMzwDMzAD8zBERERERER/3d3d3d3d393zM8AzMwA/MwRERERERH913d///d3d/d3AAzMzMAAzMERERERERREd3fM/3d//3d//MzMzMwP/BEREREREURHd3zP93f/93f/zMzMzMD/wRERERERFP/3d8zHd3//d3d3zMzMzAAMERERERER//93d3d3d//3d3d8zMzMwP8BEREREREf//d3d3d3f/93d3fMzMzMD/ARERERERFMzHf/93d3/3d3d3EczM/wABERERERERR3x3fMd3f/93d3dxEREREREREREREREUd8d3zHd3//d3d3cRERERERERERERERFHd3d3d///d3d3dxERERERERERERERERR3d3f///93d3dxEREREREREREREREREUd3d3////d3d3cRERERERERERERERERH//////3d3d3d8ERERERERERERERERER/////MzMx3fMz/8REREREREREREREREf////zMzMd3zM//EREREREREREREREREf/8zMzMzMzMzP//ERERERERERERERERERzMzM/8zP/xEREREREREREREREREREREczMzP/Mz/8REREREREREREREREREREczMzMz//8///xERERERERERERERERERA="))
|
|
@ -0,0 +1,102 @@
|
|||
var sprite = {
|
||||
width : 47, height : 47, bpp : 3,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("kmSpICFn/+BAwCImV//VICJuT//SogRMpmT/2SCJtSyQDB/4RMymRkmX/gRLygDC3/piVhCJElAYf/pNIkgRIlIDCl/6pVBkIRIGwWJEYPypMJCI9KGwQRBLANIPRI2CGoPkyVCBwmeyVLTYNJom8yImBz4gEqV/6Vf+g2BPwf/IIq8C/+kyVRkgDBp/5CIX/+mkz/+y/9BIOf0v6///5LdCz+kCIOk34RBYQMSp5XBGQVk/pNBAQP/9IyBxGSv4yCk/1OIK8EC4QgEpM/JgJ+EGoIRBTApQCEYvplLOFXIIdBO4SqBeQJABGoeTDQMlk5WCAAPSYQLgEz4aBlM/9IgB/7CCcAvP/QsBiVfUwOJBgUiCIcmpAVCy/+pMAKwMkRgIRCp6VBAwW6qVOgmSgPkwgRDv53E6WSuEkyEPRgmf2VJv5HBl2SgAKBwEJRgnJiVKp/Sr/0y/yBQOQv56DKwVSv2STwO/DgWD/BADmaDByRoBYoQRCgFCCIf/+jgDNwOUAwMg/kSPQbODX4IJBAwUH8B6DsmRl5oBl7OBklMyV+gBoDycSxMpiVLZwS8EAQeYyjaByR6BBIJBDAQnEIgbFCogOFRgQDBr//I4L0EAQsxAYP//5WCGQ6MCAAKbCpKYEAQiMB//kIQOUyf+CJF/CIIEBTYOfcgQRHBQv/CJKnBpP8GRTCDJIPkGRQCB5I3C/n/EZUgA"))
|
||||
};
|
||||
|
||||
const boxes = {
|
||||
width : 122, height : 56, bpp : 3,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("kmZkmSpICPwgDBmQUQAQMJAYNkFiOSiQDB5JESAYQsSpADByYsSyBZBydt23bAR+wgFJkwUQAQNggGSposR23AgMkzZESwECpM2IiUAgmSFiW2gDlBFiVsgDlBFiXYgDNBL4MDWZy2FgEGWZy2FgENWZy2EL4MbWZpTBWwZfBXJpTCWwZiCWZpTBWwZiCWZsbWwhiCWZpWCWwTORWwgXRWwgXRWwZESWwZESWwZESWwYXRWwgXRW362/W362/W362/W362/W362/W362/W362/W362/W362/W362/WwuAgazOWwsAgyzOWwsAhqzOWwhfBjazNKYK2DL4K5NKYS2DMQSzNKYK2DMQSzNja2EMQSzNKwS2CZyK2EC6K2EC6K2DIiS2DIiS2DIiUAFoMAAFTkBFtckyAtrLgWSpICnLIIsqyVAgAsqpIA="))
|
||||
};
|
||||
|
||||
const background = {
|
||||
width : 176, height : 176, bpp : 3,
|
||||
transparent : 5,
|
||||
buffer : require("heatshrink").decompress(atob("kmSpIC/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/ATWAgEAIP1///8iRB8gf/AAOCIPdIIARBBoJB/+E4IP4ABghB9v4CB8BB5g/92//9pB7wP/97FEIO9IgDACAAn8iVBIOlHH4xBDnA+wyY9IAAmB/BB//5B/IOQ/OAARBup5B/yV/IP5B/IP5BRt5B7/wDC7aD8/w+B+3bBgP7IP5B7HYNt23/AQPfIPX/9oCC24IDINwCBIRAAHIOACBHI3+g4EC/l/4BByAQkA//wpED//4gGAhJB3pMAgQFBgEBH3AC/AX4C/AX4C/AX4C/AX4C/AUOAgBB/v//ghB9gf///gH3UgiVIIAJBBwRB5j+CIIf8uBB5//wIIXb//+hJB6o/92/7v5B7/0/97GCIPYAG4MgIP/BjkSIP34/hB//5B/AAQ+0IP5B/IP5BN7ZB97///wCBIPX93yAB2wCB+5B5tv//dt24CB35B5v/+n/t+P/I4PH8ESIO38gFA/+CgH/+EIgiD3gACCPoMAgQ+2AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/ASVIgAACgRB/IPY8GkAHBiRB/IPBLKgJB/IP5B/AQUAkmQghB/IP2AgEAyVAiRB/IP5BBpMAIP5B/IIUkgBB/IP5BpoAsBgJBOgEEIIoIBIP5BlyE27dt2EEIJ4CBBAlIgRBgpEAhu2IIO24ESQwxB/IJQhGkEJIL8GHwQCDgOweQpB/IKMkwAKJILVgAofYeQhBzsEAIKICLoESILmBQARBBtuwgZB3kA4B4ENIgJBcpMAIMYCDIOcAgEbHYgCGsEJkhEBE6cBIP5BZfYQ+JIIkDsEBIP5BVyEAIKtAHxgCDwBEBINk2IKCGCIKmSpECIP5BUkEBHyACD2BBUFoMJIP5BSpEbHyQCDIP5BXkmAIP5B/AQcAbKJB/ILH/AAP8hM/AgWSv4KCAAP+gmfAoXJk4ME//gpIEC8mTBgvwkgEC+QRDAAX4gVPAgP5kgsCLwWQh/kMIUf5LuFg4jBAoMBKAJ5EwF/AoUA/yFFoE/CI6RDgY+BCIQsDIP5B/IP5B/IP5B/IJ/AIJfghJBKv0EIJcAIJfwIP5BMhMAAAMEz5BGgmABoVJII9IBgUkII8kBgUSII8CoAMBhJB/IIsQoMAYoP/AAP4YpAMC/+BII9/BgXAYpAMC8DFIBgXwIIcCIP6DCgkQh/kCIRBIbQcBIJAFCgBBICI5BE/IRDFgQA="))
|
||||
};
|
||||
|
||||
numbersDims = {
|
||||
width: 20,
|
||||
height: 44
|
||||
};
|
||||
const numbers = [
|
||||
require("heatshrink").decompress(atob("ikswcBkmSpIC/ARGQKYQIDAwUEBxMAAQNAgECpMgAQMkB4IOIAQQLCgEQBwQaBgEBB1oCBBwYCCiRWDCIRWEO5wOHAX4CnA=")),
|
||||
require("heatshrink").decompress(atob("ikswcBkmSpIC/ARNIKYIIEwEAggOKNIQODyAHCBxQsWB3TUFgMgA4sSBwzU/AVA=")),
|
||||
require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ8gKggIBAwkCBw+QCIQLCgIRCDQcQBwwyDDwUSCgVAAwIOBEwI7EpI7FBw4FDghZGHwgOEF4Y+CEYQ+DBxQADNAIAFNAIOFa/4CoA=")),
|
||||
require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ8gKosSAwsBBw4aCoEAgQjEBoIpEBwtIBoIUEwEAggUDBwwyDDoWQA4ZWHhIIEJQoOCgI+EBwMQEAYOJO4oLBO4oRDJQrX/AU4")),
|
||||
require("heatshrink").decompress(atob("ikswcBkmSpIC/ARNIKgQIDwAGBgQOJNQYOCyAHDBxEggB6BBwYDBiVABxIjBCIIODF4YOEAAkBV40QBwxiDNAosEB0IC/AUg")),
|
||||
require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ5UFkmQAwkCBxIdGCIIIDBxAsTgAaEkEASooOBiQOVJQgOBiBKDBxMSJQwRBLIgRCBwjX/AVA=")),
|
||||
require("heatshrink").decompress(atob("ikswcBkmSpIC/ARGQKgYICAwcCBxADBiQdDkEANYoOGEAYyEHYoOIHYqfFBxIdDBAMQFgZHCBysSFgwRBO46GFa/4CnA")),
|
||||
require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ5VGiAGFgIOIDQUgBwUCEYQOJGQYNBHAlADQgOHwEAggUDpANBCgYpBBwmQAwJiGhIjDB1gC/AU4A=")),
|
||||
require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ8gKYYICAwcEBxGQgAaDgVJgACBDQQOJgB6CBwcAiQODHa4AEhIRBpAHDiARBwAGCgIgCFIYOCFIYOHiQrEJQxlCBwzX/AVAA=")),
|
||||
require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ8gKggIBAwkCBw+QCIQLCgIRCDQcQBzkSTAsBHYoOIL4gOCMooOENAYOCoA4EBwoqDgiGGF4gOEa/4CoA=")),
|
||||
];
|
||||
digitPositions = [ // relative to the box
|
||||
{x:13, y:6}, {x:32, y:6},
|
||||
{x:74, y:6}, {x:93, y:6},
|
||||
];
|
||||
|
||||
var drawTimeout;
|
||||
const animation_duration = 1; // seconds
|
||||
const animation_steps = 20;
|
||||
const jump_height = 45; // top coordinate of the jump
|
||||
const seconds_per_minute = 60;
|
||||
|
||||
function draw() {
|
||||
const now = new Date();
|
||||
g.drawImage(background, 0, 0);
|
||||
var boxTL_x = 27; var boxTL_y = 29;
|
||||
var sprite_TL_x = 72; var sprite_TL_y = 161 - sprite.height;
|
||||
const seconds = now.getSeconds()%seconds_per_minute + now.getMilliseconds()/1000;
|
||||
const hours = now.getHours();
|
||||
const minutes = now.getMinutes();
|
||||
|
||||
var time_advance = seconds / animation_duration;
|
||||
|
||||
if (time_advance < 0.5) {
|
||||
sprite_TL_y += (jump_height - sprite_TL_y) * time_advance * 2;
|
||||
} else if (time_advance < 1) {
|
||||
sprite_TL_y = jump_height + (sprite_TL_y-jump_height) * (time_advance-0.5) * 2;
|
||||
}
|
||||
const box_penetration = boxTL_y + boxes.height - sprite_TL_y;
|
||||
if (box_penetration > 0) {
|
||||
boxTL_y -= box_penetration;
|
||||
}
|
||||
g.drawImage(boxes, boxTL_x, boxTL_y);
|
||||
g.drawImage(numbers[(hours / 10) >> 0], boxTL_x+digitPositions[0].x, boxTL_y+digitPositions[0].y);
|
||||
g.drawImage(numbers[(hours % 10) >> 0], boxTL_x+digitPositions[1].x, boxTL_y+digitPositions[1].y);
|
||||
g.drawImage(numbers[(minutes / 10) >> 0], boxTL_x+digitPositions[2].x, boxTL_y+digitPositions[2].y);
|
||||
g.drawImage(numbers[(minutes % 10) >> 0], boxTL_x+digitPositions[3].x, boxTL_y+digitPositions[3].y);
|
||||
g.drawImage(sprite, sprite_TL_x, sprite_TL_y);
|
||||
Bangle.drawWidgets();
|
||||
|
||||
const timeout = time_advance <= 1?
|
||||
animation_duration / animation_steps
|
||||
: (seconds_per_minute - seconds);
|
||||
setTimeout( _=>{
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, timeout * 1000);
|
||||
}
|
||||
|
||||
// Clear the screen once, at startup
|
||||
g.setTheme({bg:"#00f",fg:"#fff",dark:true}).clear();
|
||||
|
||||
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();
|
||||
|
||||
draw();
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,14 @@
|
|||
{ "id": "bowserWF",
|
||||
"name": "Bowser Watchface",
|
||||
"shortName":"Bowser Watchface",
|
||||
"version":"0.01",
|
||||
"description": "Let bowser show you the time",
|
||||
"icon": "app.png",
|
||||
"tags": "",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"bowserWF.app.js","url":"app.js"},
|
||||
{"name":"bowserWF.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -17,3 +17,6 @@
|
|||
0.06: Fix bug if no request waiting time is set
|
||||
Fix bug if no connection data was cached
|
||||
Fix error during disconnect
|
||||
0.07: Recorder icon only blue if values actually arive
|
||||
Adds some preset modes and a custom one
|
||||
Restructure the settings menu
|
||||
|
|
|
@ -29,6 +29,7 @@ Heart Rate Service (`180D`) and characteristic (`2A37`).
|
|||
So far it has been tested on:
|
||||
|
||||
* CooSpo Bluetooth Heart Rate Monitor
|
||||
* Wahoo TICKR X 2
|
||||
|
||||
## Internals
|
||||
|
||||
|
@ -36,7 +37,6 @@ This replaces `Bangle.setHRMPower` with its own implementation.
|
|||
|
||||
## TODO
|
||||
|
||||
* Maybe a `bthrm.settings.js` and app (that calls it) to enable it to be turned on and off
|
||||
* A widget to show connection state?
|
||||
* Specify a specific device by address?
|
||||
|
||||
|
|
|
@ -548,9 +548,7 @@
|
|||
E.on("kill", ()=>{
|
||||
if (gatt && gatt.connected){
|
||||
log("Got killed, trying to disconnect");
|
||||
var promise = gatt.disconnect();
|
||||
promise.then(()=>log("Disconnected on kill"));
|
||||
promise.catch((e)=>log("Error during disconnnect on kill", e));
|
||||
var promise = gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect on kill", e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"mode": 1,
|
||||
"enabled": true,
|
||||
"replace": true,
|
||||
"debuglog": false,
|
||||
|
@ -6,6 +7,12 @@
|
|||
"allowFallback": true,
|
||||
"warnDisconnect": false,
|
||||
"fallbackTimeout": 10,
|
||||
"custom_replace": false,
|
||||
"custom_debuglog": false,
|
||||
"custom_startWithHrm": false,
|
||||
"custom_allowFallback": false,
|
||||
"custom_warnDisconnect": false,
|
||||
"custom_fallbackTimeout": 10,
|
||||
"gracePeriodNotification": 0,
|
||||
"gracePeriodConnect": 0,
|
||||
"gracePeriodService": 0,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "bthrm",
|
||||
"name": "Bluetooth Heart Rate Monitor",
|
||||
"shortName": "BT HRM",
|
||||
"version": "0.06",
|
||||
"version": "0.07",
|
||||
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
@ -12,7 +12,7 @@
|
|||
"storage": [
|
||||
{"name":"bthrm.app.js","url":"bthrm.js"},
|
||||
{"name":"bthrm.recorder.js","url":"recorder.js"},
|
||||
{"name":"bthrm.boot.js","url":"boot.js"},
|
||||
{"name":"bthrm.0.boot.js","url":"boot.js"},
|
||||
{"name":"bthrm.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"bthrm.settings.js","url":"settings.js"},
|
||||
{"name":"bthrm.default.json","url":"default.json"}
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
Bangle.removeListener('BTHRM', onHRM);
|
||||
if (Bangle.setBTRHMPower) Bangle.setBTHRMPower(0,"recorder");
|
||||
},
|
||||
draw : (x,y) => g.setColor((Bangle.isBTHRMConnected && Bangle.isBTHRMConnected())?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
|
||||
draw : (x,y) => g.setColor((bpm != "")?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
|
||||
};
|
||||
}
|
||||
})
|
||||
|
|
|
@ -20,182 +20,147 @@
|
|||
var mainmenu = {
|
||||
'': { 'title': 'Bluetooth HRM' },
|
||||
'< Back': back,
|
||||
'Use BT HRM': {
|
||||
value: !!settings.enabled,
|
||||
format: v => settings.enabled ? "On" : "Off",
|
||||
'Mode': {
|
||||
value: 0 | settings.mode,
|
||||
min: 0,
|
||||
max: 3,
|
||||
format: v => ["Off", "Default", "Both", "Custom"][v],
|
||||
onchange: v => {
|
||||
writeSettings("enabled",v);
|
||||
settings.mode = v;
|
||||
switch (v){
|
||||
case 0:
|
||||
writeSettings("enabled",false);
|
||||
break;
|
||||
case 1:
|
||||
writeSettings("enabled",true);
|
||||
writeSettings("replace",true);
|
||||
writeSettings("debuglog",false);
|
||||
writeSettings("startWithHrm",true);
|
||||
writeSettings("allowFallback",true);
|
||||
writeSettings("fallbackTimeout",10);
|
||||
break;
|
||||
case 2:
|
||||
writeSettings("enabled",true);
|
||||
writeSettings("replace",false);
|
||||
writeSettings("debuglog",false);
|
||||
writeSettings("startWithHrm",false);
|
||||
writeSettings("allowFallback",false);
|
||||
break;
|
||||
case 3:
|
||||
writeSettings("enabled",true);
|
||||
writeSettings("replace",settings.custom_replace);
|
||||
writeSettings("debuglog",settings.custom_debuglog);
|
||||
writeSettings("startWithHrm",settings.custom_startWithHrm);
|
||||
writeSettings("allowFallback",settings.custom_allowFallback);
|
||||
writeSettings("fallbackTimeout",settings.custom_fallbackTimeout);
|
||||
break;
|
||||
}
|
||||
writeSettings("mode",v);
|
||||
}
|
||||
},
|
||||
'Replace HRM': {
|
||||
value: !!settings.replace,
|
||||
format: v => settings.replace ? "On" : "Off",
|
||||
onchange: v => {
|
||||
writeSettings("replace",v);
|
||||
}
|
||||
},
|
||||
'Start with HRM': {
|
||||
value: !!settings.startWithHrm,
|
||||
format: v => settings.startWithHrm ? "On" : "Off",
|
||||
onchange: v => {(function(back) {
|
||||
function writeSettings(key, value) {
|
||||
var s = require('Storage').readJSON(FILE, true) || {};
|
||||
s[key] = value;
|
||||
require('Storage').writeJSON(FILE, s);
|
||||
readSettings();
|
||||
}
|
||||
'Custom Mode': function() { E.showMenu(submenu_custom); },
|
||||
'Debug': function() { E.showMenu(submenu_debug); }
|
||||
};
|
||||
|
||||
function readSettings(){
|
||||
settings = Object.assign(
|
||||
require('Storage').readJSON("bthrm.default.json", true) || {},
|
||||
require('Storage').readJSON(FILE, true) || {}
|
||||
);
|
||||
}
|
||||
|
||||
var FILE="bthrm.json";
|
||||
var settings;
|
||||
readSettings();
|
||||
|
||||
var mainmenu = {
|
||||
'': { 'title': 'Bluetooth HRM' },
|
||||
'< Back': back,
|
||||
'Use BT HRM': {
|
||||
value: !!settings.enabled,
|
||||
format: v => settings.enabled ? "On" : "Off",
|
||||
var submenu_debug = {
|
||||
'' : { title: "Debug"},
|
||||
'< Back': function() { E.showMenu(mainmenu); },
|
||||
'Alert on disconnect': {
|
||||
value: !!settings.warnDisconnect,
|
||||
format: v => settings.warnDisconnect ? "On" : "Off",
|
||||
onchange: v => {
|
||||
writeSettings("enabled",v);
|
||||
writeSettings("warnDisconnect",v);
|
||||
}
|
||||
},
|
||||
'Replace HRM': {
|
||||
value: !!settings.replace,
|
||||
format: v => settings.replace ? "On" : "Off",
|
||||
'Debug log': {
|
||||
value: !!settings.debuglog,
|
||||
format: v => settings.debuglog ? "On" : "Off",
|
||||
onchange: v => {
|
||||
writeSettings("replace",v);
|
||||
writeSettings("debuglog",v);
|
||||
}
|
||||
},
|
||||
'Grace periods': function() { E.showMenu(submenu_grace); }
|
||||
};
|
||||
|
||||
var submenu_custom = {
|
||||
'' : { title: "Custom mode"},
|
||||
'< Back': function() { E.showMenu(mainmenu); },
|
||||
'Replace HRM': {
|
||||
value: !!settings.custom_replace,
|
||||
format: v => settings.custom_replace ? "On" : "Off",
|
||||
onchange: v => {
|
||||
writeSettings("custom_replace",v);
|
||||
}
|
||||
},
|
||||
'Start w. HRM': {
|
||||
value: !!settings.startWithHrm,
|
||||
format: v => settings.startWithHrm ? "On" : "Off",
|
||||
value: !!settings.custom_startWithHrm,
|
||||
format: v => settings.custom_startWithHrm ? "On" : "Off",
|
||||
onchange: v => {
|
||||
writeSettings("startWithHrm",v);
|
||||
writeSettings("custom_startWithHrm",v);
|
||||
}
|
||||
},
|
||||
'HRM Fallback': {
|
||||
value: !!settings.allowFallback,
|
||||
format: v => settings.allowFallback ? "On" : "Off",
|
||||
value: !!settings.custom_allowFallback,
|
||||
format: v => settings.custom_allowFallback ? "On" : "Off",
|
||||
onchange: v => {
|
||||
writeSettings("allowFallback",v);
|
||||
writeSettings("custom_allowFallback",v);
|
||||
}
|
||||
},
|
||||
'Fallback Timeout': {
|
||||
value: settings.fallbackTimeout,
|
||||
value: settings.custom_fallbackTimeout,
|
||||
min: 5,
|
||||
max: 60,
|
||||
step: 5,
|
||||
format: v=>v+"s",
|
||||
onchange: v => {
|
||||
writeSettings("fallbackTimout",v*1000);
|
||||
writeSettings("custom_fallbackTimout",v*1000);
|
||||
}
|
||||
},
|
||||
'Conn. Alert': {
|
||||
value: !!settings.warnDisconnect,
|
||||
format: v => settings.warnDisconnect ? "On" : "Off",
|
||||
};
|
||||
|
||||
var submenu_grace = {
|
||||
'' : { title: "Grace periods"},
|
||||
'< Back': function() { E.showMenu(submenu_debug); },
|
||||
'Request': {
|
||||
value: settings.gracePeriodRequest,
|
||||
min: 0,
|
||||
max: 3000,
|
||||
step: 100,
|
||||
format: v=>v+"ms",
|
||||
onchange: v => {
|
||||
writeSettings("warnDisconnect",v);
|
||||
writeSettings("gracePeriodRequest",v);
|
||||
}
|
||||
},
|
||||
'Debug log': {
|
||||
value: !!settings.debuglog,
|
||||
format: v => settings.debuglog ? "On" : "Off",
|
||||
'Connect': {
|
||||
value: settings.gracePeriodConnect,
|
||||
min: 0,
|
||||
max: 3000,
|
||||
step: 100,
|
||||
format: v=>v+"ms",
|
||||
onchange: v => {
|
||||
writeSettings("debuglog",v);
|
||||
writeSettings("gracePeriodConnect",v);
|
||||
}
|
||||
},
|
||||
'Grace periods >': function() { E.showMenu(submenu); }
|
||||
};
|
||||
|
||||
var submenu = {
|
||||
'' : { title: "Grace periods"},
|
||||
'< Back': function() { E.showMenu(mainmenu); },
|
||||
'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(mainmenu);
|
||||
})
|
||||
writeSettings("startWithHrm",v);
|
||||
}
|
||||
},
|
||||
'Fallback to HRM': {
|
||||
value: !!settings.allowFallback,
|
||||
format: v => settings.allowFallback ? "On" : "Off",
|
||||
onchange: v => {
|
||||
writeSettings("allowFallback",v);
|
||||
}
|
||||
},
|
||||
'Fallback Timeout': {
|
||||
value: settings.fallbackTimeout,
|
||||
min: 5,
|
||||
max: 60,
|
||||
step: 5,
|
||||
format: v=>v+"s",
|
||||
onchange: v => {
|
||||
writeSettings("fallbackTimout",v*1000);
|
||||
}
|
||||
},
|
||||
'Conn. Alert': {
|
||||
value: !!settings.warnDisconnect,
|
||||
format: v => settings.warnDisconnect ? "On" : "Off",
|
||||
onchange: v => {
|
||||
writeSettings("warnDisconnect",v);
|
||||
}
|
||||
},
|
||||
'Debug log': {
|
||||
value: !!settings.debuglog,
|
||||
format: v => settings.debuglog ? "On" : "Off",
|
||||
onchange: v => {
|
||||
writeSettings("debuglog",v);
|
||||
}
|
||||
},
|
||||
'Grace periods': function() { E.showMenu(submenu); }
|
||||
'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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var submenu = {
|
||||
|
|
|
@ -1,11 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Make overriding the HRM event optional
|
||||
Emit BTHRM event for external sensor
|
||||
Add recorder app plugin
|
||||
0.03: Prevent readings from internal sensor mixing into BT values
|
||||
Mark events with src property
|
||||
Show actual source of event in app
|
||||
0.04: Allow reading additional data if available: HRM battery and position
|
||||
Better caching of scanned BT device properties
|
||||
New setting for not starting the BTHRM together with HRM
|
||||
Save some RAM by not definining functions if disabled in settings
|
||||
0.02: Write available data on reset or kill
|
||||
|
|
|
@ -11,34 +11,30 @@ var currentSlot = 0;
|
|||
var hrvSlots = [10,20,30,60,120,300];
|
||||
var hrvValues = {};
|
||||
var rrRmsProgress;
|
||||
var saved = false;
|
||||
|
||||
var rrNumberOfValues = 0;
|
||||
var rrSquared = 0;
|
||||
var rrLastValue
|
||||
var rrLastValue;
|
||||
var rrMax;
|
||||
var rrMin;
|
||||
|
||||
function calcHrv(rr){
|
||||
//Calculate HRV with RMSSD method: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5624990/
|
||||
for (currentRr of rr){
|
||||
for (var currentRr of rr){
|
||||
if (!rrMax) rrMax = currentRr;
|
||||
if (!rrMin) rrMin = currentRr;
|
||||
rrMax = Math.max(rrMax, currentRr);
|
||||
rrMin = Math.min(rrMin, currentRr);
|
||||
//print("Calc for: " + currentRr);
|
||||
rrNumberOfValues++;
|
||||
if (!rrLastValue){
|
||||
rrLastValue = currentRr;
|
||||
continue;
|
||||
}
|
||||
rrSquared += (rrLastValue - currentRr)*(rrLastValue - currentRr);
|
||||
|
||||
//print("rr²: " + rrSquared);
|
||||
|
||||
rrLastValue = currentRr;
|
||||
}
|
||||
var rms = Math.sqrt(rrSquared / rrNumberOfValues);
|
||||
//print("rms: " + rms);
|
||||
return rms;
|
||||
}
|
||||
|
||||
|
@ -56,17 +52,36 @@ function draw(y, hrv) {
|
|||
if (hrvValues[hrvSlots[i]]) str += hrvValues[hrvSlots[i]].toFixed(1) + "ms";
|
||||
g.setFontVector(16).drawString(str,px,y+44+(i*17));
|
||||
}
|
||||
|
||||
|
||||
g.setRotation(3);
|
||||
g.setFontVector(12).drawString("Reset",g.getHeight()/2, g.getWidth()-10);
|
||||
g.setRotation(0);
|
||||
}
|
||||
|
||||
function write(){
|
||||
if (!hrvValues[hrvSlots[0]]){
|
||||
return;
|
||||
}
|
||||
|
||||
var file = require('Storage').open("bthrv.csv", "a");
|
||||
var data = new Date(startingTime).toISOString();
|
||||
for (var i = 0; i < hrvSlots.length; i++ ){
|
||||
data += ",";
|
||||
if (hrvValues[hrvSlots[i]]){
|
||||
data += hrvValues[hrvSlots[i]];
|
||||
}
|
||||
}
|
||||
|
||||
data += "," + rrMax + "," + rrMin + ","+rrNumberOfValues;
|
||||
data += "\n";
|
||||
file.write(data);
|
||||
Bangle.buzz(500);
|
||||
}
|
||||
|
||||
function onBtHrm(e) {
|
||||
if (e.rr && !startingTime) Bangle.buzz(500);
|
||||
if (e.rr && !startingTime) startingTime=Date.now();
|
||||
//print("Event:" + e.rr);
|
||||
|
||||
|
||||
var hrv = calcHrv(e.rr);
|
||||
if (hrv){
|
||||
if (currentSlot <= hrvSlots.length && (Date.now() - startingTime) > (hrvSlots[currentSlot] * 1000) && !hrvValues[hrvSlots[currentSlot]]){
|
||||
|
@ -74,35 +89,25 @@ function onBtHrm(e) {
|
|||
currentSlot++;
|
||||
}
|
||||
}
|
||||
if (!saved && currentSlot == hrvSlots.length){
|
||||
var file = require('Storage').open("bthrv.csv", "a");
|
||||
var data = new Date(startingTime).toISOString();
|
||||
for (var c of hrvSlots){
|
||||
data+=","+hrvValues[c];
|
||||
}
|
||||
data+="," + rrMax + "," + rrMin + ","+rrNumberOfValues;
|
||||
data+="\n";
|
||||
file.write(data);
|
||||
saved = true;
|
||||
Bangle.buzz(500);
|
||||
}
|
||||
|
||||
if (hrv){
|
||||
if (!ui){
|
||||
if (!ui){
|
||||
Bangle.setUI("leftright", ()=>{
|
||||
resetHrv();
|
||||
clear(30);
|
||||
});
|
||||
ui = true;
|
||||
}
|
||||
|
||||
draw(30, hrv);
|
||||
}
|
||||
}
|
||||
|
||||
function resetHrv(){
|
||||
write();
|
||||
hrvValues={};
|
||||
startingTime=undefined;
|
||||
currentSlot=0;
|
||||
saved=false;
|
||||
rrNumberOfValues = 0;
|
||||
rrSquared = 0;
|
||||
rrLastValue = undefined;
|
||||
|
@ -117,7 +122,6 @@ g.clear();
|
|||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
|
||||
if (Bangle.setBTHRMPower){
|
||||
Bangle.on('BTHRM', onBtHrm);
|
||||
Bangle.setBTHRMPower(1,'bthrv');
|
||||
|
@ -133,6 +137,11 @@ if (Bangle.setBTHRMPower){
|
|||
file.write(data);
|
||||
}
|
||||
|
||||
E.on('kill', ()=>{
|
||||
write();
|
||||
Bangle.setBTHRMPower(0,'bthrv');
|
||||
});
|
||||
|
||||
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
||||
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16);
|
||||
} else {
|
||||
|
@ -140,4 +149,3 @@ if (Bangle.setBTHRMPower){
|
|||
g.drawString("Missing BT HRM",g.getWidth()/2,g.getHeight()/2 - 16);
|
||||
}
|
||||
|
||||
E.on('kill', ()=>Bangle.setBTHRMPower(0,'bthrv'));
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "bthrv",
|
||||
"name": "Bluetooth Heart Rate variance calculator",
|
||||
"shortName": "BT HRV",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "Calculates HRV from a a BT HRM with interval data",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -19,3 +19,4 @@
|
|||
Colors of circles can be configured
|
||||
Color depending on value (green -> red, red -> green) option
|
||||
Good HRM value will not be overwritten so fast anymore
|
||||
0.10: Use roboto font for time, date and day of week and center align them
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"minHR": 40,
|
||||
"maxHR": 200,
|
||||
"confidence": 0,
|
||||
"stepGoal": 10000,
|
||||
"stepDistanceGoal": 8000,
|
||||
"stepLength": 0.8,
|
||||
"batteryWarn": 30,
|
||||
"showWidgets": false,
|
||||
"weatherCircleData": "humidity",
|
||||
"circleCount": 3,
|
||||
"circle1": "hr",
|
||||
"circle2": "steps",
|
||||
"circle3": "battery",
|
||||
"circle4": "weather",
|
||||
"circle1color": "green-red",
|
||||
"circle2color": "#0000ff",
|
||||
"circle3color": "red-green",
|
||||
"circle4color": "#ffff00",
|
||||
"circle1colorizeIcon": true,
|
||||
"circle2colorizeIcon": true,
|
||||
"circle3colorizeIcon": true,
|
||||
"circle4colorizeIcon": false,
|
||||
"hrmValidity": 60
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "circlesclock",
|
||||
"name": "Circles clock",
|
||||
"shortName":"Circles clock",
|
||||
"version":"0.09",
|
||||
"version":"0.10",
|
||||
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],
|
||||
|
@ -13,7 +13,8 @@
|
|||
"storage": [
|
||||
{"name":"circlesclock.app.js","url":"app.js"},
|
||||
{"name":"circlesclock.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"circlesclock.settings.js","url":"settings.js"}
|
||||
{"name":"circlesclock.settings.js","url":"settings.js"},
|
||||
{"name":"circlesclock.default.json","url":"default.json"}
|
||||
],
|
||||
"data": [
|
||||
{"name":"circlesclock.json"}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
(function(back) {
|
||||
const SETTINGS_FILE = "circlesclock.json";
|
||||
const storage = require('Storage');
|
||||
let settings = storage.readJSON(SETTINGS_FILE, 1) || {};
|
||||
let settings = Object.assign(
|
||||
storage.readJSON("circlesclock.default.json", true) || {},
|
||||
storage.readJSON(SETTINGS_FILE, true) || {}
|
||||
);
|
||||
|
||||
function save(key, value) {
|
||||
settings[key] = value;
|
||||
storage.write(SETTINGS_FILE, settings);
|
||||
|
@ -10,8 +14,8 @@
|
|||
const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude"];
|
||||
const namesCircleTypes = ["empty", "steps", "distance", "heart", "battery", "weather", "sun", "temperature", "pressure", "altitude"];
|
||||
|
||||
const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", "#00ffff", "#fff", "#000", "green-red", "red-green"];
|
||||
const namesColors = ["default", "red", "green", "blue", "yellow", "magenta", "cyan", "white", "black", "green->red", "red->green"];
|
||||
const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", "#00ffff", "#fff", "#000", "green-red", "red-green"];
|
||||
const namesColors = ["default", "red", "green", "blue", "yellow", "magenta", "cyan", "white", "black", "green->red", "red->green"];
|
||||
|
||||
const weatherData = ["empty", "humidity", "wind"];
|
||||
|
||||
|
@ -20,7 +24,7 @@
|
|||
'': { 'title': 'Circles clock' },
|
||||
/*LANG*/'< Back': back,
|
||||
/*LANG*/'circle count': {
|
||||
value: "circleCount" in settings ? settings.circleCount : 3,
|
||||
value: settings.circleCount,
|
||||
min: 3,
|
||||
max : 4,
|
||||
step: 1,
|
||||
|
@ -33,7 +37,7 @@
|
|||
/*LANG*/'heartrate': ()=>showHRMenu(),
|
||||
/*LANG*/'steps': ()=>showStepMenu(),
|
||||
/*LANG*/'battery warn': {
|
||||
value: "batteryWarn" in settings ? settings.batteryWarn : 30,
|
||||
value: settings.batteryWarn,
|
||||
min: 10,
|
||||
max : 100,
|
||||
step: 10,
|
||||
|
@ -43,12 +47,12 @@
|
|||
onchange: x => save('batteryWarn', x),
|
||||
},
|
||||
/*LANG*/'show widgets': {
|
||||
value: "showWidgets" in settings ? settings.showWidgets : false,
|
||||
value: !!settings.showWidgets,
|
||||
format: () => (settings.showWidgets ? 'Yes' : 'No'),
|
||||
onchange: x => save('showWidgets', x),
|
||||
},
|
||||
/*LANG*/'weather circle': {
|
||||
value: settings.weatherCircleData ? weatherData.indexOf(settings.weatherCircleData) : 1,
|
||||
/*LANG*/'weather data': {
|
||||
value: weatherData.indexOf(settings.weatherCircleData),
|
||||
min: 0, max: 2,
|
||||
format: v => weatherData[v],
|
||||
onchange: x => save('weatherCircleData', weatherData[x]),
|
||||
|
@ -62,7 +66,7 @@
|
|||
'': { 'title': /*LANG*/'Heartrate' },
|
||||
/*LANG*/'< Back': ()=>showMainMenu(),
|
||||
/*LANG*/'minimum': {
|
||||
value: "minHR" in settings ? settings.minHR : 40,
|
||||
value: settings.minHR,
|
||||
min: 0,
|
||||
max : 250,
|
||||
step: 5,
|
||||
|
@ -72,7 +76,7 @@
|
|||
onchange: x => save('minHR', x),
|
||||
},
|
||||
/*LANG*/'maximum': {
|
||||
value: "maxHR" in settings ? settings.maxHR : 200,
|
||||
value: settings.maxHR,
|
||||
min: 20,
|
||||
max : 250,
|
||||
step: 5,
|
||||
|
@ -82,7 +86,7 @@
|
|||
onchange: x => save('maxHR', x),
|
||||
},
|
||||
/*LANG*/'min. confidence': {
|
||||
value: "confidence" in settings ? settings.confidence : 0,
|
||||
value: settings.confidence,
|
||||
min: 0,
|
||||
max : 100,
|
||||
step: 10,
|
||||
|
@ -92,7 +96,7 @@
|
|||
onchange: x => save('confidence', x),
|
||||
},
|
||||
/*LANG*/'valid period': {
|
||||
value: "hrmValidity" in settings ? settings.hrmValidity : 30,
|
||||
value: settings.hrmValidity,
|
||||
min: 10,
|
||||
max : 600,
|
||||
step: 10,
|
||||
|
@ -110,7 +114,7 @@
|
|||
'': { 'title': /*LANG*/'Steps' },
|
||||
/*LANG*/'< Back': ()=>showMainMenu(),
|
||||
/*LANG*/'goal': {
|
||||
value: "stepGoal" in settings ? settings.stepGoal : 10000,
|
||||
value: settings.stepGoal,
|
||||
min: 2000,
|
||||
max : 50000,
|
||||
step: 2000,
|
||||
|
@ -120,7 +124,7 @@
|
|||
onchange: x => save('stepGoal', x),
|
||||
},
|
||||
/*LANG*/'distance goal': {
|
||||
value: "stepDistanceGoal" in settings ? settings.stepDistanceGoal : 8000,
|
||||
value: settings.stepDistanceGoal,
|
||||
min: 2000,
|
||||
max : 30000,
|
||||
step: 1000,
|
||||
|
@ -130,7 +134,7 @@
|
|||
onchange: x => save('stepDistanceGoal', x),
|
||||
},
|
||||
/*LANG*/'step length': {
|
||||
value: "stepLength" in settings ? settings.stepLength : 0.8,
|
||||
value: settings.stepLength,
|
||||
min: 0.1,
|
||||
max : 1.5,
|
||||
step: 0.01,
|
||||
|
@ -142,9 +146,6 @@
|
|||
};
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
const defaultCircleTypes = ["steps", "hr", "battery", "weather"];
|
||||
|
||||
function showCircleMenu(circleId) {
|
||||
const circleName = "circle" + circleId;
|
||||
const colorKey = circleName + "color";
|
||||
|
@ -154,19 +155,19 @@
|
|||
'': { 'title': /*LANG*/'Circle ' + circleId },
|
||||
/*LANG*/'< Back': ()=>showMainMenu(),
|
||||
/*LANG*/'data': {
|
||||
value: settings[circleName]!=undefined ? valuesCircleTypes.indexOf(settings[circleName]) : valuesCircleTypes.indexOf(defaultCircleTypes[circleId -1]),
|
||||
value: valuesCircleTypes.indexOf(settings[circleName]),
|
||||
min: 0, max: valuesCircleTypes.length - 1,
|
||||
format: v => namesCircleTypes[v],
|
||||
onchange: x => save(circleName, valuesCircleTypes[x]),
|
||||
},
|
||||
/*LANG*/'color': {
|
||||
value: settings[colorKey] ? valuesColors.indexOf(settings[colorKey]) : 0,
|
||||
value: valuesColors.indexOf(settings[colorKey]) || 0,
|
||||
min: 0, max: valuesColors.length - 1,
|
||||
format: v => namesColors[v],
|
||||
onchange: x => save(colorKey, valuesColors[x]),
|
||||
},
|
||||
/*LANG*/'colorize icon': {
|
||||
value: colorizeIconKey in settings ? settings[colorizeIconKey] : false,
|
||||
value: settings[colorizeIconKey] || false,
|
||||
format: () => (settings[colorizeIconKey] ? 'Yes' : 'No'),
|
||||
onchange: x => save(colorizeIconKey, x),
|
||||
},
|
||||
|
|
|
@ -5,3 +5,4 @@
|
|||
0.05: add Bangle 2 version
|
||||
0.06: Adds settings page (hide clocks or launchers)
|
||||
0.07: Adds setting for directly launching app on touch for Bangle 2
|
||||
0.08: Optimize line wrapping for Bangle 2
|
||||
|
|
|
@ -45,11 +45,23 @@ function draw_icon(p,n,selected) {
|
|||
g.setColor(g.theme.fg);
|
||||
try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){}
|
||||
g.setFontAlign(0,-1,0).setFont("6x8",1);
|
||||
var txt = apps[p*4+n].name.split(" ");
|
||||
for (var i = 0; i < txt.length; i++) {
|
||||
txt[i] = txt[i].trim();
|
||||
g.drawString(txt[i],x+36,y+54+i*8);
|
||||
var txt = apps[p*4+n].name.replace(/([a-z])([A-Z])/g, "$1 $2").split(" ");
|
||||
var lineY = 0;
|
||||
var line = "";
|
||||
while (txt.length > 0){
|
||||
var c = txt.shift();
|
||||
|
||||
if (c.length + 1 + line.length > 13){
|
||||
if (line.length > 0){
|
||||
g.drawString(line.trim(),x+36,y+54+lineY*8);
|
||||
lineY++;
|
||||
}
|
||||
line = c;
|
||||
} else {
|
||||
line += " " + c;
|
||||
}
|
||||
}
|
||||
g.drawString(line.trim(),x+36,y+54+lineY*8);
|
||||
}
|
||||
|
||||
function drawPage(p){
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "dtlaunch",
|
||||
"name": "Desktop Launcher",
|
||||
"version": "0.07",
|
||||
"version": "0.08",
|
||||
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
|
||||
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
|
||||
"icon": "icon.png",
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
0.06: Bangle.js 2 support
|
||||
0.07: Fix "previous" button image
|
||||
0.08: Fix scrolling title background color
|
||||
0.09: Move event listener from widget to boot code, stops music from showing up in messages
|
||||
|
|
|
@ -175,10 +175,8 @@ function rIcon(l) {
|
|||
}
|
||||
let layout;
|
||||
function makeUI() {
|
||||
global.gbmusic_active = true; // we don't need our widget (needed for <2.09 devices)
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
delete (global.gbmusic_active);
|
||||
const Layout = require("Layout");
|
||||
layout = new Layout({
|
||||
type: "v", c: [
|
||||
|
@ -331,7 +329,7 @@ function formatNum(info) {
|
|||
* Update music info
|
||||
* @param {Object} info - Gadgetbridge musicinfo event
|
||||
*/
|
||||
function musicInfo(info) {
|
||||
function info(info) {
|
||||
scrollStop();
|
||||
layout.title.label = info.track || "";
|
||||
layout.album.label = info.album || "";
|
||||
|
@ -360,7 +358,7 @@ let tPxt, tIxt; // Timeouts to eXiT when Paused/Inactive for too long
|
|||
* Update music state
|
||||
* @param {Object} e - Gadgetbridge musicstate event
|
||||
*/
|
||||
function musicState(e) {
|
||||
function state(e) {
|
||||
stat = e.state;
|
||||
// if paused for five minutes, load the clock
|
||||
// (but timeout resets if we get new info, even while paused)
|
||||
|
@ -584,8 +582,8 @@ function startEmulator() {
|
|||
println: (line) => {console.log("Bluetooth:", line);},
|
||||
};
|
||||
// some example info
|
||||
GB({"t": "musicinfo", "artist": "Some Artist Name", "album": "The Album Name", "track": "The Track Title Goes Here", "dur": 241, "c": 2, "n": 2});
|
||||
GB({"t": "musicstate", "state": "play", "position": 0, "shuffle": 1, "repeat": 1});
|
||||
info({"t": "musicinfo", "artist": "Some Artist Name", "album": "The Album Name", "track": "The Track Title Goes Here", "dur": 241, "c": 2, "n": 2});
|
||||
state({"t": "musicstate", "state": "play", "position": 0, "shuffle": 1, "repeat": 1});
|
||||
}
|
||||
}
|
||||
function startWatches() {
|
||||
|
@ -596,25 +594,6 @@ function startWatches() {
|
|||
|
||||
function start() {
|
||||
makeUI();
|
||||
// start listening for music updates
|
||||
const _GB = global.GB;
|
||||
global.GB = (event) => {
|
||||
// we eat music events!
|
||||
switch(event.t) {
|
||||
case "musicinfo":
|
||||
musicInfo(event);
|
||||
break;
|
||||
case "musicstate":
|
||||
musicState(event);
|
||||
break;
|
||||
default:
|
||||
// pass on other events
|
||||
if (_GB) {
|
||||
setTimeout(_GB, 0, event);
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
startWatches();
|
||||
tick();
|
||||
startEmulator();
|
||||
|
@ -625,11 +604,11 @@ function init() {
|
|||
let saved = require("Storage").readJSON("gbmusic.load.json", true);
|
||||
require("Storage").erase("gbmusic.load.json");
|
||||
if (saved) {
|
||||
// autoloaded: load state was saved by widget
|
||||
// autoloaded: load state as saved by widget
|
||||
auto = true;
|
||||
start();
|
||||
musicInfo(saved.info);
|
||||
musicState(saved.state);
|
||||
info(saved.info);
|
||||
state(saved.state);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
setTimeout( // make other boot code run first, so we override e.g. android.boot.js GB
|
||||
() => {
|
||||
const APP = global.__FILE__==="gbmusic.app.js",
|
||||
a = !!(require("Storage").readJSON("gbmusic.json", 1) || {}).autoStart;
|
||||
|
||||
let s, i; // state, info
|
||||
/**
|
||||
* Save current song and check if we want to load the gbmusic app
|
||||
*
|
||||
* Only runs while other apps are loaded
|
||||
*/
|
||||
function check() {
|
||||
if (s!=="play" || !i || !a || !Bangle.CLOCK) return; // only launch app if we know which song we are playing, and autoLoad is enabled
|
||||
delete (i.t);
|
||||
// store info and launch music app
|
||||
require("Storage").writeJSON("gbmusic.load.json", {
|
||||
state: s,
|
||||
info: i,
|
||||
});
|
||||
load("gbmusic.app.js");
|
||||
}
|
||||
|
||||
global.GB = (_GB => e => {
|
||||
// we eat music events!
|
||||
switch(e.t) {
|
||||
case "musicinfo":
|
||||
i = e;
|
||||
return APP ? info(e) : check();
|
||||
case "musicstate":
|
||||
s = e.state;
|
||||
return APP ? state(e) : check();
|
||||
default:
|
||||
// pass on other events
|
||||
if (_GB) setTimeout(_GB, 0, e);
|
||||
}
|
||||
})(global.GB);
|
||||
}, 1);
|
|
@ -2,7 +2,7 @@
|
|||
"id": "gbmusic",
|
||||
"name": "Gadgetbridge Music Controls",
|
||||
"shortName": "Music Controls",
|
||||
"version": "0.08",
|
||||
"version": "0.09",
|
||||
"description": "Control the music on your Gadgetbridge-connected phone",
|
||||
"icon": "icon.png",
|
||||
"screenshots": [{"url":"screenshot_v1.png"},{"url":"screenshot_v2.png"}],
|
||||
|
@ -14,7 +14,7 @@
|
|||
"storage": [
|
||||
{"name":"gbmusic.app.js","url":"app.js"},
|
||||
{"name":"gbmusic.settings.js","url":"settings.js"},
|
||||
{"name":"gbmusic.wid.js","url":"widget.js"},
|
||||
{"name":"gbmusic.boot.js","url":"boot.js"},
|
||||
{"name":"gbmusic.img","url":"icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"gbmusic.json"},{"name":"gbmusic.load.json"}]
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
(() => {
|
||||
if (global.gbmusic_active || !(require("Storage").readJSON("gbmusic.json", 1) || {}).autoStart) {
|
||||
return;
|
||||
}
|
||||
if (typeof __FILE__ === 'string') { // only exists since 2v09
|
||||
const info = require("Storage").readJSON(__FILE__.split(".")[0]+".info", 1) || false;
|
||||
if (info && info.type!=="clock") { // info can have no type (but then it isn't a clock)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let state, info;
|
||||
function checkMusic() {
|
||||
if (state!=="play" || !info) {
|
||||
return;
|
||||
}
|
||||
// playing music: launch music app
|
||||
require("Storage").writeJSON("gbmusic.load.json", {
|
||||
state: state,
|
||||
info: info,
|
||||
});
|
||||
load("gbmusic.app.js");
|
||||
}
|
||||
|
||||
const _GB = global.GB;
|
||||
global.GB = (event) => {
|
||||
// we eat music events!
|
||||
switch(event.t) {
|
||||
case "musicinfo":
|
||||
info = event;
|
||||
delete (info.t);
|
||||
checkMusic();
|
||||
break;
|
||||
case "musicstate":
|
||||
state = event.state;
|
||||
checkMusic();
|
||||
break;
|
||||
default:
|
||||
if (_GB) {
|
||||
setTimeout(_GB, 0, event);
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
|
@ -107,3 +107,8 @@ try {
|
|||
|
||||
* Some useful code on Github can be found [here](https://portal.u-blox.com/s/question/0D52p0000925T00CAE/ublox-max-m8q-getting-stuck-when-sleeping-with-extint-pin-control)
|
||||
and [here](https://github.com/thasti/utrak/blob/master/gps.c)
|
||||
|
||||
|
||||
Written by: [Hugh Barney, with support from Gordon Williams](https://github.com/hughbarney) For support
|
||||
and discussion please post in the [Bangle JS
|
||||
Forum](http://forum.espruino.com/microcosms/1424/)
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: Release
|
|
@ -0,0 +1,17 @@
|
|||
# Info
|
||||
|
||||
A very simple app that shows information on 3 different screens.
|
||||
Go to the next screen via tab right, go to the previous screen
|
||||
via tab left and reload the data via tab in the middle of the
|
||||
screen. Very useful if combined with pattern launcher ;)
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
## Contributors
|
||||
- [David Peer](https://github.com/peerdavid).
|
||||
|
||||
## Thanks To
|
||||
<a href="https://www.flaticon.com/free-icons/info" title="info icons">Info icons created by Freepik - Flaticon</a>
|
|
@ -0,0 +1,108 @@
|
|||
var s = require("Storage");
|
||||
const locale = require('locale');
|
||||
var ENV = process.env;
|
||||
var W = g.getWidth(), H = g.getHeight();
|
||||
var screen = 0;
|
||||
const maxScreen = 2;
|
||||
|
||||
function getVersion(file) {
|
||||
var j = s.readJSON(file,1);
|
||||
var v = ("object"==typeof j)?j.version:false;
|
||||
return v?((v?"v"+v:"Unknown")):"NO ";
|
||||
}
|
||||
|
||||
|
||||
function drawData(name, value, y){
|
||||
g.drawString(name, 5, y);
|
||||
g.drawString(value, 100, y);
|
||||
}
|
||||
|
||||
function getSteps(){
|
||||
try{
|
||||
return Bangle.getHealthStatus("day").steps;
|
||||
} catch(e) {
|
||||
return ">= 2v12";
|
||||
}
|
||||
}
|
||||
|
||||
function getBpm(){
|
||||
try{
|
||||
return Math.round(Bangle.getHealthStatus("day").bpm) + "bpm";
|
||||
} catch(e) {
|
||||
return ">= 2v12";
|
||||
}
|
||||
}
|
||||
|
||||
function drawInfo() {
|
||||
g.reset().clearRect(Bangle.appRect);
|
||||
var h=18, y = h;//-h;
|
||||
|
||||
// Header
|
||||
g.setFont("Vector", h+2).setFontAlign(0,-1);
|
||||
g.drawString("--==|| INFO ||==--", W/2, 0);
|
||||
g.setFont("Vector",h).setFontAlign(-1,-1);
|
||||
|
||||
// Dynamic data
|
||||
if(screen == 0){
|
||||
drawData("Steps", getSteps(), y+=h);
|
||||
drawData("HRM", getBpm(), y+=h);
|
||||
drawData("Battery", E.getBattery() + "%", y+=h);
|
||||
drawData("Voltage", E.getAnalogVRef().toFixed(2) + "V", y+=h);
|
||||
drawData("IntTemp.", locale.temp(parseInt(E.getTemperature())), y+=h);
|
||||
}
|
||||
|
||||
if(screen == 1){
|
||||
drawData("Charging?", Bangle.isCharging() ? "Yes" : "No", y+=h);
|
||||
drawData("Bluetooth", NRF.getSecurityStatus().connected ? "Conn." : "Disconn.", y+=h);
|
||||
drawData("GPS", Bangle.isGPSOn() ? "On" : "Off", y+=h);
|
||||
drawData("Compass", Bangle.isCompassOn() ? "On" : "Off", y+=h);
|
||||
drawData("HRM", Bangle.isHRMOn() ? "On" : "Off", y+=h);
|
||||
}
|
||||
|
||||
// Static data
|
||||
if(screen == 2){
|
||||
drawData("Firmw.", ENV.VERSION, y+=h);
|
||||
drawData("Boot.", getVersion("boot.info"), y+=h);
|
||||
drawData("Settings", getVersion("setting.info"), y+=h);
|
||||
drawData("Storage", "", y+=h);
|
||||
drawData(" Total", ENV.STORAGE>>10, y+=h);
|
||||
drawData(" Free", require("Storage").getFree()>>10, y+=h);
|
||||
}
|
||||
|
||||
if(Bangle.isLocked()){
|
||||
g.setFont("Vector",h-2).setFontAlign(-1,-1);
|
||||
g.drawString("Locked", 0, H-h+2);
|
||||
}
|
||||
|
||||
g.setFont("Vector",h-2).setFontAlign(1,-1);
|
||||
g.drawString((screen+1) + "/3", W, H-h+2);
|
||||
}
|
||||
|
||||
drawInfo();
|
||||
setWatch(_=>load(), BTN1);
|
||||
|
||||
Bangle.on('touch', function(btn, e){
|
||||
var left = parseInt(g.getWidth() * 0.3);
|
||||
var right = g.getWidth() - left;
|
||||
var isLeft = e.x < left;
|
||||
var isRight = e.x > right;
|
||||
|
||||
if(isRight){
|
||||
screen = (screen + 1) % (maxScreen+1);
|
||||
}
|
||||
|
||||
if(isLeft){
|
||||
screen -= 1;
|
||||
screen = screen < 0 ? maxScreen : screen;
|
||||
}
|
||||
|
||||
drawInfo();
|
||||
});
|
||||
|
||||
Bangle.on('lock', function(isLocked) {
|
||||
drawInfo();
|
||||
});
|
||||
|
||||
Bangle.loadWidgets();
|
||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
||||
// Bangle.drawWidgets();
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwcBkmSpICDBwcJBYwCDpAhFggRJGg8SCI+ABgU//gSDCI4JBj//AAX4JRAIBg4QDAAPgBIJWGgIQFAAI+BLglAgEPCI/wEgJoEgYQHAAPANwhWFAApcBCIWQgAQJAAMAgSMDCJiSCwB6GQA6eCn5TFL4q5BUgIRF/wuBv4RGkCeGO4IREUgMBCJCVGCISwIWw0BYRLIICLBHHCJRrGCIQIFR44I5LIoRaPpARcdIwRJfYMBCJuACKUkgE/a5f8gEJCJD7FCIeAg78FAAvggFJCIMACJZOBCIOQCJsCCIOSgEfCBP4gESCIZTFOIwRDoDIGaguSCIVIgCkFTwcAggRDpIYBQAx6BgAOCAQYIBLghWBTwQRFFgIABXIIFDBwgCDBYQAENAYCFLgIAEKwpKIIhA="))
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"id": "info",
|
||||
"name": "Info",
|
||||
"version": "0.01",
|
||||
"description": "An application that displays information such as battery level, steps etc.",
|
||||
"icon": "info.png",
|
||||
"type": "app",
|
||||
"tags": "tool",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"screenshots": [
|
||||
{"url":"screenshot_1.png"},
|
||||
{"url":"screenshot_2.png"},
|
||||
{"url":"screenshot_3.png"}],
|
||||
"storage": [
|
||||
{"name":"info.app.js","url":"info.app.js"},
|
||||
{"name":"info.img","url":"info.icon.js","evaluate":true}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.4 KiB |
|
@ -280,3 +280,8 @@ The following error codes will be displayed if one of the dependancies is not me
|
|||
* Add a small graph to the heart rate monitor app
|
||||
* Add a facility to call the Arrow calibration process
|
||||
* Maybe create waypoints.json file if missing
|
||||
|
||||
|
||||
Written by: [Hugh Barney](https://github.com/hughbarney) For support
|
||||
and discussion please post in the [Bangle JS
|
||||
Forum](http://forum.espruino.com/microcosms/1424/)
|
||||
|
|
|
@ -14,4 +14,5 @@
|
|||
0.14: Added altitude as an option to display.
|
||||
0.15: Using wpedom to count steps.
|
||||
0.16: Improved stability. Wind can now be shown.
|
||||
0.17: Settings for mph/kph and other minor improvements.
|
||||
0.17: Settings for mph/kph and other minor improvements.
|
||||
0.18: Fullscreen mode can now be enabled or disabled in the settings.
|
|
@ -11,7 +11,7 @@ with Gadgetbride and the weather app must be installed.
|
|||
|
||||
## Features
|
||||
* LCARS Style watch face.
|
||||
* Full screen mode - widgets are still loaded but not shown.
|
||||
* Enable or disable fullscreen mode (widgets are always loaded, but hidden if fullscreen).
|
||||
* Tab on left/right to switch between different screens.
|
||||
* Cusomizable data that is shown on screen 1 (steps, weather etc.)
|
||||
* Shows random and real images of planets.
|
||||
|
@ -33,7 +33,7 @@ with Gadgetbride and the weather app must be installed.
|
|||
## Multiple screens support
|
||||
Access different screens via tap on the left/ right side of the screen
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
|
|
After Width: | Height: | Size: 772 B |
After Width: | Height: | Size: 769 B |
|
@ -7,6 +7,7 @@ let settings = {
|
|||
dataRow2: "Temp",
|
||||
dataRow3: "Battery",
|
||||
speed: "kph",
|
||||
fullscreen: false,
|
||||
};
|
||||
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
|
||||
for (const key in saved_settings) {
|
||||
|
@ -30,23 +31,39 @@ let lcarsViewPos = 0;
|
|||
// let hrmValue = 0;
|
||||
var plotMonth = false;
|
||||
|
||||
|
||||
/*
|
||||
* Requirements and globals
|
||||
*/
|
||||
|
||||
|
||||
var bgLeft = {
|
||||
var bgLeftFullscreen = {
|
||||
width : 27, height : 176, bpp : 3,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("AAUM2XLlgCCwAJBBAuy4EAmQIF5cggAIGlmwgYIG2XIF42wF4ImGF4ImHJoQmGJoQdJhZNHNY47CgRNGBIJZHHgRiGBIRQ/KH5QCAFCh/eX5Q/KAwdCAGVbtu27YCCoAJBkuWrNlAQRGCiwRDAQPQBIMJCIYCBsAJBgomEtu0WoQmEy1YBIMBHYttIwQ7FyxQ/KHFlFAQ7F2weCHYplKChRTCCg5TCHw5TMAD0GzVp0wCCBBGaBIMaBAtpwECBA2mwEJBAugDgMmCIwJBF5EABAtoeQQvGCYQdPJoI7LMQzTCLJKAGzAJBO4xQ/KGQA8UP7y/KH5QnAHih/eX5Q/GQ4JCGRJlKCgxTDBAwgCCg5TCHwxTCNA4A=="))
|
||||
};
|
||||
|
||||
var bgRight = {
|
||||
var bgLeftNotFullscreen = {
|
||||
width : 27, height : 152, bpp : 3,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("AAUM2XLlgCCwAJBBAuy4EAmQIF5cggAIGlmwgYIG2XIF42wF4ImGF4ImHJoQmGJoQdJhZNHNY47CgRNGBIJZHHgRiGBIRQ/KH5QCAGVbtu27YCCoAJBkuWrNlAQRkCiwRDAQPQBIMJCIYCBsAJBgomEtu0WoQmEy1YBIMBHYttIwQ7FyxQ/KHFlFAQ7F2weCHYplKChRTCCg5TCHw5TMAD0GzVp0wCCBBGaBIMaBAtpwECBA2mwEJBAugDgMmCIwJBF5EABAtoeQQvGCYQdPJoI7LMQzTCLJKAGzAJBO4xQ/KGQA8UP7y/KH5QnAHih/eX5Q/GQ4JCGRJlKCgxTDBAwgCCg5TCHwxTCNA4A="))
|
||||
};
|
||||
|
||||
var bgRightFullscreen = {
|
||||
width : 27, height : 176, bpp : 3,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("lmy5YCDBIUyBAmy5AJBhYUG2EAhgIFAQMAgQIGCgQABCg4ABEAwUNFI2AKZHAKZEgGRZTGOIUDQxJxGKH5Q/agwAnUP7y/KH4yGeVYAJrdt23bAQVABIMly1ZsoCCMgUWCIYCB6AJBhIRDAQNgBIMFEwlt2i1CEwmWrAJBgI7FtpGCHYuWKH5QxEwpQDlo7F0A7IqBZBEwo7BCIwCBJo53CJoxiCJpIAdgOmzVpAQR/CgAIEAQJ2CBAoCBBIMmCg1oD4QLGFQUCCjQ+CKYw+CKY4JCKYwoCGRMaGREJDoroCgwdFzBlLKH5QvAHih/eX5Q/KE4A8UP7y/KH5QGDpg7HJoxZCCIx3CJowmCF4yACJox/CgAA="))
|
||||
};
|
||||
|
||||
var bgRightNotFullscreen = {
|
||||
width : 27, height : 152, bpp : 3,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("lmy5YCDBIUyBAmy5AJBhYUG2EAhgIFAQMAgQIGCgQABCg4ABEAwUNFI2AKZHAKZEgGRZTGOIUDQxJxGKH5Q/agwAxrdt23bAQVABIMly1ZsoCCMgUWCIYCB6AJBhIRDAQNgBIMFEwlt2i1CEwmWrAJBgI7FtpGCHYuWKH5QxEwpQDlo7F0A7IqBZBEwo7BCIwCBJo53CJoxiCJpIAdgOmzVpAQR/CgAIEAQJ2CBAoCBBIMmCg1oD4QLGFQUCCjQ+CKYw+CKY4JCKYwoCGRMaGREJDoroCgwdFzBlLKH5QvAHih/eX5Q/KE4A8UP7y/KH5QGDpg7HJoxZCCIx3CJowmCF4yACJox/CgA="))
|
||||
};
|
||||
|
||||
var bgLeft = settings.fullscreen ? bgLeftFullscreen : bgLeftNotFullscreen;
|
||||
var bgRight= settings.fullscreen ? bgRightFullscreen : bgRightNotFullscreen;
|
||||
|
||||
var iconEarth = {
|
||||
width : 50, height : 50, bpp : 3,
|
||||
buffer : require("heatshrink").decompress(atob("AFtx48ECBsDwU5k/yhARLjgjBjlzAQMQEZcIkOP/fn31IEZgCBnlz58cEpM4geugEgwU/8+WNZJHDuHHvgmBCQ8goEOnVgJoMnyV58mACItHI4X8uAFBuVHnnz4BuGxk4////Egz3IkmWvPgNw8f/prB//BghTC+AjE7848eMjNnzySBwUJkmf/BuGuPDAQIjBiPHhhTCSQnjMo0ITANJn44Dg8MuFBggCCiFBcAJ0Bv5xEh+ITo2OhHkyf/OIQdBWwVHhgjBNwUE+fP/5EEgePMoYLBhMgyVJk/+BQQdC688I4XxOIc8v//NAvr+QEBj/5NwKVBy1/QYUciPBhk1EAJrC+KeC489QYaMBgU/8BNB9+ChEjz1Jkn/QYMBDQIgCcYTCCiP/nlzJQmenMAgV4//uy/9wRaB/1J8iVCcAfHjt9TYYICnhKCgRKBw159/v//r927OIeeoASBDQccvv3791KYVDBYPLJQeCnPnz//AAP6ocEjEkXgMgJQtz79fLAP8KYkccAcJ8Gf/f/xu/cAMQ4eP5MlyQRCMolx40YsOGBAPfnnzU4KVDpKMBvz8Dh0/8me7IICgkxJQXPIgZTD58sEgcJk+eNoONnFBhk4/5uB/pcDg5KD+4mEv4CBXISVDhEn31/8/+mH7x//JQK5CAAMB4JBCnnxJQf/+fJEgkAa4L+CAQOOjMn/1bXIRxDJQXx58f//Hhlz/88EgsChMgz/Zs/+nfkyV/8huDOI6SD498NwoACi1Z8+S/Plz17/+QCI7jC+ZxBmfPnojIAAMDcYWSp//2wRJEwq2GABECjMgNYwAmA="))
|
||||
|
@ -217,7 +234,7 @@ function drawHorizontalBgLine(color, x1, x2, y, h){
|
|||
|
||||
|
||||
function drawInfo(){
|
||||
if(lcarsViewPos != 0){
|
||||
if(lcarsViewPos != 0 || !settings.fullscreen){
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -276,9 +293,10 @@ function drawState(){
|
|||
|
||||
function drawPosition0(){
|
||||
// Draw background image
|
||||
g.drawImage(bgLeft, 0, 0);
|
||||
drawHorizontalBgLine(cBlue, 25, 120, 0, 4);
|
||||
drawHorizontalBgLine(cBlue, 130, 176, 0, 4);
|
||||
var offset = settings.fullscreen ? 0 : 24;
|
||||
g.drawImage(bgLeft, 0, offset);
|
||||
drawHorizontalBgLine(cBlue, 25, 120, offset, 4);
|
||||
drawHorizontalBgLine(cBlue, 130, 176, offset, 4);
|
||||
drawHorizontalBgLine(cPurple, 20, 70, 80, 4);
|
||||
drawHorizontalBgLine(cPurple, 80, 176, 80, 4);
|
||||
drawHorizontalBgLine(cOrange, 35, 110, 87, 4);
|
||||
|
@ -304,15 +322,26 @@ function drawPosition0(){
|
|||
var currentDate = new Date();
|
||||
var timeStr = locale.time(currentDate,1);
|
||||
g.setFontAntonioLarge();
|
||||
g.drawString(timeStr, 27, 10);
|
||||
if(settings.fullscreen){
|
||||
g.drawString(timeStr, 27, 10);
|
||||
} else {
|
||||
g.drawString(timeStr, 27, 33);
|
||||
}
|
||||
|
||||
// Write date
|
||||
g.setColor(cWhite);
|
||||
g.setFontAntonioMedium();
|
||||
var dayStr = locale.dow(currentDate, true).toUpperCase();
|
||||
dayStr += " " + currentDate.getDate();
|
||||
dayStr += " " + locale.month(currentDate, 1).toUpperCase();
|
||||
g.drawString(dayStr, 30, 56);
|
||||
if(settings.fullscreen){
|
||||
var dayStr = locale.dow(currentDate, true).toUpperCase();
|
||||
dayStr += " " + currentDate.getDate();
|
||||
dayStr += " " + locale.month(currentDate, 1).toUpperCase();
|
||||
g.drawString(dayStr, 30, 56);
|
||||
} else {
|
||||
var dayStr = locale.dow(currentDate, true).toUpperCase();
|
||||
var date = currentDate.getDate();
|
||||
g.drawString(dayStr, 128, 35);
|
||||
g.drawString(date, 128, 55);
|
||||
}
|
||||
|
||||
// Draw data
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
|
@ -327,8 +356,11 @@ function drawPosition0(){
|
|||
|
||||
function drawPosition1(){
|
||||
// Draw background image
|
||||
g.drawImage(bgRight, 149, 0);
|
||||
drawHorizontalBgLine(cBlue, 0, 140, 0, 4);
|
||||
var offset = settings.fullscreen ? 0 : 24;
|
||||
g.drawImage(bgRight, 149, offset);
|
||||
if(settings.fullscreen){
|
||||
drawHorizontalBgLine(cBlue, 0, 140, offset, 4);
|
||||
}
|
||||
drawHorizontalBgLine(cPurple, 0, 80, 80, 4);
|
||||
drawHorizontalBgLine(cPurple, 90, 150, 80, 4);
|
||||
drawHorizontalBgLine(cOrange, 0, 50, 87, 4);
|
||||
|
@ -388,8 +420,13 @@ function drawPosition1(){
|
|||
g.setFontAlign(1, 1, 0);
|
||||
g.setFontAntonioMedium();
|
||||
g.setColor(cWhite);
|
||||
g.drawString("M-HRM", 154, 27);
|
||||
g.drawString("M-STEPS [K]", 154, 115);
|
||||
|
||||
if(settings.fullscreen){
|
||||
g.drawString("M-HRM", 154, 27);
|
||||
g.drawString("M-STEPS [K]", 154, 115);
|
||||
} else {
|
||||
g.drawString("MONTH", 154, 115);
|
||||
}
|
||||
|
||||
// Plot day
|
||||
} else {
|
||||
|
@ -429,8 +466,13 @@ function drawPosition1(){
|
|||
g.setFontAlign(1, 1, 0);
|
||||
g.setFontAntonioMedium();
|
||||
g.setColor(cWhite);
|
||||
g.drawString("D-HRM", 154, 27);
|
||||
g.drawString("D-STEPS", 154, 115);
|
||||
|
||||
if(settings.fullscreen){
|
||||
g.drawString("D-HRM", 154, 27);
|
||||
g.drawString("D-STEPS", 154, 115);
|
||||
} else {
|
||||
g.drawString("DAY", 154, 115);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -451,6 +493,13 @@ function draw(){
|
|||
} else if (lcarsViewPos == 1) {
|
||||
drawPosition1();
|
||||
}
|
||||
|
||||
// After drawing the watch face, we can draw the widgets
|
||||
if(settings.fullscreen){
|
||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
||||
} else {
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -501,8 +550,9 @@ function getWeather(){
|
|||
weather.hum = weather.hum + "%";
|
||||
|
||||
// Wind
|
||||
var speedFactor = settings.speed == "kph" ? 1.60934 : 1.0;
|
||||
weather.wind = Math.round(weather.wind * speedFactor);
|
||||
const wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/);
|
||||
var speedFactor = settings.speed == "kph" ? 1.0 : 1.0 / 1.60934;
|
||||
weather.wind = Math.round(wind[1] * speedFactor);
|
||||
|
||||
return weather
|
||||
}
|
||||
|
@ -652,16 +702,7 @@ Bangle.on('touch', function(btn, e){
|
|||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
/*
|
||||
* we are not drawing the widgets as we are taking over the whole screen
|
||||
* so we will blank out the draw() functions of each widget and change the
|
||||
* area to the top bar doesn't get cleared.
|
||||
*/
|
||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
||||
|
||||
// Clear the screen once, at startup and draw clock
|
||||
g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
|
||||
draw();
|
||||
|
||||
// After drawing the watch face, we can draw the widgets
|
||||
// Bangle.drawWidgets();
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
dataRow2: "Steps",
|
||||
dataRow3: "Temp",
|
||||
speed: "kph",
|
||||
fullscreen: false,
|
||||
};
|
||||
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
|
||||
for (const key in saved_settings) {
|
||||
|
@ -52,6 +53,14 @@
|
|||
save();
|
||||
},
|
||||
},
|
||||
'Full Screen': {
|
||||
value: settings.fullscreen,
|
||||
format: () => (settings.fullscreen ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.fullscreen = !settings.fullscreen;
|
||||
save();
|
||||
},
|
||||
},
|
||||
'Speed': {
|
||||
value: 0 | speedOptions.indexOf(settings.speed),
|
||||
min: 0, max: 1,
|
||||
|
|
|
@ -3,13 +3,15 @@
|
|||
"name": "LCARS Clock",
|
||||
"shortName":"LCARS",
|
||||
"icon": "lcars.png",
|
||||
"version":"0.17",
|
||||
"version":"0.18",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"description": "Library Computer Access Retrieval System (LCARS) clock.",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"screenshots": [
|
||||
{"url":"screenshot_1.png"},
|
||||
{"url":"screenshot_3.png"}],
|
||||
"storage": [
|
||||
{"name":"lcars.app.js","url":"lcars.app.js"},
|
||||
{"name":"lcars.img","url":"lcars.icon.js","evaluate":true},
|
||||
|
|
Before Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 4.6 KiB |
|
@ -30,3 +30,4 @@
|
|||
If showMessage called with no message (eg all messages deleted) now return to the clock (fix #1267)
|
||||
0.19: Use a larger font for message text if it'll fit
|
||||
0.20: Allow tapping on the body to show a scrollable view of the message and title in a bigger font (fix #1405, #1031)
|
||||
0.21: Improve list readability on dark theme
|
||||
|
|
|
@ -27,7 +27,7 @@ var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2";
|
|||
var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4";
|
||||
var colBg = g.theme.dark ? "#141":"#4f4";
|
||||
var colSBg1 = g.theme.dark ? "#121":"#cFc";
|
||||
var colSBg2 = g.theme.dark ? "#242":"#9F9";
|
||||
var colSBg2 = g.theme.dark ? "#000":"#9F9";
|
||||
// hack for 2v10 firmware's lack of ':size' font handling
|
||||
try {
|
||||
g.setFont("6x8:2");
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "messages",
|
||||
"name": "Messages",
|
||||
"version": "0.20",
|
||||
"version": "0.21",
|
||||
"description": "App to display notifications from iOS and Gadgetbridge",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: First release
|
||||
0.02: Enhanced icon, make it bolder
|
||||
0.03: Fixed issue with defaulting back to London
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"icon": "mylocation.png",
|
||||
"type": "app",
|
||||
"screenshots": [{"url":"screenshot_1.png"}],
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"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",
|
||||
"tags": "tool,utility",
|
||||
|
|
|
@ -9,32 +9,35 @@ let s = {
|
|||
'lat': 51.5072,
|
||||
'lon': 0.1276,
|
||||
'location': "London"
|
||||
}
|
||||
};
|
||||
|
||||
function loadSettings() {
|
||||
settings = require('Storage').readJSON(SETTINGS_FILE, 1) || s;
|
||||
settings = require('Storage').readJSON(SETTINGS_FILE, 1) || {};
|
||||
for (const key in settings) {
|
||||
s[key] = settings[key]
|
||||
}
|
||||
}
|
||||
|
||||
function save() {
|
||||
settings = s
|
||||
require('Storage').write(SETTINGS_FILE, settings)
|
||||
settings = s;
|
||||
require('Storage').write(SETTINGS_FILE, settings);
|
||||
}
|
||||
|
||||
const locations = ["London", "Newcastle", "Edinburgh", "Paris", "New York", "Tokyo","???"];
|
||||
const lats = [51.5072 ,54.9783 ,55.9533 ,48.8566 ,40.7128 ,35.6762, 0.0];
|
||||
const lons = [-0.1276 ,-1.6178 ,-3.1883 ,2.3522 , -74.0060 ,139.6503, 0.0];
|
||||
const locations = ["London" ,"Newcastle","Edinburgh", "Paris" , "New York" , "Tokyo" , "Frankfurt", "Auckland", "???"];
|
||||
const lats = [ 51.5072 , 54.9783 , 55.9533 , 48.8566 , 40.7128 , 35.6762 , 50.1236 , -36.9 , 0.0 ];
|
||||
const lons = [ -0.1276 , -1.6178 , -3.1883 , 2.3522 , -74.0060 , 139.6503 , 8.6553 , 174.7832 , 0.0 ];
|
||||
|
||||
function setFromGPS() {
|
||||
Bangle.on('GPS', (gps) => {
|
||||
//console.log(".");
|
||||
if (gps.fix === 0) return;
|
||||
//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.setGPSPower(0);
|
||||
save();
|
||||
|
||||
Bangle.setUI("updown", ()=>{ load() });
|
||||
Bangle.setUI("updown", ()=>{ load(); });
|
||||
E.showPrompt("Location has been saved from the GPS fix",{
|
||||
title:"Location Saved",
|
||||
buttons : {"OK":1}
|
||||
|
@ -49,13 +52,13 @@ function setFromGPS() {
|
|||
}
|
||||
|
||||
function showMainMenu() {
|
||||
console.log("showMainMenu");
|
||||
//console.log("showMainMenu");
|
||||
const mainmenu = {
|
||||
'': { 'title': 'My Location' },
|
||||
'<Back': ()=>{ load(); },
|
||||
'City': {
|
||||
value: 0 | locations.indexOf(s.location),
|
||||
min: 0, max: 6,
|
||||
min: 0, max: locations.length - 1,
|
||||
format: v => locations[v],
|
||||
onchange: v => {
|
||||
if (v != 6) {
|
||||
|
@ -67,7 +70,7 @@ function showMainMenu() {
|
|||
}
|
||||
},
|
||||
'Set From GPS': ()=>{ setFromGPS(); }
|
||||
}
|
||||
};
|
||||
return E.showMenu(mainmenu);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
0.01: Initial release
|
||||
0.02: Optional fullscreen mode
|
|
@ -0,0 +1,24 @@
|
|||
# Neon X and IO X Clock
|
||||
|
||||
|  |  |
|
||||
|---------------------------------|--------------------------------------|
|
||||
| <center>Neon X</center> | <center>Neon IO X</center> |
|
||||
|
||||
This is a clock based on Pebble's Neon X and Neon IO X watchfaces by Sam Jerichow.
|
||||
Can be switched between in the Settings menu, which can be accessed through
|
||||
the app/widget settings menu of the Bangle.js
|
||||
|
||||
## Settings
|
||||
|
||||
### Neon IO X:
|
||||
Activate the Neon IO X clock look, a bit hard to read until one gets used to it.
|
||||
|
||||
### Thickness
|
||||
The thickness of watch lines, from 1 to 6.
|
||||
|
||||
### Date on touch
|
||||
Shows the current date as DD MM on touch and reverts back to time after 5 seconds or with another touch.
|
||||
|
||||
### Fullscreen
|
||||
Shows the watchface in fullscreen mode.
|
||||
Note: In fullscreen mode, widgets are hidden, but still loaded.
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"id": "neonx",
|
||||
"name": "Neon X & IO X Clock",
|
||||
"shortName": "Neon X Clock",
|
||||
"version": "0.02",
|
||||
"description": "Pebble Neon X & Neon IO X for Bangle.js",
|
||||
"icon": "neonx.png",
|
||||
"type": "clock",
|
||||
"readme": "README.md",
|
||||
"tags": "neonx,neonio,neoniox,clock",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url": "neonx-screenshot.png"}, {"url": "neoniox-screenshot.png"}],
|
||||
"storage": [
|
||||
{"name": "neonx.app.js", "url": "neonx.app.js"},
|
||||
{"name": "neonx.img", "url": "neonx-icon.js", "evaluate": true},
|
||||
{"name": "neonx.settings.js", "url": "neonx.settings.js"}
|
||||
],
|
||||
"data": [{"name": "neonx.json"}]
|
||||
}
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1 @@
|
|||
E.toArrayBuffer(atob("MDCDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAwAAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAABQAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAgABttttttttsAAEkAAAAAAAEkANtttttttttgAEkAAAAAAAEkABttttttttsAAEkAAAAAAAEkAAAAAAAAAAAAAEkAAAAAAAEkAAAAAAAAAAAAAEkAAAAAAAEkAAAAAAAAAAAAAEkAAAAAAAEkAAAAAAAAAAAAAEkAAAAAAAEkAAAAAAAAAAAAAEkAAAAAAAEkAAAAAAAAAAAAAEkAAAAAAAEkAAAAAAAAAAAAAAgAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkABttttttttsAAAAAAAAAAAEkANtttttttttgAAAAAAAAAAAgABttttttttsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="))
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,168 @@
|
|||
/**
|
||||
* Bangle.js Neon X/IO X Clock
|
||||
*
|
||||
* Author: Bundyo
|
||||
* Repo: https://github.com/bundyo/BangleApps/tree/master/apps/neonx
|
||||
* Initial code based on Numerals Clock by Raik M.
|
||||
* Pebble Watchface Author: Sam Jerichow
|
||||
* Created: February 2022
|
||||
*/
|
||||
|
||||
const digits = {
|
||||
0:[[15,15,85,15,85,85,15,85,15,15]],
|
||||
1:[[85,15,85,85]],
|
||||
2:[[15,15,85,15,85,50], [15,50,15,85,85,85]],
|
||||
3:[[15,15,85,15,85,85,15,85]],
|
||||
4:[[15,15,15,50], [85,15,85,85]],
|
||||
5:[[85,15,15,15,15,50], [85,50,85,85,15,85]],
|
||||
6:[[85,15,15,15,15,85,85,85,85,50]],
|
||||
7:[[15,15,85,15,85,85]],
|
||||
8:[[15,15,85,15],[15,85,85,85]],
|
||||
9:[[15,50,15,15,85,15,85,85,15,85]],
|
||||
};
|
||||
|
||||
const colors = {
|
||||
x: [
|
||||
["#FF00FF", "#00FFFF"],
|
||||
["#00FF00", "#FFFF00"]
|
||||
],
|
||||
io: [
|
||||
["#FF00FF", "#FFFF00"],
|
||||
["#00FF00", "#00FFFF"]
|
||||
]
|
||||
};
|
||||
|
||||
const is12hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]||false;
|
||||
const screenWidth = g.getWidth();
|
||||
const screenHeight = g.getHeight();
|
||||
const halfWidth = screenWidth / 2;
|
||||
const scale = screenWidth / 240;
|
||||
const REFRESH_RATE = 10E3;
|
||||
|
||||
let interval = 0;
|
||||
let showingDate = false;
|
||||
|
||||
function drawLine(poly, thickness){
|
||||
for (let i = 0; i < poly.length; i = i + 2){
|
||||
if (poly[i + 2] === undefined) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (poly[i] !== poly[i + 2]) {
|
||||
g.fillRect(poly[i], poly[i + 1] - thickness / 2, poly[i + 2], poly[i + 3] + thickness / 2);
|
||||
} else {
|
||||
g.fillRect(poly[i] - thickness / 2, poly[i + 1], poly[i + 2] + thickness / 2, poly[i + 3]);
|
||||
}
|
||||
|
||||
g.fillCircle(poly[i], poly[i + 1], thickness / 2);
|
||||
g.fillCircle(poly[i + 2], poly[i + 3], thickness / 2);
|
||||
}
|
||||
}
|
||||
|
||||
let settings = {
|
||||
thickness: 4,
|
||||
io: 0,
|
||||
showDate: 1,
|
||||
fullscreen: false,
|
||||
};
|
||||
let saved_settings = require('Storage').readJSON('neonx.json', 1) || settings;
|
||||
for (const key in saved_settings) {
|
||||
settings[key] = saved_settings[key]
|
||||
}
|
||||
|
||||
|
||||
|
||||
function drawClock(num){
|
||||
let tx, ty;
|
||||
|
||||
for (let x = 0; x <= 1; x++) {
|
||||
for (let y = 0; y <= 1; y++) {
|
||||
const current = ((y + 1) * 2 + x - 1);
|
||||
let newScale = scale;
|
||||
|
||||
g.setColor(colors[settings.io ? 'io' : 'x'][y][x]);
|
||||
|
||||
if (!settings.io) {
|
||||
newScale *= settings.fullscreen ? 1.18 : 1.0;
|
||||
let dx = settings.fullscreen ? 0 : 18
|
||||
tx = (x * 100 + dx) * newScale;
|
||||
ty = (y * 100 + dx*2) * newScale;
|
||||
} else {
|
||||
newScale = 0.33 + current * (settings.fullscreen ? 0.48 : 0.4);
|
||||
|
||||
tx = (halfWidth - 139) * newScale + halfWidth + (settings.fullscreen ? 2 : 0);
|
||||
ty = (halfWidth - 139) * newScale + halfWidth + (settings.fullscreen ? 2 : 12);
|
||||
}
|
||||
|
||||
for (let i = 0; i < digits[num[y][x]].length; i++) {
|
||||
drawLine(g.transformVertices(digits[num[y][x]][i], { x: tx, y: ty, scale: newScale}), settings.thickness);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function draw(date){
|
||||
let d = new Date();
|
||||
let l1, l2;
|
||||
|
||||
showingDate = date;
|
||||
|
||||
if (date) {
|
||||
setUpdateInt(0);
|
||||
|
||||
l1 = ('0' + (new Date()).getDate()).substr(-2);
|
||||
l2 = ('0' + ((new Date()).getMonth() + 1)).substr(-2);
|
||||
|
||||
setTimeout(_ => {
|
||||
draw();
|
||||
setUpdateInt(1);
|
||||
}, 5000);
|
||||
} else {
|
||||
l1 = ('0' + (d.getHours() % (is12hour ? 12 : 24))).substr(-2);
|
||||
l2 = ('0' + d.getMinutes()).substr(-2);
|
||||
}
|
||||
|
||||
if(settings.fullscreen){
|
||||
g.clearRect(0,0,screenWidth,screenHeight);
|
||||
} else {
|
||||
g.clearRect(0,24,240,240);
|
||||
}
|
||||
|
||||
drawClock([l1, l2]);
|
||||
}
|
||||
|
||||
function setUpdateInt(set){
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
|
||||
if (set) {
|
||||
interval = setInterval(draw, REFRESH_RATE);
|
||||
}
|
||||
}
|
||||
|
||||
g.clear(1);
|
||||
|
||||
Bangle.setUI("clock");
|
||||
|
||||
setUpdateInt(1);
|
||||
draw();
|
||||
|
||||
if (settings.showDate) {
|
||||
Bangle.on('touch', () => draw(!showingDate));
|
||||
}
|
||||
|
||||
Bangle.on('lcdPower', function(on){
|
||||
if (on){
|
||||
draw();
|
||||
setUpdateInt(1);
|
||||
} else setUpdateInt(0);
|
||||
});
|
||||
|
||||
Bangle.loadWidgets();
|
||||
|
||||
if(settings.fullscreen){
|
||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
||||
} else {
|
||||
Bangle.drawWidgets();
|
||||
}
|
After Width: | Height: | Size: 885 B |
|
@ -0,0 +1,63 @@
|
|||
(function(back) {
|
||||
function updateSettings() {
|
||||
storage.write('neonx.json', neonXSettings);
|
||||
}
|
||||
|
||||
function resetSettings() {
|
||||
neonXSettings = {
|
||||
thickness: 4,
|
||||
io: 0,
|
||||
showDate: 1,
|
||||
fullscreen: false,
|
||||
};
|
||||
|
||||
updateSettings();
|
||||
}
|
||||
|
||||
let neonXSettings = storage.readJSON('neonx.json',1);
|
||||
|
||||
if (!neonXSettings) resetSettings();
|
||||
|
||||
let thicknesses = [1, 2, 3, 4, 5, 6];
|
||||
|
||||
const menu = {
|
||||
"" : { "title":"Neon X & IO"},
|
||||
"< Back": back,
|
||||
"Neon IO X": {
|
||||
value: 0 | neonXSettings.io,
|
||||
min: 0, max: 1,
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
neonXSettings.io = v;
|
||||
updateSettings();
|
||||
}
|
||||
},
|
||||
"Thickness": {
|
||||
value: 0 | thicknesses.indexOf(neonXSettings.thickness),
|
||||
min: 0, max: thicknesses.length - 1,
|
||||
format: v => thicknesses[v],
|
||||
onchange: v => {
|
||||
neonXSettings.thickness = thicknesses[v];
|
||||
updateSettings();
|
||||
}
|
||||
},
|
||||
"Date on touch": {
|
||||
value: 0 | neonXSettings.showDate,
|
||||
min: 0, max: 1,
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
neonXSettings.showDate = v;
|
||||
updateSettings();
|
||||
}
|
||||
},
|
||||
'Fullscreen': {
|
||||
value: false | neonXSettings.fullscreen,
|
||||
format: () => (neonXSettings.fullscreen ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
neonXSettings.fullscreen = !neonXSettings.fullscreen;
|
||||
updateSettings();
|
||||
},
|
||||
},
|
||||
};
|
||||
E.showMenu(menu);
|
||||
})
|
|
@ -13,3 +13,7 @@
|
|||
which requires 2.11.27 firmware to reset at midnight
|
||||
0.13: call process.memory(false) to avoid triggering a GC of memory
|
||||
supported in pre 2.12.13 firmware
|
||||
0.14: incorporated lazybones idle timer, configuration settings to come
|
||||
0.15: fixed tendancy for mylocation to default to London
|
||||
added setting to enable/disable idle timer warning
|
||||
0.16: make check_idle boolean setting work properly with new B2 menu
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
"id": "pastel",
|
||||
"name": "Pastel Clock",
|
||||
"shortName": "Pastel",
|
||||
"version": "0.13",
|
||||
"description": "A Configurable clock with custom fonts, background and weather display. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times.",
|
||||
"version": "0.16",
|
||||
"description": "A Configurable clock with custom fonts, background and weather display. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times",
|
||||
"icon": "pastel.png",
|
||||
"dependencies": {"mylocation":"app","weather":"app"},
|
||||
"screenshots": [{"url":"screenshot_pastel.png"}, {"url":"weather_icons.png"}],
|
||||
|
|
|
@ -4,9 +4,18 @@ const storage = require('Storage');
|
|||
const locale = require("locale");
|
||||
const SETTINGS_FILE = "pastel.json";
|
||||
const LOCATION_FILE = "mylocation.json";
|
||||
const w = g.getWidth();
|
||||
const h = g.getHeight();
|
||||
let settings;
|
||||
let location;
|
||||
|
||||
// variable for controlling idle alert
|
||||
let lastStep = getTime();
|
||||
let lastStepTime = '??';
|
||||
let warned = 0;
|
||||
let idle = false;
|
||||
let IDLE_MINUTES = 26;
|
||||
|
||||
// cloud, sun, partSun, snow, rain, storm, error
|
||||
// create 1 bit, max contrast, brightness set to 85
|
||||
var cloudIcon = require("heatshrink").decompress(atob("kEggIfcj+AAYM/8ADBuFwAYPAmADCCAMBwEf8ADBhFwg4aBnEPAYMYjAVBhgDDDoQDHCYc4jwDB+EP///FYIDBMTgA=="));
|
||||
|
@ -16,16 +25,24 @@ var snowIcon = require("heatshrink").decompress(atob("kEggITQj/AAYM98ADBsEwAYPAj
|
|||
var rainIcon = require("heatshrink").decompress(atob("kEggIPMh+AAYM/8ADBuFwAYPgmADB4EbAYOAj/ggOAhnwg4aBnAeCjEcCIMMjADCDoQDHjAPCnAXCuEP///8EDAYJECAAXBwkAgPDhwDBwUMgEEhkggEOjFgFgMQLYQAOA=="));
|
||||
var errIcon = require("heatshrink").decompress(atob("kEggILIgOAAYsD4ADBg/gAYMGsADBhkwAYsYjADCjgDBmEMAYNxxwDBsOGAYPBwYDEgOBwOAgYDB4EDHYPAgwDBsADDhgDBFIcwjAHBjE4AYMcmADBhhNCKIcG/4AGOw4A=="));
|
||||
|
||||
// saves having to recode all the small font calls
|
||||
function setSmallFont() {
|
||||
g.setFontLatoSmall();
|
||||
}
|
||||
|
||||
function loadSettings() {
|
||||
settings = require("Storage").readJSON(SETTINGS_FILE,1)||{};
|
||||
settings.grid = settings.grid||false;
|
||||
settings.font = settings.font||"Lato";
|
||||
settings.idle_check = settings.idle_check||true;
|
||||
}
|
||||
|
||||
// requires the myLocation app
|
||||
function loadLocation() {
|
||||
location = require("Storage").readJSON(LOCATION_FILE,1)||{"lat":51.5072,"lon":0.1276,"location":"London"};
|
||||
location = require("Storage").readJSON(LOCATION_FILE,1)||{};
|
||||
location.lat = location.lat||51.5072;
|
||||
location.lon = location.lon||0.1276;
|
||||
location.location = location.location||"London";
|
||||
}
|
||||
|
||||
function extractTime(d){
|
||||
|
@ -71,17 +88,18 @@ function getSteps() {
|
|||
if (WIDGETS.wpedom !== undefined)
|
||||
return WIDGETS.wpedom.getSteps();
|
||||
else
|
||||
return '???'
|
||||
return '???';
|
||||
}
|
||||
}
|
||||
|
||||
const infoData = {
|
||||
ID_BLANK: { calc: () => '' },
|
||||
ID_DATE: { calc: () => {var d = (new Date).toString().split(" "); return d[2] + ' ' + d[1] + ' ' + d[3];} },
|
||||
ID_DAY: { calc: () => {var d = require("locale").dow(new Date).toLowerCase(); return d[0].toUpperCase() + d.substring(1);} },
|
||||
ID_DATE: { calc: () => {var d = (new Date()).toString().split(" "); return d[2] + ' ' + d[1] + ' ' + d[3];} },
|
||||
ID_DAY: { calc: () => {var d = require("locale").dow(new Date()).toLowerCase(); return d[0].toUpperCase() + d.substring(1);} },
|
||||
ID_SR: { calc: () => 'Sunrise: ' + sunRise },
|
||||
ID_SS: { calc: () => 'Sunset: ' + sunSet },
|
||||
ID_STEP: { calc: () => 'Steps: ' + getSteps() },
|
||||
ID_LAST: { calc: () => 'Last Step: ' + lastStepTime },
|
||||
ID_BATT: { calc: () => 'Battery: ' + E.getBattery() + '%' },
|
||||
ID_MEM: { calc: () => {var val = process.memory(false); return 'Ram: ' + Math.round(val.usage*100/val.total) + '%';} },
|
||||
ID_ID: { calc: () => {var val = NRF.getAddress().split(':'); return 'Id: ' + val[4] + val[5];} },
|
||||
|
@ -152,6 +170,14 @@ function getWeather() {
|
|||
}
|
||||
|
||||
function draw() {
|
||||
if (!idle)
|
||||
drawClock();
|
||||
else
|
||||
drawIdle();
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
function drawClock() {
|
||||
var d = new Date();
|
||||
var da = d.toString().split(" ");
|
||||
var time = da[4].substr(0,5);
|
||||
|
@ -166,11 +192,8 @@ function draw() {
|
|||
if (parseInt(hh) > 12)
|
||||
hh = h2.substr(h2.length -2);
|
||||
|
||||
var w = g.getWidth();
|
||||
var h = g.getHeight();
|
||||
var x = (g.getWidth()/2);
|
||||
var y = (g.getHeight()/3);
|
||||
|
||||
var weatherJson = getWeather();
|
||||
var w_temp;
|
||||
var w_icon;
|
||||
|
@ -190,7 +213,8 @@ function draw() {
|
|||
}
|
||||
|
||||
g.reset();
|
||||
g.clearRect(0, 30, w, h - 24);
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillRect(Bangle.appRect);
|
||||
|
||||
// draw a grid like graph paper
|
||||
if (settings.grid && process.env.HWVERSION !=1) {
|
||||
|
@ -249,6 +273,141 @@ function draw() {
|
|||
queueDraw();
|
||||
}
|
||||
|
||||
|
||||
///////////////// IDLE TIMER /////////////////////////////////////
|
||||
|
||||
function log_debug(o) {
|
||||
//print(o);
|
||||
}
|
||||
|
||||
function drawIdle() {
|
||||
let mins = Math.round((getTime() - lastStep) / 60);
|
||||
g.reset();
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillRect(Bangle.appRect);
|
||||
g.setColor(g.theme.fg);
|
||||
setSmallFont();
|
||||
g.setFontAlign(0, 0);
|
||||
g.drawString('Last step was', w/2, (h/3));
|
||||
g.drawString(mins + ' minutes ago', w/2, 20+(h/3));
|
||||
dismissBtn.draw();
|
||||
}
|
||||
|
||||
/////////////// BUTTON CLASS ///////////////////////////////////////////
|
||||
|
||||
// simple on screen button class
|
||||
function BUTTON(name,x,y,w,h,c,f,tx) {
|
||||
this.name = name;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
this.color = c;
|
||||
this.callback = f;
|
||||
this.text = tx;
|
||||
}
|
||||
|
||||
// if pressed the callback
|
||||
BUTTON.prototype.check = function(x,y) {
|
||||
//console.log(this.name + ":check() x=" + x + " y=" + y +"\n");
|
||||
|
||||
if (x>= this.x && x<= (this.x + this.w) && y>= this.y && y<= (this.y + this.h)) {
|
||||
log_debug(this.name + ":callback\n");
|
||||
this.callback();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
BUTTON.prototype.draw = function() {
|
||||
g.setColor(this.color);
|
||||
g.fillRect(this.x, this.y, this.x + this.w, this.y + this.h);
|
||||
g.setColor("#000"); // the icons and boxes are drawn black
|
||||
setSmallFont();
|
||||
g.setFontAlign(0, 0);
|
||||
g.drawString(this.text, (this.x + this.w/2), (this.y + this.h/2));
|
||||
g.drawRect(this.x, this.y, (this.x + this.w), (this.y + this.h));
|
||||
};
|
||||
|
||||
function dismissPrompt() {
|
||||
idle = false;
|
||||
warned = false;
|
||||
lastStep = getTime();
|
||||
Bangle.buzz(100);
|
||||
draw();
|
||||
}
|
||||
|
||||
var dismissBtn = new BUTTON("big",0, 3*h/4 ,w, h/4, "#0ff", dismissPrompt, "Dismiss");
|
||||
|
||||
Bangle.on('touch', function(button, xy) {
|
||||
if (idle && dismissBtn.check(xy.x, xy.y)) return;
|
||||
});
|
||||
|
||||
// if we get a step then we are not idle
|
||||
Bangle.on('step', s => {
|
||||
setLastStepTime();
|
||||
lastStep = getTime();
|
||||
// redraw if we had been idle
|
||||
if (idle == true) {
|
||||
dismissPrompt();
|
||||
}
|
||||
idle = false;
|
||||
warned = 0;
|
||||
});
|
||||
|
||||
function setLastStepTime() {
|
||||
var date = new Date();
|
||||
lastStepTime = require("locale").time(date,1);
|
||||
}
|
||||
|
||||
function checkIdle() {
|
||||
if (!settings.idle_check) {
|
||||
idle = false;
|
||||
warned = false;
|
||||
return;
|
||||
}
|
||||
|
||||
let hour = (new Date()).getHours();
|
||||
let active = (hour >= 9 && hour < 21);
|
||||
//let active = true;
|
||||
let dur = getTime() - lastStep;
|
||||
|
||||
if (active && dur > IDLE_MINUTES * 60) {
|
||||
drawIdle();
|
||||
if (warned++ < 3) {
|
||||
buzzer(warned);
|
||||
log_debug("checkIdle: warned=" + warned);
|
||||
Bangle.setLocked(false);
|
||||
}
|
||||
idle = true;
|
||||
} else {
|
||||
idle = false;
|
||||
warned = 0;
|
||||
}
|
||||
}
|
||||
|
||||
setLastStepTime();
|
||||
|
||||
// timeout for multi-buzzer
|
||||
var buzzTimeout;
|
||||
|
||||
// n buzzes
|
||||
function buzzer(n) {
|
||||
log_debug("buzzer n=" + n);
|
||||
|
||||
if (n-- < 1) return;
|
||||
Bangle.buzz(250);
|
||||
|
||||
if (buzzTimeout) clearTimeout(buzzTimeout);
|
||||
buzzTimeout = setTimeout(function() {
|
||||
buzzTimeout = undefined;
|
||||
buzzer(n);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
|
||||
|
@ -258,6 +417,7 @@ function queueDraw() {
|
|||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
prevInfo();
|
||||
checkIdle();
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
let s = {
|
||||
'grid': false,
|
||||
'weather': false,
|
||||
'idle_check': true,
|
||||
'font': "Lato"
|
||||
}
|
||||
|
||||
|
@ -37,18 +38,26 @@
|
|||
},
|
||||
},
|
||||
'Show Grid': {
|
||||
value: s.grid,
|
||||
format: () => (s.grid ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
s.grid = !s.grid;
|
||||
value: !!s.grid,
|
||||
format: v => v ? /*LANG*/"Yes":/*LANG*/"No",
|
||||
onchange: v => {
|
||||
s.grid = v;
|
||||
save();
|
||||
},
|
||||
},
|
||||
'Show Weather': {
|
||||
value: s.weather,
|
||||
format: () => (s.weather ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
s.weather = !s.weather;
|
||||
value: !!s.weather,
|
||||
format: v => v ? /*LANG*/"Yes":/*LANG*/"No",
|
||||
onchange: v => {
|
||||
s.weather = v;
|
||||
save();
|
||||
},
|
||||
},
|
||||
'Idle Warning': {
|
||||
value: !!s.idle_check,
|
||||
format: v => v ? /*LANG*/"Yes":/*LANG*/"No",
|
||||
onchange: v => {
|
||||
s.idle_check = v;
|
||||
save();
|
||||
},
|
||||
}
|
||||
|
|
|
@ -4,8 +4,11 @@
|
|||
</head>
|
||||
<body>
|
||||
<div id="tracks"></div>
|
||||
<div class="container" id="toastcontainer" stlye="position:fixed; bottom:8px; left:0px; right:0px; z-index: 100;"></div>
|
||||
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
<script src="../../core/js/ui.js"></script>
|
||||
<script src="../../core/js/utils.js"></script>
|
||||
<script>
|
||||
var domTracks = document.getElementById("tracks");
|
||||
|
||||
|
@ -70,9 +73,15 @@ ${track.map(pt=>` <gx:value>${0|pt.Skin}</gx:value>\n`).join("")}
|
|||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
}, 0);
|
||||
showToast("Download finished.", "success");
|
||||
}
|
||||
|
||||
function saveGPX(track, title) {
|
||||
if (!track || !track[0] || !"Time" in track[0] || !track[0].Time) {
|
||||
showToast("Error in trackfile.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
var gpx = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx creator="Bangle.js" version="1.1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3">
|
||||
<metadata>
|
||||
|
@ -109,6 +118,7 @@ function saveGPX(track, title) {
|
|||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
}, 0);
|
||||
showToast("Download finished.", "success");
|
||||
}
|
||||
|
||||
function saveCSV(track, title) {
|
||||
|
@ -121,6 +131,7 @@ function saveCSV(track, title) {
|
|||
}).join(",")+"\n";
|
||||
});
|
||||
Util.saveCSV(title, csv);
|
||||
showToast("Download finished.", "success");
|
||||
}
|
||||
|
||||
function trackLineToObject(headers, l) {
|
||||
|
@ -171,6 +182,10 @@ function getTrackList() {
|
|||
return {headers:headers,l:data};
|
||||
})(${JSON.stringify(filename)})`, trackInfo=>{
|
||||
console.log(filename," => ",trackInfo);
|
||||
if (!trackInfo || !"headers" in trackInfo) {
|
||||
showToast("Error loading track list.", "error");
|
||||
resolve();
|
||||
}
|
||||
trackInfo.headers = trackInfo.headers.split(",");
|
||||
trackList.push({
|
||||
filename : filename,
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
1.0: Initial version of game
|
||||
0.01: Initial version of game
|
||||
0.02: Fix mistake preventing game from ending in some cases.
|
||||
0.03: Update help screen with more details.
|
|
@ -9,7 +9,7 @@ For rules, see [here](https://asmadigames.com/Red7Rules.pdf).
|
|||
## Usage
|
||||
|
||||
Current rule card is shown in center of screen when viewing your hand.
|
||||
Swipe left to see your palettes and right on the palette screen to go back to your hand. Tap on a card to see it's details and then swipe either left or right to play the card as a rule or a palette card.
|
||||
Swipe left to see your palettes and right on the palette screen to go back to your hand. Tap on a card to see it's details and then swipe either left or right to play the card as a rule or a palette card. Taping anywhere besides the card will dismis the card details.
|
||||
Press the watch button to bring up the menu, which you can undo your card plays, end your turn, or start a new game.
|
||||
|
||||
## Creator
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "Red 7 Card Game",
|
||||
"shortName" : "Red 7",
|
||||
"icon": "icon.png",
|
||||
"version":"1.0",
|
||||
"version":"0.03",
|
||||
"description": "An implementation of the card game Red 7 for your watch. Play against the AI and be the last player still in the game to win!",
|
||||
"tags": "game",
|
||||
"supports":["BANGLEJS2"],
|
||||
|
|
|
@ -654,7 +654,52 @@ function drawScreen2() {
|
|||
}
|
||||
|
||||
function drawScreenHelp() {
|
||||
E.showAlert("Rules can be found on asmadigames.com").then(function(){drawMainMenu();});
|
||||
//E.showAlert("Rules can be found on asmadigames.com").then(function(){drawMainMenu();});
|
||||
E.showScroller({
|
||||
h: 25,
|
||||
c: 10,
|
||||
draw: (idx,r) => {
|
||||
g.setBgColor("#000").clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1);
|
||||
g.setColor("#fff");
|
||||
switch(idx) {
|
||||
case 0:
|
||||
g.setFont("6x8:2").drawString("Rules can be",r.x+10,r.y+4);
|
||||
break;
|
||||
case 1:
|
||||
g.setFont("6x8:2").drawString("found on",r.x+10,r.y+4);
|
||||
break;
|
||||
case 2:
|
||||
g.setFont("Vector:18").drawString("asmadigames.com",r.x+10,r.y+4);
|
||||
break;
|
||||
case 3:
|
||||
g.setFont("6x8:1").drawString("Use button to show menu.",r.x+10,r.y+4);
|
||||
break;
|
||||
case 4:
|
||||
g.setFont("6x8:1").drawString("Swipe L/R for hand/palette.",r.x+10,r.y+4);
|
||||
break;
|
||||
case 5:
|
||||
g.setFont("6x8:1").drawString("Tap card to see details.",r.x+10,r.y+4);
|
||||
break;
|
||||
case 6:
|
||||
g.setFont("6x8:1").drawString("Swipe card L/R to play.",r.x+10,r.y+4);
|
||||
break;
|
||||
case 7:
|
||||
g.setFont("6x8:1").drawString("Finish turn in menu.",r.x+10,r.y+4);
|
||||
break;
|
||||
case 9:
|
||||
g.fillRect(r.x+40,r.y+0,r.x+140,r.y+20);
|
||||
g.setColor(0,0,0);
|
||||
g.setFont("Vector:14").drawString("OK",r.x+80,r.y+4);
|
||||
break;
|
||||
}
|
||||
},
|
||||
select: (idx) => {
|
||||
if(idx === 9){
|
||||
E.showScroller();
|
||||
drawMainMenu();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function drawGameOver(win) {
|
||||
|
@ -678,9 +723,7 @@ function finishTurn() {
|
|||
} else if(playerHand.handCards.length === 0) {
|
||||
drawGameOver(false);
|
||||
} else if(!canPlay(playerHand, playerPalette, AIPalette)) {
|
||||
console.log("no play");
|
||||
//drawGameOver(false);
|
||||
drawScreen1();
|
||||
drawGameOver(false);
|
||||
} else {
|
||||
E.showMenu();
|
||||
drawScreen1();
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
0.01: Initial Release
|
||||
0.02: Minor tweaks for light theme
|
||||
0.03: Made images 2 bit and fixed theme honoring
|
|
@ -0,0 +1,12 @@
|
|||
# Rolex
|
||||
|
||||

|
||||
|
||||
Created with the aid of the Espruino documentation and looking through many of the wonderful exising watchfaces that have been made.
|
||||
This has not been tested on a watch yet as I haven't aquired one but has been tested in the emulator.
|
||||
The hands don't rotate dead on center but they're as close as I could get them to.
|
||||
|
||||
Special thanks to:
|
||||
* rozek (for his updated widget draw code for utilization with background images)
|
||||
* Gordon Williams (Bangle.js, watchapps for reference code and documentation)
|
||||
* The community (for helping drive such a wonderful project)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkEBxURiIlUgMxiUQC6cCiMhmAqPmQEDkUhF4cjGhUD/4FDn/zAof/GhYMEC4kCEQgAHl/xGoYcDj/yC5ZIFJgnwPBglIn6rNBpEBXp8QiQSBiMQFpMCS4sDXgMjgMhgYXFEgIDBh//kA/EiEhiURiMBBYs/FYSwB+YdBCQIBBkAYBiUQkACBCwTOEUYKaBkUhAAIXCDYMRkYxBIILNDAAMTHgZxBiBFBFQKOCgMvbRUBgUxIYJ3BSYUBmYJBU5QsCkIDBgQIBkcyYJwAFkczeBoAGiYWVgYWTbQMCmchfojgBDxc/+f/mUjC4abBkEf/4ABDY0C+cvmQKFgcxkTyBC47pBC4LgDAAUPmMyh4IEiUQiUyiJHBIwJ9GmMxC4kBmXyGYcBdQMykIPEcIIlBFgMikMzIAxiBkSPEIYqSKmX/mLWTgEimRiGAB0T+bwTVocCMBEAj51GAA4aGif/+AXNh//FAcC//ziEBgEhiCxBiADCXAIDBCIYdFgLCBaIMCkKNBkQIBkQTBgZBDgRdEiIsBGoMBAoLoDLQRxIkMhewMigMyiESiRrNWqpMB+QJHl4hMh/zBI//a5IlDYQcBFQcf+IWKgLJEj4cDgY5IBgf/AoYXEEQp2HHggXEKQIXKAAoXFACIXkA"))
|
|
@ -0,0 +1,144 @@
|
|||
var imgBg = {
|
||||
width : 176, height : 176, bpp : 1,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("ABMBwAVpmfMCqdxxwVpmOMCiUDmOIoAVRg8xzHwCqUZCqcDCoPAYBwDDzOZYx0DB4QVGF5UPComRAoZbKn4hD7uzGof4CpN/8AVI/wVO5+3T4YVKn5NDt/2Cqd/CAcP/gVJj5jDCogJECpc/EwYVLG4gVEJYgAGMYgVVLgqjDAA0D/5GDRAgVPg4VD/77DAA0B/+ABBwAEEQ4VVJQgAIMg4VVUQgAIn4VUj5kGgbfDABEeBA7fDABEMBA84CpYAIiAVUAHVAh4USgf4j+HVgPg4AVOxlw44DBuLMGcgQVFg1gs0DgE7JowVCGognB4AVB0DiFgPgAYMPAYRXD8AVB+EOHooVDfQkDjBBDj4VEh/wAYMf/wVEhkw40Tg0zNok/Cof/BQcDxyZB+f8sJjE/49Cn//Uh0B//8FYYwCwEgAYMQVQ4VDh4ECgfSufOvF548c8+CuQmGgf//4eBv3BxFMtuN23DmVzCAN//6rDCoPAg1suEMsAVBmgVBrYPDPwZuCCoMZhhBBx84CoPyNgSqETQUDpnZhFIsODmGDiX7KIRsCOYeAgOAuDsBgKhB8UMwPAFYJsCT4avNX41/Cpz2EgEeAYU+jl8vwHBnl8BQV+DgpSBAAMjmkQoEDsVmnAKCvA2JkcmCocweocwCpMhm0QvECsccCoYAKkd+FYNCscGCp0ysQVBs0iAgIVNn1DPYM8AAIVOhACBnkYhEYChoA/AH4A/AH4A/AC1///+CtH/AAIVVAAIuQCqkBCv4V/CozERCqrxZCtF/QCAA/AH4A/AH4A/AH4ArhgONkAGFnwVNuAGFvwVN/gFEgP/CpoOFg4VNEgOAAwcfAwoVJ8AGDn//4AVLgf//BHEFZoVB/wFIIJZnDh//RQMYhGICxN/KIZWB+EAmEfzsN4Ew8PMw3JnYMBPoIDBNgk6u8XjmA8OT2XJ/8zvg8ECoRsBh1/jscwdhj/ztO7iV4NAQVCj5sCh1Tzseydhy/jnO3icwSgSaCh4DCnOBwviwcw+Msm0BidINwRXCh4DCABsfCIUHbJgADg7yCg4IDhlmjEgAgPssOGsDHDCoQAEiXMs9yt8R5VrzmwBoY9HCoNjuVGiHOpOcyBKLiWMsd6s0R51LjgVMjkIjEPukM5UIjiQMhkAjEDmEMMoMGNoYA0jAVUjgIHuAVLn4IH/AVLv7OGgf8Cpj6Gg4VM/4rGg/+CsEB/+AK43/CpQMIDwIVVGgxONMA4VIj/wCp0PUwYVEXA4VZj7+DCok/AgYAGn4VID4gVHCAgVPJogVEMIgVRXJClGCojPJFYQVIgYVKj79DCoptKh4VIgKvKgYwECohLDABYVEACAV/Cv4V7"))
|
||||
};
|
||||
|
||||
/* Set hour hand image */
|
||||
|
||||
var imgHour = {
|
||||
width : 19, height : 62, bpp : 2,
|
||||
transparent : 0,
|
||||
buffer : E.toArrayBuffer(atob("AAP/wAAA///wAA////AA////8A/////A/////8P/////D/////w/////8P/////D/////w/////8D////8AP////AA////AAD///AAAP//AAAA//AAAAPXwAAAD18AAAA9fAAAAPXwAAAD18AAAA9fAAAAPXwAAAD18AAAA9fAAAAPXwAAAD18AAAA//AAAA//wAAA///wAD/X1/AD/V9X8A/VfVfw/VX1V8PVV9VXD1VfVV89VX1VfPVf/1Xz1f//V89/9f/fP/1Vf/A/VVVfwP1VVXwA/VVX8AD/VfwAAP//wAAA//wAAAD18AAAA9fAAAAPXAAAAD1wAAAAN8AAAAD8AAAAA/AAAAAPwAAAAA8AAAAAMAAAAADAAAAAAwAAAAAAAAAA=="))
|
||||
};
|
||||
|
||||
/* Set minute hand image */
|
||||
|
||||
var imgMin = {
|
||||
width : 10, height : 80, bpp : 2,
|
||||
transparent : 0,
|
||||
buffer : E.toArrayBuffer(atob("AAAADwAP/wP//D//z/////////8//8P//A//AD/AA/wAP8AD/AA/8A/8AP/wD/8A//AP/wD/8A9fAPXwD18A9fAPXwD18A9fAPXwD18A9fAPXwD18A9fAPXwD18A9fAPXwD18A9fAPXwD18A9fAPXwD18A9fAPXwD18A9fAPXwD18A9fAPXwD18A9fAPXwD18A9fAPXwD18A9fAPXwD18A//AP/wA/wAP8AD/AA/wAP8AA/AAPAADwAA8AAPAADwAAMAAAAAAAA="))
|
||||
};
|
||||
|
||||
/* Set second hand image */
|
||||
|
||||
var imgSec = {
|
||||
width : 8, height : 116, bpp : 2,
|
||||
transparent : 2,
|
||||
buffer : E.toArrayBuffer(atob("v/q//r/+v/qv+q/qq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qv+v/6/D/wD8PDw8PwD/w///6v+qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqr+r//v///X/1X/Vf9V/1X/1///+//q/qq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq6qrqg=="))
|
||||
};
|
||||
|
||||
/* Set variables to get screen width, height and center points */
|
||||
|
||||
let W = g.getWidth();
|
||||
let H = g.getHeight();
|
||||
let cx = W/2;
|
||||
let cy = H/2;
|
||||
let Timeout;
|
||||
|
||||
/* set font */
|
||||
|
||||
require("Font4x5Numeric").add(Graphics);
|
||||
|
||||
Bangle.loadWidgets();
|
||||
|
||||
/* Custom version of Bangle.drawWidgets (does not clear the widget areas) Thanks to rozek */
|
||||
|
||||
Bangle.drawWidgets = function () {
|
||||
var w = g.getWidth(), h = g.getHeight();
|
||||
|
||||
var pos = {
|
||||
tl:{x:0, y:0, r:0, c:0}, // if r==1, we're right->left
|
||||
tr:{x:w-1, y:0, r:1, c:0},
|
||||
bl:{x:0, y:h-24, r:0, c:0},
|
||||
br:{x:w-1, y:h-24, r:1, c:0}
|
||||
};
|
||||
|
||||
if (global.WIDGETS) {
|
||||
for (var wd of WIDGETS) {
|
||||
var p = pos[wd.area];
|
||||
if (!p) continue;
|
||||
|
||||
wd.x = p.x - p.r*wd.width;
|
||||
wd.y = p.y;
|
||||
|
||||
p.x += wd.width*(1-2*p.r);
|
||||
p.c++;
|
||||
}
|
||||
|
||||
g.reset(); // also loads the current theme
|
||||
|
||||
try {
|
||||
for (var wd of WIDGETS) {
|
||||
g.setClipRect(wd.x,wd.y, wd.x+wd.width-1,23);
|
||||
wd.draw(wd);
|
||||
}
|
||||
} catch (e) { print(e); }
|
||||
|
||||
g.reset(); // clears the clipping rectangle!
|
||||
}
|
||||
};
|
||||
|
||||
/* Draws the clock hands and date */
|
||||
|
||||
function drawHands() {
|
||||
let d = new Date();
|
||||
|
||||
let hour = d.getHours() % 12;
|
||||
let min = d.getMinutes();
|
||||
let sec = d.getSeconds();
|
||||
|
||||
let twoPi = 2*Math.PI;
|
||||
let Pi = Math.PI;
|
||||
let halfPi = Math.PI/2;
|
||||
|
||||
let hourAngle = (hour+(min/60))/12 * twoPi - Pi;
|
||||
let minAngle = (min/60) * twoPi - Pi;
|
||||
let secAngle = (sec/60) * twoPi - Pi;
|
||||
|
||||
let hourSin = Math.sin(hourAngle);
|
||||
let hourCos = Math.cos(hourAngle);
|
||||
let minSin = Math.sin(minAngle);
|
||||
let minCos = Math.cos(minAngle);
|
||||
let secSin = Math.sin(secAngle);
|
||||
let secCos = Math.cos(secAngle);
|
||||
|
||||
g.drawImage(imgHour,cx-22*hourSin,cy+22*hourCos,{rotate:hourAngle});
|
||||
g.drawImage(imgMin,cx-34*minSin,cy+34*minCos,{rotate:minAngle});
|
||||
g.drawImage(imgSec,cx-25*secSin,cy+25*secCos,{rotate:secAngle});
|
||||
g.setFont("4x5Numeric:3");
|
||||
g.setColor(g.theme.bg);
|
||||
g.drawString(d.getDate(),157,81);
|
||||
}
|
||||
|
||||
function drawBackground() {
|
||||
g.clear(1);
|
||||
g.setBgColor(g.theme.bg);
|
||||
g.setColor(g.theme.fg);
|
||||
g.drawImage(imgBg,0,0);
|
||||
g.reset();
|
||||
}
|
||||
|
||||
/* Refresh the display every second */
|
||||
|
||||
function displayRefresh() {
|
||||
g.clear(true);
|
||||
drawBackground();
|
||||
drawHands();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
let Pause = 1000 - (Date.now() % 1000);
|
||||
Timeout = setTimeout(displayRefresh,Pause);
|
||||
}
|
||||
|
||||
Bangle.on('lcdPower', (on) => {
|
||||
if (on) {
|
||||
if (Timeout != null) { clearTimeout(Timeout); Timeout = undefined;}
|
||||
displayRefresh();
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.setUI("clock");
|
||||
// load widgets after 'setUI' so they're aware there is a clock active
|
||||
Bangle.loadWidgets();
|
||||
displayRefresh();
|
|
@ -0,0 +1,17 @@
|
|||
{ "id": "rolex",
|
||||
"name": "rolex",
|
||||
"shortName":"rolex",
|
||||
"icon": "rolex.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"version":"0.03",
|
||||
"description": "A rolex like watch face",
|
||||
"tags": "clock",
|
||||
"type": "clock",
|
||||
"supports":["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"rolex.app.js","url":"app.js"},
|
||||
{"name":"rolex.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 3.6 KiB |
|
@ -1,3 +1,4 @@
|
|||
0.01: Hello Ruuvi Watch!
|
||||
0.02: Clear gfx on startup.
|
||||
0.03: Improve design and code, reduce flicker.
|
||||
0.04: Ability to rename tags. Sauna, Fridge & Freezer alert. Support °F based on locale.
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
Watch the status of [RuuviTags](https://ruuvi.com) in range.
|
||||
|
||||
By Marc Englund [GitHub](https://github.com/emarc) | [Twitter](https://twitter.com/marcenglund)
|
||||
|
||||

|
||||
|
||||
- Id
|
||||
|
@ -9,18 +11,23 @@ Watch the status of [RuuviTags](https://ruuvi.com) in range.
|
|||
- Humidity (%)
|
||||
- Pressure (hPa)
|
||||
- Battery voltage
|
||||
|
||||
Also shows how "fresh" the data is (age of reading).
|
||||
- Reading "freshness" (age)
|
||||
- Ability to name tags
|
||||
- Alerts for Sauna, Fridge, Freezer
|
||||
|
||||
## Usage
|
||||
|
||||
- Scans for devices when launched and every N seconds.
|
||||
- Page trough devices with BTN1/BTN3.
|
||||
- Trigger scan with BTN2.
|
||||
- Page trough devices with left/right swipe or BTN1/BTN3.
|
||||
- Page past last/first to trigger scan.
|
||||
- BTN2 = Menu; name tag & trigger scan
|
||||
- Change locale (via App Loader) to get Farenheit.
|
||||
|
||||
## Todo / ideas
|
||||
|
||||
- Settings for scan frequency, units
|
||||
- Allow to "name" known devices
|
||||
- Include more data
|
||||
- Bangle 2 support (I don't have one, let me know if you want to help with testing!)
|
||||
- Settings for scan frequency
|
||||
- Settings for alert limits
|
||||
- Alert for "Wine cellar"
|
||||
- Alert for Washer & Dryer (stops shaking = ready)
|
||||
- Support older Ruuvi protocols
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "Ruuvi Watch",
|
||||
"shortName":"Ruuvi Watch",
|
||||
"icon": "ruuviwatch.png",
|
||||
"version":"0.03",
|
||||
"version":"0.04",
|
||||
"description": "Keep an eye on RuuviTag devices (https://ruuvi.com). For RuuviTags using the v5 format.",
|
||||
"readme":"README.md",
|
||||
"tags": "bluetooth",
|
||||
|
|
|
@ -7,11 +7,21 @@ require("Storage").write("ruuviwatch.info", {
|
|||
|
||||
const lookup = {};
|
||||
const ruuvis = [];
|
||||
const names = require("Storage").readJSON("RuuviNames") || {};
|
||||
let current = 0;
|
||||
let scanning = false;
|
||||
|
||||
let paused = false;
|
||||
|
||||
const SCAN_FREQ = 1000 * 30;
|
||||
|
||||
// ALERT LIMITS
|
||||
LIMIT_SAUNA = 60;
|
||||
LIMIT_FRIDGE = 4;
|
||||
LIMIT_FREEZER = -18;
|
||||
// TODO add wine cellar limits
|
||||
// TODO configurable limits
|
||||
|
||||
// Fonts
|
||||
const FONT_L = "Vector:60";
|
||||
const FONT_M = "Vector:20";
|
||||
|
@ -80,8 +90,8 @@ function p(data) {
|
|||
int2Hex(data[OFFSET + 23]),
|
||||
].join(":");
|
||||
|
||||
robject.name =
|
||||
"Ruuvi " + int2Hex(data[OFFSET + 22]) + int2Hex(data[OFFSET + 23]);
|
||||
robject.id = int2Hex(data[OFFSET + 22]) + int2Hex(data[OFFSET + 23]);
|
||||
|
||||
return robject;
|
||||
}
|
||||
|
||||
|
@ -114,6 +124,7 @@ function drawAge() {
|
|||
}
|
||||
|
||||
function redrawAge() {
|
||||
if (paused) return;
|
||||
const originalColor = g.getColor();
|
||||
g.clearRect(0, SCANNING_Y - 10, g.getWidth(), SCANNING_Y + 10);
|
||||
g.setFont(FONT_S);
|
||||
|
@ -128,9 +139,15 @@ function redrawAge() {
|
|||
g.setColor(originalColor);
|
||||
}
|
||||
|
||||
function getName(id) {
|
||||
let name = names[id] || "Ruuvi";
|
||||
return name + " (" + id + ")";
|
||||
}
|
||||
|
||||
function redraw() {
|
||||
g.clear();
|
||||
g.setColor("#ffffff");
|
||||
g.setFontAlign(0, 0);
|
||||
|
||||
if (ruuvis.length > 0 && ruuvis[current]) {
|
||||
const ruuvi = ruuvis[current];
|
||||
|
@ -145,14 +162,22 @@ function redraw() {
|
|||
|
||||
// name
|
||||
g.setFont(FONT_M);
|
||||
g.drawString(ruuvi.name, CENTER, NAME_Y);
|
||||
g.drawString(getName(ruuvi.id), CENTER, NAME_Y);
|
||||
|
||||
// age
|
||||
redrawAge();
|
||||
|
||||
// temp
|
||||
g.setFont(FONT_L);
|
||||
g.drawString(ruuvi.temperature.toFixed(2) + "°c", CENTER, TEMP_Y);
|
||||
if (
|
||||
(ruuvi.name.startsWith("Sauna") && ruuvi.temperature > LIMIT_SAUNA) ||
|
||||
(ruuvi.name.startsWith("Fridge") && ruuvi.temperature > LIMIT_FRIDGE) ||
|
||||
(ruuvi.name.startsWith("Freezer") && ruuvi.temperature > LIMIT_FREEZER)
|
||||
) {
|
||||
g.setColor("#ffe800");
|
||||
}
|
||||
g.drawString(getTempString(ruuvi.temperature), CENTER, TEMP_Y);
|
||||
g.setColor("#ffffff");
|
||||
|
||||
// humid & pressure
|
||||
g.setFont(FONT_M);
|
||||
|
@ -175,8 +200,28 @@ function redraw() {
|
|||
}
|
||||
}
|
||||
|
||||
function getTempString(temp) {
|
||||
// workaround: built-in 'locale' looses precision :-(
|
||||
let unit = "°C";
|
||||
const isF = require("locale").temp(1).endsWith("F");
|
||||
if (isF) {
|
||||
unit = "°F";
|
||||
temp = (temp + 40) * 1.8 - 40;
|
||||
}
|
||||
return temp.toFixed(2) + unit;
|
||||
}
|
||||
|
||||
function attention(message) {
|
||||
// message ignored for now
|
||||
Bangle.beep();
|
||||
Bangle.beep();
|
||||
Bangle.beep();
|
||||
Bangle.buzz();
|
||||
}
|
||||
|
||||
function scan() {
|
||||
if (scanning) return;
|
||||
if (paused) return;
|
||||
scanning = true;
|
||||
NRF.findDevices(
|
||||
function (devices) {
|
||||
|
@ -184,11 +229,36 @@ function scan() {
|
|||
devices.forEach((device) => {
|
||||
const data = p(device.data);
|
||||
data.time = new Date().getTime();
|
||||
const idx = lookup[data.name];
|
||||
data.name = names[data.id] || "Ruuvi";
|
||||
|
||||
const idx = lookup[data.id];
|
||||
if (idx !== undefined) {
|
||||
const old = ruuvis[idx];
|
||||
if (
|
||||
data.name.startsWith("Sauna") &&
|
||||
old.temperature < LIMIT_SAUNA &&
|
||||
data.temperature > LIMIT_SAUNA
|
||||
) {
|
||||
current = idx;
|
||||
attention(data.name + " ready!");
|
||||
} else if (
|
||||
data.name.startsWith("Fridge") &&
|
||||
old.temperature < LIMIT_FRIDGE &&
|
||||
data.temperature > LIMIT_FRIDGE
|
||||
) {
|
||||
current = idx;
|
||||
attention(data.name + " warning!");
|
||||
} else if (
|
||||
data.name.startsWith("Freezer") &&
|
||||
old.temperature < LIMIT_FREEZER &&
|
||||
data.temperature > LIMIT_FREEZER
|
||||
) {
|
||||
current = idx;
|
||||
attention(data.name + " warning!");
|
||||
}
|
||||
ruuvis[idx] = data;
|
||||
} else {
|
||||
lookup[data.name] = ruuvis.push(data) - 1;
|
||||
lookup[data.id] = ruuvis.push(data) - 1;
|
||||
foundNew = true;
|
||||
}
|
||||
});
|
||||
|
@ -202,23 +272,195 @@ function scan() {
|
|||
);
|
||||
}
|
||||
|
||||
function setName(newName) {
|
||||
const ruuvi = ruuvis[current];
|
||||
ruuvi.name = newName;
|
||||
names[ruuvi.id] = ruuvi.name;
|
||||
require("Storage").writeJSON("RuuviNames", names);
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
E.showMenu();
|
||||
paused = false;
|
||||
redraw();
|
||||
}
|
||||
|
||||
function showMenu() {
|
||||
// TODO make this DRY + indicate current in menu
|
||||
if (!ruuvis.length) {
|
||||
scan();
|
||||
return;
|
||||
}
|
||||
paused = true;
|
||||
const ruuvi = ruuvis[current];
|
||||
const id = ruuvi.id;
|
||||
const name = getName(id);
|
||||
|
||||
var mainmenu = {
|
||||
"": { title: name },
|
||||
"Scan now": function () {
|
||||
closeMenu();
|
||||
scan();
|
||||
},
|
||||
"Rename tag": function () {
|
||||
E.showMenu(namemenu);
|
||||
},
|
||||
"< Back": function () {
|
||||
closeMenu();
|
||||
}, // remove the menu
|
||||
};
|
||||
// Submenu
|
||||
var namemenu = {
|
||||
"": { title: "Rename " + name },
|
||||
Ruuvi: function () {
|
||||
setName("Ruuvi");
|
||||
closeMenu();
|
||||
},
|
||||
Indoors: function () {
|
||||
setName("Indoors");
|
||||
closeMenu();
|
||||
},
|
||||
Downstairs: function () {
|
||||
setName("Downstairs");
|
||||
closeMenu();
|
||||
},
|
||||
Upstairs: function () {
|
||||
setName("Upstairs");
|
||||
closeMenu();
|
||||
},
|
||||
Attic: function () {
|
||||
setName("Attic");
|
||||
closeMenu();
|
||||
},
|
||||
Basement: function () {
|
||||
setName("Basement");
|
||||
closeMenu();
|
||||
},
|
||||
Kitchen: function () {
|
||||
setName("Kitchen");
|
||||
closeMenu();
|
||||
},
|
||||
Pantry: function () {
|
||||
setName("Pantry");
|
||||
closeMenu();
|
||||
},
|
||||
"Living room": function () {
|
||||
setName("Living room");
|
||||
closeMenu();
|
||||
},
|
||||
"Dining room": function () {
|
||||
setName("Dining room");
|
||||
closeMenu();
|
||||
},
|
||||
Office: function () {
|
||||
setName("Office");
|
||||
closeMenu();
|
||||
},
|
||||
Bedroom: function () {
|
||||
setName("Bedroom");
|
||||
closeMenu();
|
||||
},
|
||||
Bathroom: function () {
|
||||
setName("Bathroom");
|
||||
closeMenu();
|
||||
},
|
||||
Sauna: function () {
|
||||
setName("Sauna");
|
||||
closeMenu();
|
||||
},
|
||||
"Wine cellar": function () {
|
||||
setName("Wine cellar");
|
||||
closeMenu();
|
||||
},
|
||||
Outdoors: function () {
|
||||
setName("Outdoors");
|
||||
closeMenu();
|
||||
},
|
||||
Porch: function () {
|
||||
setName("Porch");
|
||||
closeMenu();
|
||||
},
|
||||
Backyard: function () {
|
||||
setName("Backyard");
|
||||
closeMenu();
|
||||
},
|
||||
Garage: function () {
|
||||
setName("Garage");
|
||||
closeMenu();
|
||||
},
|
||||
Greenhouse: function () {
|
||||
setName("Greenhouse");
|
||||
closeMenu();
|
||||
},
|
||||
Shed: function () {
|
||||
setName("Shed");
|
||||
closeMenu();
|
||||
},
|
||||
Fridge: function () {
|
||||
setName("Fridge");
|
||||
closeMenu();
|
||||
},
|
||||
Freezer: function () {
|
||||
setName("Freezer");
|
||||
closeMenu();
|
||||
},
|
||||
Dryer: function () {
|
||||
setName("Dryer");
|
||||
closeMenu();
|
||||
},
|
||||
Washer: function () {
|
||||
setName("Washer");
|
||||
closeMenu();
|
||||
},
|
||||
"< Back": function () {
|
||||
E.showMenu(mainmenu);
|
||||
},
|
||||
};
|
||||
// Actually display the menu
|
||||
E.showMenu(mainmenu);
|
||||
}
|
||||
|
||||
function nextPage() {
|
||||
current++;
|
||||
if (current >= ruuvis.length) {
|
||||
current = 0;
|
||||
scan();
|
||||
}
|
||||
redraw();
|
||||
}
|
||||
|
||||
function prevPage() {
|
||||
current--;
|
||||
if (current < 0) {
|
||||
current = ruuvis.length - 1;
|
||||
scan();
|
||||
}
|
||||
redraw();
|
||||
}
|
||||
|
||||
// START
|
||||
Bangle.on("swipe", function (dir) {
|
||||
if (paused) return;
|
||||
if (dir > 0) {
|
||||
prevPage();
|
||||
} else {
|
||||
nextPage();
|
||||
}
|
||||
});
|
||||
// Button 1 pages up
|
||||
setWatch(
|
||||
() => {
|
||||
current--;
|
||||
if (current < 0) {
|
||||
current = ruuvis.length - 1;
|
||||
}
|
||||
redraw();
|
||||
if (paused) return;
|
||||
prevPage();
|
||||
},
|
||||
BTN1,
|
||||
{ repeat: true }
|
||||
);
|
||||
// button triggers scan
|
||||
// button triggers menu
|
||||
setWatch(
|
||||
() => {
|
||||
scan();
|
||||
if (paused) return;
|
||||
showMenu();
|
||||
},
|
||||
BTN2,
|
||||
{ repeat: true }
|
||||
|
@ -226,11 +468,8 @@ setWatch(
|
|||
// button 3 pages down
|
||||
setWatch(
|
||||
() => {
|
||||
current++;
|
||||
if (current >= ruuvis.length) {
|
||||
current = 0;
|
||||
}
|
||||
redraw();
|
||||
if (paused) return;
|
||||
nextPage();
|
||||
},
|
||||
BTN3,
|
||||
{ repeat: true }
|
||||
|
|
|
@ -44,3 +44,4 @@
|
|||
0.39: Fix misbehaving debug info option
|
||||
0.40: Moved off into Utils, put System after Apps
|
||||
0.41: Stop users disabling all wake-up methods and locking themselves out (fix #1272)
|
||||
0.42: Fix theme customizer on new Bangle 2 firmware
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "setting",
|
||||
"name": "Settings",
|
||||
"version": "0.41",
|
||||
"version": "0.42",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"icon": "settings.png",
|
||||
"tags": "tool,system",
|
||||
|
|
|
@ -243,12 +243,11 @@ function showThemeMenu() {
|
|||
});
|
||||
|
||||
function showCustomThemeMenu() {
|
||||
function cv(x) { return g.setColor(x).getColor(); }
|
||||
function setT(t, v) {
|
||||
let th = g.theme;
|
||||
th[t] = v;
|
||||
if (t==="bg") {
|
||||
th['dark'] = (v===cv("#000"));
|
||||
th['dark'] = (v===cl("#000"));
|
||||
}
|
||||
upd(th);
|
||||
}
|
||||
|
@ -260,11 +259,7 @@ function showThemeMenu() {
|
|||
let colors = [], names = [];
|
||||
for(const c in rgb) {
|
||||
names.push(c);
|
||||
colors.push(cv(rgb[c]));
|
||||
}
|
||||
function cn(v) {
|
||||
const i = colors.indexOf(v);
|
||||
return i!== -1 ? names[i] : v; // another color: just show value
|
||||
colors.push(cl(rgb[c]));
|
||||
}
|
||||
let menu = {
|
||||
'':{title:'Custom Theme'},
|
||||
|
@ -277,14 +272,11 @@ function showThemeMenu() {
|
|||
};
|
||||
["fg", "bg", "fg2", "bg2", "fgH", "bgH"].forEach(t => {
|
||||
menu[labels[t]] = {
|
||||
value: colors.indexOf(g.theme[t]),
|
||||
format: () => cn(g.theme[t]),
|
||||
min : 0, max : colors.length-1, wrap : true,
|
||||
value: Math.max(colors.indexOf(g.theme[t]),0),
|
||||
format: v => names[v],
|
||||
onchange: function(v) {
|
||||
// wrap around
|
||||
if (v>=colors.length) {v = 0;}
|
||||
if (v<0) {v = colors.length-1;}
|
||||
this.value = v;
|
||||
const c = colors[v];
|
||||
var c = colors[v];
|
||||
// if we select the same fg and bg: set the other to the old color
|
||||
// e.g. bg=black;fg=white, user selects fg=black -> bg changes to white automatically
|
||||
// so users don't end up with a black-on-black menu
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
"name": "Simplest Clock",
|
||||
"version": "0.06",
|
||||
"description": "The simplest working clock, acts as a tutorial piece",
|
||||
"readme": "README.md",
|
||||
"icon": "simplest.png",
|
||||
"screenshots": [{"url":"screenshot_simplest.png"}],
|
||||
"type": "clock",
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Fix crash on start
|
||||
0.02: Fix crash on start #1423
|
||||
0.03: Added power saving mode, move all read/write log actions into lib/module
|
||||
0.04: Fix #1445, display loading info, add icons to display service states
|
||||
|
|
|
@ -2,20 +2,26 @@
|
|||
|
||||
This app logs and displays the four following states:
|
||||
_unknown, not worn, awake, sleeping_
|
||||
It derived from the [SleepPhaseAlarm](https://banglejs.com/apps/#sleepphasealarm) and uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments ([ESS](https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en)) and the internal temperature to decide _sleeping_ or _not worn_ when the watch is resting.
|
||||
It derived from the [SleepPhaseAlarm](https://banglejs.com/apps/#sleepphasealarm) and uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments ([ESS](https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en)) and
|
||||
also provides a power saving mode using the built in movement calculation. The internal temperature is used to decide if the status is _sleeping_ or _not worn_.
|
||||
|
||||
#### Operating Principle
|
||||
* __ESS calculation__
|
||||
The accelerometer polls values with 12.5Hz. On each poll the magnitude value is saved. When 13 values are collected, every 1.04 seconds, the standard deviation over this values is calculated.
|
||||
Is the calculated standard deviation lower than the "no movement" threshold (__NoMoThresh__) a "no movement" counter is incremented. Each time the "no movement" threshold is reached the "no movement" counter will be reset.
|
||||
When the "no movement" counter reaches the sleep threshold the watch is considered as resting. (The sleep threshold is calculated from the __MinDuration__ setting, Example: _sleep threshold = MinDuration * 60 / calculation interval => 10min * 60s/min / 1.04s ~= 576,9 rounded up to 577_)
|
||||
To check if a resting watch indicates as sleeping, the internal temperature must be greater than the temperature threshold (__TempThresh__). Otherwise the watch is considered as not worn.
|
||||
Is the calculated standard deviation lower than the "no movement" threshold (__NoMo Thresh__) a "no movement" counter is incremented. Each time the "no movement" threshold is reached the "no movement" counter will be reset. The first time no movement is detected the actual timestamp is cached (in _sleeplog.firstnomodate_) for logging.
|
||||
When the "no movement" counter reaches the sleep threshold the watch is considered as resting. (The sleep threshold is calculated from the __Min Duration__ setting, Example: _sleep threshold = Min Duration * 60 / calculation interval => 10min * 60s/min / 1.04s ~= 576,9 rounded up to 577_)
|
||||
* __Power Saving Mode__
|
||||
On power saving mode the movement value of bangle's build in health event is checked against the maximal movement threshold (__Max Move__). The event is only triggered every 10 minutes which decreases the battery impact but also reduces accurracy.
|
||||
* ___Sleeping___ __or__ ___Not Worn___
|
||||
To check if a resting watch indicates a sleeping status, the internal temperature must be greater than the temperature threshold (__Temp Thresh__). Otherwise the watch is considered as not worn.
|
||||
* __True Sleep__
|
||||
The true sleep value is a simple addition of all registert sleeping periods.
|
||||
* __Consecutive Sleep__
|
||||
In addition the consecutive sleep value tries to predict the complete time you were asleep, even the light sleeping phases with registered movements. All periods after a sleeping period will be summarized til the first following non sleeping period that is longer then the maximal awake duration (__MaxAwake__). If this sum is lower than the minimal consecutive sleep duration (__MinConsec__) it is not considered, otherwise it will be added to the consecutive sleep value.
|
||||
In addition the consecutive sleep value tries to predict the complete time you were asleep, even the light sleeping phases with registered movements. All periods after a sleeping period will be summarized til the first following non sleeping period that is longer then the maximal awake duration (__Max Awake__). If this sum is lower than the minimal consecutive sleep duration (__Min Consec__) it is not considered, otherwise it will be added to the consecutive sleep value.
|
||||
* __Logging__
|
||||
To minimize the log size only a changed state is logged.
|
||||
To minimize the log size only a changed state is logged. The logged timestamp is matching the beginning of its measurement period.
|
||||
When not on power saving mode a movement is detected nearly instantaneous and the detection of a no movement period is delayed by the minimal no movement duration. To match the beginning of the measurement period a cached timestamp (_sleeplog.firstnomodate_) is logged.
|
||||
On power saving mode the measurement period is fixed to 10 minutes and all logged timestamps are also set back 10 minutes.
|
||||
|
||||
---
|
||||
### Control
|
||||
|
@ -28,32 +34,44 @@ It derived from the [SleepPhaseAlarm](https://banglejs.com/apps/#sleepphasealarm
|
|||
---
|
||||
### Settings
|
||||
---
|
||||
* __BreakTod__ break at time of day
|
||||
* __Break Tod__ | break at time of day
|
||||
_0_ / _1_ / _..._ / __10__ / _..._ / _12_
|
||||
Change time of day on wich the lower graph starts and the upper graph ends.
|
||||
* __MaxAwake__ maximal awake duration
|
||||
* __Max Awake__ | maximal awake duration
|
||||
_15min_ / _20min_ / _..._ / __60min__ / _..._ / _120min_
|
||||
Adjust the maximal awake duration upon the exceeding of which aborts the consecutive sleep period.
|
||||
* __MinConsec__ minimal consecutive sleep duration
|
||||
* __Min Consec__ | minimal consecutive sleep duration
|
||||
_15min_ / _20min_ / _..._ / __30min__ / _..._ / _120min_
|
||||
Adjust the minimal consecutive sleep duration that will be considered for the consecutive sleep value.
|
||||
* __TempThresh__ temperature threshold
|
||||
* __Temp Thresh__ | temperature threshold
|
||||
_20°C_ / _20.5°C_ / _..._ / __25°C__ / _..._ / _40°C_
|
||||
The internal temperature must be greater than this threshold to log _sleeping_, otherwise it is _not worn_.
|
||||
* __NoMoThresh__ no movement threshold
|
||||
* __Power Saving__
|
||||
_on_ / __off__
|
||||
En-/Disable power saving mode. _Saves battery, but might decrease accurracy._
|
||||
In app icon showing that power saving mode is enabled: 
|
||||
* __Max Move__ | maximal movement threshold
|
||||
(only available when on power saving mode)
|
||||
_50_ / _51_ / _..._ / __100__ / _..._ / _200_
|
||||
On power saving mode the watch is considered resting if this threshold is lower or equal to the movement value of bangle's health event.
|
||||
* __NoMo Thresh__ | no movement threshold
|
||||
(only available when not on power saving mode)
|
||||
_0.006_ / _0.007_ / _..._ / __0.012__ / _..._ / _0.020_
|
||||
The standard deviation over the measured values needs to be lower then this threshold to count as not moving.
|
||||
The defaut threshold value worked best for my watch. A threshold value below 0.008 may get triggert by noise.
|
||||
* __MinDuration__ minimal no movement duration
|
||||
* __Min Duration__ | minimal no movement duration
|
||||
(only available when not on power saving mode)
|
||||
_5min_ / _6min_ / _..._ / __10min__ / _..._ / _15min_
|
||||
If no movement is detected for this duration, the watch is considered as resting.
|
||||
* __Enabled__
|
||||
__on__ / _off_
|
||||
En-/Disable the service (all background activities). _Saves battery, but might make this app useless._
|
||||
En-/Disable the service (all background activities). _Saves the most battery, but might make this app useless._
|
||||
In app icon showing that the service is disabled: 
|
||||
* __Logfile__
|
||||
__default__ / _off_
|
||||
En-/Disable logging by setting the logfile to _sleeplog.log_ / _undefined_.
|
||||
If the logfile has been customized it is displayed with _custom_.
|
||||
If the logfile has been customized it is displayed with _custom_.
|
||||
In app icon showing that logging is disabled: 
|
||||
|
||||
---
|
||||
### Global Object and Module Functions
|
||||
|
@ -65,8 +83,9 @@ For easy access from the console or other apps the following parameters, values
|
|||
enabled: true, // bool / service status indicator
|
||||
logfile: "sleeplog.log", // string / used logfile
|
||||
resting: false, // bool / indicates if the watch is resting
|
||||
status: 2, // int / actual status: 0 = unknown, 1 = not worn, 2 = awake, 3 = sleeping
|
||||
firstnomodate: 1644435877595, // number / Date.now() from first recognised no movement
|
||||
status: 2, // int / actual status:
|
||||
/ undefined = service stopped, 0 = unknown, 1 = not worn, 2 = awake, 3 = sleeping
|
||||
firstnomodate: 1644435877595, // number / Date.now() from first recognised no movement, not available in power saving mode
|
||||
stop: function () { ... }, // funct / stops the service until the next load()
|
||||
start: function () { ... }, // funct / restarts the service
|
||||
...
|
||||
|
@ -74,42 +93,54 @@ For easy access from the console or other apps the following parameters, values
|
|||
|
||||
>require("sleeplog")
|
||||
={
|
||||
setEnabled: function (enable, logfile) { ... },
|
||||
// en-/disable the service and/or logging
|
||||
// * enable / bool / service status to change to
|
||||
// * logfile / bool or string
|
||||
setEnabled: function (enable, logfile, powersaving) { ... },
|
||||
// restarts the service with changed settings
|
||||
// * enable / bool / new service status
|
||||
// * logfile / bool or string
|
||||
// - true = enables logging to "sleeplog.log"
|
||||
// - "some_file.log" = enables logging to "some_file.log"
|
||||
// - false = disables logging
|
||||
// returns: bool or undefined
|
||||
// - true = changes executed
|
||||
// - false = no changes needed
|
||||
// * (powersaving) / bool / new power saving status, default: false
|
||||
// returns: true or undefined
|
||||
// - true = service restart executed
|
||||
// - undefined = no global.sleeplog found
|
||||
readLog: function (since, until) { ... },
|
||||
readLog: function (logfile, since, until) { ... },
|
||||
// read the raw log data for a specific time period
|
||||
// * since / Date or number / startpoint of period
|
||||
// * until / Date or number / endpoint of period
|
||||
// * logfile / string / on no string uses logfile from global object or "sleeplog.log"
|
||||
// * (since) / Date or number / startpoint of period, default: 0
|
||||
// * (until) / Date or number / endpoint of period, default: 1E14
|
||||
// returns: array
|
||||
// * [[number, int, string], [...], ... ] / sorting: latest first
|
||||
// - number // timestamp in ms
|
||||
// - int // status: 0 = unknown, 1 = not worn, 2 = awake, 3 = sleeping
|
||||
// - string // additional information
|
||||
// * [] = no data available or global.sleeplog found
|
||||
getReadableLog: function (printLog, since, until) { ... }
|
||||
// * [] = no data available or global.sleeplog not found
|
||||
writeLog: function (logfile, input) { ... },
|
||||
// append or replace log depending on input
|
||||
// * logfile / string / on no string uses logfile from global object or default
|
||||
// * input / array
|
||||
// - append input if array length >1 and element[0] >9E11
|
||||
// - replace log with input if at least one entry like above is inside another array
|
||||
// returns: true or undefined
|
||||
// - true = changest written to storage
|
||||
// - undefined = wrong input
|
||||
getReadableLog: function (printLog, since, until, logfile) { ... }
|
||||
// read the log data as humanreadable string for a specific time period
|
||||
// * since / Date or number / startpoint of period
|
||||
// * until / Date or number / endpoint of period
|
||||
// * (printLog) / bool / direct print output with additional information, default: false
|
||||
// * (since) / Date or number / see readLog(..)
|
||||
// * (until) / Date or number / see readLog(..)
|
||||
// * (logfile) / string / see readLog(..)
|
||||
// returns: string
|
||||
// * "{substring of ISO date} - {status} for {duration}min\n...", sorting: latest last
|
||||
// * undefined = no data available or global.sleeplog found
|
||||
restoreLog: function (logfile) { ... }
|
||||
// eliminate some errors inside a specific logfile
|
||||
// * logfile / string / name of the logfile that will be restored
|
||||
// * (logfile) / string / see readLog(..)
|
||||
// returns: int / number of changes that were made
|
||||
reinterpretTemp: function (logfile, tempthresh) { ... }
|
||||
// reinterpret worn status based on given temperature threshold
|
||||
// * logfile / string / name of the logfile
|
||||
// * tempthresh / float / new temperature threshold
|
||||
// * (logfile) / string / see readLog(..)
|
||||
// * (tempthresh) / float / new temperature threshold, on default uses tempthresh from global object or 27
|
||||
// returns: int / number of changes that were made
|
||||
}
|
||||
```
|
||||
|
@ -120,7 +151,9 @@ For easy access from the console or other apps the following parameters, values
|
|||
#### To do list
|
||||
* Send the logged information to Gadgetbridge.
|
||||
_(For now I have no idea how to achieve this, help is appreciated.)_
|
||||
* View, down- and upload log functions via App Loader.
|
||||
* Calculate and display overall sleep statistics.
|
||||
* Option to automatically change power saving mode depending on time of day.
|
||||
|
||||
#### Requests, Bugs and Feedback
|
||||
Please leave requests and bug reports by raising an issue at [github.com/storm64/BangleApps](https://github.com/storm64/BangleApps) or send me a [mail](mailto:banglejs@storm64.de).
|
||||
|
|