Merge branch 'espruino:master' into master
|
@ -3,4 +3,5 @@ apps/banglerun/rollup.config.js
|
|||
apps/schoolCalendar/fullcalendar/main.js
|
||||
apps/authentiwatch/qr_packed.js
|
||||
apps/qrcode/qr-scanner.umd.min.js
|
||||
apps/gipy/pkg/gpconv.js
|
||||
*.test.js
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
[submodule "EspruinoAppLoaderCore"]
|
||||
path = core
|
||||
url = https://github.com/espruino/EspruinoAppLoaderCore.git
|
||||
[submodule "webtools"]
|
||||
path = webtools
|
||||
url = https://github.com/espruino/EspruinoWebTools.git
|
||||
|
|
19
README.md
|
@ -255,8 +255,11 @@ and which gives information about the app for the Launcher.
|
|||
// 'app' - an application
|
||||
// 'clock' - a clock - required for clocks to automatically start
|
||||
// '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'
|
||||
// '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
|
||||
// 'launch' - replacement 'Launcher'
|
||||
// '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
|
||||
// (a version of 'locale' is included in the firmware)
|
||||
"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
|
||||
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on (see "type" above)
|
||||
"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'
|
||||
"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
|
||||
// that contains more information about this app (usage, etc)
|
||||
// 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
|
||||
|
||||
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
|
||||
then you can use it from your app as normal.
|
||||
|
||||
|
|
|
@ -170,10 +170,10 @@
|
|||
</div>
|
||||
</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/espruinotools.js"></script>
|
||||
<script src="core/lib/heatshrink.js"></script>
|
||||
<script src="core/js/utils.js"></script>
|
||||
<script src="loader.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js"></script> <!-- for backup.js -->
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
0.01: AdvCasio first version
|
||||
0.02: Remove un-needed fonts to improve memory usage
|
||||
0.03: Tell clock widgets to hide.
|
||||
0.04: Swipe down to see widgets, step counter now just uses getHealthStatus
|
||||
|
|
|
@ -1,304 +1,160 @@
|
|||
const storage = require('Storage');
|
||||
|
||||
require("Font6x12").add(Graphics);
|
||||
require("Font8x12").add(Graphics);
|
||||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
|
||||
function bigThenSmall(big, small, x, y) {
|
||||
g.setFont("7x11Numeric7Seg", 2);
|
||||
g.drawString(big, x, y);
|
||||
x += g.stringWidth(big);
|
||||
g.setFont("8x12");
|
||||
g.drawString(small, x, y);
|
||||
g.setFont("7x11Numeric7Seg", 2);
|
||||
g.drawString(big, x, y);
|
||||
x += g.stringWidth(big);
|
||||
g.setFont("8x12");
|
||||
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() {
|
||||
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
|
||||
let rocketInterval;
|
||||
var drawTimeout;
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
|
||||
function clearIntervals() {
|
||||
if (rocketInterval) clearInterval(rocketInterval);
|
||||
rocketInterval = undefined;
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
if (rocketInterval) clearInterval(rocketInterval);
|
||||
rocketInterval = undefined;
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
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() {
|
||||
g.setFont("7x11Numeric7Seg", 3);
|
||||
g.clearRect(80, 57, 170, 96);
|
||||
g.setColor(255, 255, 255);
|
||||
var l = 77;
|
||||
var t = 57;
|
||||
var w = 170;
|
||||
var h = 116;
|
||||
g.drawRect(l, t, w, h);
|
||||
g.fillRect(l, t, w, h);
|
||||
g.setColor(0, 0, 0);
|
||||
g.drawString(require("locale").time(new Date(), 1), 76, 60);
|
||||
|
||||
// day
|
||||
//g.setFont("8x12", 1);
|
||||
//g.setFont("9x18", 1);
|
||||
//g.drawString(require("locale").dow(new Date(), 2).toUpperCase(), 25, 136);
|
||||
g.setFont("8x12", 2);
|
||||
g.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);
|
||||
g.setFont("7x11Numeric7Seg", 3);
|
||||
g.clearRect(80, 57, 170, 96);
|
||||
g.setColor(0, 255, 255);
|
||||
g.drawRect(80, 57, 170, 96);
|
||||
g.fillRect(80, 57, 170, 96);
|
||||
g.setColor(0, 0, 0);
|
||||
g.drawString(require("locale").time(new Date(), 1), 70, 60);
|
||||
g.setFont("8x12", 2);
|
||||
g.drawString(require("locale").dow(new Date(), 2).toUpperCase(), 18, 130);
|
||||
g.setFont("8x12");
|
||||
g.drawString(require("locale").month(new Date(), 2).toUpperCase(), 80, 126);
|
||||
g.setFont("8x12", 2);
|
||||
const time = new Date().getDate();
|
||||
g.drawString(time < 10 ? "0" + time : time, 78, 137);
|
||||
}
|
||||
|
||||
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() {
|
||||
var steps = 0;
|
||||
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);
|
||||
return steps + "k";
|
||||
var steps = Bangle.getHealthStatus("day").steps;
|
||||
steps = Math.round(steps/1000);
|
||||
return steps + "k";
|
||||
}
|
||||
|
||||
|
||||
|
||||
function draw() {
|
||||
queueDraw();
|
||||
|
||||
queueDraw();
|
||||
g.clear(1);
|
||||
g.setColor(0, 255, 255);
|
||||
g.fillRect(0, 0, g.getWidth(), g.getHeight());
|
||||
let background = getBackgroundImage();
|
||||
g.drawImage(background, 0, 0, { scale: 1 });
|
||||
g.setColor(0, 0, 0);
|
||||
g.setFont("6x12");
|
||||
g.drawString("Launching Process", 30, 20);
|
||||
g.setFont("8x12");
|
||||
g.drawString("ACTIVATE", 40, 35);
|
||||
|
||||
g.reset();
|
||||
g.clear();
|
||||
g.setColor(255, 255, 255);
|
||||
g.fillRect(0, 0, g.getWidth(), g.getHeight());
|
||||
let background = getBackgroundImage();
|
||||
g.drawImage(background, 0, 0, { scale: 1 });
|
||||
g.setFontAlign(0,-1);
|
||||
g.setFont("8x12", 2);
|
||||
g.drawString(getTemperature(), 155, 132);
|
||||
g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), 109, 98);
|
||||
g.drawString(getSteps(), 158, 98);
|
||||
|
||||
g.setFontAlign(-1,-1);
|
||||
drawClock();
|
||||
drawRocket();
|
||||
drawBattery();
|
||||
|
||||
g.setColor(0, 0, 0);
|
||||
if(dataJson && dataJson.weather) drawWeather(dataJson.weather);
|
||||
if(dataJson && dataJson.tasks) drawTasks(dataJson.tasks);
|
||||
|
||||
|
||||
g.setFontAlign(0,-1);
|
||||
g.setFont("8x12", 2);
|
||||
|
||||
drawSteps();
|
||||
g.setFontAlign(-1,-1);
|
||||
drawClock();
|
||||
drawBattery();
|
||||
drawTimer();
|
||||
// Hide widgets
|
||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
||||
// Hide widgets
|
||||
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) => {
|
||||
if (on) {
|
||||
draw();
|
||||
} else {
|
||||
canTouch = false;
|
||||
clearIntervals();
|
||||
}
|
||||
if (on) {
|
||||
draw();
|
||||
} else {
|
||||
clearIntervals();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Bangle.on("lock", (locked) => {
|
||||
clearIntervals();
|
||||
draw();
|
||||
if (!locked) {
|
||||
canTouch = true;
|
||||
} else {
|
||||
canTouch = false;
|
||||
}
|
||||
clearIntervals();
|
||||
draw();
|
||||
if (!locked) {
|
||||
rocketInterval = setInterval(drawRocket, rocketSpeed);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Bangle.setUI("clock");
|
||||
|
||||
// Load widgets, but don't show them
|
||||
Bangle.loadWidgets();
|
||||
|
||||
g.reset();
|
||||
g.clear();
|
||||
require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe
|
||||
g.clear(1);
|
||||
draw();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "advcasio",
|
||||
"name": "Advanced Casio Clock",
|
||||
"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.",
|
||||
"icon": "app.png",
|
||||
"tags": "clock",
|
||||
|
|
|
@ -5,3 +5,5 @@
|
|||
0.05: Displaying calendar colour and name
|
||||
0.06: Added clkinfo for clocks.
|
||||
0.07: Clkinfo improvements.
|
||||
0.08: Fix error in clkinfo (didn't require Storage & locale)
|
||||
Fix clkinfo icon
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
(function() {
|
||||
var agendaItems = {
|
||||
name: "Agenda",
|
||||
img: atob("GBiBAf////////85z/AAAPAAAPgAAP////AAAPAAAPAAAPAAAOAAAeAAAeAAAcAAA8AAAoAABgAADP//+P//8PAAAPAAAPgAAf///w=="),
|
||||
items: []
|
||||
};
|
||||
var agendaItems = {
|
||||
name: "Agenda",
|
||||
img: atob("GBiBAAAAAAAAAADGMA///w///wf//wAAAA///w///w///w///x///h///h///j///D///X//+f//8wAABwAADw///w///wf//gAAAA=="),
|
||||
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();
|
||||
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) => {
|
||||
|
||||
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);
|
||||
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) : "";
|
||||
agendaItems.items.push({
|
||||
name: "Agenda "+i,
|
||||
get: () => ({ text: title + "\n" + dateStr, img: null}),
|
||||
show: function() { agendaItems.items[i].emit("redraw"); },
|
||||
hide: function () {}
|
||||
});
|
||||
});
|
||||
|
||||
agendaItems.items.push({
|
||||
name: null,
|
||||
get: () => ({ text: title + "\n" + dateStr, img: null}),
|
||||
show: function() { agendaItems.items[i].emit("redraw"); },
|
||||
hide: function () {}
|
||||
});
|
||||
});
|
||||
|
||||
return agendaItems;
|
||||
return agendaItems;
|
||||
})
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"id": "agenda",
|
||||
"name": "Agenda",
|
||||
"version": "0.07",
|
||||
"version": "0.08",
|
||||
"description": "Simple agenda",
|
||||
"icon": "agenda.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"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
|
|
|
@ -14,3 +14,4 @@
|
|||
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.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
|
||||
|
|
|
@ -91,10 +91,6 @@
|
|||
sched.reload();
|
||||
},
|
||||
//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)
|
||||
"calendar" : function() {
|
||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||
|
@ -109,7 +105,7 @@
|
|||
"calendar-" : function() {
|
||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||
//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);
|
||||
require("Storage").writeJSON("android.calendar.json", cal);
|
||||
},
|
||||
|
@ -170,7 +166,10 @@
|
|||
|
||||
// Battery monitor
|
||||
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);
|
||||
if (!settings.keep)
|
||||
NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "android",
|
||||
"name": "Android Integration",
|
||||
"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.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,messages,notifications,gadgetbridge",
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"version": "0.08",
|
||||
"description": "A clock based on the portal series",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
|
|
After Width: | Height: | Size: 5.4 KiB |
|
@ -59,3 +59,5 @@
|
|||
Add patch to ensure that compass heading is corrected on pre-2v15.68 firmware
|
||||
Ensure clock is only fast-loaded if it doesn't contain widgets
|
||||
0.52: Ensure heading patch for pre-2v15.68 firmware applies to getCompass
|
||||
0.53: Add polyfills for pre-2v15.135 firmware for Bangle.load and Bangle.showClock
|
||||
0.54: Fix for invalid version comparison in polyfill
|
||||
|
|
|
@ -4,6 +4,7 @@ of the time. */
|
|||
E.showMessage(/*LANG*/"Updating boot0...");
|
||||
var s = require('Storage').readJSON('setting.json',1)||{};
|
||||
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 = "";
|
||||
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);
|
||||
|
@ -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.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation
|
||||
// ================================================== FIXING OLDER FIRMWARES
|
||||
// 2v15.68 and before had compass heading inverted.
|
||||
if (process.version.replace("v","")<215.68)
|
||||
if (FWVERSION<215.068) // 2v15.68 and before had compass heading inverted.
|
||||
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);`;
|
||||
|
||||
// deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||
// this is a polyfill without fastloading capability
|
||||
delete Bangle.showClock;
|
||||
if (!Bangle.showClock) boot += `Bangle.showClock = ()=>{load(".bootcde")};\n`;
|
||||
delete Bangle.load;
|
||||
if (!Bangle.load) boot += `Bangle.load = load;\n`;
|
||||
|
||||
// ================================================== BOOT.JS
|
||||
// Append *.boot.js files
|
||||
// These could change bleServices/bleServiceOptions if needed
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "boot",
|
||||
"name": "Bootloader",
|
||||
"version": "0.52",
|
||||
"version": "0.54",
|
||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||
"icon": "bootloader.png",
|
||||
"type": "bootloader",
|
||||
|
|
|
@ -34,3 +34,9 @@
|
|||
Prevent mixing of BT and internal HRM events if both are enabled
|
||||
Always use a grace period (default 0 ms) to decouple some connection steps
|
||||
Device not found errors now utilize increasing timeouts
|
||||
0.15: Fix recording internal sensor
|
||||
Handle fallback to internal sensor consistently if BT bpm is 0
|
||||
Power internal sensor down if not needed for fallback
|
||||
0.16: Set powerdownRequested correctly on BTHRM power on
|
||||
Additional logging on errors
|
||||
Add debug option for disabling active scanning
|
||||
|
|
|
@ -17,5 +17,6 @@
|
|||
"gracePeriodConnect": 0,
|
||||
"gracePeriodService": 0,
|
||||
"gracePeriodRequest": 0,
|
||||
"bonding": false
|
||||
"bonding": false,
|
||||
"active": true
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ exports.enable = () => {
|
|||
var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit
|
||||
supportedCharacteristics["0x2a37"].active = bpm > 0;
|
||||
log("BTHRM BPM " + supportedCharacteristics["0x2a37"].active);
|
||||
if (supportedCharacteristics["0x2a37"].active) stopFallback();
|
||||
switchFallback();
|
||||
if (bpmTimeout) clearTimeout(bpmTimeout);
|
||||
bpmTimeout = setTimeout(()=>{
|
||||
bpmTimeout = undefined;
|
||||
|
@ -148,14 +148,14 @@ exports.enable = () => {
|
|||
battery = lastReceivedData["0x180f"]["0x2a19"];
|
||||
}
|
||||
|
||||
if (settings.replace){
|
||||
if (settings.replace && bpm > 0){
|
||||
var repEvent = {
|
||||
bpm: bpm,
|
||||
confidence: (sensorContact || sensorContact === undefined)? 100 : 0,
|
||||
src: "bthrm"
|
||||
};
|
||||
|
||||
log("Emitting aggregated HRM", repEvent);
|
||||
log("Emitting HRM_R(bt)", repEvent);
|
||||
Bangle.emit("HRM_R", repEvent);
|
||||
}
|
||||
|
||||
|
@ -255,7 +255,7 @@ exports.enable = () => {
|
|||
var retry = function() {
|
||||
log("Retry");
|
||||
|
||||
if (!currentRetryTimeout){
|
||||
if (!currentRetryTimeout && !powerdownRequested){
|
||||
|
||||
var clampedTime = retryTime < 100 ? 100 : retryTime;
|
||||
|
||||
|
@ -287,7 +287,7 @@ exports.enable = () => {
|
|||
retryTimeResetNeeded &= reason != "No device found matching filters";
|
||||
clearRetryTimeout(retryTimeResetNeeded);
|
||||
supportedCharacteristics["0x2a37"].active = false;
|
||||
startFallback();
|
||||
if (!powerdownRequested) startFallback();
|
||||
blockInit = false;
|
||||
if (settings.warnDisconnect && !buzzing){
|
||||
buzzing = true;
|
||||
|
@ -369,7 +369,7 @@ exports.enable = () => {
|
|||
|
||||
var initBt = function () {
|
||||
log("initBt with blockInit: " + blockInit);
|
||||
if (blockInit){
|
||||
if (blockInit && !powerdownRequested){
|
||||
retry();
|
||||
return;
|
||||
}
|
||||
|
@ -387,7 +387,13 @@ exports.enable = () => {
|
|||
return;
|
||||
}
|
||||
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){
|
||||
log("Add " + settings.gracePeriodRequest + "ms grace period after request");
|
||||
|
@ -454,7 +460,7 @@ exports.enable = () => {
|
|||
} else {
|
||||
log("Start bonding");
|
||||
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) {
|
||||
// Do app power handling
|
||||
if (!app) app="?";
|
||||
|
@ -518,11 +526,14 @@ exports.enable = () => {
|
|||
isOn = Bangle._PWR.BTHRM.length;
|
||||
// so now we know if we're really on
|
||||
if (isOn) {
|
||||
powerdownRequested = false;
|
||||
switchFallback();
|
||||
if (!Bangle.isBTHRMConnected()) initBt();
|
||||
} else { // not on
|
||||
log("Power off for " + app);
|
||||
powerdownRequested = true;
|
||||
clearRetryTimeout(true);
|
||||
stopFallback();
|
||||
if (gatt) {
|
||||
if (gatt.connected){
|
||||
log("Disconnect with gatt", gatt);
|
||||
|
@ -544,9 +555,11 @@ exports.enable = () => {
|
|||
// register a listener for original HRM events and emit as HRM_int
|
||||
Bangle.on("HRM", (e) => {
|
||||
e.modified = true;
|
||||
log("Emitting HRM_int", e);
|
||||
Bangle.emit("HRM_int", e);
|
||||
if (fallbackActive){
|
||||
// if fallback to internal HRM is active, emit as HRM_R to which everyone listens
|
||||
log("Emitting HRM_R(int)", e);
|
||||
Bangle.emit("HRM_R", e);
|
||||
}
|
||||
});
|
||||
|
@ -572,6 +585,11 @@ exports.enable = () => {
|
|||
log("setHRMPower for " + app + ": " + (isOn?"on":"off"));
|
||||
if (settings.enabled){
|
||||
Bangle.setBTHRMPower(isOn, app);
|
||||
if (Bangle._PWR && Bangle._PWR.HRM && Object.keys(Bangle._PWR.HRM).length == 0) {
|
||||
Bangle._PWR.BTHRM = [];
|
||||
Bangle.setBTHRMPower(0);
|
||||
if (!isOn) stopFallback();
|
||||
}
|
||||
}
|
||||
if ((settings.enabled && !settings.replace) || !settings.enabled){
|
||||
Bangle.origSetHRMPower(isOn, app);
|
||||
|
@ -627,7 +645,11 @@ exports.enable = () => {
|
|||
E.on("kill", ()=>{
|
||||
if (gatt && gatt.connected){
|
||||
log("Got killed, trying to disconnect");
|
||||
gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect on kill", e));
|
||||
try {
|
||||
gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect promise on kill", e));
|
||||
} catch (e) {
|
||||
log("Error during disconnnect on kill", e)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "bthrm",
|
||||
"name": "Bluetooth Heart Rate Monitor",
|
||||
"shortName": "BT HRM",
|
||||
"version": "0.14",
|
||||
"version": "0.16",
|
||||
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -38,35 +38,32 @@
|
|||
recorders.hrmint = function() {
|
||||
var active = false;
|
||||
var bpmTimeout;
|
||||
var bpm = "", bpmConfidence = "", src="";
|
||||
var bpm = "", bpmConfidence = "";
|
||||
function onHRM(h) {
|
||||
bpmConfidence = h.confidence;
|
||||
bpm = h.bpm;
|
||||
srv = h.src;
|
||||
if (h.bpm > 0){
|
||||
active = true;
|
||||
print("active" + h.bpm);
|
||||
if (bpmTimeout) clearTimeout(bpmTimeout);
|
||||
bpmTimeout = setTimeout(()=>{
|
||||
print("inactive");
|
||||
active = false;
|
||||
},3000);
|
||||
}
|
||||
}
|
||||
return {
|
||||
name : "HR int",
|
||||
fields : ["Heartrate", "Confidence"],
|
||||
fields : ["Int Heartrate", "Int Confidence"],
|
||||
getValues : () => {
|
||||
var r = [bpm,bpmConfidence,src];
|
||||
bpm = ""; bpmConfidence = ""; src="";
|
||||
var r = [bpm,bpmConfidence];
|
||||
bpm = ""; bpmConfidence = "";
|
||||
return r;
|
||||
},
|
||||
start : () => {
|
||||
Bangle.origOn('HRM', onHRM);
|
||||
Bangle.on('HRM_int', onHRM);
|
||||
if (Bangle.origSetHRMPower) Bangle.origSetHRMPower(1,"recorder");
|
||||
},
|
||||
stop : () => {
|
||||
Bangle.removeListener('HRM', onHRM);
|
||||
Bangle.removeListener('HRM_int', onHRM);
|
||||
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)
|
||||
|
|
|
@ -102,6 +102,12 @@
|
|||
writeSettings("bonding",v);
|
||||
}
|
||||
},
|
||||
'Use active scanning': {
|
||||
value: !!settings.active,
|
||||
onchange: v => {
|
||||
writeSettings("active",v);
|
||||
}
|
||||
},
|
||||
'Grace periods': function() { E.showMenu(submenu_grace); }
|
||||
};
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}, {"url":"screenshot_4.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"tags": "clock,clkinfo",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"shortName":"Calibration",
|
||||
"icon": "calibration.png",
|
||||
"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"],
|
||||
"readme": "README.md",
|
||||
"tags": "tool",
|
||||
|
|
|
@ -10,3 +10,4 @@
|
|||
0.9: Remove ESLint spaces
|
||||
0.10: Show daily steps, heartrate and the temperature if weather information is available.
|
||||
0.11: Tell clock widgets to hide.
|
||||
0.12: Swipe down to see widgets, step counter now just uses getHealthStatus
|
||||
|
|
|
@ -8,4 +8,5 @@ It displays current temperature,day,steps,battery.heartbeat and weather.
|
|||
|
||||
|
||||
**To-do**:
|
||||
Align and change size of some elements.
|
||||
|
||||
* Align and change size of some elements
|
||||
|
|
|
@ -91,7 +91,6 @@ function getTemperature(){
|
|||
var weatherJson = storage.readJSON('weather.json');
|
||||
var weather = weatherJson.weather;
|
||||
return Math.round(weather.temp-273.15);
|
||||
|
||||
} catch(ex) {
|
||||
print(ex)
|
||||
return "?"
|
||||
|
@ -99,20 +98,7 @@ function getTemperature(){
|
|||
}
|
||||
|
||||
function getSteps() {
|
||||
var steps = 0;
|
||||
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";
|
||||
}
|
||||
|
||||
var steps = Bangle.getHealthStatus("day").steps;
|
||||
steps = Math.round(steps/1000);
|
||||
return steps + "k";
|
||||
}
|
||||
|
@ -121,8 +107,7 @@ function getSteps() {
|
|||
function draw() {
|
||||
queueDraw();
|
||||
|
||||
g.reset();
|
||||
g.clear();
|
||||
g.clear(1);
|
||||
g.setColor(0, 255, 255);
|
||||
g.fillRect(0, 0, g.getWidth(), g.getHeight());
|
||||
let background = getBackgroundImage();
|
||||
|
@ -143,9 +128,6 @@ function draw() {
|
|||
drawClock();
|
||||
drawRocket();
|
||||
drawBattery();
|
||||
|
||||
// Hide widgets
|
||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
||||
}
|
||||
|
||||
Bangle.on("lcdPower", (on) => {
|
||||
|
@ -169,7 +151,6 @@ Bangle.setUI("clock");
|
|||
|
||||
// Load widgets, but don't show them
|
||||
Bangle.loadWidgets();
|
||||
|
||||
g.reset();
|
||||
g.clear();
|
||||
require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe
|
||||
g.clear(1);
|
||||
draw();
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"description": "Animated Clock with Space Cassio Watch Style",
|
||||
"screenshots": [{ "url": "screens/screen_night.png" },{ "url": "screens/screen_day.png" }],
|
||||
"icon": "app.png",
|
||||
"version": "0.11",
|
||||
"version": "0.12",
|
||||
"type": "clock",
|
||||
"tags": "clock, weather, cassio, retro",
|
||||
"supports": ["BANGLEJS2"],
|
||||
|
|
|
@ -30,3 +30,4 @@
|
|||
0.15: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16
|
||||
0.16: Fix const error
|
||||
Use widget_utils if available
|
||||
0.17: Load circles from clkinfo
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
let clock_info = require("clock_info");
|
||||
let locale = require("locale");
|
||||
let storage = require("Storage");
|
||||
Graphics.prototype.setFontRobotoRegular50NumericOnly = function(scale) {
|
||||
|
@ -18,6 +19,7 @@ let settings = Object.assign(
|
|||
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
|
||||
if (settings.stepGoal == undefined) {
|
||||
let d = storage.readJSON("health.json", true) || {};
|
||||
|
@ -29,7 +31,7 @@ if (settings.stepGoal == undefined) {
|
|||
}
|
||||
}
|
||||
|
||||
let timerHrm;
|
||||
let timerHrm; //TODO deprecate this
|
||||
let drawTimeout;
|
||||
|
||||
/*
|
||||
|
@ -44,10 +46,9 @@ let showWidgets = settings.showWidgets || false;
|
|||
let circleCount = settings.circleCount || 3;
|
||||
let showBigWeather = settings.showBigWeather || false;
|
||||
|
||||
let hrtValue;
|
||||
let hrtValue; //TODO deprecate this
|
||||
let now = Math.round(new Date().getTime() / 1000);
|
||||
|
||||
|
||||
// layout values:
|
||||
let colorFg = g.theme.dark ? '#fff' : '#000';
|
||||
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 circleFontBig = circleCount == 3 ? "Vector:16" : "Vector:12";
|
||||
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() {
|
||||
/*
|
||||
|
@ -177,6 +190,15 @@ function drawCircle(index) {
|
|||
let w = getCircleXPosition(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":
|
||||
drawSteps(w);
|
||||
break;
|
||||
|
@ -189,13 +211,6 @@ function drawCircle(index) {
|
|||
case "battery":
|
||||
drawBattery(w);
|
||||
break;
|
||||
case "weather":
|
||||
drawWeather(w);
|
||||
break;
|
||||
case "sunprogress":
|
||||
case "sunProgress":
|
||||
drawSunProgress(w);
|
||||
break;
|
||||
case "temperature":
|
||||
drawTemperature(w);
|
||||
break;
|
||||
|
@ -205,9 +220,12 @@ function drawCircle(index) {
|
|||
case "altitude":
|
||||
drawAltitude(w);
|
||||
break;
|
||||
//end deprecated
|
||||
case "empty":
|
||||
// we draw nothing here
|
||||
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) {
|
||||
if (!w) w = getCircleXPosition("steps");
|
||||
let steps = getSteps();
|
||||
|
@ -406,99 +520,6 @@ function drawBattery(w) {
|
|||
|
||||
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) {
|
||||
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)
|
||||
*/
|
||||
|
@ -770,125 +898,15 @@ function writeCircleText(w, content) {
|
|||
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() {
|
||||
let jsonWeather = storage.readJSON('weather.json');
|
||||
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({
|
||||
mode : "clock",
|
||||
remove : function() {
|
||||
// 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);
|
||||
drawTimeout = undefined;
|
||||
|
||||
|
|
|
@ -1,17 +1,11 @@
|
|||
{
|
||||
"minHR": 40,
|
||||
"maxHR": 200,
|
||||
"confidence": 0,
|
||||
"stepGoal": 10000,
|
||||
"stepDistanceGoal": 8000,
|
||||
"stepLength": 0.8,
|
||||
"batteryWarn": 30,
|
||||
"showWidgets": false,
|
||||
"weatherCircleData": "humidity",
|
||||
"circleCount": 3,
|
||||
"circle1": "hr",
|
||||
"circle2": "steps",
|
||||
"circle3": "battery",
|
||||
"circle1": "Bangle/HRM",
|
||||
"circle2": "Bangle/Steps",
|
||||
"circle3": "Bangle/Battery",
|
||||
"circle4": "weather",
|
||||
"circle1color": "green-red",
|
||||
"circle2color": "#0000ff",
|
||||
|
@ -21,7 +15,15 @@
|
|||
"circle2colorizeIcon": true,
|
||||
"circle3colorizeIcon": true,
|
||||
"circle4colorizeIcon": false,
|
||||
"hrmValidity": 60,
|
||||
"updateInterval": 60,
|
||||
"showBigWeather": false
|
||||
"showBigWeather": false,
|
||||
|
||||
"minHR": 40,
|
||||
"maxHR": 200,
|
||||
"confidence": 0,
|
||||
"stepGoal": 10000,
|
||||
"stepDistanceGoal": 8000,
|
||||
"stepLength": 0.8,
|
||||
"hrmValidity": 60
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "circlesclock",
|
||||
"name": "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",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
(function(back) {
|
||||
const SETTINGS_FILE = "circlesclock.json";
|
||||
const storage = require('Storage');
|
||||
const clock_info = require("clock_info");
|
||||
let settings = Object.assign(
|
||||
storage.readJSON("circlesclock.default.json", true) || {},
|
||||
storage.readJSON(SETTINGS_FILE, true) || {}
|
||||
|
@ -11,8 +12,25 @@
|
|||
storage.write(SETTINGS_FILE, settings);
|
||||
}
|
||||
|
||||
const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude"];
|
||||
const namesCircleTypes = ["empty", "steps", "distance", "heart", "battery", "weather", "sun", "temperature", "pressure", "altitude"];
|
||||
//const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude", "timer"];
|
||||
//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",
|
||||
"#00ffff", "#fff", "#000", "green-red", "red-green", "fg"];
|
||||
|
@ -36,8 +54,6 @@
|
|||
/*LANG*/'circle 2': ()=>showCircleMenu(2),
|
||||
/*LANG*/'circle 3': ()=>showCircleMenu(3),
|
||||
/*LANG*/'circle 4': ()=>showCircleMenu(4),
|
||||
/*LANG*/'heartrate': ()=>showHRMenu(),
|
||||
/*LANG*/'steps': ()=>showStepMenu(),
|
||||
/*LANG*/'battery warn': {
|
||||
value: settings.batteryWarn,
|
||||
min: 10,
|
||||
|
@ -78,91 +94,6 @@
|
|||
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) {
|
||||
const circleName = "circle" + circleId;
|
||||
const colorKey = circleName + "color";
|
||||
|
@ -192,6 +123,5 @@
|
|||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
|
||||
showMainMenu();
|
||||
});
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
After Width: | Height: | Size: 661 B |
|
@ -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 : () => {}
|
||||
}
|
||||
]
|
||||
};
|
||||
})
|
|
@ -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"}
|
||||
]
|
||||
}
|
|
@ -12,6 +12,5 @@
|
|||
"storage": [
|
||||
{"name":"demoapp.app.js","url":"app.js"},
|
||||
{"name":"demoapp.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"sortorder": -9
|
||||
]
|
||||
}
|
||||
|
|
|
@ -16,6 +16,11 @@
|
|||
0.16: Use default Bangle formatter for booleans
|
||||
0.17: Bangle 2: Fast loading on exit to clock face. Added option for exit to
|
||||
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
|
||||
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.
|
||||
|
||||
|
|
|
@ -1,47 +1,47 @@
|
|||
{ // must be inside our own scope here so that when we are unloaded everything disappears
|
||||
|
||||
/* Desktop launcher
|
||||
*
|
||||
*/
|
||||
/* Desktop launcher
|
||||
*
|
||||
*/
|
||||
|
||||
let settings = Object.assign({
|
||||
showClocks: true,
|
||||
showLaunchers: true,
|
||||
direct: false,
|
||||
swipeExit: false,
|
||||
timeOut: "Off"
|
||||
}, require('Storage').readJSON("dtlaunch.json", true) || {});
|
||||
let settings = Object.assign({
|
||||
showClocks: true,
|
||||
showLaunchers: true,
|
||||
direct: false,
|
||||
swipeExit: false,
|
||||
timeOut: "Off"
|
||||
}, require('Storage').readJSON("dtlaunch.json", true) || {});
|
||||
|
||||
let s = require("Storage");
|
||||
let s = require("Storage");
|
||||
var apps = s.list(/\.info$/).map(app=>{
|
||||
let a=s.readJSON(app,1);
|
||||
return a && {
|
||||
name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src
|
||||
};}).filter(
|
||||
app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type));
|
||||
let a=s.readJSON(app,1);
|
||||
return a && {
|
||||
name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src
|
||||
};}).filter(
|
||||
app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type));
|
||||
|
||||
apps.sort((a,b)=>{
|
||||
let n=(0|a.sortorder)-(0|b.sortorder);
|
||||
if (n) return n; // do sortorder first
|
||||
if (a.name<b.name) return -1;
|
||||
if (a.name>b.name) return 1;
|
||||
return 0;
|
||||
});
|
||||
apps.forEach(app=>{
|
||||
apps.sort((a,b)=>{
|
||||
let n=(0|a.sortorder)-(0|b.sortorder);
|
||||
if (n) return n; // do sortorder first
|
||||
if (a.name<b.name) return -1;
|
||||
if (a.name>b.name) return 1;
|
||||
return 0;
|
||||
});
|
||||
apps.forEach(app=>{
|
||||
if (app.icon)
|
||||
app.icon = s.read(app.icon); // should just be a link to a memory area
|
||||
});
|
||||
|
||||
let Napps = apps.length;
|
||||
let Npages = Math.ceil(Napps/4);
|
||||
let maxPage = Npages-1;
|
||||
let selected = -1;
|
||||
let oldselected = -1;
|
||||
let page = 0;
|
||||
const XOFF = 24;
|
||||
const YOFF = 30;
|
||||
let Napps = apps.length;
|
||||
let Npages = Math.ceil(Napps/4);
|
||||
let maxPage = Npages-1;
|
||||
let selected = -1;
|
||||
let oldselected = -1;
|
||||
let page = 0;
|
||||
const XOFF = 24;
|
||||
const YOFF = 30;
|
||||
|
||||
let drawIcon= function(p,n,selected) {
|
||||
let drawIcon= function(p,n,selected) {
|
||||
let x = (n%2)*72+XOFF;
|
||||
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);
|
||||
|
@ -65,95 +65,91 @@ let drawIcon= function(p,n,selected) {
|
|||
}
|
||||
}
|
||||
g.drawString(line.trim(),x+36,y+54+lineY*8);
|
||||
};
|
||||
};
|
||||
|
||||
let drawPage = function(p){
|
||||
let drawPage = function(p){
|
||||
g.reset();
|
||||
g.clearRect(0,24,175,175);
|
||||
let O = 88+YOFF/2-12*(Npages/2);
|
||||
for (let j=0;j<Npages;j++){
|
||||
let y = O+j*12;
|
||||
g.setColor(g.theme.fg);
|
||||
if (j==page) g.fillCircle(XOFF/2,y,4);
|
||||
else g.drawCircle(XOFF/2,y,4);
|
||||
let y = O+j*12;
|
||||
g.setColor(g.theme.fg);
|
||||
if (j==page) g.fillCircle(XOFF/2,y,4);
|
||||
else g.drawCircle(XOFF/2,y,4);
|
||||
}
|
||||
for (let i=0;i<4;i++) {
|
||||
if (!apps[p*4+i]) return i;
|
||||
drawIcon(p,i,selected==i && !settings.direct);
|
||||
if (!apps[p*4+i]) return i;
|
||||
drawIcon(p,i,selected==i && !settings.direct);
|
||||
}
|
||||
g.flip();
|
||||
};
|
||||
};
|
||||
|
||||
Bangle.loadWidgets();
|
||||
//g.clear();
|
||||
//Bangle.drawWidgets();
|
||||
drawPage(0);
|
||||
Bangle.loadWidgets();
|
||||
drawPage(0);
|
||||
|
||||
let swipeListenerDt = function(dirLeftRight, dirUpDown){
|
||||
let swipeListenerDt = function(dirLeftRight, dirUpDown){
|
||||
updateTimeoutToClock();
|
||||
selected = 0;
|
||||
oldselected=-1;
|
||||
if(settings.swipeExit && dirLeftRight==1) returnToClock();
|
||||
if(settings.swipeExit && dirLeftRight==1) Bangle.showClock();
|
||||
if (dirUpDown==-1||dirLeftRight==-1){
|
||||
++page; if (page>maxPage) page=0;
|
||||
drawPage(page);
|
||||
++page; if (page>maxPage) page=0;
|
||||
drawPage(page);
|
||||
} else if (dirUpDown==1||(dirLeftRight==1 && !settings.swipeExit)){
|
||||
--page; if (page<0) page=maxPage;
|
||||
drawPage(page);
|
||||
--page; if (page<0) page=maxPage;
|
||||
drawPage(page);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let isTouched = function(p,n){
|
||||
let isTouched = function(p,n){
|
||||
if (n<0 || n>3) return false;
|
||||
let x1 = (n%2)*72+XOFF; let y1 = n>1?72+YOFF:YOFF;
|
||||
let x2 = x1+71; let y2 = y1+81;
|
||||
return (p.x>x1 && p.y>y1 && p.x<x2 && p.y<y2);
|
||||
};
|
||||
};
|
||||
|
||||
let touchListenerDt = function(_,p){
|
||||
let touchListenerDt = function(_,p){
|
||||
updateTimeoutToClock();
|
||||
let i;
|
||||
for (i=0;i<4;i++){
|
||||
if((page*4+i)<Napps){
|
||||
if (isTouched(p,i)) {
|
||||
drawIcon(page,i,true && !settings.direct);
|
||||
if (selected>=0 || settings.direct) {
|
||||
if (selected!=i && !settings.direct){
|
||||
drawIcon(page,selected,false);
|
||||
} else {
|
||||
load(apps[page*4+i].src);
|
||||
}
|
||||
}
|
||||
selected=i;
|
||||
break;
|
||||
if((page*4+i)<Napps){
|
||||
if (isTouched(p,i)) {
|
||||
drawIcon(page,i,true && !settings.direct);
|
||||
if (selected>=0 || settings.direct) {
|
||||
if (selected!=i && !settings.direct){
|
||||
drawIcon(page,selected,false);
|
||||
} else {
|
||||
load(apps[page*4+i].src);
|
||||
}
|
||||
}
|
||||
selected=i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((i==4 || (page*4+i)>Napps) && selected>=0) {
|
||||
drawIcon(page,selected,false);
|
||||
selected=-1;
|
||||
drawIcon(page,selected,false);
|
||||
selected=-1;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const returnToClock = function() {
|
||||
Bangle.setUI();
|
||||
setTimeout(eval, 0, s.read(".bootcde"));
|
||||
};
|
||||
Bangle.setUI({
|
||||
mode : 'custom',
|
||||
back : Bangle.showClock,
|
||||
swipe : swipeListenerDt,
|
||||
touch : touchListenerDt,
|
||||
remove : ()=>{if (timeoutToClock) clearTimeout(timeoutToClock);}
|
||||
});
|
||||
|
||||
Bangle.setUI({
|
||||
mode : 'custom',
|
||||
back : returnToClock,
|
||||
swipe : swipeListenerDt,
|
||||
touch : touchListenerDt
|
||||
});
|
||||
|
||||
// taken from Icon Launcher with minor alterations
|
||||
var timeoutToClock;
|
||||
const updateTimeoutToClock = function(){
|
||||
if (settings.timeOut!="Off"){
|
||||
let time=parseInt(settings.timeOut); //the "s" will be trimmed by the parseInt
|
||||
if (timeoutToClock) clearTimeout(timeoutToClock);
|
||||
timeoutToClock = setTimeout(returnToClock,time*1000);
|
||||
}
|
||||
};
|
||||
updateTimeoutToClock();
|
||||
// taken from Icon Launcher with minor alterations
|
||||
let timeoutToClock;
|
||||
const updateTimeoutToClock = function(){
|
||||
if (settings.timeOut!="Off"){
|
||||
let time=parseInt(settings.timeOut); //the "s" will be trimmed by the parseInt
|
||||
if (timeoutToClock) clearTimeout(timeoutToClock);
|
||||
timeoutToClock = setTimeout(Bangle.showClock,time*1000);
|
||||
}
|
||||
};
|
||||
updateTimeoutToClock();
|
||||
|
||||
} // end of app scope
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "dtlaunch",
|
||||
"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.",
|
||||
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
|
||||
"icon": "icon.png",
|
||||
|
|
|
@ -5,3 +5,4 @@
|
|||
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
|
||||
0.04: Include a precompiled bootloader for easy bootloader updates
|
||||
0.05: Rename Bootloader->DFU and add explanation to avoid confusion with Bootloader app
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
<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
|
||||
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>
|
||||
<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 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">
|
||||
<p>The currently available Espruino firmware releases are:</p>
|
||||
<ul id="latest-firmware-list">
|
||||
</ul>
|
||||
<p>To update, click a link above and then click the 'Upload' button that appears.</p>
|
||||
</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">
|
||||
<p><b>Advanced</b></p>
|
||||
<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
|
||||
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>
|
||||
<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
|
||||
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>
|
||||
</div>
|
||||
<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 = "";
|
||||
}
|
||||
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 ok = true;
|
||||
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
|
||||
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(0xF7000,0x7000))==1339551013) { print("BOOTLOADER 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))==1339551013) { print("DFU 2v10.219 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 += '\x10s.erase(".firmware");\n';
|
||||
var CHUNKSIZE = 2048;
|
||||
|
@ -320,7 +334,7 @@ function createJS_app(binary, startAddress, endAddress) {
|
|||
function createJS_bootloader(binary, startAddress, endAddress) {
|
||||
var crc = CRC32(binary);
|
||||
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`;
|
||||
var CHUNKSIZE = 1024;
|
||||
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(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 += 'var f = require("Flash");\n';
|
||||
for (var i=startAddress;i<endAddress;i+=4096)
|
||||
hexJS += 'f.erasePage(0x'+i.toString(16)+');\n';
|
||||
hexJS += `f.write(_fw,${startAddress});\n`;
|
||||
hexJS += `})()\n`;
|
||||
log("Bootloader ready for upload");
|
||||
log("DFU ready for upload");
|
||||
}
|
||||
|
||||
function hexFileLoaded(hexString) {
|
||||
|
@ -383,7 +397,7 @@ function hexFileLoaded(hexString) {
|
|||
});
|
||||
|
||||
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);
|
||||
} else {
|
||||
console.log("App - Writing to external flash");
|
||||
|
@ -406,6 +420,10 @@ function handleUpload() {
|
|||
|
||||
document.getElementById('fileLoader').addEventListener('change', handleFileSelect, false);
|
||||
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").style = "display:none";
|
||||
document.getElementById("advanced-div").style = "";
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "fwupdate",
|
||||
"name": "Firmware Update",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Uploads new Espruino firmwares to Bangle.js 2",
|
||||
"icon": "app.png",
|
||||
"type": "RAM",
|
||||
|
@ -10,5 +10,5 @@
|
|||
"custom": "custom.html",
|
||||
"customConnect": true,
|
||||
"storage": [],
|
||||
"sortorder": 20
|
||||
"sortorder": -11
|
||||
}
|
||||
|
|
|
@ -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.
|
|
@ -0,0 +1,109 @@
|
|||
# Gipy
|
||||
|
||||
Gipy allows you to follow gpx traces on your watch.
|
||||
|
||||

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

|
||||
|
||||
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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkBiIA/AE8VqoAGCy1RiN3CyYuBi93uIXJIBV3AAIuMBY4XjQ5YXPRAIAEOwIABPBC4LF54wGF6IwFC5jWGIwxIJC4xJFgDuJJAxJFC6TEIJBzEHGCIYPGA5JQC44YPGBBJKY4gwRfQL4DGCL4GGCAXPGAxGBAAJIMGAwWCGCoWGC55HHJB5HIC8pGDSChfXC5AWIL5ynOC45GJC4h3IIyYwCFxwADgB1SC44uSC4guSAH4Ab"))
|
|
@ -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();
|
||||
}
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -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>
|
|
@ -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"}
|
||||
]
|
||||
}
|
|
@ -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>;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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
|
||||
}
|
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 3.0 KiB |
|
@ -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();
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
|
@ -5,7 +5,7 @@
|
|||
"description": "An application that displays information about altitude, lat/lon, satellites and time",
|
||||
"icon": "gps-info.png",
|
||||
"type": "app",
|
||||
"tags": "gps",
|
||||
"tags": "gps,outdoors",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"gpsinfo.app.js","url":"gps-info.js"},
|
||||
|
|
|
@ -8,3 +8,8 @@
|
|||
Fix widget adding listeners more than once
|
||||
0.07: Show checkered flag for target markers
|
||||
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
|
||||
|
|
|
@ -1,48 +1,69 @@
|
|||
|
||||
{ //run in own scope for fast switch
|
||||
const STORAGE = require("Storage");
|
||||
const showWidgets = true;
|
||||
let numberOfSlices=4;
|
||||
const BAT_FULL = require("Storage").readJSON("setting.json").batFullVoltage || 0.3144;
|
||||
|
||||
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();
|
||||
}
|
||||
WIDGETS.gpstrek.start(false);
|
||||
if (!WIDGETS.gpstrek.getState().numberOfSlices) WIDGETS.gpstrek.getState().numberOfSlices = 3;
|
||||
};
|
||||
|
||||
let state = WIDGETS.gpstrek.getState();
|
||||
WIDGETS.gpstrek.start(false);
|
||||
let cleanup = function(){
|
||||
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);
|
||||
return parseFloat("" + toParse + ".0");
|
||||
}
|
||||
};
|
||||
|
||||
function parseWaypoint(filename, offset, result){
|
||||
let parseWaypoint = function(filename, offset, result){
|
||||
result.lat = parseNumber(STORAGE.read(filename, offset, 11));
|
||||
result.lon = parseNumber(STORAGE.read(filename, offset += 11, 12));
|
||||
return offset + 12;
|
||||
}
|
||||
};
|
||||
|
||||
function parseWaypointWithElevation(filename, offset, result){
|
||||
let parseWaypointWithElevation = function (filename, offset, result){
|
||||
offset = parseWaypoint(filename, offset, result);
|
||||
result.alt = parseNumber(STORAGE.read(filename, offset, 6));
|
||||
return offset + 6;
|
||||
}
|
||||
};
|
||||
|
||||
function parseWaypointWithName(filename, offset, result){
|
||||
let parseWaypointWithName = function(filename, offset, result){
|
||||
offset = parseWaypoint(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;
|
||||
result.name = STORAGE.read(filename, offset += 2, nameLength);
|
||||
return offset + nameLength;
|
||||
}
|
||||
};
|
||||
|
||||
function parseWaypointWithElevationAndName(filename, offset, result){
|
||||
let parseWaypointWithElevationAndName = function(filename, offset, result){
|
||||
offset = parseWaypointWithElevation(filename, offset, result);
|
||||
return parseName(filename, offset, result);
|
||||
}
|
||||
};
|
||||
|
||||
function getEntry(filename, offset, result){
|
||||
let getEntry = function(filename, offset, result){
|
||||
result.fileOffset = offset;
|
||||
let type = STORAGE.read(filename, offset++, 1);
|
||||
if (type == "") return -1;
|
||||
|
@ -68,12 +89,12 @@ function getEntry(filename, offset, result){
|
|||
result.fileLength = offset - result.fileOffset;
|
||||
//print(result);
|
||||
return offset;
|
||||
}
|
||||
};
|
||||
|
||||
const labels = ["N","NE","E","SE","S","SW","W","NW"];
|
||||
const loc = require("locale");
|
||||
|
||||
function matchFontSize(graphics, text, height, width){
|
||||
let matchFontSize = function(graphics, text, height, width){
|
||||
graphics.setFontVector(height);
|
||||
let metrics;
|
||||
let size = 1;
|
||||
|
@ -81,13 +102,19 @@ function matchFontSize(graphics, text, height, width){
|
|||
size -= 0.05;
|
||||
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 lastValue1 = 0;
|
||||
let lastValue2 = 0;
|
||||
return {
|
||||
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){
|
||||
lastDrawn = Date.now();
|
||||
|
@ -95,29 +122,29 @@ function getDoubleLineSlice(title1,title2,provider1,provider2,refreshTime){
|
|||
if (typeof title2 == "function") title2 = title2();
|
||||
graphics.clearRect(x,y,x+width,y+height);
|
||||
|
||||
let value = provider1();
|
||||
matchFontSize(graphics, title1 + value, Math.floor(height*0.5), width);
|
||||
lastValue1 = provider1();
|
||||
matchFontSize(graphics, title1 + lastValue1, Math.floor(height*0.5), width);
|
||||
graphics.setFontAlign(-1,-1);
|
||||
graphics.drawString(title1, x+2, y);
|
||||
graphics.setFontAlign(1,-1);
|
||||
graphics.drawString(value, x+width, y);
|
||||
graphics.drawString(lastValue1, x+width, y);
|
||||
|
||||
value = provider2();
|
||||
matchFontSize(graphics, title2 + value, Math.floor(height*0.5), width);
|
||||
lastValue2 = provider2();
|
||||
matchFontSize(graphics, title2 + lastValue2, Math.floor(height*0.5), width);
|
||||
graphics.setFontAlign(-1,-1);
|
||||
graphics.drawString(title2, x+2, y+(height*0.5));
|
||||
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 lastDrawn = Date.now() - Math.random()*3000;
|
||||
return {
|
||||
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){
|
||||
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.setFontAlign(0,-1);
|
||||
graphics.setColor(graphics.theme.fg);
|
||||
|
@ -197,14 +224,19 @@ function drawCompass(graphics, x, y, height, width, increment, start){
|
|||
xpos+=increment*15;
|
||||
if (xpos > width + 20) break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function getCompassSlice(compassDataSource){
|
||||
let getCompassSlice = function(compassDataSource){
|
||||
let lastDrawn = Date.now() - Math.random()*2000;
|
||||
let lastDrawnValue = 0;
|
||||
const buffers = 4;
|
||||
let buf = [];
|
||||
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){
|
||||
lastDrawn = Date.now();
|
||||
const max = 180;
|
||||
|
@ -212,12 +244,14 @@ function getCompassSlice(compassDataSource){
|
|||
|
||||
graphics.clearRect(x,y,x+width,y+height);
|
||||
|
||||
var start = compassDataSource.getCourse() - 90;
|
||||
if (isNaN(compassDataSource.getCourse())) start = -90;
|
||||
lastDrawnValue = compassDataSource.getCourse();
|
||||
|
||||
var start = lastDrawnValue - 90;
|
||||
if (isNaN(lastDrawnValue)) start = -90;
|
||||
if (start<0) 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);
|
||||
} else {
|
||||
drawCompass(graphics,0,y,height,width,increment,start);
|
||||
|
@ -226,7 +260,8 @@ function getCompassSlice(compassDataSource){
|
|||
|
||||
if (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;
|
||||
bpos+=120;
|
||||
|
@ -251,6 +286,7 @@ function getCompassSlice(compassDataSource){
|
|||
}
|
||||
if (compassDataSource.getMarkers){
|
||||
for (let m of compassDataSource.getMarkers()){
|
||||
g.reset();
|
||||
g.setColor(m.fillcolor);
|
||||
let mpos = m.xpos * width;
|
||||
if (m.xpos < 0.05) mpos = Math.floor(width*0.05);
|
||||
|
@ -263,9 +299,9 @@ function getCompassSlice(compassDataSource){
|
|||
graphics.setColor(g.theme.fg);
|
||||
graphics.fillRect(x,y,Math.floor(width*0.05),y+height);
|
||||
graphics.fillRect(Math.ceil(width*0.95),y,width,y+height);
|
||||
if (state.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 yh = E.clip(y+(((state.acc.y+1)/2)*height),y,y+height);
|
||||
if (WIDGETS.gpstrek.getState().acc && compassDataSource.getCourseType() == "MAG") {
|
||||
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+(((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));
|
||||
|
||||
|
@ -287,44 +323,48 @@ function getCompassSlice(compassDataSource){
|
|||
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;
|
||||
}
|
||||
};
|
||||
|
||||
function degrees(a) {
|
||||
var d = a*180/Math.PI;
|
||||
let degrees = function(a) {
|
||||
let d = a*180/Math.PI;
|
||||
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;
|
||||
var delta = radians(b.lon-a.lon);
|
||||
var alat = radians(a.lat);
|
||||
var blat = radians(b.lat);
|
||||
var y = Math.sin(delta) * Math.cos(blat);
|
||||
var x = Math.cos(alat)*Math.sin(blat) -
|
||||
let delta = radians(b.lon-a.lon);
|
||||
let alat = radians(a.lat);
|
||||
let blat = radians(b.lat);
|
||||
let y = Math.sin(delta) * Math.cos(blat);
|
||||
let x = Math.cos(alat)*Math.sin(blat) -
|
||||
Math.sin(alat)*Math.cos(blat)*Math.cos(delta);
|
||||
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;
|
||||
var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2));
|
||||
var y = radians(b.lat-a.lat);
|
||||
let x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2));
|
||||
let y = radians(b.lat-a.lat);
|
||||
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 [
|
||||
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)
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
function onSwipe(dir){
|
||||
let onSwipe = function(dir){
|
||||
if (dir < 0) {
|
||||
nextScreen();
|
||||
} else if (dir > 0) {
|
||||
|
@ -332,9 +372,9 @@ function onSwipe(dir){
|
|||
} else {
|
||||
nextScreen();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function setButtons(){
|
||||
let setButtons = function(){
|
||||
let options = {
|
||||
mode: "custom",
|
||||
swipe: onSwipe,
|
||||
|
@ -342,9 +382,9 @@ function setButtons(){
|
|||
touch: nextScreen
|
||||
};
|
||||
Bangle.setUI(options);
|
||||
}
|
||||
};
|
||||
|
||||
function getApproxFileSize(name){
|
||||
let getApproxFileSize = function(name){
|
||||
let currentStart = STORAGE.getStats().totalBytes;
|
||||
let currentSize = 0;
|
||||
for (let i = currentStart; i > 500; i/=2){
|
||||
|
@ -358,9 +398,9 @@ function getApproxFileSize(name){
|
|||
currentSize += currentDiff;
|
||||
}
|
||||
return currentSize;
|
||||
}
|
||||
};
|
||||
|
||||
function parseRouteData(filename, progressMonitor){
|
||||
let parseRouteData = function(filename, progressMonitor){
|
||||
let routeInfo = {};
|
||||
|
||||
routeInfo.filename = filename;
|
||||
|
@ -406,40 +446,40 @@ function parseRouteData(filename, progressMonitor){
|
|||
|
||||
set(routeInfo, 0);
|
||||
return routeInfo;
|
||||
}
|
||||
};
|
||||
|
||||
function hasPrev(route){
|
||||
let hasPrev = function(route){
|
||||
if (route.mirror) return route.index < (route.count - 1);
|
||||
return route.index > 0;
|
||||
}
|
||||
};
|
||||
|
||||
function hasNext(route){
|
||||
let hasNext = function(route){
|
||||
if (route.mirror) return route.index > 0;
|
||||
return route.index < (route.count - 1);
|
||||
}
|
||||
};
|
||||
|
||||
function next(route){
|
||||
let next = function(route){
|
||||
if (!hasNext(route)) return;
|
||||
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.index = index;
|
||||
getEntry(route.filename, route.refs[index], route.currentWaypoint);
|
||||
}
|
||||
};
|
||||
|
||||
function prev(route){
|
||||
let prev = function(route){
|
||||
if (!hasPrev(route)) return;
|
||||
if (route.mirror) set(route, ++route.index);
|
||||
if (!route.mirror) set(route, --route.index);
|
||||
}
|
||||
};
|
||||
|
||||
let lastMirror;
|
||||
let cachedLast;
|
||||
|
||||
function getLast(route){
|
||||
let getLast = function(route){
|
||||
let wp = {};
|
||||
if (lastMirror != route.mirror){
|
||||
if (route.mirror) getEntry(route.filename, route.refs[0], wp);
|
||||
|
@ -448,14 +488,14 @@ function getLast(route){
|
|||
cachedLast = wp;
|
||||
}
|
||||
return cachedLast;
|
||||
}
|
||||
};
|
||||
|
||||
function removeMenu(){
|
||||
let removeMenu = function(){
|
||||
E.showMenu();
|
||||
switchNav();
|
||||
}
|
||||
};
|
||||
|
||||
function showProgress(progress, title, max){
|
||||
let showProgress = function(progress, title, max){
|
||||
//print("Progress",progress,max)
|
||||
let message = title? title: "Loading";
|
||||
if (max){
|
||||
|
@ -466,17 +506,17 @@ function showProgress(progress, title, max){
|
|||
for (let i = dots; i < 4; i++) message += " ";
|
||||
}
|
||||
E.showMessage(message);
|
||||
}
|
||||
};
|
||||
|
||||
function handleLoading(c){
|
||||
let handleLoading = function(c){
|
||||
E.showMenu();
|
||||
state.route = parseRouteData(c, showProgress);
|
||||
state.waypoint = null;
|
||||
WIDGETS.gpstrek.getState().route = parseRouteData(c, showProgress);
|
||||
WIDGETS.gpstrek.getState().waypoint = null;
|
||||
WIDGETS.gpstrek.getState().route.mirror = false;
|
||||
removeMenu();
|
||||
state.route.mirror = false;
|
||||
}
|
||||
};
|
||||
|
||||
function showRouteSelector (){
|
||||
let showRouteSelector = function(){
|
||||
var menu = {
|
||||
"" : {
|
||||
back : showRouteMenu,
|
||||
|
@ -488,9 +528,9 @@ function showRouteSelector (){
|
|||
});
|
||||
|
||||
E.showMenu(menu);
|
||||
}
|
||||
};
|
||||
|
||||
function showRouteMenu(){
|
||||
let showRouteMenu = function(){
|
||||
var menu = {
|
||||
"" : {
|
||||
"title" : "Route",
|
||||
|
@ -499,48 +539,48 @@ function showRouteMenu(){
|
|||
"Select file" : showRouteSelector
|
||||
};
|
||||
|
||||
if (state.route){
|
||||
if (WIDGETS.gpstrek.getState().route){
|
||||
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=>{
|
||||
state.route.mirror = v;
|
||||
WIDGETS.gpstrek.getState().route.mirror = v;
|
||||
}
|
||||
};
|
||||
menu['Select closest waypoint'] = function () {
|
||||
if (state.currentPos && state.currentPos.lat){
|
||||
setClosestWaypoint(state.route, null, showProgress); removeMenu();
|
||||
if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lat){
|
||||
setClosestWaypoint(WIDGETS.gpstrek.getState().route, null, showProgress); removeMenu();
|
||||
} else {
|
||||
E.showAlert("No position").then(()=>{E.showMenu(menu);});
|
||||
}
|
||||
};
|
||||
menu['Select closest waypoint (not visited)'] = function () {
|
||||
if (state.currentPos && state.currentPos.lat){
|
||||
setClosestWaypoint(state.route, state.route.index, showProgress); removeMenu();
|
||||
if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lat){
|
||||
setClosestWaypoint(WIDGETS.gpstrek.getState().route, WIDGETS.gpstrek.getState().route.index, showProgress); removeMenu();
|
||||
} else {
|
||||
E.showAlert("No position").then(()=>{E.showMenu(menu);});
|
||||
}
|
||||
};
|
||||
menu['Select waypoint'] = {
|
||||
value : state.route.index,
|
||||
min:1,max:state.route.count,step:1,
|
||||
onchange : v => { set(state.route, v-1); }
|
||||
value : WIDGETS.gpstrek.getState().route.index,
|
||||
min:1,max:WIDGETS.gpstrek.getState().route.count,step:1,
|
||||
onchange : v => { set(WIDGETS.gpstrek.getState().route, v-1); }
|
||||
};
|
||||
menu['Select waypoint as current position'] = function (){
|
||||
state.currentPos.lat = state.route.currentWaypoint.lat;
|
||||
state.currentPos.lon = state.route.currentWaypoint.lon;
|
||||
state.currentPos.alt = state.route.currentWaypoint.alt;
|
||||
WIDGETS.gpstrek.getState().currentPos.lat = WIDGETS.gpstrek.getState().route.currentWaypoint.lat;
|
||||
WIDGETS.gpstrek.getState().currentPos.lon = WIDGETS.gpstrek.getState().route.currentWaypoint.lon;
|
||||
WIDGETS.gpstrek.getState().currentPos.alt = WIDGETS.gpstrek.getState().route.currentWaypoint.alt;
|
||||
removeMenu();
|
||||
};
|
||||
}
|
||||
|
||||
if (state.route && hasPrev(state.route))
|
||||
menu['Previous waypoint'] = function() { prev(state.route); removeMenu(); };
|
||||
if (state.route && hasNext(state.route))
|
||||
menu['Next waypoint'] = function() { next(state.route); removeMenu(); };
|
||||
if (WIDGETS.gpstrek.getState().route && hasPrev(WIDGETS.gpstrek.getState().route))
|
||||
menu['Previous waypoint'] = function() { prev(WIDGETS.gpstrek.getState().route); removeMenu(); };
|
||||
if (WIDGETS.gpstrek.getState().route && hasNext(WIDGETS.gpstrek.getState().route))
|
||||
menu['Next waypoint'] = function() { next(WIDGETS.gpstrek.getState().route); removeMenu(); };
|
||||
E.showMenu(menu);
|
||||
}
|
||||
};
|
||||
|
||||
function showWaypointSelector(){
|
||||
let showWaypointSelector = function(){
|
||||
let waypoints = require("waypoints").load();
|
||||
var menu = {
|
||||
"" : {
|
||||
|
@ -550,41 +590,41 @@ function showWaypointSelector(){
|
|||
|
||||
waypoints.forEach((wp,c)=>{
|
||||
menu[waypoints[c].name] = function (){
|
||||
state.waypoint = waypoints[c];
|
||||
state.waypointIndex = c;
|
||||
state.route = null;
|
||||
WIDGETS.gpstrek.getState().waypoint = waypoints[c];
|
||||
WIDGETS.gpstrek.getState().waypointIndex = c;
|
||||
WIDGETS.gpstrek.getState().route = null;
|
||||
removeMenu();
|
||||
};
|
||||
});
|
||||
|
||||
E.showMenu(menu);
|
||||
}
|
||||
};
|
||||
|
||||
function showCalibrationMenu(){
|
||||
let showCalibrationMenu = function(){
|
||||
let menu = {
|
||||
"" : {
|
||||
"title" : "Calibration",
|
||||
back : showMenu,
|
||||
},
|
||||
"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);});
|
||||
} else {
|
||||
state.calibAltDiff = state.altitude - state.currentPos.alt;
|
||||
E.showAlert("Calibrated Altitude Difference: " + state.calibAltDiff.toFixed(0)).then(()=>{removeMenu();});
|
||||
WIDGETS.gpstrek.getState().calibAltDiff = WIDGETS.gpstrek.getState().altitude - WIDGETS.gpstrek.getState().currentPos.alt;
|
||||
E.showAlert("Calibrated Altitude Difference: " + WIDGETS.gpstrek.getState().calibAltDiff.toFixed(0)).then(()=>{removeMenu();});
|
||||
}
|
||||
},
|
||||
"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,
|
||||
onchange : v => { state.calibAltDiff = state.altitude - v; }
|
||||
onchange : v => { WIDGETS.gpstrek.getState().calibAltDiff = WIDGETS.gpstrek.getState().altitude - v; }
|
||||
},
|
||||
"Reset Compass" : ()=>{ Bangle.resetCompass(); removeMenu();},
|
||||
};
|
||||
E.showMenu(menu);
|
||||
}
|
||||
};
|
||||
|
||||
function showWaypointMenu(){
|
||||
let showWaypointMenu = function(){
|
||||
let menu = {
|
||||
"" : {
|
||||
"title" : "Waypoint",
|
||||
|
@ -593,21 +633,21 @@ function showWaypointMenu(){
|
|||
"Select waypoint" : showWaypointSelector,
|
||||
};
|
||||
E.showMenu(menu);
|
||||
}
|
||||
};
|
||||
|
||||
function showBackgroundMenu(){
|
||||
let showBackgroundMenu = function(){
|
||||
let menu = {
|
||||
"" : {
|
||||
"title" : "Background",
|
||||
back : showMenu,
|
||||
},
|
||||
"Start" : ()=>{ E.showPrompt("Start?").then((v)=>{ if (v) {WIDGETS.gpstrek.start(true); removeMenu();} else {showMenu();}}).catch(()=>{E.showMenu(mainmenu);});},
|
||||
"Stop" : ()=>{ E.showPrompt("Stop?").then((v)=>{ if (v) {WIDGETS.gpstrek.stop(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(()=>{showMenu();});},
|
||||
};
|
||||
E.showMenu(menu);
|
||||
}
|
||||
};
|
||||
|
||||
function showMenu(){
|
||||
let showMenu = function(){
|
||||
var mainmenu = {
|
||||
"" : {
|
||||
"title" : "Main",
|
||||
|
@ -617,50 +657,55 @@ function showMenu(){
|
|||
"Waypoint" : showWaypointMenu,
|
||||
"Background" : showBackgroundMenu,
|
||||
"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" : {
|
||||
value : numberOfSlices,
|
||||
value : WIDGETS.gpstrek.getState().numberOfSlices,
|
||||
min:1,max:6,step:1,
|
||||
onchange : v => { setNumberOfSlices(v); }
|
||||
onchange : v => { WIDGETS.gpstrek.getState().numberOfSlices = v; }
|
||||
},
|
||||
};
|
||||
|
||||
E.showMenu(mainmenu);
|
||||
}
|
||||
};
|
||||
|
||||
let scheduleDraw = true;
|
||||
|
||||
function switchMenu(){
|
||||
screen = 0;
|
||||
scheduleDraw = false;
|
||||
showMenu();
|
||||
}
|
||||
let switchMenu = function(){
|
||||
stopDrawing();
|
||||
showMenu();
|
||||
};
|
||||
|
||||
function drawInTimeout(){
|
||||
setTimeout(()=>{
|
||||
let stopDrawing = function(){
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
scheduleDraw = false;
|
||||
};
|
||||
|
||||
let drawInTimeout = function(){
|
||||
if (global.drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(()=>{
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
if (scheduleDraw)
|
||||
setTimeout(drawInTimeout, 0);
|
||||
},0);
|
||||
}
|
||||
},50);
|
||||
};
|
||||
|
||||
function switchNav(){
|
||||
let switchNav = function(){
|
||||
if (!screen) screen = 1;
|
||||
setButtons();
|
||||
scheduleDraw = true;
|
||||
firstDraw = true;
|
||||
drawInTimeout();
|
||||
}
|
||||
};
|
||||
|
||||
function nextScreen(){
|
||||
let nextScreen = function(){
|
||||
screen++;
|
||||
if (screen > maxScreens){
|
||||
screen = 1;
|
||||
}
|
||||
}
|
||||
drawInTimeout();
|
||||
};
|
||||
|
||||
function setClosestWaypoint(route, startindex, progress){
|
||||
if (startindex >= state.route.count) startindex = state.route.count - 1;
|
||||
if (!state.currentPos.lat){
|
||||
let setClosestWaypoint = function(route, startindex, progress){
|
||||
if (startindex >= WIDGETS.gpstrek.getState().route.count) startindex = WIDGETS.gpstrek.getState().route.count - 1;
|
||||
if (!WIDGETS.gpstrek.getState().currentPos.lat){
|
||||
set(route, startindex);
|
||||
return;
|
||||
}
|
||||
|
@ -670,7 +715,7 @@ function setClosestWaypoint(route, startindex, progress){
|
|||
if (progress && (i % 5 == 0)) progress(i-(startindex?startindex:0), "Searching", route.count);
|
||||
let 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){
|
||||
minDist = curDist;
|
||||
minIndex = i;
|
||||
|
@ -679,30 +724,28 @@ function setClosestWaypoint(route, startindex, progress){
|
|||
}
|
||||
}
|
||||
set(route, minIndex);
|
||||
}
|
||||
|
||||
let screen = 1;
|
||||
};
|
||||
|
||||
const finishIcon = atob("CggB//meZmeZ+Z5n/w==");
|
||||
|
||||
const compassSliceData = {
|
||||
getCourseType: function(){
|
||||
return (state.currentPos && state.currentPos.course) ? "GPS" : "MAG";
|
||||
return (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.course) ? "GPS" : "MAG";
|
||||
},
|
||||
getCourse: function (){
|
||||
if(compassSliceData.getCourseType() == "GPS") return state.currentPos.course;
|
||||
return state.compassHeading?state.compassHeading:undefined;
|
||||
if(compassSliceData.getCourseType() == "GPS") return WIDGETS.gpstrek.getState().currentPos.course;
|
||||
return getAveragedCompass();
|
||||
},
|
||||
getPoints: function (){
|
||||
let points = [];
|
||||
if (state.currentPos && state.currentPos.lon && state.route && state.route.currentWaypoint){
|
||||
points.push({bearing:bearing(state.currentPos, state.route.currentWaypoint), color:"#0f0"});
|
||||
if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lon && WIDGETS.gpstrek.getState().route && WIDGETS.gpstrek.getState().route.currentWaypoint){
|
||||
points.push({bearing:bearing(WIDGETS.gpstrek.getState().currentPos, WIDGETS.gpstrek.getState().route.currentWaypoint), color:"#0f0"});
|
||||
}
|
||||
if (state.currentPos && state.currentPos.lon && state.route){
|
||||
points.push({bearing:bearing(state.currentPos, getLast(state.route)), icon: finishIcon});
|
||||
if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lon && WIDGETS.gpstrek.getState().route){
|
||||
points.push({bearing:bearing(WIDGETS.gpstrek.getState().currentPos, getLast(WIDGETS.gpstrek.getState().route)), icon: finishIcon});
|
||||
}
|
||||
if (state.currentPos && state.currentPos.lon && state.waypoint){
|
||||
points.push({bearing:bearing(state.currentPos, state.waypoint), icon: finishIcon});
|
||||
if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lon && WIDGETS.gpstrek.getState().waypoint){
|
||||
points.push({bearing:bearing(WIDGETS.gpstrek.getState().currentPos, WIDGETS.gpstrek.getState().waypoint), icon: finishIcon});
|
||||
}
|
||||
return points;
|
||||
},
|
||||
|
@ -714,79 +757,74 @@ const compassSliceData = {
|
|||
const waypointData = {
|
||||
icon: atob("EBCBAAAAAAAAAAAAcIB+zg/uAe4AwACAAAAAAAAAAAAAAAAA"),
|
||||
getProgress: function() {
|
||||
return (state.route.index + 1) + "/" + state.route.count;
|
||||
return (WIDGETS.gpstrek.getState().route.index + 1) + "/" + WIDGETS.gpstrek.getState().route.count;
|
||||
},
|
||||
getTarget: function (){
|
||||
if (distance(state.currentPos,state.route.currentWaypoint) < 30 && hasNext(state.route)){
|
||||
next(state.route);
|
||||
if (distance(WIDGETS.gpstrek.getState().currentPos,WIDGETS.gpstrek.getState().route.currentWaypoint) < 30 && hasNext(WIDGETS.gpstrek.getState().route)){
|
||||
next(WIDGETS.gpstrek.getState().route);
|
||||
Bangle.buzz(1000);
|
||||
}
|
||||
return state.route.currentWaypoint;
|
||||
return WIDGETS.gpstrek.getState().route.currentWaypoint;
|
||||
},
|
||||
getStart: function (){
|
||||
return state.currentPos;
|
||||
return WIDGETS.gpstrek.getState().currentPos;
|
||||
}
|
||||
};
|
||||
|
||||
const finishData = {
|
||||
icon: atob("EBABAAA/4DmgJmAmYDmgOaAmYD/gMAAwADAAMAAwAAAAAAA="),
|
||||
getTarget: function (){
|
||||
if (state.route) return getLast(state.route);
|
||||
if (state.waypoint) return state.waypoint;
|
||||
if (WIDGETS.gpstrek.getState().route) return getLast(WIDGETS.gpstrek.getState().route);
|
||||
if (WIDGETS.gpstrek.getState().waypoint) return WIDGETS.gpstrek.getState().waypoint;
|
||||
},
|
||||
getStart: function (){
|
||||
return state.currentPos;
|
||||
return WIDGETS.gpstrek.getState().currentPos;
|
||||
}
|
||||
};
|
||||
|
||||
let sliceHeight;
|
||||
function setNumberOfSlices(number){
|
||||
numberOfSlices = number;
|
||||
sliceHeight = Math.floor((g.getHeight()-(showWidgets?24:0))/numberOfSlices);
|
||||
}
|
||||
|
||||
let slices = [];
|
||||
let maxScreens = 1;
|
||||
setNumberOfSlices(3);
|
||||
let getSliceHeight = function(number){
|
||||
return Math.floor(Bangle.appRect.h/WIDGETS.gpstrek.getState().numberOfSlices);
|
||||
};
|
||||
|
||||
let compassSlice = getCompassSlice(compassSliceData);
|
||||
let waypointSlice = getTargetSlice(waypointData);
|
||||
let finishSlice = getTargetSlice(finishData);
|
||||
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 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);
|
||||
},()=>{
|
||||
let alt = Infinity;
|
||||
if (!isNaN(state.altitude)){
|
||||
alt = isNaN(state.calibAltDiff) ? state.altitude : (state.altitude - state.calibAltDiff);
|
||||
if (!isNaN(WIDGETS.gpstrek.getState().altitude)){
|
||||
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);
|
||||
});
|
||||
|
||||
let status2Slice = getDoubleLineSlice("Compass","GPS",()=>{
|
||||
return (state.compassHeading?Math.round(state.compassHeading):"---") + "°";
|
||||
return getAveragedCompass() + "°";
|
||||
},()=>{
|
||||
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;
|
||||
},200);
|
||||
|
||||
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","",()=>{
|
||||
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 "";
|
||||
});
|
||||
|
@ -798,17 +836,17 @@ let systemSlice = getDoubleLineSlice("RAM","Storage",()=>{
|
|||
return (STORAGE.getFree()/1024).toFixed(0)+"kB";
|
||||
});
|
||||
|
||||
function updateSlices(){
|
||||
let updateSlices = function(){
|
||||
slices = [];
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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(statusSlice);
|
||||
|
@ -816,42 +854,44 @@ function updateSlices(){
|
|||
slices.push(healthSlice);
|
||||
slices.push(systemSlice);
|
||||
slices.push(system2Slice);
|
||||
maxScreens = Math.ceil(slices.length/numberOfSlices);
|
||||
}
|
||||
maxScreens = Math.ceil(slices.length/WIDGETS.gpstrek.getState().numberOfSlices);
|
||||
};
|
||||
|
||||
function clear() {
|
||||
g.clearRect(0,(showWidgets ? 24 : 0), g.getWidth(),g.getHeight());
|
||||
}
|
||||
let lastDrawnScreen;
|
||||
let firstDraw = true;
|
||||
let clear = function() {
|
||||
g.clearRect(Bangle.appRect);
|
||||
};
|
||||
|
||||
function draw(){
|
||||
if (!screen) return;
|
||||
let ypos = showWidgets ? 24 : 0;
|
||||
let draw = function(){
|
||||
if (!global.screen) return;
|
||||
let ypos = Bangle.appRect.y;
|
||||
|
||||
let firstSlice = (screen-1)*numberOfSlices;
|
||||
let firstSlice = (screen-1)*WIDGETS.gpstrek.getState().numberOfSlices;
|
||||
|
||||
updateSlices();
|
||||
|
||||
let force = lastDrawnScreen != screen || firstDraw;
|
||||
if (force){
|
||||
clear();
|
||||
if (showWidgets){
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
}
|
||||
if (firstDraw) Bangle.drawWidgets();
|
||||
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();
|
||||
if (!slice.refresh || slice.refresh() || force) slice.draw(g,0,ypos,sliceHeight,g.getWidth());
|
||||
ypos += sliceHeight+1;
|
||||
g.drawLine(0,ypos-1,g.getWidth(),ypos-1);
|
||||
}
|
||||
|
||||
if (scheduleDraw){
|
||||
drawInTimeout();
|
||||
}
|
||||
firstDraw = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
switchNav();
|
||||
|
||||
g.clear();
|
||||
clear();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "gpstrek",
|
||||
"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!",
|
||||
"icon": "icon.png",
|
||||
"screenshots": [{"url":"screen1.png"},{"url":"screen2.png"},{"url":"screen3.png"},{"url":"screen4.png"}],
|
||||
|
|
|
@ -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');
|
||||
let state = STORAGE.readJSON("gpstrek.state.json")||{};
|
||||
let state = STORAGE.readJSON("gpstrek.state.json");
|
||||
if (!state) {
|
||||
state = {};
|
||||
initState();
|
||||
}
|
||||
let bgChanged = false;
|
||||
|
||||
function saveState(){
|
||||
|
@ -8,12 +30,13 @@ function saveState(){
|
|||
STORAGE.writeJSON("gpstrek.state.json", state);
|
||||
}
|
||||
|
||||
E.on("kill",()=>{
|
||||
if (bgChanged){
|
||||
function onKill(){
|
||||
if (bgChanged || state.route || state.waypoint){
|
||||
saveState();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
E.on("kill", onKill);
|
||||
|
||||
function onPulse(e){
|
||||
state.bpm = e.bpm;
|
||||
|
@ -23,27 +46,47 @@ function onGPS(fix) {
|
|||
if(fix.fix) state.currentPos = fix;
|
||||
}
|
||||
|
||||
function onMag(e) {
|
||||
if (!state.compassHeading) state.compassHeading = e.heading;
|
||||
let radians = function(a) {
|
||||
return a*Math.PI/180;
|
||||
};
|
||||
|
||||
//if (a+180)mod 360 == b then
|
||||
//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)
|
||||
//else
|
||||
//return arctan( (sin(a)+sin(b)) / (cos(a)+cos(b) )
|
||||
let degrees = function(a) {
|
||||
let d = a*180/Math.PI;
|
||||
return (d+360)%360;
|
||||
};
|
||||
|
||||
/*
|
||||
let average;
|
||||
let a = radians(compassHeading);
|
||||
let b = radians(e.heading);
|
||||
if ((a+180) % 360 == b){
|
||||
average = ((a+b)/2 % 360); //can add 180 depending on rotation
|
||||
} else {
|
||||
average = Math.atan( (Math.sin(a)+Math.sin(b))/(Math.cos(a)+Math.cos(b)) );
|
||||
function average(samples){
|
||||
let s = 0;
|
||||
let c = 0;
|
||||
for (let h of samples){
|
||||
s += Math.sin(radians(h));
|
||||
c += Math.cos(radians(h));
|
||||
}
|
||||
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) {
|
||||
|
@ -73,6 +116,16 @@ function onAcc (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){
|
||||
Bangle.removeListener('GPS', onGPS);
|
||||
Bangle.removeListener("HRM", onPulse);
|
||||
|
@ -94,9 +147,9 @@ function start(bg){
|
|||
if (bg){
|
||||
if (!state.active) bgChanged = true;
|
||||
state.active = true;
|
||||
update();
|
||||
saveState();
|
||||
}
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
||||
function stop(bg){
|
||||
|
@ -114,22 +167,10 @@ function stop(bg){
|
|||
Bangle.removeListener("step", onStep);
|
||||
Bangle.removeListener("pressure", onPressure);
|
||||
Bangle.removeListener('accel', onAcc);
|
||||
E.removeListener("kill", onKill);
|
||||
}
|
||||
update();
|
||||
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){
|
||||
|
@ -141,11 +182,15 @@ WIDGETS["gpstrek"]={
|
|||
width:state.active?24:0,
|
||||
resetState: initState,
|
||||
getState: function() {
|
||||
if (state.saved && Date.now() - state.saved > 60000 || !state){
|
||||
initState();
|
||||
}
|
||||
return state;
|
||||
},
|
||||
start:start,
|
||||
stop:stop,
|
||||
draw:function() {
|
||||
update();
|
||||
if (state.active){
|
||||
g.reset();
|
||||
g.drawImage(atob("GBiBAAAAAAAAAAAYAAAYAAAYAAA8AAA8AAB+AAB+AADbAADbAAGZgAGZgAMYwAMYwAcY4AYYYA5+cA3/sB/D+B4AeBAACAAAAAAAAA=="), this.x, this.y);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"description": "Integrates your BangleJS into HomeAssistant.",
|
||||
"icon": "ha.png",
|
||||
"type": "app",
|
||||
"tags": "tool",
|
||||
"tags": "tool,clkinfo",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"custom": "custom.html",
|
||||
|
|
|
@ -7,3 +7,5 @@
|
|||
0.21: Add Settings
|
||||
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.24: Added fast load
|
||||
0.25: Minor code optimization
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
{ // must be inside our own scope here so that when we are unloaded everything disappears
|
||||
|
||||
// ------- Settings file
|
||||
const SETTINGSFILE = "hworldclock.json";
|
||||
var secondsMode;
|
||||
|
@ -153,15 +155,15 @@ function updatePos() {
|
|||
|
||||
function drawSeconds() {
|
||||
// get date
|
||||
var d = new Date();
|
||||
var da = d.toString().split(" ");
|
||||
let d = new Date();
|
||||
let da = d.toString().split(" ");
|
||||
|
||||
// default draw styles
|
||||
g.reset().setBgColor(g.theme.bg).setFontAlign(0, 0);
|
||||
|
||||
// draw time
|
||||
var time = da[4].split(":");
|
||||
var seconds = time[2];
|
||||
let time = da[4].split(":");
|
||||
let seconds = time[2];
|
||||
|
||||
g.setFont("5x9Numeric7Seg",primaryTimeFontSize - 3);
|
||||
if (g.theme.dark) {
|
||||
|
@ -184,15 +186,15 @@ function drawSeconds() {
|
|||
|
||||
function draw() {
|
||||
// get date
|
||||
var d = new Date();
|
||||
var da = d.toString().split(" ");
|
||||
let d = new Date();
|
||||
let da = d.toString().split(" ");
|
||||
|
||||
// default draw styles
|
||||
g.reset().setBgColor(g.theme.bg).setFontAlign(0, 0);
|
||||
|
||||
// draw time
|
||||
var time = da[4].split(":");
|
||||
var hours = time[0],
|
||||
let time = da[4].split(":");
|
||||
let hours = time[0],
|
||||
minutes = time[1];
|
||||
|
||||
|
||||
|
@ -223,7 +225,7 @@ function draw() {
|
|||
// am / PM ?
|
||||
if (_12hour){
|
||||
//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.drawString(ampm, xyCenterSeconds, yAmPm, true);
|
||||
}
|
||||
|
@ -232,14 +234,14 @@ function draw() {
|
|||
|
||||
// draw Day, name of month, 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);
|
||||
g.setFont("Vector", 17);
|
||||
g.drawString(require("locale").dow(new Date(), 1).toUpperCase() + ", " + localDate, xyCenter, yposDate, true);
|
||||
|
||||
g.setFont(font, primaryDateFontSize);
|
||||
// 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
|
||||
offsets.forEach((offset, index) => {
|
||||
|
@ -249,7 +251,7 @@ function draw() {
|
|||
|
||||
|
||||
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
|
||||
const xOffset = 30;
|
||||
g.setFont(font, secondaryTimeFontSize).drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true);
|
||||
|
@ -295,8 +297,18 @@ g.clear();
|
|||
// Init the settings of the app
|
||||
loadMySettings();
|
||||
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
// Show launcher when middle button pressed
|
||||
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.drawWidgets();
|
||||
|
||||
|
@ -307,7 +319,7 @@ draw();
|
|||
|
||||
if (!Bangle.isLocked()) { // Initial state
|
||||
if (showSunInfo) {
|
||||
if (PosInterval != 0) clearInterval(PosInterval);
|
||||
if (PosInterval != 0 && typeof PosInterval != 'undefined') clearInterval(PosInterval);
|
||||
PosInterval = setInterval(updatePos, 60*10E3); // refesh every 10 mins
|
||||
updatePos();
|
||||
}
|
||||
|
@ -333,7 +345,7 @@ if (!Bangle.isLocked()) { // Initial state
|
|||
drawTimeout = undefined;
|
||||
|
||||
if (showSunInfo) {
|
||||
if (PosInterval != 0) clearInterval(PosInterval);
|
||||
if (PosInterval != 0 && typeof PosInterval != 'undefined') clearInterval(PosInterval);
|
||||
PosInterval = setInterval(updatePos, 60*60E3); // refesh every 60 mins
|
||||
updatePos();
|
||||
}
|
||||
|
@ -379,3 +391,4 @@ Bangle.on('lock',on=>{
|
|||
draw(); // draw immediately, queue redraw
|
||||
}
|
||||
});
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
"id": "hworldclock",
|
||||
"name": "Hanks World Clock",
|
||||
"shortName": "Hanks World Clock",
|
||||
"version": "0.23",
|
||||
"version": "0.25",
|
||||
"description": "Current time zone plus up to three others",
|
||||
"allow_emulator":true,
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -14,3 +14,7 @@
|
|||
added timeOut to return to the clock
|
||||
0.11: Cleanup timeout when changing to clock
|
||||
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.
|
||||
|
|
|
@ -186,38 +186,21 @@
|
|||
let i = YtoIdx(e.y);
|
||||
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;
|
||||
const updateTimeout = function(){
|
||||
if (settings.timeOut!="Off"){
|
||||
let time=parseInt(settings.timeOut); //the "s" will be trimmed by the parseInt
|
||||
if (timeout) clearTimeout(timeout);
|
||||
timeout = setTimeout(returnToClock,time*1000);
|
||||
timeout = setTimeout(Bangle.showClock,time*1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
updateTimeout();
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "iconlaunch",
|
||||
"name": "Icon Launcher",
|
||||
"shortName" : "Icon launcher",
|
||||
"version": "0.11",
|
||||
"version": "0.14",
|
||||
"icon": "app.png",
|
||||
"description": "A launcher inspired by smartphones, with an icon-only scrollable menu.",
|
||||
"tags": "tool,system,launcher",
|
||||
|
|
|
@ -16,3 +16,5 @@
|
|||
Fix colorsetting in promises in generated code
|
||||
Some performance improvements by caching lookups
|
||||
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
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
let unlockedDrawInterval = [];
|
||||
let lockedDrawInterval = [];
|
||||
let showWidgets = false;
|
||||
let firstDraw = true;
|
||||
let s = {};
|
||||
// unlocked draw intervals
|
||||
s.udi = [];
|
||||
// locked draw intervals
|
||||
s.ldi = [];
|
||||
// full draw
|
||||
s.fd = true;
|
||||
// performance log
|
||||
s.pl = {};
|
||||
|
||||
{
|
||||
let x = g.getWidth()/2;
|
||||
|
@ -21,12 +26,10 @@ let firstDraw = true;
|
|||
let precompiledJs = eval(require("Storage").read("imageclock.draw.js"));
|
||||
let settings = require('Storage').readJSON("imageclock.json", true) || {};
|
||||
|
||||
let performanceLog = {};
|
||||
|
||||
let startPerfLog = () => {};
|
||||
let endPerfLog = () => {};
|
||||
Bangle.printPerfLog = () => {print("Deactivated");};
|
||||
Bangle.resetPerfLog = () => {performanceLog = {};};
|
||||
Bangle.resetPerfLog = () => {s.pl = {};};
|
||||
|
||||
let colormap={
|
||||
"#000":0,
|
||||
|
@ -64,35 +67,37 @@ let firstDraw = true;
|
|||
if (settings.perflog){
|
||||
startPerfLog = function(name){
|
||||
let time = getTime();
|
||||
if (!performanceLog.start) performanceLog.start={};
|
||||
performanceLog.start[name] = time;
|
||||
if (!s.pl.start) s.pl.start={};
|
||||
s.pl.start[name] = time;
|
||||
};
|
||||
endPerfLog = function (name){
|
||||
endPerfLog = function (name, once){
|
||||
let time = getTime();
|
||||
if (!performanceLog.last) performanceLog.last={};
|
||||
let duration = time - performanceLog.start[name];
|
||||
performanceLog.last[name] = duration;
|
||||
if (!performanceLog.cum) performanceLog.cum={};
|
||||
if (!performanceLog.cum[name]) performanceLog.cum[name] = 0;
|
||||
performanceLog.cum[name] += duration;
|
||||
if (!performanceLog.count) performanceLog.count={};
|
||||
if (!performanceLog.count[name]) performanceLog.count[name] = 0;
|
||||
performanceLog.count[name]++;
|
||||
if (!s.pl.start[name]) return;
|
||||
if (!s.pl.last) s.pl.last={};
|
||||
let duration = time - s.pl.start[name];
|
||||
s.pl.last[name] = duration;
|
||||
if (!s.pl.cum) s.pl.cum={};
|
||||
if (!s.pl.cum[name]) s.pl.cum[name] = 0;
|
||||
s.pl.cum[name] += duration;
|
||||
if (!s.pl.count) s.pl.count={};
|
||||
if (!s.pl.count[name]) s.pl.count[name] = 0;
|
||||
s.pl.count[name]++;
|
||||
if (once){s.pl.start[name] = undefined}
|
||||
};
|
||||
|
||||
Bangle.printPerfLog = function(){
|
||||
let result = "";
|
||||
let keys = [];
|
||||
for (let c in performanceLog.cum){
|
||||
for (let c in s.pl.cum){
|
||||
keys.push(c);
|
||||
}
|
||||
keys.sort();
|
||||
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");
|
||||
|
||||
let delayTimeouts = {};
|
||||
|
@ -609,15 +614,22 @@ let firstDraw = true;
|
|||
|
||||
promise.then(()=>{
|
||||
let currentDrawingTime = Date.now();
|
||||
if (showWidgets){
|
||||
restoreWidgetDraw();
|
||||
}
|
||||
lastDrawTime = Date.now() - start;
|
||||
isDrawing=false;
|
||||
firstDraw=false;
|
||||
s.fd=false;
|
||||
requestRefresh = false;
|
||||
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)=>{
|
||||
print("Error during drawing", e);
|
||||
});
|
||||
|
@ -701,16 +713,16 @@ let firstDraw = true;
|
|||
|
||||
let handleLock = function(isLocked, forceRedraw){
|
||||
//print("isLocked", Bangle.isLocked());
|
||||
for (let i of unlockedDrawInterval){
|
||||
for (let i of s.udi){
|
||||
//print("Clearing unlocked", i);
|
||||
clearInterval(i);
|
||||
}
|
||||
for (let i of lockedDrawInterval){
|
||||
for (let i of s.ldi){
|
||||
//print("Clearing locked", i);
|
||||
clearInterval(i);
|
||||
}
|
||||
unlockedDrawInterval = [];
|
||||
lockedDrawInterval = [];
|
||||
s.udi = [];
|
||||
s.ldi = [];
|
||||
|
||||
if (!isLocked){
|
||||
if (forceRedraw || !redrawEvents || (redrawEvents.includes("unlock"))){
|
||||
|
@ -726,7 +738,7 @@ let firstDraw = true;
|
|||
initialDraw(watchfaceResources, watchface);
|
||||
},unlockedRedraw, (v)=>{
|
||||
//print("New matched unlocked interval", v);
|
||||
unlockedDrawInterval.push(v);
|
||||
s.udi.push(v);
|
||||
}, lastDrawTime);
|
||||
if (!events || events.includes("HRM")) Bangle.setHRMPower(1, "imageclock");
|
||||
if (!events || events.includes("pressure")) Bangle.setBarometerPower(1, 'imageclock');
|
||||
|
@ -744,43 +756,13 @@ let firstDraw = true;
|
|||
initialDraw(watchfaceResources, watchface);
|
||||
},lockedRedraw, (v)=>{
|
||||
//print("New matched locked interval", v);
|
||||
lockedDrawInterval.push(v);
|
||||
s.ldi.push(v);
|
||||
}, lastDrawTime);
|
||||
Bangle.setHRMPower(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")){
|
||||
Bangle.on('pressure', handlePressure);
|
||||
try{
|
||||
|
@ -800,14 +782,6 @@ let firstDraw = true;
|
|||
Bangle.on('charging', handleCharging);
|
||||
}
|
||||
|
||||
let originalWidgetDraw = {};
|
||||
let originalWidgetArea = {};
|
||||
|
||||
let clearWidgetsDraw = function(){
|
||||
//print("Clear widget draw calls");
|
||||
require("widget_utils").hide();
|
||||
}
|
||||
|
||||
handleLock(Bangle.isLocked(), true);
|
||||
|
||||
let setUi = function(){
|
||||
|
@ -819,7 +793,6 @@ let firstDraw = true;
|
|||
Bangle.setHRMPower(0, "imageclock");
|
||||
Bangle.setBarometerPower(0, 'imageclock');
|
||||
|
||||
Bangle.removeListener('swipe', handleSwipe);
|
||||
Bangle.removeListener('lock', handleLock);
|
||||
Bangle.removeListener('charging', handleCharging);
|
||||
Bangle.removeListener('HRM', handleHrm);
|
||||
|
@ -829,31 +802,22 @@ let firstDraw = true;
|
|||
if (initialDrawTimeoutUnlocked) clearTimeout(initialDrawTimeoutUnlocked);
|
||||
if (initialDrawTimeoutLocked) clearTimeout(initialDrawTimeoutLocked);
|
||||
|
||||
for (let i of global.unlockedDrawInterval){
|
||||
for (let i of global.s.udi){
|
||||
//print("Clearing unlocked", i);
|
||||
clearInterval(i);
|
||||
}
|
||||
delete global.unlockedDrawInterval;
|
||||
for (let i of global.lockedDrawInterval){
|
||||
for (let i of global.s.ldi){
|
||||
//print("Clearing locked", i);
|
||||
clearInterval(i);
|
||||
}
|
||||
delete global.lockedDrawInterval;
|
||||
delete global.showWidgets;
|
||||
delete global.firstDraw;
|
||||
|
||||
delete Bangle.printPerfLog;
|
||||
if (settings.perflog){
|
||||
delete Bangle.resetPerfLog;
|
||||
delete performanceLog;
|
||||
}
|
||||
|
||||
cleanupDelays();
|
||||
restoreWidgetDraw();
|
||||
require("widget_utils").show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Bangle.loadWidgets();
|
||||
clearWidgetsDraw();
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
</head>
|
||||
<body>
|
||||
|
||||
<script src="../../core/lib/heatshrink.js"></script>
|
||||
<script src="../../core/lib/imageconverter.js"></script>
|
||||
<script src="../../webtools/heatshrink.js"></script>
|
||||
<script src="../../webtools/imageconverter.js"></script>
|
||||
<script src="../../core/lib/customize.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>
|
||||
<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>
|
||||
<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"/>
|
||||
<label for="debugprints">Add debug prints to generated code</label></br>
|
||||
</p>
|
||||
|
@ -656,7 +658,7 @@
|
|||
var checkcode = "";
|
||||
|
||||
if (!(properties.Redraw && properties.Redraw.Clear)){
|
||||
checkcode = 'firstDraw';
|
||||
checkcode = 's.fd';
|
||||
for (var i = 0; i< layerElements.length; i++){
|
||||
var layerElement = layerElements[i];
|
||||
var referencedElement = elements[layerElements[i].index];
|
||||
|
@ -664,9 +666,9 @@
|
|||
console.log("Check for change:", layerElement, referencedElement);
|
||||
if (layerElement.element.Value){
|
||||
if (elementType == "MultiState" && layerElement.element.Value) {
|
||||
checkcode += '| isChangedMultistate(wf.Collapsed[' + layerElement.index + '].value)';
|
||||
checkcode += '| isChangedMultistate(wf.c[' + layerElement.index + '].value)';
|
||||
} else {
|
||||
checkcode += '| isChangedNumber(wf.Collapsed[' + layerElement.index + '].value)';
|
||||
checkcode += '| isChangedNumber(wf.c[' + layerElement.index + '].value)';
|
||||
}
|
||||
checkForLayerChange = true;
|
||||
}
|
||||
|
@ -693,7 +695,7 @@
|
|||
|
||||
if (c.value.Type == "Once"){
|
||||
if (condition.length > 0) condition += " && ";
|
||||
condition += "firstDraw";
|
||||
condition += "s.fd";
|
||||
}
|
||||
|
||||
var planeName = "p" + plane;
|
||||
|
@ -722,7 +724,7 @@
|
|||
}
|
||||
code += "" + colorsetting;
|
||||
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 += (condition.length > 0 ? "}\n" : "");
|
||||
|
@ -744,9 +746,9 @@
|
|||
console.log("Created data file", resourceDataString, resourceDataOffset, resultJson);
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -1011,22 +1013,45 @@
|
|||
});
|
||||
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 = {
|
||||
id : "imageclock",
|
||||
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},
|
||||
]
|
||||
};
|
||||
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){
|
||||
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);
|
||||
sendCustomizedApp(appDef);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "imageclock",
|
||||
"name": "Imageclock",
|
||||
"shortName": "Imageclock",
|
||||
"version": "0.12",
|
||||
"version": "0.13",
|
||||
"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.",
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</div>
|
||||
|
||||
<script src="../../core/lib/customize.js"></script>
|
||||
<script src="../../core/lib/imageconverter.js"></script>
|
||||
<script src="../../webtools/imageconverter.js"></script>
|
||||
|
||||
<script>
|
||||
var faces = [];
|
||||
|
|
|
@ -8,3 +8,4 @@
|
|||
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.10: Added more bundleIds
|
||||
0.11: Added letters with caron to unicodeRemap, to properly display messages in Czech language
|
||||
|
|
|
@ -127,18 +127,34 @@ E.on('notify',msg=>{
|
|||
'261':"a",
|
||||
'262':"C",
|
||||
'263':"c",
|
||||
'268':"C",
|
||||
'269':"c",
|
||||
'270':"D",
|
||||
'271':"d",
|
||||
'280':"E",
|
||||
'281':"e",
|
||||
'282':"E",
|
||||
'283':"e",
|
||||
'321':"L",
|
||||
'322':"l",
|
||||
'323':"N",
|
||||
'324':"n",
|
||||
'327':"N",
|
||||
'328':"n",
|
||||
'344':"R",
|
||||
'345':"r",
|
||||
'346':"S",
|
||||
'347':"s",
|
||||
'352':"S",
|
||||
'353':"s",
|
||||
'356':"T",
|
||||
'357':"t",
|
||||
'377':"Z",
|
||||
'378':"z",
|
||||
'379':"Z",
|
||||
'380':"z",
|
||||
'381':"Z",
|
||||
'382':"z",
|
||||
};
|
||||
var replacer = ""; //(n)=>print('Unknown unicode '+n.toString(16));
|
||||
//if (appNames[msg.appId]) msg.a
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "ios",
|
||||
"name": "iOS Integration",
|
||||
"version": "0.10",
|
||||
"version": "0.11",
|
||||
"description": "Display notifications/music/etc from iOS devices",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,ios,apple,messages,notifications",
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
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.05: Tell clock widgets to hide.
|
||||
0.06: Widgets can now be made visible by swiping down (#2196)
|
||||
|
|
|
@ -41,9 +41,6 @@ function draw() {
|
|||
yy = ("0"+((new Date()).getFullYear())).substr(-2);
|
||||
g.setFontCustom(font, 48, 8, 521);
|
||||
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
|
||||
Bangle.loadWidgets();
|
||||
require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe
|
||||
draw();
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "MacWatch2",
|
||||
"shortName":"MacWatch2",
|
||||
"icon": "app.png",
|
||||
"version":"0.05",
|
||||
"version":"0.06",
|
||||
"description": "Classic Mac Finder clock",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: Moved message icons from messages into standalone library
|
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 237 B |
After Width: | Height: | Size: 248 B |
After Width: | Height: | Size: 248 B |
After Width: | Height: | Size: 244 B |
After Width: | Height: | Size: 228 B |
After Width: | Height: | Size: 211 B |
After Width: | Height: | Size: 231 B |
After Width: | Height: | Size: 229 B |
After Width: | Height: | Size: 213 B |
After Width: | Height: | Size: 212 B |
After Width: | Height: | Size: 206 B |
After Width: | Height: | Size: 234 B |
After Width: | Height: | Size: 216 B |