1
0
Fork 0

Merge branch 'espruino:master' into master

master
berkenbu 2022-11-25 20:00:21 -05:00 committed by GitHub
commit 32a170d0a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
307 changed files with 6188 additions and 2354 deletions

View File

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

3
.gitmodules vendored
View File

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

View File

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

View File

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

View File

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

View File

@ -1,304 +1,160 @@
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);
function bigThenSmall(big, small, x, y) { function bigThenSmall(big, small, x, y) {
g.setFont("7x11Numeric7Seg", 2); g.setFont("7x11Numeric7Seg", 2);
g.drawString(big, x, y); g.drawString(big, x, y);
x += g.stringWidth(big); x += g.stringWidth(big);
g.setFont("8x12"); g.setFont("8x12");
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;
var drawTimeout; var drawTimeout;
function queueDraw() { function queueDraw() {
if (drawTimeout) clearTimeout(drawTimeout); if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() { drawTimeout = setTimeout(function() {
drawTimeout = undefined; drawTimeout = undefined;
draw(); draw();
}, 60000 - (Date.now() % 60000)); }, 60000 - (Date.now() % 60000));
} }
function clearIntervals() { function clearIntervals() {
if (rocketInterval) clearInterval(rocketInterval); if (rocketInterval) clearInterval(rocketInterval);
rocketInterval = undefined; rocketInterval = undefined;
if (drawTimeout) clearTimeout(drawTimeout); if (drawTimeout) clearTimeout(drawTimeout);
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; g.setColor(0, 0, 0);
var h = 116; g.drawString(require("locale").time(new Date(), 1), 70, 60);
g.drawRect(l, t, w, h); g.setFont("8x12", 2);
g.fillRect(l, t, w, h); g.drawString(require("locale").dow(new Date(), 2).toUpperCase(), 18, 130);
g.setColor(0, 0, 0); g.setFont("8x12");
g.drawString(require("locale").time(new Date(), 1), 76, 60); g.drawString(require("locale").month(new Date(), 2).toUpperCase(), 80, 126);
g.setFont("8x12", 2);
// day const time = new Date().getDate();
//g.setFont("8x12", 1); g.drawString(time < 10 ? "0" + time : time, 78, 137);
//g.setFont("9x18", 1);
//g.drawString(require("locale").dow(new Date(), 2).toUpperCase(), 25, 136);
g.setFont("8x12", 2);
g.drawString(require("locale").dow(new Date(), 2), 18, 130);
// month
g.setFont("8x12");
g.drawString(require("locale").month(new Date(), 2).toUpperCase(), 80, 127);
// day nb
g.setFont("8x12", 2);
const time = new Date().getDate();
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{ steps = Math.round(steps/1000);
if (WIDGETS.wpedom !== undefined) { return steps + "k";
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);
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.setFont("6x12");
g.drawString("Launching Process", 30, 20);
g.setColor(0, 0, 0); g.setFont("8x12");
if(dataJson && dataJson.weather) drawWeather(dataJson.weather); g.drawString("ACTIVATE", 40, 35);
if(dataJson && dataJson.tasks) drawTasks(dataJson.tasks);
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(); }
}
}); });
Bangle.on("lock", (locked) => { 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();

View File

@ -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",
@ -12,7 +12,7 @@
{ "url": "screenshot-clock-3.jpg" }, { "url": "screenshot-clock-3.jpg" },
{ "url": "screenshot-webapp.jpg" } { "url": "screenshot-webapp.jpg" }
], ],
"supports" : ["BANGLEJS", "BANGLEJS2"], "supports" : ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md", "readme": "README.md",
"allow_emulator":true, "allow_emulator":true,
"storage": [ "storage": [

View File

@ -4,4 +4,6 @@
0.04: Added awareness of allDay field 0.04: Added awareness of allDay field
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

View File

@ -1,29 +1,29 @@
(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 agenda = require("Storage").readJSON("android.calendar.json")
.filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000)
.sort((a,b)=>a.timestamp - b.timestamp);
var now = new Date(); agenda.forEach((entry, i) => {
var agenda = storage.readJSON("android.calendar.json")
.filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000)
.sort((a,b)=>a.timestamp - b.timestamp);
agenda.forEach((entry, i) => { var title = entry.title.slice(0,12);
var date = new Date(entry.timestamp*1000);
var dateStr = locale.date(date).replace(/\d\d\d\d/,"");
dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : "";
var title = entry.title.slice(0,18); agendaItems.items.push({
var date = new Date(entry.timestamp*1000); name: "Agenda "+i,
var dateStr = locale.date(date).replace(/\d\d\d\d/,""); get: () => ({ text: title + "\n" + dateStr, img: null}),
dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : ""; show: function() { agendaItems.items[i].emit("redraw"); },
hide: function () {}
});
});
agendaItems.items.push({ return agendaItems;
name: null, })
get: () => ({ text: title + "\n" + dateStr, img: null}),
show: function() { agendaItems.items[i].emit("redraw"); },
hide: function () {}
});
});
return agendaItems;
})

View File

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

View File

@ -14,3 +14,4 @@
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

View File

@ -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);
}, },
@ -170,7 +166,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

View File

@ -2,7 +2,7 @@
"id": "android", "id": "android",
"name": "Android Integration", "name": "Android Integration",
"shortName": "Android", "shortName": "Android",
"version": "0.16", "version": "0.17",
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
"icon": "app.png", "icon": "app.png",
"tags": "tool,system,messages,notifications,gadgetbridge", "tags": "tool,system,messages,notifications,gadgetbridge",

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -59,3 +59,5 @@
Add patch to ensure that compass heading is corrected on pre-2v15.68 firmware 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 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.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

View File

@ -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);
@ -77,11 +78,17 @@ if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passke
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`;
if (s.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation if (s.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation
// ================================================== FIXING OLDER FIRMWARES // ================================================== FIXING OLDER FIRMWARES
// 2v15.68 and before had compass heading inverted. if (FWVERSION<215.068) // 2v15.68 and before had compass heading inverted.
if (process.version.replace("v","")<215.68)
boot += `Bangle.on('mag',e=>{if(!isNaN(e.heading))e.heading=360-e.heading;}); 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);`; Bangle.getCompass=(c=>(()=>{e=c();if(!isNaN(e.heading))e.heading=360-e.heading;return e;}))(Bangle.getCompass);`;
// 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 // ================================================== BOOT.JS
// Append *.boot.js files // Append *.boot.js files
// These could change bleServices/bleServiceOptions if needed // These could change bleServices/bleServiceOptions if needed

View File

@ -1,7 +1,7 @@
{ {
"id": "boot", "id": "boot",
"name": "Bootloader", "name": "Bootloader",
"version": "0.52", "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",

View File

@ -34,3 +34,9 @@
Prevent mixing of BT and internal HRM events if both are enabled 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 Always use a grace period (default 0 ms) to decouple some connection steps
Device not found errors now utilize increasing timeouts 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

View File

@ -17,5 +17,6 @@
"gracePeriodConnect": 0, "gracePeriodConnect": 0,
"gracePeriodService": 0, "gracePeriodService": 0,
"gracePeriodRequest": 0, "gracePeriodRequest": 0,
"bonding": false "bonding": false,
"active": true
} }

View File

@ -106,7 +106,7 @@ exports.enable = () => {
var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit
supportedCharacteristics["0x2a37"].active = bpm > 0; supportedCharacteristics["0x2a37"].active = bpm > 0;
log("BTHRM BPM " + supportedCharacteristics["0x2a37"].active); log("BTHRM BPM " + supportedCharacteristics["0x2a37"].active);
if (supportedCharacteristics["0x2a37"].active) stopFallback(); switchFallback();
if (bpmTimeout) clearTimeout(bpmTimeout); if (bpmTimeout) clearTimeout(bpmTimeout);
bpmTimeout = setTimeout(()=>{ bpmTimeout = setTimeout(()=>{
bpmTimeout = undefined; bpmTimeout = undefined;
@ -148,14 +148,14 @@ exports.enable = () => {
battery = lastReceivedData["0x180f"]["0x2a19"]; battery = lastReceivedData["0x180f"]["0x2a19"];
} }
if (settings.replace){ if (settings.replace && bpm > 0){
var repEvent = { var repEvent = {
bpm: bpm, bpm: bpm,
confidence: (sensorContact || sensorContact === undefined)? 100 : 0, confidence: (sensorContact || sensorContact === undefined)? 100 : 0,
src: "bthrm" src: "bthrm"
}; };
log("Emitting aggregated HRM", repEvent); log("Emitting HRM_R(bt)", repEvent);
Bangle.emit("HRM_R", repEvent); Bangle.emit("HRM_R", repEvent);
} }
@ -255,7 +255,7 @@ exports.enable = () => {
var retry = function() { var retry = function() {
log("Retry"); log("Retry");
if (!currentRetryTimeout){ if (!currentRetryTimeout && !powerdownRequested){
var clampedTime = retryTime < 100 ? 100 : retryTime; var clampedTime = retryTime < 100 ? 100 : retryTime;
@ -281,13 +281,13 @@ exports.enable = () => {
log("Disconnect: " + reason); log("Disconnect: " + reason);
log("GATT", gatt); log("GATT", gatt);
log("Characteristics", characteristics); log("Characteristics", characteristics);
var retryTimeResetNeeded = true; var retryTimeResetNeeded = true;
retryTimeResetNeeded &= reason != "Connection Timeout"; retryTimeResetNeeded &= reason != "Connection Timeout";
retryTimeResetNeeded &= reason != "No device found matching filters"; retryTimeResetNeeded &= reason != "No device found matching filters";
clearRetryTimeout(retryTimeResetNeeded); clearRetryTimeout(retryTimeResetNeeded);
supportedCharacteristics["0x2a37"].active = false; supportedCharacteristics["0x2a37"].active = false;
startFallback(); if (!powerdownRequested) startFallback();
blockInit = false; blockInit = false;
if (settings.warnDisconnect && !buzzing){ if (settings.warnDisconnect && !buzzing){
buzzing = true; buzzing = true;
@ -369,7 +369,7 @@ exports.enable = () => {
var initBt = function () { var initBt = function () {
log("initBt with blockInit: " + blockInit); log("initBt with blockInit: " + blockInit);
if (blockInit){ if (blockInit && !powerdownRequested){
retry(); retry();
return; return;
} }
@ -387,8 +387,14 @@ exports.enable = () => {
return; return;
} }
log("Requesting device with filters", filters); log("Requesting device with filters", filters);
promise = NRF.requestDevice({ filters: filters, active: true }); try {
promise = NRF.requestDevice({ filters: filters, active: settings.active });
} catch (e){
log("Error during initial request:", e);
onDisconnect(e);
return;
}
if (settings.gracePeriodRequest){ if (settings.gracePeriodRequest){
log("Add " + settings.gracePeriodRequest + "ms grace period after request"); log("Add " + settings.gracePeriodRequest + "ms grace period after request");
} }
@ -454,7 +460,7 @@ exports.enable = () => {
} else { } else {
log("Start bonding"); log("Start bonding");
return gatt.startBonding() return gatt.startBonding()
.then(() => console.log(gatt.getSecurityStatus())); .then(() => log("Security status" + gatt.getSecurityStatus()));
} }
}); });
} }
@ -508,6 +514,8 @@ exports.enable = () => {
}); });
}; };
var powerdownRequested = false;
Bangle.setBTHRMPower = function(isOn, app) { Bangle.setBTHRMPower = function(isOn, app) {
// Do app power handling // Do app power handling
if (!app) app="?"; if (!app) app="?";
@ -518,11 +526,14 @@ exports.enable = () => {
isOn = Bangle._PWR.BTHRM.length; isOn = Bangle._PWR.BTHRM.length;
// so now we know if we're really on // so now we know if we're really on
if (isOn) { if (isOn) {
powerdownRequested = false;
switchFallback(); switchFallback();
if (!Bangle.isBTHRMConnected()) initBt(); if (!Bangle.isBTHRMConnected()) initBt();
} else { // not on } else { // not on
log("Power off for " + app); log("Power off for " + app);
powerdownRequested = true;
clearRetryTimeout(true); clearRetryTimeout(true);
stopFallback();
if (gatt) { if (gatt) {
if (gatt.connected){ if (gatt.connected){
log("Disconnect with gatt", gatt); log("Disconnect with gatt", gatt);
@ -544,9 +555,11 @@ exports.enable = () => {
// register a listener for original HRM events and emit as HRM_int // register a listener for original HRM events and emit as HRM_int
Bangle.on("HRM", (e) => { Bangle.on("HRM", (e) => {
e.modified = true; e.modified = true;
log("Emitting HRM_int", e);
Bangle.emit("HRM_int", e); Bangle.emit("HRM_int", e);
if (fallbackActive){ if (fallbackActive){
// if fallback to internal HRM is active, emit as HRM_R to which everyone listens // 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); Bangle.emit("HRM_R", e);
} }
}); });
@ -572,6 +585,11 @@ exports.enable = () => {
log("setHRMPower for " + app + ": " + (isOn?"on":"off")); log("setHRMPower for " + app + ": " + (isOn?"on":"off"));
if (settings.enabled){ if (settings.enabled){
Bangle.setBTHRMPower(isOn, app); 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){ if ((settings.enabled && !settings.replace) || !settings.enabled){
Bangle.origSetHRMPower(isOn, app); Bangle.origSetHRMPower(isOn, app);
@ -627,7 +645,11 @@ exports.enable = () => {
E.on("kill", ()=>{ E.on("kill", ()=>{
if (gatt && gatt.connected){ if (gatt && gatt.connected){
log("Got killed, trying to disconnect"); log("Got killed, trying to disconnect");
gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect on kill", e)); 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)
}
} }
}); });
} }

View File

@ -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.14", "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",

View File

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

View File

@ -102,6 +102,12 @@
writeSettings("bonding",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); }
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,3 +30,4 @@
0.15: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16 0.15: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16
0.16: Fix const error 0.16: Fix const error
Use widget_utils if available Use widget_utils if available
0.17: Load circles from clkinfo

View File

@ -1,3 +1,4 @@
let clock_info = require("clock_info");
let locale = require("locale"); let locale = require("locale");
let storage = require("Storage"); let storage = require("Storage");
Graphics.prototype.setFontRobotoRegular50NumericOnly = function(scale) { Graphics.prototype.setFontRobotoRegular50NumericOnly = function(scale) {
@ -18,6 +19,7 @@ let settings = Object.assign(
storage.readJSON(SETTINGS_FILE, true) || {} storage.readJSON(SETTINGS_FILE, true) || {}
); );
//TODO deprecate this (and perhaps use in the clkinfo module)
// Load step goal from health app and pedometer widget as fallback // Load step goal from health app and pedometer widget as fallback
if (settings.stepGoal == undefined) { if (settings.stepGoal == undefined) {
let d = storage.readJSON("health.json", true) || {}; let d = storage.readJSON("health.json", true) || {};
@ -29,7 +31,7 @@ if (settings.stepGoal == undefined) {
} }
} }
let timerHrm; let timerHrm; //TODO deprecate this
let drawTimeout; let drawTimeout;
/* /*
@ -44,10 +46,9 @@ let showWidgets = settings.showWidgets || false;
let circleCount = settings.circleCount || 3; let circleCount = settings.circleCount || 3;
let showBigWeather = settings.showBigWeather || false; let showBigWeather = settings.showBigWeather || false;
let hrtValue; let hrtValue; //TODO deprecate this
let now = Math.round(new Date().getTime() / 1000); let now = Math.round(new Date().getTime() / 1000);
// layout values: // layout values:
let colorFg = g.theme.dark ? '#fff' : '#000'; let colorFg = g.theme.dark ? '#fff' : '#000';
let colorBg = g.theme.dark ? '#000' : '#fff'; let colorBg = g.theme.dark ? '#000' : '#fff';
@ -91,8 +92,20 @@ let circleFontSmall = circleCount == 3 ? "Vector:14" : "Vector:10";
let circleFont = circleCount == 3 ? "Vector:15" : "Vector:11"; let circleFont = circleCount == 3 ? "Vector:15" : "Vector:11";
let circleFontBig = circleCount == 3 ? "Vector:16" : "Vector:12"; let circleFontBig = circleCount == 3 ? "Vector:16" : "Vector:12";
let iconOffset = circleCount == 3 ? 6 : 8; let iconOffset = circleCount == 3 ? 6 : 8;
let defaultCircleTypes = ["steps", "hr", "battery", "weather"]; let defaultCircleTypes = ["Bangle/Steps", "Bangle/HRM", "Bangle/Battery", "weather"];
let circleInfoNum = [
0, // circle1
0, // circle2
0, // circle3
0, // circle4
];
let circleItemNum = [
0, // circle1
1, // circle2
2, // circle3
3, // circle4
];
function hideWidgets() { function hideWidgets() {
/* /*
@ -111,7 +124,7 @@ function hideWidgets() {
function draw() { function draw() {
g.clear(true); g.clear(true);
let widgetUtils; let widgetUtils;
try { try {
widgetUtils = require("widget_utils"); widgetUtils = require("widget_utils");
} catch (e) { } catch (e) {
@ -177,6 +190,15 @@ function drawCircle(index) {
let w = getCircleXPosition(type); let w = getCircleXPosition(type);
switch (type) { switch (type) {
case "weather":
drawWeather(w);
break;
case "sunprogress":
case "sunProgress":
drawSunProgress(w);
break;
//TODO those are going to be deprecated, keep for backwards compatibility for now
//ideally all data should come from some clkinfo
case "steps": case "steps":
drawSteps(w); drawSteps(w);
break; break;
@ -189,13 +211,6 @@ function drawCircle(index) {
case "battery": case "battery":
drawBattery(w); drawBattery(w);
break; break;
case "weather":
drawWeather(w);
break;
case "sunprogress":
case "sunProgress":
drawSunProgress(w);
break;
case "temperature": case "temperature":
drawTemperature(w); drawTemperature(w);
break; break;
@ -205,9 +220,12 @@ function drawCircle(index) {
case "altitude": case "altitude":
drawAltitude(w); drawAltitude(w);
break; break;
//end deprecated
case "empty": case "empty":
// we draw nothing here // we draw nothing here
return; return;
default:
drawClkInfo(index, w);
} }
} }
@ -304,6 +322,102 @@ function getImage(graphic, color) {
} }
} }
function drawWeather(w) {
if (!w) w = getCircleXPosition("weather");
let weather = getWeather();
let tempString = weather ? locale.temp(weather.temp - 273.15) : undefined;
let code = weather ? weather.code : -1;
drawCircleBackground(w);
let color = getCircleColor("weather");
let percent;
let data = settings.weatherCircleData;
switch (data) {
case "humidity":
let humidity = weather ? weather.hum : undefined;
if (humidity >= 0) {
percent = humidity / 100;
drawGauge(w, h3, percent, color);
}
break;
case "wind":
if (weather) {
let wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/);
if (wind[1] >= 0) {
if (wind[2] == "kmh") {
wind[1] = windAsBeaufort(wind[1]);
}
// wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale)
percent = wind[1] / 12;
drawGauge(w, h3, percent, color);
}
}
break;
case "empty":
break;
}
drawInnerCircleAndTriangle(w);
writeCircleText(w, tempString ? tempString : "?");
if (code > 0) {
let icon = getWeatherIconByCode(code);
if (icon) g.drawImage(getImage(icon, getCircleIconColor("weather", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
} else {
g.drawString("?", w, h3 + radiusOuter);
}
}
function drawSunProgress(w) {
if (!w) w = getCircleXPosition("sunprogress");
let percent = getSunProgress();
// sunset icons:
let sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA");
let sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA");
drawCircleBackground(w);
let color = getCircleColor("sunprogress");
drawGauge(w, h3, percent, color);
drawInnerCircleAndTriangle(w);
let icon = sunSetDown;
let text = "?";
let times = getSunData();
if (times != undefined) {
let sunRise = Math.round(times.sunrise.getTime() / 1000);
let sunSet = Math.round(times.sunset.getTime() / 1000);
if (!isDay()) {
// night
if (now > sunRise) {
// after sunRise
let upcomingSunRise = sunRise + 60 * 60 * 24;
text = formatSeconds(upcomingSunRise - now);
} else {
text = formatSeconds(sunRise - now);
}
icon = sunSetUp;
} else {
// day, approx sunrise tomorrow:
text = formatSeconds(sunSet - now);
icon = sunSetDown;
}
}
writeCircleText(w, text);
g.drawImage(getImage(icon, getCircleIconColor("sunprogress", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
}
/*
* Deprecated but nice as references for clkinfo
*/
function drawSteps(w) { function drawSteps(w) {
if (!w) w = getCircleXPosition("steps"); if (!w) w = getCircleXPosition("steps");
let steps = getSteps(); let steps = getSteps();
@ -406,99 +520,6 @@ function drawBattery(w) {
g.drawImage(getImage(powerIcon, getCircleIconColor("battery", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); g.drawImage(getImage(powerIcon, getCircleIconColor("battery", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
} }
function drawWeather(w) {
if (!w) w = getCircleXPosition("weather");
let weather = getWeather();
let tempString = weather ? locale.temp(weather.temp - 273.15) : undefined;
let code = weather ? weather.code : -1;
drawCircleBackground(w);
let color = getCircleColor("weather");
let percent;
let data = settings.weatherCircleData;
switch (data) {
case "humidity":
let humidity = weather ? weather.hum : undefined;
if (humidity >= 0) {
percent = humidity / 100;
drawGauge(w, h3, percent, color);
}
break;
case "wind":
if (weather) {
let wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/);
if (wind[1] >= 0) {
if (wind[2] == "kmh") {
wind[1] = windAsBeaufort(wind[1]);
}
// wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale)
percent = wind[1] / 12;
drawGauge(w, h3, percent, color);
}
}
break;
case "empty":
break;
}
drawInnerCircleAndTriangle(w);
writeCircleText(w, tempString ? tempString : "?");
if (code > 0) {
let icon = getWeatherIconByCode(code);
if (icon) g.drawImage(getImage(icon, getCircleIconColor("weather", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
} else {
g.drawString("?", w, h3 + radiusOuter);
}
}
function drawSunProgress(w) {
if (!w) w = getCircleXPosition("sunprogress");
let percent = getSunProgress();
// sunset icons:
let sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA");
let sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA");
drawCircleBackground(w);
let color = getCircleColor("sunprogress");
drawGauge(w, h3, percent, color);
drawInnerCircleAndTriangle(w);
let icon = sunSetDown;
let text = "?";
let times = getSunData();
if (times != undefined) {
let sunRise = Math.round(times.sunrise.getTime() / 1000);
let sunSet = Math.round(times.sunset.getTime() / 1000);
if (!isDay()) {
// night
if (now > sunRise) {
// after sunRise
let upcomingSunRise = sunRise + 60 * 60 * 24;
text = formatSeconds(upcomingSunRise - now);
} else {
text = formatSeconds(sunRise - now);
}
icon = sunSetUp;
} else {
// day, approx sunrise tomorrow:
text = formatSeconds(sunSet - now);
icon = sunSetDown;
}
}
writeCircleText(w, text);
g.drawImage(getImage(icon, getCircleIconColor("sunprogress", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
}
function drawTemperature(w) { function drawTemperature(w) {
if (!w) w = getCircleXPosition("temperature"); if (!w) w = getCircleXPosition("temperature");
@ -577,6 +598,113 @@ function drawAltitude(w) {
}); });
} }
function shortValue(v) {
if (isNaN(v)) return '-';
if (v <= 999) return v;
if (v >= 1000 && v < 10000) {
v = Math.floor(v / 100) * 100;
return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
}
if (v >= 10000) {
v = Math.floor(v / 1000) * 1000;
return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
}
}
function getSteps() {
if (Bangle.getHealthStatus) {
return Bangle.getHealthStatus("day").steps;
}
if (WIDGETS && WIDGETS.wpedom !== undefined) {
return WIDGETS.wpedom.getSteps();
}
return 0;
}
function getPressureValue(type) {
return new Promise((resolve) => {
if (Bangle.getPressure) {
if (!pressureLocked) {
pressureLocked = true;
if (pressureCache && pressureCache[type]) {
resolve(pressureCache[type]);
}
Bangle.getPressure().then(function(d) {
pressureLocked = false;
if (d) {
pressureCache = d;
if (d[type]) {
resolve(d[type]);
}
}
}).catch(() => {});
} else {
if (pressureCache && pressureCache[type]) {
resolve(pressureCache[type]);
}
}
}
});
}
/*
* end deprecated
*/
var menu = null;
function reloadMenu() {
menu = clock_info.load();
for(var i=1; i<5; i++)
if(settings['circle'+i].includes("/")) {
let parts = settings['circle'+i].split("/");
let infoName = parts[0], itemName = parts[1];
let infoNum = menu.findIndex(e=>e.name==infoName);
let itemNum = 0;
//suppose unnamed are varying (like timers or events), pick the first
if(itemName)
itemNum = menu[infoNum].items.findIndex(it=>it.name==itemName);
circleInfoNum[i-1] = infoNum;
circleItemNum[i-1] = itemNum;
}
}
//reload periodically for changes?
reloadMenu();
function drawEmpty(img, w, color) {
drawGauge(w, h3, 0, color);
drawInnerCircleAndTriangle(w);
writeCircleText(w, "?");
if(img)
g.setColor(getGradientColor(color, 0))
.drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24});
}
function drawClkInfo(index, w) {
var info = menu[circleInfoNum[index-1]];
var type = settings['circle'+index];
if (!w) w = getCircleXPosition(type);
drawCircleBackground(w);
const color = getCircleColor(type);
if(!info || !info.items.length) {
drawEmpty(info? info.img : null, w, color);
return;
}
var item = info.items[circleItemNum[index-1]];
//TODO do hide()+get() here
item.show();
item.hide();
item=item.get();
var img = item.img;
if(!img) img = info.img;
let percent = (item.v-item.min) / item.max;
if(isNaN(percent)) percent = 1; //fill it up
drawGauge(w, h3, percent, color);
drawInnerCircleAndTriangle(w);
writeCircleText(w, item.text);
g.setColor(getCircleIconColor(type, color, percent))
.drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24});
}
/* /*
* wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale) * wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale)
*/ */
@ -770,125 +898,15 @@ function writeCircleText(w, content) {
g.drawString(content, w, h3); g.drawString(content, w, h3);
} }
function shortValue(v) {
if (isNaN(v)) return '-';
if (v <= 999) return v;
if (v >= 1000 && v < 10000) {
v = Math.floor(v / 100) * 100;
return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
}
if (v >= 10000) {
v = Math.floor(v / 1000) * 1000;
return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
}
}
function getSteps() {
if (Bangle.getHealthStatus) {
return Bangle.getHealthStatus("day").steps;
}
if (WIDGETS && WIDGETS.wpedom !== undefined) {
return WIDGETS.wpedom.getSteps();
}
return 0;
}
function getWeather() { function getWeather() {
let jsonWeather = storage.readJSON('weather.json'); let jsonWeather = storage.readJSON('weather.json');
return jsonWeather && jsonWeather.weather ? jsonWeather.weather : undefined; return jsonWeather && jsonWeather.weather ? jsonWeather.weather : undefined;
} }
function enableHRMSensor() {
Bangle.setHRMPower(1, "circleclock");
if (hrtValue == undefined) {
hrtValue = '...';
drawHeartRate();
}
}
let pressureLocked = false;
let pressureCache;
function getPressureValue(type) {
return new Promise((resolve) => {
if (Bangle.getPressure) {
if (!pressureLocked) {
pressureLocked = true;
if (pressureCache && pressureCache[type]) {
resolve(pressureCache[type]);
}
Bangle.getPressure().then(function(d) {
pressureLocked = false;
if (d) {
pressureCache = d;
if (d[type]) {
resolve(d[type]);
}
}
}).catch(() => {});
} else {
if (pressureCache && pressureCache[type]) {
resolve(pressureCache[type]);
}
}
}
});
}
function onLock(isLocked) {
if (!isLocked) {
draw();
if (isCircleEnabled("hr")) {
enableHRMSensor();
}
} else {
Bangle.setHRMPower(0, "circleclock");
}
}
Bangle.on('lock', onLock);
function onHRM(hrm) {
if (isCircleEnabled("hr")) {
if (hrm.confidence >= (settings.confidence)) {
hrtValue = hrm.bpm;
if (Bangle.isLCDOn()) {
drawHeartRate();
}
}
// Let us wait before we overwrite "good" HRM values:
if (Bangle.isLCDOn()) {
if (timerHrm) clearTimeout(timerHrm);
timerHrm = setTimeout(() => {
hrtValue = '...';
drawHeartRate();
}, settings.hrmValidity * 1000);
}
}
}
Bangle.on('HRM', onHRM);
function onCharging(charging) {
if (isCircleEnabled("battery")) drawBattery();
}
Bangle.on('charging', onCharging);
if (isCircleEnabled("hr")) {
enableHRMSensor();
}
Bangle.setUI({ Bangle.setUI({
mode : "clock", mode : "clock",
remove : function() { remove : function() {
// Called to unload all of the clock app // Called to unload all of the clock app
Bangle.removeListener('charging', onCharging);
Bangle.removeListener('lock', onLock);
Bangle.removeListener('HRM', onHRM);
Bangle.setHRMPower(0, "circleclock");
if (timerHrm) clearTimeout(timerHrm);
timerHrm = undefined;
if (drawTimeout) clearTimeout(drawTimeout); if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined; drawTimeout = undefined;

View File

@ -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,7 +15,15 @@
"circle2colorizeIcon": true, "circle2colorizeIcon": true,
"circle3colorizeIcon": true, "circle3colorizeIcon": true,
"circle4colorizeIcon": false, "circle4colorizeIcon": false,
"hrmValidity": 60,
"updateInterval": 60, "updateInterval": 60,
"showBigWeather": false "showBigWeather": false,
"minHR": 40,
"maxHR": 200,
"confidence": 0,
"stepGoal": 10000,
"stepDistanceGoal": 8000,
"stepLength": 0.8,
"hrmValidity": 60
} }

View File

@ -1,7 +1,7 @@
{ "id": "circlesclock", { "id": "circlesclock",
"name": "Circles clock", "name": "Circles clock",
"shortName":"Circles clock", "shortName":"Circles clock",
"version":"0.16", "version":"0.17",
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style", "description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
"icon": "app.png", "icon": "app.png",
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}], "screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],

View File

@ -1,6 +1,7 @@
(function(back) { (function(back) {
const SETTINGS_FILE = "circlesclock.json"; const SETTINGS_FILE = "circlesclock.json";
const storage = require('Storage'); const storage = require('Storage');
const clock_info = require("clock_info");
let settings = Object.assign( let settings = Object.assign(
storage.readJSON("circlesclock.default.json", true) || {}, storage.readJSON("circlesclock.default.json", true) || {},
storage.readJSON(SETTINGS_FILE, true) || {} storage.readJSON(SETTINGS_FILE, true) || {}
@ -11,8 +12,25 @@
storage.write(SETTINGS_FILE, settings); storage.write(SETTINGS_FILE, settings);
} }
const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude"]; //const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude", "timer"];
const namesCircleTypes = ["empty", "steps", "distance", "heart", "battery", "weather", "sun", "temperature", "pressure", "altitude"]; //const namesCircleTypes = ["empty", "steps", "distance", "heart", "battery", "weather", "sun", "temperature", "pressure", "altitude", "timer"];
var valuesCircleTypes = ["empty","weather", "sunprogress"];
var namesCircleTypes = ["empty","weather", "sun"];
clock_info.load().forEach(e=>{
//TODO filter for hasRange and other
if(!e.items.length || !e.items[0].name) {
//suppose unnamed are varying (like timers or events), pick the first
item = e.items[0];
valuesCircleTypes = valuesCircleTypes.concat([e.name+"/"]);
namesCircleTypes = namesCircleTypes.concat([e.name]);
} else {
let values = e.items.map(i=>e.name+"/"+i.name);
let names =e.name=="Bangle" ? e.items.map(i=>i.name) : values;
valuesCircleTypes = valuesCircleTypes.concat(values);
namesCircleTypes = namesCircleTypes.concat(names);
}
})
const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff",
"#00ffff", "#fff", "#000", "green-red", "red-green", "fg"]; "#00ffff", "#fff", "#000", "green-red", "red-green", "fg"];
@ -36,8 +54,6 @@
/*LANG*/'circle 2': ()=>showCircleMenu(2), /*LANG*/'circle 2': ()=>showCircleMenu(2),
/*LANG*/'circle 3': ()=>showCircleMenu(3), /*LANG*/'circle 3': ()=>showCircleMenu(3),
/*LANG*/'circle 4': ()=>showCircleMenu(4), /*LANG*/'circle 4': ()=>showCircleMenu(4),
/*LANG*/'heartrate': ()=>showHRMenu(),
/*LANG*/'steps': ()=>showStepMenu(),
/*LANG*/'battery warn': { /*LANG*/'battery warn': {
value: settings.batteryWarn, value: settings.batteryWarn,
min: 10, min: 10,
@ -78,91 +94,6 @@
E.showMenu(menu); E.showMenu(menu);
} }
function showHRMenu() {
let menu = {
'': { 'title': /*LANG*/'Heartrate' },
/*LANG*/'< Back': ()=>showMainMenu(),
/*LANG*/'minimum': {
value: settings.minHR,
min: 0,
max : 250,
step: 5,
format: x => {
return x + " bpm";
},
onchange: x => save('minHR', x),
},
/*LANG*/'maximum': {
value: settings.maxHR,
min: 20,
max : 250,
step: 5,
format: x => {
return x + " bpm";
},
onchange: x => save('maxHR', x),
},
/*LANG*/'min. confidence': {
value: settings.confidence,
min: 0,
max : 100,
step: 10,
format: x => {
return x + "%";
},
onchange: x => save('confidence', x),
},
/*LANG*/'valid period': {
value: settings.hrmValidity,
min: 10,
max : 1800,
step: 10,
format: x => {
return x + "s";
},
onchange: x => save('hrmValidity', x),
},
};
E.showMenu(menu);
}
function showStepMenu() {
let menu = {
'': { 'title': /*LANG*/'Steps' },
/*LANG*/'< Back': ()=>showMainMenu(),
/*LANG*/'goal': {
value: settings.stepGoal,
min: 1000,
max : 50000,
step: 500,
format: x => {
return x;
},
onchange: x => save('stepGoal', x),
},
/*LANG*/'distance goal': {
value: settings.stepDistanceGoal,
min: 1000,
max : 50000,
step: 500,
format: x => {
return x;
},
onchange: x => save('stepDistanceGoal', x),
},
/*LANG*/'step length': {
value: settings.stepLength,
min: 0.1,
max : 1.5,
step: 0.01,
format: x => {
return x;
},
onchange: x => save('stepLength', x),
}
};
E.showMenu(menu);
}
function showCircleMenu(circleId) { function showCircleMenu(circleId) {
const circleName = "circle" + circleId; const circleName = "circle" + circleId;
const colorKey = circleName + "color"; const colorKey = circleName + "color";
@ -192,6 +123,5 @@
E.showMenu(menu); E.showMenu(menu);
} }
showMainMenu(); showMainMenu();
}); });

View File

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

BIN
apps/clkinfosunrise/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

View File

@ -0,0 +1,33 @@
(function() {
// get today's sunlight times for lat/lon
var sunrise, sunset;
function calculate() {
var SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js");
const locale = require("locale");
var location = require("Storage").readJSON("mylocation.json",1)||{};
location.lat = location.lat||51.5072;
location.lon = location.lon||0.1276;
location.location = location.location||"London";
var times = SunCalc.getTimes(new Date(), location.lat, location.lon);
sunrise = locale.time(times.sunrise,1);
sunset = locale.time(times.sunset,1);
/* do we want to re-calculate this every day? Or we just assume
that 'show' will get called once a day? */
}
return {
name: "Bangle",
items: [
{ name : "Sunrise",
get : () => ({ text : sunrise,
img : atob("GBiBAAAAAAAAAAAAAAAYAAA8AAB+AAD/AAAAAAAAAAAAAAAYAAAYAAQYIA4AcAYAYAA8AAB+AAD/AAH/gD///D///AAAAAAAAAAAAA==") }),
show : calculate, hide : () => {}
}, { name : "Sunset",
get : () => ({ text : sunset,
img : atob("GBiBAAAAAAAAAAAAAAB+AAA8AAAYAAAYAAAAAAAAAAAAAAAYAAAYAAQYIA4AcAYAYAA8AAB+AAD/AAH/gD///D///AAAAAAAAAAAAA==") }),
show : calculate, hide : () => {}
}
]
};
})

View File

@ -0,0 +1,12 @@
{ "id": "clkinfosunrise",
"name": "Sunrise Clockinfo",
"version":"0.01",
"description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays sunrise and sunset based on the location from the 'My Location' app",
"icon": "app.png",
"type": "clkinfo",
"tags": "clkinfo,sunrise",
"supports" : ["BANGLEJS2"],
"storage": [
{"name":"sunrise.clkinfo.js","url":"clkinfo.js"}
]
}

View File

@ -12,6 +12,5 @@
"storage": [ "storage": [
{"name":"demoapp.app.js","url":"app.js"}, {"name":"demoapp.app.js","url":"app.js"},
{"name":"demoapp.img","url":"app-icon.js","evaluate":true} {"name":"demoapp.img","url":"app-icon.js","evaluate":true}
], ]
"sortorder": -9
} }

View File

@ -16,6 +16,11 @@
0.16: Use default Bangle formatter for booleans 0.16: Use default Bangle formatter for booleans
0.17: Bangle 2: Fast loading on exit to clock face. Added option for exit to 0.17: Bangle 2: Fast loading on exit to clock face. Added option for exit to
clock face by timeout. clock face by timeout.
0.18: Move interactions inside setUI. Replace "one click exit" with 0.18: Bangle 2: Move interactions inside setUI. Replace "one click exit" with
back-functionality through setUI, adding the red back button as well. Hardware back-functionality through setUI, adding the red back button as well. Hardware
button to exit is no longer an option. button to exit is no longer an option.
0.19: Bangle 2: Utilize new Bangle.load(), Bangle.showClock() functions to
facilitate 'fast switching' of apps where available.
0.20: Bangle 2: Revert use of Bangle.load() to classic load() calls since
widgets would still be loaded when they weren't supposed to.

View File

@ -1,47 +1,47 @@
{ // must be inside our own scope here so that when we are unloaded everything disappears { // must be inside our own scope here so that when we are unloaded everything disappears
/* Desktop launcher /* Desktop launcher
* *
*/ */
let settings = Object.assign({ let settings = Object.assign({
showClocks: true, showClocks: true,
showLaunchers: true, showLaunchers: true,
direct: false, direct: false,
swipeExit: false, swipeExit: false,
timeOut: "Off" timeOut: "Off"
}, require('Storage').readJSON("dtlaunch.json", true) || {}); }, require('Storage').readJSON("dtlaunch.json", true) || {});
let s = require("Storage"); let s = require("Storage");
var apps = s.list(/\.info$/).map(app=>{ var apps = s.list(/\.info$/).map(app=>{
let a=s.readJSON(app,1); let a=s.readJSON(app,1);
return a && { return a && {
name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src
};}).filter( };}).filter(
app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type)); app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type));
apps.sort((a,b)=>{ apps.sort((a,b)=>{
let n=(0|a.sortorder)-(0|b.sortorder); let n=(0|a.sortorder)-(0|b.sortorder);
if (n) return n; // do sortorder first if (n) return n; // do sortorder first
if (a.name<b.name) return -1; if (a.name<b.name) return -1;
if (a.name>b.name) return 1; if (a.name>b.name) return 1;
return 0; return 0;
}); });
apps.forEach(app=>{ apps.forEach(app=>{
if (app.icon) if (app.icon)
app.icon = s.read(app.icon); // should just be a link to a memory area app.icon = s.read(app.icon); // should just be a link to a memory area
}); });
let Napps = apps.length; let Napps = apps.length;
let Npages = Math.ceil(Napps/4); let Npages = Math.ceil(Napps/4);
let maxPage = Npages-1; let maxPage = Npages-1;
let selected = -1; let selected = -1;
let oldselected = -1; let oldselected = -1;
let page = 0; let page = 0;
const XOFF = 24; const XOFF = 24;
const YOFF = 30; const YOFF = 30;
let drawIcon= function(p,n,selected) { let drawIcon= function(p,n,selected) {
let x = (n%2)*72+XOFF; let x = (n%2)*72+XOFF;
let y = n>1?72+YOFF:YOFF; let y = n>1?72+YOFF:YOFF;
(selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+11,y+3,x+60,y+52); (selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+11,y+3,x+60,y+52);
@ -65,95 +65,91 @@ let drawIcon= function(p,n,selected) {
} }
} }
g.drawString(line.trim(),x+36,y+54+lineY*8); g.drawString(line.trim(),x+36,y+54+lineY*8);
}; };
let drawPage = function(p){ let drawPage = function(p){
g.reset(); g.reset();
g.clearRect(0,24,175,175); g.clearRect(0,24,175,175);
let O = 88+YOFF/2-12*(Npages/2); let O = 88+YOFF/2-12*(Npages/2);
for (let j=0;j<Npages;j++){ for (let j=0;j<Npages;j++){
let y = O+j*12; let y = O+j*12;
g.setColor(g.theme.fg); g.setColor(g.theme.fg);
if (j==page) g.fillCircle(XOFF/2,y,4); if (j==page) g.fillCircle(XOFF/2,y,4);
else g.drawCircle(XOFF/2,y,4); else g.drawCircle(XOFF/2,y,4);
} }
for (let i=0;i<4;i++) { for (let i=0;i<4;i++) {
if (!apps[p*4+i]) return i; if (!apps[p*4+i]) return i;
drawIcon(p,i,selected==i && !settings.direct); drawIcon(p,i,selected==i && !settings.direct);
} }
g.flip(); g.flip();
}; };
Bangle.loadWidgets(); Bangle.loadWidgets();
//g.clear(); drawPage(0);
//Bangle.drawWidgets();
drawPage(0);
let swipeListenerDt = function(dirLeftRight, dirUpDown){ let swipeListenerDt = function(dirLeftRight, dirUpDown){
updateTimeoutToClock();
selected = 0; selected = 0;
oldselected=-1; oldselected=-1;
if(settings.swipeExit && dirLeftRight==1) returnToClock(); if(settings.swipeExit && dirLeftRight==1) Bangle.showClock();
if (dirUpDown==-1||dirLeftRight==-1){ if (dirUpDown==-1||dirLeftRight==-1){
++page; if (page>maxPage) page=0; ++page; if (page>maxPage) page=0;
drawPage(page); drawPage(page);
} else if (dirUpDown==1||(dirLeftRight==1 && !settings.swipeExit)){ } else if (dirUpDown==1||(dirLeftRight==1 && !settings.swipeExit)){
--page; if (page<0) page=maxPage; --page; if (page<0) page=maxPage;
drawPage(page); drawPage(page);
} }
}; };
let isTouched = function(p,n){ let isTouched = function(p,n){
if (n<0 || n>3) return false; if (n<0 || n>3) return false;
let x1 = (n%2)*72+XOFF; let y1 = n>1?72+YOFF:YOFF; let x1 = (n%2)*72+XOFF; let y1 = n>1?72+YOFF:YOFF;
let x2 = x1+71; let y2 = y1+81; let x2 = x1+71; let y2 = y1+81;
return (p.x>x1 && p.y>y1 && p.x<x2 && p.y<y2); return (p.x>x1 && p.y>y1 && p.x<x2 && p.y<y2);
}; };
let touchListenerDt = function(_,p){ let touchListenerDt = function(_,p){
updateTimeoutToClock();
let i; let i;
for (i=0;i<4;i++){ for (i=0;i<4;i++){
if((page*4+i)<Napps){ if((page*4+i)<Napps){
if (isTouched(p,i)) { if (isTouched(p,i)) {
drawIcon(page,i,true && !settings.direct); drawIcon(page,i,true && !settings.direct);
if (selected>=0 || settings.direct) { if (selected>=0 || settings.direct) {
if (selected!=i && !settings.direct){ if (selected!=i && !settings.direct){
drawIcon(page,selected,false); drawIcon(page,selected,false);
} else { } else {
load(apps[page*4+i].src); load(apps[page*4+i].src);
}
}
selected=i;
break;
} }
}
selected=i;
break;
} }
}
} }
if ((i==4 || (page*4+i)>Napps) && selected>=0) { if ((i==4 || (page*4+i)>Napps) && selected>=0) {
drawIcon(page,selected,false); drawIcon(page,selected,false);
selected=-1; selected=-1;
} }
}; };
const returnToClock = function() { Bangle.setUI({
Bangle.setUI(); mode : 'custom',
setTimeout(eval, 0, s.read(".bootcde")); back : Bangle.showClock,
}; swipe : swipeListenerDt,
touch : touchListenerDt,
Bangle.setUI({ remove : ()=>{if (timeoutToClock) clearTimeout(timeoutToClock);}
mode : 'custom', });
back : returnToClock,
swipe : swipeListenerDt,
touch : touchListenerDt
});
// taken from Icon Launcher with minor alterations // taken from Icon Launcher with minor alterations
var timeoutToClock; let timeoutToClock;
const updateTimeoutToClock = function(){ const updateTimeoutToClock = function(){
if (settings.timeOut!="Off"){ if (settings.timeOut!="Off"){
let time=parseInt(settings.timeOut); //the "s" will be trimmed by the parseInt let time=parseInt(settings.timeOut); //the "s" will be trimmed by the parseInt
if (timeoutToClock) clearTimeout(timeoutToClock); if (timeoutToClock) clearTimeout(timeoutToClock);
timeoutToClock = setTimeout(returnToClock,time*1000); timeoutToClock = setTimeout(Bangle.showClock,time*1000);
} }
}; };
updateTimeoutToClock(); updateTimeoutToClock();
} // end of app scope } // end of app scope

View File

@ -1,7 +1,7 @@
{ {
"id": "dtlaunch", "id": "dtlaunch",
"name": "Desktop Launcher", "name": "Desktop Launcher",
"version": "0.18", "version": "0.20",
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.", "description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}], "screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
"icon": "icon.png", "icon": "icon.png",

View File

@ -5,3 +5,4 @@
0.03: Improve bootloader update safety. Now sets unsafeFlash:1 to allow flash with 2v11 and later 0.03: Improve bootloader update safety. Now sets unsafeFlash:1 to allow flash with 2v11 and later
Add CRC checks for common bootloaders that we know don't work Add CRC checks for common bootloaders that we know don't work
0.04: Include a precompiled bootloader for easy bootloader updates 0.04: Include a precompiled bootloader for easy bootloader updates
0.05: Rename Bootloader->DFU and add explanation to avoid confusion with Bootloader app

View File

@ -3,7 +3,7 @@
<link rel="stylesheet" href="../../css/spectre.min.css"> <link rel="stylesheet" href="../../css/spectre.min.css">
</head> </head>
<body> <body>
<p>This tool allows you to update the bootloader on <a href="https://www.espruino.com/Bangle.js2">Bangle.js 2</a> devices <p>This tool allows you to update the firmware on <a href="https://www.espruino.com/Bangle.js2">Bangle.js 2</a> devices
from within the App Loader.</p> from within the App Loader.</p>
<div id="fw-unknown"> <div id="fw-unknown">
@ -12,27 +12,41 @@
<a href="https://www.espruino.com/Bangle.js#firmware-updates" target="_blank">see the Bangle.js 1 instructions</a></b></p> <a href="https://www.espruino.com/Bangle.js#firmware-updates" target="_blank">see the Bangle.js 1 instructions</a></b></p>
</div> </div>
<ul> <ul>
<p>Your current firmware version is <span id="fw-version" style="font-weight:bold">unknown</span> and bootloader is <span id="boot-version" style="font-weight:bold">unknown</span></p> <p>Your current firmware version is <span id="fw-version" style="font-weight:bold">unknown</span> and DFU is <span id="boot-version" style="font-weight:bold">unknown</span></p>
</ul> </ul>
<div id="fw-ok" style="display:none"> <div id="fw-ok" style="display:none">
<p>If you have an early (KickStarter or developer) Bangle.js device and still have the old 2v10.x bootloader, the Firmware Update <p>If you have an early (KickStarter or developer) Bangle.js device and still have the old 2v10.x DFU, the Firmware Update
will fail with a message about the bootloader version. If so, please <a href="bootloader_espruino_2v12_banglejs2.hex" class="fw-link">click here to update to bootloader 2v12</a> and then click the 'Upload' button that appears.</p> will fail with a message about the DFU version. If so, please <a href="bootloader_espruino_2v12_banglejs2.hex" class="fw-link">click here to update to DFU 2v12</a> and then click the 'Upload' button that appears.</p>
<div id="latest-firmware" style="display:none"> <div id="latest-firmware" style="display:none">
<p>The currently available Espruino firmware releases are:</p> <p>The currently available Espruino firmware releases are:</p>
<ul id="latest-firmware-list"> <ul id="latest-firmware-list">
</ul> </ul>
<p>To update, click a link above and then click the 'Upload' button that appears.</p> <p>To update, click a link above and then click the 'Upload' button that appears.</p>
</div> </div>
<a href="#" id="advanced-btn">Advanced ▼</a>
<p><a href="#" id="info-btn">What is DFU? ▼</a></p>
<div id="info-div" style="display:none">
<p><b>What is DFU?</b></p>
<p><b>DFU</b> stands for <b>Device Firmware Update</b>. This is the first
bit of code that runs when Bangle.js starts, and it is able to update the
Bangle.js firmware. Normally you would update firmware via this Firmware
Updater app, but if for some reason Bangle.js will not boot, you can
<a href="https://www.espruino.com/Bangle.js2#firmware-updates">always use DFU to to the update manually</a>.</p>
<p>DFU is itself a bootloader, but here we're calling it DFU to avoid confusion
with the Bootloader app in the app loader (which prepares Bangle.js for running apps).</p>
</div>
<p><a href="#" id="advanced-btn">Advanced ▼</a></p>
<div id="advanced-div" style="display:none"> <div id="advanced-div" style="display:none">
<p><b>Advanced</b></p>
<p>Firmware updates via this tool work differently to the NRF Connect method mentioned on <p>Firmware updates via this tool work differently to the NRF Connect method mentioned on
<a href="https://www.espruino.com/Bangle.js2#firmware-updates">the Bangle.js 2 page</a>. Firmware <a href="https://www.espruino.com/Bangle.js2#firmware-updates">the Bangle.js 2 page</a>. Firmware
is uploaded to a file on the Bangle. Once complete the Bangle reboots and the bootloader copies is uploaded to a file on the Bangle. Once complete the Bangle reboots and DFU copies
the new firmware into internal Storage.</p> the new firmware into internal Storage.</p>
<p>In addition to the links above, you can upload a hex or zip file directly below. This file should be an <code>.app_hex</code> <p>In addition to the links above, you can upload a hex or zip file directly below. This file should be an <code>.app_hex</code>
file, *not* the normal <code>.hex</code> (as that contains the bootloader as well).</p> file, *not* the normal <code>.hex</code> (as that contains the DFU as well).</p>
<p><b>DANGER!</b> No verification is performed on uploaded ZIP or HEX files - you could <p><b>DANGER!</b> No verification is performed on uploaded ZIP or HEX files - you could
potentially overwrite your bootloader with the wrong binary and brick your Bangle.</p> potentially overwrite your DFU with the wrong binary and brick your Bangle.</p>
<input class="form-input" type="file" id="fileLoader" accept=".hex,.app_hex,.zip"/><br> <input class="form-input" type="file" id="fileLoader" accept=".hex,.app_hex,.zip"/><br>
</div> </div>
<p><button id="upload" class="btn btn-primary" style="display:none">Upload</button></p> <p><button id="upload" class="btn btn-primary" style="display:none">Upload</button></p>
@ -73,7 +87,7 @@ function onInit(device) {
document.getElementById("fw-ok").style = ""; document.getElementById("fw-ok").style = "";
} }
Puck.eval("E.CRC32(E.memoryArea(0xF7000,0x7000))", crc => { Puck.eval("E.CRC32(E.memoryArea(0xF7000,0x7000))", crc => {
console.log("Bootloader CRC = "+crc); console.log("DFU CRC = "+crc);
var version = `unknown (CRC ${crc})`; var version = `unknown (CRC ${crc})`;
var ok = true; var ok = true;
if (crc==1339551013) { version = "2v10.219"; ok = false; } if (crc==1339551013) { version = "2v10.219"; ok = false; }
@ -299,8 +313,8 @@ function createJS_app(binary, startAddress, endAddress) {
bin32[3] = VERSION; // VERSION! Use this to test ourselves bin32[3] = VERSION; // VERSION! Use this to test ourselves
console.log("CRC 0x"+bin32[2].toString(16)); console.log("CRC 0x"+bin32[2].toString(16));
hexJS = "";//`\x10if (E.CRC32(E.memoryArea(${startAddress},${endAddress-startAddress}))==${bin32[2]}) { print("FIRMWARE UP TO DATE!"); load();}\n`; hexJS = "";//`\x10if (E.CRC32(E.memoryArea(${startAddress},${endAddress-startAddress}))==${bin32[2]}) { print("FIRMWARE UP TO DATE!"); load();}\n`;
hexJS += `\x10if (E.CRC32(E.memoryArea(0xF7000,0x7000))==1339551013) { print("BOOTLOADER 2v10.219 needs update"); load();}\n`; hexJS += `\x10if (E.CRC32(E.memoryArea(0xF7000,0x7000))==1339551013) { print("DFU 2v10.219 needs update"); load();}\n`;
hexJS += `\x10if (E.CRC32(E.memoryArea(0xF7000,0x7000))==1207580954) { print("BOOTLOADER 2v10.236 needs update"); load();}\n`; hexJS += `\x10if (E.CRC32(E.memoryArea(0xF7000,0x7000))==1207580954) { print("DFU 2v10.236 needs update"); load();}\n`;
hexJS += '\x10var s = require("Storage");\n'; hexJS += '\x10var s = require("Storage");\n';
hexJS += '\x10s.erase(".firmware");\n'; hexJS += '\x10s.erase(".firmware");\n';
var CHUNKSIZE = 2048; var CHUNKSIZE = 2048;
@ -320,7 +334,7 @@ function createJS_app(binary, startAddress, endAddress) {
function createJS_bootloader(binary, startAddress, endAddress) { function createJS_bootloader(binary, startAddress, endAddress) {
var crc = CRC32(binary); var crc = CRC32(binary);
console.log("CRC 0x"+crc.toString(16)); console.log("CRC 0x"+crc.toString(16));
hexJS = `\x10if (E.CRC32(E.memoryArea(${startAddress},${endAddress-startAddress}))==${crc}) { print("BOOTLOADER UP TO DATE!"); load();}\n`; hexJS = `\x10if (E.CRC32(E.memoryArea(${startAddress},${endAddress-startAddress}))==${crc}) { print("DFU UP TO DATE!"); load();}\n`;
hexJS += `\x10var _fw = new Uint8Array(${binary.length})\n`; hexJS += `\x10var _fw = new Uint8Array(${binary.length})\n`;
var CHUNKSIZE = 1024; var CHUNKSIZE = 1024;
for (var i=0;i<binary.length;i+=CHUNKSIZE) { for (var i=0;i<binary.length;i+=CHUNKSIZE) {
@ -330,14 +344,14 @@ function createJS_bootloader(binary, startAddress, endAddress) {
hexJS += '\x10_fw.set(atob("'+chunk+'"), 0x'+(i).toString(16)+');\n'; hexJS += '\x10_fw.set(atob("'+chunk+'"), 0x'+(i).toString(16)+');\n';
} }
hexJS += `\x10(function() { if (E.CRC32(_fw)!=${crc}) throw "Invalid CRC: 0x"+E.CRC32(_fw).toString(16);\n`; hexJS += `\x10(function() { if (E.CRC32(_fw)!=${crc}) throw "Invalid CRC: 0x"+E.CRC32(_fw).toString(16);\n`;
hexJS += 'E.showMessage("Flashing Bootloader...")\n'; hexJS += 'E.showMessage("Flashing DFU...")\n';
hexJS += 'E.setFlags({unsafeFlash:1})\n'; hexJS += 'E.setFlags({unsafeFlash:1})\n';
hexJS += 'var f = require("Flash");\n'; hexJS += 'var f = require("Flash");\n';
for (var i=startAddress;i<endAddress;i+=4096) for (var i=startAddress;i<endAddress;i+=4096)
hexJS += 'f.erasePage(0x'+i.toString(16)+');\n'; hexJS += 'f.erasePage(0x'+i.toString(16)+');\n';
hexJS += `f.write(_fw,${startAddress});\n`; hexJS += `f.write(_fw,${startAddress});\n`;
hexJS += `})()\n`; hexJS += `})()\n`;
log("Bootloader ready for upload"); log("DFU ready for upload");
} }
function hexFileLoaded(hexString) { function hexFileLoaded(hexString) {
@ -383,7 +397,7 @@ function hexFileLoaded(hexString) {
}); });
if (startAddress == 0xf7000) { if (startAddress == 0xf7000) {
console.log("Bootloader - Writing to internal flash"); console.log("DFU - Writing to internal flash");
createJS_bootloader(new Uint8Array(binary.buffer, HEADER_LEN), startAddress, endAddress); createJS_bootloader(new Uint8Array(binary.buffer, HEADER_LEN), startAddress, endAddress);
} else { } else {
console.log("App - Writing to external flash"); console.log("App - Writing to external flash");
@ -406,6 +420,10 @@ function handleUpload() {
document.getElementById('fileLoader').addEventListener('change', handleFileSelect, false); document.getElementById('fileLoader').addEventListener('change', handleFileSelect, false);
document.getElementById("upload").addEventListener("click", handleUpload); document.getElementById("upload").addEventListener("click", handleUpload);
document.getElementById("info-btn").addEventListener("click", function() {
document.getElementById("info-btn").style = "display:none";
document.getElementById("info-div").style = "";
});
document.getElementById("advanced-btn").addEventListener("click", function() { document.getElementById("advanced-btn").addEventListener("click", function() {
document.getElementById("advanced-btn").style = "display:none"; document.getElementById("advanced-btn").style = "display:none";
document.getElementById("advanced-div").style = ""; document.getElementById("advanced-div").style = "";

View File

@ -1,7 +1,7 @@
{ {
"id": "fwupdate", "id": "fwupdate",
"name": "Firmware Update", "name": "Firmware Update",
"version": "0.04", "version": "0.05",
"description": "Uploads new Espruino firmwares to Bangle.js 2", "description": "Uploads new Espruino firmwares to Bangle.js 2",
"icon": "app.png", "icon": "app.png",
"type": "RAM", "type": "RAM",
@ -10,5 +10,5 @@
"custom": "custom.html", "custom": "custom.html",
"customConnect": true, "customConnect": true,
"storage": [], "storage": [],
"sortorder": 20 "sortorder": -11
} }

65
apps/gipy/ChangeLog Normal file
View File

@ -0,0 +1,65 @@
0.01: Initial code
0.05:
* We now buzz before reaching a waypoint.
* Display is only updated when not locked.
* We detect leaving path and finding path again.
* We display remaining distance to next point.
0.06:
* Special display for points with steep turns.
* Buzz on points with steep turns and unlock.
* Losing gps is now displayed.
0.07:
* We now use orientation to detect current segment
when segments overlap going in both directions.
* File format is now versioned.
0.08:
* Don't use gps course anymore but figure it from previous positions.
* Bugfix: path colors are back.
* Always buzz when reaching waypoint even if unlocked.
0.09:
* We now display interest points.
* Menu to choose which file to load.
0.10:
* Display performances enhancement.
* Waypoints information is embedded in file and extracted from comments on
points.
* Bugfix in map display (last segment was missing + wrong colors).
* Waypoint detections using OSM + sharp angles
* New algorith for direction detection
0.11:
* Better fonts (more free space, still readable).
* Display direction to nearest point when lost.
* Display average speed.
* Turn off gps when locked and between points
0.12:
* Bugfix in speed computation.
* Bugfix in current segment detection.
* Bugfix : lost direction.
* Larger fonts.
* Detecting next point correctly when going back.
0.13:
* Bugfix in lost direction.
* Buzzing 100m ahead instead of 50m.
* Detect sharp turns.
* Display instant speed.
* New instant speed algorithm.
* Bugfix for remaining distance when going back.
0.14:
* Detect starting distance to compute a good average speed.
* Settings
* Account for breaks in average speed.
0.15:
* Record traveled distance to get a good average speed.
* Breaks (low speed) will not count in average speed.
* Bugfix in average speed.

109
apps/gipy/README.md Normal file
View File

@ -0,0 +1,109 @@
# Gipy
Gipy allows you to follow gpx traces on your watch.
![Screenshot](screenshot1.png)
It is for now meant for bicycling and not hiking
(it uses your movement to figure out your orientation
and walking is too slow).
It is untested on Banglejs1. If you can try it, you would be welcome.
This software is not perfect but surprisingly useful.
## Features
It provides the following features :
- display the path with current position from gps
- detects and buzzes if you leave the path
- buzzes before sharp turns
- buzzes before nodes with comments
(for example when you need to turn in https://mapstogpx.com/)
- display instant / average speed
- display distance to next node
- display additional data from openstreetmap :
- water points
- toilets
- artwork
- bakeries
optionally it can also:
- try to turn off gps between crossroads to save battery
## Usage
### Preparing the file
You first need to have a trace file in *gpx* format.
Usually I download from [komoot](https://www.komoot.com/) or I export
from google maps using [mapstogpx](https://mapstogpx.com/).
Note that *mapstogpx* has a super nice feature in its advanced settings.
You can turn on 'next turn info' and be warned by the watch when you need to turn.
Once you have your gpx file you need to convert it to *gpc* which is my custom file format.
They are smaller than gpx and reduce the number of computations left to be done on the watch.
Just click the disk icon and select your gpx file.
This will request additional information from openstreetmap.
Your path will be displayed in svg.
### Starting Gipy
Once you start gipy you will have a menu for selecting your trace (if more than one).
Choose the one you want and here you go :
![Screenshot](screenshot2.png)
On your screen you can see :
- yourself (the big black dot)
- the path (the top of the screen is in front of you)
- if needed a projection of yourself on the path (small black dot)
- extremities of segments as white dots
- turning points as doubled white dots
- some text on the left (from top to bottom) :
* current time
* left distance till end of current segment
* distance from start of path / path length
* average speed / instant speed
- interest points from openstreetmap as color dots :
* red : bakery
* deep blue : water point
* cyan : toilets (often doubles as water point)
* green : artwork
- a *turn* indicator on the top right when you reach a turning point
- a *gps* indicator (blinking) on the top right if you lose gps signal
- a *lost* indicator on the top right if you stray too far away from path
- a black segment extending from you when you are lost, indicating the rough direction of where to go
### Settings
Few settings for now (feel free to suggest me more) :
- keep gps alive : if turned off, will try to save battery by turning the gps off on long segments
- max speed : used to compute how long to turn the gps off
### Caveats
It is good to use but you should know :
- the gps might take a long time to start initially (see the assisted gps update app).
- gps signal is noisy : there is therefore a small delay for instant speed. sometimes you may jump somewhere else.
- your gpx trace has been decimated and approximated : the **REAL PATH** might be **A FEW METERS AWAY**
- sometimes the watch will tell you that you are lost but you are in fact on the path.
- battery saving by turning off gps is not very well tested (disabled by default).
- buzzing does not always work: when there is a high load on the watch, the buzzes might just never happen :-(.
- buzzes are not strong enough to be always easily noticed.
- be careful when **GOING DOWNHILL AT VERY HIGH SPEED**. I already missed a few turning points and by the time I realized it,
I had to go back uphill by quite a distance.
## Creator
Feel free to give me feedback : is it useful for you ? what other features would you like ?
frederic.wagner@imag.fr

25
apps/gipy/TODO Normal file
View File

@ -0,0 +1,25 @@
* bugs
- when exactly on turn, distance to next point is still often 50m
-----> it does not buzz very often on turns
- when going backwards we have a tendencing to get a wrong current_segment
* additional features
- config screen
- are we on foot (and should use compass)
- we need to buzz 200m before sharp turns (or even better, 30seconds)
(and look at more than next point)
- display distance to next water/toilet ?
- dynamic map rescale
- display scale (100m)
- compress path ?
* misc
- code is becoming messy

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkBiIA/AE8VqoAGCy1RiN3CyYuBi93uIXJIBV3AAIuMBY4XjQ5YXPRAIAEOwIABPBC4LF54wGF6IwFC5jWGIwxIJC4xJFgDuJJAxJFC6TEIJBzEHGCIYPGA5JQC44YPGBBJKY4gwRfQL4DGCL4GGCAXPGAxGBAAJIMGAwWCGCoWGC55HHJB5HIC8pGDSChfXC5AWIL5ynOC45GJC4h3IIyYwCFxwADgB1SC44uSC4guSAH4Ab"))

766
apps/gipy/app.js Normal file
View File

@ -0,0 +1,766 @@
let simulated = false;
let file_version = 3;
let code_key = 47490;
var settings = Object.assign(
{
keep_gps_alive: true,
max_speed: 35,
},
require("Storage").readJSON("gipy.json", true) || {}
);
let interests_colors = [
0xf800, // Bakery, red
0x001f, // DrinkingWater, blue
0x07ff, // Toilets, cyan
0x07e0, // Artwork, green
];
function binary_search(array, x) {
let start = 0,
end = array.length - 1;
while (start <= end) {
let mid = Math.floor((start + end) / 2);
if (array[mid] < x) start = mid + 1;
else end = mid - 1;
}
return start;
}
class Status {
constructor(path) {
this.path = path;
this.on_path = false; // are we on the path or lost ?
this.position = null; // where we are
this.adjusted_cos_direction = null; // cos of where we look at
this.adjusted_sin_direction = null; // sin of where we look at
this.current_segment = null; // which segment is closest
this.reaching = null; // which waypoint are we reaching ?
this.distance_to_next_point = null; // how far are we from next point ?
this.paused_time = 0.0; // how long did we stop (stops don't count in avg speed)
this.paused_since = getTime();
let r = [0];
// let's do a reversed prefix computations on all distances:
// loop on all segments in reversed order
let previous_point = null;
for (let i = this.path.len - 1; i >= 0; i--) {
let point = this.path.point(i);
if (previous_point !== null) {
r.unshift(r[0] + point.distance(previous_point));
}
previous_point = point;
}
this.remaining_distances = r; // how much distance remains at start of each segment
this.starting_time = this.paused_since; // time we start
this.advanced_distance = 0.0;
this.gps_coordinates_counter = 0; // how many coordinates did we receive
this.old_points = [];
this.old_times = [];
}
new_position_reached(position) {
// we try to figure out direction by looking at previous points
// instead of the gps course which is not very nice.
this.gps_coordinates_counter += 1;
let now = getTime();
this.old_points.push(position);
this.old_times.push(now);
if (this.old_points.length == 1) {
return null;
}
let last_point = this.old_points[this.old_points.length - 1];
let oldest_point = this.old_points[0];
// every 7 points we count the distance
if (this.gps_coordinates_counter % 7 == 0) {
let distance = last_point.distance(oldest_point);
if (distance < 150.0) {
// to avoid gps glitches
this.advanced_distance += distance;
}
}
if (this.old_points.length == 8) {
let p1 = this.old_points[0]
.plus(this.old_points[1])
.plus(this.old_points[2])
.plus(this.old_points[3])
.times(1 / 4);
let p2 = this.old_points[4]
.plus(this.old_points[5])
.plus(this.old_points[6])
.plus(this.old_points[7])
.times(1 / 4);
let t1 = (this.old_times[1] + this.old_times[2]) / 2;
let t2 = (this.old_times[5] + this.old_times[6]) / 2;
this.instant_speed = p1.distance(p2) / (t2 - t1);
this.old_points.shift();
this.old_times.shift();
} else {
this.instant_speed =
oldest_point.distance(last_point) / (now - this.old_times[0]);
// update paused time if we are too slow
if (this.instant_speed < 2) {
if (this.paused_since === null) {
this.paused_since = now;
}
} else {
if (this.paused_since !== null) {
this.paused_time += now - this.paused_since;
this.paused_since = null;
}
}
}
// let's just take angle of segment between newest point and a point a bit before
let previous_index = this.old_points.length - 3;
if (previous_index < 0) {
previous_index = 0;
}
let diff = position.minus(this.old_points[previous_index]);
let angle = Math.atan2(diff.lat, diff.lon);
return angle;
}
update_position(new_position, maybe_direction) {
let direction = this.new_position_reached(new_position);
if (direction === null) {
if (maybe_direction === null) {
return;
} else {
direction = maybe_direction;
}
}
this.adjusted_cos_direction = Math.cos(-direction - Math.PI / 2.0);
this.adjusted_sin_direction = Math.sin(-direction - Math.PI / 2.0);
cos_direction = Math.cos(direction);
sin_direction = Math.sin(direction);
this.position = new_position;
// detect segment we are on now
let res = this.path.nearest_segment(
this.position,
Math.max(0, this.current_segment - 1),
Math.min(this.current_segment + 2, this.path.len - 1),
cos_direction,
sin_direction
);
let orientation = res[0];
let next_segment = res[1];
if (this.is_lost(next_segment)) {
// it did not work, try anywhere
res = this.path.nearest_segment(
this.position,
0,
this.path.len - 1,
cos_direction,
sin_direction
);
orientation = res[0];
next_segment = res[1];
}
// now check if we strayed away from path or back to it
let lost = this.is_lost(next_segment);
if (this.on_path == lost) {
// if status changes
if (lost) {
Bangle.buzz(); // we lost path
setTimeout(() => Bangle.buzz(), 500);
setTimeout(() => Bangle.buzz(), 1000);
setTimeout(() => Bangle.buzz(), 1500);
}
this.on_path = !lost;
}
this.current_segment = next_segment;
// check if we are nearing the next point on our path and alert the user
let next_point = this.current_segment + (1 - orientation);
this.distance_to_next_point = Math.ceil(
this.position.distance(this.path.point(next_point))
);
// disable gps when far from next point and locked
if (Bangle.isLocked() && !settings.keep_gps_alive) {
let time_to_next_point =
(this.distance_to_next_point * 3.6) / settings.max_speed;
if (time_to_next_point > 60) {
Bangle.setGPSPower(false, "gipy");
setTimeout(function () {
Bangle.setGPSPower(true, "gipy");
}, time_to_next_point);
}
}
if (this.reaching != next_point && this.distance_to_next_point <= 100) {
this.reaching = next_point;
let reaching_waypoint = this.path.is_waypoint(next_point);
if (reaching_waypoint) {
Bangle.buzz();
setTimeout(() => Bangle.buzz(), 500);
setTimeout(() => Bangle.buzz(), 1000);
setTimeout(() => Bangle.buzz(), 1500);
if (Bangle.isLocked()) {
Bangle.setLocked(false);
}
}
}
// re-display
this.display(orientation);
}
remaining_distance(orientation) {
let remaining_in_correct_orientation =
this.remaining_distances[this.current_segment + 1] +
this.position.distance(this.path.point(this.current_segment + 1));
if (orientation == 0) {
return remaining_in_correct_orientation;
} else {
return this.remaining_distances[0] - remaining_in_correct_orientation;
}
}
is_lost(segment) {
let distance_to_nearest = this.position.distance_to_segment(
this.path.point(segment),
this.path.point(segment + 1)
);
return distance_to_nearest > 50;
}
display(orientation) {
g.clear();
this.display_map();
this.display_interest_points();
this.display_stats(orientation);
Bangle.drawWidgets();
}
display_interest_points() {
// this is the algorithm in case we have a lot of interest points
// let's draw all points for 5 segments centered on current one
let starting_group = Math.floor(Math.max(this.current_segment - 2, 0) / 3);
let ending_group = Math.floor(
Math.min(this.current_segment + 2, this.path.len - 2) / 3
);
let starting_bucket = binary_search(
this.path.interests_starts,
starting_group
);
let ending_bucket = binary_search(
this.path.interests_starts,
ending_group + 0.5
);
// we have 5 points per bucket
let end_index = Math.min(
this.path.interests_types.length - 1,
ending_bucket * 5
);
for (let i = starting_bucket * 5; i <= end_index; i++) {
let index = this.path.interests_on_path[i];
let interest_point = this.path.interest_point(index);
let color = this.path.interest_color(i);
let c = interest_point.coordinates(
this.position,
this.adjusted_cos_direction,
this.adjusted_sin_direction
);
g.setColor(color).fillCircle(c[0], c[1], 5);
}
}
display_stats(orientation) {
let remaining_distance = this.remaining_distance(orientation);
let rounded_distance = Math.round(remaining_distance / 100) / 10;
let total = Math.round(this.remaining_distances[0] / 100) / 10;
let now = new Date();
let minutes = now.getMinutes().toString();
if (minutes.length < 2) {
minutes = "0" + minutes;
}
let hours = now.getHours().toString();
g.setFont("6x8:2")
.setFontAlign(-1, -1, 0)
.setColor(g.theme.fg)
.drawString(hours + ":" + minutes, 0, 30);
g.setFont("6x8:2").drawString(
"" + this.distance_to_next_point + "m",
0,
g.getHeight() - 49
);
let point_time = this.old_times[this.old_times.length - 1];
let done_in = point_time - this.starting_time - this.paused_time;
let approximate_speed = Math.round(
(this.advanced_distance * 3.6) / done_in
);
let approximate_instant_speed = Math.round(this.instant_speed * 3.6);
g.setFont("6x8:2")
.setFontAlign(-1, -1, 0)
.drawString(
"" + approximate_speed + "km/h (in." + approximate_instant_speed + ")",
0,
g.getHeight() - 15
);
g.setFont("6x8:2").drawString(
"" + rounded_distance + "/" + total,
0,
g.getHeight() - 32
);
if (this.distance_to_next_point <= 100) {
if (this.path.is_waypoint(this.reaching)) {
g.setColor(0.0, 1.0, 0.0)
.setFont("6x15")
.drawString("turn", g.getWidth() - 50, 30);
}
}
if (!this.on_path) {
g.setColor(1.0, 0.0, 0.0)
.setFont("6x15")
.drawString("lost", g.getWidth() - 55, 35);
}
}
display_map() {
// don't display all segments, only those neighbouring current segment
// this is most likely to be the correct display
// while lowering the cost a lot
//
// note that all code is inlined here to speed things up from 400ms to 200ms
let start = Math.max(this.current_segment - 4, 0);
let end = Math.min(this.current_segment + 6, this.path.len);
let pos = this.position;
let cos = this.adjusted_cos_direction;
let sin = this.adjusted_sin_direction;
let points = this.path.points;
let cx = pos.lon;
let cy = pos.lat;
let half_width = g.getWidth() / 2;
let half_height = g.getHeight() / 2;
let previous_x = null;
let previous_y = null;
for (let i = start; i < end; i++) {
let tx = (points[2 * i] - cx) * 40000.0;
let ty = (points[2 * i + 1] - cy) * 40000.0;
let rotated_x = tx * cos - ty * sin;
let rotated_y = tx * sin + ty * cos;
let x = half_width - Math.round(rotated_x); // x is inverted
let y = half_height + Math.round(rotated_y);
if (previous_x !== null) {
if (i == this.current_segment + 1) {
g.setColor(0.0, 1.0, 0.0);
} else {
g.setColor(1.0, 0.0, 0.0);
}
g.drawLine(previous_x, previous_y, x, y);
if (this.path.is_waypoint(i - 1)) {
g.setColor(g.theme.fg);
g.fillCircle(previous_x, previous_y, 6);
g.setColor(g.theme.bg);
g.fillCircle(previous_x, previous_y, 5);
}
g.setColor(g.theme.fg);
g.fillCircle(previous_x, previous_y, 4);
g.setColor(g.theme.bg);
g.fillCircle(previous_x, previous_y, 3);
}
previous_x = x;
previous_y = y;
}
if (this.path.is_waypoint(end - 1)) {
g.setColor(g.theme.fg);
g.fillCircle(previous_x, previous_y, 6);
g.setColor(g.theme.bg);
g.fillCircle(previous_x, previous_y, 5);
}
g.setColor(g.theme.fg);
g.fillCircle(previous_x, previous_y, 4);
g.setColor(g.theme.bg);
g.fillCircle(previous_x, previous_y, 3);
// now display ourselves
g.setColor(g.theme.fgH);
g.fillCircle(half_width, half_height, 5);
// display old points for direction debug
// for (let i = 0; i < this.old_points.length; i++) {
// let tx = (this.old_points[i].lon - cx) * 40000.0;
// let ty = (this.old_points[i].lat - cy) * 40000.0;
// let rotated_x = tx * cos - ty * sin;
// let rotated_y = tx * sin + ty * cos;
// let x = half_width - Math.round(rotated_x); // x is inverted
// let y = half_height + Math.round(rotated_y);
// g.setColor((i + 1) / 4.0, 0.0, 0.0);
// g.fillCircle(x, y, 3);
// }
// display current-segment's projection for debug
let projection = pos.closest_segment_point(
this.path.point(this.current_segment),
this.path.point(this.current_segment + 1)
);
let tx = (projection.lon - cx) * 40000.0;
let ty = (projection.lat - cy) * 40000.0;
let rotated_x = tx * cos - ty * sin;
let rotated_y = tx * sin + ty * cos;
let x = half_width - Math.round(rotated_x); // x is inverted
let y = half_height + Math.round(rotated_y);
g.setColor(g.theme.fg);
g.fillCircle(x, y, 4);
// display direction to next point if lost
if (!this.on_path) {
let next_point = this.path.point(this.current_segment + 1);
let diff = next_point.minus(this.position);
let angle = Math.atan2(diff.lat, diff.lon);
let tx = Math.cos(angle) * 50.0;
let ty = Math.sin(angle) * 50.0;
let rotated_x = tx * cos - ty * sin;
let rotated_y = tx * sin + ty * cos;
let x = half_width - Math.round(rotated_x); // x is inverted
let y = half_height + Math.round(rotated_y);
g.setColor(g.theme.fgH).drawLine(half_width, half_height, x, y);
}
}
}
function load_gpc(filename) {
let buffer = require("Storage").readArrayBuffer(filename);
let offset = 0;
// header
let header = Uint16Array(buffer, offset, 5);
offset += 5 * 2;
let key = header[0];
let version = header[1];
let points_number = header[2];
if (key != code_key || version > file_version) {
E.showMessage("Invalid gpc file");
load();
}
// path points
let points = Float64Array(buffer, offset, points_number * 2);
offset += 8 * points_number * 2;
// path waypoints
let waypoints_len = Math.ceil(points_number / 8.0);
let waypoints = Uint8Array(buffer, offset, waypoints_len);
offset += waypoints_len;
// interest points
let interests_number = header[3];
let interests_coordinates = Float64Array(
buffer,
offset,
interests_number * 2
);
offset += 8 * interests_number * 2;
let interests_types = Uint8Array(buffer, offset, interests_number);
offset += interests_number;
// interests on path
let interests_on_path_number = header[4];
let interests_on_path = Uint16Array(buffer, offset, interests_on_path_number);
offset += 2 * interests_on_path_number;
let starts_length = Math.ceil(interests_on_path_number / 5.0);
let interests_starts = Uint16Array(buffer, offset, starts_length);
offset += 2 * starts_length;
return [
points,
waypoints,
interests_coordinates,
interests_types,
interests_on_path,
interests_starts,
];
}
class Path {
constructor(arrays) {
this.points = arrays[0];
this.waypoints = arrays[1];
this.interests_coordinates = arrays[2];
this.interests_types = arrays[3];
this.interests_on_path = arrays[4];
this.interests_starts = arrays[5];
}
is_waypoint(point_index) {
let i = Math.floor(point_index / 8);
let subindex = point_index % 8;
let r = this.waypoints[i] & (1 << subindex);
return r != 0;
}
// execute op on all segments.
// start is index of first wanted segment
// end is 1 after index of last wanted segment
on_segments(op, start, end) {
let previous_point = null;
for (let i = start; i < end + 1; i++) {
let point = new Point(this.points[2 * i], this.points[2 * i + 1]);
if (previous_point !== null) {
op(previous_point, point, i);
}
previous_point = point;
}
}
// return point at given index
point(index) {
let lon = this.points[2 * index];
let lat = this.points[2 * index + 1];
return new Point(lon, lat);
}
interest_point(index) {
let lon = this.interests_coordinates[2 * index];
let lat = this.interests_coordinates[2 * index + 1];
return new Point(lon, lat);
}
interest_color(index) {
return interests_colors[this.interests_types[index]];
}
// return index of segment which is nearest from point.
// we need a direction because we need there is an ambiguity
// for overlapping segments which are taken once to go and once to come back.
// (in the other direction).
nearest_segment(point, start, end, cos_direction, sin_direction) {
// we are going to compute two min distances, one for each direction.
let indices = [0, 0];
let mins = [Number.MAX_VALUE, Number.MAX_VALUE];
this.on_segments(
function (p1, p2, i) {
// we use the dot product to figure out if oriented correctly
// let distance = point.fake_distance_to_segment(p1, p2);
let projection = point.closest_segment_point(p1, p2);
let distance = point.fake_distance(projection);
// let d = projection.minus(point).times(40000.0);
// let rotated_x = d.lon * acos - d.lat * asin;
// let rotated_y = d.lon * asin + d.lat * acos;
// let x = g.getWidth() / 2 - Math.round(rotated_x); // x is inverted
// let y = g.getHeight() / 2 + Math.round(rotated_y);
//
let diff = p2.minus(p1);
let dot = cos_direction * diff.lon + sin_direction * diff.lat;
let orientation = +(dot < 0); // index 0 is good orientation
// g.setColor(0.0, 0.0 + orientation, 1.0 - orientation).fillCircle(
// x,
// y,
// 10
// );
if (distance <= mins[orientation]) {
mins[orientation] = distance;
indices[orientation] = i - 1;
}
},
start,
end
);
// by default correct orientation (0) wins
// but if other one is really closer, return other one
if (mins[1] < mins[0] / 10.0) {
return [1, indices[1]];
} else {
return [0, indices[0]];
}
}
get len() {
return this.points.length / 2;
}
}
class Point {
constructor(lon, lat) {
this.lon = lon;
this.lat = lat;
}
coordinates(current_position, cos_direction, sin_direction) {
let translated = this.minus(current_position).times(40000.0);
let rotated_x =
translated.lon * cos_direction - translated.lat * sin_direction;
let rotated_y =
translated.lon * sin_direction + translated.lat * cos_direction;
return [
g.getWidth() / 2 - Math.round(rotated_x), // x is inverted
g.getHeight() / 2 + Math.round(rotated_y),
];
}
minus(other_point) {
let xdiff = this.lon - other_point.lon;
let ydiff = this.lat - other_point.lat;
return new Point(xdiff, ydiff);
}
plus(other_point) {
return new Point(this.lon + other_point.lon, this.lat + other_point.lat);
}
length_squared(other_point) {
let d = this.minus(other_point);
return d.lon * d.lon + d.lat * d.lat;
}
times(scalar) {
return new Point(this.lon * scalar, this.lat * scalar);
}
dot(other_point) {
return this.lon * other_point.lon + this.lat * other_point.lat;
}
distance(other_point) {
//see https://www.movable-type.co.uk/scripts/latlong.html
const R = 6371e3; // metres
const phi1 = (this.lat * Math.PI) / 180;
const phi2 = (other_point.lat * Math.PI) / 180;
const deltaphi = ((other_point.lat - this.lat) * Math.PI) / 180;
const deltalambda = ((other_point.lon - this.lon) * Math.PI) / 180;
const a =
Math.sin(deltaphi / 2) * Math.sin(deltaphi / 2) +
Math.cos(phi1) *
Math.cos(phi2) *
Math.sin(deltalambda / 2) *
Math.sin(deltalambda / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c; // in meters
}
fake_distance(other_point) {
return Math.sqrt(this.length_squared(other_point));
}
closest_segment_point(v, w) {
// from : https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
// Return minimum distance between line segment vw and point p
let l2 = v.length_squared(w); // i.e. |w-v|^2 - avoid a sqrt
if (l2 == 0.0) {
return v; // v == w case
}
// Consider the line extending the segment, parameterized as v + t (w - v).
// We find projection of point p onto the line.
// It falls where t = [(p-v) . (w-v)] / |w-v|^2
// We clamp t from [0,1] to handle points outside the segment vw.
let t = Math.max(0, Math.min(1, this.minus(v).dot(w.minus(v)) / l2));
return v.plus(w.minus(v).times(t)); // Projection falls on the segment
}
distance_to_segment(v, w) {
let projection = this.closest_segment_point(v, w);
return this.distance(projection);
}
fake_distance_to_segment(v, w) {
let projection = this.closest_segment_point(v, w);
return this.fake_distance(projection);
}
}
Bangle.loadWidgets();
let fake_gps_point = 0.0;
function simulate_gps(status) {
if (fake_gps_point > status.path.len - 1) {
return;
}
let point_index = Math.floor(fake_gps_point);
if (point_index >= status.path.len) {
return;
}
//let p1 = status.path.point(0);
//let n = status.path.len;
//let p2 = status.path.point(n - 1);
let p1 = status.path.point(point_index);
let p2 = status.path.point(point_index + 1);
let alpha = fake_gps_point - point_index;
let pos = p1.times(1 - alpha).plus(p2.times(alpha));
let old_pos = status.position;
fake_gps_point += 0.05; // advance simulation
status.update_position(pos, null);
}
function drawMenu() {
const menu = {
"": { title: "choose trace" },
};
var files = require("Storage").list(".gpc");
for (var i = 0; i < files.length; ++i) {
menu[files[i]] = start.bind(null, files[i]);
}
menu["Exit"] = function () {
load();
};
E.showMenu(menu);
}
function start(fn) {
E.showMenu();
console.log("loading", fn);
// let path = new Path(load_gpx("test.gpx"));
let path = new Path(load_gpc(fn));
let status = new Status(path);
if (simulated) {
status.position = new Point(status.path.point(0));
setInterval(simulate_gps, 500, status);
} else {
// let's display start while waiting for gps signal
let p1 = status.path.point(0);
let p2 = status.path.point(1);
let diff = p2.minus(p1);
let direction = Math.atan2(diff.lat, diff.lon);
Bangle.setLocked(false);
status.update_position(p1, direction);
let frame = 0;
let set_coordinates = function (data) {
frame += 1;
// 0,0 coordinates are considered invalid since we sometimes receive them out of nowhere
let valid_coordinates =
!isNaN(data.lat) &&
!isNaN(data.lon) &&
(data.lat != 0.0 || data.lon != 0.0);
if (valid_coordinates) {
status.update_position(new Point(data.lon, data.lat), null);
}
let gps_status_color;
if (frame % 2 == 0 || valid_coordinates) {
gps_status_color = g.theme.bg;
} else {
gps_status_color = g.theme.fg;
}
g.setColor(gps_status_color)
.setFont("6x8:2")
.drawString("gps", g.getWidth() - 40, 30);
};
Bangle.setGPSPower(true, "gipy");
Bangle.on("GPS", set_coordinates);
Bangle.on("lock", function (on) {
if (!on) {
Bangle.setGPSPower(true, "gipy"); // activate gps when unlocking
}
});
}
}
let files = require("Storage").list(".gpc");
if (files.length <= 1) {
if (files.length == 0) {
load();
} else {
start(files[0]);
}
} else {
drawMenu();
}

BIN
apps/gipy/gipy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

196
apps/gipy/interface.html Normal file
View File

@ -0,0 +1,196 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
<style>
svg { width:95% }
</style>
</head>
<body>
<p>Please select a gpx file to be converted to gpc and loaded.</p>
gpx file : <input type="file" is="gpx_file" id="fileInput" accept=".gpx">
<br>
gpc filename : <input type="text" id="gpc_file" name="gpc_file" maxlength="24">.gpc (max 24 characters)
<br>
<input type="checkbox" id="osm" name="osm" checked>
<label for="osm">fetch interests from openstreetmap</label>
<table>
<tr>
<th><bold>OpenstreetMap <a href="https://wiki.openstreetmap.org/wiki/Tags">NODE Tags</a></bold></th>
</tr>
<tr>
<th>color</th><th>key</th><th>value</th>
</tr>
<tr>
<th style="color:red">red</th><th><input type="text" id="key1" name="key1" value="shop"></th><th><input type="text" id="value1" name="value1" value="bakery"></th>
</tr>
<tr>
<th style="color:blue">blue</th><th><input type="text" id="key2" name="key2" value="amenity"></th><th><input type="text" id="value2" name="value2" value="drinking_water"></th>
</tr>
<tr>
<th style="color:cyan">cyan</th><th><input type="text" id="key3" name="key3" value="amenity"></th><th><input type="text" id="value3" name="value3" value="toilets"></th>
</tr>
<tr>
<th style="color:green">green</th><th><input type="text" id="key4" name="key4" value="tourism"></th><th><input type="text" id="value4" name="value4" value="artwork"></th>
</tr>
</table>
<p>nice tags could be :
shop/bicycle, amenity/bank, shop/supermarket, leisure/picnic_table, tourism/information, amenity/pharmacy
</p>
<input type="button" id="convert" name="convert" value="Convert" disabled>
<input type="button" id="upload" name="upload" value="Upload" disabled>
<div id="status"></div>
<div id="map"></div>
<script src="../../core/lib/interface.js"></script>
<script>
function onInit() {
}
</script>
<script type="module">
function vec_to_string(vec) {
let final_string = '';
for (let i = 0 ; i < vec.length ; i++) {
final_string += String.fromCharCode(vec[i]);
}
return final_string;
}
import init, { convert_gpx_strings, convert_gpx_strings_no_osm, get_gpc, get_svg } from "./pkg/gpconv.js";
console.log("imported wasm");
let osm_checkbox = document.querySelector("input[name=osm]");
let with_osm = true;
osm_checkbox.addEventListener('change', function() {
if (this.checked) {
with_osm = true;
document.getElementById('key1').disabled = false;
document.getElementById('key2').disabled = false;
document.getElementById('key3').disabled = false;
document.getElementById('key4').disabled = false;
document.getElementById('value1').disabled = false;
document.getElementById('value2').disabled = false;
document.getElementById('value3').disabled = false;
document.getElementById('value4').disabled = false;
} else {
with_osm = false;
document.getElementById('key1').disabled = true;
document.getElementById('key2').disabled = true;
document.getElementById('key3').disabled = true;
document.getElementById('key4').disabled = true;
document.getElementById('value1').disabled = true;
document.getElementById('value2').disabled = true;
document.getElementById('value3').disabled = true;
document.getElementById('value4').disabled = true;
}
});
let status = document.getElementById("status");
let gpx_content = null;
let gpc_filename = null;
let gpc_content = null;
document
.getElementById("fileInput")
.addEventListener("change", function selectedFileChanged() {
document.getElementById('convert').disabled = true;
document.getElementById('upload').disabled = true;
if (this.files.length === 0) {
console.log("No file selected.");
return;
}
status.innerHTML = "reading file";
let gpx_filename = this.files[0].name;
if (gpc_filename === null || gpc_filename == "") {
if (gpx_filename.length <= 28) {
gpc_filename = gpx_filename.slice(0, gpx_filename.length - 4);
document.getElementById('gpc_file').value = gpc_filename;
}
}
const reader = new FileReader();
reader.onload = function fileReadCompleted() {
console.log("reading file completed");
status.innerHTML = "file reading completed";
gpx_content = reader.result;
document.getElementById('convert').disabled = false;
};
reader.readAsText(this.files[0]);
});
document
.getElementById("convert")
.addEventListener('click', function() {
console.log("starting conversion");
document.getElementById('convert').disabled = true;
document.getElementById('upload').disabled = true;
status.innerHTML = "please wait, converting file";
init().then(() => {
let gpc_svg;
if (with_osm) {
let key1 = document.getElementById('key1').value;
let key2 = document.getElementById('key2').value;
let key3 = document.getElementById('key3').value;
let key4 = document.getElementById('key4').value;
let value1 = document.getElementById('value1').value;
let value2 = document.getElementById('value2').value;
let value3 = document.getElementById('value3').value;
let value4 = document.getElementById('value4').value;
gpc_svg = convert_gpx_strings(gpx_content, key1, value1, key2, value2, key3, value3, key4, value4);
} else {
gpc_svg = convert_gpx_strings_no_osm(gpx_content);
}
gpc_svg.then(gs => {
status.innerHTML = "file converted";
let svg = get_svg(gs);
let svg_string = vec_to_string(svg);
let img = document.getElementById("map");
img.innerHTML = svg_string;
gpc_content = get_gpc(gs);
if (gpc_filename !== null) {
document.getElementById('upload').disabled = false;
}
});
});
});
document
.getElementById("gpc_file")
.addEventListener('change', function() {
gpc_filename = document.getElementById("gpc_file").value;
if (gpc_filename == "") {
document.getElementById("upload").disabled = true;
} else {
if (gpc_content !== null) {
document.getElementById("upload").disabled = false;
}
}
});
document
.getElementById("upload")
.addEventListener('click', function() {
status.innerHTML = "uploading file";
console.log("uploading");
let gpc_string = vec_to_string(gpc_content);
Util.writeStorage(gpc_filename + ".gpc", gpc_string, () => {
status.innerHTML = `${gpc_filename}.gpc uploaded`;
console.log("DONE");
});
});
</script>
</body>
</html>

23
apps/gipy/metadata.json Normal file
View File

@ -0,0 +1,23 @@
{
"id": "gipy",
"name": "Gipy",
"shortName": "Gipy",
"version": "0.15",
"description": "Follow gpx files",
"allow_emulator":false,
"icon": "gipy.png",
"type": "app",
"tags": "tool,outdoors,gps",
"screenshots": [],
"supports": ["BANGLEJS2"],
"readme": "README.md",
"interface": "interface.html",
"storage": [
{"name":"gipy.app.js","url":"app.js"},
{"name":"gipy.settings.js","url":"settings.js"},
{"name":"gipy.img","url":"app-icon.js","evaluate":true}
],
"data": [
{"name":"gipy.json"}
]
}

75
apps/gipy/pkg/gpconv.d.ts vendored Normal file
View File

@ -0,0 +1,75 @@
/* tslint:disable */
/* eslint-disable */
/**
* @param {GpcSvg} gpcsvg
* @returns {Uint8Array}
*/
export function get_gpc(gpcsvg: GpcSvg): Uint8Array;
/**
* @param {GpcSvg} gpcsvg
* @returns {Uint8Array}
*/
export function get_svg(gpcsvg: GpcSvg): Uint8Array;
/**
* @param {string} input_str
* @returns {Promise<GpcSvg>}
*/
export function convert_gpx_strings_no_osm(input_str: string): Promise<GpcSvg>;
/**
* @param {string} input_str
* @param {string} key1
* @param {string} value1
* @param {string} key2
* @param {string} value2
* @param {string} key3
* @param {string} value3
* @param {string} key4
* @param {string} value4
* @returns {Promise<GpcSvg>}
*/
export function convert_gpx_strings(input_str: string, key1: string, value1: string, key2: string, value2: string, key3: string, value3: string, key4: string, value4: string): Promise<GpcSvg>;
/**
*/
export class GpcSvg {
free(): void;
}
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
export interface InitOutput {
readonly memory: WebAssembly.Memory;
readonly __wbg_gpcsvg_free: (a: number) => void;
readonly get_gpc: (a: number, b: number) => void;
readonly get_svg: (a: number, b: number) => void;
readonly convert_gpx_strings_no_osm: (a: number, b: number) => number;
readonly convert_gpx_strings: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number) => number;
readonly __wbindgen_malloc: (a: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number) => number;
readonly __wbindgen_export_2: WebAssembly.Table;
readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd: (a: number, b: number, c: number) => void;
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
readonly __wbindgen_free: (a: number, b: number) => void;
readonly __wbindgen_exn_store: (a: number) => void;
readonly wasm_bindgen__convert__closures__invoke2_mut__h25ed812378167476: (a: number, b: number, c: number, d: number) => void;
}
export type SyncInitInput = BufferSource | WebAssembly.Module;
/**
* Instantiates the given `module`, which can either be bytes or
* a precompiled `WebAssembly.Module`.
*
* @param {SyncInitInput} module
*
* @returns {InitOutput}
*/
export function initSync(module: SyncInitInput): InitOutput;
/**
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {InitInput | Promise<InitInput>} module_or_path
*
* @returns {Promise<InitOutput>}
*/
export default function init (module_or_path?: InitInput | Promise<InitInput>): Promise<InitOutput>;

645
apps/gipy/pkg/gpconv.js Normal file
View File

@ -0,0 +1,645 @@
let wasm;
const heap = new Array(32).fill(undefined);
heap.push(undefined, null, true, false);
function getObject(idx) { return heap[idx]; }
let heap_next = heap.length;
function dropObject(idx) {
if (idx < 36) return;
heap[idx] = heap_next;
heap_next = idx;
}
function takeObject(idx) {
const ret = getObject(idx);
dropObject(idx);
return ret;
}
let WASM_VECTOR_LEN = 0;
let cachedUint8Memory0 = new Uint8Array();
function getUint8Memory0() {
if (cachedUint8Memory0.byteLength === 0) {
cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
}
return cachedUint8Memory0;
}
const cachedTextEncoder = new TextEncoder('utf-8');
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
? function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view);
}
: function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length
};
});
function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length);
getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len);
const mem = getUint8Memory0();
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3);
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
offset += ret.written;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
function isLikeNone(x) {
return x === undefined || x === null;
}
let cachedInt32Memory0 = new Int32Array();
function getInt32Memory0() {
if (cachedInt32Memory0.byteLength === 0) {
cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
}
return cachedInt32Memory0;
}
const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
function getStringFromWasm0(ptr, len) {
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
}
function addHeapObject(obj) {
if (heap_next === heap.length) heap.push(heap.length + 1);
const idx = heap_next;
heap_next = heap[idx];
heap[idx] = obj;
return idx;
}
function debugString(val) {
// primitive types
const type = typeof val;
if (type == 'number' || type == 'boolean' || val == null) {
return `${val}`;
}
if (type == 'string') {
return `"${val}"`;
}
if (type == 'symbol') {
const description = val.description;
if (description == null) {
return 'Symbol';
} else {
return `Symbol(${description})`;
}
}
if (type == 'function') {
const name = val.name;
if (typeof name == 'string' && name.length > 0) {
return `Function(${name})`;
} else {
return 'Function';
}
}
// objects
if (Array.isArray(val)) {
const length = val.length;
let debug = '[';
if (length > 0) {
debug += debugString(val[0]);
}
for(let i = 1; i < length; i++) {
debug += ', ' + debugString(val[i]);
}
debug += ']';
return debug;
}
// Test for built-in
const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
let className;
if (builtInMatches.length > 1) {
className = builtInMatches[1];
} else {
// Failed to match the standard '[object ClassName]'
return toString.call(val);
}
if (className == 'Object') {
// we're a user defined class or Object
// JSON.stringify avoids problems with cycles, and is generally much
// easier than looping through ownProperties of `val`.
try {
return 'Object(' + JSON.stringify(val) + ')';
} catch (_) {
return 'Object';
}
}
// errors
if (val instanceof Error) {
return `${val.name}: ${val.message}\n${val.stack}`;
}
// TODO we could test for more things here, like `Set`s and `Map`s.
return className;
}
function makeMutClosure(arg0, arg1, dtor, f) {
const state = { a: arg0, b: arg1, cnt: 1, dtor };
const real = (...args) => {
// First up with a closure we increment the internal reference
// count. This ensures that the Rust closure environment won't
// be deallocated while we're invoking it.
state.cnt++;
const a = state.a;
state.a = 0;
try {
return f(a, state.b, ...args);
} finally {
if (--state.cnt === 0) {
wasm.__wbindgen_export_2.get(state.dtor)(a, state.b);
} else {
state.a = a;
}
}
};
real.original = state;
return real;
}
function __wbg_adapter_24(arg0, arg1, arg2) {
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd(arg0, arg1, addHeapObject(arg2));
}
function _assertClass(instance, klass) {
if (!(instance instanceof klass)) {
throw new Error(`expected instance of ${klass.name}`);
}
return instance.ptr;
}
function getArrayU8FromWasm0(ptr, len) {
return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
}
/**
* @param {GpcSvg} gpcsvg
* @returns {Uint8Array}
*/
export function get_gpc(gpcsvg) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
_assertClass(gpcsvg, GpcSvg);
wasm.get_gpc(retptr, gpcsvg.ptr);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
var v0 = getArrayU8FromWasm0(r0, r1).slice();
wasm.__wbindgen_free(r0, r1 * 1);
return v0;
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
}
}
/**
* @param {GpcSvg} gpcsvg
* @returns {Uint8Array}
*/
export function get_svg(gpcsvg) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
_assertClass(gpcsvg, GpcSvg);
wasm.get_svg(retptr, gpcsvg.ptr);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
var v0 = getArrayU8FromWasm0(r0, r1).slice();
wasm.__wbindgen_free(r0, r1 * 1);
return v0;
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
}
}
/**
* @param {string} input_str
* @returns {Promise<GpcSvg>}
*/
export function convert_gpx_strings_no_osm(input_str) {
const ptr0 = passStringToWasm0(input_str, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.convert_gpx_strings_no_osm(ptr0, len0);
return takeObject(ret);
}
/**
* @param {string} input_str
* @param {string} key1
* @param {string} value1
* @param {string} key2
* @param {string} value2
* @param {string} key3
* @param {string} value3
* @param {string} key4
* @param {string} value4
* @returns {Promise<GpcSvg>}
*/
export function convert_gpx_strings(input_str, key1, value1, key2, value2, key3, value3, key4, value4) {
const ptr0 = passStringToWasm0(input_str, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(key1, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ptr2 = passStringToWasm0(value1, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len2 = WASM_VECTOR_LEN;
const ptr3 = passStringToWasm0(key2, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len3 = WASM_VECTOR_LEN;
const ptr4 = passStringToWasm0(value2, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len4 = WASM_VECTOR_LEN;
const ptr5 = passStringToWasm0(key3, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len5 = WASM_VECTOR_LEN;
const ptr6 = passStringToWasm0(value3, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len6 = WASM_VECTOR_LEN;
const ptr7 = passStringToWasm0(key4, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len7 = WASM_VECTOR_LEN;
const ptr8 = passStringToWasm0(value4, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len8 = WASM_VECTOR_LEN;
const ret = wasm.convert_gpx_strings(ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5, ptr6, len6, ptr7, len7, ptr8, len8);
return takeObject(ret);
}
function handleError(f, args) {
try {
return f.apply(this, args);
} catch (e) {
wasm.__wbindgen_exn_store(addHeapObject(e));
}
}
function __wbg_adapter_69(arg0, arg1, arg2, arg3) {
wasm.wasm_bindgen__convert__closures__invoke2_mut__h25ed812378167476(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
}
/**
*/
export class GpcSvg {
static __wrap(ptr) {
const obj = Object.create(GpcSvg.prototype);
obj.ptr = ptr;
return obj;
}
__destroy_into_raw() {
const ptr = this.ptr;
this.ptr = 0;
return ptr;
}
free() {
const ptr = this.__destroy_into_raw();
wasm.__wbg_gpcsvg_free(ptr);
}
}
async function load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
if (module.headers.get('Content-Type') != 'application/wasm') {
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
} else {
throw e;
}
}
}
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
} else {
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
}
}
}
function getImports() {
const imports = {};
imports.wbg = {};
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
takeObject(arg0);
};
imports.wbg.__wbg_gpcsvg_new = function(arg0) {
const ret = GpcSvg.__wrap(arg0);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
const obj = getObject(arg1);
const ret = typeof(obj) === 'string' ? obj : undefined;
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
const ret = getStringFromWasm0(arg0, arg1);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
const ret = getObject(arg0);
return addHeapObject(ret);
};
imports.wbg.__wbg_fetch_386f87a3ebf5003c = function(arg0) {
const ret = fetch(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbindgen_cb_drop = function(arg0) {
const obj = takeObject(arg0).original;
if (obj.cnt-- == 1) {
obj.a = 0;
return true;
}
const ret = false;
return ret;
};
imports.wbg.__wbg_fetch_749a56934f95c96c = function(arg0, arg1) {
const ret = getObject(arg0).fetch(getObject(arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_instanceof_Response_eaa426220848a39e = function(arg0) {
let result;
try {
result = getObject(arg0) instanceof Response;
} catch {
result = false;
}
const ret = result;
return ret;
};
imports.wbg.__wbg_url_74285ddf2747cb3d = function(arg0, arg1) {
const ret = getObject(arg1).url;
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbg_status_c4ef3dd591e63435 = function(arg0) {
const ret = getObject(arg0).status;
return ret;
};
imports.wbg.__wbg_headers_fd64ad685cf22e5d = function(arg0) {
const ret = getObject(arg0).headers;
return addHeapObject(ret);
};
imports.wbg.__wbg_text_1169d752cc697903 = function() { return handleError(function (arg0) {
const ret = getObject(arg0).text();
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_newwithstrandinit_05d7180788420c40 = function() { return handleError(function (arg0, arg1, arg2) {
const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_new_2d0053ee81e4dd2a = function() { return handleError(function () {
const ret = new Headers();
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_append_de37df908812970d = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
getObject(arg0).append(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
}, arguments) };
imports.wbg.__wbindgen_is_object = function(arg0) {
const val = getObject(arg0);
const ret = typeof(val) === 'object' && val !== null;
return ret;
};
imports.wbg.__wbg_newnoargs_b5b063fc6c2f0376 = function(arg0, arg1) {
const ret = new Function(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_next_579e583d33566a86 = function(arg0) {
const ret = getObject(arg0).next;
return addHeapObject(ret);
};
imports.wbg.__wbindgen_is_function = function(arg0) {
const ret = typeof(getObject(arg0)) === 'function';
return ret;
};
imports.wbg.__wbg_value_1ccc36bc03462d71 = function(arg0) {
const ret = getObject(arg0).value;
return addHeapObject(ret);
};
imports.wbg.__wbg_iterator_6f9d4f28845f426c = function() {
const ret = Symbol.iterator;
return addHeapObject(ret);
};
imports.wbg.__wbg_new_0b9bfdd97583284e = function() {
const ret = new Object();
return addHeapObject(ret);
};
imports.wbg.__wbg_self_6d479506f72c6a71 = function() { return handleError(function () {
const ret = self.self;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_window_f2557cc78490aceb = function() { return handleError(function () {
const ret = window.window;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_globalThis_7f206bda628d5286 = function() { return handleError(function () {
const ret = globalThis.globalThis;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_global_ba75c50d1cf384f4 = function() { return handleError(function () {
const ret = global.global;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbindgen_is_undefined = function(arg0) {
const ret = getObject(arg0) === undefined;
return ret;
};
imports.wbg.__wbg_call_97ae9d8645dc388b = function() { return handleError(function (arg0, arg1) {
const ret = getObject(arg0).call(getObject(arg1));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_call_168da88779e35f61 = function() { return handleError(function (arg0, arg1, arg2) {
const ret = getObject(arg0).call(getObject(arg1), getObject(arg2));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_next_aaef7c8aa5e212ac = function() { return handleError(function (arg0) {
const ret = getObject(arg0).next();
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_done_1b73b0672e15f234 = function(arg0) {
const ret = getObject(arg0).done;
return ret;
};
imports.wbg.__wbg_new_9962f939219f1820 = function(arg0, arg1) {
try {
var state0 = {a: arg0, b: arg1};
var cb0 = (arg0, arg1) => {
const a = state0.a;
state0.a = 0;
try {
return __wbg_adapter_69(a, state0.b, arg0, arg1);
} finally {
state0.a = a;
}
};
const ret = new Promise(cb0);
return addHeapObject(ret);
} finally {
state0.a = state0.b = 0;
}
};
imports.wbg.__wbg_resolve_99fe17964f31ffc0 = function(arg0) {
const ret = Promise.resolve(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbg_then_11f7a54d67b4bfad = function(arg0, arg1) {
const ret = getObject(arg0).then(getObject(arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_then_cedad20fbbd9418a = function(arg0, arg1, arg2) {
const ret = getObject(arg0).then(getObject(arg1), getObject(arg2));
return addHeapObject(ret);
};
imports.wbg.__wbg_buffer_3f3d764d4747d564 = function(arg0) {
const ret = getObject(arg0).buffer;
return addHeapObject(ret);
};
imports.wbg.__wbg_newwithbyteoffsetandlength_d9aa266703cb98be = function(arg0, arg1, arg2) {
const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
return addHeapObject(ret);
};
imports.wbg.__wbg_new_8c3f0052272a457a = function(arg0) {
const ret = new Uint8Array(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbg_stringify_d6471d300ded9b68 = function() { return handleError(function (arg0) {
const ret = JSON.stringify(getObject(arg0));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_get_765201544a2b6869 = function() { return handleError(function (arg0, arg1) {
const ret = Reflect.get(getObject(arg0), getObject(arg1));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_has_8359f114ce042f5a = function() { return handleError(function (arg0, arg1) {
const ret = Reflect.has(getObject(arg0), getObject(arg1));
return ret;
}, arguments) };
imports.wbg.__wbg_set_bf3f89b92d5a34bf = function() { return handleError(function (arg0, arg1, arg2) {
const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));
return ret;
}, arguments) };
imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
const ret = debugString(getObject(arg1));
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
};
imports.wbg.__wbindgen_memory = function() {
const ret = wasm.memory;
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper947 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 147, __wbg_adapter_24);
return addHeapObject(ret);
};
return imports;
}
function initMemory(imports, maybe_memory) {
}
function finalizeInit(instance, module) {
wasm = instance.exports;
init.__wbindgen_wasm_module = module;
cachedInt32Memory0 = new Int32Array();
cachedUint8Memory0 = new Uint8Array();
return wasm;
}
function initSync(module) {
const imports = getImports();
initMemory(imports);
if (!(module instanceof WebAssembly.Module)) {
module = new WebAssembly.Module(module);
}
const instance = new WebAssembly.Instance(module, imports);
return finalizeInit(instance, module);
}
async function init(input) {
if (typeof input === 'undefined') {
input = new URL('gpconv_bg.wasm', import.meta.url);
}
const imports = getImports();
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
input = fetch(input);
}
initMemory(imports);
const { instance, module } = await load(await input, imports);
return finalizeInit(instance, module);
}
export { initSync }
export default init;

Binary file not shown.

16
apps/gipy/pkg/gpconv_bg.wasm.d.ts vendored Normal file
View File

@ -0,0 +1,16 @@
/* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory;
export function __wbg_gpcsvg_free(a: number): void;
export function get_gpc(a: number, b: number): void;
export function get_svg(a: number, b: number): void;
export function convert_gpx_strings_no_osm(a: number, b: number): number;
export function convert_gpx_strings(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number): number;
export function __wbindgen_malloc(a: number): number;
export function __wbindgen_realloc(a: number, b: number, c: number): number;
export const __wbindgen_export_2: WebAssembly.Table;
export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd(a: number, b: number, c: number): void;
export function __wbindgen_add_to_stack_pointer(a: number): number;
export function __wbindgen_free(a: number, b: number): void;
export function __wbindgen_exn_store(a: number): void;
export function wasm_bindgen__convert__closures__invoke2_mut__h25ed812378167476(a: number, b: number, c: number, d: number): void;

View File

@ -0,0 +1,12 @@
{
"name": "gpconv",
"version": "0.1.0",
"files": [
"gpconv_bg.wasm",
"gpconv.js",
"gpconv.d.ts"
],
"module": "gpconv.js",
"types": "gpconv.d.ts",
"sideEffects": false
}

BIN
apps/gipy/screenshot1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
apps/gipy/screenshot2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

38
apps/gipy/settings.js Normal file
View File

@ -0,0 +1,38 @@
(function (back) {
var FILE = "gipy.json";
// Load settings
var settings = Object.assign(
{
keep_gps_alive: false,
max_speed: 35,
},
require("Storage").readJSON(FILE, true) || {}
);
function writeSettings() {
require("Storage").writeJSON(FILE, settings);
}
// Show the menu
E.showMenu({
"": { title: "Gipy" },
"< Back": () => back(),
"keep gps alive": {
value: !!settings.keep_gps_alive, // !! converts undefined to false
format: (v) => (v ? "Yes" : "No"),
onchange: (v) => {
settings.keep_gps_alive = v;
writeSettings();
},
},
"max speed": {
value: 35 | settings.max_speed, // 0| converts undefined to 0
min: 0,
max: 130,
onchange: (v) => {
settings.max_speed = v;
writeSettings();
},
},
});
});

View File

@ -5,7 +5,7 @@
"description": "An application that displays information about altitude, lat/lon, satellites and time", "description": "An application that displays information about altitude, lat/lon, satellites and time",
"icon": "gps-info.png", "icon": "gps-info.png",
"type": "app", "type": "app",
"tags": "gps", "tags": "gps,outdoors",
"supports": ["BANGLEJS","BANGLEJS2"], "supports": ["BANGLEJS","BANGLEJS2"],
"storage": [ "storage": [
{"name":"gpsinfo.app.js","url":"gps-info.js"}, {"name":"gpsinfo.app.js","url":"gps-info.js"},

View File

@ -8,3 +8,8 @@
Fix widget adding listeners more than once Fix widget adding listeners more than once
0.07: Show checkered flag for target markers 0.07: Show checkered flag for target markers
Single waypoints are now shown in the compass view Single waypoints are now shown in the compass view
0.08: Better handle state in widget
Slightly faster drawing by doing some caching
Reconstruct battery voltage by using calibrated batFullVoltage
Averaging for smoothing compass headings
Save state if route or waypoint has been chosen

View File

@ -1,48 +1,69 @@
{ //run in own scope for fast switch
const STORAGE = require("Storage"); const STORAGE = require("Storage");
const showWidgets = true; const BAT_FULL = require("Storage").readJSON("setting.json").batFullVoltage || 0.3144;
let numberOfSlices=4;
let init = function(){
global.screen = 1;
global.drawTimeout = undefined;
global.lastDrawnScreen = 0;
global.firstDraw = true;
global.slices = [];
global.maxScreens = 1;
global.scheduleDraw = false;
if (showWidgets){
Bangle.loadWidgets(); Bangle.loadWidgets();
} WIDGETS.gpstrek.start(false);
if (!WIDGETS.gpstrek.getState().numberOfSlices) WIDGETS.gpstrek.getState().numberOfSlices = 3;
};
let state = WIDGETS.gpstrek.getState(); let cleanup = function(){
WIDGETS.gpstrek.start(false); if (global.drawTimeout) clearTimeout(global.drawTimeout);
delete global.screen;
delete global.drawTimeout;
delete global.lastDrawnScreen;
delete global.firstDraw;
delete global.slices;
delete global.maxScreens;
};
function parseNumber(toParse){ init();
scheduleDraw = true;
let parseNumber = function(toParse){
if (toParse.includes(".")) return parseFloat(toParse); if (toParse.includes(".")) return parseFloat(toParse);
return parseFloat("" + toParse + ".0"); return parseFloat("" + toParse + ".0");
} };
function parseWaypoint(filename, offset, result){ let parseWaypoint = function(filename, offset, result){
result.lat = parseNumber(STORAGE.read(filename, offset, 11)); result.lat = parseNumber(STORAGE.read(filename, offset, 11));
result.lon = parseNumber(STORAGE.read(filename, offset += 11, 12)); result.lon = parseNumber(STORAGE.read(filename, offset += 11, 12));
return offset + 12; return offset + 12;
} };
function parseWaypointWithElevation(filename, offset, result){ let parseWaypointWithElevation = function (filename, offset, result){
offset = parseWaypoint(filename, offset, result); offset = parseWaypoint(filename, offset, result);
result.alt = parseNumber(STORAGE.read(filename, offset, 6)); result.alt = parseNumber(STORAGE.read(filename, offset, 6));
return offset + 6; return offset + 6;
} };
function parseWaypointWithName(filename, offset, result){ let parseWaypointWithName = function(filename, offset, result){
offset = parseWaypoint(filename, offset, result); offset = parseWaypoint(filename, offset, result);
return parseName(filename, offset, result); return parseName(filename, offset, result);
} };
function parseName(filename, offset, result){ let parseName = function(filename, offset, result){
let nameLength = STORAGE.read(filename, offset, 2) - 0; let nameLength = STORAGE.read(filename, offset, 2) - 0;
result.name = STORAGE.read(filename, offset += 2, nameLength); result.name = STORAGE.read(filename, offset += 2, nameLength);
return offset + nameLength; return offset + nameLength;
} };
function parseWaypointWithElevationAndName(filename, offset, result){ let parseWaypointWithElevationAndName = function(filename, offset, result){
offset = parseWaypointWithElevation(filename, offset, result); offset = parseWaypointWithElevation(filename, offset, result);
return parseName(filename, offset, result); return parseName(filename, offset, result);
} };
function getEntry(filename, offset, result){ let getEntry = function(filename, offset, result){
result.fileOffset = offset; result.fileOffset = offset;
let type = STORAGE.read(filename, offset++, 1); let type = STORAGE.read(filename, offset++, 1);
if (type == "") return -1; if (type == "") return -1;
@ -68,12 +89,12 @@ function getEntry(filename, offset, result){
result.fileLength = offset - result.fileOffset; result.fileLength = offset - result.fileOffset;
//print(result); //print(result);
return offset; return offset;
} };
const labels = ["N","NE","E","SE","S","SW","W","NW"]; const labels = ["N","NE","E","SE","S","SW","W","NW"];
const loc = require("locale"); const loc = require("locale");
function matchFontSize(graphics, text, height, width){ let matchFontSize = function(graphics, text, height, width){
graphics.setFontVector(height); graphics.setFontVector(height);
let metrics; let metrics;
let size = 1; let size = 1;
@ -81,13 +102,19 @@ function matchFontSize(graphics, text, height, width){
size -= 0.05; size -= 0.05;
graphics.setFont("Vector",Math.floor(height*size)); graphics.setFont("Vector",Math.floor(height*size));
} }
} };
function getDoubleLineSlice(title1,title2,provider1,provider2,refreshTime){ let getDoubleLineSlice = function(title1,title2,provider1,provider2,refreshTime){
let lastDrawn = Date.now() - Math.random()*refreshTime; let lastDrawn = Date.now() - Math.random()*refreshTime;
let lastValue1 = 0;
let lastValue2 = 0;
return { return {
refresh: function (){ refresh: function (){
return Date.now() - lastDrawn > (Bangle.isLocked()?(refreshTime?refreshTime:5000):(refreshTime?refreshTime*2:10000)); let bigChange1 = (Math.abs(lastValue1 - provider1()) > 1);
let bigChange2 = (Math.abs(lastValue2 - provider2()) > 1);
let refresh = (Bangle.isLocked()?(refreshTime?refreshTime*5:10000):(refreshTime?refreshTime*2:1000));
let old = (Date.now() - lastDrawn) > refresh;
return (bigChange1 || bigChange2) && old;
}, },
draw: function (graphics, x, y, height, width){ draw: function (graphics, x, y, height, width){
lastDrawn = Date.now(); lastDrawn = Date.now();
@ -95,29 +122,29 @@ function getDoubleLineSlice(title1,title2,provider1,provider2,refreshTime){
if (typeof title2 == "function") title2 = title2(); if (typeof title2 == "function") title2 = title2();
graphics.clearRect(x,y,x+width,y+height); graphics.clearRect(x,y,x+width,y+height);
let value = provider1(); lastValue1 = provider1();
matchFontSize(graphics, title1 + value, Math.floor(height*0.5), width); matchFontSize(graphics, title1 + lastValue1, Math.floor(height*0.5), width);
graphics.setFontAlign(-1,-1); graphics.setFontAlign(-1,-1);
graphics.drawString(title1, x+2, y); graphics.drawString(title1, x+2, y);
graphics.setFontAlign(1,-1); graphics.setFontAlign(1,-1);
graphics.drawString(value, x+width, y); graphics.drawString(lastValue1, x+width, y);
value = provider2(); lastValue2 = provider2();
matchFontSize(graphics, title2 + value, Math.floor(height*0.5), width); matchFontSize(graphics, title2 + lastValue2, Math.floor(height*0.5), width);
graphics.setFontAlign(-1,-1); graphics.setFontAlign(-1,-1);
graphics.drawString(title2, x+2, y+(height*0.5)); graphics.drawString(title2, x+2, y+(height*0.5));
graphics.setFontAlign(1,-1); graphics.setFontAlign(1,-1);
graphics.drawString(value, x+width, y+(height*0.5)); graphics.drawString(lastValue2, x+width, y+(height*0.5));
} }
}; };
} };
function getTargetSlice(targetDataSource){ let getTargetSlice = function(targetDataSource){
let nameIndex = 0; let nameIndex = 0;
let lastDrawn = Date.now() - Math.random()*3000; let lastDrawn = Date.now() - Math.random()*3000;
return { return {
refresh: function (){ refresh: function (){
return Date.now() - lastDrawn > (Bangle.isLocked()?10000:3000); return Date.now() - lastDrawn > (Bangle.isLocked()?3000:10000);
}, },
draw: function (graphics, x, y, height, width){ draw: function (graphics, x, y, height, width){
lastDrawn = Date.now(); lastDrawn = Date.now();
@ -174,9 +201,9 @@ function getTargetSlice(targetDataSource){
} }
} }
}; };
} };
function drawCompass(graphics, x, y, height, width, increment, start){ let drawCompass = function(graphics, x, y, height, width, increment, start){
graphics.setFont12x20(); graphics.setFont12x20();
graphics.setFontAlign(0,-1); graphics.setFontAlign(0,-1);
graphics.setColor(graphics.theme.fg); graphics.setColor(graphics.theme.fg);
@ -197,14 +224,19 @@ function drawCompass(graphics, x, y, height, width, increment, start){
xpos+=increment*15; xpos+=increment*15;
if (xpos > width + 20) break; if (xpos > width + 20) break;
} }
} };
function getCompassSlice(compassDataSource){ let getCompassSlice = function(compassDataSource){
let lastDrawn = Date.now() - Math.random()*2000; let lastDrawn = Date.now() - Math.random()*2000;
let lastDrawnValue = 0;
const buffers = 4; const buffers = 4;
let buf = []; let buf = [];
return { return {
refresh : function (){return Bangle.isLocked()?(Date.now() - lastDrawn > 2000):true;}, refresh : function (){
let bigChange = (Math.abs(lastDrawnValue - compassDataSource.getCourse()) > 2);
let old = (Bangle.isLocked()?(Date.now() - lastDrawn > 2000):true);
return bigChange && old;
},
draw: function (graphics, x,y,height,width){ draw: function (graphics, x,y,height,width){
lastDrawn = Date.now(); lastDrawn = Date.now();
const max = 180; const max = 180;
@ -212,12 +244,14 @@ function getCompassSlice(compassDataSource){
graphics.clearRect(x,y,x+width,y+height); graphics.clearRect(x,y,x+width,y+height);
var start = compassDataSource.getCourse() - 90; lastDrawnValue = compassDataSource.getCourse();
if (isNaN(compassDataSource.getCourse())) start = -90;
var start = lastDrawnValue - 90;
if (isNaN(lastDrawnValue)) start = -90;
if (start<0) start+=360; if (start<0) start+=360;
start = start % 360; start = start % 360;
if (state.acc && compassDataSource.getCourseType() == "MAG"){ if (WIDGETS.gpstrek.getState().acc && compassDataSource.getCourseType() == "MAG"){
drawCompass(graphics,0,y+width*0.05,height-width*0.05,width,increment,start); drawCompass(graphics,0,y+width*0.05,height-width*0.05,width,increment,start);
} else { } else {
drawCompass(graphics,0,y,height,width,increment,start); drawCompass(graphics,0,y,height,width,increment,start);
@ -226,7 +260,8 @@ function getCompassSlice(compassDataSource){
if (compassDataSource.getPoints){ if (compassDataSource.getPoints){
for (let p of compassDataSource.getPoints()){ for (let p of compassDataSource.getPoints()){
var bpos = p.bearing - compassDataSource.getCourse(); g.reset();
var bpos = p.bearing - lastDrawnValue;
if (bpos>180) bpos -=360; if (bpos>180) bpos -=360;
if (bpos<-180) bpos +=360; if (bpos<-180) bpos +=360;
bpos+=120; bpos+=120;
@ -251,6 +286,7 @@ function getCompassSlice(compassDataSource){
} }
if (compassDataSource.getMarkers){ if (compassDataSource.getMarkers){
for (let m of compassDataSource.getMarkers()){ for (let m of compassDataSource.getMarkers()){
g.reset();
g.setColor(m.fillcolor); g.setColor(m.fillcolor);
let mpos = m.xpos * width; let mpos = m.xpos * width;
if (m.xpos < 0.05) mpos = Math.floor(width*0.05); if (m.xpos < 0.05) mpos = Math.floor(width*0.05);
@ -263,9 +299,9 @@ function getCompassSlice(compassDataSource){
graphics.setColor(g.theme.fg); graphics.setColor(g.theme.fg);
graphics.fillRect(x,y,Math.floor(width*0.05),y+height); graphics.fillRect(x,y,Math.floor(width*0.05),y+height);
graphics.fillRect(Math.ceil(width*0.95),y,width,y+height); graphics.fillRect(Math.ceil(width*0.95),y,width,y+height);
if (state.acc && compassDataSource.getCourseType() == "MAG") { if (WIDGETS.gpstrek.getState().acc && compassDataSource.getCourseType() == "MAG") {
let xh = E.clip(width*0.5-height/2+(((state.acc.x+1)/2)*height),width*0.5 - height/2, width*0.5 + height/2); let xh = E.clip(width*0.5-height/2+(((WIDGETS.gpstrek.getState().acc.x+1)/2)*height),width*0.5 - height/2, width*0.5 + height/2);
let yh = E.clip(y+(((state.acc.y+1)/2)*height),y,y+height); let yh = E.clip(y+(((WIDGETS.gpstrek.getState().acc.y+1)/2)*height),y,y+height);
graphics.fillRect(width*0.5 - height/2, y, width*0.5 + height/2, y + Math.floor(width*0.05)); graphics.fillRect(width*0.5 - height/2, y, width*0.5 + height/2, y + Math.floor(width*0.05));
@ -287,44 +323,48 @@ function getCompassSlice(compassDataSource){
graphics.drawRect(Math.floor(width*0.05),y,Math.ceil(width*0.95),y+height); graphics.drawRect(Math.floor(width*0.05),y,Math.ceil(width*0.95),y+height);
} }
}; };
} };
function radians(a) { let radians = function(a) {
return a*Math.PI/180; return a*Math.PI/180;
} };
function degrees(a) { let degrees = function(a) {
var d = a*180/Math.PI; let d = a*180/Math.PI;
return (d+360)%360; return (d+360)%360;
} };
function bearing(a,b){ let bearing = function(a,b){
if (!a || !b || !a.lon || !a.lat || !b.lon || !b.lat) return Infinity; if (!a || !b || !a.lon || !a.lat || !b.lon || !b.lat) return Infinity;
var delta = radians(b.lon-a.lon); let delta = radians(b.lon-a.lon);
var alat = radians(a.lat); let alat = radians(a.lat);
var blat = radians(b.lat); let blat = radians(b.lat);
var y = Math.sin(delta) * Math.cos(blat); let y = Math.sin(delta) * Math.cos(blat);
var x = Math.cos(alat)*Math.sin(blat) - let x = Math.cos(alat)*Math.sin(blat) -
Math.sin(alat)*Math.cos(blat)*Math.cos(delta); Math.sin(alat)*Math.cos(blat)*Math.cos(delta);
return Math.round(degrees(Math.atan2(y, x))); return Math.round(degrees(Math.atan2(y, x)));
} };
function distance(a,b){ let distance = function(a,b){
if (!a || !b || !a.lon || !a.lat || !b.lon || !b.lat) return Infinity; if (!a || !b || !a.lon || !a.lat || !b.lon || !b.lat) return Infinity;
var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2)); let x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2));
var y = radians(b.lat-a.lat); let y = radians(b.lat-a.lat);
return Math.round(Math.sqrt(x*x + y*y) * 6371000); return Math.round(Math.sqrt(x*x + y*y) * 6371000);
} };
function triangle (x, y, width, height){ let getAveragedCompass = function(){
return Math.round(WIDGETS.gpstrek.getState().avgComp);
};
let triangle = function(x, y, width, height){
return [ return [
Math.round(x),Math.round(y), Math.round(x),Math.round(y),
Math.round(x+width * 0.5), Math.round(y+height), Math.round(x+width * 0.5), Math.round(y+height),
Math.round(x-width * 0.5), Math.round(y+height) Math.round(x-width * 0.5), Math.round(y+height)
]; ];
} };
function onSwipe(dir){ let onSwipe = function(dir){
if (dir < 0) { if (dir < 0) {
nextScreen(); nextScreen();
} else if (dir > 0) { } else if (dir > 0) {
@ -332,9 +372,9 @@ function onSwipe(dir){
} else { } else {
nextScreen(); nextScreen();
} }
} };
function setButtons(){ let setButtons = function(){
let options = { let options = {
mode: "custom", mode: "custom",
swipe: onSwipe, swipe: onSwipe,
@ -342,9 +382,9 @@ function setButtons(){
touch: nextScreen touch: nextScreen
}; };
Bangle.setUI(options); Bangle.setUI(options);
} };
function getApproxFileSize(name){ let getApproxFileSize = function(name){
let currentStart = STORAGE.getStats().totalBytes; let currentStart = STORAGE.getStats().totalBytes;
let currentSize = 0; let currentSize = 0;
for (let i = currentStart; i > 500; i/=2){ for (let i = currentStart; i > 500; i/=2){
@ -358,9 +398,9 @@ function getApproxFileSize(name){
currentSize += currentDiff; currentSize += currentDiff;
} }
return currentSize; return currentSize;
} };
function parseRouteData(filename, progressMonitor){ let parseRouteData = function(filename, progressMonitor){
let routeInfo = {}; let routeInfo = {};
routeInfo.filename = filename; routeInfo.filename = filename;
@ -406,40 +446,40 @@ function parseRouteData(filename, progressMonitor){
set(routeInfo, 0); set(routeInfo, 0);
return routeInfo; return routeInfo;
} };
function hasPrev(route){ let hasPrev = function(route){
if (route.mirror) return route.index < (route.count - 1); if (route.mirror) return route.index < (route.count - 1);
return route.index > 0; return route.index > 0;
} };
function hasNext(route){ let hasNext = function(route){
if (route.mirror) return route.index > 0; if (route.mirror) return route.index > 0;
return route.index < (route.count - 1); return route.index < (route.count - 1);
} };
function next(route){ let next = function(route){
if (!hasNext(route)) return; if (!hasNext(route)) return;
if (route.mirror) set(route, --route.index); if (route.mirror) set(route, --route.index);
if (!route.mirror) set(route, ++route.index); if (!route.mirror) set(route, ++route.index);
} };
function set(route, index){ let set = function(route, index){
route.currentWaypoint = {}; route.currentWaypoint = {};
route.index = index; route.index = index;
getEntry(route.filename, route.refs[index], route.currentWaypoint); getEntry(route.filename, route.refs[index], route.currentWaypoint);
} };
function prev(route){ let prev = function(route){
if (!hasPrev(route)) return; if (!hasPrev(route)) return;
if (route.mirror) set(route, ++route.index); if (route.mirror) set(route, ++route.index);
if (!route.mirror) set(route, --route.index); if (!route.mirror) set(route, --route.index);
} };
let lastMirror; let lastMirror;
let cachedLast; let cachedLast;
function getLast(route){ let getLast = function(route){
let wp = {}; let wp = {};
if (lastMirror != route.mirror){ if (lastMirror != route.mirror){
if (route.mirror) getEntry(route.filename, route.refs[0], wp); if (route.mirror) getEntry(route.filename, route.refs[0], wp);
@ -448,14 +488,14 @@ function getLast(route){
cachedLast = wp; cachedLast = wp;
} }
return cachedLast; return cachedLast;
} };
function removeMenu(){ let removeMenu = function(){
E.showMenu(); E.showMenu();
switchNav(); switchNav();
} };
function showProgress(progress, title, max){ let showProgress = function(progress, title, max){
//print("Progress",progress,max) //print("Progress",progress,max)
let message = title? title: "Loading"; let message = title? title: "Loading";
if (max){ if (max){
@ -466,17 +506,17 @@ function showProgress(progress, title, max){
for (let i = dots; i < 4; i++) message += " "; for (let i = dots; i < 4; i++) message += " ";
} }
E.showMessage(message); E.showMessage(message);
} };
function handleLoading(c){ let handleLoading = function(c){
E.showMenu(); E.showMenu();
state.route = parseRouteData(c, showProgress); WIDGETS.gpstrek.getState().route = parseRouteData(c, showProgress);
state.waypoint = null; WIDGETS.gpstrek.getState().waypoint = null;
WIDGETS.gpstrek.getState().route.mirror = false;
removeMenu(); removeMenu();
state.route.mirror = false; };
}
function showRouteSelector (){ let showRouteSelector = function(){
var menu = { var menu = {
"" : { "" : {
back : showRouteMenu, back : showRouteMenu,
@ -488,9 +528,9 @@ function showRouteSelector (){
}); });
E.showMenu(menu); E.showMenu(menu);
} };
function showRouteMenu(){ let showRouteMenu = function(){
var menu = { var menu = {
"" : { "" : {
"title" : "Route", "title" : "Route",
@ -499,48 +539,48 @@ function showRouteMenu(){
"Select file" : showRouteSelector "Select file" : showRouteSelector
}; };
if (state.route){ if (WIDGETS.gpstrek.getState().route){
menu.Mirror = { menu.Mirror = {
value: state && state.route && !!state.route.mirror || false, value: WIDGETS.gpstrek.getState() && WIDGETS.gpstrek.getState().route && !!WIDGETS.gpstrek.getState().route.mirror || false,
onchange: v=>{ onchange: v=>{
state.route.mirror = v; WIDGETS.gpstrek.getState().route.mirror = v;
} }
}; };
menu['Select closest waypoint'] = function () { menu['Select closest waypoint'] = function () {
if (state.currentPos && state.currentPos.lat){ if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lat){
setClosestWaypoint(state.route, null, showProgress); removeMenu(); setClosestWaypoint(WIDGETS.gpstrek.getState().route, null, showProgress); removeMenu();
} else { } else {
E.showAlert("No position").then(()=>{E.showMenu(menu);}); E.showAlert("No position").then(()=>{E.showMenu(menu);});
} }
}; };
menu['Select closest waypoint (not visited)'] = function () { menu['Select closest waypoint (not visited)'] = function () {
if (state.currentPos && state.currentPos.lat){ if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lat){
setClosestWaypoint(state.route, state.route.index, showProgress); removeMenu(); setClosestWaypoint(WIDGETS.gpstrek.getState().route, WIDGETS.gpstrek.getState().route.index, showProgress); removeMenu();
} else { } else {
E.showAlert("No position").then(()=>{E.showMenu(menu);}); E.showAlert("No position").then(()=>{E.showMenu(menu);});
} }
}; };
menu['Select waypoint'] = { menu['Select waypoint'] = {
value : state.route.index, value : WIDGETS.gpstrek.getState().route.index,
min:1,max:state.route.count,step:1, min:1,max:WIDGETS.gpstrek.getState().route.count,step:1,
onchange : v => { set(state.route, v-1); } onchange : v => { set(WIDGETS.gpstrek.getState().route, v-1); }
}; };
menu['Select waypoint as current position'] = function (){ menu['Select waypoint as current position'] = function (){
state.currentPos.lat = state.route.currentWaypoint.lat; WIDGETS.gpstrek.getState().currentPos.lat = WIDGETS.gpstrek.getState().route.currentWaypoint.lat;
state.currentPos.lon = state.route.currentWaypoint.lon; WIDGETS.gpstrek.getState().currentPos.lon = WIDGETS.gpstrek.getState().route.currentWaypoint.lon;
state.currentPos.alt = state.route.currentWaypoint.alt; WIDGETS.gpstrek.getState().currentPos.alt = WIDGETS.gpstrek.getState().route.currentWaypoint.alt;
removeMenu(); removeMenu();
}; };
} }
if (state.route && hasPrev(state.route)) if (WIDGETS.gpstrek.getState().route && hasPrev(WIDGETS.gpstrek.getState().route))
menu['Previous waypoint'] = function() { prev(state.route); removeMenu(); }; menu['Previous waypoint'] = function() { prev(WIDGETS.gpstrek.getState().route); removeMenu(); };
if (state.route && hasNext(state.route)) if (WIDGETS.gpstrek.getState().route && hasNext(WIDGETS.gpstrek.getState().route))
menu['Next waypoint'] = function() { next(state.route); removeMenu(); }; menu['Next waypoint'] = function() { next(WIDGETS.gpstrek.getState().route); removeMenu(); };
E.showMenu(menu); E.showMenu(menu);
} };
function showWaypointSelector(){ let showWaypointSelector = function(){
let waypoints = require("waypoints").load(); let waypoints = require("waypoints").load();
var menu = { var menu = {
"" : { "" : {
@ -550,41 +590,41 @@ function showWaypointSelector(){
waypoints.forEach((wp,c)=>{ waypoints.forEach((wp,c)=>{
menu[waypoints[c].name] = function (){ menu[waypoints[c].name] = function (){
state.waypoint = waypoints[c]; WIDGETS.gpstrek.getState().waypoint = waypoints[c];
state.waypointIndex = c; WIDGETS.gpstrek.getState().waypointIndex = c;
state.route = null; WIDGETS.gpstrek.getState().route = null;
removeMenu(); removeMenu();
}; };
}); });
E.showMenu(menu); E.showMenu(menu);
} };
function showCalibrationMenu(){ let showCalibrationMenu = function(){
let menu = { let menu = {
"" : { "" : {
"title" : "Calibration", "title" : "Calibration",
back : showMenu, back : showMenu,
}, },
"Barometer (GPS)" : ()=>{ "Barometer (GPS)" : ()=>{
if (!state.currentPos || isNaN(state.currentPos.alt)){ if (!WIDGETS.gpstrek.getState().currentPos || isNaN(WIDGETS.gpstrek.getState().currentPos.alt)){
E.showAlert("No GPS altitude").then(()=>{E.showMenu(menu);}); E.showAlert("No GPS altitude").then(()=>{E.showMenu(menu);});
} else { } else {
state.calibAltDiff = state.altitude - state.currentPos.alt; WIDGETS.gpstrek.getState().calibAltDiff = WIDGETS.gpstrek.getState().altitude - WIDGETS.gpstrek.getState().currentPos.alt;
E.showAlert("Calibrated Altitude Difference: " + state.calibAltDiff.toFixed(0)).then(()=>{removeMenu();}); E.showAlert("Calibrated Altitude Difference: " + WIDGETS.gpstrek.getState().calibAltDiff.toFixed(0)).then(()=>{removeMenu();});
} }
}, },
"Barometer (Manual)" : { "Barometer (Manual)" : {
value : Math.round(state.currentPos && (state.currentPos.alt != undefined && !isNaN(state.currentPos.alt)) ? state.currentPos.alt: state.altitude), value : Math.round(WIDGETS.gpstrek.getState().currentPos && (WIDGETS.gpstrek.getState().currentPos.alt != undefined && !isNaN(WIDGETS.gpstrek.getState().currentPos.alt)) ? WIDGETS.gpstrek.getState().currentPos.alt: WIDGETS.gpstrek.getState().altitude),
min:-2000,max: 10000,step:1, min:-2000,max: 10000,step:1,
onchange : v => { state.calibAltDiff = state.altitude - v; } onchange : v => { WIDGETS.gpstrek.getState().calibAltDiff = WIDGETS.gpstrek.getState().altitude - v; }
}, },
"Reset Compass" : ()=>{ Bangle.resetCompass(); removeMenu();}, "Reset Compass" : ()=>{ Bangle.resetCompass(); removeMenu();},
}; };
E.showMenu(menu); E.showMenu(menu);
} };
function showWaypointMenu(){ let showWaypointMenu = function(){
let menu = { let menu = {
"" : { "" : {
"title" : "Waypoint", "title" : "Waypoint",
@ -593,21 +633,21 @@ function showWaypointMenu(){
"Select waypoint" : showWaypointSelector, "Select waypoint" : showWaypointSelector,
}; };
E.showMenu(menu); E.showMenu(menu);
} };
function showBackgroundMenu(){ let showBackgroundMenu = function(){
let menu = { let menu = {
"" : { "" : {
"title" : "Background", "title" : "Background",
back : showMenu, back : showMenu,
}, },
"Start" : ()=>{ E.showPrompt("Start?").then((v)=>{ if (v) {WIDGETS.gpstrek.start(true); removeMenu();} else {showMenu();}}).catch(()=>{E.showMenu(mainmenu);});}, "Start" : ()=>{ E.showPrompt("Start?").then((v)=>{ if (v) {WIDGETS.gpstrek.start(true); removeMenu();} else {showMenu();}}).catch(()=>{showMenu();});},
"Stop" : ()=>{ E.showPrompt("Stop?").then((v)=>{ if (v) {WIDGETS.gpstrek.stop(true); removeMenu();} else {showMenu();}}).catch(()=>{E.showMenu(mainmenu);});}, "Stop" : ()=>{ E.showPrompt("Stop?").then((v)=>{ if (v) {WIDGETS.gpstrek.stop(true); removeMenu();} else {showMenu();}}).catch(()=>{showMenu();});},
}; };
E.showMenu(menu); E.showMenu(menu);
} };
function showMenu(){ let showMenu = function(){
var mainmenu = { var mainmenu = {
"" : { "" : {
"title" : "Main", "title" : "Main",
@ -617,50 +657,55 @@ function showMenu(){
"Waypoint" : showWaypointMenu, "Waypoint" : showWaypointMenu,
"Background" : showBackgroundMenu, "Background" : showBackgroundMenu,
"Calibration": showCalibrationMenu, "Calibration": showCalibrationMenu,
"Reset" : ()=>{ E.showPrompt("Do Reset?").then((v)=>{ if (v) {WIDGETS.gpstrek.resetState(); removeMenu();} else {E.showMenu(mainmenu);}});}, "Reset" : ()=>{ E.showPrompt("Do Reset?").then((v)=>{ if (v) {WIDGETS.gpstrek.resetState(); removeMenu();} else {E.showMenu(mainmenu);}}).catch(()=>{E.showMenu(mainmenu);});},
"Info rows" : { "Info rows" : {
value : numberOfSlices, value : WIDGETS.gpstrek.getState().numberOfSlices,
min:1,max:6,step:1, min:1,max:6,step:1,
onchange : v => { setNumberOfSlices(v); } onchange : v => { WIDGETS.gpstrek.getState().numberOfSlices = v; }
}, },
}; };
E.showMenu(mainmenu); E.showMenu(mainmenu);
} };
let scheduleDraw = true;
function switchMenu(){ let switchMenu = function(){
screen = 0; stopDrawing();
scheduleDraw = false; showMenu();
showMenu(); };
}
function drawInTimeout(){ let stopDrawing = function(){
setTimeout(()=>{ if (drawTimeout) clearTimeout(drawTimeout);
scheduleDraw = false;
};
let drawInTimeout = function(){
if (global.drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(()=>{
drawTimeout = undefined;
draw(); draw();
if (scheduleDraw) },50);
setTimeout(drawInTimeout, 0); };
},0);
}
function switchNav(){ let switchNav = function(){
if (!screen) screen = 1; if (!screen) screen = 1;
setButtons(); setButtons();
scheduleDraw = true; scheduleDraw = true;
firstDraw = true;
drawInTimeout(); drawInTimeout();
} };
function nextScreen(){ let nextScreen = function(){
screen++; screen++;
if (screen > maxScreens){ if (screen > maxScreens){
screen = 1; screen = 1;
} }
} drawInTimeout();
};
function setClosestWaypoint(route, startindex, progress){ let setClosestWaypoint = function(route, startindex, progress){
if (startindex >= state.route.count) startindex = state.route.count - 1; if (startindex >= WIDGETS.gpstrek.getState().route.count) startindex = WIDGETS.gpstrek.getState().route.count - 1;
if (!state.currentPos.lat){ if (!WIDGETS.gpstrek.getState().currentPos.lat){
set(route, startindex); set(route, startindex);
return; return;
} }
@ -670,7 +715,7 @@ function setClosestWaypoint(route, startindex, progress){
if (progress && (i % 5 == 0)) progress(i-(startindex?startindex:0), "Searching", route.count); if (progress && (i % 5 == 0)) progress(i-(startindex?startindex:0), "Searching", route.count);
let wp = {}; let wp = {};
getEntry(route.filename, route.refs[i], wp); getEntry(route.filename, route.refs[i], wp);
let curDist = distance(state.currentPos, wp); let curDist = distance(WIDGETS.gpstrek.getState().currentPos, wp);
if (curDist < minDist){ if (curDist < minDist){
minDist = curDist; minDist = curDist;
minIndex = i; minIndex = i;
@ -679,30 +724,28 @@ function setClosestWaypoint(route, startindex, progress){
} }
} }
set(route, minIndex); set(route, minIndex);
} };
let screen = 1;
const finishIcon = atob("CggB//meZmeZ+Z5n/w=="); const finishIcon = atob("CggB//meZmeZ+Z5n/w==");
const compassSliceData = { const compassSliceData = {
getCourseType: function(){ getCourseType: function(){
return (state.currentPos && state.currentPos.course) ? "GPS" : "MAG"; return (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.course) ? "GPS" : "MAG";
}, },
getCourse: function (){ getCourse: function (){
if(compassSliceData.getCourseType() == "GPS") return state.currentPos.course; if(compassSliceData.getCourseType() == "GPS") return WIDGETS.gpstrek.getState().currentPos.course;
return state.compassHeading?state.compassHeading:undefined; return getAveragedCompass();
}, },
getPoints: function (){ getPoints: function (){
let points = []; let points = [];
if (state.currentPos && state.currentPos.lon && state.route && state.route.currentWaypoint){ if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lon && WIDGETS.gpstrek.getState().route && WIDGETS.gpstrek.getState().route.currentWaypoint){
points.push({bearing:bearing(state.currentPos, state.route.currentWaypoint), color:"#0f0"}); points.push({bearing:bearing(WIDGETS.gpstrek.getState().currentPos, WIDGETS.gpstrek.getState().route.currentWaypoint), color:"#0f0"});
} }
if (state.currentPos && state.currentPos.lon && state.route){ if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lon && WIDGETS.gpstrek.getState().route){
points.push({bearing:bearing(state.currentPos, getLast(state.route)), icon: finishIcon}); points.push({bearing:bearing(WIDGETS.gpstrek.getState().currentPos, getLast(WIDGETS.gpstrek.getState().route)), icon: finishIcon});
} }
if (state.currentPos && state.currentPos.lon && state.waypoint){ if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lon && WIDGETS.gpstrek.getState().waypoint){
points.push({bearing:bearing(state.currentPos, state.waypoint), icon: finishIcon}); points.push({bearing:bearing(WIDGETS.gpstrek.getState().currentPos, WIDGETS.gpstrek.getState().waypoint), icon: finishIcon});
} }
return points; return points;
}, },
@ -714,79 +757,74 @@ const compassSliceData = {
const waypointData = { const waypointData = {
icon: atob("EBCBAAAAAAAAAAAAcIB+zg/uAe4AwACAAAAAAAAAAAAAAAAA"), icon: atob("EBCBAAAAAAAAAAAAcIB+zg/uAe4AwACAAAAAAAAAAAAAAAAA"),
getProgress: function() { getProgress: function() {
return (state.route.index + 1) + "/" + state.route.count; return (WIDGETS.gpstrek.getState().route.index + 1) + "/" + WIDGETS.gpstrek.getState().route.count;
}, },
getTarget: function (){ getTarget: function (){
if (distance(state.currentPos,state.route.currentWaypoint) < 30 && hasNext(state.route)){ if (distance(WIDGETS.gpstrek.getState().currentPos,WIDGETS.gpstrek.getState().route.currentWaypoint) < 30 && hasNext(WIDGETS.gpstrek.getState().route)){
next(state.route); next(WIDGETS.gpstrek.getState().route);
Bangle.buzz(1000); Bangle.buzz(1000);
} }
return state.route.currentWaypoint; return WIDGETS.gpstrek.getState().route.currentWaypoint;
}, },
getStart: function (){ getStart: function (){
return state.currentPos; return WIDGETS.gpstrek.getState().currentPos;
} }
}; };
const finishData = { const finishData = {
icon: atob("EBABAAA/4DmgJmAmYDmgOaAmYD/gMAAwADAAMAAwAAAAAAA="), icon: atob("EBABAAA/4DmgJmAmYDmgOaAmYD/gMAAwADAAMAAwAAAAAAA="),
getTarget: function (){ getTarget: function (){
if (state.route) return getLast(state.route); if (WIDGETS.gpstrek.getState().route) return getLast(WIDGETS.gpstrek.getState().route);
if (state.waypoint) return state.waypoint; if (WIDGETS.gpstrek.getState().waypoint) return WIDGETS.gpstrek.getState().waypoint;
}, },
getStart: function (){ getStart: function (){
return state.currentPos; return WIDGETS.gpstrek.getState().currentPos;
} }
}; };
let sliceHeight; let getSliceHeight = function(number){
function setNumberOfSlices(number){ return Math.floor(Bangle.appRect.h/WIDGETS.gpstrek.getState().numberOfSlices);
numberOfSlices = number; };
sliceHeight = Math.floor((g.getHeight()-(showWidgets?24:0))/numberOfSlices);
}
let slices = [];
let maxScreens = 1;
setNumberOfSlices(3);
let compassSlice = getCompassSlice(compassSliceData); let compassSlice = getCompassSlice(compassSliceData);
let waypointSlice = getTargetSlice(waypointData); let waypointSlice = getTargetSlice(waypointData);
let finishSlice = getTargetSlice(finishData); let finishSlice = getTargetSlice(finishData);
let eleSlice = getDoubleLineSlice("Up","Down",()=>{ let eleSlice = getDoubleLineSlice("Up","Down",()=>{
return loc.distance(state.up,3) + "/" + (state.route ? loc.distance(state.route.up,3):"---"); return loc.distance(WIDGETS.gpstrek.getState().up,3) + "/" + (WIDGETS.gpstrek.getState().route ? loc.distance(WIDGETS.gpstrek.getState().route.up,3):"---");
},()=>{ },()=>{
return loc.distance(state.down,3) + "/" + (state.route ? loc.distance(state.route.down,3): "---"); return loc.distance(WIDGETS.gpstrek.getState().down,3) + "/" + (WIDGETS.gpstrek.getState().route ? loc.distance(WIDGETS.gpstrek.getState().route.down,3): "---");
}); });
let statusSlice = getDoubleLineSlice("Speed","Alt",()=>{ let statusSlice = getDoubleLineSlice("Speed","Alt",()=>{
let speed = 0; let speed = 0;
if (state.currentPos && state.currentPos.speed) speed = state.currentPos.speed; if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.speed) speed = WIDGETS.gpstrek.getState().currentPos.speed;
return loc.speed(speed,2); return loc.speed(speed,2);
},()=>{ },()=>{
let alt = Infinity; let alt = Infinity;
if (!isNaN(state.altitude)){ if (!isNaN(WIDGETS.gpstrek.getState().altitude)){
alt = isNaN(state.calibAltDiff) ? state.altitude : (state.altitude - state.calibAltDiff); alt = isNaN(WIDGETS.gpstrek.getState().calibAltDiff) ? WIDGETS.gpstrek.getState().altitude : (WIDGETS.gpstrek.getState().altitude - WIDGETS.gpstrek.getState().calibAltDiff);
} }
if (state.currentPos && state.currentPos.alt) alt = state.currentPos.alt; if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.alt) alt = WIDGETS.gpstrek.getState().currentPos.alt;
if (isNaN(alt)) return "---";
return loc.distance(alt,3); return loc.distance(alt,3);
}); });
let status2Slice = getDoubleLineSlice("Compass","GPS",()=>{ let status2Slice = getDoubleLineSlice("Compass","GPS",()=>{
return (state.compassHeading?Math.round(state.compassHeading):"---") + "°"; return getAveragedCompass() + "°";
},()=>{ },()=>{
let course = "---°"; let course = "---°";
if (state.currentPos && state.currentPos.course) course = state.currentPos.course + "°"; if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.course) course = WIDGETS.gpstrek.getState().currentPos.course + "°";
return course; return course;
},200); },200);
let healthSlice = getDoubleLineSlice("Heart","Steps",()=>{ let healthSlice = getDoubleLineSlice("Heart","Steps",()=>{
return state.bpm; return WIDGETS.gpstrek.getState().bpm || "---";
},()=>{ },()=>{
return state.steps; return !isNaN(WIDGETS.gpstrek.getState().steps)? WIDGETS.gpstrek.getState().steps: "---";
}); });
let system2Slice = getDoubleLineSlice("Bat","",()=>{ let system2Slice = getDoubleLineSlice("Bat","",()=>{
return (Bangle.isCharging()?"+":"") + E.getBattery().toFixed(0)+"% " + NRF.getBattery().toFixed(2) + "V"; return (Bangle.isCharging()?"+":"") + E.getBattery().toFixed(0)+"% " + (analogRead(D3)*4.2/BAT_FULL).toFixed(2) + "V";
},()=>{ },()=>{
return ""; return "";
}); });
@ -798,17 +836,17 @@ let systemSlice = getDoubleLineSlice("RAM","Storage",()=>{
return (STORAGE.getFree()/1024).toFixed(0)+"kB"; return (STORAGE.getFree()/1024).toFixed(0)+"kB";
}); });
function updateSlices(){ let updateSlices = function(){
slices = []; slices = [];
slices.push(compassSlice); slices.push(compassSlice);
if (state.currentPos && state.currentPos.lat && state.route && state.route.currentWaypoint && state.route.index < state.route.count - 1) { if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lat && WIDGETS.gpstrek.getState().route && WIDGETS.gpstrek.getState().route.currentWaypoint && WIDGETS.gpstrek.getState().route.index < WIDGETS.gpstrek.getState().route.count - 1) {
slices.push(waypointSlice); slices.push(waypointSlice);
} }
if (state.currentPos && state.currentPos.lat && (state.route || state.waypoint)) { if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lat && (WIDGETS.gpstrek.getState().route || WIDGETS.gpstrek.getState().waypoint)) {
slices.push(finishSlice); slices.push(finishSlice);
} }
if ((state.route && state.route.down !== undefined) || state.down != undefined) { if ((WIDGETS.gpstrek.getState().route && WIDGETS.gpstrek.getState().route.down !== undefined) || WIDGETS.gpstrek.getState().down != undefined) {
slices.push(eleSlice); slices.push(eleSlice);
} }
slices.push(statusSlice); slices.push(statusSlice);
@ -816,42 +854,44 @@ function updateSlices(){
slices.push(healthSlice); slices.push(healthSlice);
slices.push(systemSlice); slices.push(systemSlice);
slices.push(system2Slice); slices.push(system2Slice);
maxScreens = Math.ceil(slices.length/numberOfSlices); maxScreens = Math.ceil(slices.length/WIDGETS.gpstrek.getState().numberOfSlices);
} };
function clear() { let clear = function() {
g.clearRect(0,(showWidgets ? 24 : 0), g.getWidth(),g.getHeight()); g.clearRect(Bangle.appRect);
} };
let lastDrawnScreen;
let firstDraw = true;
function draw(){ let draw = function(){
if (!screen) return; if (!global.screen) return;
let ypos = showWidgets ? 24 : 0; let ypos = Bangle.appRect.y;
let firstSlice = (screen-1)*numberOfSlices; let firstSlice = (screen-1)*WIDGETS.gpstrek.getState().numberOfSlices;
updateSlices(); updateSlices();
let force = lastDrawnScreen != screen || firstDraw; let force = lastDrawnScreen != screen || firstDraw;
if (force){ if (force){
clear(); clear();
if (showWidgets){
Bangle.drawWidgets();
}
} }
if (firstDraw) Bangle.drawWidgets();
lastDrawnScreen = screen; lastDrawnScreen = screen;
for (let slice of slices.slice(firstSlice,firstSlice + numberOfSlices)) { let sliceHeight = getSliceHeight();
for (let slice of slices.slice(firstSlice,firstSlice + WIDGETS.gpstrek.getState().numberOfSlices)) {
g.reset(); g.reset();
if (!slice.refresh || slice.refresh() || force) slice.draw(g,0,ypos,sliceHeight,g.getWidth()); if (!slice.refresh || slice.refresh() || force) slice.draw(g,0,ypos,sliceHeight,g.getWidth());
ypos += sliceHeight+1; ypos += sliceHeight+1;
g.drawLine(0,ypos-1,g.getWidth(),ypos-1); g.drawLine(0,ypos-1,g.getWidth(),ypos-1);
} }
if (scheduleDraw){
drawInTimeout();
}
firstDraw = false; firstDraw = false;
} };
switchNav(); switchNav();
g.clear(); clear();
}

View File

@ -1,7 +1,7 @@
{ {
"id": "gpstrek", "id": "gpstrek",
"name": "GPS Trekking", "name": "GPS Trekking",
"version": "0.07", "version": "0.08",
"description": "Helper for tracking the status/progress during hiking. Do NOT depend on this for navigation!", "description": "Helper for tracking the status/progress during hiking. Do NOT depend on this for navigation!",
"icon": "icon.png", "icon": "icon.png",
"screenshots": [{"url":"screen1.png"},{"url":"screen2.png"},{"url":"screen3.png"},{"url":"screen4.png"}], "screenshots": [{"url":"screen1.png"},{"url":"screen2.png"},{"url":"screen3.png"},{"url":"screen4.png"}],

View File

@ -1,6 +1,28 @@
(() => { (() => {
const SAMPLES=5;
function initState(){
//cleanup volatile state here
state = {};
state.compassSamples = new Array(SAMPLES).fill(0);
state.lastSample = 0;
state.sampleIndex = 0;
state.currentPos={};
state.steps = 0;
state.calibAltDiff = 0;
state.numberOfSlices = 3;
state.steps = 0;
state.up = 0;
state.down = 0;
state.saved = 0;
state.avgComp = 0;
}
const STORAGE=require('Storage'); const STORAGE=require('Storage');
let state = STORAGE.readJSON("gpstrek.state.json")||{}; let state = STORAGE.readJSON("gpstrek.state.json");
if (!state) {
state = {};
initState();
}
let bgChanged = false; let bgChanged = false;
function saveState(){ function saveState(){
@ -8,12 +30,13 @@ function saveState(){
STORAGE.writeJSON("gpstrek.state.json", state); STORAGE.writeJSON("gpstrek.state.json", state);
} }
E.on("kill",()=>{ function onKill(){
if (bgChanged){ if (bgChanged || state.route || state.waypoint){
saveState(); saveState();
} }
}); }
E.on("kill", onKill);
function onPulse(e){ function onPulse(e){
state.bpm = e.bpm; state.bpm = e.bpm;
@ -23,27 +46,47 @@ function onGPS(fix) {
if(fix.fix) state.currentPos = fix; if(fix.fix) state.currentPos = fix;
} }
function onMag(e) { let radians = function(a) {
if (!state.compassHeading) state.compassHeading = e.heading; return a*Math.PI/180;
};
//if (a+180)mod 360 == b then let degrees = function(a) {
//return (a+b)/2 mod 360 and ((a+b)/2 mod 360) + 180 (they are both the solution, so you may choose one depending if you prefer counterclockwise or clockwise direction) let d = a*180/Math.PI;
//else return (d+360)%360;
//return arctan( (sin(a)+sin(b)) / (cos(a)+cos(b) ) };
/* function average(samples){
let average; let s = 0;
let a = radians(compassHeading); let c = 0;
let b = radians(e.heading); for (let h of samples){
if ((a+180) % 360 == b){ s += Math.sin(radians(h));
average = ((a+b)/2 % 360); //can add 180 depending on rotation c += Math.cos(radians(h));
} else { }
average = Math.atan( (Math.sin(a)+Math.sin(b))/(Math.cos(a)+Math.cos(b)) ); s /= samples.length;
c /= samples.length;
let result = degrees(Math.atan(s/c));
if (c < 0) result += 180;
if (s < 0 && c > 0) result += 360;
result%=360;
return result;
}
function onMag(e) {
if (!isNaN(e.heading)){
if (Bangle.isLocked() || (Bangle.getGPSFix() && Bangle.getGPSFix().lon))
state.avgComp = e.heading;
else {
state.compassSamples[state.sampleIndex++] = e.heading;
state.lastSample = Date.now();
if (state.sampleIndex > SAMPLES - 1){
state.sampleIndex = 0;
let avg = average(state.compassSamples);
state.avgComp = average([state.avgComp,avg]);
}
}
} }
print("Angle",compassHeading,e.heading, average);
compassHeading = (compassHeading + degrees(average)) % 360;
*/
state.compassHeading = Math.round(e.heading);
} }
function onStep(e) { function onStep(e) {
@ -73,6 +116,16 @@ function onAcc (e){
state.acc = e; state.acc = e;
} }
function update(){
if (state.active){
start(false);
}
if (state.active == !(WIDGETS.gpstrek.width)) {
if(WIDGETS.gpstrek) WIDGETS.gpstrek.width = state.active?24:0;
Bangle.drawWidgets();
}
}
function start(bg){ function start(bg){
Bangle.removeListener('GPS', onGPS); Bangle.removeListener('GPS', onGPS);
Bangle.removeListener("HRM", onPulse); Bangle.removeListener("HRM", onPulse);
@ -94,9 +147,9 @@ function start(bg){
if (bg){ if (bg){
if (!state.active) bgChanged = true; if (!state.active) bgChanged = true;
state.active = true; state.active = true;
update();
saveState(); saveState();
} }
Bangle.drawWidgets();
} }
function stop(bg){ function stop(bg){
@ -114,22 +167,10 @@ function stop(bg){
Bangle.removeListener("step", onStep); Bangle.removeListener("step", onStep);
Bangle.removeListener("pressure", onPressure); Bangle.removeListener("pressure", onPressure);
Bangle.removeListener('accel', onAcc); Bangle.removeListener('accel', onAcc);
E.removeListener("kill", onKill);
} }
update();
saveState(); saveState();
Bangle.drawWidgets();
}
function initState(){
//cleanup volatile state here
state.currentPos={};
state.steps = Bangle.getStepCount();
state.calibAltDiff = 0;
state.up = 0;
state.down = 0;
}
if (state.saved && state.saved < Date.now() - 60000){
initState();
} }
if (state.active){ if (state.active){
@ -141,11 +182,15 @@ WIDGETS["gpstrek"]={
width:state.active?24:0, width:state.active?24:0,
resetState: initState, resetState: initState,
getState: function() { getState: function() {
if (state.saved && Date.now() - state.saved > 60000 || !state){
initState();
}
return state; return state;
}, },
start:start, start:start,
stop:stop, stop:stop,
draw:function() { draw:function() {
update();
if (state.active){ if (state.active){
g.reset(); g.reset();
g.drawImage(atob("GBiBAAAAAAAAAAAYAAAYAAAYAAA8AAA8AAB+AAB+AADbAADbAAGZgAGZgAMYwAMYwAcY4AYYYA5+cA3/sB/D+B4AeBAACAAAAAAAAA=="), this.x, this.y); g.drawImage(atob("GBiBAAAAAAAAAAAYAAAYAAAYAAA8AAA8AAB+AAB+AADbAADbAAGZgAGZgAMYwAMYwAcY4AYYYA5+cA3/sB/D+B4AeBAACAAAAAAAAA=="), this.x, this.y);

View File

@ -5,7 +5,7 @@
"description": "Integrates your BangleJS into HomeAssistant.", "description": "Integrates your BangleJS into HomeAssistant.",
"icon": "ha.png", "icon": "ha.png",
"type": "app", "type": "app",
"tags": "tool", "tags": "tool,clkinfo",
"readme": "README.md", "readme": "README.md",
"supports": ["BANGLEJS2"], "supports": ["BANGLEJS2"],
"custom": "custom.html", "custom": "custom.html",

View File

@ -7,3 +7,5 @@
0.21: Add Settings 0.21: Add Settings
0.22: Use default Bangle formatter for booleans 0.22: Use default Bangle formatter for booleans
0.23: Added note to configure position in "my location" if not done yet. Small fixes. 0.23: Added note to configure position in "my location" if not done yet. Small fixes.
0.24: Added fast load
0.25: Minor code optimization

View File

@ -1,3 +1,5 @@
{ // must be inside our own scope here so that when we are unloaded everything disappears
// ------- Settings file // ------- Settings file
const SETTINGSFILE = "hworldclock.json"; const SETTINGSFILE = "hworldclock.json";
var secondsMode; var secondsMode;
@ -153,15 +155,15 @@ function updatePos() {
function drawSeconds() { function drawSeconds() {
// get date // get date
var d = new Date(); let d = new Date();
var da = d.toString().split(" "); let da = d.toString().split(" ");
// default draw styles // default draw styles
g.reset().setBgColor(g.theme.bg).setFontAlign(0, 0); g.reset().setBgColor(g.theme.bg).setFontAlign(0, 0);
// draw time // draw time
var time = da[4].split(":"); let time = da[4].split(":");
var seconds = time[2]; let seconds = time[2];
g.setFont("5x9Numeric7Seg",primaryTimeFontSize - 3); g.setFont("5x9Numeric7Seg",primaryTimeFontSize - 3);
if (g.theme.dark) { if (g.theme.dark) {
@ -184,15 +186,15 @@ function drawSeconds() {
function draw() { function draw() {
// get date // get date
var d = new Date(); let d = new Date();
var da = d.toString().split(" "); let da = d.toString().split(" ");
// default draw styles // default draw styles
g.reset().setBgColor(g.theme.bg).setFontAlign(0, 0); g.reset().setBgColor(g.theme.bg).setFontAlign(0, 0);
// draw time // draw time
var time = da[4].split(":"); let time = da[4].split(":");
var hours = time[0], let hours = time[0],
minutes = time[1]; minutes = time[1];
@ -223,7 +225,7 @@ function draw() {
// am / PM ? // am / PM ?
if (_12hour){ if (_12hour){
//do 12 hour stuff //do 12 hour stuff
//var ampm = require("locale").medidian(new Date()); Not working //let ampm = require("locale").medidian(new Date()); Not working
g.setFont("Vector", 17); g.setFont("Vector", 17);
g.drawString(ampm, xyCenterSeconds, yAmPm, true); g.drawString(ampm, xyCenterSeconds, yAmPm, true);
} }
@ -232,14 +234,14 @@ function draw() {
// draw Day, name of month, Date // draw Day, name of month, Date
//DATE //DATE
var localDate = require("locale").date(new Date(), 1); let localDate = require("locale").date(new Date(), 1);
localDate = localDate.substring(0, localDate.length - 5); localDate = localDate.substring(0, localDate.length - 5);
g.setFont("Vector", 17); g.setFont("Vector", 17);
g.drawString(require("locale").dow(new Date(), 1).toUpperCase() + ", " + localDate, xyCenter, yposDate, true); g.drawString(require("locale").dow(new Date(), 1).toUpperCase() + ", " + localDate, xyCenter, yposDate, true);
g.setFont(font, primaryDateFontSize); g.setFont(font, primaryDateFontSize);
// set gmt to UTC+0 // set gmt to UTC+0
var gmt = new Date(d.getTime() + d.getTimezoneOffset() * 60 * 1000); let gmt = new Date(d.getTime() + d.getTimezoneOffset() * 60 * 1000);
// Loop through offset(s) and render // Loop through offset(s) and render
offsets.forEach((offset, index) => { offsets.forEach((offset, index) => {
@ -249,7 +251,7 @@ function draw() {
if (offsets.length === 1) { if (offsets.length === 1) {
var date = [require("locale").dow(new Date(), 1), require("locale").date(new Date(), 1)]; let date = [require("locale").dow(new Date(), 1), require("locale").date(new Date(), 1)];
// For a single secondary timezone, draw it bigger and drop time zone to second line // For a single secondary timezone, draw it bigger and drop time zone to second line
const xOffset = 30; const xOffset = 30;
g.setFont(font, secondaryTimeFontSize).drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true); g.setFont(font, secondaryTimeFontSize).drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true);
@ -295,8 +297,18 @@ g.clear();
// Init the settings of the app // Init the settings of the app
loadMySettings(); loadMySettings();
// Show launcher when button pressed // Show launcher when middle button pressed
Bangle.setUI("clock"); Bangle.setUI({
mode : "clock",
remove : function() {
// Called to unload all of the clock app
if (PosInterval) clearInterval(PosInterval);
PosInterval = undefined;
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
drawTimeoutSeconds = undefined;
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
}});
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
@ -307,7 +319,7 @@ draw();
if (!Bangle.isLocked()) { // Initial state if (!Bangle.isLocked()) { // Initial state
if (showSunInfo) { if (showSunInfo) {
if (PosInterval != 0) clearInterval(PosInterval); if (PosInterval != 0 && typeof PosInterval != 'undefined') clearInterval(PosInterval);
PosInterval = setInterval(updatePos, 60*10E3); // refesh every 10 mins PosInterval = setInterval(updatePos, 60*10E3); // refesh every 10 mins
updatePos(); updatePos();
} }
@ -333,7 +345,7 @@ if (!Bangle.isLocked()) { // Initial state
drawTimeout = undefined; drawTimeout = undefined;
if (showSunInfo) { if (showSunInfo) {
if (PosInterval != 0) clearInterval(PosInterval); if (PosInterval != 0 && typeof PosInterval != 'undefined') clearInterval(PosInterval);
PosInterval = setInterval(updatePos, 60*60E3); // refesh every 60 mins PosInterval = setInterval(updatePos, 60*60E3); // refesh every 60 mins
updatePos(); updatePos();
} }
@ -378,4 +390,5 @@ Bangle.on('lock',on=>{
} }
draw(); // draw immediately, queue redraw draw(); // draw immediately, queue redraw
} }
}); });
}

View File

@ -2,7 +2,7 @@
"id": "hworldclock", "id": "hworldclock",
"name": "Hanks World Clock", "name": "Hanks World Clock",
"shortName": "Hanks World Clock", "shortName": "Hanks World Clock",
"version": "0.23", "version": "0.25",
"description": "Current time zone plus up to three others", "description": "Current time zone plus up to three others",
"allow_emulator":true, "allow_emulator":true,
"icon": "app.png", "icon": "app.png",

View File

@ -14,3 +14,7 @@
added timeOut to return to the clock added timeOut to return to the clock
0.11: Cleanup timeout when changing to clock 0.11: Cleanup timeout when changing to clock
Reset timeout on swipe and drag Reset timeout on swipe and drag
0.12: Use Bangle.load and Bangle.showClock
0.13: Fix automatic switch to clock
0.14: Revert use of Bangle.load to classic load calls since widgets would
still be loaded when they weren't supposed to.

View File

@ -186,39 +186,22 @@
let i = YtoIdx(e.y); let i = YtoIdx(e.y);
selectItem(i, e); selectItem(i, e);
}, },
swipe: (h,_) => { if(settings.swipeExit && h==1) { returnToClock(); } }, swipe: (h,_) => { if(settings.swipeExit && h==1) { Bangle.showClock(); } },
btn: _=> { if (settings.oneClickExit) Bangle.showClock(); },
remove: function() {
if (timeout) clearTimeout(timeout);
}
}; };
const returnToClock = function() {
Bangle.setUI();
delete launchCache;
delete launchHash;
delete drawItemAuto;
delete drawText;
delete selectItem;
delete onDrag;
delete drawItems;
delete drawItem;
delete returnToClock;
delete idxToY;
delete YtoIdx;
delete settings;
if (timeout) clearTimeout(timeout);
setTimeout(eval, 0, s.read(".bootcde"));
};
if (settings.oneClickExit) mode.btn = returnToClock;
let timeout; let timeout;
const updateTimeout = function(){ const updateTimeout = function(){
if (settings.timeOut!="Off"){ if (settings.timeOut!="Off"){
let time=parseInt(settings.timeOut); //the "s" will be trimmed by the parseInt let time=parseInt(settings.timeOut); //the "s" will be trimmed by the parseInt
if (timeout) clearTimeout(timeout); if (timeout) clearTimeout(timeout);
timeout = setTimeout(returnToClock,time*1000); timeout = setTimeout(Bangle.showClock,time*1000);
} }
} };
updateTimeout(); updateTimeout();
Bangle.setUI(mode); Bangle.setUI(mode);

View File

@ -2,7 +2,7 @@
"id": "iconlaunch", "id": "iconlaunch",
"name": "Icon Launcher", "name": "Icon Launcher",
"shortName" : "Icon launcher", "shortName" : "Icon launcher",
"version": "0.11", "version": "0.14",
"icon": "app.png", "icon": "app.png",
"description": "A launcher inspired by smartphones, with an icon-only scrollable menu.", "description": "A launcher inspired by smartphones, with an icon-only scrollable menu.",
"tags": "tool,system,launcher", "tags": "tool,system,launcher",

View File

@ -16,3 +16,5 @@
Fix colorsetting in promises in generated code Fix colorsetting in promises in generated code
Some performance improvements by caching lookups Some performance improvements by caching lookups
Activate UI after first draw is complete to prevent drawing over launcher Activate UI after first draw is complete to prevent drawing over launcher
0.13: Use widget_utils swipeOn()
Allows minification by combining all but picture data into one file

View File

@ -1,7 +1,12 @@
let unlockedDrawInterval = []; let s = {};
let lockedDrawInterval = []; // unlocked draw intervals
let showWidgets = false; s.udi = [];
let firstDraw = true; // locked draw intervals
s.ldi = [];
// full draw
s.fd = true;
// performance log
s.pl = {};
{ {
let x = g.getWidth()/2; let x = g.getWidth()/2;
@ -21,12 +26,10 @@ let firstDraw = true;
let precompiledJs = eval(require("Storage").read("imageclock.draw.js")); let precompiledJs = eval(require("Storage").read("imageclock.draw.js"));
let settings = require('Storage').readJSON("imageclock.json", true) || {}; let settings = require('Storage').readJSON("imageclock.json", true) || {};
let performanceLog = {};
let startPerfLog = () => {}; let startPerfLog = () => {};
let endPerfLog = () => {}; let endPerfLog = () => {};
Bangle.printPerfLog = () => {print("Deactivated");}; Bangle.printPerfLog = () => {print("Deactivated");};
Bangle.resetPerfLog = () => {performanceLog = {};}; Bangle.resetPerfLog = () => {s.pl = {};};
let colormap={ let colormap={
"#000":0, "#000":0,
@ -64,35 +67,37 @@ let firstDraw = true;
if (settings.perflog){ if (settings.perflog){
startPerfLog = function(name){ startPerfLog = function(name){
let time = getTime(); let time = getTime();
if (!performanceLog.start) performanceLog.start={}; if (!s.pl.start) s.pl.start={};
performanceLog.start[name] = time; s.pl.start[name] = time;
}; };
endPerfLog = function (name){ endPerfLog = function (name, once){
let time = getTime(); let time = getTime();
if (!performanceLog.last) performanceLog.last={}; if (!s.pl.start[name]) return;
let duration = time - performanceLog.start[name]; if (!s.pl.last) s.pl.last={};
performanceLog.last[name] = duration; let duration = time - s.pl.start[name];
if (!performanceLog.cum) performanceLog.cum={}; s.pl.last[name] = duration;
if (!performanceLog.cum[name]) performanceLog.cum[name] = 0; if (!s.pl.cum) s.pl.cum={};
performanceLog.cum[name] += duration; if (!s.pl.cum[name]) s.pl.cum[name] = 0;
if (!performanceLog.count) performanceLog.count={}; s.pl.cum[name] += duration;
if (!performanceLog.count[name]) performanceLog.count[name] = 0; if (!s.pl.count) s.pl.count={};
performanceLog.count[name]++; if (!s.pl.count[name]) s.pl.count[name] = 0;
s.pl.count[name]++;
if (once){s.pl.start[name] = undefined}
}; };
Bangle.printPerfLog = function(){ Bangle.printPerfLog = function(){
let result = ""; let result = "";
let keys = []; let keys = [];
for (let c in performanceLog.cum){ for (let c in s.pl.cum){
keys.push(c); keys.push(c);
} }
keys.sort(); keys.sort();
for (let k of keys){ for (let k of keys){
print(k, "last:", (performanceLog.last[k] * 1000).toFixed(0), "average:", (performanceLog.cum[k]/performanceLog.count[k]*1000).toFixed(0), "count:", performanceLog.count[k], "total:", (performanceLog.cum[k] * 1000).toFixed(0)); print(k, "last:", (s.pl.last[k] * 1000).toFixed(0), "average:", (s.pl.cum[k]/s.pl.count[k]*1000).toFixed(0), "count:", s.pl.count[k], "total:", (s.pl.cum[k] * 1000).toFixed(0));
} }
}; };
} }
startPerfLog("fullDraw");
startPerfLog("loadFunctions"); startPerfLog("loadFunctions");
let delayTimeouts = {}; let delayTimeouts = {};
@ -609,15 +614,22 @@ let firstDraw = true;
promise.then(()=>{ promise.then(()=>{
let currentDrawingTime = Date.now(); let currentDrawingTime = Date.now();
if (showWidgets){
restoreWidgetDraw();
}
lastDrawTime = Date.now() - start; lastDrawTime = Date.now() - start;
isDrawing=false; isDrawing=false;
firstDraw=false; s.fd=false;
requestRefresh = false; requestRefresh = false;
endPerfLog("initialDraw"); endPerfLog("initialDraw");
if (!Bangle.uiRemove) setUi(); endPerfLog("fullDraw", true);
if (!Bangle.uiRemove){
setUi();
let orig = Bangle.drawWidgets;
Bangle.drawWidgets = ()=>{};
Bangle.loadWidgets();
Bangle.drawWidgets = orig;
require("widget_utils").swipeOn();
Bangle.drawWidgets();
}
}).catch((e)=>{ }).catch((e)=>{
print("Error during drawing", e); print("Error during drawing", e);
}); });
@ -701,16 +713,16 @@ let firstDraw = true;
let handleLock = function(isLocked, forceRedraw){ let handleLock = function(isLocked, forceRedraw){
//print("isLocked", Bangle.isLocked()); //print("isLocked", Bangle.isLocked());
for (let i of unlockedDrawInterval){ for (let i of s.udi){
//print("Clearing unlocked", i); //print("Clearing unlocked", i);
clearInterval(i); clearInterval(i);
} }
for (let i of lockedDrawInterval){ for (let i of s.ldi){
//print("Clearing locked", i); //print("Clearing locked", i);
clearInterval(i); clearInterval(i);
} }
unlockedDrawInterval = []; s.udi = [];
lockedDrawInterval = []; s.ldi = [];
if (!isLocked){ if (!isLocked){
if (forceRedraw || !redrawEvents || (redrawEvents.includes("unlock"))){ if (forceRedraw || !redrawEvents || (redrawEvents.includes("unlock"))){
@ -726,7 +738,7 @@ let firstDraw = true;
initialDraw(watchfaceResources, watchface); initialDraw(watchfaceResources, watchface);
},unlockedRedraw, (v)=>{ },unlockedRedraw, (v)=>{
//print("New matched unlocked interval", v); //print("New matched unlocked interval", v);
unlockedDrawInterval.push(v); s.udi.push(v);
}, lastDrawTime); }, lastDrawTime);
if (!events || events.includes("HRM")) Bangle.setHRMPower(1, "imageclock"); if (!events || events.includes("HRM")) Bangle.setHRMPower(1, "imageclock");
if (!events || events.includes("pressure")) Bangle.setBarometerPower(1, 'imageclock'); if (!events || events.includes("pressure")) Bangle.setBarometerPower(1, 'imageclock');
@ -744,43 +756,13 @@ let firstDraw = true;
initialDraw(watchfaceResources, watchface); initialDraw(watchfaceResources, watchface);
},lockedRedraw, (v)=>{ },lockedRedraw, (v)=>{
//print("New matched locked interval", v); //print("New matched locked interval", v);
lockedDrawInterval.push(v); s.ldi.push(v);
}, lastDrawTime); }, lastDrawTime);
Bangle.setHRMPower(0, "imageclock"); Bangle.setHRMPower(0, "imageclock");
Bangle.setBarometerPower(0, 'imageclock'); Bangle.setBarometerPower(0, 'imageclock');
} }
}; };
let showWidgetsChanged = false;
let restoreWidgetDraw = function(){
require("widget_utils").show();
Bangle.drawWidgets();
};
let handleSwipe = function(lr, ud){
if (!showWidgets && ud == 1){
//print("Enable widgets");
restoreWidgetDraw();
showWidgetsChanged = true;
}
if (showWidgets && ud == -1){
//print("Disable widgets");
clearWidgetsDraw();
firstDraw = true;
showWidgetsChanged = true;
}
if (showWidgetsChanged){
showWidgetsChanged = false;
//print("Draw after widget change");
showWidgets = ud == 1;
initialDraw();
}
};
Bangle.on('swipe', handleSwipe);
if (!events || events.includes("pressure")){ if (!events || events.includes("pressure")){
Bangle.on('pressure', handlePressure); Bangle.on('pressure', handlePressure);
try{ try{
@ -799,14 +781,6 @@ let firstDraw = true;
if (!events || events.includes("charging")) { if (!events || events.includes("charging")) {
Bangle.on('charging', handleCharging); Bangle.on('charging', handleCharging);
} }
let originalWidgetDraw = {};
let originalWidgetArea = {};
let clearWidgetsDraw = function(){
//print("Clear widget draw calls");
require("widget_utils").hide();
}
handleLock(Bangle.isLocked(), true); handleLock(Bangle.isLocked(), true);
@ -819,7 +793,6 @@ let firstDraw = true;
Bangle.setHRMPower(0, "imageclock"); Bangle.setHRMPower(0, "imageclock");
Bangle.setBarometerPower(0, 'imageclock'); Bangle.setBarometerPower(0, 'imageclock');
Bangle.removeListener('swipe', handleSwipe);
Bangle.removeListener('lock', handleLock); Bangle.removeListener('lock', handleLock);
Bangle.removeListener('charging', handleCharging); Bangle.removeListener('charging', handleCharging);
Bangle.removeListener('HRM', handleHrm); Bangle.removeListener('HRM', handleHrm);
@ -829,31 +802,22 @@ let firstDraw = true;
if (initialDrawTimeoutUnlocked) clearTimeout(initialDrawTimeoutUnlocked); if (initialDrawTimeoutUnlocked) clearTimeout(initialDrawTimeoutUnlocked);
if (initialDrawTimeoutLocked) clearTimeout(initialDrawTimeoutLocked); if (initialDrawTimeoutLocked) clearTimeout(initialDrawTimeoutLocked);
for (let i of global.unlockedDrawInterval){ for (let i of global.s.udi){
//print("Clearing unlocked", i); //print("Clearing unlocked", i);
clearInterval(i); clearInterval(i);
} }
delete global.unlockedDrawInterval; for (let i of global.s.ldi){
for (let i of global.lockedDrawInterval){
//print("Clearing locked", i); //print("Clearing locked", i);
clearInterval(i); clearInterval(i);
} }
delete global.lockedDrawInterval;
delete global.showWidgets;
delete global.firstDraw;
delete Bangle.printPerfLog; delete Bangle.printPerfLog;
if (settings.perflog){ if (settings.perflog){
delete Bangle.resetPerfLog; delete Bangle.resetPerfLog;
delete performanceLog;
} }
cleanupDelays(); cleanupDelays();
restoreWidgetDraw(); require("widget_utils").show();
} }
}); });
} }
Bangle.loadWidgets();
clearWidgetsDraw();
} }

View File

@ -4,8 +4,8 @@
</head> </head>
<body> <body>
<script src="../../core/lib/heatshrink.js"></script> <script src="../../webtools/heatshrink.js"></script>
<script src="../../core/lib/imageconverter.js"></script> <script src="../../webtools/imageconverter.js"></script>
<script src="../../core/lib/customize.js"></script> <script src="../../core/lib/customize.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.js"></script>
@ -25,6 +25,8 @@
<label for="timeoutwrap">Wrap draw calls in timeouts (Slower, more RAM use, better interactivity)</label></br> <label for="timeoutwrap">Wrap draw calls in timeouts (Slower, more RAM use, better interactivity)</label></br>
<input type="checkbox" id="forceOrigPlane" name="mode" disabled="true"/> <input type="checkbox" id="forceOrigPlane" name="mode" disabled="true"/>
<label for="forceOrigPlane">Force use of direct drawing (Even faster, but will produce visible artifacts on not optimized watch faces)</label></br> <label for="forceOrigPlane">Force use of direct drawing (Even faster, but will produce visible artifacts on not optimized watch faces)</label></br>
<input type="checkbox" id="separateFiles" name="mode"/>
<label for="separateFiles">Do not create combined app flle (slower but more flexible for debugging, incompatible with minification)</label></br>
<input type="checkbox" id="debugprints" name="mode"/> <input type="checkbox" id="debugprints" name="mode"/>
<label for="debugprints">Add debug prints to generated code</label></br> <label for="debugprints">Add debug prints to generated code</label></br>
</p> </p>
@ -32,7 +34,7 @@
<p>Select watchface folder:</br><input type="file" id="fileLoader" name="files[]" multiple directory="" webkitdirectory="" moxdirectory="" /></p> <p>Select watchface folder:</br><input type="file" id="fileLoader" name="files[]" multiple directory="" webkitdirectory="" moxdirectory="" /></p>
<p><b>or</b></p> <p><b>or</b></p>
<p>Select watchface zip file: </br><input type="file" id="zipLoader" name="zip"/></p><br/> <p>Select watchface zip file: </br><input type="file" id="zipLoader" name="zip"/></p><br/>
<button id="btnUpload" class="btn btn-primary">Upload to watch</button></br> <button id="btnUpload" class="btn btn-primary">Upload to watch</button></br>
<button id="btnSave" class="btn btn-secondary">Save resources file</button></br> <button id="btnSave" class="btn btn-secondary">Save resources file</button></br>
<button id="btnSaveFace" class="btn btn-secondary">Save face file</button></br> <button id="btnSaveFace" class="btn btn-secondary">Save face file</button></br>
@ -55,15 +57,15 @@
var expectedFiles = 0; var expectedFiles = 0;
var rootZip = new JSZip(); var rootZip = new JSZip();
var resourcesZip = rootZip.folder("resources"); var resourcesZip = rootZip.folder("resources");
function isNativeFormat(){ function isNativeFormat(){
return document.getElementById("useNative").checked; return document.getElementById("useNative").checked;
} }
function addDebug(){ function addDebug(){
return document.getElementById("debugprints").checked; return document.getElementById("debugprints").checked;
} }
function convertAmazfitTime(time){ function convertAmazfitTime(time){
var result = {}; var result = {};
if (time.Hours){ if (time.Hours){
@ -88,7 +90,7 @@
} }
return result; return result;
} }
function convertAmazfitDate(date){ function convertAmazfitDate(date){
var result = {}; var result = {};
if (date.MonthAndDay.Separate.Day) result.Day = convertAmazfitNumber(date.MonthAndDay.Separate.Day, "Day"); if (date.MonthAndDay.Separate.Day) result.Day = convertAmazfitNumber(date.MonthAndDay.Separate.Day, "Day");
@ -98,11 +100,11 @@
} }
return result; return result;
} }
var filesToMove={}; var filesToMove={};
var zipChangePromise = Promise.resolve(); var zipChangePromise = Promise.resolve();
function performFileChanges(){ function performFileChanges(){
var promise = Promise.resolve(); var promise = Promise.resolve();
//rename all files to just numbers without leading zeroes //rename all files to just numbers without leading zeroes
@ -111,7 +113,7 @@
var tmp = resultJson[c]; var tmp = resultJson[c];
delete resultJson[c]; delete resultJson[c];
resultJson[Number(c)] = tmp; resultJson[Number(c)] = tmp;
async function modZip(c){ async function modZip(c){
console.log("Async modification of ", c) console.log("Async modification of ", c)
var fileRegex = new RegExp(c + ".*"); var fileRegex = new RegExp(c + ".*");
@ -120,27 +122,27 @@
console.log("Filedata is", fileData); console.log("Filedata is", fileData);
var extension = resourcesZip.file(fileRegex)[0].name.match(/\.[^.]*$/); var extension = resourcesZip.file(fileRegex)[0].name.match(/\.[^.]*$/);
var newName = Number(c) + extension; var newName = Number(c) + extension;
console.log("Renaming to", newName); console.log("Renaming to", newName);
resourcesZip.remove(c + extension); resourcesZip.remove(c + extension);
resourcesZip.file(newName, fileData); resourcesZip.file(newName, fileData);
} }
promise = promise.then(modZip(c)); promise = promise.then(modZip(c));
} }
console.log("File moves:", filesToMove); console.log("File moves:", filesToMove);
for (var c in filesToMove){ for (var c in filesToMove){
var tmp = resultJson[c]; var tmp = resultJson[c];
console.log("Handle filemove", c, filesToMove[c], tmp); console.log("Handle filemove", c, filesToMove[c], tmp);
var element = resultJson; var element = resultJson;
var path = filesToMove[c]; var path = filesToMove[c];
async function modZip(c){ async function modZip(c){
console.log("Async modification of ", c) console.log("Async modification of ", c)
var fileRegex = new RegExp(c + ".*"); var fileRegex = new RegExp(c + ".*");
@ -149,13 +151,13 @@
console.log("Filedata is", fileData); console.log("Filedata is", fileData);
var extension = resourcesZip.file(fileRegex)[0].name.match(/\.[^.]*$/); var extension = resourcesZip.file(fileRegex)[0].name.match(/\.[^.]*$/);
var newName = Number(c) + extension; var newName = Number(c) + extension;
console.log("Copying to", newName); console.log("Copying to", newName);
resourcesZip.file(filesToMove[c].join("/") + extension, fileData); resourcesZip.file(filesToMove[c].join("/") + extension, fileData);
} }
promise = promise.then(modZip(c)); promise = promise.then(modZip(c));
for (var i = 0; i< path.length; i++){ for (var i = 0; i< path.length; i++){
if (!element[path[i]]) element[path[i]] = {}; if (!element[path[i]]) element[path[i]] = {};
if (i == path.length - 1){ if (i == path.length - 1){
@ -164,7 +166,7 @@
element = element[path[i]]; element = element[path[i]];
} }
} }
} }
promise.then(()=>{ promise.then(()=>{
document.getElementById('btnUpload').disabled = true; document.getElementById('btnUpload').disabled = true;
@ -172,7 +174,7 @@
console.log("After moves", resultJson); console.log("After moves", resultJson);
return promise; return promise;
}; };
function convertAmazfitMultistate(multistate, value, minValue, maxValue){ function convertAmazfitMultistate(multistate, value, minValue, maxValue){
var result = { var result = {
MultiState: { MultiState: {
@ -188,18 +190,18 @@
if (multistate.ImageIndexOff) filesToMove[multistate.ImageIndexOff] = ["status", value, "off"]; if (multistate.ImageIndexOff) filesToMove[multistate.ImageIndexOff] = ["status", value, "off"];
return result; return result;
} }
function convertAmazfitStatus(status){ function convertAmazfitStatus(status){
var result = {}; var result = {};
if (status.Alarm) result.Alarm = convertAmazfitMultistate(status.Alarm,"Alarm"); if (status.Alarm) result.Alarm = convertAmazfitMultistate(status.Alarm,"Alarm");
if (status.Bluetooth) result.Bluetooth = convertAmazfitMultistate(status.Bluetooth,"Bluetooth"); if (status.Bluetooth) result.Bluetooth = convertAmazfitMultistate(status.Bluetooth,"Bluetooth");
if (status.DoNotDisturb) result.DoNotDisturb = convertAmazfitMultistate(status.DoNotDisturb,"Notifications"); if (status.DoNotDisturb) result.DoNotDisturb = convertAmazfitMultistate(status.DoNotDisturb,"Notifications");
if (status.Lock) result.Lock = convertAmazfitMultistate(status.Lock,"Lock"); if (status.Lock) result.Lock = convertAmazfitMultistate(status.Lock,"Lock");
return result; return result;
} }
function convertAmazfitNumber(element, value, minValue, maxValue){ function convertAmazfitNumber(element, value, minValue, maxValue){
var number = {}; var number = {};
var result = { var result = {
@ -233,10 +235,10 @@
if (maxValue !== undefined) number.MinValue = minValue; if (maxValue !== undefined) number.MinValue = minValue;
return result; return result;
} }
function moveWeatherIcons(icon){ function moveWeatherIcons(icon){
filesToMove[icon.ImageIndex + 0] = ["weather", "fallback"]; filesToMove[icon.ImageIndex + 0] = ["weather", "fallback"];
// Light clouds // Light clouds
filesToMove[icon.ImageIndex + 1] = ["weather", 801]; filesToMove[icon.ImageIndex + 1] = ["weather", 801];
// Cloudy, possible rain // Cloudy, possible rain
@ -282,7 +284,7 @@
// Very heavy shower // Very heavy shower
filesToMove[icon.ImageIndex + 22] = ["weather", 531]; filesToMove[icon.ImageIndex + 22] = ["weather", 531];
} }
function convertAmazfitTemperature(temp){ function convertAmazfitTemperature(temp){
var result = {}; var result = {};
result = convertAmazfitNumber(temp.Number, "WeatherTemperature"); result = convertAmazfitNumber(temp.Number, "WeatherTemperature");
@ -294,15 +296,15 @@
} }
return result; return result;
} }
function convertAmazfitWeather(weather){ function convertAmazfitWeather(weather){
var result = {}; var result = {};
if (weather.Temperature && weather.Temperature.Current){ if (weather.Temperature && weather.Temperature.Current){
if (!result.Temperature) result.Temperature = {}; if (!result.Temperature) result.Temperature = {};
result.Temperature.Current = convertAmazfitTemperature(weather.Temperature.Current); result.Temperature.Current = convertAmazfitTemperature(weather.Temperature.Current);
} }
if (weather.Temperature && weather.Temperature.Today){ if (weather.Temperature && weather.Temperature.Today){
if (!result.Temperature) result.Temperature = {}; if (!result.Temperature) result.Temperature = {};
if (weather.Temperature.Today.Separate){ if (weather.Temperature.Today.Separate){
@ -327,10 +329,10 @@
} }
return result; return result;
} }
function convertAmazfitActivity(activity){ function convertAmazfitActivity(activity){
var result = {}; var result = {};
if (activity.Steps){ if (activity.Steps){
result.Steps = convertAmazfitNumber(activity.Steps, "Steps"); result.Steps = convertAmazfitNumber(activity.Steps, "Steps");
} }
@ -339,7 +341,7 @@
} }
return result; return result;
} }
function convertAmazfitScale(scale, value, minValue, maxValue){ function convertAmazfitScale(scale, value, minValue, maxValue){
var result = {}; var result = {};
result.Scale = { result.Scale = {
@ -356,10 +358,10 @@
Y: c.Y Y: c.Y
}); });
} }
return result; return result;
} }
function convertAmazfitStepsProgress(steps){ function convertAmazfitStepsProgress(steps){
var result = {}; var result = {};
if (steps.GoalImage){ if (steps.GoalImage){
@ -378,7 +380,7 @@
} }
return result; return result;
} }
function convertAmazfitBattery(battery){ function convertAmazfitBattery(battery){
var result = {}; var result = {};
if (battery.Scale){ if (battery.Scale){
@ -389,7 +391,7 @@
} }
return result; return result;
} }
function convertAmazfitImage(image){ function convertAmazfitImage(image){
var result = { var result = {
Image: { Image: {
@ -401,11 +403,11 @@
}; };
return result; return result;
} }
function convertAmazfitColor(color){ function convertAmazfitColor(color){
return "#" + color.substring(2); return "#" + color.substring(2);
} }
function convertAmazfitHand(hand, rotationValue, minRotationValue, maxRotationValue){ function convertAmazfitHand(hand, rotationValue, minRotationValue, maxRotationValue){
var result = { var result = {
Filled: !hand.OnlyBorder, Filled: !hand.OnlyBorder,
@ -418,18 +420,18 @@
MaxRotationValue: maxRotationValue, MaxRotationValue: maxRotationValue,
MinRotationValue: minRotationValue MinRotationValue: minRotationValue
}; };
result.Vertices = [] result.Vertices = []
for (var c of hand.Shape){ for (var c of hand.Shape){
result.Vertices.push(c); result.Vertices.push(c);
} }
return { Poly: result }; return { Poly: result };
} }
function convertAmazfitAnalog(analog, face){ function convertAmazfitAnalog(analog, face){
var result = { var result = {
}; };
if (analog.Hours){ if (analog.Hours){
result.Hours = {}; result.Hours = {};
result.Hours.Hand = convertAmazfitHand(analog.Hours, "Hour12Analog", 0, 12); result.Hours.Hand = convertAmazfitHand(analog.Hours, "Hour12Analog", 0, 12);
@ -464,14 +466,14 @@
} }
return result; return result;
} }
function restructureAmazfitFormat(dataString){ function restructureAmazfitFormat(dataString){
console.log("Amazfit data:", dataString); console.log("Amazfit data:", dataString);
var json = JSON.parse(dataString); var json = JSON.parse(dataString);
faceJson = json; faceJson = json;
var result = {}; var result = {};
result.Properties = {}; result.Properties = {};
@ -479,8 +481,8 @@
result.Properties.Redraw.Unlocked = 60000; result.Properties.Redraw.Unlocked = 60000;
result.Properties.Redraw.Locked = 60000; result.Properties.Redraw.Locked = 60000;
result.Properties.Redraw.Clear = true; result.Properties.Redraw.Clear = true;
if (json.Background){ if (json.Background){
result.Background = json.Background; result.Background = json.Background;
result.Background.Image.ImagePath = []; result.Background.Image.ImagePath = [];
@ -491,32 +493,32 @@
result.Time = convertAmazfitTime(json.Time); result.Time = convertAmazfitTime(json.Time);
if (json.AnalogDialFace) result.Time.Plane = 1; if (json.AnalogDialFace) result.Time.Plane = 1;
} }
if (json.Date){ if (json.Date){
result.Date = convertAmazfitDate(json.Date); result.Date = convertAmazfitDate(json.Date);
if (json.AnalogDialFace) result.Date.Plane = 1; if (json.AnalogDialFace) result.Date.Plane = 1;
} }
if (json.Status){ if (json.Status){
result.Status = convertAmazfitStatus(json.Status); result.Status = convertAmazfitStatus(json.Status);
if (json.AnalogDialFace) result.Status.Plane = 1; if (json.AnalogDialFace) result.Status.Plane = 1;
} }
if (json.Weather){ if (json.Weather){
result.Weather = convertAmazfitWeather(json.Weather); result.Weather = convertAmazfitWeather(json.Weather);
if (json.AnalogDialFace) result.Weather.Plane = 1; if (json.AnalogDialFace) result.Weather.Plane = 1;
} }
if (json.Activity){ if (json.Activity){
result.Activity = convertAmazfitActivity(json.Activity); result.Activity = convertAmazfitActivity(json.Activity);
if (json.AnalogDialFace) result.Activity.Plane = 1; if (json.AnalogDialFace) result.Activity.Plane = 1;
} }
if (json.StepsProgress){ if (json.StepsProgress){
result.StepsProgress = convertAmazfitStepsProgress(json.StepsProgress); result.StepsProgress = convertAmazfitStepsProgress(json.StepsProgress);
if (json.AnalogDialFace) result.StepsProgress.Plane = 1; if (json.AnalogDialFace) result.StepsProgress.Plane = 1;
} }
if (json.Battery){ if (json.Battery){
result.Battery = convertAmazfitBattery(json.Battery); result.Battery = convertAmazfitBattery(json.Battery);
if (json.AnalogDialFace) result.Battery.Plane = 1; if (json.AnalogDialFace) result.Battery.Plane = 1;
@ -529,7 +531,7 @@
return result; return result;
} }
function parseFaceJson(jsonString){ function parseFaceJson(jsonString){
if (isNativeFormat()){ if (isNativeFormat()){
return JSON.parse(jsonString); return JSON.parse(jsonString);
@ -537,7 +539,7 @@
return restructureAmazfitFormat(jsonString); return restructureAmazfitFormat(jsonString);
} }
} }
function combineProperty(name, source, target){ function combineProperty(name, source, target){
if (source[name] && target[name]){ if (source[name] && target[name]){
if (Array.isArray(target[name])){ if (Array.isArray(target[name])){
@ -556,7 +558,7 @@
if (typeof element == "string" || typeof element == "number") return []; if (typeof element == "string" || typeof element == "number") return [];
for (var c in element){ for (var c in element){
var next = element[c]; var next = element[c];
combineProperty("X",element,next); combineProperty("X",element,next);
combineProperty("Y",element,next); combineProperty("Y",element,next);
combineProperty("Width",element,next); combineProperty("Width",element,next);
@ -571,7 +573,7 @@
combineProperty("MaxRotationValue",element,next); combineProperty("MaxRotationValue",element,next);
if (typeof element.Plane == "number") next.Plane = element.Plane; if (typeof element.Plane == "number") next.Plane = element.Plane;
next.Layer = element.Layer ? (element.Layer) : "" + c; next.Layer = element.Layer ? (element.Layer) : "" + c;
if (["MultiState","Image","CodedImage","Number","Circle","Poly","Rect","Scale"].includes(c)){ if (["MultiState","Image","CodedImage","Number","Circle","Poly","Rect","Scale"].includes(c)){
result.push({type:c, value: next}); result.push({type:c, value: next});
} else { } else {
@ -580,12 +582,12 @@
} }
return result; return result;
} }
function convertToCode(elements, properties, wrapInTimeouts, forceUseOrigPlane){ function convertToCode(elements, properties, wrapInTimeouts, forceUseOrigPlane){
var code = "(function (wr, wf) {\n"; var code = "(function (wr, wf) {\n";
code += "var lc;\n"; code += "var lc;\n";
code += "var p = Promise.resolve();\n"; code += "var p = Promise.resolve();\n";
//get mapped by layer //get mapped by layer
var counter = 0; var counter = 0;
var planes = {}; var planes = {};
@ -606,20 +608,20 @@
} }
if (!planeNumbers.includes(0)) planeNumbers.push(0); if (!planeNumbers.includes(0)) planeNumbers.push(0);
planeNumbers.sort().reverse(); planeNumbers.sort().reverse();
console.log("Found planes", planes, "with numbers", planeNumbers) console.log("Found planes", planes, "with numbers", planeNumbers)
code += "p0 = g;\n"; code += "p0 = g;\n";
for (var planeIndex = 0; planeIndex < planeNumbers.length; planeIndex++){ for (var planeIndex = 0; planeIndex < planeNumbers.length; planeIndex++){
var layers = planes[planeNumbers[planeIndex]]; var layers = planes[planeNumbers[planeIndex]];
var plane = planeNumbers[planeIndex]; var plane = planeNumbers[planeIndex];
var lastSetColor; var lastSetColor;
var lastSetBgColor; var lastSetBgColor;
if (plane != 0) code += "if (!p" + plane + ") p" + plane + " = Graphics.createArrayBuffer(g.getWidth(),g.getHeight(),4,{msb:true});\n"; if (plane != 0) code += "if (!p" + plane + ") p" + plane + " = Graphics.createArrayBuffer(g.getWidth(),g.getHeight(),4,{msb:true});\n";
if (properties.Redraw && properties.Redraw.Clear){ if (properties.Redraw && properties.Redraw.Clear){
if (wrapInTimeouts && (plane != 0 || forceUseOrigPlane)){ if (wrapInTimeouts && (plane != 0 || forceUseOrigPlane)){
code += "p = p.then(()=>delay(0)).then(()=>{\n"; code += "p = p.then(()=>delay(0)).then(()=>{\n";
@ -632,31 +634,31 @@
code += 'endPerfLog("initialDraw_g.clear");'+ "\n"; code += 'endPerfLog("initialDraw_g.clear");'+ "\n";
code += "});\n"; code += "});\n";
} }
var previousPlane = plane + 1; var previousPlane = plane + 1;
if (previousPlane < planeNumbers.length){ if (previousPlane < planeNumbers.length){
code += "p = p.then(()=>{\n"; code += "p = p.then(()=>{\n";
if (addDebug()) code += 'print("Copying of plane ' + previousPlane + ' to display");'+"\n"; if (addDebug()) code += 'print("Copying of plane ' + previousPlane + ' to display");'+"\n";
//code += "g.drawImage(p" + i + ".asImage());"; //code += "g.drawImage(p" + i + ".asImage());";
code += "p0.drawImage({width: p" + previousPlane + ".getWidth(), height: p" + previousPlane + ".getHeight(), bpp: p" + previousPlane + ".getBPP(), buffer: p" + previousPlane + ".buffer, palette: palette});\n"; code += "p0.drawImage({width: p" + previousPlane + ".getWidth(), height: p" + previousPlane + ".getHeight(), bpp: p" + previousPlane + ".getBPP(), buffer: p" + previousPlane + ".buffer, palette: palette});\n";
code += "});\n"; code += "});\n";
} }
console.log("Got layers", layers); console.log("Got layers", layers);
for (var layername in layers){ for (var layername in layers){
var layerElements = layers[layername]; var layerElements = layers[layername];
console.log("Layer elements", layername, layerElements); console.log("Layer elements", layername, layerElements);
//code for whole layer //code for whole layer
if (addDebug()) code += 'print("Starting layer ' + layername + '");' + "\n"; if (addDebug()) code += 'print("Starting layer ' + layername + '");' + "\n";
var checkForLayerChange = false; var checkForLayerChange = false;
var checkcode = ""; var checkcode = "";
if (!(properties.Redraw && properties.Redraw.Clear)){ if (!(properties.Redraw && properties.Redraw.Clear)){
checkcode = 'firstDraw'; checkcode = 's.fd';
for (var i = 0; i< layerElements.length; i++){ for (var i = 0; i< layerElements.length; i++){
var layerElement = layerElements[i]; var layerElement = layerElements[i];
var referencedElement = elements[layerElements[i].index]; var referencedElement = elements[layerElements[i].index];
@ -664,38 +666,38 @@
console.log("Check for change:", layerElement, referencedElement); console.log("Check for change:", layerElement, referencedElement);
if (layerElement.element.Value){ if (layerElement.element.Value){
if (elementType == "MultiState" && layerElement.element.Value) { if (elementType == "MultiState" && layerElement.element.Value) {
checkcode += '| isChangedMultistate(wf.Collapsed[' + layerElement.index + '].value)'; checkcode += '| isChangedMultistate(wf.c[' + layerElement.index + '].value)';
} else { } else {
checkcode += '| isChangedNumber(wf.Collapsed[' + layerElement.index + '].value)'; checkcode += '| isChangedNumber(wf.c[' + layerElement.index + '].value)';
} }
checkForLayerChange = true; checkForLayerChange = true;
} }
} }
} }
//code for elements //code for elements
for (var i = 0; i< layerElements.length; i++){ for (var i = 0; i< layerElements.length; i++){
var elementIndex = layerElements[i].index; var elementIndex = layerElements[i].index;
var c = elements[elementIndex]; var c = elements[elementIndex];
console.log("convert to code", c); console.log("convert to code", c);
var condition = ""; var condition = "";
if (checkcode.length > 0 && checkForLayerChange){ if (checkcode.length > 0 && checkForLayerChange){
if (condition.length > 0) condition += " && "; if (condition.length > 0) condition += " && ";
condition = '(' + checkcode + ')'; condition = '(' + checkcode + ')';
} }
if (c.value.HideOn && c.value.HideOn.includes("Lock")){ if (c.value.HideOn && c.value.HideOn.includes("Lock")){
if (condition.length > 0) condition += " && "; if (condition.length > 0) condition += " && ";
condition = '!Bangle.isLocked()'; condition = '!Bangle.isLocked()';
} }
if (c.value.Type == "Once"){ if (c.value.Type == "Once"){
if (condition.length > 0) condition += " && "; if (condition.length > 0) condition += " && ";
condition += "firstDraw"; condition += "s.fd";
} }
var planeName = "p" + plane; var planeName = "p" + plane;
var colorsetting = ""; var colorsetting = "";
if (c.value.ForegroundColor && lastSetColor != c.value.ForegroundColor){ if (c.value.ForegroundColor && lastSetColor != c.value.ForegroundColor){
@ -712,7 +714,7 @@
else else
colorsetting += planeName + ".setBgColor(\"" + c.value.BackgroundColor + "\");\n"; colorsetting += planeName + ".setBgColor(\"" + c.value.BackgroundColor + "\");\n";
} }
if (addDebug()) code += 'print("Element condition is ' + condition + '");' + "\n"; if (addDebug()) code += 'print("Element condition is ' + condition + '");' + "\n";
code += (condition.length > 0 ? "if (" + condition + "){\n" : ""); code += (condition.length > 0 ? "if (" + condition + "){\n" : "");
if (wrapInTimeouts && (plane != 0 || forceUseOrigPlane)){ if (wrapInTimeouts && (plane != 0 || forceUseOrigPlane)){
@ -722,18 +724,18 @@
} }
code += "" + colorsetting; code += "" + colorsetting;
if (addDebug()) code += 'print("Drawing element ' + elementIndex + ' with type ' + c.type + ' on plane ' + planeName + '");' + "\n"; if (addDebug()) code += 'print("Drawing element ' + elementIndex + ' with type ' + c.type + ' on plane ' + planeName + '");' + "\n";
code += "draw" + c.type + "(" + planeName + ", wr, wf.Collapsed[" + elementIndex + "].value);\n"; code += "draw" + c.type + "(" + planeName + ", wr, wf.c[" + elementIndex + "].value);\n";
code += "});\n"; code += "});\n";
code += (condition.length > 0 ? "}\n" : ""); code += (condition.length > 0 ? "}\n" : "");
} }
} }
console.log("Current plane is", plane); console.log("Current plane is", plane);
} }
code += "return p;})"; code += "return p;})";
console.log("Code:", code); console.log("Code:", code);
return code return code
@ -742,14 +744,14 @@
function postProcess(){ function postProcess(){
moveData(resultJson); moveData(resultJson);
console.log("Created data file", resourceDataString, resourceDataOffset, resultJson); console.log("Created data file", resourceDataString, resourceDataOffset, resultJson);
var properties = faceJson.Properties; var properties = faceJson.Properties;
faceJson = { Properties: properties, Collapsed: collapseTree(faceJson,{X:0,Y:0})}; faceJson = { Properties: properties, c: collapseTree(faceJson,{X:0,Y:0})};
console.log("After collapsing", faceJson); console.log("After collapsing", faceJson);
precompiledJs = convertToCode(faceJson.Collapsed, properties, document.getElementById('timeoutwrap').checked, document.getElementById('forceOrigPlane').checked); precompiledJs = convertToCode(faceJson.c, properties, document.getElementById('timeoutwrap').checked, document.getElementById('forceOrigPlane').checked);
console.log("After precompiling", precompiledJs); console.log("After precompiling", precompiledJs);
} }
function convertJsToJson(imgstr){ function convertJsToJson(imgstr){
var E = {}; var E = {};
E.toArrayBuffer = (s)=>s; E.toArrayBuffer = (s)=>s;
@ -768,7 +770,7 @@
function imageLoaded() { function imageLoaded() {
var options = {}; var options = {};
options.diffusion = infoJson.diffusion ? infoJson.diffusion : "none"; options.diffusion = infoJson.diffusion ? infoJson.diffusion : "none";
options.compression = false; options.compression = false;
options.alphaToColor = false; options.alphaToColor = false;
@ -779,12 +781,12 @@
options.contrast = 0; options.contrast = 0;
options.mode = infoJson.color ? infoJson.color : "1bit"; options.mode = infoJson.color ? infoJson.color : "1bit";
options.output = "object"; options.output = "object";
console.log("Loaded image has path", this.path); console.log("Loaded image has path", this.path);
var jsonPath = this.path.split("/"); var jsonPath = this.path.split("/");
var forcedTransparentColorMatch = jsonPath[jsonPath.length-1].match(/.*\.t([^.]+)\..*/) var forcedTransparentColorMatch = jsonPath[jsonPath.length-1].match(/.*\.t([^.]+)\..*/)
var forcedTransparentColor; var forcedTransparentColor;
if (jsonPath[jsonPath.length-1].includes(".t.")){ if (jsonPath[jsonPath.length-1].includes(".t.")){
options.transparent = true; options.transparent = true;
@ -792,13 +794,13 @@
options.transparent = false; options.transparent = false;
forcedTransparentColor = forcedTransparentColorMatch[1]; forcedTransparentColor = forcedTransparentColorMatch[1];
} }
console.log("image has transparency", options.transparent); console.log("image has transparency", options.transparent);
console.log("image has forced transparent color", forcedTransparentColor); console.log("image has forced transparent color", forcedTransparentColor);
jsonPath[jsonPath.length-1] = jsonPath[jsonPath.length-1].replace(/([^.]*)\..*/, "$1"); jsonPath[jsonPath.length-1] = jsonPath[jsonPath.length-1].replace(/([^.]*)\..*/, "$1");
console.log("Loaded image has json path", jsonPath); console.log("Loaded image has json path", jsonPath);
var canvas = document.getElementById("canvas") var canvas = document.getElementById("canvas")
canvas.width = this.width*2; canvas.width = this.width*2;
canvas.height = this.height; canvas.height = this.height;
@ -819,7 +821,7 @@
imgstr = imageconverter.RGBAtoString(rgba, options); imgstr = imageconverter.RGBAtoString(rgba, options);
var outputImageData = new ImageData(options.rgbaOut, options.width, options.height); var outputImageData = new ImageData(options.rgbaOut, options.width, options.height);
ctx.putImageData(outputImageData,this.width,0); ctx.putImageData(outputImageData,this.width,0);
imgstr = convertJsToJson(imgstr); imgstr = convertJsToJson(imgstr);
// checkerboard for transparency on original image // checkerboard for transparency on original image
@ -827,9 +829,9 @@
imageconverter.RGBAtoCheckerboard(imageData.data, {width:this.width,height:this.height}); imageconverter.RGBAtoCheckerboard(imageData.data, {width:this.width,height:this.height});
ctx.putImageData(imageData,0,0); ctx.putImageData(imageData,0,0);
var currentElement = resultJson; var currentElement = resultJson;
for (var i = 0; i < jsonPath.length; i++){ for (var i = 0; i < jsonPath.length; i++){
if (i == jsonPath.length - 1){ if (i == jsonPath.length - 1){
var resultingObject = JSON.parse(imgstr); var resultingObject = JSON.parse(imgstr);
@ -841,18 +843,18 @@
currentElement = currentElement[jsonPath[i]]; currentElement = currentElement[jsonPath[i]];
} }
} }
handledFiles++; handledFiles++;
console.log("Expected:", expectedFiles, " handled:", handledFiles); console.log("Expected:", expectedFiles, " handled:", handledFiles);
if (handledFiles == expectedFiles){ if (handledFiles == expectedFiles){
if (!isNativeFormat()) { if (!isNativeFormat()) {
performFileChanges().then(()=>{ performFileChanges().then(()=>{
postProcess(); postProcess();
rootZip.file("face.json", JSON.stringify(faceJson, null, 2)); rootZip.file("face.json", JSON.stringify(faceJson, null, 2));
rootZip.file("info.json", JSON.stringify(infoJson, null, 2)); rootZip.file("info.json", JSON.stringify(infoJson, null, 2));
document.getElementById('btnSave').disabled = false; document.getElementById('btnSave').disabled = false;
document.getElementById('btnSaveFace').disabled = false; document.getElementById('btnSaveFace').disabled = false;
document.getElementById('btnSaveZip').disabled = false; document.getElementById('btnSaveZip').disabled = false;
@ -860,21 +862,21 @@
}); });
} else { } else {
postProcess(); postProcess();
document.getElementById('btnSave').disabled = false; document.getElementById('btnSave').disabled = false;
document.getElementById('btnSaveFace').disabled = false; document.getElementById('btnSaveFace').disabled = false;
document.getElementById('btnUpload').disabled = false; document.getElementById('btnUpload').disabled = false;
} }
} }
} }
function handleWatchFace(infoFile, faceFile, resourceFiles){ function handleWatchFace(infoFile, faceFile, resourceFiles){
if (isNativeFormat()){ if (isNativeFormat()){
var reader = new FileReader(); var reader = new FileReader();
reader.path = infoFile.webkitRelativePath; reader.path = infoFile.webkitRelativePath;
reader.onload = function(event) { reader.onload = function(event) {
infoJson = JSON.parse(reader.result); infoJson = JSON.parse(reader.result);
handleFaceJson(faceFile, resourceFiles); handleFaceJson(faceFile, resourceFiles);
}; };
reader.readAsText(infoFile); reader.readAsText(infoFile);
@ -883,18 +885,18 @@
handleFaceJson(faceFile, resourceFiles); handleFaceJson(faceFile, resourceFiles);
} }
} }
function handleFaceJson(faceFile, resourceFiles){ function handleFaceJson(faceFile, resourceFiles){
var reader = new FileReader(); var reader = new FileReader();
reader.path = faceFile.webkitRelativePath; reader.path = faceFile.webkitRelativePath;
reader.onload = function(event) { reader.onload = function(event) {
faceJson = parseFaceJson(reader.result); faceJson = parseFaceJson(reader.result);
handleResourceFiles(resourceFiles); handleResourceFiles(resourceFiles);
}; };
reader.readAsText(faceFile); reader.readAsText(faceFile);
} }
function handleResourceFiles(files){ function handleResourceFiles(files){
for (var current of files){ for (var current of files){
console.log('Handle resource file ', current); console.log('Handle resource file ', current);
@ -917,25 +919,25 @@
reader.readAsDataURL(current); reader.readAsDataURL(current);
} }
} }
function handleFileSelect(event) { function handleFileSelect(event) {
handledFiles = 0; handledFiles = 0;
expectedFiles = undefined; expectedFiles = undefined;
document.getElementById('btnSave').disabled = true; document.getElementById('btnSave').disabled = true;
document.getElementById('btnSaveZip').disabled = true; document.getElementById('btnSaveZip').disabled = true;
document.getElementById('btnSaveFace').disabled = true; document.getElementById('btnSaveFace').disabled = true;
document.getElementById('btnUpload').disabled = true; document.getElementById('btnUpload').disabled = true;
console.log("File select event", event); console.log("File select event", event);
if (event.target.files.length == 0) return; if (event.target.files.length == 0) return;
result = ""; result = "";
resultJson= {}; resultJson= {};
var resourceFiles = []; var resourceFiles = [];
var faceFile; var faceFile;
var infoFile; var infoFile;
for (var current of event.target.files){ for (var current of event.target.files){
console.log('Handle file ', current); console.log('Handle file ', current);
if (isNativeFormat()){ if (isNativeFormat()){
@ -970,10 +972,10 @@
} }
} }
handleWatchFace(infoFile, faceFile, resourceFiles); handleWatchFace(infoFile, faceFile, resourceFiles);
}; };
document.getElementById('fileLoader').addEventListener('change', handleFileSelect, false); document.getElementById('fileLoader').addEventListener('change', handleFileSelect, false);
function moveData(json){ function moveData(json){
console.log("MoveData for", json); console.log("MoveData for", json);
for (var k in json){ for (var k in json){
@ -997,11 +999,11 @@
} }
} }
} }
document.getElementById("timeoutwrap").addEventListener("click", function() { document.getElementById("timeoutwrap").addEventListener("click", function() {
document.getElementById("forceOrigPlane").disabled = !document.getElementById("timeoutwrap").checked; document.getElementById("forceOrigPlane").disabled = !document.getElementById("timeoutwrap").checked;
}); });
document.getElementById("btnSave").addEventListener("click", function() { document.getElementById("btnSave").addEventListener("click", function() {
var h = document.createElement('a'); var h = document.createElement('a');
h.href = 'data:text/json;charset=utf-8,' + encodeURI(JSON.stringify(resultJson)); h.href = 'data:text/json;charset=utf-8,' + encodeURI(JSON.stringify(resultJson));
@ -1010,25 +1012,48 @@
h.click(); h.click();
}); });
document.getElementById("btnUpload").addEventListener("click", function() { document.getElementById("btnUpload").addEventListener("click", function() {
console.log("Fetching app");
fetch('app.js').then((r) => {
console.log("Got response", r);
return r.text();
}
).then((imageclockSrc) => {
console.log("Got src", imageclockSrc)
if (!document.getElementById('separateFiles').checked){
if (precompiledJs.length > 0){
const replacementString = 'eval(require("Storage").read("imageclock.draw.js"))';
console.log("Can replace:", imageclockSrc.includes(replacementString));
imageclockSrc = imageclockSrc.replace(replacementString, precompiledJs);
}
imageclockSrc = imageclockSrc.replace('require("Storage").readJSON("imageclock.face.json")', JSON.stringify(faceJson));
imageclockSrc = imageclockSrc.replace('require("Storage").readJSON("imageclock.resources.json")', JSON.stringify(resultJson));
}
var appDef = { var appDef = {
id : "imageclock", id : "imageclock",
storage:[ storage:[
{name:"imageclock.app.js", url:"app.js"},
{name:"imageclock.resources.json", content: JSON.stringify(resultJson)},
{name:"imageclock.img", url:"app-icon.js", evaluate:true}, {name:"imageclock.img", url:"app-icon.js", evaluate:true},
] ]
}; };
if (document.getElementById('separateFiles').checked){
appDef.storage.push({name:"imageclock.app.js", url:"app.js"});
if (precompiledJs.length > 0){
appDef.storage.push({name:"imageclock.draw.js", content:precompiledJs});
}
appDef.storage.push({name:"imageclock.face.json", content: JSON.stringify(faceJson)});
appDef.storage.push({name:"imageclock.resources.json", content: JSON.stringify(resultJson)});
} else {
appDef.storage.push({name:"imageclock.app.js", url:"pleaseminifycontent.js", content:imageclockSrc});
}
if (resourceDataString.length > 0){ if (resourceDataString.length > 0){
appDef.storage.push({name:"imageclock.resources.data", content: resourceDataString}); appDef.storage.push({name:"imageclock.resources.data", content: resourceDataString});
} }
appDef.storage.push({name:"imageclock.draw.js", content: precompiledJs.length > 0 ? precompiledJs : "//empty"});
appDef.storage.push({name:"imageclock.face.json", content: JSON.stringify(faceJson)});
console.log("Uploading app:", appDef); console.log("Uploading app:", appDef);
sendCustomizedApp(appDef); sendCustomizedApp(appDef);
});
}); });
function handleZipSelect(evt) { function handleZipSelect(evt) {
@ -1040,18 +1065,18 @@
document.getElementById('btnSaveZip').disabled = true; document.getElementById('btnSaveZip').disabled = true;
document.getElementById('btnUpload').disabled = true; document.getElementById('btnUpload').disabled = true;
JSZip.loadAsync(f).then(function(zip) { JSZip.loadAsync(f).then(function(zip) {
console.log("Zip loaded", zip); console.log("Zip loaded", zip);
result = ""; result = "";
resultJson= {}; resultJson= {};
var resourceFiles = []; var resourceFiles = [];
var promise = zip.file("face.json").async("string").then((data)=>{ var promise = zip.file("face.json").async("string").then((data)=>{
console.log("face.json data", data); console.log("face.json data", data);
faceJson = parseFaceJson(data); faceJson = parseFaceJson(data);
}); });
if (isNativeFormat()){ if (isNativeFormat()){
promise = promise.then(zip.file("info.json").async("string").then((data)=>{ promise = promise.then(zip.file("info.json").async("string").then((data)=>{
console.log("info.json data", data); console.log("info.json data", data);
@ -1062,12 +1087,12 @@
"color": "3bit", "color": "3bit",
"transparent": true "transparent": true
}; };
} }
zip.folder("resources").forEach(function (relativePath, file){ zip.folder("resources").forEach(function (relativePath, file){
console.log("iterating over", relativePath); console.log("iterating over", relativePath);
if (!file.dir){ if (!file.dir){
expectedFiles++; expectedFiles++;
promise = promise.then(file.async("blob").then(function (blob) { promise = promise.then(file.async("blob").then(function (blob) {
@ -1083,10 +1108,10 @@
reader.readAsDataURL(blob); reader.readAsDataURL(blob);
})); }));
} }
}); });
}, function (e) { }, function (e) {
console.log("Error reading " + f.name + ": " + e.message); console.log("Error reading " + f.name + ": " + e.message);
}); });
@ -1095,11 +1120,11 @@
console.log("Zip select event", evt); console.log("Zip select event", evt);
var files = evt.target.files; var files = evt.target.files;
if (files.length > 1){ if (files.length > 1){
alert("Only one file allowed"); alert("Only one file allowed");
} }
handleFile(files[0]); handleFile(files[0]);
} }
@ -1113,7 +1138,7 @@
}); });
} }
document.getElementById("btnSaveFace").addEventListener("click", function() { document.getElementById("btnSaveFace").addEventListener("click", function() {
var h = document.createElement('a'); var h = document.createElement('a');
h.href = 'data:text/json;charset=utf-8,' + encodeURI(JSON.stringify(faceJson)); h.href = 'data:text/json;charset=utf-8,' + encodeURI(JSON.stringify(faceJson));
@ -1121,14 +1146,14 @@
h.download = "face.json"; h.download = "face.json";
h.click(); h.click();
}); });
document.getElementById('zipLoader').addEventListener('change', handleZipSelect, false); document.getElementById('zipLoader').addEventListener('change', handleZipSelect, false);
document.getElementById('btnSaveZip').addEventListener('click', handleZipExport, false); document.getElementById('btnSaveZip').addEventListener('click', handleZipExport, false);
document.getElementById('btnSave').disabled = true; document.getElementById('btnSave').disabled = true;
document.getElementById('btnSaveFace').disabled = true; document.getElementById('btnSaveFace').disabled = true;
document.getElementById('btnSaveZip').disabled = true; document.getElementById('btnSaveZip').disabled = true;
document.getElementById('btnUpload').disabled = true; document.getElementById('btnUpload').disabled = true;
</script> </script>
</body> </body>

View File

@ -2,7 +2,7 @@
"id": "imageclock", "id": "imageclock",
"name": "Imageclock", "name": "Imageclock",
"shortName": "Imageclock", "shortName": "Imageclock",
"version": "0.12", "version": "0.13",
"type": "clock", "type": "clock",
"description": "BETA!!! File formats still subject to change --- This app is a highly customizable watchface. To use it, you need to select a watchface. You can build the watchfaces yourself without programming anything. All you need to do is write some json and create image files.", "description": "BETA!!! File formats still subject to change --- This app is a highly customizable watchface. To use it, you need to select a watchface. You can build the watchfaces yourself without programming anything. All you need to do is write some json and create image files.",
"icon": "app.png", "icon": "app.png",

View File

@ -10,7 +10,7 @@
</div> </div>
<script src="../../core/lib/customize.js"></script> <script src="../../core/lib/customize.js"></script>
<script src="../../core/lib/imageconverter.js"></script> <script src="../../webtools/imageconverter.js"></script>
<script> <script>
var faces = []; var faces = [];

View File

@ -8,3 +8,4 @@
0.08: Added more app identifiers, added 'cannot display' in case a message goes empty because of replacements 0.08: Added more app identifiers, added 'cannot display' in case a message goes empty because of replacements
0.09: Enable 'ams' on new firmwares (ams/ancs can now be enabled individually) (fix #1365) 0.09: Enable 'ams' on new firmwares (ams/ancs can now be enabled individually) (fix #1365)
0.10: Added more bundleIds 0.10: Added more bundleIds
0.11: Added letters with caron to unicodeRemap, to properly display messages in Czech language

View File

@ -127,18 +127,34 @@ E.on('notify',msg=>{
'261':"a", '261':"a",
'262':"C", '262':"C",
'263':"c", '263':"c",
'268':"C",
'269':"c",
'270':"D",
'271':"d",
'280':"E", '280':"E",
'281':"e", '281':"e",
'282':"E",
'283':"e",
'321':"L", '321':"L",
'322':"l", '322':"l",
'323':"N", '323':"N",
'324':"n", '324':"n",
'327':"N",
'328':"n",
'344':"R",
'345':"r",
'346':"S", '346':"S",
'347':"s", '347':"s",
'352':"S",
'353':"s",
'356':"T",
'357':"t",
'377':"Z", '377':"Z",
'378':"z", '378':"z",
'379':"Z", '379':"Z",
'380':"z", '380':"z",
'381':"Z",
'382':"z",
}; };
var replacer = ""; //(n)=>print('Unknown unicode '+n.toString(16)); var replacer = ""; //(n)=>print('Unknown unicode '+n.toString(16));
//if (appNames[msg.appId]) msg.a //if (appNames[msg.appId]) msg.a

View File

@ -1,7 +1,7 @@
{ {
"id": "ios", "id": "ios",
"name": "iOS Integration", "name": "iOS Integration",
"version": "0.10", "version": "0.11",
"description": "Display notifications/music/etc from iOS devices", "description": "Display notifications/music/etc from iOS devices",
"icon": "app.png", "icon": "app.png",
"tags": "tool,system,ios,apple,messages,notifications", "tags": "tool,system,ios,apple,messages,notifications",

View File

@ -3,3 +3,4 @@
0.03: Incorporated improvements from Peer David for accuracy, fix dark mode, widgets run in background 0.03: Incorporated improvements from Peer David for accuracy, fix dark mode, widgets run in background
0.04: Changed clock to use 12/24 hour format based on locale 0.04: Changed clock to use 12/24 hour format based on locale
0.05: Tell clock widgets to hide. 0.05: Tell clock widgets to hide.
0.06: Widgets can now be made visible by swiping down (#2196)

View File

@ -20,7 +20,7 @@ function queueDraw() {
function draw() { function draw() {
queueDraw(); queueDraw();
// Fix theme to "light" // Fix theme to "light"
g.setTheme({bg:"#fff", fg:"#000", dark:false}).clear(); g.setTheme({bg:"#fff", fg:"#000", dark:false}).clear();
g.reset(); g.reset();
@ -41,9 +41,6 @@ function draw() {
yy = ("0"+((new Date()).getFullYear())).substr(-2); yy = ("0"+((new Date()).getFullYear())).substr(-2);
g.setFontCustom(font, 48, 8, 521); g.setFontCustom(font, 48, 8, 521);
g.drawString(dd + ':' + mo + ':' + yy, 88, 120, true); g.drawString(dd + ':' + mo + ':' + yy, 88, 120, true);
// Hide widgets
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
} }
@ -61,4 +58,5 @@ Bangle.setUI("clock");
// Load widgets but hide them // Load widgets but hide them
Bangle.loadWidgets(); Bangle.loadWidgets();
require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe
draw(); draw();

View File

@ -2,7 +2,7 @@
"name": "MacWatch2", "name": "MacWatch2",
"shortName":"MacWatch2", "shortName":"MacWatch2",
"icon": "app.png", "icon": "app.png",
"version":"0.05", "version":"0.06",
"description": "Classic Mac Finder clock", "description": "Classic Mac Finder clock",
"type": "clock", "type": "clock",
"tags": "clock", "tags": "clock",

View File

@ -0,0 +1 @@
0.01: Moved message icons from messages into standalone library

BIN
apps/messageicons/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

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