Correct way to deal with button click to wakeup

I have what I guess is a pretty common requirement: I need to detect single button clicks but I want to ignore any clicks that were done to wake up a blink. On my loop(), I have this:

if (hasWoken()) {
    // We just woke up. Consume button click event so it will not
    buttonSingleClicked();
}

Even with this, it looks like button clicks are still somehow being detected. This is bad for my game because most button clicks results in datagrams being sent and them things might get into a weird state (different Blinks having a different view of the world). This is most evident when the Blinks go to deep sleep so that a click in a blink does not wake up the others.

Any suggestions on how to effectively deal with this?

Does your game require the game state to be saved once it sleeps? I’m not aware of the reasonable assumptions that we can make about memory volatility.

I wasn’t even sure what the hasWoken() method was for until I got my Blinks in hand and woke up some sleeping ones. I assumed once they went to sleep, they would reset. I was wrong. I was thinking of reinitializing play in my game on hasWoken().

Could you experiment with consuming multiple single clicks? Or all the the clicks? Dynamo does this, though I don’t know if it’s standard.

That is not required. A Blink waking up should be in exactly the same state it was when it went to sleep. In any caso, no, I am not doing anything special other than trying to “eat” the button click from a wakeup.

Sure, although it should be enough to only consume single clicks. And I am also doing something similar to what Dynamo is doing (to be honest, I am cargo-culting this. I have no idea why one would have to “eat” all possible button clicks on every loop iteration).

Me neither. I would suggest start by wrapping those three lines with if (hasWoken()) like this.

if (hasWoken()) {
    buttonPressed();
    buttonSingleClicked();
    // start with first two and uncomment below, if needed
    // buttonDoubleClicked();
}

In theory, that should be enough and it won’t be called every loop.

I use:

if (buttonSingleClicked() && !hasWoken())
{
   ...
}
2 Likes

Yep, this is what I using but, somehow, it does not seem to always work. Considering I am sending datagrams all the time, it might be something else that is causing this. I will investigate.

I did some testing and here is what I noticed:

If the Blink that caused the sleep (by holding its button for a while) is used to wake (by clicking its button), then hasWoken() does work as expected.

If another Blink is used to wake, then it does not. I will add some more debugging to double check what is happening.

2 Likes

I think I found the problem and it looks like a blinklib bug.

The code paths for sleeping when you are the Blink being long-pressed does this:

if (blinkbios_button_block.bitflags & BUTTON_BITFLAG_6SECPRESSED) {
        // Held down past the 7 second mark, so this is a force sleep request

        warm_sleep_cycle();

        // Clear out the press that put us to sleep so we do not see it again
        // Also clear out everything else so we start with a clean slate on
        // waking

        blinkbios_button_block.bitflags = 0;

}

While the code path for when it goes to sleep because it was told to do so by another Blink does this:

if (packetDataLen == 2 && decodedByte == TRIGGER_WARM_SLEEP_SPECIAL_VALUE && packetData[1] == TRIGGER_WARM_SLEEP_SPECIAL_VALUE) {
                warm_sleep_cycle();
}

Note the missing blinkbios_button_block.bitflags = 0 in the second case.

I added it to my local blinklib and now it works as expected.

@bigjosh

1 Like

Hmmm.

I think your code looks good, although I usually use the if buttonSingleClicked() && !hasWoken() pattern.

I don’t have blinks with me but I will look into this more deeply next time I am at my desk. Here is a new issue on github…

Thanks!

1 Like

I think I FINALLY got button click handling to work reliably. Here are things I learned:

1 - The first one is the bug in blinklib. I was able to confirm and fix it (it is fixed in my custom blinklib and I have a pending pull request for the official blinklib without much traction so far). Without this fix, the other points are only relevant when not dealing with wake-up events.
2 - I added a convenience function that is simple but gets the job done:

bool NoSleepButtonSingleClicked() {
  return buttonSingleClicked() && !hasWoken();
}

3 - Indiscriminately consuming button clicks at the end of the loop is not a good strategy as it might result in button clicks incorrectly being consumed (which results in games feeling non-responsive).

4 - If your game has separate states, consume clicks only on states that actually handle clicks and do that on all possible code paths. Something like this:

void state_that_uses_clicks() {
  bool button_clicked = NoSleepButtonSingleClicked();

  if (button_clicked) {
    // Do things with the button click.
    return;
  }

  // Click was already consumed above, so we can simply return.
}

With this, Hexxagon is now feeling a lot more responsive (and I managed to save some more bytes of storage too).

2 Likes

Another thing I forgot to mention that was brought to my attention again because someone else stumbled upon it: ALWAYS consume woken state at the end of every loop iteration (hasWoken()).