diff --git a/.eslintignore b/.eslintignore index fcbea07f9..4af79d129 100644 --- a/.eslintignore +++ b/.eslintignore @@ -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 diff --git a/.gitmodules b/.gitmodules index fd6663d2a..c2c1104c2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/README.md b/README.md index d2f7022e9..1058787bb 100644 --- a/README.md +++ b/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. diff --git a/android.html b/android.html index 93999008f..8a70a46e9 100644 --- a/android.html +++ b/android.html @@ -170,10 +170,10 @@ - + + - diff --git a/apps/advcasio/ChangeLog b/apps/advcasio/ChangeLog index a1b528cf6..fd37c324e 100644 --- a/apps/advcasio/ChangeLog +++ b/apps/advcasio/ChangeLog @@ -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 diff --git a/apps/advcasio/app.js b/apps/advcasio/app.js index 8cb904f90..9d246b7ef 100644 --- a/apps/advcasio/app.js +++ b/apps/advcasio/app.js @@ -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 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.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.setColor(0, 0, 0); - if(dataJson && dataJson.weather) drawWeather(dataJson.weather); - if(dataJson && dataJson.tasks) drawTasks(dataJson.tasks); - + 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.setFontAlign(0,-1); - g.setFont("8x12", 2); + 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); - drawSteps(); - g.setFontAlign(-1,-1); - drawClock(); - drawBattery(); - drawTimer(); - // Hide widgets - for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} + g.setFontAlign(-1,-1); + drawClock(); + drawRocket(); + drawBattery(); + + // 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(); diff --git a/apps/advcasio/metadata.json b/apps/advcasio/metadata.json index 152f47132..25dc1243a 100644 --- a/apps/advcasio/metadata.json +++ b/apps/advcasio/metadata.json @@ -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", @@ -12,7 +12,7 @@ { "url": "screenshot-clock-3.jpg" }, { "url": "screenshot-webapp.jpg" } ], - "supports" : ["BANGLEJS", "BANGLEJS2"], + "supports" : ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", "allow_emulator":true, "storage": [ diff --git a/apps/agenda/metadata.json b/apps/agenda/metadata.json index 6d91455f0..4436e143c 100644 --- a/apps/agenda/metadata.json +++ b/apps/agenda/metadata.json @@ -5,7 +5,7 @@ "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, diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index a65326941..fcb139c94 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -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 diff --git a/apps/android/boot.js b/apps/android/boot.js index 0d1edae99..d4f50d501 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -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 diff --git a/apps/android/metadata.json b/apps/android/metadata.json index ab340340c..ac47449d6 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -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", diff --git a/apps/aptsciclk/metadata.json b/apps/aptsciclk/metadata.json index c450d926e..77e40f843 100644 --- a/apps/aptsciclk/metadata.json +++ b/apps/aptsciclk/metadata.json @@ -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"], diff --git a/apps/aptsciclk/screenshot.png b/apps/aptsciclk/screenshot.png new file mode 100644 index 000000000..4803e4b13 Binary files /dev/null and b/apps/aptsciclk/screenshot.png differ diff --git a/apps/barwatch/ChangeLog b/apps/barwatch/ChangeLog new file mode 100644 index 000000000..7f837e50e --- /dev/null +++ b/apps/barwatch/ChangeLog @@ -0,0 +1 @@ +0.01: First version diff --git a/apps/barwatch/README.md b/apps/barwatch/README.md new file mode 100644 index 000000000..c37caa6e4 --- /dev/null +++ b/apps/barwatch/README.md @@ -0,0 +1,5 @@ +# BarWatch - an experimental watch + +For too long the watches have shown the time with digits or hands. No more! +With this stylish watch the time is represented by bars. Up to 24 as the day goes by. +Practical? Not really, but a different look! \ No newline at end of file diff --git a/apps/barwatch/app-icon.js b/apps/barwatch/app-icon.js new file mode 100644 index 000000000..82416ee28 --- /dev/null +++ b/apps/barwatch/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("l0uwkE/4A/AH4A/AB0gicQmUB+EPgEigExh8gj8A+ECAgMQn4WCgcACyotWC34W/C34W/CycACw0wgYWFBYIWCAAc/+YGHCAgNFACkxl8hGYwAMLYUvCykQC34WycoIW/C34W0gAWTmUjkUzkbmSAFY=")) \ No newline at end of file diff --git a/apps/barwatch/app.js b/apps/barwatch/app.js new file mode 100644 index 000000000..e0ed15ce6 --- /dev/null +++ b/apps/barwatch/app.js @@ -0,0 +1,76 @@ +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + + +function draw() { + g.reset(); + + if(g.theme.dark){ + g.setColor(1,1,1); + }else{ + g.setColor(0,0,0); + } + + // work out how to display the current time + var d = new Date(); + var h = d.getHours(), m = d.getMinutes(); + + // hour bars + var bx_offset = 10, by_offset = 35; + var b_width = 8, b_height = 60; + var b_space = 5; + + for(var i=0; i 11){ + by_offset = 105; + } + var iter = i % 12; + //console.log(iter); + g.fillRect(bx_offset+(b_width*(iter+1))+(b_space*iter), + by_offset, + bx_offset+(b_width*iter)+(b_space*iter), + by_offset+b_height); + } + + // minute bar + if(h > 11){ + by_offset = 105; + } + var m_bar = h % 12; + if(m != 0){ + g.fillRect(bx_offset+(b_width*(m_bar+1))+(b_space*m_bar), + by_offset+b_height-m, + bx_offset+(b_width*m_bar)+(b_space*m_bar), + by_offset+b_height); + } + + // queue draw in one minute + queueDraw(); +} + +// Clear the screen once, at startup +g.clear(); +// draw immediately at first +draw(); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +Bangle.setUI("clock"); +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/barwatch/app.png b/apps/barwatch/app.png new file mode 100644 index 000000000..134de9424 Binary files /dev/null and b/apps/barwatch/app.png differ diff --git a/apps/barwatch/metadata.json b/apps/barwatch/metadata.json new file mode 100644 index 000000000..adcd44107 --- /dev/null +++ b/apps/barwatch/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "barwatch", + "name": "BarWatch", + "shortName":"BarWatch", + "version":"0.01", + "description": "A watch that displays the time using bars. One bar for each hour.", + "readme": "README.md", + "icon": "screenshot.png", + "tags": "clock", + "type": "clock", + "allow_emulator":true, + "screenshots" : [ { "url": "screenshot.png" } ], + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"barwatch.app.js","url":"app.js"}, + {"name":"barwatch.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/barwatch/screenshot.png b/apps/barwatch/screenshot.png new file mode 100644 index 000000000..305138252 Binary files /dev/null and b/apps/barwatch/screenshot.png differ diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 7b95d8686..0eaf517d9 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -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 diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index e9a24f5f5..ad58437ec 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -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 diff --git a/apps/boot/metadata.json b/apps/boot/metadata.json index 339f8503e..bd39beb7f 100644 --- a/apps/boot/metadata.json +++ b/apps/boot/metadata.json @@ -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", diff --git a/apps/bthrm/ChangeLog b/apps/bthrm/ChangeLog index c7b5a865f..99cf0c670 100644 --- a/apps/bthrm/ChangeLog +++ b/apps/bthrm/ChangeLog @@ -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 diff --git a/apps/bthrm/default.json b/apps/bthrm/default.json index 2c729ec68..79605b412 100644 --- a/apps/bthrm/default.json +++ b/apps/bthrm/default.json @@ -17,5 +17,6 @@ "gracePeriodConnect": 0, "gracePeriodService": 0, "gracePeriodRequest": 0, - "bonding": false + "bonding": false, + "active": true } diff --git a/apps/bthrm/lib.js b/apps/bthrm/lib.js index 13e8b0383..f5e0e1e5b 100644 --- a/apps/bthrm/lib.js +++ b/apps/bthrm/lib.js @@ -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; @@ -281,13 +281,13 @@ exports.enable = () => { log("Disconnect: " + reason); log("GATT", gatt); log("Characteristics", characteristics); - + var retryTimeResetNeeded = true; retryTimeResetNeeded &= reason != "Connection Timeout"; retryTimeResetNeeded &= reason != "No device found matching filters"; clearRetryTimeout(retryTimeResetNeeded); supportedCharacteristics["0x2a37"].active = false; - 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,8 +387,14 @@ 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) + } } }); } diff --git a/apps/bthrm/metadata.json b/apps/bthrm/metadata.json index df0ac1fc1..18c34ea33 100644 --- a/apps/bthrm/metadata.json +++ b/apps/bthrm/metadata.json @@ -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", diff --git a/apps/bthrm/recorder.js b/apps/bthrm/recorder.js index ed36b5aef..fcfed47c3 100644 --- a/apps/bthrm/recorder.js +++ b/apps/bthrm/recorder.js @@ -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) diff --git a/apps/bthrm/settings.js b/apps/bthrm/settings.js index fb5aa04da..459ed29fc 100644 --- a/apps/bthrm/settings.js +++ b/apps/bthrm/settings.js @@ -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); } }; diff --git a/apps/bwclk/metadata.json b/apps/bwclk/metadata.json index fbae0e1e7..fa0f7b01f 100644 --- a/apps/bwclk/metadata.json +++ b/apps/bwclk/metadata.json @@ -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": [ diff --git a/apps/calibration/metadata.json b/apps/calibration/metadata.json index b60650300..f428bd538 100644 --- a/apps/calibration/metadata.json +++ b/apps/calibration/metadata.json @@ -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", diff --git a/apps/cassioWatch/ChangeLog b/apps/cassioWatch/ChangeLog index 883bd2585..1180554ff 100644 --- a/apps/cassioWatch/ChangeLog +++ b/apps/cassioWatch/ChangeLog @@ -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 diff --git a/apps/cassioWatch/README.md b/apps/cassioWatch/README.md index aaeb3f122..6c13cdcac 100644 --- a/apps/cassioWatch/README.md +++ b/apps/cassioWatch/README.md @@ -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 diff --git a/apps/cassioWatch/app.js b/apps/cassioWatch/app.js index 91f6737ad..19dd883d2 100644 --- a/apps/cassioWatch/app.js +++ b/apps/cassioWatch/app.js @@ -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(); diff --git a/apps/cassioWatch/metadata.json b/apps/cassioWatch/metadata.json index abfee9b93..5ac4502fd 100644 --- a/apps/cassioWatch/metadata.json +++ b/apps/cassioWatch/metadata.json @@ -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"], diff --git a/apps/circlesclock/ChangeLog b/apps/circlesclock/ChangeLog index 26e531be7..ea7266442 100644 --- a/apps/circlesclock/ChangeLog +++ b/apps/circlesclock/ChangeLog @@ -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 diff --git a/apps/circlesclock/app.js b/apps/circlesclock/app.js index 25e34cce0..d4a170ce8 100644 --- a/apps/circlesclock/app.js +++ b/apps/circlesclock/app.js @@ -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() { /* @@ -111,7 +124,7 @@ function hideWidgets() { function draw() { g.clear(true); let widgetUtils; - + try { widgetUtils = require("widget_utils"); } catch (e) { @@ -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; diff --git a/apps/circlesclock/default.json b/apps/circlesclock/default.json index 6247c058c..0a41bf9e0 100644 --- a/apps/circlesclock/default.json +++ b/apps/circlesclock/default.json @@ -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 + } diff --git a/apps/circlesclock/metadata.json b/apps/circlesclock/metadata.json index c2d3b3364..490d6c4fb 100644 --- a/apps/circlesclock/metadata.json +++ b/apps/circlesclock/metadata.json @@ -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"}], diff --git a/apps/circlesclock/settings.js b/apps/circlesclock/settings.js index 0aa8dc826..ca26cb295 100644 --- a/apps/circlesclock/settings.js +++ b/apps/circlesclock/settings.js @@ -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(); }); diff --git a/apps/clkinfosunrise/ChangeLog b/apps/clkinfosunrise/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/clkinfosunrise/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/clkinfosunrise/app.png b/apps/clkinfosunrise/app.png new file mode 100644 index 000000000..a1d53946d Binary files /dev/null and b/apps/clkinfosunrise/app.png differ diff --git a/apps/clkinfosunrise/clkinfo.js b/apps/clkinfosunrise/clkinfo.js new file mode 100644 index 000000000..1454a83f3 --- /dev/null +++ b/apps/clkinfosunrise/clkinfo.js @@ -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 : () => {} + } + ] + }; +}) diff --git a/apps/clkinfosunrise/metadata.json b/apps/clkinfosunrise/metadata.json new file mode 100644 index 000000000..f8b68e11f --- /dev/null +++ b/apps/clkinfosunrise/metadata.json @@ -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"} + ] +} diff --git a/apps/demoapp/metadata.json b/apps/demoapp/metadata.json index df6554ef5..2fb30f718 100644 --- a/apps/demoapp/metadata.json +++ b/apps/demoapp/metadata.json @@ -12,6 +12,5 @@ "storage": [ {"name":"demoapp.app.js","url":"app.js"}, {"name":"demoapp.img","url":"app-icon.js","evaluate":true} - ], - "sortorder": -9 + ] } diff --git a/apps/dragboard/ChangeLog b/apps/dragboard/ChangeLog index 48a1ffb03..265094e87 100644 --- a/apps/dragboard/ChangeLog +++ b/apps/dragboard/ChangeLog @@ -3,3 +3,4 @@ 0.03: Made the code shorter and somewhat more readable by writing some functions. Also made it work as a library where it returns the text once finished. The keyboard is now made to exit correctly when the 'back' event is called. The keyboard now uses theme colors correctly, although it still looks best with dark theme. The numbers row is now solidly green - except for highlights. 0.04: Now displays the opened text string at launch. 0.05: Now scrolls text when string gets longer than screen width. +0.06: The code is now more reliable and the input snappier. Widgets will be drawn if present. diff --git a/apps/dragboard/lib.js b/apps/dragboard/lib.js index b9b19f982..220f075d7 100644 --- a/apps/dragboard/lib.js +++ b/apps/dragboard/lib.js @@ -1,12 +1,9 @@ -//Keep banglejs screen on for 100 sec at 0.55 power level for development purposes -//Bangle.setLCDTimeout(30); -//Bangle.setLCDPower(1); - exports.input = function(options) { options = options||{}; var text = options.text; if ("string"!=typeof text) text=""; - + + var R = Bangle.appRect; var BGCOLOR = g.theme.bg; var HLCOLOR = g.theme.fg; var ABCCOLOR = g.toColor(1,0,0);//'#FF0000'; @@ -17,35 +14,38 @@ exports.input = function(options) { var SMALLFONTWIDTH = parseInt(SMALLFONT.charAt(0)*parseInt(SMALLFONT.charAt(-1))); var ABC = 'abcdefghijklmnopqrstuvwxyz'.toUpperCase(); - var ABCPADDING = (g.getWidth()-6*ABC.length)/2; + var ABCPADDING = ((R.x+R.w)-6*ABC.length)/2; var NUM = ' 1234567890!?,.- '; var NUMHIDDEN = ' 1234567890!?,.- '; - var NUMPADDING = (g.getWidth()-6*NUM.length)/2; + var NUMPADDING = ((R.x+R.w)-6*NUM.length)/2; var rectHeight = 40; - var delSpaceLast; function drawAbcRow() { g.clear(); + try { // Draw widgets if they are present in the current app. + if (WIDGETS) Bangle.drawWidgets(); + } catch (_) {} g.setFont(SMALLFONT); g.setColor(ABCCOLOR); - g.drawString(ABC, ABCPADDING, g.getHeight()/2); - g.fillRect(0, g.getHeight()-26, g.getWidth(), g.getHeight()); + g.setFontAlign(-1, -1, 0); + g.drawString(ABC, ABCPADDING, (R.y+R.h)/2); + g.fillRect(0, (R.y+R.h)-26, (R.x+R.w), (R.y+R.h)); } function drawNumRow() { g.setFont(SMALLFONT); g.setColor(NUMCOLOR); - g.drawString(NUM, NUMPADDING, g.getHeight()/4); + g.setFontAlign(-1, -1, 0); + g.drawString(NUM, NUMPADDING, (R.y+R.h)/4); - g.fillRect(NUMPADDING, g.getHeight()-rectHeight*4/3, g.getWidth()-NUMPADDING, g.getHeight()-rectHeight*2/3); + g.fillRect(NUMPADDING, (R.y+R.h)-rectHeight*4/3, (R.x+R.w)-NUMPADDING, (R.y+R.h)-rectHeight*2/3); } function updateTopString() { - "ram" g.setColor(BGCOLOR); g.fillRect(0,4+20,176,13+20); g.setColor(0.2,0,0); @@ -54,13 +54,10 @@ exports.input = function(options) { g.setColor(0.7,0,0); g.fillRect(rectLen+5,4+20,rectLen+10,13+20); g.setColor(1,1,1); + g.setFontAlign(-1, -1, 0); g.drawString(text.length<=27? text.substr(-27, 27) : '<- '+text.substr(-24,24), 5, 5+20); } - drawAbcRow(); - drawNumRow(); - updateTopString(); - var abcHL; var abcHLPrev = -10; var numHL; @@ -68,194 +65,182 @@ exports.input = function(options) { var type = ''; var typePrev = ''; var largeCharOffset = 6; - + function resetChars(char, HLPrev, typePadding, heightDivisor, rowColor) { - "ram" + "ram"; // Small character in list g.setColor(rowColor); g.setFont(SMALLFONT); - g.drawString(char, typePadding + HLPrev*6, g.getHeight()/heightDivisor); + g.setFontAlign(-1, -1, 0); + g.drawString(char, typePadding + HLPrev*6, (R.y+R.h)/heightDivisor); // Large character g.setColor(BGCOLOR); - g.fillRect(0,g.getHeight()/3,176,g.getHeight()/3+24); - //g.drawString(charSet.charAt(HLPrev), typePadding + HLPrev*6 -largeCharOffset, g.getHeight()/3);; //Old implementation where I find the shape and place of letter to remove instead of just a rectangle. + g.fillRect(0,(R.y+R.h)/3,176,(R.y+R.h)/3+24); + //g.drawString(charSet.charAt(HLPrev), typePadding + HLPrev*6 -largeCharOffset, (R.y+R.h)/3);; //Old implementation where I find the shape and place of letter to remove instead of just a rectangle. // mark in the list } function showChars(char, HL, typePadding, heightDivisor) { - "ram" + "ram"; // mark in the list g.setColor(HLCOLOR); g.setFont(SMALLFONT); - if (char != 'del' && char != 'space') g.drawString(char, typePadding + HL*6, g.getHeight()/heightDivisor); + g.setFontAlign(-1, -1, 0); + if (char != 'del' && char != 'space') g.drawString(char, typePadding + HL*6, (R.y+R.h)/heightDivisor); // show new large character g.setFont(BIGFONT); - g.drawString(char, typePadding + HL*6 -largeCharOffset, g.getHeight()/3); + g.drawString(char, typePadding + HL*6 -largeCharOffset, (R.y+R.h)/3); g.setFont(SMALLFONT); } - + + function initDraw() { + //var R = Bangle.appRect; // To make sure it's properly updated. Not sure if this is needed. + drawAbcRow(); + drawNumRow(); + updateTopString(); + } + initDraw(); + //setTimeout(initDraw, 0); // So Bangle.appRect reads the correct environment. It would draw off to the side sometimes otherwise. + function changeCase(abcHL) { g.setColor(BGCOLOR); - g.drawString(ABC, ABCPADDING, g.getHeight()/2); + g.setFontAlign(-1, -1, 0); + g.drawString(ABC, ABCPADDING, (R.y+R.h)/2); if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) ABC = ABC.toLowerCase(); else ABC = ABC.toUpperCase(); g.setColor(ABCCOLOR); - g.drawString(ABC, ABCPADDING, g.getHeight()/2); + g.drawString(ABC, ABCPADDING, (R.y+R.h)/2); } return new Promise((resolve,reject) => { - // Interpret touch input + // Interpret touch input Bangle.setUI({ - mode: 'custom', - back: ()=>{ - Bangle.setUI(); - g.clearRect(Bangle.appRect); - resolve(text); - }, - drag: function(event) { + mode: 'custom', + back: ()=>{ + Bangle.setUI(); + g.clearRect(Bangle.appRect); + resolve(text); + }, + drag: function(event) { + "ram"; + // ABCDEFGHIJKLMNOPQRSTUVWXYZ + // Choose character by draging along red rectangle at bottom of screen + if (event.y >= ( (R.y+R.h) - 12 )) { + // Translate x-position to character + if (event.x < ABCPADDING) { abcHL = 0; } + else if (event.x >= 176-ABCPADDING) { abcHL = 25; } + else { abcHL = Math.floor((event.x-ABCPADDING)/6); } - // ABCDEFGHIJKLMNOPQRSTUVWXYZ - // Choose character by draging along red rectangle at bottom of screen - if (event.y >= ( g.getHeight() - 12 )) { - // Translate x-position to character - if (event.x < ABCPADDING) { abcHL = 0; } - else if (event.x >= 176-ABCPADDING) { abcHL = 25; } - else { abcHL = Math.floor((event.x-ABCPADDING)/6); } + // Datastream for development purposes + //print(event.x, event.y, event.b, ABC.charAt(abcHL), ABC.charAt(abcHLPrev)); - // Datastream for development purposes - //print(event.x, event.y, event.b, ABC.charAt(abcHL), ABC.charAt(abcHLPrev)); + // Unmark previous character and mark the current one... + // Handling switching between letters and numbers/punctuation + if (typePrev != 'abc') resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); - // Unmark previous character and mark the current one... - // Handling switching between letters and numbers/punctuation - if (typePrev != 'abc') resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); - - if (abcHL != abcHLPrev) { - resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); - showChars(ABC.charAt(abcHL), abcHL, ABCPADDING, 2); + if (abcHL != abcHLPrev) { + resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + showChars(ABC.charAt(abcHL), abcHL, ABCPADDING, 2); } - // Print string at top of screen - if (event.b == 0) { - text = text + ABC.charAt(abcHL); - updateTopString(); - - // Autoswitching letter case - if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) changeCase(abcHL); - } - // Update previous character to current one - abcHLPrev = abcHL; - typePrev = 'abc'; - } - - - - - - - - - - // 12345678901234567890 - // Choose number or puctuation by draging on green rectangle - else if ((event.y < ( g.getHeight() - 12 )) && (event.y > ( g.getHeight() - 52 ))) { - // Translate x-position to character - if (event.x < NUMPADDING) { numHL = 0; } - else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; } - else { numHL = Math.floor((event.x-NUMPADDING)/6); } - - // Datastream for development purposes - //print(event.x, event.y, event.b, NUM.charAt(numHL), NUM.charAt(numHLPrev)); - - // Unmark previous character and mark the current one... - // Handling switching between letters and numbers/punctuation - if (typePrev != 'num') resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); - - if (numHL != numHLPrev) { - resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); - showChars(NUM.charAt(numHL), numHL, NUMPADDING, 4); - } - // Print string at top of screen - if (event.b == 0) { - g.setColor(HLCOLOR); - // Backspace if releasing before list of numbers/punctuation - if (event.x < NUMPADDING) { - // show delete sign - showChars('del', 0, g.getWidth()/2 +6 -27 , 4); - delSpaceLast = 1; - text = text.slice(0, -1); - updateTopString(); - //print(text); - } - // Append space if releasing after list of numbers/punctuation - else if (event.x > g.getWidth()-NUMPADDING) { - //show space sign - showChars('space', 0, g.getWidth()/2 +6 -6*3*5/2 , 4); - delSpaceLast = 1; - text = text + ' '; - updateTopString(); - //print(text); - } - // Append selected number/punctuation - else { - text = text + NUMHIDDEN.charAt(numHL); + // Print string at top of screen + if (event.b == 0) { + text = text + ABC.charAt(abcHL); updateTopString(); // Autoswitching letter case - if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) changeCase(); + if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) changeCase(abcHL); } + // Update previous character to current one + abcHLPrev = abcHL; + typePrev = 'abc'; } - // Update previous character to current one - numHLPrev = numHL; - typePrev = 'num'; - } - - - - - - - - - // Make a space or backspace by swiping right or left on screen above green rectangle - else if (event.y > 20+4) { - if (event.b == 0) { - g.setColor(HLCOLOR); - if (event.x < g.getWidth()/2) { - resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + + // 12345678901234567890 + // Choose number or puctuation by draging on green rectangle + else if ((event.y < ( (R.y+R.h) - 12 )) && (event.y > ( (R.y+R.h) - 52 ))) { + // Translate x-position to character + if (event.x < NUMPADDING) { numHL = 0; } + else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; } + else { numHL = Math.floor((event.x-NUMPADDING)/6); } + + // Datastream for development purposes + //print(event.x, event.y, event.b, NUM.charAt(numHL), NUM.charAt(numHLPrev)); + + // Unmark previous character and mark the current one... + // Handling switching between letters and numbers/punctuation + if (typePrev != 'num') resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + + if (numHL != numHLPrev) { resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); - - // show delete sign - showChars('del', 0, g.getWidth()/2 +6 -27 , 4); - delSpaceLast = 1; - - // Backspace and draw string upper right corner - text = text.slice(0, -1); - updateTopString(); - if (text.length==0) changeCase(abcHL); - //print(text, 'undid'); + showChars(NUM.charAt(numHL), numHL, NUMPADDING, 4); } - else { - resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); - resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); + // Print string at top of screen + if (event.b == 0) { + g.setColor(HLCOLOR); + // Backspace if releasing before list of numbers/punctuation + if (event.x < NUMPADDING) { + // show delete sign + showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4); + delSpaceLast = 1; + text = text.slice(0, -1); + updateTopString(); + //print(text); + } + // Append space if releasing after list of numbers/punctuation + else if (event.x > (R.x+R.w)-NUMPADDING) { + //show space sign + showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4); + delSpaceLast = 1; + text = text + ' '; + updateTopString(); + //print(text); + } + // Append selected number/punctuation + else { + text = text + NUMHIDDEN.charAt(numHL); + updateTopString(); - //show space sign - showChars('space', 0, g.getWidth()/2 +6 -6*3*5/2 , 4); - delSpaceLast = 1; + // Autoswitching letter case + if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) changeCase(); + } + } + // Update previous character to current one + numHLPrev = numHL; + typePrev = 'num'; + } - // Append space and draw string upper right corner - text = text + NUMHIDDEN.charAt(0); - updateTopString(); - //print(text, 'made space'); + // Make a space or backspace by swiping right or left on screen above green rectangle + else if (event.y > 20+4) { + if (event.b == 0) { + g.setColor(HLCOLOR); + if (event.x < (R.x+R.w)/2) { + resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); + + // show delete sign + showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4); + delSpaceLast = 1; + + // Backspace and draw string upper right corner + text = text.slice(0, -1); + updateTopString(); + if (text.length==0) changeCase(abcHL); + //print(text, 'undid'); + } + else { + resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); + + //show space sign + showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4); + delSpaceLast = 1; + + // Append space and draw string upper right corner + text = text + NUMHIDDEN.charAt(0); + updateTopString(); + //print(text, 'made space'); + } } } } - } - }); -}); -/* return new Promise((resolve,reject) => { - Bangle.setUI({mode:"custom", back:()=>{ - Bangle.setUI(); - g.clearRect(Bangle.appRect); - Bangle.setUI(); - resolve(text); - }}); - }); */ - + }); + }); }; diff --git a/apps/dragboard/metadata.json b/apps/dragboard/metadata.json index f9c73ddde..64b6dbe18 100644 --- a/apps/dragboard/metadata.json +++ b/apps/dragboard/metadata.json @@ -1,6 +1,6 @@ { "id": "dragboard", "name": "Dragboard", - "version":"0.05", + "version":"0.06", "description": "A library for text input via swiping keyboard", "icon": "app.png", "type":"textinput", diff --git a/apps/dtlaunch/ChangeLog b/apps/dtlaunch/ChangeLog index 1054aa387..044b8c35f 100644 --- a/apps/dtlaunch/ChangeLog +++ b/apps/dtlaunch/ChangeLog @@ -16,3 +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: 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. + diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js index 48d9b2f38..a7a318c18 100644 --- a/apps/dtlaunch/app-b2.js +++ b/apps/dtlaunch/app-b2.js @@ -1,52 +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, - oneClickExit: 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) || {}); -if (settings.oneClickExit) { - var buttonWatch = setWatch(_=> returnToClock(), BTN1, {edge: 'falling'}); -} - -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.nameb.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.nameb.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); @@ -70,103 +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;jmaxPage) 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); } -}; -Bangle.on("swipe",swipeListenerDt); + }; -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=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)=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; } -}; -Bangle.on("touch",touchListenerDt); + }; -const returnToClock = function() { - Bangle.setUI(); - if (buttonWatch) { - clearWatch(buttonWatch); - delete buttonWatch; - } - if (timeoutToClock) { - clearTimeout(timeoutToClock); - delete timeoutToClock; - } - Bangle.removeListener("swipe", swipeListenerDt); - Bangle.removeListener("touch", touchListenerDt); - var apps = []; - delete apps; - delete returnToClock; - setTimeout(eval, 0, s.read(".bootcde")); -}; + Bangle.setUI({ + mode : 'custom', + back : Bangle.showClock, + swipe : swipeListenerDt, + touch : touchListenerDt, + remove : ()=>{if (timeoutToClock) clearTimeout(timeoutToClock);} + }); -// 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 diff --git a/apps/dtlaunch/metadata.json b/apps/dtlaunch/metadata.json index b71f4ca9b..b69a1a5e6 100644 --- a/apps/dtlaunch/metadata.json +++ b/apps/dtlaunch/metadata.json @@ -1,7 +1,7 @@ { "id": "dtlaunch", "name": "Desktop Launcher", - "version": "0.17", + "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", diff --git a/apps/dtlaunch/settings-b2.js b/apps/dtlaunch/settings-b2.js index 80ad0414a..24959df8c 100644 --- a/apps/dtlaunch/settings-b2.js +++ b/apps/dtlaunch/settings-b2.js @@ -5,7 +5,6 @@ showClocks: true, showLaunchers: true, direct: false, - oneClickExit:false, swipeExit: false, timeOut: "Off" }, require('Storage').readJSON(FILE, true) || {}); @@ -47,13 +46,6 @@ writeSettings(); } }, - /*LANG*/'One click exit': { - value: settings.oneClickExit, - onchange: v => { - settings.oneClickExit = v; - writeSettings(); - } - }, /*LANG*/'Time Out': { // Adapted from Icon Launcher value: timeOutChoices.indexOf(settings.timeOut), min: 0, @@ -63,6 +55,6 @@ settings.timeOut = timeOutChoices[v]; writeSettings(); } - }, + } }); }); diff --git a/apps/fwupdate/ChangeLog b/apps/fwupdate/ChangeLog index 06f84a11a..ea0b48eb9 100644 --- a/apps/fwupdate/ChangeLog +++ b/apps/fwupdate/ChangeLog @@ -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 diff --git a/apps/fwupdate/custom.html b/apps/fwupdate/custom.html index 8ef117933..31eb4a256 100644 --- a/apps/fwupdate/custom.html +++ b/apps/fwupdate/custom.html @@ -3,7 +3,7 @@ -

This tool allows you to update the bootloader on Bangle.js 2 devices +

This tool allows you to update the firmware on Bangle.js 2 devices from within the App Loader.

@@ -12,27 +12,41 @@ see the Bangle.js 1 instructions

    -

    Your current firmware version is unknown and bootloader is unknown

    +

    Your current firmware version is unknown and DFU is unknown

- + + + + + + + + + + diff --git a/apps/mylocation/metadata.json b/apps/mylocation/metadata.json index 4ab9aa37e..bef5b983c 100644 --- a/apps/mylocation/metadata.json +++ b/apps/mylocation/metadata.json @@ -4,11 +4,12 @@ "icon": "app.png", "type": "settings", "screenshots": [{"url":"screenshot_1.png"}], - "version":"0.07", - "description": "Sets and stores the lat and long of your preferred City or it can be set from the GPS. mylocation.json can be used by other apps that need your main location lat and lon. See README", + "version":"0.08", + "description": "Sets and stores the latitude and longitude of your preferred City. It can be set from GPS or webinterface. `mylocation.json` can be used by other apps that need your main location. See README for details.", "readme": "README.md", "tags": "tool,utility", "supports": ["BANGLEJS", "BANGLEJS2"], + "custom": "custom.html","custom": "custom.html", "storage": [ {"name":"mylocation.settings.js","url":"settings.js"} ], diff --git a/apps/nixie/nixie.info b/apps/nixie/nixie.info deleted file mode 100644 index 66f5ff2a5..000000000 --- a/apps/nixie/nixie.info +++ /dev/null @@ -1,10 +0,0 @@ -{ -"id":"jvNixie", -"name":"Nixie Clock", -"type":"clock", -"src":"nixie.app.js", -"icon": "nixie.img", -"sortorder":1, -"version":"1.1", -"files":"nixie.info,nixie.app.js,nixie.img, m_vatch.js" -} diff --git a/apps/openhaystack/ChangeLog b/apps/openhaystack/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/openhaystack/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/openhaystack/README.md b/apps/openhaystack/README.md new file mode 100644 index 000000000..e2d5e2212 --- /dev/null +++ b/apps/openhaystack/README.md @@ -0,0 +1,18 @@ +# OpenHaystack (AirTag) + +Copy a base64 key from https://github.com/seemoo-lab/openhaystack and make your Bangle.js trackable as if it's an AirTag + +Based on https://github.com/seemoo-lab/openhaystack/issues/59 + +## Usage + +* Follow the steps on https://github.com/seemoo-lab/openhaystack#how-to-use-openhaystack to install OpenHaystack and get a unique base64 code +* Click the ≡ icon next to `OpenHaystack (AirTag)` +* Paste in the base64 code +* Click `Upload` + +## Note + +This code changes your Bangle's MAC address, so while it still advertises with +the same `Bangle.js abcd` name, devices that were previously paired with it +won't automatically reconnect it until you re-pair. diff --git a/apps/openhaystack/custom.html b/apps/openhaystack/custom.html new file mode 100644 index 000000000..f56e94a98 --- /dev/null +++ b/apps/openhaystack/custom.html @@ -0,0 +1,56 @@ + + + + + + +

Follow the steps on https://github.com/seemoo-lab/openhaystack to install OpenHaystack and get a unique base64 code

+

Then paste the key in below and click Upload

+ +

Base64 key:

+

 

+ +

Click

+ + + + + + + diff --git a/apps/openhaystack/icon.png b/apps/openhaystack/icon.png new file mode 100644 index 000000000..f5e4f7f3b Binary files /dev/null and b/apps/openhaystack/icon.png differ diff --git a/apps/openhaystack/metadata.json b/apps/openhaystack/metadata.json new file mode 100644 index 000000000..5573529f7 --- /dev/null +++ b/apps/openhaystack/metadata.json @@ -0,0 +1,14 @@ +{ "id": "openhaystack", + "name": "OpenHaystack (AirTag)", + "icon": "icon.png", + "version":"0.01", + "description": "Copy a base64 key from https://github.com/seemoo-lab/openhaystack and make your Bangle.js trackable as if it's an AirTag", + "tags": "openhaystack,bluetooth,ble,tracking,airtag", + "type": "bootloader", + "custom": "custom.html", + "readme": "README.md", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"openhaystack.boot.js"} + ] +} diff --git a/apps/openstmap/custom.html b/apps/openstmap/custom.html index 3bba997e7..c1a161458 100644 --- a/apps/openstmap/custom.html +++ b/apps/openstmap/custom.html @@ -43,8 +43,8 @@ - - + + - - + + + + - diff --git a/loader.js b/loader.js index c0cd15de5..251f94ff1 100644 --- a/loader.js +++ b/loader.js @@ -162,7 +162,7 @@ window.addEventListener('load', (event) => {