Making Bangle.js more responsive - please test!

Posted on
Page
of 2
/ 2
Next
  • At the moment Bangle.js responds to button presses when you release the button, rather than when you initially press it. It's because initially some users wanted the ability to detect a long press of the button (which you can't know when the button is first pressed), but the time taken to press the button and release it is quite noticeable, and does really make the Bangle seem less responsive than it could be.

    Despite detecting long presses seeming like a good idea, very few apps actually do it, so thyttan has been working on seeing what happens if we respond when the button is pressed rather than released: https://github.com/espruino/BangleApps/issues/3435 (apps could still use button release if they need to detect long presses)

    You can now test this out by installing the https://banglejs.com/apps/?id=setuichange app - and I'd really like to her your thoughts after trying it...

    Does this break anything?

    • run/runplus currently start/stop recording when the button is pressed (but you hold the button to exit) - so those need tweaking
    • One or two apps (like spotrem) directly access the button event and then won't see an improvement here

    But personally, I feel like the difference in perceived speed when moving from clock->launcher and back (which is what happens 80% of the time) makes this more than worth any small inconveniences

  • A couple of things I've noticed -

    1. After rebooting two clicks are needed to get to the launcher from the clock.
    2. With a keyboard I'm currently working on I'm using the button to
      exit and return the text but Bangle.setUI({mode:"custom", btn:()=>{}}) is throwing

      Uncaught Error: Unhandled promise rejection: Error: Can't read property 'push' of undefined
      at line 12 col 2054 in .boot0
      ...tions.btn)Bangle.btnWatches.push(setWatch(options.btn.bind(o...
      
  • Thanks @woogal!

    Regarding 1:

    • What clock app do you use?
    • What launcher app do you use?
    • Just to make sure, you're saying you need to press once to unlock, an then two times more to go from clock to launcher?

    Regarding 2:
    Oversight on my part. I'll fix that and push an update in a bit.

  • @woogal

    I've pushed a version 0.02 with a fix to the development app loader: https://espruino.github.io/BangleApps/?id=setuichange

    I also realized there's a bug where if you do e.g.

    Bangle.setUI({
    mode: "updown",
    drag: function() { ... }
    })
    

    We will add two drag handlers, the mode "updown" one and the custom one. But we will only keep the custom handler in Bangle.dragHandler. So if we call Bangle.setUI() to clear everything we will leave the "updown" drag handler still active.

    @Gordon what do you reccon:

    • should we make Bangle.dragHandler hold an array instead? Should it be renamed in that case?
    • or should we add another Bangle.dragHandlerCustom to keep track of them in two separate places?

    This doesn't happen for the touch handlers since we only add the last of them at the end of setUI's code. I wonder if we should change this so they add to each other rather than overwrite, like for drag, in order to preserve the callback functionality mode "updown" provides even when a custom handler is added.

  • @Ganblejs thanks that version has fixed the btnWatches error :)

    I think the first problem happens with any clock, and I'm using the default launcher. At first I thought it might just be something I'd done in my own clock so I switched to Anton and it happens there too. Long press to reboot and immediately after, with the watch still unlocked, the first press seems to be ignored, after that it functions normally. If I reboot and wait for the watch to lock then the first press unlocks, the second press gets ignored, and I have to press for a third time to get to the launcher.

  • Hm.

    What firmware do you have currently?

    Could you:

    1. Make a backup from the app loader "More ..." tab and keep it around.
    2. Then use the "Reinstall apps" function from the same "More ..." tab.

    ... and see if that helps?

    I'll install anton clock and the default launcher to try to reproduce as well. Edit: I can't reproduce on my current setup.

    Edit: could you also also copy/paste the "Apps installed" field from the "More ..." tab of the app loader?

  • Firmware is 23 (stable, not cutting edge), reinstalling didn't make any difference.

    Apps installed are -
    antonclk (0.11), about (0.15), widlock (0.08), welcome (0.14), notify (0.14), health (0.30), sched (0.26), widalarm (0.02), messageicons (0.07), widmessages (0.06), messages (0.62), android (0.36), info (0.03), contourclock (0.32), widbthide (0.01), widbatc (0.01), setting (0.72), alarm (0.48), noteify (0.03), agenda (0.15), weather (0.26), messagegui (0.79), widclk (0.08), launch (0.21), boot (0.63), setuichange (0.02)

  • I just did:

    1. Flash stable fw 2v23
    2. factory reset via Web IDE
    3. install setuichange

    ... and can't reproduce your problem.

    So I would guess you can fix it by factory resetting and then set up the watch again.

    If you don't feel uncomfortable (privacy/sensitive data reasons) sharing that backup you did you could send it in a message to me here on the forum and I'll restore it to my watch to see what happens.

  • Thanks for the quick fix @Ganblejs - I'm just pulling that into the main app loader now.

    Wrt Bangle.dragHandler it looks like none of the apps mess with it directly, so it could be changed. Or perhaps we could do:

    if (mode=="updown") {
        let dy = 0, oldDrag = options.drag;
        delete options.drag;
        Bangle.dragHandler = e=>{
          dy += e.dy;
          if (!e.b) dy=0;
          while (Math.abs(dy)>32) {
            if (dy>0) { dy-=32; cb(1) }
            else { dy+=32; cb(-1) }
            Bangle.buzz(20);
          }
          options.drag&&options.drag(e);
        };
        ...
    

    OR (and maybe this makes more sense) we just throw an exception if options.drag is defined in the updown or leftright modes? It feels like if you want it maybe you should just be using custom mode instead, and I'm not sure we should be making the code more complex for these edge cases?

  • OR (and maybe this makes more sense) we just throw an exception if options.drag is defined in the updown or leftright modes? It feels like if you want it maybe you should just be using custom mode instead, and I'm not sure we should be making the code more complex for these edge cases?

    I think this is the way.

    There's a part of me that thinks it's cool to be able to add on the custom handler on top of the standard updown and leftright ones - but not worth the trade off I think.

    1. After rebooting two clicks are needed to get to the launcher from the clock.

    Ok, I now managed to reproduce by doing (edit: see my next reply for additional info re reproducing):

    1. Flash either fw 2v23 stable or cutting edge 2v23.16
    2. Run Bangle.factoryReset() from the Web IDE
    3. Install setuichange from the app loader
    4. Long press HW button to get to the clock face (release when the "Loading..." message appears/flashes).
    5. Long press HW button to reset the watch to the clock face again.
    6. Click HW button - nothing happens
    7. Click HW button a second time - the launcher is loaded.

    If I change this:

      } else if (mode=="clock") {
        Bangle.CLOCK=1;
        Bangle.btnWatches = [
          setWatch(Bangle.showLauncher, BTN1, {repeat:1,edge:"rising"})
        ];
    

    ... to edge falling like this:

      } else if (mode=="clock") {
        Bangle.CLOCK=1;
        Bangle.btnWatches = [
          setWatch(Bangle.showLauncher, BTN1, {repeat:1,edge:"falling"})
        ];
    

    ... the problem goes away.

    So somewhere there is something hindering the watch from acting on the first rising edge after a long press of the hardware button. But not the first falling edge it seems.

  • By changing to a clock that takes slightly longer to load, e.g. LCARS is what I used, I could test what happens if:

    1. You release the hardware button AS SOON AS the Loading... message appears - before the clock face has had the time to finish loading.
      or
    2. You release the hardware button after the Loading... message has been shown for a while, say two seconds - when the clock face has had the time to finish loading.

    The problem only manifests for case 2 (on a factory reset watch as well as on my personal usual setup).

  • I'm not so sure if that is the only (or good) way to give the impression to be (more) responsive.

    Why? If something 'goes off' on the press event, you cannot 'go back'. To still support long presses requires two different contexts and the awareness of them - action starts either on press or on release. To be aware of which context is active requires a visual cue.

    My solution to give feedback - visual cue - to the user that the press has happen / is noticed - and I think it is all about that - can be conveyed differently, for example with a quick change / flash of a half circle or oval next to button.

    The case of press event starts something is still a valid case, thinking about the cursor keys on a key board or the special character - character w/ diacritics - in pop-up. The response to such a press would be passing callback that is then called over and over on a configurable interval until release. Double press in rapid fashion is could be integrated as well in the event.

    In the beginning when I had to use a single button interface vs a two - left and right - button interface, it created some relearning pain, but the value of dealing with just a track pad - position irrelevant - made up for a lot of it.

    When building the UI - https://www.espruino.com/ui - visuals at https://forum.espruino.com/conversations/292990/ - I had to handle the touch this way... on touch a visual cue is give to the user about it: an element fence or border shows as a white rectangle, which goes away on un-touch (for speed sake redrawn in same place in background color... all on a 'big', 262K color serially connected - thus slow - display. On other occasions I was trying to save and restore, but that took ages, see https://forum.espruino.com/comments/11906513/. With Bangle2's limited color depth and speed, it should not be that a big issue to save the few pixels around the button and flip the around and then restore, or some other kind 'flash', noticeable by the user.

    This kind of a visual cue could be a 'OS' level integrated option. It would also have to handle app-redraws of the area, so than on release the correct display is restored in this area.

    Side note: touch screen vs. (single) button is not that much difference: touch is just setting the focus before treating the touch (or release), and w/ button, the select happens in a different way. Without touch there are two modes: navigation and (data) entry/edit mode. In navigation mode, a short press unselects current element and selects next element, a longer press switches into the entry and would also be either directly the return to navigation mode or to a (popup-) menu, that even allows a cancel / restore (and other - app - options). A single button UI needs absolute consistency in behavior for a 'happy' - not confused and giving up - user.

  • @allObjects Did you try the test app linked in the opening post? Would be interesting to know if your viewpoint is also based on testing that or if it's a more general viewpoint not based on testing it. Thanks 🙂

  • Honestly, I wasn't very convinced by @Ganblejs's idea either - but then I tried it and it did make a huge difference to how the watch felt.

    Regardless of whether there is feedback, making the new app load ~200ms or more quicker is a big improvement.

    The problem is that while several people were very vocal about wanting support for long button presses, the reality is that nobody actually uses them, as the long press currently reloads to the clock at an OS level.

    ... but even if someone did want to use long presses you still can do it easily (with the btnRelease handler) - it's just that in the 99% of cases where long press isn't used, it makes sense to use the rising edge.

    So somewhere there is something hindering the watch from acting on the first rising edge after a long press of the hardware button. But not the first falling edge it seems.

    Hmm, this is a problem. So I think what's happened is:

    • Button is pressed to load the clock
    • The clock itself takes a long time to load
    • The button is pressed again to load the launcher
    • Bangle.setUI gets called at the end of the clock loading, after the button has already been pressed.

    The solution that comes to mind is:

    • In boot code, add setWatch(()=>Bangle.BTN_PRESSED=true,BTN,{repeat:0,edge:"falling"});
    • In setUI, before we do setWatch(Bangle.showLauncher do if (Bangle.BTN_PRESSED) Bangle.showLauncher() else setWatch...
  • @Ganblejs and @Gordon,

    I see the value of firing on press when there is no other option and I understand also the issue of double press because of timing and queuing of the events. For that reason - and especially on app / context switch - repeat has to be disabled and the new context (app) has to arm the button again the way the app expects.

  • So somewhere there is something hindering the watch from acting on the first rising edge after a long press of the hardware button. But not the first falling edge it seems.

    Hmm, this is a problem. So I think what's happened is:

    • Button is pressed to load the clock
    • The clock itself takes a long time to load
    • The button is pressed again to load the launcher
    • Bangle.setUI gets called at the end of the clock loading, after the button has already been pressed.

    I don't think this is it, at least not the full picture. It feels like it doesn't add up with the following I posted above:

    By changing to a clock that takes slightly longer to load, e.g. LCARS is what I used, I could test what happens if:

    1. You release the hardware button AS SOON AS the Loading... message appears - before the clock face has had the time to finish loading.
      or
    2. You release the hardware button after the Loading... message has been shown for a while, say two seconds - when the clock face has had the time to finish loading.

    The problem only manifests for case 2 (on a factory reset watch as well as on my personal usual setup).

    To clarify that, I do for case 1:

    1. Have the clock face be in a state where a short button press will fastload the launcher (I use launch and antonclk currently when writing this).
    2. Press and hold the button.
    3. The launcher is loaded on button rising edge.
    4. Release the button as soon as the Loading... message appears.
    5. The clock is loaded and shown with a short delay after the button was released.
    6. Short press the button to load the launcher (It works!).

    And for case 2:

    1. Have the clock face be in a state where a short button press will fastload the launcher.
    2. Press and hold the button.
    3. The launcher is loaded on button rising edge.
    4. Release the button only after the Loading... message has been showing for a while (it doesn't seem to matter how long I wait with releasing the button, as long as it doesn't go on to trigger the watchdog).
    5. The clock is shown immediately on button release.
    6. Wait 3 seconds (or more) to make sure that all clock code including Bangle.setUI has run.
    7. Short press the button to load the launcher (It doesn't work!).

    I think points 6 and 7 in case 2 rules out that it's a problem with the Bangle.setUI not having run yet.

    I believe the problem manifests when we hold the button and go through an regular, non-fastload, load cycle with the whole boot process being run and only release the button after the boot process has finished. If we release it ASAP when the boot process was initiated there seems to be no problem.

    Can it have something to do with the watchdog functionality?

  • The problem is reproducible also with this code:

    g.clear();
    
    let test = (e)=>{
      if (!e.lastTime) print("\n");
      if (e.state) print("\n");
      print(e);
    };
    
    setWatch(test, BTN, {repeat:true, edge:"both"});
    

    ... when set as the current clock (I set daisy as the clock and overwrote its code with the above).

    I look at the prints in the Web IDE console field to see the problem when running this code.

    So that should narrow it down some more. Something about setWatch and holding the button throughout a boot cycle?

    If I release the button ASAP when the Loading... message appears -> no problem.
    If I release the button only after the Loading... message has been showing for a while (e.g. some seconds) -> problem manifests.

  • Thanks! Yes, I see what you mean.

    Just fixed it in latest cutting edge builds - it was the debounce logic getting out of sync with the button state because we were 'eating' the first button release event after a restart to avoid starting the launcher immediately (because right now we look at the falling edge)

  • Awesome, thanks!

    Just tried it and that fixes it on my watch. 🙂

    As expected then maybe though, if I remove setuichange and use a long press to reset the watch I end up on the launcher. Just so we are aware 😇

  • Damn, thanks! I fixed it when you setWatch with debounce:0 which is what I was testing with, but it looks like it breaks when it's not.

    fixed now.

  • Thanks - now works as expected both with and without setuichange.

  • Just wanted to report in: I've tried setuichange for the past week and everything worked nicely for me, the watch feels much snappier. I think this change is awesome and the impact is huge!

    @Gordon I'll try to find the build you once made that would install apps on the internal storage instead of the one connected through SPI. I wonder if that shaves of another few milliseconds from the startup time of the apps.

  • I'll try to find the build you once made that would install apps on the internal storage instead of the one connected through SPI.

    I think it's this your after: https://forum.espruino.com/conversations/374260/?offset=200#17407732

    Beware of this though: https://github.com/espruino/Espruino/issues/2509

  • the build you once made that would install apps on the internal storage

    I did one recently for someone else - try this! This one uses only internal flash, not external too (as there were issues there as @Ganblejs has pointed out).


    1 Attachment

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Making Bangle.js more responsive - please test!

Posted by Avatar for Gordon @Gordon

Actions