Merge branch 'espruino:master' into development
|
@ -3,4 +3,5 @@ apps/banglerun/rollup.config.js
|
||||||
apps/schoolCalendar/fullcalendar/main.js
|
apps/schoolCalendar/fullcalendar/main.js
|
||||||
apps/authentiwatch/qr_packed.js
|
apps/authentiwatch/qr_packed.js
|
||||||
apps/qrcode/qr-scanner.umd.min.js
|
apps/qrcode/qr-scanner.umd.min.js
|
||||||
|
apps/gipy/pkg/gpconv.js
|
||||||
*.test.js
|
*.test.js
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
[submodule "EspruinoAppLoaderCore"]
|
[submodule "EspruinoAppLoaderCore"]
|
||||||
path = core
|
path = core
|
||||||
url = https://github.com/espruino/EspruinoAppLoaderCore.git
|
url = https://github.com/espruino/EspruinoAppLoaderCore.git
|
||||||
|
[submodule "webtools"]
|
||||||
|
path = webtools
|
||||||
|
url = https://github.com/espruino/EspruinoWebTools.git
|
||||||
|
|
19
README.md
|
@ -255,8 +255,11 @@ and which gives information about the app for the Launcher.
|
||||||
// 'app' - an application
|
// 'app' - an application
|
||||||
// 'clock' - a clock - required for clocks to automatically start
|
// 'clock' - a clock - required for clocks to automatically start
|
||||||
// 'widget' - a widget
|
// 'widget' - a widget
|
||||||
|
// 'module' - this provides a module that can be used with 'require'.
|
||||||
|
// 'provides_modules' should be used if type:module is specified
|
||||||
// 'bootloader' - an app that at startup (app.boot.js) but doesn't have a launcher entry for 'app.js'
|
// 'bootloader' - an app that at startup (app.boot.js) but doesn't have a launcher entry for 'app.js'
|
||||||
// 'settings' - apps that appear in Settings->Apps (with appname.settings.js) but that have no 'app.js'
|
// 'settings' - apps that appear in Settings->Apps (with appname.settings.js) but that have no 'app.js'
|
||||||
|
// 'clkinfo' - Provides a 'myapp.clkinfo.js' file that can be used to display info in clocks - see modules/clock_info.js
|
||||||
// 'RAM' - code that runs and doesn't upload anything to storage
|
// 'RAM' - code that runs and doesn't upload anything to storage
|
||||||
// 'launch' - replacement 'Launcher'
|
// 'launch' - replacement 'Launcher'
|
||||||
// 'textinput' - provides a 'textinput' library that allows text to be input on the Bangle
|
// 'textinput' - provides a 'textinput' library that allows text to be input on the Bangle
|
||||||
|
@ -266,10 +269,21 @@ and which gives information about the app for the Launcher.
|
||||||
// 'locale' - provides 'locale' library for language-specific date/distance/etc
|
// 'locale' - provides 'locale' library for language-specific date/distance/etc
|
||||||
// (a version of 'locale' is included in the firmware)
|
// (a version of 'locale' is included in the firmware)
|
||||||
"tags": "", // comma separated tag list for searching
|
"tags": "", // comma separated tag list for searching
|
||||||
|
// common types are:
|
||||||
|
// 'clock' - it's a clock
|
||||||
|
// 'widget' - it is (or provides) a widget
|
||||||
|
// 'outdoors' - useful for outdoor activities
|
||||||
|
// 'tool' - a useful utility (timer, calculator, etc)
|
||||||
|
// 'game' - a game
|
||||||
|
// 'bluetooth' - uses Bluetooth LE
|
||||||
|
// 'system' - used by the system
|
||||||
|
// 'clkinfo' - provides or uses clock_info module for data on your clock face (see modules/clock_info.js)
|
||||||
"supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
|
"supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
|
||||||
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on (see "type" above)
|
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on (see "type" above)
|
||||||
"dependencies" : { "messages":"app" } // optional, depend on a specific app ID
|
"dependencies" : { "messages":"app" } // optional, depend on a specific app ID
|
||||||
// for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
|
// for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
|
||||||
|
"dependencies" : { "messageicons":"module" } // optional, depend on a specific library to be used with 'require'
|
||||||
|
"provides_modules" : ["messageicons"] // optional, this app provides a module that can be used with 'require'
|
||||||
"readme": "README.md", // if supplied, a link to a markdown-style text file
|
"readme": "README.md", // if supplied, a link to a markdown-style text file
|
||||||
// that contains more information about this app (usage, etc)
|
// that contains more information about this app (usage, etc)
|
||||||
// A 'Read more...' link will be added under the app
|
// A 'Read more...' link will be added under the app
|
||||||
|
@ -454,7 +468,10 @@ It should also add `myappid.json` to `data`, to make sure it is cleaned up when
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
You can include any of [Espruino's modules](https://www.espruino.com/Modules) as
|
You can include any of [Espruino's modules](https://www.espruino.com/Modules) as
|
||||||
normal with `require("modulename")`. If you want to develop your own module for your
|
normal with `require("modulename")`. To include [Bangle's modules](modules) for use in the Web
|
||||||
|
IDE, [upload the modules to internal storage](modules#upload-the-module-to-the-bangles-internal-storage)
|
||||||
|
or [change the IDE's search path](modules#change-the-web-ide-search-path-to-include-banglejs-modules).
|
||||||
|
If you want to develop your own module for your
|
||||||
app(s) then you can do that too. Just add the module into the `modules` folder
|
app(s) then you can do that too. Just add the module into the `modules` folder
|
||||||
then you can use it from your app as normal.
|
then you can use it from your app as normal.
|
||||||
|
|
||||||
|
|
|
@ -170,10 +170,10 @@
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script src="https://www.puck-js.com/puck.js"></script>
|
<script src="webtools/puck.js"></script>
|
||||||
|
<script src="webtools/heatshrink.js"></script>
|
||||||
<script src="core/lib/marked.min.js"></script>
|
<script src="core/lib/marked.min.js"></script>
|
||||||
<script src="core/lib/espruinotools.js"></script>
|
<script src="core/lib/espruinotools.js"></script>
|
||||||
<script src="core/lib/heatshrink.js"></script>
|
|
||||||
<script src="core/js/utils.js"></script>
|
<script src="core/js/utils.js"></script>
|
||||||
<script src="loader.js"></script>
|
<script src="loader.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js"></script> <!-- for backup.js -->
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js"></script> <!-- for backup.js -->
|
||||||
|
|
|
@ -11,3 +11,4 @@
|
||||||
0.11: Bangle.js2: New pixels, btn1 to exit
|
0.11: Bangle.js2: New pixels, btn1 to exit
|
||||||
0.12: Actual pixels as of 29th Nov 2021
|
0.12: Actual pixels as of 29th Nov 2021
|
||||||
0.13: Bangle.js 2: Use setUI to add software back button
|
0.13: Bangle.js 2: Use setUI to add software back button
|
||||||
|
0.14: Add automatic translation of more strings
|
||||||
|
|
|
@ -11,8 +11,8 @@ g.drawString("BANGLEJS.COM",120,y-4);
|
||||||
} else {
|
} else {
|
||||||
y=-(4+h); // small screen, start right at top
|
y=-(4+h); // small screen, start right at top
|
||||||
}
|
}
|
||||||
g.drawString("Powered by Espruino",0,y+=4+h);
|
g.drawString(/*LANG*/"Powered by Espruino",0,y+=4+h);
|
||||||
g.drawString("Version "+ENV.VERSION,0,y+=h);
|
g.drawString(/*LANG*/"Version "+ENV.VERSION,0,y+=h);
|
||||||
g.drawString("Commit "+ENV.GIT_COMMIT,0,y+=h);
|
g.drawString("Commit "+ENV.GIT_COMMIT,0,y+=h);
|
||||||
function getVersion(name,file) {
|
function getVersion(name,file) {
|
||||||
var j = s.readJSON(file,1);
|
var j = s.readJSON(file,1);
|
||||||
|
@ -24,9 +24,9 @@ getVersion("Launcher","launch.info");
|
||||||
getVersion("Settings","setting.info");
|
getVersion("Settings","setting.info");
|
||||||
|
|
||||||
y+=h;
|
y+=h;
|
||||||
g.drawString(MEM.total+" JS Variables available",0,y+=h);
|
g.drawString(MEM.total+/*LANG*/" JS Variables available",0,y+=h);
|
||||||
g.drawString("Storage: "+(require("Storage").getFree()>>10)+"k free",0,y+=h);
|
g.drawString("Storage: "+(require("Storage").getFree()>>10)+/*LANG*/"k free",0,y+=h);
|
||||||
if (ENV.STORAGE) g.drawString(" "+(ENV.STORAGE>>10)+"k total",0,y+=h);
|
if (ENV.STORAGE) g.drawString(" "+(ENV.STORAGE>>10)+/*LANG*/"k total",0,y+=h);
|
||||||
if (ENV.SPIFLASH) g.drawString("SPI Flash: "+(ENV.SPIFLASH>>10)+"k",0,y+=h);
|
if (ENV.SPIFLASH) g.drawString("SPI Flash: "+(ENV.SPIFLASH>>10)+"k",0,y+=h);
|
||||||
g.setFontAlign(0,-1);
|
g.setFontAlign(0,-1);
|
||||||
g.flip();
|
g.flip();
|
||||||
|
|
|
@ -35,17 +35,17 @@ function drawInfo() {
|
||||||
g.setFont("4x6").setFontAlign(0,0).drawString("BANGLEJS.COM",W-30,56);
|
g.setFont("4x6").setFontAlign(0,0).drawString("BANGLEJS.COM",W-30,56);
|
||||||
var h=8, y = 24-h;
|
var h=8, y = 24-h;
|
||||||
g.setFont("6x8").setFontAlign(-1,-1);
|
g.setFont("6x8").setFontAlign(-1,-1);
|
||||||
g.drawString("Powered by Espruino",0,y+=4+h);
|
g.drawString(/*LANG*/"Powered by Espruino",0,y+=4+h);
|
||||||
g.drawString("Version "+ENV.VERSION,0,y+=h);
|
g.drawString(/*LANG*/"Version "+ENV.VERSION,0,y+=h);
|
||||||
g.drawString("Commit "+ENV.GIT_COMMIT,0,y+=h);
|
g.drawString("Commit "+ENV.GIT_COMMIT,0,y+=h);
|
||||||
|
|
||||||
getVersion("Bootloader","boot.info");
|
getVersion("Bootloader","boot.info");
|
||||||
getVersion("Launcher","launch.info");
|
getVersion("Launcher","launch.info");
|
||||||
getVersion("Settings","setting.info");
|
getVersion("Settings","setting.info");
|
||||||
|
|
||||||
g.drawString(MEM.total+" JS Vars",0,y+=h);
|
g.drawString(MEM.total+/*LANG*/" JS Vars",0,y+=h);
|
||||||
g.drawString("Storage: "+(require("Storage").getFree()>>10)+"k free",0,y+=h);
|
g.drawString("Storage: "+(require("Storage").getFree()>>10)+/*LANG*/"k free",0,y+=h);
|
||||||
if (ENV.STORAGE) g.drawString(" "+(ENV.STORAGE>>10)+"k total",0,y+=h);
|
if (ENV.STORAGE) g.drawString(" "+(ENV.STORAGE>>10)+/*LANG*/"k total",0,y+=h);
|
||||||
if (ENV.SPIFLASH) g.drawString("SPI Flash: "+(ENV.SPIFLASH>>10)+"k",0,y+=h);
|
if (ENV.SPIFLASH) g.drawString("SPI Flash: "+(ENV.SPIFLASH>>10)+"k",0,y+=h);
|
||||||
imageTop = y+h;
|
imageTop = y+h;
|
||||||
imgScroll = imgHeight-imageTop;
|
imgScroll = imgHeight-imageTop;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "about",
|
"id": "about",
|
||||||
"name": "About",
|
"name": "About",
|
||||||
"version": "0.13",
|
"version": "0.14",
|
||||||
"description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers",
|
"description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool,system",
|
"tags": "tool,system",
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
# Active Pedometer
|
# Active Pedometer
|
||||||
|
|
||||||
Pedometer that filters out arm movement and displays a step goal progress.
|
Pedometer that filters out arm movement and displays a step goal progress.
|
||||||
|
|
||||||
|
**Note:** Since creation of this app, Bangle.js's step counting algorithm has
|
||||||
|
improved significantly - and as a result the algorithm in this app (which
|
||||||
|
runs *on top* of Bangle.js's algorithm) may no longer be accurate.
|
||||||
|
|
||||||
I changed the step counting algorithm completely.
|
I changed the step counting algorithm completely.
|
||||||
Now every step is counted when in status 'active', if the time difference between two steps is not too short or too long.
|
Now every step is counted when in status 'active', if the time difference between two steps is not too short or too long.
|
||||||
To get in 'active' mode, you have to reach the step threshold before the active timer runs out.
|
To get in 'active' mode, you have to reach the step threshold before the active timer runs out.
|
||||||
|
@ -9,6 +14,7 @@ When you reach the step threshold, the steps needed to reach the threshold are c
|
||||||
Steps are saved to a datafile every 5 minutes. You can watch a graph using the app.
|
Steps are saved to a datafile every 5 minutes. You can watch a graph using the app.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
* 600 steps
|
* 600 steps
|
||||||

|

|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "Active Pedometer",
|
"name": "Active Pedometer",
|
||||||
"shortName": "Active Pedometer",
|
"shortName": "Active Pedometer",
|
||||||
"version": "0.09",
|
"version": "0.09",
|
||||||
"description": "Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph.",
|
"description": "(NOT RECOMMENDED) Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph. The `Health` app now provides step logging and graphs.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "outdoors,widget",
|
"tags": "outdoors,widget",
|
||||||
"supports": ["BANGLEJS"],
|
"supports": ["BANGLEJS"],
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
0.01: AdvCasio first version
|
0.01: AdvCasio first version
|
||||||
0.02: Remove un-needed fonts to improve memory usage
|
0.02: Remove un-needed fonts to improve memory usage
|
||||||
0.03: Tell clock widgets to hide.
|
0.03: Tell clock widgets to hide.
|
||||||
|
0.04: Swipe down to see widgets, step counter now just uses getHealthStatus
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const storage = require('Storage');
|
const storage = require('Storage');
|
||||||
|
|
||||||
|
require("Font6x12").add(Graphics);
|
||||||
require("Font8x12").add(Graphics);
|
require("Font8x12").add(Graphics);
|
||||||
require("Font7x11Numeric7Seg").add(Graphics);
|
require("Font7x11Numeric7Seg").add(Graphics);
|
||||||
|
|
||||||
|
@ -11,22 +12,27 @@ function bigThenSmall(big, small, x, y) {
|
||||||
g.drawString(small, x, y);
|
g.drawString(small, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getClockBg() {
|
|
||||||
return require("heatshrink").decompress(atob("icVgf/ABv8v4DBx4CB+PH8F+nAGB48fwEHBwXjxwqBuPH//+nAGBBwIjCAwI2D/wGBgIyDI4QGDwAGBHYX/4AGBn4UFEYQpCEYYpCAAMfMhP4FIgABwJ8OEBIA=="));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// sun, cloud, rain, thunder
|
|
||||||
var iconsWeather = [
|
|
||||||
require("heatshrink").decompress(atob("i8Ugf/ACcfA434BA/AAwsAv0/8F/BAcDwEHHIpECFI3wn4GC/gOC+PAGoXggEH/+ODQgXBGQv/wAbBBAnguEACIn4gfxI4JXFwJmG/kPBA3jSynw")), require("heatshrink").decompress(atob("i0Ugf/AEXggIGE/0A/kPBAmBCIN/A4Y8CgAICwEHBYoUE/ACCj4sDn4CBC4YyDwBrDCgYA3A")), require("heatshrink").decompress(atob("h8Rgf/AAuBAgf8h4FDCwM/AgPA/gFC/0HgEBBQPwnEfDoWAg4jC/gOCAoQmBAQXjFIV//8f//4IQP4j/+gAIB4EcHII4CAoI+DLQJXF/AA==")), require("heatshrink").decompress(atob("h0Pgf/AA8fAYX+g4EC8EBAgXADAeAgAECgAOC/wrCDQIOBBYfwgAaC/kAn4EB/EAv4aDHAeBIg38"))
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
function getBackgroundImage() {
|
function getBackgroundImage() {
|
||||||
return require("heatshrink").decompress(atob("2GwghC/AH4A/AH4AMl////wAwURiQECgUzmcxBQQCBiYUBBARW+LAcCAgcPBYgFBkAIFG7kQiAKIiIKBgISOAAJBD//zKQfxK4vyAoMQCgn/ERBhBBYR5BAwR1DB4Y2DgYPCGIQRCCQcP+EfGJI0FEgRSCGAQCCX4JXCkAhDn4lI+HyK4YWBFIPzJYJXHAIMSK4cwJ4I3CAYMzA4cfcRMBdwytBK4i6FK4IUCMgYAEGIITBK4cCaAPwgJXB+fzK4sAgYtCK5EfA4pXR+AmBaIZYCK6KcCAwSjDEYXx/8vK5QRCK4kPK6cDkJREBIMBfgIrDK5svUAIQBAwIaCK4w+DK4YGBK7IaBboIuCK4gFCJwYBBiBCCCgQhHHYgGDgArBK5IGDAYMgJ4Xwn53BGgLVDmBXKAAinDLpJXCAAYhHR4YODn/wJIPyTYZXDE4RXD+ECNILIDAIPwj4xIAAYNCR4fyVIYLFA4KEBBAglKAGUCmcykEAiMQBIURBYM/BgIUEgcz+bTKAH4A/AH4A/AHP/AGY1d+BWCh5X/LCpW1K74fgG/5X/AH5X/K9Bg/K63wK/5XWgBX/K6pWBK/5XU+BWBh5J/K6auCK/5XTVwRfFAH5XOKwRX/K6auDh5I/K6SuDWP5XSVwYADWX6vXK/5XQWQpW/K6auDJP5XWV35XT+Cu/K7Ku/K65H/K6hW/K7EPI35XWIv5XWAH5X/K/4A/K/5X/K/4A/K9cAAH4A/AFzz/AHRX/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/40VAH4A/AFzLb+EPDm4AdK/5X/K+PwgEAHy5X9HgMAK/5XXH6xX/H65X/K/5X/K98AK7sAgBX3DjBWFO644DSTHwGzJXED4RXaDoLqcK7weWDIQcXK8I6YK77KXK4o8DPbY6ZK7qvDDy6vdR7JXDh60EDyw5BAIRXYSwjMbAgIhUDwJZCHwJX0GwjRWNwIAEHSwBCDSpXFH4pXzDS5XIEARXVSYbQEDaYzCK+6vcKaxXNDypX9HwQkbHS40COSpXKK2A6CHgRXcPIhX0SwpXYVuQ6EgBX/K644YODBXkSDJX/K/5X/DtRX6gA3YOkRWbLDZX4KwYA/AG8F5vdABncKH4AGhpRJAYXNAgPAKP4AF5vMJwoDBAQIKE6BR/AAvc5vO9wAB7oCB9veAoPcAoPcK+kwh8AgcA98An//gH/+sD//wCISgBJ4IABAYpaC9vdK4UP/9AAQNQr/zgHwEYNQFYQAh+EP+FegH+A4QBCMQIKBAAPNK4yxBA4RXCV4YZBE4IjChwCDmApCK8VdmHggHgFYf0SQJXE5nMK4anCAoYHC5pXCaQJXBop+BqAGEK7f/AAQeEKwQrBqCtDAILjBCQfNK4JTCAYZXF7qvD//gV4S2DgEFFIYAECgIACMC8PKoIBB8n1K4ivF5vc5xOCWYZbBAYavHU4RXCr4pEAEMDfoNQGoMEgEwYQPwAoIBBAAPM5ipC7oDCVIIAE7hXCD4SdBiEP+gGBgihCFYIAz5pXBAAnN7oIB7nc5gOBK4QA/K4pNCWgSpCBInNK/4AGhncKIStC7gCBA4QAC4BR/AAysCABZW/AHwA="));
|
return require("heatshrink").decompress(atob("2GwwkGIf4AfgMRkUiiIHCiMRiAMDAwYCCBAYVDAHMv/4ACkBIBAgPxBgM/BYXyAoICBCowA5gRADKQUDKAYMCmYCBiBXBCo4A5J4MxiMSKQUf+YBBBgSiBgc/kBXBBAMyCoK2CK/btCiUhfAJLCkBkDiMQgBXDCoUvNAJX+AAU/+MB/8wAQIAC+cQK5hoDgIEBBIQFEAYIPHBIgBBAQQIDBwZXSKIMxgJaBgEjmZYCmBXLgLBBkkAgUhiMxBIM0iMSCoMRkZECkQJEichBINDiETAgISBiQTDK6MvJAXzVIQrBBYMCK5E/K4kwGIJXFgdAMgQQBiYiCDgU0HQSlCgMikIEBEAMTDYJXQ+UikYDBj6nCAAMTWoJ6BK4oVEK4c0oQ+BK4MjAgMDJoJXHNYJXHBwa0BohcDY4QAKgJQE+LzBNwJVBkQMEkBXBCoyvFJAVAKISaBiMiHQRIDkVBoSyCK5CvBAgavNDAJAC+cQn5DCgSpBl4MDgBXBgCsBCoYoMLAKREgIKDBJIdKK5oA/AH4A/AH4A/ADUBIH4APiAFEi1mAGUADrkRKwUGK2ZXes1gK2xXfD8A3/K/4AWgxX/ACtga2AwIHLkAgCwvJw6RcDgIABK+w4cK/I4dsEGP5BXtSAQ6BV/5XSG4RX/K6Y3fK+42CK/5XTGwcGK/5XSVwY5cK+o1DAAayYsAhDsCv4K7BTBK4YeYK7CyFVzJXFFIpXtVwYiYK/rmZKYYDDELJXXG4YiaK/Y0aKgQAEK+gkdKt5XGKzqv5GTpX6ETlgK4xWrKTyxKVthXmAGRX/K/5X/AH5X/K/4gBAH4A/AFz/uAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHNggEGHfEAgAEHKyQXVK0qTCAggbUK+6SDAApzXK/5BRDYZX3KxBBSYqxXngyvaV25XEd4ZCSsAcBAoRZ2dQZXBLwgaQCIYeCAGirCS4YGCDSJXCC6ZaodYICBZzSw4S4I+XDgSv4K4rzCK/47RAQTMaWHI9YV3TscV3aVagByBK3SwCSqyt8AAQ+XK/4A/AH4A/AH4A3gAA/AH4AuZbdggwc3ADpX/K/5XxsEAgA+XK/o8BgBX/K64/WK/4/XK/5X/K/5XvgBX/K64cYHrw4CSTFggCuXK4oDCEQJXYDS6ScDgg4CPKyRCAAZX0HAgBDK+LlYK4oeBAwZ9aK+lgAoQGBgyvzDIIDBK66sCG4JXYCwIBDK7ADCK+xZCHwJXzGoQ8BK7DpBAAaSXSgRXZO4okCK+IaXV4oABEILSWSYjRCHSo3BDSxXEAAIcBAISvyKawcIAYIGCK/4cUH4YlaHS0AHgI1XOg5YBPrY6WHgRXfAGRXDHzBX8VoJX/K68ADjRX6sBX/K/5X/K8wdcK/UAG7B0iKzZYbK/BWDAH4A/hWpzWhIf4ASgOpzIAB0EAhhH/AB8ZzGJ1WazMA4pH/AB+pxOZxOpzVMqA2ugUzmcgD7cKVYOqzGqpnRFw8ykchK8kviEBmQFBgMiFocSCAcSkUQAgMikRsHhWqxOq0Ut4mqBw0DC4IxBD4wpBHAQMCA4cCGJIAFj8hDIQuBkMTCwU/AYQJBiUxFoPxiIVDK4kyxUz4cxl+KK5MfDQXyD4UCmMSmAEBAQQHDgMTmIxHAAqpBmaqCFwMDEYZRBgEjCQQBB+USK5E/ns/0Uzwc6K48ykYkCK4IfCc4I4CK4QHEBAYAMiICBmYuDmQEBh8iAgRXCLISvJO4MqwcklEiK5CADV4oaBV4oHEK6Eve4JNCbwRfCiMTFoMDkMRSAJXCD49azWp0UqzWayJXIQwcAO4cCkMCFIJOCA4XxK6KPBkR6DTwYyBAwYPEAggfFzORpWK1OZyAOHJ4QfERAUSEgQxIIIgAr1URWIOZzOgGtwAhgMZzWq1OaIv4ASKgOqzTkvAEmq1WgFtQA=="));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRocketSequences() {
|
||||||
|
return {
|
||||||
|
1: require("heatshrink").decompress(atob("qFGwkCkQAiiEBEkUgKQhPhE8ogCE8YhCiQoEE7pKEPIgncTQ4neEwpQCPoh1eJYYwCJ7QmHKAh1hZIpOjPAUBJ0ZQCTzEhExZ1lPAZ1kKDQmOJ65O2E65OPOy5O2E64mPOyxO/J2wnPJyx2QJ35O/J2khE0p2POq52PEy4nOiQnlOrEhiSfMJrEggQnLJzB1CPBQmZkInMEzBQDPBImbPBR1ZEoRMCZYImhgQgEE0BzFKAgmaDwLDFKAbqdYQwHBOrcgDgLBFJrsiiRNGYbpLBY4Ymhd4omkkUhE0pQEEwUBJjrHBd4QmCdzoiBDwYrCPLyZHF4QnagQeCE8UgJwYniJwgnIOzwfFO0wJCJzMQE4gyFEzR2FBQombkInDQI4AakAnBTYS+ZE5BMDE0LEES7YnLE0R3FAEQA=")),
|
||||||
|
2: require("heatshrink").decompress(atob("qFGwkCkQAikMAgIliKYon/AA0gEAQniEwIhCAgYndEIjqBE8CaGKogmgKAp1fKAgncExBQBBQR1gKAp7BJ0IndExR4CE0idaOpYnbExqeYJxxPYEx0BJ0x2XExx2XJ20QE6xONJi5OPGwJOlBwLFkLoLFlBwJOkOwJOlE4JOkTjBOOE/52Pdi5OPEy7FnE5wmXE5xOZT5gmYEoMiiB1lgR4KTLAkDPBJ1WIAYDDKA4mWJwchDwYEDTjQiDJQh4GYLAhHFosSJy6OCTIxaEEywbBKYwjEEzMgUQxQFBogAURwZOGOjTKJdTYnOEryfHE0JQEfIpQgYQMAgJLeAgrtfTI4ndgSaFE4h0bdQkSZQpOfEAgIBO0AnEdrh2FJAb1EdbInEBIpObOwhOEEzYnFXzZ2HE4QlhE4QlDFMKcDYooniO0QnDT0YnCE0ciA")),
|
||||||
|
3: require("heatshrink").decompress(atob("qFGwkEogAjiMUEkVAKYgnhPYolgOQIniOYZ4FOcLqBE8CaGKojpgKAomhEYUQE7gmHKAIxCE0QkCPYR1gZIgnZExR4CJ0idmE7ZONYzImNgEUJ0p3YJRh2ZJJwnXOpQhBdkpaETsMEGQhOhE7jFLUYpOfTzgmKE4hOiE4hOigEUJ0rvCEywnPEqx2OTjBOOE7ImOTsqeZE5zFYoJOmT5kBJzEAih4LdK5mBAQInKOqoYDEgR4JEypHDEYbxJOq5ABdgZ7CEzZOEJQgnGihOYEIzJFTionCKYxWGEy9ADAYnGUIYmWog/EdBFAEy7KIKAwnjKwLqWE5pMeT48CVQpQfgMjKEtEiAnfEQJQCgJSCTcB6FJzkEdYcUE8FAdQghDOzonKTjh2EZAidcDoInHJzodBOwx/BE8JxcOwsAOwQmhJgSXDObwnFEwUUO0LFGE8aeiE4YmiokQE0tE")),
|
||||||
|
4: require("heatshrink").decompress(atob("qFGwkCkQAjiMSEkRTFE/4AGkMAgQCBE8MgEIYEDE7whDdQIngTQxVEE0ChFTjxQFE7jnFKAgxCOsBQFZgJ1gE7wmKPAROkTrTEHGAwnYiBHJFAaeXOoyXBEQZPac5AsFgJOhAoh2XJwwnFKoROdE4J9GJzwnIiQmVkInPAC0QE5AJFE64mHY5DFdE4SBEYr5JDJ0hKDJ0jCZJxoACgInmKLAmOTq5OOEy5OPTsxOYE5wmXO5wlYkAnMOqshiRNCgR4LOC8CkJCCEzxHDAgYnJOqpAEDoZ4HEyodDEQpQHdCsQOwwFHEyzoCPYzJGEy0gEwaZGA4acVEQSjHKAomXkQYEYAwlZeRKYDE8gjCYa7zJEwcCkImfKAb4FAD0hdTh4LgRSBOcR0CJz0gYYrrgN4QnEYrxOEE4bEeiAnGF4J2idL6VDE8ohBE0gnFE0J0BE4QGBiROgdIQABgJ2hJoTtjYgZSEE8ScgE4omikUQTcQADA=")),
|
||||||
|
5: require("heatshrink").decompress(atob("qFGwkCkQAikMAgIliKYonhiAnjkEATIIniEwIhCAgYndEIhQFYUZVEE0BQFOr5QEeQQmiKAL1DOr5QEE7ROCDgZVEAoInZDwchFQQoDPAJOdEQYrBdrZFDOYwncEJDsDVIpOXgJxEE4pObEAgGFgJOaE48BaIhOZJ5ZObY5ROcE441CE6xOGPAwtCJzpGCJ0hHDkI1DJzwoEJzInLFg52dUo5O/J35OzE54mWOx4mXJxx1XE54mXkUhExkSJzCfMOrAlBPBiZXgQDBAQQmgJgh4JOqoYEFYwmaDoZzEFgh1YDgkiiAFEKAroXJJAGFiQmVkCNDTIz5EJy57HKAomXkQYEJoqaYeRadEJrAnJEQUAgJPiAoYmeT4cCkAnBE0BKCJkT1EkDCeJYYiDOkLDFFL5wBE4guCPDhEBEwQiDY70CkInDiQnCJzkhOwhKDdzp2Idb4nEE0B0Bdo4niE0J0CeYhOhgESUYYnidsgnEE0KeCE0gnDE0ciA")),
|
||||||
|
6: require("heatshrink").decompress(atob("qFGwkCkQA/ABEgKQZPhEwgABEsAoGJkBxBE8JKEAowAbJIhQEgLDiPooAdKA4ncTZAndSwhQEFoInaJQkSKAwlZdgwnfSgYADE4h1ZDwInlcggnIOzAdCE8i7EY5J3XDgYhGd4pOZEI52bSYwGCOAJ2bYIodEOzZOFFAjFcEwwAIE6xOHABBO/J34ndEyx2PJ00BJ00SJ0p1XE54mXOxxO/J5wmYgQnMOrB2BPBgkWiJ1CPBbBYAYR4KiTAXRwIrFTjgZDJYZ4IEyoiEIwrDcEJJQFOqwiBDARxFFwgmXkAYDEogsBF4QmXEQJ7GUYYkBEzDKJAgYmdEQbKFEzonEKYgngJwgmfZggmjKQghgiBRGkBzeTgUikJRgc47LDErTnDEAkQJzkCJwYnEJzonEJIaddOwhJEJzgdBE4hYEJzieJADgnEE0KUCXzoAGkJLEiB2hOgQDBT0TsDT0YmlE4YmjkQ=")),
|
||||||
|
7: require("heatshrink").decompress(atob("qFGwkCkQAhkIpBiQlhkBSEJ8InlEIIoFE7whEE8pQFE7giBJQoneI4MCTYhQDE7YdCYYondEQYnEPwZ1bE5BQCJzonHkR2ZEAkBE4pNBE7zHFYrYhFUgonaXAQeEEwruZEYcgiROHJ7AfDAwxOeAAURiAmHE65HIOzwmOJ35OPE6xOPO35O/J35O/J1gnPEyx2PEy5OOOq5OnE5xOYO5omZgJQMJrQnLiQnagR4JOq5nCDgZ1fEYRLDE5DoZkUQNoZ4GOrJKGAoomXOw7lCAwYmYDgJSEAAUBA4QDBJzB6FOQrDXJwTJFdLjJKE9jDYZRAmkKAwmhKAgmiKAYmBkApdJIgjCKYIncOQYvJYTovGE84lagR2DE4xOakBOEgJXFOjYnEJAbtdOwggEkAmbDgInDE0B0BE4QgcE5AkiXYbpCOLonGYo4nhPMYnCUEgnBY0kiA==")),
|
||||||
|
8: require("heatshrink").decompress(atob("qFGwkCkQA/ABBSEJ8MgE4kBEsBPFE7xMCOIJ3hOYgFEE7rCGE70gE4pQBiAndYQwjBUohOZD4ZQFE7YkBE5AICYbZ2GE7sggJRCAA8iYzZOITroALE7EhExh4CAC0QExpPXOponZExx2XJ24nWdh52XdhzF/Yu5O/J35O0E55OXOx5O/J2omXE5x1XO54mYgQnMJrR4LOrciiAmiJgR4KEzIjDPBAlYiAiEeI51YkEBE4J5CD4KceTQQcBJgRQFdTZDCJIjDcNIqhGdTQmCkByFTTInDKgoAEE7ZEEJwhPdE1R1FE0InEE0R3DEwTGcDwomEE7hKFPYqafE8ROCE5DJbE5B/IEqh2ED4gnCJrMCJwgnEiB2bE4qeFEzUggQmIBQLEaEQImHLIImaE4YfcOw4lEFMLECS7onJO8wmkE4QljAAIA==")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let rocketSequence = 1;
|
||||||
|
let settings = storage.readJSON("cassioWatch.settings.json", true) || {};
|
||||||
|
let rocketSpeed = settings.rocketSpeed || 700;
|
||||||
|
delete settings;
|
||||||
|
|
||||||
// schedule a draw for the next minute
|
// schedule a draw for the next minute
|
||||||
let rocketInterval;
|
let rocketInterval;
|
||||||
|
@ -47,237 +53,91 @@ function clearIntervals() {
|
||||||
drawTimeout = undefined;
|
drawTimeout = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////
|
|
||||||
// TIMER FUNC
|
|
||||||
//
|
|
||||||
var timer_time = 0;
|
|
||||||
var alreadyListenTouch = false;
|
|
||||||
function initTouchTimer () {
|
|
||||||
if (alreadyListenTouch) return;
|
|
||||||
alreadyListenTouch = true;
|
|
||||||
|
|
||||||
Bangle.on('swipe', function(dirX,dirY) {
|
|
||||||
if (canTouch === false) return;
|
|
||||||
var njson = getDataJson();
|
|
||||||
if (!njson) return;
|
|
||||||
|
|
||||||
if (dirX === -1) {
|
|
||||||
timer_time = 0;
|
|
||||||
delete njson.timer;
|
|
||||||
setDataJson(njson);
|
|
||||||
}
|
|
||||||
else if (dirX === 1) {
|
|
||||||
var now = new Date().getTime();
|
|
||||||
njson.timer = now + (timer_time * 1000 * 60);
|
|
||||||
Bangle.setLocked(true);
|
|
||||||
setDataJson(njson);
|
|
||||||
Bangle.buzz(200, 0);
|
|
||||||
timer_time = 0;
|
|
||||||
}
|
|
||||||
else if (dirY === -1) {
|
|
||||||
if (canTouch === false || njson.timer) return;
|
|
||||||
timer_time = timer_time + 5;
|
|
||||||
}
|
|
||||||
else if (dirY === 1) {
|
|
||||||
if (canTouch === false || njson.timer) return;
|
|
||||||
timer_time = timer_time - 5;
|
|
||||||
}
|
|
||||||
draw();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
initTouchTimer ();
|
|
||||||
});
|
|
||||||
|
|
||||||
function getTimerTime() {
|
|
||||||
// if timer_time !== -1, take it
|
|
||||||
if (timer_time !== 0) {
|
|
||||||
return timer_time + "m";
|
|
||||||
} else {
|
|
||||||
// else, show diff between njsontime and now
|
|
||||||
var njson = getDataJson();
|
|
||||||
if (!njson) return false;
|
|
||||||
var now = new Date().getTime();
|
|
||||||
var diff = Math.round((njson.timer - now) / (1000 * 60));
|
|
||||||
//console.log(123, njson, diff, now, njson.timer - now);
|
|
||||||
if (diff > 0) return diff + "m";
|
|
||||||
else if (njson.timer) {
|
|
||||||
Bangle.buzz(1000, 1);
|
|
||||||
console.log("END OF TIMER");
|
|
||||||
delete njson.timer;
|
|
||||||
setDataJson(njson);
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// if diff is <0, delete timer from json
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function drawTimer() {
|
|
||||||
//g.drawString(getTimerTime(), 100, 100);
|
|
||||||
g.setFont("8x12", 2);
|
|
||||||
var t = 97;
|
|
||||||
var l = 105;
|
|
||||||
var time = getTimerTime();
|
|
||||||
if (time || timer_time !== 0) g.drawString(time, l+5, t+0);
|
|
||||||
if (time && timer_time === 0) g.drawImage(getClockBg(), l-20, t+2, { scale: 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////
|
|
||||||
// DATA READING
|
|
||||||
//
|
|
||||||
function getDataJson(){
|
|
||||||
var res = {"tasks":"", "weather":[]};
|
|
||||||
try {
|
|
||||||
res = storage.readJSON('advcasio.data.json');
|
|
||||||
} catch(ex) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
function setDataJson(resJson){
|
|
||||||
try {
|
|
||||||
res = storage.writeJSON('advcasio.data.json', resJson);
|
|
||||||
} catch(ex) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
var dataJson = getDataJson();
|
|
||||||
|
|
||||||
////////////////////////////////////////////
|
|
||||||
// WEATHER!
|
|
||||||
//
|
|
||||||
function drawWeather(arr) {
|
|
||||||
g.setFont("6x8", 1);
|
|
||||||
var p = {l: 8, tText: 40, tIcon:20, decal:25};
|
|
||||||
var today = new Date().getTime();
|
|
||||||
var yesterday = today - (1000 * 60 * 60 * 24);
|
|
||||||
var testday = today + (1000 * 60 * 60 * 24 * 2);
|
|
||||||
//12h auj > 12h hier qui est sup a 0h auj
|
|
||||||
//23h59 hier est sup a 0h auj
|
|
||||||
var j = 0;
|
|
||||||
for(var i = 0; i<arr.length;i++) {
|
|
||||||
if (arr[i][2] > yesterday && j < 4) {
|
|
||||||
g.drawString(arr[i][0], p.l + p.decal*j + 4, p.tText);
|
|
||||||
g.drawImage(iconsWeather[arr[i][1]], p.l + p.decal*j, p.tIcon, { scale: 1 });
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////
|
|
||||||
// DRAWING FUNCS
|
|
||||||
//
|
|
||||||
function drawTasks(str) {
|
|
||||||
g.setFont("6x8", 1);
|
|
||||||
var t = 57;
|
|
||||||
var l = 0;
|
|
||||||
g.drawString(str, l+5, t+0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawSteps() {
|
|
||||||
g.setFont("8x12", 2);
|
|
||||||
var t = 132;
|
|
||||||
var l = 150;
|
|
||||||
g.drawString(getSteps(), l+5, t+0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function drawClock() {
|
function drawClock() {
|
||||||
g.setFont("7x11Numeric7Seg", 3);
|
g.setFont("7x11Numeric7Seg", 3);
|
||||||
g.clearRect(80, 57, 170, 96);
|
g.clearRect(80, 57, 170, 96);
|
||||||
g.setColor(255, 255, 255);
|
g.setColor(0, 255, 255);
|
||||||
var l = 77;
|
g.drawRect(80, 57, 170, 96);
|
||||||
var t = 57;
|
g.fillRect(80, 57, 170, 96);
|
||||||
var w = 170;
|
|
||||||
var h = 116;
|
|
||||||
g.drawRect(l, t, w, h);
|
|
||||||
g.fillRect(l, t, w, h);
|
|
||||||
g.setColor(0, 0, 0);
|
g.setColor(0, 0, 0);
|
||||||
g.drawString(require("locale").time(new Date(), 1), 76, 60);
|
g.drawString(require("locale").time(new Date(), 1), 70, 60);
|
||||||
|
|
||||||
// day
|
|
||||||
//g.setFont("8x12", 1);
|
|
||||||
//g.setFont("9x18", 1);
|
|
||||||
//g.drawString(require("locale").dow(new Date(), 2).toUpperCase(), 25, 136);
|
|
||||||
g.setFont("8x12", 2);
|
g.setFont("8x12", 2);
|
||||||
g.drawString(require("locale").dow(new Date(), 2), 18, 130);
|
g.drawString(require("locale").dow(new Date(), 2).toUpperCase(), 18, 130);
|
||||||
|
|
||||||
// month
|
|
||||||
g.setFont("8x12");
|
g.setFont("8x12");
|
||||||
g.drawString(require("locale").month(new Date(), 2).toUpperCase(), 80, 127);
|
g.drawString(require("locale").month(new Date(), 2).toUpperCase(), 80, 126);
|
||||||
|
|
||||||
// day nb
|
|
||||||
g.setFont("8x12", 2);
|
g.setFont("8x12", 2);
|
||||||
const time = new Date().getDate();
|
const time = new Date().getDate();
|
||||||
g.drawString(time < 10 ? "0" + time : time, 78, 137);
|
g.drawString(time < 10 ? "0" + time : time, 78, 137);
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawBattery() {
|
function drawBattery() {
|
||||||
bigThenSmall(E.getBattery(), "%", 140, 23);
|
bigThenSmall(E.getBattery(), "%", 135, 21);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drawRocket() {
|
||||||
|
let Rocket = getRocketSequences();
|
||||||
|
g.clearRect(5, 62, 63, 115);
|
||||||
|
g.setColor(0, 255, 255);
|
||||||
|
g.drawRect(5, 62, 63, 115);
|
||||||
|
g.fillRect(5, 62, 63, 115);
|
||||||
|
g.drawImage(Rocket[rocketSequence], 5, 65, { scale: 0.7 });
|
||||||
|
g.setColor(0, 0, 0);
|
||||||
|
rocketSequence = rocketSequence + 1;
|
||||||
|
if(rocketSequence > 8) rocketSequence = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTemperature(){
|
||||||
|
try {
|
||||||
|
var weatherJson = storage.readJSON('weather.json');
|
||||||
|
var weather = weatherJson.weather;
|
||||||
|
return Math.round(weather.temp-273.15);
|
||||||
|
|
||||||
|
} catch(ex) {
|
||||||
|
print(ex)
|
||||||
|
return "?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getSteps() {
|
function getSteps() {
|
||||||
var steps = 0;
|
var steps = Bangle.getHealthStatus("day").steps;
|
||||||
try{
|
|
||||||
if (WIDGETS.wpedom !== undefined) {
|
|
||||||
steps = WIDGETS.wpedom.getSteps();
|
|
||||||
} else if (WIDGETS.activepedom !== undefined) {
|
|
||||||
steps = WIDGETS.activepedom.getSteps();
|
|
||||||
} else {
|
|
||||||
steps = Bangle.getHealthStatus("day").steps;
|
|
||||||
}
|
|
||||||
} catch(ex) {
|
|
||||||
// In case we failed, we can only show 0 steps.
|
|
||||||
return "? k";
|
|
||||||
}
|
|
||||||
|
|
||||||
steps = Math.round(steps/1000);
|
steps = Math.round(steps/1000);
|
||||||
return steps + "k";
|
return steps + "k";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function draw() {
|
function draw() {
|
||||||
|
|
||||||
queueDraw();
|
queueDraw();
|
||||||
|
|
||||||
g.reset();
|
g.clear(1);
|
||||||
g.clear();
|
g.setColor(0, 255, 255);
|
||||||
g.setColor(255, 255, 255);
|
|
||||||
g.fillRect(0, 0, g.getWidth(), g.getHeight());
|
g.fillRect(0, 0, g.getWidth(), g.getHeight());
|
||||||
let background = getBackgroundImage();
|
let background = getBackgroundImage();
|
||||||
g.drawImage(background, 0, 0, { scale: 1 });
|
g.drawImage(background, 0, 0, { scale: 1 });
|
||||||
|
|
||||||
|
|
||||||
g.setColor(0, 0, 0);
|
g.setColor(0, 0, 0);
|
||||||
if(dataJson && dataJson.weather) drawWeather(dataJson.weather);
|
g.setFont("6x12");
|
||||||
if(dataJson && dataJson.tasks) drawTasks(dataJson.tasks);
|
g.drawString("Launching Process", 30, 20);
|
||||||
|
g.setFont("8x12");
|
||||||
|
g.drawString("ACTIVATE", 40, 35);
|
||||||
|
|
||||||
g.setFontAlign(0,-1);
|
g.setFontAlign(0,-1);
|
||||||
g.setFont("8x12", 2);
|
g.setFont("8x12", 2);
|
||||||
|
g.drawString(getTemperature(), 155, 132);
|
||||||
|
g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), 109, 98);
|
||||||
|
g.drawString(getSteps(), 158, 98);
|
||||||
|
|
||||||
drawSteps();
|
|
||||||
g.setFontAlign(-1,-1);
|
g.setFontAlign(-1,-1);
|
||||||
drawClock();
|
drawClock();
|
||||||
|
drawRocket();
|
||||||
drawBattery();
|
drawBattery();
|
||||||
drawTimer();
|
|
||||||
// Hide widgets
|
// Hide widgets
|
||||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
||||||
}
|
}
|
||||||
|
|
||||||
// save batt power, does not seem to work although...
|
|
||||||
var canTouch = true;
|
|
||||||
Bangle.on("lcdPower", (on) => {
|
Bangle.on("lcdPower", (on) => {
|
||||||
if (on) {
|
if (on) {
|
||||||
draw();
|
draw();
|
||||||
} else {
|
} else {
|
||||||
canTouch = false;
|
|
||||||
clearIntervals();
|
clearIntervals();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -287,18 +147,14 @@ Bangle.on("lock", (locked) => {
|
||||||
clearIntervals();
|
clearIntervals();
|
||||||
draw();
|
draw();
|
||||||
if (!locked) {
|
if (!locked) {
|
||||||
canTouch = true;
|
rocketInterval = setInterval(drawRocket, rocketSpeed);
|
||||||
} else {
|
|
||||||
canTouch = false;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Bangle.setUI("clock");
|
Bangle.setUI("clock");
|
||||||
|
|
||||||
// Load widgets, but don't show them
|
// Load widgets, but don't show them
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
|
require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe
|
||||||
g.reset();
|
g.clear(1);
|
||||||
g.clear();
|
|
||||||
draw();
|
draw();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{ "id": "advcasio",
|
{ "id": "advcasio",
|
||||||
"name": "Advanced Casio Clock",
|
"name": "Advanced Casio Clock",
|
||||||
"shortName":"advcasio",
|
"shortName":"advcasio",
|
||||||
"version":"0.03",
|
"version":"0.04",
|
||||||
"description": "An over-engineered clock inspired by Casio watches. It has a 4 days weather, a timer using swipe and a scratchpad. Can be updated using a dedicated webapp.",
|
"description": "An over-engineered clock inspired by Casio watches. It has a 4 days weather, a timer using swipe and a scratchpad. Can be updated using a dedicated webapp.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
|
|
|
@ -5,3 +5,5 @@
|
||||||
0.05: Displaying calendar colour and name
|
0.05: Displaying calendar colour and name
|
||||||
0.06: Added clkinfo for clocks.
|
0.06: Added clkinfo for clocks.
|
||||||
0.07: Clkinfo improvements.
|
0.07: Clkinfo improvements.
|
||||||
|
0.08: Fix error in clkinfo (didn't require Storage & locale)
|
||||||
|
Fix clkinfo icon
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
(function() {
|
(function() {
|
||||||
var agendaItems = {
|
var agendaItems = {
|
||||||
name: "Agenda",
|
name: "Agenda",
|
||||||
img: atob("GBiBAf////////85z/AAAPAAAPgAAP////AAAPAAAPAAAPAAAOAAAeAAAeAAAcAAA8AAAoAABgAADP//+P//8PAAAPAAAPgAAf///w=="),
|
img: atob("GBiBAAAAAAAAAADGMA///w///wf//wAAAA///w///w///w///x///h///h///j///D///X//+f//8wAABwAADw///w///wf//gAAAA=="),
|
||||||
items: []
|
items: []
|
||||||
};
|
};
|
||||||
|
var locale = require("locale");
|
||||||
var now = new Date();
|
var now = new Date();
|
||||||
var agenda = storage.readJSON("android.calendar.json")
|
var agenda = require("Storage").readJSON("android.calendar.json")
|
||||||
.filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000)
|
.filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000)
|
||||||
.sort((a,b)=>a.timestamp - b.timestamp);
|
.sort((a,b)=>a.timestamp - b.timestamp);
|
||||||
|
|
||||||
agenda.forEach((entry, i) => {
|
agenda.forEach((entry, i) => {
|
||||||
|
|
||||||
var title = entry.title.slice(0,18);
|
var title = entry.title.slice(0,12);
|
||||||
var date = new Date(entry.timestamp*1000);
|
var date = new Date(entry.timestamp*1000);
|
||||||
var dateStr = locale.date(date).replace(/\d\d\d\d/,"");
|
var dateStr = locale.date(date).replace(/\d\d\d\d/,"");
|
||||||
dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : "";
|
dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : "";
|
||||||
|
|
||||||
agendaItems.items.push({
|
agendaItems.items.push({
|
||||||
name: null,
|
name: "Agenda "+i,
|
||||||
get: () => ({ text: title + "\n" + dateStr, img: null}),
|
get: () => ({ text: title + "\n" + dateStr, img: null}),
|
||||||
show: function() { agendaItems.items[i].emit("redraw"); },
|
show: function() { agendaItems.items[i].emit("redraw"); },
|
||||||
hide: function () {}
|
hide: function () {}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"id": "agenda",
|
"id": "agenda",
|
||||||
"name": "Agenda",
|
"name": "Agenda",
|
||||||
"version": "0.07",
|
"version": "0.08",
|
||||||
"description": "Simple agenda",
|
"description": "Simple agenda",
|
||||||
"icon": "agenda.png",
|
"icon": "agenda.png",
|
||||||
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
|
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
|
||||||
"tags": "agenda",
|
"tags": "agenda,clkinfo",
|
||||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"allow_emulator": true,
|
"allow_emulator": true,
|
||||||
|
|
|
@ -2,3 +2,4 @@
|
||||||
0.02: Load AGPS data on app start and automatically in background
|
0.02: Load AGPS data on app start and automatically in background
|
||||||
0.03: Do not load AGPS data on boot
|
0.03: Do not load AGPS data on boot
|
||||||
Increase minimum interval to 6 hours
|
Increase minimum interval to 6 hours
|
||||||
|
0.04: Write AGPS data chunks with delay to improve reliability
|
||||||
|
|
|
@ -36,7 +36,7 @@ function updateAgps() {
|
||||||
g.clear();
|
g.clear();
|
||||||
if (!waiting) {
|
if (!waiting) {
|
||||||
waiting = true;
|
waiting = true;
|
||||||
display("Updating A-GPS...");
|
display("Updating A-GPS...", "takes ~ 10 seconds");
|
||||||
require("agpsdata").pull(function() {
|
require("agpsdata").pull(function() {
|
||||||
waiting = false;
|
waiting = false;
|
||||||
display("A-GPS updated.", "touch to close");
|
display("A-GPS updated.", "touch to close");
|
||||||
|
|
|
@ -8,34 +8,45 @@ var FILE = "agpsdata.settings.json";
|
||||||
var settings;
|
var settings;
|
||||||
readSettings();
|
readSettings();
|
||||||
|
|
||||||
function setAGPS(data) {
|
function setAGPS(b64) {
|
||||||
var js = jsFromBase64(data);
|
return new Promise(function(resolve, reject) {
|
||||||
try {
|
var initCommands = "Bangle.setGPSPower(1);\n"; // turn GPS on
|
||||||
eval(js);
|
const gnsstype = settings.gnsstype || 1; // default GPS
|
||||||
return true;
|
initCommands += `Serial1.println("${CASIC_CHECKSUM("$PCAS04," + gnsstype)}")\n`; // set GNSS mode
|
||||||
}
|
|
||||||
catch(e) {
|
|
||||||
console.log("error:", e);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function jsFromBase64(b64) {
|
|
||||||
var bin = atob(b64);
|
|
||||||
var chunkSize = 128;
|
|
||||||
var js = "Bangle.setGPSPower(1);\n"; // turn GPS on
|
|
||||||
var gnsstype = settings.gnsstype || 1; // default GPS
|
|
||||||
js += `Serial1.println("${CASIC_CHECKSUM("$PCAS04,"+gnsstype)}")\n`; // set GNSS mode
|
|
||||||
// What about:
|
// What about:
|
||||||
// NAV-TIMEUTC (0x01 0x10)
|
// NAV-TIMEUTC (0x01 0x10)
|
||||||
// NAV-PV (0x01 0x03)
|
// NAV-PV (0x01 0x03)
|
||||||
// or AGPS.zip uses AID-INI (0x0B 0x01)
|
// or AGPS.zip uses AID-INI (0x0B 0x01)
|
||||||
|
|
||||||
for (var i=0;i<bin.length;i+=chunkSize) {
|
eval(initCommands);
|
||||||
var chunk = bin.substr(i,chunkSize);
|
|
||||||
js += `Serial1.write(atob("${btoa(chunk)}"))\n`;
|
try {
|
||||||
|
writeChunks(atob(b64), resolve);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("error:", e);
|
||||||
|
reject();
|
||||||
}
|
}
|
||||||
return js;
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var chunkI = 0;
|
||||||
|
function writeChunks(bin, resolve) {
|
||||||
|
return new Promise(function(resolve2) {
|
||||||
|
const chunkSize = 128;
|
||||||
|
setTimeout(function() {
|
||||||
|
if (chunkI < bin.length) {
|
||||||
|
var chunk = bin.substr(chunkI, chunkSize);
|
||||||
|
js = `Serial1.write(atob("${btoa(chunk)}"))\n`;
|
||||||
|
eval(js);
|
||||||
|
|
||||||
|
chunkI += chunkSize;
|
||||||
|
writeChunks(bin, resolve);
|
||||||
|
} else {
|
||||||
|
if (resolve)
|
||||||
|
resolve(); // call outer resolve
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function CASIC_CHECKSUM(cmd) {
|
function CASIC_CHECKSUM(cmd) {
|
||||||
|
@ -53,23 +64,30 @@ function updateLastUpdate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.pull = function(successCallback, failureCallback) {
|
exports.pull = function(successCallback, failureCallback) {
|
||||||
let uri = "https://www.espruino.com/agps/casic.base64";
|
const uri = "https://www.espruino.com/agps/casic.base64";
|
||||||
if (Bangle.http) {
|
if (Bangle.http) {
|
||||||
Bangle.http(uri, {timeout:10000}).then(event => {
|
Bangle.http(uri, {timeout : 10000})
|
||||||
let result = setAGPS(event.resp);
|
.then(event => {
|
||||||
if (result) {
|
setAGPS(event.resp)
|
||||||
|
.then(r => {
|
||||||
updateLastUpdate();
|
updateLastUpdate();
|
||||||
if (successCallback) successCallback();
|
if (successCallback)
|
||||||
} else {
|
successCallback();
|
||||||
console.log("error applying AGPS data");
|
})
|
||||||
if (failureCallback) failureCallback("Error applying AGPS data");
|
.catch((e) => {
|
||||||
}
|
|
||||||
}).catch((e)=>{
|
|
||||||
console.log("error", e);
|
console.log("error", e);
|
||||||
if (failureCallback) failureCallback(e);
|
if (failureCallback)
|
||||||
|
failureCallback(e);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log("error", e);
|
||||||
|
if (failureCallback)
|
||||||
|
failureCallback(e);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log("error: No http method found");
|
console.log("error: No http method found");
|
||||||
if (failureCallback) failureCallback(/*LANG*/"No http method");
|
if (failureCallback)
|
||||||
|
failureCallback(/*LANG*/ "No http method");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "A-GPS Data Downloader App",
|
"name": "A-GPS Data Downloader App",
|
||||||
"shortName":"A-GPS Data",
|
"shortName":"A-GPS Data",
|
||||||
"icon": "agpsdata.png",
|
"icon": "agpsdata.png",
|
||||||
"version":"0.03",
|
"version":"0.04",
|
||||||
"description": "Once installed, this app allows you to download assisted GPS (A-GPS) data directly to your Bangle.js **via Gadgetbridge on an Android phone** when you run the app. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.",
|
"description": "Once installed, this app allows you to download assisted GPS (A-GPS) data directly to your Bangle.js **via Gadgetbridge on an Android phone** when you run the app. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.",
|
||||||
"tags": "boot,tool,assisted,gps,agps,http",
|
"tags": "boot,tool,assisted,gps,agps,http",
|
||||||
"allow_emulator":true,
|
"allow_emulator":true,
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
0.01: New app!
|
0.01: New app!
|
||||||
0.02: Design improvements and fixes.
|
0.02: Design improvements and fixes.
|
||||||
|
0.03: Indicate battery level through line occurrence.
|
||||||
|
0.04: Use widget_utils module.
|
||||||
|
|
|
@ -8,14 +8,16 @@ The original output of stable diffusion is shown here:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
And my implementation is shown here:
|
My implementation is shown below. Note that horizontal lines occur randomly, but the
|
||||||
|
probability is correlated with the battery level. So if your screen contains only
|
||||||
|
a few lines its time to charge your bangle again ;)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
# Thanks to
|
# Thanks to
|
||||||
The great open source community: I used an open source diffusion model (https://github.com/CompVis/stable-diffusion)
|
The great open-source community: I used an open-source diffusion model (https://github.com/CompVis/stable-diffusion)
|
||||||
to generate a watch face for the open source smartwatch BangleJs.
|
to generate a watch face for the open-source smartwatch BangleJs.
|
||||||
|
|
||||||
## Creator
|
## Creator
|
||||||
- [David Peer](https://github.com/peerdavid).
|
- [David Peer](https://github.com/peerdavid).
|
|
@ -37,8 +37,15 @@ function drawBackground() {
|
||||||
g.setFontAlign(0,0);
|
g.setFontAlign(0,0);
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
|
|
||||||
y = 0;
|
var bat = E.getBattery() / 100.0;
|
||||||
|
var y = 0;
|
||||||
while(y < H){
|
while(y < H){
|
||||||
|
// Show less lines in case of small battery level.
|
||||||
|
if(Math.random() > bat){
|
||||||
|
y += 5;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
y += 3 + Math.floor(Math.random() * 10);
|
y += 3 + Math.floor(Math.random() * 10);
|
||||||
g.drawLine(0, y, W, y);
|
g.drawLine(0, y, W, y);
|
||||||
g.drawLine(0, y+1, W, y+1);
|
g.drawLine(0, y+1, W, y+1);
|
||||||
|
@ -103,7 +110,7 @@ function drawDate(){
|
||||||
g.setFontAlign(0,0);
|
g.setFontAlign(0,0);
|
||||||
g.setFontGochiHand();
|
g.setFontGochiHand();
|
||||||
|
|
||||||
var text = ("0"+date.getDate()).substr(-2) + "/" + ("0"+date.getMonth()).substr(-2);
|
var text = ("0"+date.getDate()).substr(-2) + "/" + ("0"+(date.getMonth()+1)).substr(-2);
|
||||||
var w = g.stringWidth(text);
|
var w = g.stringWidth(text);
|
||||||
g.setColor(g.theme.bg);
|
g.setColor(g.theme.bg);
|
||||||
g.fillRect(cx-w/2-4, 20, cx+w/2+4, 40+12);
|
g.fillRect(cx-w/2-4, 20, cx+w/2+4, 40+12);
|
||||||
|
@ -208,8 +215,7 @@ Bangle.loadWidgets();
|
||||||
* so we will blank out the draw() functions of each widget and change the
|
* so we will blank out the draw() functions of each widget and change the
|
||||||
* area to the top bar doesn't get cleared.
|
* area to the top bar doesn't get cleared.
|
||||||
*/
|
*/
|
||||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
require('widget_utils').hide();
|
||||||
|
|
||||||
// Clear the screen once, at startup and draw clock
|
// Clear the screen once, at startup and draw clock
|
||||||
g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear();
|
g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear();
|
||||||
draw();
|
draw();
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "AI Clock",
|
"name": "AI Clock",
|
||||||
"shortName":"AI Clock",
|
"shortName":"AI Clock",
|
||||||
"icon": "aiclock.png",
|
"icon": "aiclock.png",
|
||||||
"version":"0.02",
|
"version":"0.04",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"supports": ["BANGLEJS2"],
|
"supports": ["BANGLEJS2"],
|
||||||
"description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.",
|
"description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.",
|
||||||
|
|
|
@ -34,4 +34,6 @@
|
||||||
0.32: Fix wrong hidden filter
|
0.32: Fix wrong hidden filter
|
||||||
Add option for auto-delete a timer after it expires
|
Add option for auto-delete a timer after it expires
|
||||||
0.33: Allow hiding timers&alarms
|
0.33: Allow hiding timers&alarms
|
||||||
|
0.34: Add "Confirm" option to alarm/timer edit menus
|
||||||
|
0.35: Add automatic translation of more strings
|
||||||
|
|
||||||
|
|
|
@ -128,7 +128,12 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
|
||||||
value: alarm.hidden || false,
|
value: alarm.hidden || false,
|
||||||
onchange: v => alarm.hidden = v
|
onchange: v => alarm.hidden = v
|
||||||
},
|
},
|
||||||
/*LANG*/"Cancel": () => showMainMenu()
|
/*LANG*/"Cancel": () => showMainMenu(),
|
||||||
|
/*LANG*/"Confirm": () => {
|
||||||
|
prepareAlarmForSave(alarm, alarmIndex, time);
|
||||||
|
saveAndReload();
|
||||||
|
showMainMenu();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isNew) {
|
if (!isNew) {
|
||||||
|
@ -178,7 +183,7 @@ function decodeDOW(alarm) {
|
||||||
.map((day, index) => alarm.dow & (1 << (index + firstDayOfWeek)) ? day : "_")
|
.map((day, index) => alarm.dow & (1 << (index + firstDayOfWeek)) ? day : "_")
|
||||||
.join("")
|
.join("")
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
: "Once"
|
: /*LANG*/"Once"
|
||||||
}
|
}
|
||||||
|
|
||||||
function showEditRepeatMenu(repeat, dow, dowChangeCallback) {
|
function showEditRepeatMenu(repeat, dow, dowChangeCallback) {
|
||||||
|
@ -293,7 +298,12 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
|
||||||
onchange: v => timer.hidden = v
|
onchange: v => timer.hidden = v
|
||||||
},
|
},
|
||||||
/*LANG*/"Vibrate": require("buzz_menu").pattern(timer.vibrate, v => timer.vibrate = v),
|
/*LANG*/"Vibrate": require("buzz_menu").pattern(timer.vibrate, v => timer.vibrate = v),
|
||||||
/*LANG*/"Cancel": () => showMainMenu()
|
/*LANG*/"Cancel": () => showMainMenu(),
|
||||||
|
/*LANG*/"Confirm": () => {
|
||||||
|
prepareTimerForSave(timer, timerIndex, time);
|
||||||
|
saveAndReload();
|
||||||
|
showMainMenu();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isNew) {
|
if (!isNew) {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "alarm",
|
"id": "alarm",
|
||||||
"name": "Alarms & Timers",
|
"name": "Alarms & Timers",
|
||||||
"shortName": "Alarms",
|
"shortName": "Alarms",
|
||||||
"version": "0.33",
|
"version": "0.35",
|
||||||
"description": "Set alarms and timers on your Bangle",
|
"description": "Set alarms and timers on your Bangle",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool,alarm,widget",
|
"tags": "tool,alarm,widget",
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
0.01: New App!
|
||||||
|
0.02: Added adjustment for Bangle.js magnetometer heading fix
|
|
@ -224,7 +224,7 @@ Bangle.on('mag', function (m) {
|
||||||
if (isNaN(m.heading))
|
if (isNaN(m.heading))
|
||||||
compass_heading = "---";
|
compass_heading = "---";
|
||||||
else
|
else
|
||||||
compass_heading = 360 - Math.round(m.heading);
|
compass_heading = Math.round(m.heading);
|
||||||
current_colour = g.getColor();
|
current_colour = g.getColor();
|
||||||
g.reset();
|
g.reset();
|
||||||
g.setColor(background_colour);
|
g.setColor(background_colour);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "alpinenav",
|
"id": "alpinenav",
|
||||||
"name": "Alpine Nav",
|
"name": "Alpine Nav",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "App that performs GPS monitoring to track and display position relative to a given origin in realtime",
|
"description": "App that performs GPS monitoring to track and display position relative to a given origin in realtime",
|
||||||
"icon": "app-icon.png",
|
"icon": "app-icon.png",
|
||||||
"tags": "outdoors,gps",
|
"tags": "outdoors,gps",
|
||||||
|
|
|
@ -14,3 +14,6 @@
|
||||||
0.14: Fix timeout of http function not being cleaned up
|
0.14: Fix timeout of http function not being cleaned up
|
||||||
0.15: Allow method/body/headers to be specified for `http` (needs Gadgetbridge 0.68.0b or later)
|
0.15: Allow method/body/headers to be specified for `http` (needs Gadgetbridge 0.68.0b or later)
|
||||||
0.16: Bangle.http now fails immediately if there is no Bluetooth connection (fix #2152)
|
0.16: Bangle.http now fails immediately if there is no Bluetooth connection (fix #2152)
|
||||||
|
0.17: Now kick off Calendar sync as soon as connected to Gadgetbridge
|
||||||
|
0.18: Use new message library
|
||||||
|
If connected to Gadgetbridge, allow GPS forwarding from phone (Gadgetbridge code still not merged)
|
|
@ -20,6 +20,8 @@ It contains:
|
||||||
of Gadgetbridge - making your phone make noise so you can find it.
|
of Gadgetbridge - making your phone make noise so you can find it.
|
||||||
* `Keep Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js
|
* `Keep Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js
|
||||||
keep any messages it has received, or should it delete them?
|
keep any messages it has received, or should it delete them?
|
||||||
|
* `Overwrite GPS` - when GPS is requested by an app, this doesn't use Bangle.js's GPS
|
||||||
|
but instead asks Gadgetbridge on the phone to use the phone's GPS
|
||||||
* `Messages` - launches the messages app, showing a list of messages
|
* `Messages` - launches the messages app, showing a list of messages
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
|
@ -91,10 +91,6 @@
|
||||||
sched.reload();
|
sched.reload();
|
||||||
},
|
},
|
||||||
//TODO perhaps move those in a library (like messages), used also for viewing events?
|
//TODO perhaps move those in a library (like messages), used also for viewing events?
|
||||||
//simple package with events all together
|
|
||||||
"calendarevents" : function() {
|
|
||||||
require("Storage").writeJSON("android.calendar.json", event.events);
|
|
||||||
},
|
|
||||||
//add and remove events based on activity on phone (pebble-like)
|
//add and remove events based on activity on phone (pebble-like)
|
||||||
"calendar" : function() {
|
"calendar" : function() {
|
||||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||||
|
@ -109,7 +105,7 @@
|
||||||
"calendar-" : function() {
|
"calendar-" : function() {
|
||||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||||
//if any of those happen we are out of sync!
|
//if any of those happen we are out of sync!
|
||||||
if (!cal || !Array.isArray(cal)) return;
|
if (!cal || !Array.isArray(cal)) cal = [];
|
||||||
cal = cal.filter(e=>e.id!=event.id);
|
cal = cal.filter(e=>e.id!=event.id);
|
||||||
require("Storage").writeJSON("android.calendar.json", cal);
|
require("Storage").writeJSON("android.calendar.json", cal);
|
||||||
},
|
},
|
||||||
|
@ -130,6 +126,18 @@
|
||||||
request.j(event.err); //r = reJect function
|
request.j(event.err); //r = reJect function
|
||||||
else
|
else
|
||||||
request.r(event); //r = resolve function
|
request.r(event); //r = resolve function
|
||||||
|
},
|
||||||
|
"gps": function() {
|
||||||
|
const settings = require("Storage").readJSON("android.settings.json",1)||{};
|
||||||
|
if (!settings.overwriteGps) return;
|
||||||
|
delete event.t;
|
||||||
|
event.satellites = NaN;
|
||||||
|
event.course = NaN;
|
||||||
|
event.fix = 1;
|
||||||
|
Bangle.emit('gps', event);
|
||||||
|
},
|
||||||
|
"is_gps_active": function() {
|
||||||
|
gbSend({ t: "gps_power", status: Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var h = HANDLERS[event.t];
|
var h = HANDLERS[event.t];
|
||||||
|
@ -170,7 +178,10 @@
|
||||||
|
|
||||||
// Battery monitor
|
// Battery monitor
|
||||||
function sendBattery() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); }
|
function sendBattery() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); }
|
||||||
NRF.on("connect", () => setTimeout(sendBattery, 2000));
|
NRF.on("connect", () => setTimeout(function() {
|
||||||
|
sendBattery();
|
||||||
|
GB({t:"force_calendar_sync_start"}); // send a list of our calendar entries to start off the sync process
|
||||||
|
}, 2000));
|
||||||
Bangle.on("charging", sendBattery);
|
Bangle.on("charging", sendBattery);
|
||||||
if (!settings.keep)
|
if (!settings.keep)
|
||||||
NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect
|
NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect
|
||||||
|
@ -190,6 +201,30 @@
|
||||||
if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id });
|
if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id });
|
||||||
// error/warn here?
|
// error/warn here?
|
||||||
};
|
};
|
||||||
|
// GPS overwrite logic
|
||||||
|
if (settings.overwriteGps) { // if the overwrite option is set../
|
||||||
|
// Save current logic
|
||||||
|
const originalSetGpsPower = Bangle.setGPSPower;
|
||||||
|
// Replace set GPS power logic to suppress activation of gps (and instead request it from the phone)
|
||||||
|
Bangle.setGPSPower = (isOn, appID) => {
|
||||||
|
// if not connected, use old logic
|
||||||
|
if (!NRF.getSecurityStatus().connected) return originalSetGpsPower(isOn, appID);
|
||||||
|
// Emulate old GPS power logic
|
||||||
|
if (!Bangle._PWR) Bangle._PWR={};
|
||||||
|
if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[];
|
||||||
|
if (!appID) appID="?";
|
||||||
|
if (isOn && !Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.push(appID);
|
||||||
|
if (!isOn && Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.splice(Bangle._PWR.GPS.indexOf(appID),1);
|
||||||
|
let pwr = Bangle._PWR.GPS.length>0;
|
||||||
|
gbSend({ t: "gps_power", status: pwr });
|
||||||
|
return pwr;
|
||||||
|
}
|
||||||
|
// Replace check if the GPS is on to check the _PWR variable
|
||||||
|
Bangle.isGPSOn = () => {
|
||||||
|
return Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// remove settings object so it's not taking up RAM
|
// remove settings object so it's not taking up RAM
|
||||||
delete settings;
|
delete settings;
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
"id": "android",
|
"id": "android",
|
||||||
"name": "Android Integration",
|
"name": "Android Integration",
|
||||||
"shortName": "Android",
|
"shortName": "Android",
|
||||||
"version": "0.16",
|
"version": "0.18",
|
||||||
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool,system,messages,notifications,gadgetbridge",
|
"tags": "tool,system,messages,notifications,gadgetbridge",
|
||||||
"dependencies": {"messages":"app"},
|
"dependencies": {"messages":"module"},
|
||||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
(function(back) {
|
(function(back) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function gb(j) {
|
function gb(j) {
|
||||||
Bluetooth.println(JSON.stringify(j));
|
Bluetooth.println(JSON.stringify(j));
|
||||||
}
|
}
|
||||||
|
@ -23,7 +26,17 @@
|
||||||
updateSettings();
|
updateSettings();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/*LANG*/"Messages" : ()=>load("messages.app.js"),
|
/*LANG*/"Overwrite GPS" : {
|
||||||
|
value : !!settings.overwriteGps,
|
||||||
|
onchange: newValue => {
|
||||||
|
if (newValue) {
|
||||||
|
Bangle.setGPSPower(false, 'android');
|
||||||
|
}
|
||||||
|
settings.overwriteGps = newValue;
|
||||||
|
updateSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/*LANG*/"Messages" : ()=>require("message").openGUI(),
|
||||||
};
|
};
|
||||||
E.showMenu(mainmenu);
|
E.showMenu(mainmenu);
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,3 +12,5 @@
|
||||||
0.08: fixed calendar weeknumber not shortened to two digits
|
0.08: fixed calendar weeknumber not shortened to two digits
|
||||||
0.09: Use default Bangle formatter for booleans
|
0.09: Use default Bangle formatter for booleans
|
||||||
0.10: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16
|
0.10: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16
|
||||||
|
0.11: Moved enhanced Anton clock to 'Anton Clock Plus' and stripped this clock back down to make it faster for new users (270ms -> 170ms)
|
||||||
|
Modified to avoid leaving functions defined when using setUI({remove:...})
|
||||||
|
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 14 KiB |
|
@ -1,9 +1,8 @@
|
||||||
{
|
{
|
||||||
"id": "antonclk",
|
"id": "antonclk",
|
||||||
"name": "Anton Clock",
|
"name": "Anton Clock",
|
||||||
"version": "0.10",
|
"version": "0.11",
|
||||||
"description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.",
|
"description": "A simple clock using the bold Anton font. See `Anton Clock Plus` for an enhanced version",
|
||||||
"readme":"README.md",
|
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot.png"}],
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
@ -12,8 +11,6 @@
|
||||||
"allow_emulator": true,
|
"allow_emulator": true,
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"antonclk.app.js","url":"app.js"},
|
{"name":"antonclk.app.js","url":"app.js"},
|
||||||
{"name":"antonclk.settings.js","url":"settings.js"},
|
|
||||||
{"name":"antonclk.img","url":"app-icon.js","evaluate":true}
|
{"name":"antonclk.img","url":"app-icon.js","evaluate":true}
|
||||||
],
|
]
|
||||||
"data": [{"name":"antonclk.json"}]
|
|
||||||
}
|
}
|
||||||
|
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 2.8 KiB |
|
@ -0,0 +1,15 @@
|
||||||
|
0.01: New App!
|
||||||
|
0.02: Load widgets after setUI so widclk knows when to hide
|
||||||
|
0.03: Clock now shows day of week under date.
|
||||||
|
0.04: Clock can optionally show seconds, date optionally in ISO-8601 format, weekdays and uppercase configurable, too.
|
||||||
|
0.05: Clock can optionally show ISO-8601 calendar weeknumber (default: Off)
|
||||||
|
when weekday name "Off": week #:<num>
|
||||||
|
when weekday name "On": weekday name is cut at 6th position and .#<week num> is added
|
||||||
|
0.06: fixes #1271 - wrong settings name
|
||||||
|
when weekday name and calendar weeknumber are on then display is <weekday short> #<calweek>
|
||||||
|
week is buffered until date or timezone changes
|
||||||
|
0.07: align default settings with app.js (otherwise the initial displayed settings will be confusing to users)
|
||||||
|
0.08: fixed calendar weeknumber not shortened to two digits
|
||||||
|
0.09: Use default Bangle formatter for booleans
|
||||||
|
0.10: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16
|
||||||
|
Modified to avoid leaving functions defined when using setUI({remove:...})
|
|
@ -1,6 +1,6 @@
|
||||||
# Anton Clock - Large font digital watch with seconds and date
|
# Anton Clock Plus - Large font digital watch with seconds and date
|
||||||
|
|
||||||
Anton clock uses the "Anton" bold font to show the time in a clear, easily readable manner. On the Bangle.js 2, the time can be read easily even if the screen is locked and unlit.
|
Anton Clock Plus uses the "Anton" bold font to show the time in a clear, easily readable manner. On the Bangle.js 2, the time can be read easily even if the screen is locked and unlit.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
@ -16,16 +16,16 @@ The basic time representation only shows hours and minutes of the current time.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Install Anton clock through the Bangle.js app loader.
|
* Install Anton Clock Plus through the Bangle.js app loader.
|
||||||
Configure it through the default Bangle.js configuration mechanism
|
* Configure it through the default Bangle.js configuration mechanism
|
||||||
(Settings app, "Apps" menu, "Anton clock" submenu).
|
(Settings app, "Apps" menu, "Anton clock" submenu).
|
||||||
If you like it, make it your default watch face
|
* If you like it, make it your default watch face
|
||||||
(Settings app, "System" menu, "Clock" submenu, select "Anton clock").
|
(Settings app, "System" menu, "Clock" submenu, select "Anton clock").
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Anton clock is configured by the standard settings mechanism of Bangle.js's operating system:
|
Anton Clock is configured by the standard settings mechanism of Bangle.js's operating system:
|
||||||
Open the "Settings" app, then the "Apps" submenu and below it the "Anton clock" menu.
|
Open the `Settings` app, then the `Apps` submenu and below it the `Anton Clock+` menu.
|
||||||
You configure Anton clock through several "on/off" switches in two menus.
|
You configure Anton clock through several "on/off" switches in two menus.
|
||||||
|
|
||||||
### The main menu
|
### The main menu
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwgf/AH4At/l/Aofgh4DB+EAj4REQoM/AgP4AoeACIoLCg4FB4AFDCIwLCgAROgYIB8EBAoUH/gVBCIxQBCKYHBCJp9DI4ICBLJYRCn4RQEYMOR5ARDIgIRMYQZZBgARGZwZBDCKQrCgEDR5AdBUIQRJDoLXFCJD7J/xrICIQFCn4RH/4LDAoTaCCI4Ar/LLDCBfypMkCgMkyV/CJOSCIOf5IRGFwOfCJNP//JnmT588z/+pM/BYIRCk4RC/88+f/n4RCngRCz1JCIf5/nzGoQRIHwXPCIPJI4f8CJHJGQJKCCI59LCI5ZCCJ/+v/kBoM/+V/HIJrHBYJWB/JKB5x9JEYP8AQKdBpwRL841Dp41KZoTxBHYTXBWY77PCKKhJ/4/CcgMkXoQAiA="))
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"id": "antonclkplus",
|
||||||
|
"name": "Anton Clock Plus",
|
||||||
|
"shortName": "Anton Clock+",
|
||||||
|
"version": "0.10",
|
||||||
|
"description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.",
|
||||||
|
"readme":"README.md",
|
||||||
|
"icon": "app.png",
|
||||||
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
"type": "clock",
|
||||||
|
"tags": "clock",
|
||||||
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
|
"allow_emulator": true,
|
||||||
|
"storage": [
|
||||||
|
{"name":"antonclkplus.app.js","url":"app.js"},
|
||||||
|
{"name":"antonclkplus.settings.js","url":"settings.js"},
|
||||||
|
{"name":"antonclkplus.img","url":"app-icon.js","evaluate":true}
|
||||||
|
],
|
||||||
|
"data": [{"name":"antonclkplus.json"}]
|
||||||
|
}
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -5,6 +5,7 @@
|
||||||
"version": "0.08",
|
"version": "0.08",
|
||||||
"description": "A clock based on the portal series",
|
"description": "A clock based on the portal series",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
"supports": ["BANGLEJS2"],
|
"supports": ["BANGLEJS2"],
|
||||||
|
|
After Width: | Height: | Size: 5.4 KiB |
|
@ -2,3 +2,4 @@
|
||||||
0.02: Fixed Whirlpool galaxy RA/DA, larger compass display, fixed moonphase overlapping battery widget
|
0.02: Fixed Whirlpool galaxy RA/DA, larger compass display, fixed moonphase overlapping battery widget
|
||||||
0.03: Update to use Bangle.setUI instead of setWatch
|
0.03: Update to use Bangle.setUI instead of setWatch
|
||||||
0.04: Tell clock widgets to hide.
|
0.04: Tell clock widgets to hide.
|
||||||
|
0.05: Added adjustment for Bangle.js magnetometer heading fix
|
||||||
|
|
|
@ -834,7 +834,7 @@ Bangle.on('mag', function (m) {
|
||||||
if (isNaN(m.heading))
|
if (isNaN(m.heading))
|
||||||
compass_heading = "---";
|
compass_heading = "---";
|
||||||
else
|
else
|
||||||
compass_heading = 360 - Math.round(m.heading);
|
compass_heading = Math.round(m.heading);
|
||||||
// g.setColor("#000000");
|
// g.setColor("#000000");
|
||||||
// g.fillRect(160, 10, 160, 20);
|
// g.fillRect(160, 10, 160, 20);
|
||||||
g.setColor(display_colour);
|
g.setColor(display_colour);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "astral",
|
"id": "astral",
|
||||||
"name": "Astral Clock",
|
"name": "Astral Clock",
|
||||||
"version": "0.04",
|
"version": "0.05",
|
||||||
"description": "Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting. See Readme before using.",
|
"description": "Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting. See Readme before using.",
|
||||||
"icon": "app-icon.png",
|
"icon": "app-icon.png",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
|
|
@ -13,3 +13,4 @@
|
||||||
0.13: Add font setting
|
0.13: Add font setting
|
||||||
0.14: Use ClockFace_menu.addItems
|
0.14: Use ClockFace_menu.addItems
|
||||||
0.15: Add Power saving option
|
0.15: Add Power saving option
|
||||||
|
0.16: Support Fast Loading
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
/* jshint esversion: 6 */
|
/* jshint esversion: 6 */
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* A simple digital clock showing seconds as a bar
|
* A simple digital clock showing seconds as a bar
|
||||||
**/
|
**/
|
||||||
|
@ -14,7 +15,7 @@ let locale = require("locale");
|
||||||
}
|
}
|
||||||
|
|
||||||
let barW = 0, prevX = 0;
|
let barW = 0, prevX = 0;
|
||||||
function renderBar(l) {
|
const renderBar = function (l) {
|
||||||
"ram";
|
"ram";
|
||||||
if (l) prevX = 0; // called from Layout: drawing area was cleared
|
if (l) prevX = 0; // called from Layout: drawing area was cleared
|
||||||
else l = clock.layout.bar;
|
else l = clock.layout.bar;
|
||||||
|
@ -27,7 +28,7 @@ function renderBar(l) {
|
||||||
prevX = x2;
|
prevX = x2;
|
||||||
}
|
}
|
||||||
|
|
||||||
function timeText(date) {
|
const timeText = function(date) {
|
||||||
if (!clock.is12Hour) {
|
if (!clock.is12Hour) {
|
||||||
return locale.time(date, true);
|
return locale.time(date, true);
|
||||||
}
|
}
|
||||||
|
@ -40,16 +41,14 @@ function timeText(date) {
|
||||||
}
|
}
|
||||||
return locale.time(date12, true);
|
return locale.time(date12, true);
|
||||||
}
|
}
|
||||||
function ampmText(date) {
|
const ampmText = date => (clock.is12Hour && locale.hasMeridian) ? locale.meridian(date) : "";
|
||||||
return (clock.is12Hour && locale.hasMeridian) ? locale.meridian(date) : "";
|
const dateText = date => {
|
||||||
}
|
|
||||||
function dateText(date) {
|
|
||||||
const dayName = locale.dow(date, true),
|
const dayName = locale.dow(date, true),
|
||||||
month = locale.month(date, true),
|
month = locale.month(date, true),
|
||||||
day = date.getDate();
|
day = date.getDate();
|
||||||
const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}`;
|
const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}`;
|
||||||
return `${dayName} ${dayMonth}`;
|
return `${dayName} ${dayMonth}`;
|
||||||
}
|
};
|
||||||
|
|
||||||
const ClockFace = require("ClockFace"),
|
const ClockFace = require("ClockFace"),
|
||||||
clock = new ClockFace({
|
clock = new ClockFace({
|
||||||
|
@ -110,15 +109,20 @@ const ClockFace = require("ClockFace"),
|
||||||
prevX = 0; // force redraw of bar
|
prevX = 0; // force redraw of bar
|
||||||
this.layout.forgetLazyState();
|
this.layout.forgetLazyState();
|
||||||
},
|
},
|
||||||
|
remove: function() {
|
||||||
|
if (this.onLock) Bangle.removeListener("lock", this.onLock);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// power saving: only update once a minute while locked, hide bar
|
// power saving: only update once a minute while locked, hide bar
|
||||||
if (clock.powerSave) {
|
if (clock.powerSave) {
|
||||||
Bangle.on("lock", lock => {
|
clock.onLock = lock => {
|
||||||
clock.precision = lock ? 60 : 1;
|
clock.precision = lock ? 60 : 1;
|
||||||
clock.tick();
|
clock.tick();
|
||||||
renderBar(); // hide/redraw bar right away
|
renderBar(); // hide/redraw bar right away
|
||||||
});
|
}
|
||||||
|
Bangle.on("lock", clock.onLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
clock.start();
|
clock.start();
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "barclock",
|
"id": "barclock",
|
||||||
"name": "Bar Clock",
|
"name": "Bar Clock",
|
||||||
"version": "0.15",
|
"version": "0.16",
|
||||||
"description": "A simple digital clock showing seconds as a bar",
|
"description": "A simple digital clock showing seconds as a bar",
|
||||||
"icon": "clock-bar.png",
|
"icon": "clock-bar.png",
|
||||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}],
|
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}],
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
0.01: Display pressure as number and hand
|
0.01: Display pressure as number and hand
|
||||||
0.02: Use theme color
|
0.02: Use theme color
|
||||||
0.03: workaround for some firmwares that return 'undefined' for first call to barometer
|
0.03: workaround for some firmwares that return 'undefined' for first call to barometer
|
||||||
|
0.04: Update every second, go back with short button press
|
||||||
|
|
|
@ -59,6 +59,7 @@ function drawTicks(){
|
||||||
function drawScaleLabels(){
|
function drawScaleLabels(){
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
g.setFont("Vector",12);
|
g.setFont("Vector",12);
|
||||||
|
g.setFontAlign(-1,-1);
|
||||||
|
|
||||||
let label = MIN;
|
let label = MIN;
|
||||||
for (let i=0;i <= NUMBER_OF_LABELS; i++){
|
for (let i=0;i <= NUMBER_OF_LABELS; i++){
|
||||||
|
@ -103,22 +104,29 @@ function drawIcons() {
|
||||||
}
|
}
|
||||||
|
|
||||||
g.setBgColor(g.theme.bg);
|
g.setBgColor(g.theme.bg);
|
||||||
|
|
||||||
|
try {
|
||||||
|
function baroHandler(data) {
|
||||||
g.clear();
|
g.clear();
|
||||||
|
|
||||||
drawTicks();
|
drawTicks();
|
||||||
drawScaleLabels();
|
drawScaleLabels();
|
||||||
drawIcons();
|
drawIcons();
|
||||||
|
if (data!==undefined) {
|
||||||
try {
|
|
||||||
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));
|
drawHand(Math.round(data.pressure));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Bangle.getPressure().then(baroHandler);
|
Bangle.getPressure().then(baroHandler);
|
||||||
|
setInterval(() => Bangle.getPressure().then(baroHandler), 1000);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
if (e !== undefined) {
|
||||||
print(e.message);
|
print(e.message);
|
||||||
print("barometer not supporter, show a demo value");
|
}
|
||||||
|
print("barometer not supported, show a demo value");
|
||||||
drawHand(MIN);
|
drawHand(MIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bangle.setUI({
|
||||||
|
mode : "custom",
|
||||||
|
back : function() {load();}
|
||||||
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{ "id": "barometer",
|
{ "id": "barometer",
|
||||||
"name": "Barometer",
|
"name": "Barometer",
|
||||||
"shortName":"Barometer",
|
"shortName":"Barometer",
|
||||||
"version":"0.03",
|
"version":"0.04",
|
||||||
"description": "A simple barometer that displays the current air pressure",
|
"description": "A simple barometer that displays the current air pressure",
|
||||||
"icon": "barometer.png",
|
"icon": "barometer.png",
|
||||||
"tags": "tool,outdoors",
|
"tags": "tool,outdoors",
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: First version
|
|
@ -0,0 +1,5 @@
|
||||||
|
# BarWatch - an experimental watch
|
||||||
|
|
||||||
|
For too long the watches have shown the time with digits or hands. No more!
|
||||||
|
With this stylish watch the time is represented by bars. Up to 24 as the day goes by.
|
||||||
|
Practical? Not really, but a different look!
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("l0uwkE/4A/AH4A/AB0gicQmUB+EPgEigExh8gj8A+ECAgMQn4WCgcACyotWC34W/C34W/CycACw0wgYWFBYIWCAAc/+YGHCAgNFACkxl8hGYwAMLYUvCykQC34WycoIW/C34W0gAWTmUjkUzkbmSAFY="))
|
|
@ -0,0 +1,76 @@
|
||||||
|
// timeout used to update every minute
|
||||||
|
var drawTimeout;
|
||||||
|
|
||||||
|
// schedule a draw for the next minute
|
||||||
|
function queueDraw() {
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = setTimeout(function() {
|
||||||
|
drawTimeout = undefined;
|
||||||
|
draw();
|
||||||
|
}, 60000 - (Date.now() % 60000));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
g.reset();
|
||||||
|
|
||||||
|
if(g.theme.dark){
|
||||||
|
g.setColor(1,1,1);
|
||||||
|
}else{
|
||||||
|
g.setColor(0,0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// work out how to display the current time
|
||||||
|
var d = new Date();
|
||||||
|
var h = d.getHours(), m = d.getMinutes();
|
||||||
|
|
||||||
|
// hour bars
|
||||||
|
var bx_offset = 10, by_offset = 35;
|
||||||
|
var b_width = 8, b_height = 60;
|
||||||
|
var b_space = 5;
|
||||||
|
|
||||||
|
for(var i=0; i<h; i++){
|
||||||
|
if(i > 11){
|
||||||
|
by_offset = 105;
|
||||||
|
}
|
||||||
|
var iter = i % 12;
|
||||||
|
//console.log(iter);
|
||||||
|
g.fillRect(bx_offset+(b_width*(iter+1))+(b_space*iter),
|
||||||
|
by_offset,
|
||||||
|
bx_offset+(b_width*iter)+(b_space*iter),
|
||||||
|
by_offset+b_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// minute bar
|
||||||
|
if(h > 11){
|
||||||
|
by_offset = 105;
|
||||||
|
}
|
||||||
|
var m_bar = h % 12;
|
||||||
|
if(m != 0){
|
||||||
|
g.fillRect(bx_offset+(b_width*(m_bar+1))+(b_space*m_bar),
|
||||||
|
by_offset+b_height-m,
|
||||||
|
bx_offset+(b_width*m_bar)+(b_space*m_bar),
|
||||||
|
by_offset+b_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// queue draw in one minute
|
||||||
|
queueDraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the screen once, at startup
|
||||||
|
g.clear();
|
||||||
|
// draw immediately at first
|
||||||
|
draw();
|
||||||
|
// Stop updates when LCD is off, restart when on
|
||||||
|
Bangle.on('lcdPower',on=>{
|
||||||
|
if (on) {
|
||||||
|
draw(); // draw immediately, queue redraw
|
||||||
|
} else { // stop draw timer
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Bangle.setUI("clock");
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
After Width: | Height: | Size: 973 B |
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"id": "barwatch",
|
||||||
|
"name": "BarWatch",
|
||||||
|
"shortName":"BarWatch",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "A watch that displays the time using bars. One bar for each hour.",
|
||||||
|
"readme": "README.md",
|
||||||
|
"icon": "screenshot.png",
|
||||||
|
"tags": "clock",
|
||||||
|
"type": "clock",
|
||||||
|
"allow_emulator":true,
|
||||||
|
"screenshots" : [ { "url": "screenshot.png" } ],
|
||||||
|
"supports" : ["BANGLEJS2"],
|
||||||
|
"storage": [
|
||||||
|
{"name":"barwatch.app.js","url":"app.js"},
|
||||||
|
{"name":"barwatch.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
}
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,3 @@
|
||||||
|
0.01: New App!
|
||||||
|
0.02: Added adjustment for Bangle.js magnetometer heading fix
|
||||||
|
Bangle.js 2 compatibility
|
|
@ -1 +1 @@
|
||||||
require("heatshrink").decompress(atob("mEwghC/AB0O/4AG8AXNgYXHmAXl94XH+AXNn4XH/wXW+YX/C6oWHAAIXN7sz9vdAAoXN9sznvuAAXf/vuC53jC4Xd7wXQ93jn3u9vv9vt7wXT/4tBAgIXQ7wvCC4PgC5sO6czIQJfBC6PumaPDC6wwCC50NYAJcBVgIDBCxrAFbgYXP7yoDF6TADL4YXPVAIXCRyAXC7wXW9zwBC6cNC9zABC4gWQC653CR4fQC6x3TF6gXXI4M9d6wAEC9EN73dAAZfQgczAAkwC/4XXAH4"))
|
require("heatshrink").decompress(atob("mEw4cA///wH9/++1P+u3//3/qv/gv+KHkJkmABxcBBwNJkmQCJYOByQCCCBUCCItJkARQkgQHggLBku25IRDJQ4LCtu27Mt2RKJCInbAQIRLpYROglt24OB6wSC7dwLQ4LB9u2EgfbsARJ8u2mwRO+u3CNJtHCJFpCINALJoRCpCiGBoMSdQcpegIRGyaPB+QRDkARIyQRBc4YRKyet23iCJxHB6QRBzOJCJ+dCJY1CpfMGphrCp2YNZlL54CBEZgLBAQoRBiTFFCNMvmQRPndiEcJHEyQQECJMpAYIRQyARQwAROI4IAGB4wCBNAoRmhIRHCA4A/AAo"))
|
||||||
|
|
|
@ -127,6 +127,8 @@
|
||||||
var img_nofix = require("heatshrink").decompress(atob("mUyxH+ACYhJDygtYGsqLVF8u02gziGBoyhQ5gwDGRozRGCQydGCgybGCwyZC5gAaGPQwnGRAwpGQ4xwGFYyFDKsrlYxYDCsBmUyg4yXLyUsFwMyq1WAgUsNCRjUmVXroAEq8yMbcllkskwCEkplDmQwDq0sC54xEHQ9RqQAGqIwCFgOBAASYBSgMBltRAA0sgJsOGJeBxAAGwMrgIXIloxOJYNSvl8CwIDCqMBlYxNC4wxQDIOCwVYDIIDBGJ9YwV8rADBwRJCSqAVCAYaVMC4oxCPYYxQSo4xMSpIxPY4T5HY54XIMbIxKgwXKfKjhEllWGJNWlgXJGLNXruCGI+CrtXGKP+GJB9HMZ6VO/wxJcI8lfJclfKAxKfJEAGJIXLGKSvBWYQZCMZbfEqTHBGJYyFfIo1DGJ4tDGJQwCGJB9IMZyVNGIYyEfJQxPfJgwEMgoZJgAxMltRAA0tGJQyEksslkmAQklGINXxDTBFwIDCq8rC4YACC4gwJMowAJldWAAwwBABowIGJ4AYGJIymGBQylGBgyjGBwyhGCAzeF6YycGCwzYF7IzVF7o1PDqYA=="));
|
var img_nofix = require("heatshrink").decompress(atob("mUyxH+ACYhJDygtYGsqLVF8u02gziGBoyhQ5gwDGRozRGCQydGCgybGCwyZC5gAaGPQwnGRAwpGQ4xwGFYyFDKsrlYxYDCsBmUyg4yXLyUsFwMyq1WAgUsNCRjUmVXroAEq8yMbcllkskwCEkplDmQwDq0sC54xEHQ9RqQAGqIwCFgOBAASYBSgMBltRAA0sgJsOGJeBxAAGwMrgIXIloxOJYNSvl8CwIDCqMBlYxNC4wxQDIOCwVYDIIDBGJ9YwV8rADBwRJCSqAVCAYaVMC4oxCPYYxQSo4xMSpIxPY4T5HY54XIMbIxKgwXKfKjhEllWGJNWlgXJGLNXruCGI+CrtXGKP+GJB9HMZ6VO/wxJcI8lfJclfKAxKfJEAGJIXLGKSvBWYQZCMZbfEqTHBGJYyFfIo1DGJ4tDGJQwCGJB9IMZyVNGIYyEfJQxPfJgwEMgoZJgAxMltRAA0tGJQyEksslkmAQklGINXxDTBFwIDCq8rC4YACC4gwJMowAJldWAAwwBABowIGJ4AYGJIymGBQylGBgyjGBwyhGCAzeF6YycGCwzYF7IzVF7o1PDqYA=="));
|
||||||
var img_fix = require("heatshrink").decompress(atob("mUyxH+ACYhJDygtYGsqLVF94zaDYkq6wAOlQyYJo2A63VAAIoC2m0GI16My5/H5/V64ABGQIwBGQ+rTKwWHkhiBGIYwDGQ3VZioVIqoiBGAJhEGRFPGSYTIYwQxCGA4yFqodJGKeqSgQwJGQmkGKQSJfAYwLGQfPDxQwRgHVfAi/EAA4xLGQwRLYwb5BABoxQCBcA43G5wABAgIAMEBgxQ0QxB54xB5gAG4xgBBYOiGJ4PMGInPGIhcCGIt4EJoxPvHM5oxBGAnO6xrCGoXMqgxdpwxD5qQFL4QADlQxdgAhBGILIDMYoADEBwwPgCHBfQzHDAAb4NACTIIAA74OACLIIMo7GOACQoBZAoHBHQPNA4QwggGiZBA5B54HBY0DIKMYtUGMMqFYLIGY4jGhZAr6FAAYwiZAgxIY0TIFfQgADvAfR/zISGJTGR/wxRkj6CGJBiSGKL6DGP4xOGSKVDGAwxRGAQxU5oxcGR75DGJEkGCYxPlXM5vPGA/MlQxUGR1OGIL4I5lOGCgyOqgxBShHMqgwVGJt4GJd4GKwyMvHG5vGABAxMGBQyM1mtABWsGC4yLGBYABGDAyKGKwwQGZKVUF6b/OABowWGbAvZGaovdGp4dTA"));
|
var img_fix = require("heatshrink").decompress(atob("mUyxH+ACYhJDygtYGsqLVF94zaDYkq6wAOlQyYJo2A63VAAIoC2m0GI16My5/H5/V64ABGQIwBGQ+rTKwWHkhiBGIYwDGQ3VZioVIqoiBGAJhEGRFPGSYTIYwQxCGA4yFqodJGKeqSgQwJGQmkGKQSJfAYwLGQfPDxQwRgHVfAi/EAA4xLGQwRLYwb5BABoxQCBcA43G5wABAgIAMEBgxQ0QxB54xB5gAG4xgBBYOiGJ4PMGInPGIhcCGIt4EJoxPvHM5oxBGAnO6xrCGoXMqgxdpwxD5qQFL4QADlQxdgAhBGILIDMYoADEBwwPgCHBfQzHDAAb4NACTIIAA74OACLIIMo7GOACQoBZAoHBHQPNA4QwggGiZBA5B54HBY0DIKMYtUGMMqFYLIGY4jGhZAr6FAAYwiZAgxIY0TIFfQgADvAfR/zISGJTGR/wxRkj6CGJBiSGKL6DGP4xOGSKVDGAwxRGAQxU5oxcGR75DGJEkGCYxPlXM5vPGA/MlQxUGR1OGIL4I5lOGCgyOqgxBShHMqgwVGJt4GJd4GKwyMvHG5vGABAxMGBQyM1mtABWsGC4yLGBYABGDAyKGKwwQGZKVUF6b/OABowWGbAvZGaovdGp4dTA"));
|
||||||
|
|
||||||
|
var W = g.getWidth(), H = g.getHeight();
|
||||||
|
|
||||||
// https://github.com/Leaflet/Leaflet/blob/master/src/geo/projection/Projection.SphericalMercator.js
|
// https://github.com/Leaflet/Leaflet/blob/master/src/geo/projection/Projection.SphericalMercator.js
|
||||||
function project(latlong) {
|
function project(latlong) {
|
||||||
var d = Math.PI / 180,
|
var d = Math.PI / 180,
|
||||||
|
@ -170,32 +172,30 @@ Bangle.on('GPS', function(f) {
|
||||||
|
|
||||||
Bangle.on('mag', function(m) {
|
Bangle.on('mag', function(m) {
|
||||||
if (!Bangle.isLCDOn()) return;
|
if (!Bangle.isLCDOn()) return;
|
||||||
var headingrad = m.heading*Math.PI/180; // in radians
|
var headingrad = (360-m.heading)*Math.PI/180; // in radians
|
||||||
if (!isFinite(headingrad)) headingrad=0;
|
if (!isFinite(headingrad)) headingrad=0;
|
||||||
if (nearest)
|
if (nearest)
|
||||||
g.drawImage(img_fix,120,120,{
|
g.drawImage(img_fix,W/2,H/2,{
|
||||||
rotate: (Math.PI/2)+headingrad-nearestangle,
|
rotate: (Math.PI/2)+headingrad-nearestangle,
|
||||||
scale:3,
|
scale:3,
|
||||||
});
|
});
|
||||||
else
|
else
|
||||||
g.drawImage(img_nofix,120,120,{
|
g.drawImage(img_nofix,W/2,H/2,{
|
||||||
rotate: headingrad,
|
rotate: headingrad,
|
||||||
scale:2,
|
scale:2,
|
||||||
});
|
});
|
||||||
g.clearRect(60,0,180,24);
|
g.clearRect(0,0,W,24).setFontAlign(0,0).setFont("6x8");
|
||||||
g.setFontAlign(0,0);
|
|
||||||
g.setFont("6x8");
|
|
||||||
if (fix.fix) {
|
if (fix.fix) {
|
||||||
g.drawString(nearest ? nearest.name : "---",120,4);
|
g.drawString(nearest ? nearest.name : "---",W/2,4);
|
||||||
g.setFont("6x8",2);
|
g.setFont("6x8",2);
|
||||||
g.drawString(nearest ? Math.round(nearestdist)+"m" : "---",120,16);
|
g.drawString(nearest ? Math.round(nearestdist)+"m" : "---",W/2,16);
|
||||||
} else {
|
} else {
|
||||||
g.drawString(fix.satellites+" satellites",120,4);
|
g.drawString(fix.satellites+" satellites",W/2,4);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Bangle.setCompassPower(1);
|
Bangle.setCompassPower(1);
|
||||||
Bangle.setGPSPower(1);
|
Bangle.setGPSPower(1);
|
||||||
g.clear();`;
|
g.setColor("#fff").setBgColor("#000").clear();`;
|
||||||
|
|
||||||
sendCustomizedApp({
|
sendCustomizedApp({
|
||||||
storage:[
|
storage:[
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"id": "beer",
|
"id": "beer",
|
||||||
"name": "Beer Compass",
|
"name": "Beer Compass",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "Uploads all the pubs in an area onto your watch, so it can always point you at the nearest one",
|
"description": "Uploads all the pubs in an area onto your watch, so it can always point you at the nearest one",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "",
|
"tags": "",
|
||||||
"supports": ["BANGLEJS"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"custom": "custom.html",
|
"custom": "custom.html",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"beer.app.js"},
|
{"name":"beer.app.js"},
|
||||||
|
|
|
@ -53,3 +53,11 @@
|
||||||
0.47: Add polyfill for setUI with an object as an argument (fix regression for 2v12 devices after Layout module changed)
|
0.47: Add polyfill for setUI with an object as an argument (fix regression for 2v12 devices after Layout module changed)
|
||||||
0.48: Workaround for BTHRM issues on Bangle.js 1 (write .boot files in chunks)
|
0.48: Workaround for BTHRM issues on Bangle.js 1 (write .boot files in chunks)
|
||||||
0.49: Store first found clock as a setting to speed up further boots
|
0.49: Store first found clock as a setting to speed up further boots
|
||||||
|
0.50: Allow setting of screen rotation
|
||||||
|
Remove support for 2v11 and earlier firmware
|
||||||
|
0.51: Remove patches for 2v10 firmware (BEEPSET and setUI)
|
||||||
|
Add patch to ensure that compass heading is corrected on pre-2v15.68 firmware
|
||||||
|
Ensure clock is only fast-loaded if it doesn't contain widgets
|
||||||
|
0.52: Ensure heading patch for pre-2v15.68 firmware applies to getCompass
|
||||||
|
0.53: Add polyfills for pre-2v15.135 firmware for Bangle.load and Bangle.showClock
|
||||||
|
0.54: Fix for invalid version comparison in polyfill
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
// This runs after a 'fresh' boot
|
// This runs after a 'fresh' boot
|
||||||
var s = require("Storage").readJSON("setting.json",1)||{};
|
var s = require("Storage").readJSON("setting.json",1)||{};
|
||||||
|
/* If were being called from JS code in order to load the clock quickly (eg from a launcher)
|
||||||
|
and the clock in question doesn't have widgets, force a normal 'load' as this will then
|
||||||
|
reset everything and remove the widgets. */
|
||||||
|
if (global.__FILE__ && !s.clockHasWidgets) {load();throw "Clock has no widgets, can't fast load";}
|
||||||
|
// Otherwise continue to try and load the clock
|
||||||
var _clkApp = require("Storage").read(s.clock);
|
var _clkApp = require("Storage").read(s.clock);
|
||||||
if (!_clkApp) {
|
if (!_clkApp) {
|
||||||
_clkApp = require("Storage").list(/\.info$/)
|
_clkApp = require("Storage").list(/\.info$/)
|
||||||
|
@ -14,6 +19,7 @@ if (!_clkApp) {
|
||||||
if (_clkApp){
|
if (_clkApp){
|
||||||
s.clock = _clkApp.src;
|
s.clock = _clkApp.src;
|
||||||
_clkApp = require("Storage").read(_clkApp.src);
|
_clkApp = require("Storage").read(_clkApp.src);
|
||||||
|
s.clockHasWidgets = _clkApp.includes("Bangle.loadWidgets");
|
||||||
require("Storage").writeJSON("setting.json", s);
|
require("Storage").writeJSON("setting.json", s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ of the time. */
|
||||||
E.showMessage(/*LANG*/"Updating boot0...");
|
E.showMessage(/*LANG*/"Updating boot0...");
|
||||||
var s = require('Storage').readJSON('setting.json',1)||{};
|
var s = require('Storage').readJSON('setting.json',1)||{};
|
||||||
var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2
|
var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2
|
||||||
|
var FWVERSION = parseFloat(process.env.VERSION.replace("v","").replace(/\.(\d\d)$/,".0$1"));
|
||||||
var boot = "", bootPost = "";
|
var boot = "", bootPost = "";
|
||||||
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed
|
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed
|
||||||
var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT);
|
var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT);
|
||||||
|
@ -62,23 +63,6 @@ if (s.ble===false) boot += `if (!NRF.getSecurityStatus().connected) NRF.sleep();
|
||||||
if (s.timeout!==undefined) boot += `Bangle.setLCDTimeout(${s.timeout});\n`;
|
if (s.timeout!==undefined) boot += `Bangle.setLCDTimeout(${s.timeout});\n`;
|
||||||
if (!s.timeout) boot += `Bangle.setLCDPower(1);\n`;
|
if (!s.timeout) boot += `Bangle.setLCDPower(1);\n`;
|
||||||
boot += `E.setTimeZone(${s.timezone});`;
|
boot += `E.setTimeZone(${s.timezone});`;
|
||||||
// Set vibrate, beep, etc IF on older firmwares
|
|
||||||
if (!Bangle.F_BEEPSET) {
|
|
||||||
if (!s.vibrate) boot += `Bangle.buzz=Promise.resolve;\n`
|
|
||||||
if (s.beep===false) boot += `Bangle.beep=Promise.resolve;\n`
|
|
||||||
else if (s.beep=="vib" && !BANGLEJS2) boot += `Bangle.beep = function (time, freq) {
|
|
||||||
return new Promise(function(resolve) {
|
|
||||||
if ((0|freq)<=0) freq=4000;
|
|
||||||
if ((0|time)<=0) time=200;
|
|
||||||
if (time>5000) time=5000;
|
|
||||||
analogWrite(D13,0.1,{freq:freq});
|
|
||||||
setTimeout(function() {
|
|
||||||
digitalWrite(D13,0);
|
|
||||||
resolve();
|
|
||||||
}, time);
|
|
||||||
});
|
|
||||||
};\n`;
|
|
||||||
}
|
|
||||||
// Draw out of memory errors onto the screen
|
// Draw out of memory errors onto the screen
|
||||||
boot += `E.on('errorFlag', function(errorFlags) {
|
boot += `E.on('errorFlag', function(errorFlags) {
|
||||||
g.reset(1).setColor("#ff0000").setFont("6x8").setFontAlign(0,1).drawString(errorFlags,g.getWidth()/2,g.getHeight()-1).flip();
|
g.reset(1).setColor("#ff0000").setFont("6x8").setFontAlign(0,1).drawString(errorFlags,g.getWidth()/2,g.getHeight()-1).flip();
|
||||||
|
@ -92,76 +76,20 @@ if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`;
|
||||||
if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`;
|
if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`;
|
||||||
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${E.toJS(s.passkey.toString())}, mitm:1, display:1});\n`;
|
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${E.toJS(s.passkey.toString())}, mitm:1, display:1});\n`;
|
||||||
if (s.whitelist) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`;
|
if (s.whitelist) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`;
|
||||||
// Pre-2v10 firmwares without a theme/setUI
|
if (s.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation
|
||||||
delete g.theme; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
// ================================================== FIXING OLDER FIRMWARES
|
||||||
if (!g.theme) {
|
if (FWVERSION<215.068) // 2v15.68 and before had compass heading inverted.
|
||||||
boot += `g.theme={fg:-1,bg:0,fg2:-1,bg2:7,fgH:-1,bgH:0x02F7,dark:true};\n`;
|
boot += `Bangle.on('mag',e=>{if(!isNaN(e.heading))e.heading=360-e.heading;});
|
||||||
}
|
Bangle.getCompass=(c=>(()=>{e=c();if(!isNaN(e.heading))e.heading=360-e.heading;return e;}))(Bangle.getCompass);`;
|
||||||
try {
|
|
||||||
Bangle.setUI({}); // In 2v12.xx we added the option for mode to be an object - for 2v12 and earlier, add a fix if it fails with an object supplied
|
|
||||||
} catch(e) {
|
|
||||||
boot += `Bangle._setUI = Bangle.setUI;
|
|
||||||
Bangle.setUI=function(mode, cb) {
|
|
||||||
if (Bangle.uiRemove) {
|
|
||||||
Bangle.uiRemove();
|
|
||||||
delete Bangle.uiRemove;
|
|
||||||
}
|
|
||||||
if ("object"==typeof mode) {
|
|
||||||
// TODO: handle mode.back?
|
|
||||||
mode = mode.mode;
|
|
||||||
}
|
|
||||||
Bangle._setUI(mode, cb);
|
|
||||||
};\n`;
|
|
||||||
}
|
|
||||||
delete E.showScroller; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
|
||||||
if (!E.showScroller) { // added in 2v11 - this is a limited functionality polyfill
|
|
||||||
boot += `E.showScroller = (function(a){function n(){g.reset();b>=l+c&&(c=1+b-l);b<c&&(c=b);g.setColor(g.theme.fg);for(var d=0;d<l;d++){var m=d+c;if(0>m||m>=a.c)break;var f=24+d*a.h;a.draw(m,{x:0,y:f,w:h,h:a.h});d+c==b&&g.setColor(g.theme.fg).drawRect(0,f,h-1,f+a.h-1).drawRect(1,f+1,h-2,f+a.h-2)}g.setColor(c?g.theme.fg:g.theme.bg);g.fillPoly([e,6,e-14,20,e+14,20]);g.setColor(a.c>l+c?g.theme.fg:g.theme.bg);g.fillPoly([e,k-7,e-14,k-21,e+14,k-21])}if(!a)return Bangle.setUI();var b=0,c=0,h=g.getWidth(),
|
|
||||||
k=g.getHeight(),e=h/2,l=Math.floor((k-48)/a.h);g.reset().clearRect(0,24,h-1,k-1);n();Bangle.setUI("updown",d=>{d?(b+=d,0>b&&(b=a.c-1),b>=a.c&&(b=0),n()):a.select(b)})});\n`;
|
|
||||||
}
|
|
||||||
delete g.imageMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
|
||||||
if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfill
|
|
||||||
boot += `Graphics.prototype.imageMetrics=function(src) {
|
|
||||||
if (src[0]) return {width:src[0],height:src[1]};
|
|
||||||
else if ('object'==typeof src) return {
|
|
||||||
width:("width" in src) ? src.width : src.getWidth(),
|
|
||||||
height:("height" in src) ? src.height : src.getHeight()};
|
|
||||||
var im = E.toString(src);
|
|
||||||
return {width:im.charCodeAt(0), height:im.charCodeAt(1)};
|
|
||||||
};\n`;
|
|
||||||
}
|
|
||||||
delete g.stringMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
|
||||||
if (!g.stringMetrics) { // added in 2v11 - this is a limited functionality polyfill
|
|
||||||
boot += `Graphics.prototype.stringMetrics=function(txt) {
|
|
||||||
txt = txt.toString().split("\\n");
|
|
||||||
return {width:Math.max.apply(null,txt.map(x=>g.stringWidth(x))), height:this.getFontHeight()*txt.length};
|
|
||||||
};\n`;
|
|
||||||
}
|
|
||||||
delete g.wrapString; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
|
||||||
if (!g.wrapString) { // added in 2v11 - this is a limited functionality polyfill
|
|
||||||
boot += `Graphics.prototype.wrapString=function(str, maxWidth) {
|
|
||||||
var lines = [];
|
|
||||||
for (var unwrappedLine of str.split("\\n")) {
|
|
||||||
var words = unwrappedLine.split(" ");
|
|
||||||
var line = words.shift();
|
|
||||||
for (var word of words) {
|
|
||||||
if (g.stringWidth(line + " " + word) > maxWidth) {
|
|
||||||
lines.push(line);
|
|
||||||
line = word;
|
|
||||||
} else {
|
|
||||||
line += " " + word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lines.push(line);
|
|
||||||
}
|
|
||||||
return lines;
|
|
||||||
};\n`;
|
|
||||||
}
|
|
||||||
delete Bangle.appRect; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
|
||||||
if (!Bangle.appRect) { // added in 2v11 - polyfill for older firmwares
|
|
||||||
boot += `Bangle.appRect = ((y,w,h)=>({x:0,y:0,w:w,h:h,x2:w-1,y2:h-1}))(g.getWidth(),g.getHeight());
|
|
||||||
(lw=>{ Bangle.loadWidgets = () => { lw(); Bangle.appRect = ((y,w,h)=>({x:0,y:y,w:w,h:h-y,x2:w-1,y2:h-(1+h)}))(global.WIDGETS?24:0,g.getWidth(),g.getHeight()); }; })(Bangle.loadWidgets);\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||||
|
// this is a polyfill without fastloading capability
|
||||||
|
delete Bangle.showClock;
|
||||||
|
if (!Bangle.showClock) boot += `Bangle.showClock = ()=>{load(".bootcde")};\n`;
|
||||||
|
delete Bangle.load;
|
||||||
|
if (!Bangle.load) boot += `Bangle.load = load;\n`;
|
||||||
|
|
||||||
|
// ================================================== BOOT.JS
|
||||||
// Append *.boot.js files
|
// Append *.boot.js files
|
||||||
// These could change bleServices/bleServiceOptions if needed
|
// These could change bleServices/bleServiceOptions if needed
|
||||||
var bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
|
var bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "boot",
|
"id": "boot",
|
||||||
"name": "Bootloader",
|
"name": "Bootloader",
|
||||||
"version": "0.49",
|
"version": "0.54",
|
||||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||||
"icon": "bootloader.png",
|
"icon": "bootloader.png",
|
||||||
"type": "bootloader",
|
"type": "bootloader",
|
||||||
|
|
|
@ -29,3 +29,14 @@
|
||||||
Use default boolean formatter in custom menu and directly apply config if useful
|
Use default boolean formatter in custom menu and directly apply config if useful
|
||||||
Allow recording unmodified internal HR
|
Allow recording unmodified internal HR
|
||||||
Better connection retry handling
|
Better connection retry handling
|
||||||
|
0.13: Less time used during boot if disabled
|
||||||
|
0.14: Allow bonding (Debug menu)
|
||||||
|
Prevent mixing of BT and internal HRM events if both are enabled
|
||||||
|
Always use a grace period (default 0 ms) to decouple some connection steps
|
||||||
|
Device not found errors now utilize increasing timeouts
|
||||||
|
0.15: Fix recording internal sensor
|
||||||
|
Handle fallback to internal sensor consistently if BT bpm is 0
|
||||||
|
Power internal sensor down if not needed for fallback
|
||||||
|
0.16: Set powerdownRequested correctly on BTHRM power on
|
||||||
|
Additional logging on errors
|
||||||
|
Add debug option for disabling active scanning
|
||||||
|
|
|
@ -1,633 +1 @@
|
||||||
(function() {
|
if ((require('Storage').readJSON("bthrm.json", true) || {}).enabled != false) require("bthrm").enable();
|
||||||
var settings = Object.assign(
|
|
||||||
require('Storage').readJSON("bthrm.default.json", true) || {},
|
|
||||||
require('Storage').readJSON("bthrm.json", true) || {}
|
|
||||||
);
|
|
||||||
|
|
||||||
var log = function(text, param){
|
|
||||||
if (global.showStatusInfo)
|
|
||||||
showStatusInfo(text);
|
|
||||||
if (settings.debuglog){
|
|
||||||
var logline = new Date().toISOString() + " - " + text;
|
|
||||||
if (param) logline += ": " + JSON.stringify(param);
|
|
||||||
print(logline);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
log("Settings: ", settings);
|
|
||||||
|
|
||||||
if (settings.enabled){
|
|
||||||
|
|
||||||
var clearCache = function() {
|
|
||||||
return require('Storage').erase("bthrm.cache.json");
|
|
||||||
};
|
|
||||||
|
|
||||||
var getCache = function() {
|
|
||||||
var cache = require('Storage').readJSON("bthrm.cache.json", true) || {};
|
|
||||||
if (settings.btid && settings.btid === cache.id) return cache;
|
|
||||||
clearCache();
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
|
|
||||||
var addNotificationHandler = function(characteristic) {
|
|
||||||
log("Setting notification handler"/*supportedCharacteristics[characteristic.uuid].handler*/);
|
|
||||||
characteristic.on('characteristicvaluechanged', (ev) => supportedCharacteristics[characteristic.uuid].handler(ev.target.value));
|
|
||||||
};
|
|
||||||
|
|
||||||
var writeCache = function(cache) {
|
|
||||||
var oldCache = getCache();
|
|
||||||
if (oldCache !== cache) {
|
|
||||||
log("Writing cache");
|
|
||||||
require('Storage').writeJSON("bthrm.cache.json", cache);
|
|
||||||
} else {
|
|
||||||
log("No changes, don't write cache");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var characteristicsToCache = function(characteristics) {
|
|
||||||
log("Cache characteristics");
|
|
||||||
var cache = getCache();
|
|
||||||
if (!cache.characteristics) cache.characteristics = {};
|
|
||||||
for (var c of characteristics){
|
|
||||||
//"handle_value":16,"handle_decl":15
|
|
||||||
log("Saving handle " + c.handle_value + " for characteristic: ", c);
|
|
||||||
cache.characteristics[c.uuid] = {
|
|
||||||
"handle": c.handle_value,
|
|
||||||
"uuid": c.uuid,
|
|
||||||
"notify": c.properties.notify,
|
|
||||||
"read": c.properties.read
|
|
||||||
};
|
|
||||||
}
|
|
||||||
writeCache(cache);
|
|
||||||
};
|
|
||||||
|
|
||||||
var characteristicsFromCache = function(device) {
|
|
||||||
var service = { device : device }; // fake a BluetoothRemoteGATTService
|
|
||||||
log("Read cached characteristics");
|
|
||||||
var cache = getCache();
|
|
||||||
if (!cache.characteristics) return [];
|
|
||||||
var restored = [];
|
|
||||||
for (var c in cache.characteristics){
|
|
||||||
var cached = cache.characteristics[c];
|
|
||||||
var r = new BluetoothRemoteGATTCharacteristic();
|
|
||||||
log("Restoring characteristic ", cached);
|
|
||||||
r.handle_value = cached.handle;
|
|
||||||
r.uuid = cached.uuid;
|
|
||||||
r.properties = {};
|
|
||||||
r.properties.notify = cached.notify;
|
|
||||||
r.properties.read = cached.read;
|
|
||||||
r.service = service;
|
|
||||||
addNotificationHandler(r);
|
|
||||||
log("Restored characteristic: ", r);
|
|
||||||
restored.push(r);
|
|
||||||
}
|
|
||||||
return restored;
|
|
||||||
};
|
|
||||||
|
|
||||||
log("Start");
|
|
||||||
|
|
||||||
var lastReceivedData={
|
|
||||||
};
|
|
||||||
|
|
||||||
var supportedServices = [
|
|
||||||
"0x180d", // Heart Rate
|
|
||||||
"0x180f", // Battery
|
|
||||||
];
|
|
||||||
|
|
||||||
var bpmTimeout;
|
|
||||||
|
|
||||||
var supportedCharacteristics = {
|
|
||||||
"0x2a37": {
|
|
||||||
//Heart rate measurement
|
|
||||||
active: false,
|
|
||||||
handler: function (dv){
|
|
||||||
var flags = dv.getUint8(0);
|
|
||||||
|
|
||||||
var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit
|
|
||||||
supportedCharacteristics["0x2a37"].active = bpm > 0;
|
|
||||||
log("BTHRM BPM " + supportedCharacteristics["0x2a37"].active);
|
|
||||||
if (supportedCharacteristics["0x2a37"].active) stopFallback();
|
|
||||||
if (bpmTimeout) clearTimeout(bpmTimeout);
|
|
||||||
bpmTimeout = setTimeout(()=>{
|
|
||||||
supportedCharacteristics["0x2a37"].active = false;
|
|
||||||
startFallback();
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
var sensorContact;
|
|
||||||
|
|
||||||
if (flags & 2){
|
|
||||||
sensorContact = !!(flags & 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
var idx = 2 + (flags&1);
|
|
||||||
|
|
||||||
var energyExpended;
|
|
||||||
if (flags & 8){
|
|
||||||
energyExpended = dv.getUint16(idx,1);
|
|
||||||
idx += 2;
|
|
||||||
}
|
|
||||||
var interval;
|
|
||||||
if (flags & 16) {
|
|
||||||
interval = [];
|
|
||||||
var maxIntervalBytes = (dv.byteLength - idx);
|
|
||||||
log("Found " + (maxIntervalBytes / 2) + " rr data fields");
|
|
||||||
for(var i = 0 ; i < maxIntervalBytes / 2; i++){
|
|
||||||
interval[i] = dv.getUint16(idx,1); // in milliseconds
|
|
||||||
idx += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var location;
|
|
||||||
if (lastReceivedData && lastReceivedData["0x180d"] && lastReceivedData["0x180d"]["0x2a38"]){
|
|
||||||
location = lastReceivedData["0x180d"]["0x2a38"];
|
|
||||||
}
|
|
||||||
|
|
||||||
var battery;
|
|
||||||
if (lastReceivedData && lastReceivedData["0x180f"] && lastReceivedData["0x180f"]["0x2a19"]){
|
|
||||||
battery = lastReceivedData["0x180f"]["0x2a19"];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.replace){
|
|
||||||
var repEvent = {
|
|
||||||
bpm: bpm,
|
|
||||||
confidence: (sensorContact || sensorContact === undefined)? 100 : 0,
|
|
||||||
src: "bthrm"
|
|
||||||
};
|
|
||||||
|
|
||||||
log("Emitting HRM", repEvent);
|
|
||||||
Bangle.emit("HRM_int", repEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
var newEvent = {
|
|
||||||
bpm: bpm
|
|
||||||
};
|
|
||||||
|
|
||||||
if (location) newEvent.location = location;
|
|
||||||
if (interval) newEvent.rr = interval;
|
|
||||||
if (energyExpended) newEvent.energy = energyExpended;
|
|
||||||
if (battery) newEvent.battery = battery;
|
|
||||||
if (sensorContact) newEvent.contact = sensorContact;
|
|
||||||
|
|
||||||
log("Emitting BTHRM", newEvent);
|
|
||||||
Bangle.emit("BTHRM", newEvent);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"0x2a38": {
|
|
||||||
//Body sensor location
|
|
||||||
handler: function(dv){
|
|
||||||
if (!lastReceivedData["0x180d"]) lastReceivedData["0x180d"] = {};
|
|
||||||
lastReceivedData["0x180d"]["0x2a38"] = parseInt(dv.buffer, 10);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"0x2a19": {
|
|
||||||
//Battery
|
|
||||||
handler: function (dv){
|
|
||||||
if (!lastReceivedData["0x180f"]) lastReceivedData["0x180f"] = {};
|
|
||||||
lastReceivedData["0x180f"]["0x2a19"] = dv.getUint8(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var device;
|
|
||||||
var gatt;
|
|
||||||
var characteristics = [];
|
|
||||||
var blockInit = false;
|
|
||||||
var currentRetryTimeout;
|
|
||||||
var initialRetryTime = 40;
|
|
||||||
var maxRetryTime = 60000;
|
|
||||||
var retryTime = initialRetryTime;
|
|
||||||
|
|
||||||
var connectSettings = {
|
|
||||||
minInterval: 7.5,
|
|
||||||
maxInterval: 1500
|
|
||||||
};
|
|
||||||
|
|
||||||
var waitingPromise = function(timeout) {
|
|
||||||
return new Promise(function(resolve){
|
|
||||||
log("Start waiting for " + timeout);
|
|
||||||
setTimeout(()=>{
|
|
||||||
log("Done waiting for " + timeout);
|
|
||||||
resolve();
|
|
||||||
}, timeout);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (settings.enabled){
|
|
||||||
Bangle.isBTHRMActive = function (){
|
|
||||||
return supportedCharacteristics["0x2a37"].active;
|
|
||||||
};
|
|
||||||
|
|
||||||
Bangle.isBTHRMOn = function(){
|
|
||||||
return (Bangle._PWR && Bangle._PWR.BTHRM && Bangle._PWR.BTHRM.length > 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
Bangle.isBTHRMConnected = function(){
|
|
||||||
return gatt && gatt.connected;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.replace){
|
|
||||||
Bangle.origIsHRMOn = Bangle.isHRMOn;
|
|
||||||
|
|
||||||
Bangle.isHRMOn = function() {
|
|
||||||
if (settings.enabled && !settings.replace){
|
|
||||||
return Bangle.origIsHRMOn();
|
|
||||||
} else if (settings.enabled && settings.replace){
|
|
||||||
return Bangle.isBTHRMOn();
|
|
||||||
}
|
|
||||||
return Bangle.origIsHRMOn() || Bangle.isBTHRMOn();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var clearRetryTimeout = function(resetTime) {
|
|
||||||
if (currentRetryTimeout){
|
|
||||||
log("Clearing timeout " + currentRetryTimeout);
|
|
||||||
clearTimeout(currentRetryTimeout);
|
|
||||||
currentRetryTimeout = undefined;
|
|
||||||
}
|
|
||||||
if (resetTime) {
|
|
||||||
log("Resetting retry time");
|
|
||||||
retryTime = initialRetryTime;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var retry = function() {
|
|
||||||
log("Retry");
|
|
||||||
|
|
||||||
if (!currentRetryTimeout){
|
|
||||||
|
|
||||||
var clampedTime = retryTime < 100 ? 100 : retryTime;
|
|
||||||
|
|
||||||
log("Set timeout for retry as " + clampedTime);
|
|
||||||
clearRetryTimeout();
|
|
||||||
currentRetryTimeout = setTimeout(() => {
|
|
||||||
log("Retrying");
|
|
||||||
currentRetryTimeout = undefined;
|
|
||||||
initBt();
|
|
||||||
}, clampedTime);
|
|
||||||
|
|
||||||
retryTime = Math.pow(clampedTime, 1.1);
|
|
||||||
if (retryTime > maxRetryTime){
|
|
||||||
retryTime = maxRetryTime;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log("Already in retry...");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var buzzing = false;
|
|
||||||
var onDisconnect = function(reason) {
|
|
||||||
log("Disconnect: " + reason);
|
|
||||||
log("GATT", gatt);
|
|
||||||
log("Characteristics", characteristics);
|
|
||||||
clearRetryTimeout(reason != "Connection Timeout");
|
|
||||||
supportedCharacteristics["0x2a37"].active = false;
|
|
||||||
startFallback();
|
|
||||||
blockInit = false;
|
|
||||||
if (settings.warnDisconnect && !buzzing){
|
|
||||||
buzzing = true;
|
|
||||||
Bangle.buzz(500,0.3).then(()=>waitingPromise(4500)).then(()=>{buzzing = false;});
|
|
||||||
}
|
|
||||||
if (Bangle.isBTHRMOn()){
|
|
||||||
retry();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var createCharacteristicPromise = function(newCharacteristic) {
|
|
||||||
log("Create characteristic promise", newCharacteristic);
|
|
||||||
var result = Promise.resolve();
|
|
||||||
// For values that can be read, go ahead and read them, even if we might be notified in the future
|
|
||||||
// Allows for getting initial state of infrequently updating characteristics, like battery
|
|
||||||
if (newCharacteristic.readValue){
|
|
||||||
result = result.then(()=>{
|
|
||||||
log("Reading data", newCharacteristic);
|
|
||||||
return newCharacteristic.readValue().then((data)=>{
|
|
||||||
if (supportedCharacteristics[newCharacteristic.uuid] && supportedCharacteristics[newCharacteristic.uuid].handler) {
|
|
||||||
supportedCharacteristics[newCharacteristic.uuid].handler(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (newCharacteristic.properties.notify){
|
|
||||||
result = result.then(()=>{
|
|
||||||
log("Starting notifications", newCharacteristic);
|
|
||||||
var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic));
|
|
||||||
if (settings.gracePeriodNotification > 0){
|
|
||||||
log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications");
|
|
||||||
startPromise = startPromise.then(()=>{
|
|
||||||
log("Wait after connect");
|
|
||||||
return waitingPromise(settings.gracePeriodNotification);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return startPromise;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return result.then(()=>log("Handled characteristic", newCharacteristic));
|
|
||||||
};
|
|
||||||
|
|
||||||
var attachCharacteristicPromise = function(promise, characteristic) {
|
|
||||||
return promise.then(()=>{
|
|
||||||
log("Handling characteristic:", characteristic);
|
|
||||||
return createCharacteristicPromise(characteristic);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var createCharacteristicsPromise = function(newCharacteristics) {
|
|
||||||
log("Create characteristics promis ", newCharacteristics);
|
|
||||||
var result = Promise.resolve();
|
|
||||||
for (var c of newCharacteristics){
|
|
||||||
if (!supportedCharacteristics[c.uuid]) continue;
|
|
||||||
log("Supporting characteristic", c);
|
|
||||||
characteristics.push(c);
|
|
||||||
if (c.properties.notify){
|
|
||||||
addNotificationHandler(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
result = attachCharacteristicPromise(result, c);
|
|
||||||
}
|
|
||||||
return result.then(()=>log("Handled characteristics"));
|
|
||||||
};
|
|
||||||
|
|
||||||
var createServicePromise = function(service) {
|
|
||||||
log("Create service promise", service);
|
|
||||||
var result = Promise.resolve();
|
|
||||||
result = result.then(()=>{
|
|
||||||
log("Handling service" + service.uuid);
|
|
||||||
return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c));
|
|
||||||
});
|
|
||||||
return result.then(()=>log("Handled service" + service.uuid));
|
|
||||||
};
|
|
||||||
|
|
||||||
var attachServicePromise = function(promise, service) {
|
|
||||||
return promise.then(()=>createServicePromise(service));
|
|
||||||
};
|
|
||||||
|
|
||||||
var initBt = function () {
|
|
||||||
log("initBt with blockInit: " + blockInit);
|
|
||||||
if (blockInit){
|
|
||||||
retry();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockInit = true;
|
|
||||||
|
|
||||||
var promise;
|
|
||||||
var filters;
|
|
||||||
|
|
||||||
if (!device){
|
|
||||||
if (settings.btid){
|
|
||||||
log("Configured device id", settings.btid);
|
|
||||||
filters = [{ id: settings.btid }];
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log("Requesting device with filters", filters);
|
|
||||||
promise = NRF.requestDevice({ filters: filters, active: true });
|
|
||||||
|
|
||||||
if (settings.gracePeriodRequest){
|
|
||||||
log("Add " + settings.gracePeriodRequest + "ms grace period after request");
|
|
||||||
}
|
|
||||||
|
|
||||||
promise = promise.then((d)=>{
|
|
||||||
log("Got device", d);
|
|
||||||
d.on('gattserverdisconnected', onDisconnect);
|
|
||||||
device = d;
|
|
||||||
});
|
|
||||||
|
|
||||||
promise = promise.then(()=>{
|
|
||||||
log("Wait after request");
|
|
||||||
return waitingPromise(settings.gracePeriodRequest);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
promise = Promise.resolve();
|
|
||||||
log("Reuse device", device);
|
|
||||||
}
|
|
||||||
|
|
||||||
promise = promise.then(()=>{
|
|
||||||
if (gatt){
|
|
||||||
log("Reuse GATT", gatt);
|
|
||||||
} else {
|
|
||||||
log("GATT is new", gatt);
|
|
||||||
characteristics = [];
|
|
||||||
var cachedId = getCache().id;
|
|
||||||
if (device.id !== cachedId){
|
|
||||||
log("Device ID changed from " + cachedId + " to " + device.id + ", clearing cache");
|
|
||||||
clearCache();
|
|
||||||
}
|
|
||||||
var newCache = getCache();
|
|
||||||
newCache.id = device.id;
|
|
||||||
writeCache(newCache);
|
|
||||||
gatt = device.gatt;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve(gatt);
|
|
||||||
});
|
|
||||||
|
|
||||||
promise = promise.then((gatt)=>{
|
|
||||||
if (!gatt.connected){
|
|
||||||
log("Connecting...");
|
|
||||||
var connectPromise = gatt.connect(connectSettings).then(function() {
|
|
||||||
log("Connected.");
|
|
||||||
});
|
|
||||||
if (settings.gracePeriodConnect > 0){
|
|
||||||
log("Add " + settings.gracePeriodConnect + "ms grace period after connecting");
|
|
||||||
connectPromise = connectPromise.then(()=>{
|
|
||||||
log("Wait after connect");
|
|
||||||
return waitingPromise(settings.gracePeriodConnect);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return connectPromise;
|
|
||||||
} else {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* promise = promise.then(() => {
|
|
||||||
log(JSON.stringify(gatt.getSecurityStatus()));
|
|
||||||
if (gatt.getSecurityStatus()['bonded']) {
|
|
||||||
log("Already bonded");
|
|
||||||
return Promise.resolve();
|
|
||||||
} else {
|
|
||||||
log("Start bonding");
|
|
||||||
return gatt.startBonding()
|
|
||||||
.then(() => console.log(gatt.getSecurityStatus()));
|
|
||||||
}
|
|
||||||
});*/
|
|
||||||
|
|
||||||
promise = promise.then(()=>{
|
|
||||||
if (!characteristics || characteristics.length === 0){
|
|
||||||
characteristics = characteristicsFromCache(device);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
promise = promise.then(()=>{
|
|
||||||
var characteristicsPromise = Promise.resolve();
|
|
||||||
if (characteristics.length === 0){
|
|
||||||
characteristicsPromise = characteristicsPromise.then(()=>{
|
|
||||||
log("Getting services");
|
|
||||||
return gatt.getPrimaryServices();
|
|
||||||
});
|
|
||||||
|
|
||||||
characteristicsPromise = characteristicsPromise.then((services)=>{
|
|
||||||
log("Got services", services);
|
|
||||||
var result = Promise.resolve();
|
|
||||||
for (var service of services){
|
|
||||||
if (!(supportedServices.includes(service.uuid))) continue;
|
|
||||||
log("Supporting service", service.uuid);
|
|
||||||
result = attachServicePromise(result, service);
|
|
||||||
}
|
|
||||||
if (settings.gracePeriodService > 0) {
|
|
||||||
log("Add " + settings.gracePeriodService + "ms grace period after services");
|
|
||||||
result = result.then(()=>{
|
|
||||||
log("Wait after services");
|
|
||||||
return waitingPromise(settings.gracePeriodService);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
for (var characteristic of characteristics){
|
|
||||||
characteristicsPromise = attachCharacteristicPromise(characteristicsPromise, characteristic, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return characteristicsPromise;
|
|
||||||
});
|
|
||||||
|
|
||||||
return promise.then(()=>{
|
|
||||||
log("Connection established, waiting for notifications");
|
|
||||||
characteristicsToCache(characteristics);
|
|
||||||
clearRetryTimeout(true);
|
|
||||||
}).catch((e) => {
|
|
||||||
characteristics = [];
|
|
||||||
log("Error:", e);
|
|
||||||
onDisconnect(e);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Bangle.setBTHRMPower = function(isOn, app) {
|
|
||||||
// Do app power handling
|
|
||||||
if (!app) app="?";
|
|
||||||
if (Bangle._PWR===undefined) Bangle._PWR={};
|
|
||||||
if (Bangle._PWR.BTHRM===undefined) Bangle._PWR.BTHRM=[];
|
|
||||||
if (isOn && !Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM.push(app);
|
|
||||||
if (!isOn && Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM = Bangle._PWR.BTHRM.filter(a=>a!==app);
|
|
||||||
isOn = Bangle._PWR.BTHRM.length;
|
|
||||||
// so now we know if we're really on
|
|
||||||
if (isOn) {
|
|
||||||
switchFallback();
|
|
||||||
if (!Bangle.isBTHRMConnected()) initBt();
|
|
||||||
} else { // not on
|
|
||||||
log("Power off for " + app);
|
|
||||||
clearRetryTimeout(true);
|
|
||||||
if (gatt) {
|
|
||||||
if (gatt.connected){
|
|
||||||
log("Disconnect with gatt", gatt);
|
|
||||||
try{
|
|
||||||
gatt.disconnect().then(()=>{
|
|
||||||
log("Successful disconnect");
|
|
||||||
}).catch((e)=>{
|
|
||||||
log("Error during disconnect promise", e);
|
|
||||||
});
|
|
||||||
} catch (e){
|
|
||||||
log("Error during disconnect attempt", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (settings.replace){
|
|
||||||
Bangle.on("HRM", (e) => {
|
|
||||||
e.modified = true;
|
|
||||||
Bangle.emit("HRM_int", e);
|
|
||||||
});
|
|
||||||
|
|
||||||
Bangle.origOn = Bangle.on;
|
|
||||||
Bangle.on = function(name, callback) {
|
|
||||||
if (name == "HRM") {
|
|
||||||
Bangle.origOn("HRM_int", callback);
|
|
||||||
} else {
|
|
||||||
Bangle.origOn(name, callback);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Bangle.origRemoveListener = Bangle.removeListener;
|
|
||||||
Bangle.removeListener = function(name, callback) {
|
|
||||||
if (name == "HRM") {
|
|
||||||
Bangle.origRemoveListener("HRM_int", callback);
|
|
||||||
} else {
|
|
||||||
Bangle.origRemoveListener(name, callback);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Bangle.origSetHRMPower = Bangle.setHRMPower;
|
|
||||||
|
|
||||||
if (settings.startWithHrm){
|
|
||||||
|
|
||||||
Bangle.setHRMPower = function(isOn, app) {
|
|
||||||
log("setHRMPower for " + app + ": " + (isOn?"on":"off"));
|
|
||||||
if (settings.enabled){
|
|
||||||
Bangle.setBTHRMPower(isOn, app);
|
|
||||||
}
|
|
||||||
if ((settings.enabled && !settings.replace) || !settings.enabled){
|
|
||||||
Bangle.origSetHRMPower(isOn, app);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var fallbackActive = false;
|
|
||||||
var inSwitch = false;
|
|
||||||
|
|
||||||
var stopFallback = function(){
|
|
||||||
if (fallbackActive){
|
|
||||||
Bangle.origSetHRMPower(0, "bthrm_fallback");
|
|
||||||
fallbackActive = false;
|
|
||||||
log("Fallback to HRM disabled");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var startFallback = function(){
|
|
||||||
if (!fallbackActive && settings.allowFallback) {
|
|
||||||
fallbackActive = true;
|
|
||||||
Bangle.origSetHRMPower(1, "bthrm_fallback");
|
|
||||||
log("Fallback to HRM enabled");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var switchFallback = function() {
|
|
||||||
log("Check falling back to HRM");
|
|
||||||
if (!inSwitch){
|
|
||||||
inSwitch = true;
|
|
||||||
if (Bangle.isBTHRMActive()){
|
|
||||||
stopFallback();
|
|
||||||
} else {
|
|
||||||
startFallback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inSwitch = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (settings.replace){
|
|
||||||
log("Replace HRM event");
|
|
||||||
if (Bangle._PWR && Bangle._PWR.HRM){
|
|
||||||
for (var i = 0; i < Bangle._PWR.HRM.length; i++){
|
|
||||||
var app = Bangle._PWR.HRM[i];
|
|
||||||
log("Moving app " + app);
|
|
||||||
Bangle.origSetHRMPower(0, app);
|
|
||||||
Bangle.setBTHRMPower(1, app);
|
|
||||||
if (Bangle._PWR.HRM===undefined) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
E.on("kill", ()=>{
|
|
||||||
if (gatt && gatt.connected){
|
|
||||||
log("Got killed, trying to disconnect");
|
|
||||||
gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect on kill", e));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
|
@ -16,5 +16,7 @@
|
||||||
"gracePeriodNotification": 0,
|
"gracePeriodNotification": 0,
|
||||||
"gracePeriodConnect": 0,
|
"gracePeriodConnect": 0,
|
||||||
"gracePeriodService": 0,
|
"gracePeriodService": 0,
|
||||||
"gracePeriodRequest": 0
|
"gracePeriodRequest": 0,
|
||||||
|
"bonding": false,
|
||||||
|
"active": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,656 @@
|
||||||
|
exports.enable = () => {
|
||||||
|
var settings = Object.assign(
|
||||||
|
require('Storage').readJSON("bthrm.default.json", true) || {},
|
||||||
|
require('Storage').readJSON("bthrm.json", true) || {}
|
||||||
|
);
|
||||||
|
|
||||||
|
var log = function(text, param){
|
||||||
|
if (global.showStatusInfo)
|
||||||
|
showStatusInfo(text);
|
||||||
|
if (settings.debuglog){
|
||||||
|
var logline = new Date().toISOString() + " - " + text;
|
||||||
|
if (param) logline += ": " + JSON.stringify(param);
|
||||||
|
print(logline);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
log("Settings: ", settings);
|
||||||
|
|
||||||
|
if (settings.enabled){
|
||||||
|
|
||||||
|
var clearCache = function() {
|
||||||
|
return require('Storage').erase("bthrm.cache.json");
|
||||||
|
};
|
||||||
|
|
||||||
|
var getCache = function() {
|
||||||
|
var cache = require('Storage').readJSON("bthrm.cache.json", true) || {};
|
||||||
|
if (settings.btid && settings.btid === cache.id) return cache;
|
||||||
|
clearCache();
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
var addNotificationHandler = function(characteristic) {
|
||||||
|
log("Setting notification handler"/*supportedCharacteristics[characteristic.uuid].handler*/);
|
||||||
|
characteristic.on('characteristicvaluechanged', (ev) => supportedCharacteristics[characteristic.uuid].handler(ev.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
var writeCache = function(cache) {
|
||||||
|
var oldCache = getCache();
|
||||||
|
if (oldCache !== cache) {
|
||||||
|
log("Writing cache");
|
||||||
|
require('Storage').writeJSON("bthrm.cache.json", cache);
|
||||||
|
} else {
|
||||||
|
log("No changes, don't write cache");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var characteristicsToCache = function(characteristics) {
|
||||||
|
log("Cache characteristics");
|
||||||
|
var cache = getCache();
|
||||||
|
if (!cache.characteristics) cache.characteristics = {};
|
||||||
|
for (var c of characteristics){
|
||||||
|
//"handle_value":16,"handle_decl":15
|
||||||
|
log("Saving handle " + c.handle_value + " for characteristic: ", c);
|
||||||
|
cache.characteristics[c.uuid] = {
|
||||||
|
"handle": c.handle_value,
|
||||||
|
"uuid": c.uuid,
|
||||||
|
"notify": c.properties.notify,
|
||||||
|
"read": c.properties.read
|
||||||
|
};
|
||||||
|
}
|
||||||
|
writeCache(cache);
|
||||||
|
};
|
||||||
|
|
||||||
|
var characteristicsFromCache = function(device) {
|
||||||
|
var service = { device : device }; // fake a BluetoothRemoteGATTService
|
||||||
|
log("Read cached characteristics");
|
||||||
|
var cache = getCache();
|
||||||
|
if (!cache.characteristics) return [];
|
||||||
|
var restored = [];
|
||||||
|
for (var c in cache.characteristics){
|
||||||
|
var cached = cache.characteristics[c];
|
||||||
|
var r = new BluetoothRemoteGATTCharacteristic();
|
||||||
|
log("Restoring characteristic ", cached);
|
||||||
|
r.handle_value = cached.handle;
|
||||||
|
r.uuid = cached.uuid;
|
||||||
|
r.properties = {};
|
||||||
|
r.properties.notify = cached.notify;
|
||||||
|
r.properties.read = cached.read;
|
||||||
|
r.service = service;
|
||||||
|
addNotificationHandler(r);
|
||||||
|
log("Restored characteristic: ", r);
|
||||||
|
restored.push(r);
|
||||||
|
}
|
||||||
|
return restored;
|
||||||
|
};
|
||||||
|
|
||||||
|
log("Start");
|
||||||
|
|
||||||
|
var lastReceivedData={
|
||||||
|
};
|
||||||
|
|
||||||
|
var supportedServices = [
|
||||||
|
"0x180d", // Heart Rate
|
||||||
|
"0x180f", // Battery
|
||||||
|
];
|
||||||
|
|
||||||
|
var bpmTimeout;
|
||||||
|
|
||||||
|
var supportedCharacteristics = {
|
||||||
|
"0x2a37": {
|
||||||
|
//Heart rate measurement
|
||||||
|
active: false,
|
||||||
|
handler: function (dv){
|
||||||
|
var flags = dv.getUint8(0);
|
||||||
|
|
||||||
|
var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit
|
||||||
|
supportedCharacteristics["0x2a37"].active = bpm > 0;
|
||||||
|
log("BTHRM BPM " + supportedCharacteristics["0x2a37"].active);
|
||||||
|
switchFallback();
|
||||||
|
if (bpmTimeout) clearTimeout(bpmTimeout);
|
||||||
|
bpmTimeout = setTimeout(()=>{
|
||||||
|
bpmTimeout = undefined;
|
||||||
|
supportedCharacteristics["0x2a37"].active = false;
|
||||||
|
startFallback();
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
var sensorContact;
|
||||||
|
|
||||||
|
if (flags & 2){
|
||||||
|
sensorContact = !!(flags & 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
var idx = 2 + (flags&1);
|
||||||
|
|
||||||
|
var energyExpended;
|
||||||
|
if (flags & 8){
|
||||||
|
energyExpended = dv.getUint16(idx,1);
|
||||||
|
idx += 2;
|
||||||
|
}
|
||||||
|
var interval;
|
||||||
|
if (flags & 16) {
|
||||||
|
interval = [];
|
||||||
|
var maxIntervalBytes = (dv.byteLength - idx);
|
||||||
|
log("Found " + (maxIntervalBytes / 2) + " rr data fields");
|
||||||
|
for(var i = 0 ; i < maxIntervalBytes / 2; i++){
|
||||||
|
interval[i] = dv.getUint16(idx,1); // in milliseconds
|
||||||
|
idx += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var location;
|
||||||
|
if (lastReceivedData && lastReceivedData["0x180d"] && lastReceivedData["0x180d"]["0x2a38"]){
|
||||||
|
location = lastReceivedData["0x180d"]["0x2a38"];
|
||||||
|
}
|
||||||
|
|
||||||
|
var battery;
|
||||||
|
if (lastReceivedData && lastReceivedData["0x180f"] && lastReceivedData["0x180f"]["0x2a19"]){
|
||||||
|
battery = lastReceivedData["0x180f"]["0x2a19"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.replace && bpm > 0){
|
||||||
|
var repEvent = {
|
||||||
|
bpm: bpm,
|
||||||
|
confidence: (sensorContact || sensorContact === undefined)? 100 : 0,
|
||||||
|
src: "bthrm"
|
||||||
|
};
|
||||||
|
|
||||||
|
log("Emitting HRM_R(bt)", repEvent);
|
||||||
|
Bangle.emit("HRM_R", repEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
var newEvent = {
|
||||||
|
bpm: bpm
|
||||||
|
};
|
||||||
|
|
||||||
|
if (location) newEvent.location = location;
|
||||||
|
if (interval) newEvent.rr = interval;
|
||||||
|
if (energyExpended) newEvent.energy = energyExpended;
|
||||||
|
if (battery) newEvent.battery = battery;
|
||||||
|
if (sensorContact) newEvent.contact = sensorContact;
|
||||||
|
|
||||||
|
log("Emitting BTHRM", newEvent);
|
||||||
|
Bangle.emit("BTHRM", newEvent);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x2a38": {
|
||||||
|
//Body sensor location
|
||||||
|
handler: function(dv){
|
||||||
|
if (!lastReceivedData["0x180d"]) lastReceivedData["0x180d"] = {};
|
||||||
|
lastReceivedData["0x180d"]["0x2a38"] = parseInt(dv.buffer, 10);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x2a19": {
|
||||||
|
//Battery
|
||||||
|
handler: function (dv){
|
||||||
|
if (!lastReceivedData["0x180f"]) lastReceivedData["0x180f"] = {};
|
||||||
|
lastReceivedData["0x180f"]["0x2a19"] = dv.getUint8(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var device;
|
||||||
|
var gatt;
|
||||||
|
var characteristics = [];
|
||||||
|
var blockInit = false;
|
||||||
|
var currentRetryTimeout;
|
||||||
|
var initialRetryTime = 40;
|
||||||
|
var maxRetryTime = 60000;
|
||||||
|
var retryTime = initialRetryTime;
|
||||||
|
|
||||||
|
var connectSettings = {
|
||||||
|
minInterval: 7.5,
|
||||||
|
maxInterval: 1500
|
||||||
|
};
|
||||||
|
|
||||||
|
var waitingPromise = function(timeout) {
|
||||||
|
return new Promise(function(resolve){
|
||||||
|
log("Start waiting for " + timeout);
|
||||||
|
setTimeout(()=>{
|
||||||
|
log("Done waiting for " + timeout);
|
||||||
|
resolve();
|
||||||
|
}, timeout);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (settings.enabled){
|
||||||
|
Bangle.isBTHRMActive = function (){
|
||||||
|
return supportedCharacteristics["0x2a37"].active;
|
||||||
|
};
|
||||||
|
|
||||||
|
Bangle.isBTHRMOn = function(){
|
||||||
|
return (Bangle._PWR && Bangle._PWR.BTHRM && Bangle._PWR.BTHRM.length > 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
Bangle.isBTHRMConnected = function(){
|
||||||
|
return gatt && gatt.connected;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.replace){
|
||||||
|
Bangle.origIsHRMOn = Bangle.isHRMOn;
|
||||||
|
|
||||||
|
Bangle.isHRMOn = function() {
|
||||||
|
if (settings.enabled && !settings.replace){
|
||||||
|
return Bangle.origIsHRMOn();
|
||||||
|
} else if (settings.enabled && settings.replace){
|
||||||
|
return Bangle.isBTHRMOn();
|
||||||
|
}
|
||||||
|
return Bangle.origIsHRMOn() || Bangle.isBTHRMOn();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var clearRetryTimeout = function(resetTime) {
|
||||||
|
if (currentRetryTimeout){
|
||||||
|
log("Clearing timeout " + currentRetryTimeout);
|
||||||
|
clearTimeout(currentRetryTimeout);
|
||||||
|
currentRetryTimeout = undefined;
|
||||||
|
}
|
||||||
|
if (resetTime) {
|
||||||
|
log("Resetting retry time");
|
||||||
|
retryTime = initialRetryTime;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var retry = function() {
|
||||||
|
log("Retry");
|
||||||
|
|
||||||
|
if (!currentRetryTimeout && !powerdownRequested){
|
||||||
|
|
||||||
|
var clampedTime = retryTime < 100 ? 100 : retryTime;
|
||||||
|
|
||||||
|
log("Set timeout for retry as " + clampedTime);
|
||||||
|
clearRetryTimeout();
|
||||||
|
currentRetryTimeout = setTimeout(() => {
|
||||||
|
log("Retrying");
|
||||||
|
currentRetryTimeout = undefined;
|
||||||
|
initBt();
|
||||||
|
}, clampedTime);
|
||||||
|
|
||||||
|
retryTime = Math.pow(clampedTime, 1.1);
|
||||||
|
if (retryTime > maxRetryTime){
|
||||||
|
retryTime = maxRetryTime;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log("Already in retry...");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var buzzing = false;
|
||||||
|
var onDisconnect = function(reason) {
|
||||||
|
log("Disconnect: " + reason);
|
||||||
|
log("GATT", gatt);
|
||||||
|
log("Characteristics", characteristics);
|
||||||
|
|
||||||
|
var retryTimeResetNeeded = true;
|
||||||
|
retryTimeResetNeeded &= reason != "Connection Timeout";
|
||||||
|
retryTimeResetNeeded &= reason != "No device found matching filters";
|
||||||
|
clearRetryTimeout(retryTimeResetNeeded);
|
||||||
|
supportedCharacteristics["0x2a37"].active = false;
|
||||||
|
if (!powerdownRequested) startFallback();
|
||||||
|
blockInit = false;
|
||||||
|
if (settings.warnDisconnect && !buzzing){
|
||||||
|
buzzing = true;
|
||||||
|
Bangle.buzz(500,0.3).then(()=>waitingPromise(4500)).then(()=>{buzzing = false;});
|
||||||
|
}
|
||||||
|
if (Bangle.isBTHRMOn()){
|
||||||
|
retry();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var createCharacteristicPromise = function(newCharacteristic) {
|
||||||
|
log("Create characteristic promise", newCharacteristic);
|
||||||
|
var result = Promise.resolve();
|
||||||
|
// For values that can be read, go ahead and read them, even if we might be notified in the future
|
||||||
|
// Allows for getting initial state of infrequently updating characteristics, like battery
|
||||||
|
if (newCharacteristic.readValue){
|
||||||
|
result = result.then(()=>{
|
||||||
|
log("Reading data", newCharacteristic);
|
||||||
|
return newCharacteristic.readValue().then((data)=>{
|
||||||
|
if (supportedCharacteristics[newCharacteristic.uuid] && supportedCharacteristics[newCharacteristic.uuid].handler) {
|
||||||
|
supportedCharacteristics[newCharacteristic.uuid].handler(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (newCharacteristic.properties.notify){
|
||||||
|
result = result.then(()=>{
|
||||||
|
log("Starting notifications", newCharacteristic);
|
||||||
|
var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic));
|
||||||
|
|
||||||
|
log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications");
|
||||||
|
startPromise = startPromise.then(()=>{
|
||||||
|
log("Wait after connect");
|
||||||
|
return waitingPromise(settings.gracePeriodNotification);
|
||||||
|
});
|
||||||
|
|
||||||
|
return startPromise;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result.then(()=>log("Handled characteristic", newCharacteristic));
|
||||||
|
};
|
||||||
|
|
||||||
|
var attachCharacteristicPromise = function(promise, characteristic) {
|
||||||
|
return promise.then(()=>{
|
||||||
|
log("Handling characteristic:", characteristic);
|
||||||
|
return createCharacteristicPromise(characteristic);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var createCharacteristicsPromise = function(newCharacteristics) {
|
||||||
|
log("Create characteristics promis ", newCharacteristics);
|
||||||
|
var result = Promise.resolve();
|
||||||
|
for (var c of newCharacteristics){
|
||||||
|
if (!supportedCharacteristics[c.uuid]) continue;
|
||||||
|
log("Supporting characteristic", c);
|
||||||
|
characteristics.push(c);
|
||||||
|
if (c.properties.notify){
|
||||||
|
addNotificationHandler(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = attachCharacteristicPromise(result, c);
|
||||||
|
}
|
||||||
|
return result.then(()=>log("Handled characteristics"));
|
||||||
|
};
|
||||||
|
|
||||||
|
var createServicePromise = function(service) {
|
||||||
|
log("Create service promise", service);
|
||||||
|
var result = Promise.resolve();
|
||||||
|
result = result.then(()=>{
|
||||||
|
log("Handling service" + service.uuid);
|
||||||
|
return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c));
|
||||||
|
});
|
||||||
|
return result.then(()=>log("Handled service" + service.uuid));
|
||||||
|
};
|
||||||
|
|
||||||
|
var attachServicePromise = function(promise, service) {
|
||||||
|
return promise.then(()=>createServicePromise(service));
|
||||||
|
};
|
||||||
|
|
||||||
|
var initBt = function () {
|
||||||
|
log("initBt with blockInit: " + blockInit);
|
||||||
|
if (blockInit && !powerdownRequested){
|
||||||
|
retry();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockInit = true;
|
||||||
|
|
||||||
|
var promise;
|
||||||
|
var filters;
|
||||||
|
|
||||||
|
if (!device){
|
||||||
|
if (settings.btid){
|
||||||
|
log("Configured device id", settings.btid);
|
||||||
|
filters = [{ id: settings.btid }];
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log("Requesting device with filters", filters);
|
||||||
|
try {
|
||||||
|
promise = NRF.requestDevice({ filters: filters, active: settings.active });
|
||||||
|
} catch (e){
|
||||||
|
log("Error during initial request:", e);
|
||||||
|
onDisconnect(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.gracePeriodRequest){
|
||||||
|
log("Add " + settings.gracePeriodRequest + "ms grace period after request");
|
||||||
|
}
|
||||||
|
|
||||||
|
promise = promise.then((d)=>{
|
||||||
|
log("Got device", d);
|
||||||
|
d.on('gattserverdisconnected', onDisconnect);
|
||||||
|
device = d;
|
||||||
|
});
|
||||||
|
|
||||||
|
promise = promise.then(()=>{
|
||||||
|
log("Wait after request");
|
||||||
|
return waitingPromise(settings.gracePeriodRequest);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
promise = Promise.resolve();
|
||||||
|
log("Reuse device", device);
|
||||||
|
}
|
||||||
|
|
||||||
|
promise = promise.then(()=>{
|
||||||
|
if (gatt){
|
||||||
|
log("Reuse GATT", gatt);
|
||||||
|
} else {
|
||||||
|
log("GATT is new", gatt);
|
||||||
|
characteristics = [];
|
||||||
|
var cachedId = getCache().id;
|
||||||
|
if (device.id !== cachedId){
|
||||||
|
log("Device ID changed from " + cachedId + " to " + device.id + ", clearing cache");
|
||||||
|
clearCache();
|
||||||
|
}
|
||||||
|
var newCache = getCache();
|
||||||
|
newCache.id = device.id;
|
||||||
|
writeCache(newCache);
|
||||||
|
gatt = device.gatt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(gatt);
|
||||||
|
});
|
||||||
|
|
||||||
|
promise = promise.then((gatt)=>{
|
||||||
|
if (!gatt.connected){
|
||||||
|
log("Connecting...");
|
||||||
|
var connectPromise = gatt.connect(connectSettings).then(function() {
|
||||||
|
log("Connected.");
|
||||||
|
});
|
||||||
|
log("Add " + settings.gracePeriodConnect + "ms grace period after connecting");
|
||||||
|
connectPromise = connectPromise.then(()=>{
|
||||||
|
log("Wait after connect");
|
||||||
|
return waitingPromise(settings.gracePeriodConnect);
|
||||||
|
});
|
||||||
|
return connectPromise;
|
||||||
|
} else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (settings.bonding){
|
||||||
|
promise = promise.then(() => {
|
||||||
|
log(JSON.stringify(gatt.getSecurityStatus()));
|
||||||
|
if (gatt.getSecurityStatus()['bonded']) {
|
||||||
|
log("Already bonded");
|
||||||
|
return Promise.resolve();
|
||||||
|
} else {
|
||||||
|
log("Start bonding");
|
||||||
|
return gatt.startBonding()
|
||||||
|
.then(() => log("Security status" + gatt.getSecurityStatus()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
promise = promise.then(()=>{
|
||||||
|
if (!characteristics || characteristics.length === 0){
|
||||||
|
characteristics = characteristicsFromCache(device);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
promise = promise.then(()=>{
|
||||||
|
var characteristicsPromise = Promise.resolve();
|
||||||
|
if (characteristics.length === 0){
|
||||||
|
characteristicsPromise = characteristicsPromise.then(()=>{
|
||||||
|
log("Getting services");
|
||||||
|
return gatt.getPrimaryServices();
|
||||||
|
});
|
||||||
|
|
||||||
|
characteristicsPromise = characteristicsPromise.then((services)=>{
|
||||||
|
log("Got services", services);
|
||||||
|
var result = Promise.resolve();
|
||||||
|
for (var service of services){
|
||||||
|
if (!(supportedServices.includes(service.uuid))) continue;
|
||||||
|
log("Supporting service", service.uuid);
|
||||||
|
result = attachServicePromise(result, service);
|
||||||
|
}
|
||||||
|
log("Add " + settings.gracePeriodService + "ms grace period after services");
|
||||||
|
result = result.then(()=>{
|
||||||
|
log("Wait after services");
|
||||||
|
return waitingPromise(settings.gracePeriodService);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
for (var characteristic of characteristics){
|
||||||
|
characteristicsPromise = attachCharacteristicPromise(characteristicsPromise, characteristic, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return characteristicsPromise;
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise.then(()=>{
|
||||||
|
log("Connection established, waiting for notifications");
|
||||||
|
characteristicsToCache(characteristics);
|
||||||
|
clearRetryTimeout(true);
|
||||||
|
}).catch((e) => {
|
||||||
|
characteristics = [];
|
||||||
|
log("Error:", e);
|
||||||
|
onDisconnect(e);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var powerdownRequested = false;
|
||||||
|
|
||||||
|
Bangle.setBTHRMPower = function(isOn, app) {
|
||||||
|
// Do app power handling
|
||||||
|
if (!app) app="?";
|
||||||
|
if (Bangle._PWR===undefined) Bangle._PWR={};
|
||||||
|
if (Bangle._PWR.BTHRM===undefined) Bangle._PWR.BTHRM=[];
|
||||||
|
if (isOn && !Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM.push(app);
|
||||||
|
if (!isOn && Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM = Bangle._PWR.BTHRM.filter(a=>a!==app);
|
||||||
|
isOn = Bangle._PWR.BTHRM.length;
|
||||||
|
// so now we know if we're really on
|
||||||
|
if (isOn) {
|
||||||
|
powerdownRequested = false;
|
||||||
|
switchFallback();
|
||||||
|
if (!Bangle.isBTHRMConnected()) initBt();
|
||||||
|
} else { // not on
|
||||||
|
log("Power off for " + app);
|
||||||
|
powerdownRequested = true;
|
||||||
|
clearRetryTimeout(true);
|
||||||
|
stopFallback();
|
||||||
|
if (gatt) {
|
||||||
|
if (gatt.connected){
|
||||||
|
log("Disconnect with gatt", gatt);
|
||||||
|
try{
|
||||||
|
gatt.disconnect().then(()=>{
|
||||||
|
log("Successful disconnect");
|
||||||
|
}).catch((e)=>{
|
||||||
|
log("Error during disconnect promise", e);
|
||||||
|
});
|
||||||
|
} catch (e){
|
||||||
|
log("Error during disconnect attempt", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (settings.replace){
|
||||||
|
// register a listener for original HRM events and emit as HRM_int
|
||||||
|
Bangle.on("HRM", (e) => {
|
||||||
|
e.modified = true;
|
||||||
|
log("Emitting HRM_int", e);
|
||||||
|
Bangle.emit("HRM_int", e);
|
||||||
|
if (fallbackActive){
|
||||||
|
// if fallback to internal HRM is active, emit as HRM_R to which everyone listens
|
||||||
|
log("Emitting HRM_R(int)", e);
|
||||||
|
Bangle.emit("HRM_R", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// force all apps wanting to listen to HRM to actually get events for HRM_R
|
||||||
|
Bangle.on = ( o => (name, cb) => {
|
||||||
|
o = o.bind(Bangle);
|
||||||
|
if (name == "HRM") o("HRM_R", cb);
|
||||||
|
else o(name, cb);
|
||||||
|
})(Bangle.on);
|
||||||
|
|
||||||
|
Bangle.removeListener = ( o => (name, cb) => {
|
||||||
|
o = o.bind(Bangle);
|
||||||
|
if (name == "HRM") o("HRM_R", cb);
|
||||||
|
else o(name, cb);
|
||||||
|
})(Bangle.removeListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bangle.origSetHRMPower = Bangle.setHRMPower;
|
||||||
|
|
||||||
|
if (settings.startWithHrm){
|
||||||
|
Bangle.setHRMPower = function(isOn, app) {
|
||||||
|
log("setHRMPower for " + app + ": " + (isOn?"on":"off"));
|
||||||
|
if (settings.enabled){
|
||||||
|
Bangle.setBTHRMPower(isOn, app);
|
||||||
|
if (Bangle._PWR && Bangle._PWR.HRM && Object.keys(Bangle._PWR.HRM).length == 0) {
|
||||||
|
Bangle._PWR.BTHRM = [];
|
||||||
|
Bangle.setBTHRMPower(0);
|
||||||
|
if (!isOn) stopFallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((settings.enabled && !settings.replace) || !settings.enabled){
|
||||||
|
Bangle.origSetHRMPower(isOn, app);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var fallbackActive = false;
|
||||||
|
var inSwitch = false;
|
||||||
|
|
||||||
|
var stopFallback = function(){
|
||||||
|
if (fallbackActive){
|
||||||
|
Bangle.origSetHRMPower(0, "bthrm_fallback");
|
||||||
|
fallbackActive = false;
|
||||||
|
log("Fallback to HRM disabled");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var startFallback = function(){
|
||||||
|
if (!fallbackActive && settings.allowFallback) {
|
||||||
|
fallbackActive = true;
|
||||||
|
Bangle.origSetHRMPower(1, "bthrm_fallback");
|
||||||
|
log("Fallback to HRM enabled");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var switchFallback = function() {
|
||||||
|
log("Check falling back to HRM");
|
||||||
|
if (!inSwitch){
|
||||||
|
inSwitch = true;
|
||||||
|
if (Bangle.isBTHRMActive()){
|
||||||
|
stopFallback();
|
||||||
|
} else {
|
||||||
|
startFallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inSwitch = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (settings.replace){
|
||||||
|
log("Replace HRM event");
|
||||||
|
if (Bangle._PWR && Bangle._PWR.HRM){
|
||||||
|
for (var i = 0; i < Bangle._PWR.HRM.length; i++){
|
||||||
|
var app = Bangle._PWR.HRM[i];
|
||||||
|
log("Moving app " + app);
|
||||||
|
Bangle.origSetHRMPower(0, app);
|
||||||
|
Bangle.setBTHRMPower(1, app);
|
||||||
|
if (Bangle._PWR.HRM===undefined) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
E.on("kill", ()=>{
|
||||||
|
if (gatt && gatt.connected){
|
||||||
|
log("Got killed, trying to disconnect");
|
||||||
|
try {
|
||||||
|
gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect promise on kill", e));
|
||||||
|
} catch (e) {
|
||||||
|
log("Error during disconnnect on kill", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "bthrm",
|
"id": "bthrm",
|
||||||
"name": "Bluetooth Heart Rate Monitor",
|
"name": "Bluetooth Heart Rate Monitor",
|
||||||
"shortName": "BT HRM",
|
"shortName": "BT HRM",
|
||||||
"version": "0.12",
|
"version": "0.16",
|
||||||
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
|
@ -15,6 +15,7 @@
|
||||||
{"name":"bthrm.0.boot.js","url":"boot.js"},
|
{"name":"bthrm.0.boot.js","url":"boot.js"},
|
||||||
{"name":"bthrm.img","url":"app-icon.js","evaluate":true},
|
{"name":"bthrm.img","url":"app-icon.js","evaluate":true},
|
||||||
{"name":"bthrm.settings.js","url":"settings.js"},
|
{"name":"bthrm.settings.js","url":"settings.js"},
|
||||||
|
{"name":"bthrm","url":"lib.js"},
|
||||||
{"name":"bthrm.default.json","url":"default.json"}
|
{"name":"bthrm.default.json","url":"default.json"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,35 +38,32 @@
|
||||||
recorders.hrmint = function() {
|
recorders.hrmint = function() {
|
||||||
var active = false;
|
var active = false;
|
||||||
var bpmTimeout;
|
var bpmTimeout;
|
||||||
var bpm = "", bpmConfidence = "", src="";
|
var bpm = "", bpmConfidence = "";
|
||||||
function onHRM(h) {
|
function onHRM(h) {
|
||||||
bpmConfidence = h.confidence;
|
bpmConfidence = h.confidence;
|
||||||
bpm = h.bpm;
|
bpm = h.bpm;
|
||||||
srv = h.src;
|
|
||||||
if (h.bpm > 0){
|
if (h.bpm > 0){
|
||||||
active = true;
|
active = true;
|
||||||
print("active" + h.bpm);
|
|
||||||
if (bpmTimeout) clearTimeout(bpmTimeout);
|
if (bpmTimeout) clearTimeout(bpmTimeout);
|
||||||
bpmTimeout = setTimeout(()=>{
|
bpmTimeout = setTimeout(()=>{
|
||||||
print("inactive");
|
|
||||||
active = false;
|
active = false;
|
||||||
},3000);
|
},3000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
name : "HR int",
|
name : "HR int",
|
||||||
fields : ["Heartrate", "Confidence"],
|
fields : ["Int Heartrate", "Int Confidence"],
|
||||||
getValues : () => {
|
getValues : () => {
|
||||||
var r = [bpm,bpmConfidence,src];
|
var r = [bpm,bpmConfidence];
|
||||||
bpm = ""; bpmConfidence = ""; src="";
|
bpm = ""; bpmConfidence = "";
|
||||||
return r;
|
return r;
|
||||||
},
|
},
|
||||||
start : () => {
|
start : () => {
|
||||||
Bangle.origOn('HRM', onHRM);
|
Bangle.on('HRM_int', onHRM);
|
||||||
if (Bangle.origSetHRMPower) Bangle.origSetHRMPower(1,"recorder");
|
if (Bangle.origSetHRMPower) Bangle.origSetHRMPower(1,"recorder");
|
||||||
},
|
},
|
||||||
stop : () => {
|
stop : () => {
|
||||||
Bangle.removeListener('HRM', onHRM);
|
Bangle.removeListener('HRM_int', onHRM);
|
||||||
if (Bangle.origSetHRMPower) Bangle.origSetHRMPower(0,"recorder");
|
if (Bangle.origSetHRMPower) Bangle.origSetHRMPower(0,"recorder");
|
||||||
},
|
},
|
||||||
draw : (x,y) => g.setColor(( Bangle.origIsHRMOn && Bangle.origIsHRMOn() && active)?"#0f0":"#8f8").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
|
draw : (x,y) => g.setColor(( Bangle.origIsHRMOn && Bangle.origIsHRMOn() && active)?"#0f0":"#8f8").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
|
||||||
|
|
|
@ -96,6 +96,18 @@
|
||||||
writeSettings("debuglog",v);
|
writeSettings("debuglog",v);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'Use bonding': {
|
||||||
|
value: !!settings.bonding,
|
||||||
|
onchange: v => {
|
||||||
|
writeSettings("bonding",v);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Use active scanning': {
|
||||||
|
value: !!settings.active,
|
||||||
|
onchange: v => {
|
||||||
|
writeSettings("active",v);
|
||||||
|
}
|
||||||
|
},
|
||||||
'Grace periods': function() { E.showMenu(submenu_grace); }
|
'Grace periods': function() { E.showMenu(submenu_grace); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}, {"url":"screenshot_4.png"}],
|
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}, {"url":"screenshot_4.png"}],
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"tags": "clock",
|
"tags": "clock,clkinfo",
|
||||||
"supports": ["BANGLEJS2"],
|
"supports": ["BANGLEJS2"],
|
||||||
"allow_emulator": true,
|
"allow_emulator": true,
|
||||||
"storage": [
|
"storage": [
|
||||||
|
|
|
@ -2,3 +2,4 @@
|
||||||
0.02: More compact rendering & app icon
|
0.02: More compact rendering & app icon
|
||||||
0.03: Tell clock widgets to hide.
|
0.03: Tell clock widgets to hide.
|
||||||
0.04: Improve current time readability in light theme.
|
0.04: Improve current time readability in light theme.
|
||||||
|
0.05: Show calendar colors & improved all day events.
|
||||||
|
|
|
@ -20,41 +20,55 @@ function zp(str) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawEventHeader(event, y) {
|
function drawEventHeader(event, y) {
|
||||||
g.setFont("Vector", 24);
|
var x = 0;
|
||||||
|
|
||||||
var time = isActive(event) ? new Date() : new Date(event.timestamp * 1000);
|
var time = isActive(event) ? new Date() : new Date(event.timestamp * 1000);
|
||||||
|
|
||||||
|
//Don't need to know what time the event is at if its all day
|
||||||
|
if (isActive(event) || !event.allDay) {
|
||||||
|
g.setFont("Vector", 24);
|
||||||
var timeStr = zp(time.getHours()) + ":" + zp(time.getMinutes());
|
var timeStr = zp(time.getHours()) + ":" + zp(time.getMinutes());
|
||||||
g.drawString(timeStr, 5, y);
|
g.drawString(timeStr, 0, y);
|
||||||
y += 24;
|
y += 3;
|
||||||
|
x = 13*timeStr.length+5;
|
||||||
|
}
|
||||||
|
|
||||||
g.setFont("12x20", 1);
|
g.setFont("12x20", 1);
|
||||||
|
|
||||||
if (isActive(event)) {
|
if (isActive(event)) {
|
||||||
g.drawString(zp(time.getDate())+". " + require("locale").month(time,1),15*timeStr.length,y-21);
|
g.drawString(zp(time.getDate())+". " + require("locale").month(time,1),x,y);
|
||||||
} else {
|
} else {
|
||||||
var offset = 0-time.getTimezoneOffset()/1440;
|
var offset = 0-time.getTimezoneOffset()/1440;
|
||||||
var days = Math.floor((time.getTime()/1000)/86400+offset)-Math.floor(getTime()/86400+offset);
|
var days = Math.floor((time.getTime()/1000)/86400+offset)-Math.floor(getTime()/86400+offset);
|
||||||
if(days > 0) {
|
if(days > 0 || event.allDay) {
|
||||||
var daysStr = days===1?/*LANG*/"tomorrow":/*LANG*/"in "+days+/*LANG*/" days";
|
var daysStr = days===1?/*LANG*/"tomorrow":/*LANG*/"in "+days+/*LANG*/" days";
|
||||||
g.drawString(daysStr,15*timeStr.length,y-21);
|
g.drawString(daysStr,x,y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
y += 21;
|
||||||
return y;
|
return y;
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawEventBody(event, y) {
|
function drawEventBody(event, y) {
|
||||||
g.setFont("12x20", 1);
|
g.setFont("12x20", 1);
|
||||||
var lines = g.wrapString(event.title, g.getWidth()-10);
|
var lines = g.wrapString(event.title, g.getWidth()-15);
|
||||||
|
var yStart = y;
|
||||||
if (lines.length > 2) {
|
if (lines.length > 2) {
|
||||||
lines = lines.slice(0,2);
|
lines = lines.slice(0,2);
|
||||||
lines[1] = lines[1].slice(0,-3)+"...";
|
lines[1] = lines[1].slice(0,-3)+"...";
|
||||||
}
|
}
|
||||||
g.drawString(lines.join('\n'), 5, y);
|
g.drawString(lines.join('\n'),10,y);
|
||||||
y+=20 * lines.length;
|
y+=20 * lines.length;
|
||||||
if(event.location) {
|
if(event.location) {
|
||||||
g.drawImage(atob("DBSBAA8D/H/nDuB+B+B+B3Dn/j/B+A8A8AYAYAYAAAAAAA=="),5,y);
|
g.drawImage(atob("DBSBAA8D/H/nDuB+B+B+B3Dn/j/B+A8A8AYAYAYAAAAAAA=="),10,y);
|
||||||
g.drawString(event.location, 20, y);
|
g.drawString(event.location,25,y);
|
||||||
y+=20;
|
y+=20;
|
||||||
}
|
}
|
||||||
|
if (event.color) {
|
||||||
|
var oldColor = g.getColor();
|
||||||
|
g.setColor("#"+(0x1000000+Number(event.color)).toString(16).padStart(6,"0"));
|
||||||
|
g.fillRect(0,yStart,5,y-3);
|
||||||
|
g.setColor(oldColor);
|
||||||
|
}
|
||||||
y+=5;
|
y+=5;
|
||||||
return y;
|
return y;
|
||||||
}
|
}
|
||||||
|
@ -68,8 +82,8 @@ function drawEvent(event, y) {
|
||||||
var curEventHeight = 0;
|
var curEventHeight = 0;
|
||||||
|
|
||||||
function drawCurrentEvents(y) {
|
function drawCurrentEvents(y) {
|
||||||
g.setColor(g.theme.dark ? "#0ff" : "#0000ff");
|
g.setColor(g.theme.dark ? "#0ff" : "#00f");
|
||||||
g.clearRect(5, y, g.getWidth() - 5, y + curEventHeight);
|
g.clearRect(0,y,g.getWidth()-5,y+curEventHeight);
|
||||||
curEventHeight = y;
|
curEventHeight = y;
|
||||||
|
|
||||||
if(current.length === 0) {
|
if(current.length === 0) {
|
||||||
|
@ -94,7 +108,7 @@ function drawFutureEvents(y) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function fullRedraw() {
|
function fullRedraw() {
|
||||||
g.clearRect(5,24,g.getWidth()-5,g.getHeight());
|
g.clearRect(0,24,g.getWidth()-5,g.getHeight());
|
||||||
updateCalendar();
|
updateCalendar();
|
||||||
var y = 30;
|
var y = 30;
|
||||||
y = drawCurrentEvents(y);
|
y = drawCurrentEvents(y);
|
||||||
|
@ -117,3 +131,4 @@ var minuteInterval = setInterval(redraw, 60 * 1000);
|
||||||
Bangle.setUI("clock");
|
Bangle.setUI("clock");
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "calclock",
|
"id": "calclock",
|
||||||
"name": "Calendar Clock",
|
"name": "Calendar Clock",
|
||||||
"shortName": "CalClock",
|
"shortName": "CalClock",
|
||||||
"version": "0.04",
|
"version": "0.05",
|
||||||
"description": "Show the current and upcoming events synchronized from Gadgetbridge",
|
"description": "Show the current and upcoming events synchronized from Gadgetbridge",
|
||||||
"icon": "calclock.png",
|
"icon": "calclock.png",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
diff --git a/apps/calclock/calclock.js b/apps/calclock/calclock.js
|
||||||
|
index cb8c6100e..2092c1a4e 100644
|
||||||
|
--- a/apps/calclock/calclock.js
|
||||||
|
+++ b/apps/calclock/calclock.js
|
||||||
|
@@ -3,9 +3,24 @@ var current = [];
|
||||||
|
var next = [];
|
||||||
|
|
||||||
|
function updateCalendar() {
|
||||||
|
- calendar = require("Storage").readJSON("android.calendar.json",true)||[];
|
||||||
|
- calendar = calendar.filter(e => isActive(e) || getTime() <= e.timestamp);
|
||||||
|
- calendar.sort((a,b) => a.timestamp - b.timestamp);
|
||||||
|
+ calendar = [
|
||||||
|
+ {
|
||||||
|
+ t: "calendar",
|
||||||
|
+ id: 2, type: 0, timestamp: getTime(), durationInSeconds: 200,
|
||||||
|
+ title: "Capture Screenshot",
|
||||||
|
+ description: "Capture Screenshot",
|
||||||
|
+ location: "",
|
||||||
|
+ calName: "",
|
||||||
|
+ color: -7151168, allDay: true },
|
||||||
|
+ {
|
||||||
|
+ t: "calendar",
|
||||||
|
+ id: 7186, type: 0, timestamp: getTime() + 2000, durationInSeconds: 100,
|
||||||
|
+ title: "Upload to BangleApps",
|
||||||
|
+ description: "",
|
||||||
|
+ location: "",
|
||||||
|
+ calName: "",
|
||||||
|
+ color: -509406, allDay: false }
|
||||||
|
+ ];
|
||||||
|
|
||||||
|
current = calendar.filter(isActive);
|
||||||
|
next = calendar.filter(e=>!isActive(e));
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.1 KiB |
|
@ -3,3 +3,5 @@
|
||||||
0.03: Support for different screen sizes and touchscreen
|
0.03: Support for different screen sizes and touchscreen
|
||||||
0.04: Display current operation on LHS
|
0.04: Display current operation on LHS
|
||||||
0.05: Grid positioning and swipe controls to switch between numbers, operators and special (for Bangle.js 2)
|
0.05: Grid positioning and swipe controls to switch between numbers, operators and special (for Bangle.js 2)
|
||||||
|
0.06: Bangle.js 2: Exit with a short press of the physical button
|
||||||
|
0.07: Bangle.js 2: Exit by pressing upper left corner of the screen
|
||||||
|
|
|
@ -12,12 +12,20 @@ Basic calculator reminiscent of MacOs's one. Handy for small calculus.
|
||||||
|
|
||||||
## Controls
|
## Controls
|
||||||
|
|
||||||
|
Bangle.js 1
|
||||||
- UP: BTN1
|
- UP: BTN1
|
||||||
- DOWN: BTN3
|
- DOWN: BTN3
|
||||||
- LEFT: BTN4
|
- LEFT: BTN4
|
||||||
- RIGHT: BTN5
|
- RIGHT: BTN5
|
||||||
- SELECT: BTN2
|
- SELECT: BTN2
|
||||||
|
|
||||||
|
Bangle.js 2
|
||||||
|
- Swipes to change visible buttons
|
||||||
|
- Click physical button to exit
|
||||||
|
- Press upper left corner of screen to exit (where the red back button would be)
|
||||||
## Creator
|
## Creator
|
||||||
|
|
||||||
<https://twitter.com/fredericrous>
|
<https://twitter.com/fredericrous>
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
[thyttan](https://github.com/thyttan)
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
*
|
*
|
||||||
* Original Author: Frederic Rousseau https://github.com/fredericrous
|
* Original Author: Frederic Rousseau https://github.com/fredericrous
|
||||||
* Created: April 2020
|
* Created: April 2020
|
||||||
|
*
|
||||||
|
* Contributors: thyttan https://github.com/thyttan
|
||||||
*/
|
*/
|
||||||
|
|
||||||
g.clear();
|
g.clear();
|
||||||
|
@ -408,37 +410,36 @@ if (process.env.HWVERSION==1) {
|
||||||
prepareScreen(operators, operatorsGrid, COLORS.OPERATOR);
|
prepareScreen(operators, operatorsGrid, COLORS.OPERATOR);
|
||||||
prepareScreen(specials, specialsGrid, COLORS.SPECIAL);
|
prepareScreen(specials, specialsGrid, COLORS.SPECIAL);
|
||||||
drawNumbers();
|
drawNumbers();
|
||||||
Bangle.on('touch',(n,e)=>{
|
|
||||||
|
Bangle.setUI({
|
||||||
|
mode : 'custom',
|
||||||
|
back : load, // Clicking physical button or pressing upper left corner turns off (where red back button would be)
|
||||||
|
touch : (n,e)=>{
|
||||||
for (var key in screen) {
|
for (var key in screen) {
|
||||||
if (typeof screen[key] == "undefined") break;
|
if (typeof screen[key] == "undefined") break;
|
||||||
var r = screen[key].xy;
|
var r = screen[key].xy;
|
||||||
if (e.x>=r[0] && e.y>=r[1] &&
|
if (e.x>=r[0] && e.y>=r[1] && e.x<r[2] && e.y<r[3]) {
|
||||||
e.x<r[2] && e.y<r[3]) {
|
|
||||||
//print("Press "+key);
|
//print("Press "+key);
|
||||||
buttonPress(""+key);
|
buttonPress(""+key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
var lastX = 0, lastY = 0;
|
swipe : (LR, UD) => {
|
||||||
Bangle.on('drag', (e) => {
|
if (LR == 1) { // right
|
||||||
if (!e.b) {
|
|
||||||
if (lastX > 50) { // right
|
|
||||||
drawSpecials();
|
drawSpecials();
|
||||||
} else if (lastX < -50) { // left
|
}
|
||||||
|
if (LR == -1) { // left
|
||||||
drawOperators();
|
drawOperators();
|
||||||
} else if (lastY > 50) { // down
|
}
|
||||||
drawNumbers();
|
if (UD == 1) { // down
|
||||||
} else if (lastY < -50) { // up
|
drawNumbers();
|
||||||
|
}
|
||||||
|
if (UD == -1) { // up
|
||||||
drawNumbers();
|
drawNumbers();
|
||||||
}
|
}
|
||||||
lastX = 0;
|
|
||||||
lastY = 0;
|
|
||||||
} else {
|
|
||||||
lastX = lastX + e.dx;
|
|
||||||
lastY = lastY + e.dy;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
displayOutput(0);
|
displayOutput(0);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "calculator",
|
"id": "calculator",
|
||||||
"name": "Calculator",
|
"name": "Calculator",
|
||||||
"shortName": "Calculator",
|
"shortName": "Calculator",
|
||||||
"version": "0.05",
|
"version": "0.07",
|
||||||
"description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.",
|
"description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.",
|
||||||
"icon": "calculator.png",
|
"icon": "calculator.png",
|
||||||
"screenshots": [{"url":"screenshot_calculator.png"}],
|
"screenshots": [{"url":"screenshot_calculator.png"}],
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"shortName":"Calibration",
|
"shortName":"Calibration",
|
||||||
"icon": "calibration.png",
|
"icon": "calibration.png",
|
||||||
"version":"0.03",
|
"version":"0.03",
|
||||||
"description": "A simple calibration app for the touchscreen",
|
"description": "(NOT RECOMMENDED) A simple calibration app for the touchscreen. Please use the Touchscreen Calibration in the Settings app instead.",
|
||||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"tags": "tool",
|
"tags": "tool",
|
||||||
|
|
|
@ -10,3 +10,4 @@
|
||||||
0.9: Remove ESLint spaces
|
0.9: Remove ESLint spaces
|
||||||
0.10: Show daily steps, heartrate and the temperature if weather information is available.
|
0.10: Show daily steps, heartrate and the temperature if weather information is available.
|
||||||
0.11: Tell clock widgets to hide.
|
0.11: Tell clock widgets to hide.
|
||||||
|
0.12: Swipe down to see widgets, step counter now just uses getHealthStatus
|
||||||
|
|
|
@ -8,4 +8,5 @@ It displays current temperature,day,steps,battery.heartbeat and weather.
|
||||||
|
|
||||||
|
|
||||||
**To-do**:
|
**To-do**:
|
||||||
Align and change size of some elements.
|
|
||||||
|
* Align and change size of some elements
|
||||||
|
|
|
@ -91,7 +91,6 @@ function getTemperature(){
|
||||||
var weatherJson = storage.readJSON('weather.json');
|
var weatherJson = storage.readJSON('weather.json');
|
||||||
var weather = weatherJson.weather;
|
var weather = weatherJson.weather;
|
||||||
return Math.round(weather.temp-273.15);
|
return Math.round(weather.temp-273.15);
|
||||||
|
|
||||||
} catch(ex) {
|
} catch(ex) {
|
||||||
print(ex)
|
print(ex)
|
||||||
return "?"
|
return "?"
|
||||||
|
@ -99,20 +98,7 @@ function getTemperature(){
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSteps() {
|
function getSteps() {
|
||||||
var steps = 0;
|
var steps = Bangle.getHealthStatus("day").steps;
|
||||||
try{
|
|
||||||
if (WIDGETS.wpedom !== undefined) {
|
|
||||||
steps = WIDGETS.wpedom.getSteps();
|
|
||||||
} else if (WIDGETS.activepedom !== undefined) {
|
|
||||||
steps = WIDGETS.activepedom.getSteps();
|
|
||||||
} else {
|
|
||||||
steps = Bangle.getHealthStatus("day").steps;
|
|
||||||
}
|
|
||||||
} catch(ex) {
|
|
||||||
// In case we failed, we can only show 0 steps.
|
|
||||||
return "? k";
|
|
||||||
}
|
|
||||||
|
|
||||||
steps = Math.round(steps/1000);
|
steps = Math.round(steps/1000);
|
||||||
return steps + "k";
|
return steps + "k";
|
||||||
}
|
}
|
||||||
|
@ -121,8 +107,7 @@ function getSteps() {
|
||||||
function draw() {
|
function draw() {
|
||||||
queueDraw();
|
queueDraw();
|
||||||
|
|
||||||
g.reset();
|
g.clear(1);
|
||||||
g.clear();
|
|
||||||
g.setColor(0, 255, 255);
|
g.setColor(0, 255, 255);
|
||||||
g.fillRect(0, 0, g.getWidth(), g.getHeight());
|
g.fillRect(0, 0, g.getWidth(), g.getHeight());
|
||||||
let background = getBackgroundImage();
|
let background = getBackgroundImage();
|
||||||
|
@ -143,9 +128,6 @@ function draw() {
|
||||||
drawClock();
|
drawClock();
|
||||||
drawRocket();
|
drawRocket();
|
||||||
drawBattery();
|
drawBattery();
|
||||||
|
|
||||||
// Hide widgets
|
|
||||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Bangle.on("lcdPower", (on) => {
|
Bangle.on("lcdPower", (on) => {
|
||||||
|
@ -169,7 +151,6 @@ Bangle.setUI("clock");
|
||||||
|
|
||||||
// Load widgets, but don't show them
|
// Load widgets, but don't show them
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
|
require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe
|
||||||
g.reset();
|
g.clear(1);
|
||||||
g.clear();
|
|
||||||
draw();
|
draw();
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"description": "Animated Clock with Space Cassio Watch Style",
|
"description": "Animated Clock with Space Cassio Watch Style",
|
||||||
"screenshots": [{ "url": "screens/screen_night.png" },{ "url": "screens/screen_day.png" }],
|
"screenshots": [{ "url": "screens/screen_night.png" },{ "url": "screens/screen_day.png" }],
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"version": "0.11",
|
"version": "0.12",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"tags": "clock, weather, cassio, retro",
|
"tags": "clock, weather, cassio, retro",
|
||||||
"supports": ["BANGLEJS2"],
|
"supports": ["BANGLEJS2"],
|
||||||
|
|
|
@ -26,3 +26,9 @@
|
||||||
0.12: Allow configuration of update interval
|
0.12: Allow configuration of update interval
|
||||||
0.13: Load step goal from Bangle health app as fallback
|
0.13: Load step goal from Bangle health app as fallback
|
||||||
Memory optimizations
|
Memory optimizations
|
||||||
|
0.14: Support to show big weather info
|
||||||
|
0.15: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16
|
||||||
|
0.16: Fix const error
|
||||||
|
Use widget_utils if available
|
||||||
|
0.17: Load circles from clkinfo
|
||||||
|
0.18: Improved clkinfo handling and using it for the weather circle
|
||||||
|
|
|
@ -9,10 +9,11 @@ It can show the following information (this can be configured):
|
||||||
* Steps distance
|
* Steps distance
|
||||||
* Heart rate (automatically updates when screen is on and unlocked)
|
* Heart rate (automatically updates when screen is on and unlocked)
|
||||||
* Battery (including charging status and battery low warning)
|
* Battery (including charging status and battery low warning)
|
||||||
* Weather (requires [weather app](https://banglejs.com/apps/#weather))
|
* Weather (requires [OWM weather provider](https://banglejs.com/apps/?id=owmweather))
|
||||||
* Humidity or wind speed as circle progress
|
* Humidity or wind speed as circle progress
|
||||||
* Temperature inside circle
|
* Temperature inside circle
|
||||||
* Condition as icon below circle
|
* Condition as icon below circle
|
||||||
|
* Big weather icon next to clock
|
||||||
* Time and progress until next sunrise or sunset (requires [my location app](https://banglejs.com/apps/#mylocation))
|
* Time and progress until next sunrise or sunset (requires [my location app](https://banglejs.com/apps/#mylocation))
|
||||||
* Temperature, air pressure or altitude from internal pressure sensor
|
* Temperature, air pressure or altitude from internal pressure sensor
|
||||||
|
|
||||||
|
@ -27,6 +28,8 @@ The color of each circle can be configured. The following colors are available:
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
## Ideas
|
## Ideas
|
||||||
* Show compass heading
|
* Show compass heading
|
||||||
|
@ -35,4 +38,5 @@ The color of each circle can be configured. The following colors are available:
|
||||||
Marco ([myxor](https://github.com/myxor))
|
Marco ([myxor](https://github.com/myxor))
|
||||||
|
|
||||||
## Icons
|
## Icons
|
||||||
Icons taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0
|
Most of the icons are taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0 except the big weather icons which are from
|
||||||
|
[icons8](https://icons8.com/icon/set/weather/small--static--black)
|
||||||
|
|
|
@ -1,17 +1,11 @@
|
||||||
{
|
{
|
||||||
"minHR": 40,
|
|
||||||
"maxHR": 200,
|
|
||||||
"confidence": 0,
|
|
||||||
"stepGoal": 10000,
|
|
||||||
"stepDistanceGoal": 8000,
|
|
||||||
"stepLength": 0.8,
|
|
||||||
"batteryWarn": 30,
|
"batteryWarn": 30,
|
||||||
"showWidgets": false,
|
"showWidgets": false,
|
||||||
"weatherCircleData": "humidity",
|
"weatherCircleData": "humidity",
|
||||||
"circleCount": 3,
|
"circleCount": 3,
|
||||||
"circle1": "hr",
|
"circle1": "Bangle/HRM",
|
||||||
"circle2": "steps",
|
"circle2": "Bangle/Steps",
|
||||||
"circle3": "battery",
|
"circle3": "Bangle/Battery",
|
||||||
"circle4": "weather",
|
"circle4": "weather",
|
||||||
"circle1color": "green-red",
|
"circle1color": "green-red",
|
||||||
"circle2color": "#0000ff",
|
"circle2color": "#0000ff",
|
||||||
|
@ -21,6 +15,15 @@
|
||||||
"circle2colorizeIcon": true,
|
"circle2colorizeIcon": true,
|
||||||
"circle3colorizeIcon": true,
|
"circle3colorizeIcon": true,
|
||||||
"circle4colorizeIcon": false,
|
"circle4colorizeIcon": false,
|
||||||
"hrmValidity": 60,
|
"updateInterval": 60,
|
||||||
"updateInterval": 60
|
"showBigWeather": false,
|
||||||
|
|
||||||
|
"minHR": 40,
|
||||||
|
"maxHR": 200,
|
||||||
|
"confidence": 0,
|
||||||
|
"stepGoal": 10000,
|
||||||
|
"stepDistanceGoal": 8000,
|
||||||
|
"stepLength": 0.8,
|
||||||
|
"hrmValidity": 60
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|