bxparks / AceButton

An adjustable, compact, event-driven button library for Arduino that debounces and dispatches events to a user-defined event handler.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

kEventClicked comes always with kEventDoubleClicked

andreaslink-de opened this issue · comments

Hi, I see an issue here when testing the library. I registered all events (just following/modifying your examples) as I'm looking for a library that can easily distinguish between single and double clicks, which is not that easy to find. I can easily send out a click on the button and I get the kEventClicked as expected. But when I do a double click I always get first the kEventClicked which is then immeditely followed by the kEventDoubleClicked. This is a nogo for me as this is not presize enough.
In other words a kEventClicked is only allowed to be fired, when there is no kEventDoubleClicked following. I do not know, whether this is already today possible, but I did not get it managed to behave like that.
Simple test is, turn your LED on with one click and turn it off with double click. You will recognize, when it's off and you double click again, it will briefly flash, right after the first click. As I want to switch a relais, this is not good enough for me. Any suggestions?

Just to add, I used several delay times, but I never succeed, I always get the kEventClicked before I get the kEventDoubleClicked, but for the the long pressed kEventLongPressed it works absolutly fine, there I never get an unwanted event before. Here my last tested setup with really slow test times:

[...]
button.init(BUTTON_PIN, LOW);
button.setButtonConfig(&adjustableButtonConfig);
adjustableButtonConfig.setEventHandler(handleEvent);  
adjustableButtonConfig.setFeature(ButtonConfig::kFeatureClick);
adjustableButtonConfig.setFeature(ButtonConfig::kFeatureDoubleClick);  
adjustableButtonConfig.setFeature(ButtonConfig::kFeatureLongPress);
adjustableButtonConfig.setClickDelay(1000);
adjustableButtonConfig.setDoubleClickDelay(2000);
adjustableButtonConfig.setLongPressDelay(4500);
[...]

Hi, I predicted that someone would encounter this, so I even wrote a little blurb about it in the README (it's a long README so I don't blame you for missing it):

  • ButtonConfig::kFeatureSuppressAfterDoubleClick
    • suppresses the Released event and the second Clicked event if a
      DoubleClicked event is detected
    • the first Clicked event cannot be suppressed because the code does not
      wait for a possible DoubleClicked before triggering the first Clicked
      event
    • (an optional Feature flag could be added if suppression of the
      first Clicked is needed, at the expense of waiting extra time for this
      first Clicked event to trigger)

Let me see if I can explain that in a different way: After the first Clicked is detected, how does the program know if there's going to be a DoubleClicked event, so that it can suppress the first Clicked? It can't predict the future. The only way to suppress the first Clicked is to wait for another kDoubleClickedDelay after the first Clicked, then see if there was another Clicked. But that means that the response time of the first Clicked becomes (kClickDelay + kDoubleClickDelay) which is 600 ms by default (plus I think there's another 4 x 50ms delay in there for debouncing, so it could actually be 800 ms). I felt that this was too long to wait for a single Click event.

The usual way around this problem is to use the kEventReleased to trigger the first action, and use kEventDoubleClicked for the second action, and use kFeatureSuppressAfterDoubleClick so that the Released event is suppressed after a DoubleClicked. Is that possible for your application? This is what I did for one of my projects.

Thanks @bxparks you are absoutly right :-). And yes, I somehow missed this part in relation to my problem, as I was not focussing on the released event so much. And I appreciate your long and detailed README very much, there is a lot to find in it, so thank you for that 👍.

Having read and tested your suggestions now, I don't think this can really help me as the first event is always having the kEventClicked followed by kEventReleased. So looking at this does not predict, if there is a kEventDoubleClicked to come or not. And this is the key, what value does it give me to suppress a second kEventReleased?
So as I saw it, adding adjustableButtonConfig.setFeature(ButtonConfig::kFeatureSuppressAfterDoubleClick); only changes this former result...

kEventClicked
kEventReleased
kEventDoubleClicked
kEventReleased

...to this new result...

kEventClicked
kEventReleased
kEventDoubleClicked

...when doing a double click. Did I test that correctly? That does not really help me, as I want to prevent the clicked event and only handle the double click event.
I fully agree with you that this requires the response time only for the first kEventClicked to become to (kClickDelay + kDoubleClickDelay) which can be 600 ms (+x). Probably this does not necessarily need to be the default, but would be way more useful than supressing the second release event, wouldn't it?

Let me explain my usecase and why I want to use your library, I'm currently designing a switch, when I press the button once, I want a relais to close. When I press the button twice, I want it to open again. Problem I monitored during my tests, when the relais is already open and I double click it (again) it first closes and then opens again. This is an issue for my circuit/design :-). That's why I see this a design issue in the library, if a double click event is always fired together with a single click event. You are also not firing a single click, if a long hold was done (because it's easier to handle, I can understand this). But the events should be fired straight forward and only related to "what-really-happend". Else I need to handle this in my code, which is something I wanted to prevent :-).

So long story short, let's call this a feature request then, I would really like to ask you for a (non default) switch to suppress the single click event (kEventClicked), if there is a double click (kEventDoubleClicked) event following (as suggested by you in your third bullet point).
I suggest to call it e.g. ButtonConfig::kFeatureSuppressClickBeforeDoubleClick. You could only make this valid/taken into consideration, if the ButtonConfig::kFeatureDoubleClick was activated before. I will for sure accept the delayed kEventClicked due to waiting for a double click.
What do you think :-)?

Edit: Added the line:

  buttonConfig->setFeature(ButtonConfig::kFeatureSuppressAfterClick);

which I forgot in my original post. With that, it should be the same as the ClickVersusDoubleClickUsingReleased example code that I posted with Version 1.0.6.)

You are correct that kFeatureSuppressAfterDoubleClick generates:

kEventClicked
kEventReleased
kEventDoubleClicked

However, I'm suggesting that you would just ignore the kEventClicked in the EventHandler, and use just the kEventReleased to close your relay, then use kEventDoubleClicked to open your relay. I added another example code (examples/ClickVersusDoubleClick/), that turns on the LED on "Clicked" (actually "Released"), and off the LED on DoubleClicked:

#include <AceButton.h>
using namespace ace_button;

// The pin number attached to the button.
const int BUTTON_PIN = 6;

// LED states. Some microcontrollers wire their built-in LED the reverse.
const int LED_ON = HIGH;
const int LED_OFF = LOW;

// One button wired to the pin at BUTTON_PIN. Automatically uses the default
// ButtonConfig. The alternative is to call the AceButton::init() method in
// setup() below.
AceButton button(BUTTON_PIN);

void setup() {
  // initialize built-in LED as an output
  pinMode(LED_BUILTIN, OUTPUT);

  // Button uses the built-in pull up register.
  pinMode(BUTTON_PIN, INPUT_PULLUP);

  // Configure the ButtonConfig with the event handler to send only
  // kEventReleased and kEventDoubleClicked. Note that kEventDoubleClicked
  // automatically implies kEventClicked.
  ButtonConfig* buttonConfig = button.getButtonConfig();
  buttonConfig->setEventHandler(handleEvent);
  buttonConfig->setFeature(ButtonConfig::kFeatureDoubleClick);
  buttonConfig->setFeature(ButtonConfig::kFeatureSuppressAfterClick);
  buttonConfig->setFeature(ButtonConfig::kFeatureSuppressAfterDoubleClick);
}

void loop() {
  // Should be called every 20ms or faster for the default debouncing time
  // of ~50ms.
  button.check();
}

// The event handler for the button. A kEventClicked is always triggered but
// just ignore it in the event handler.
void handleEvent(AceButton* button, uint8_t eventType, uint8_t buttonState) {
  switch (eventType) {
    case AceButton::kEventReleased:
      digitalWrite(LED_BUILTIN, LED_ON);
      break;
    case AceButton::kEventDoubleClicked:
      digitalWrite(LED_BUILTIN, LED_OFF);
      break;
  }
}

Does that work for your application? I realize that it's a bit obscure.

With regards to your feature request, I will look into implementing it as a flag. The click-versus-double-click problem is something that I've struggled with, and I'm sure other people will encounter it as well.

(Just for completeness, with regards to your comment about the missing single Click after a LongPress, I'm not sure I understand that. A LongPress means that there was no Release after a lengthy period of time, so it cannot cause a single Click. Maybe I misunderstood?)

Brian

Actually, upon writing some test code, I think you are correct that this doesn't work. Because a Released event will always be generated after the first Clicked, even if there's a DoubleClicked. I have the Click suppression mostly working. I have to write some test code. Shouldn't be too long.

Thank you very much, I really appreciate it, I've prepared my code to test this for my use cases.
Looking forward to read from you :-)!

I released v1.0.6 which contains the kFeatureSuppressClickBeforeDoubleClick flag that you requested. I added a Distinguishing Between Clicked and DoubleClicked section in the README.md. I added two example sketches into the examples/ directory, showing you 2 potential solutions to your problem.

ClickVersusDoubleClickUsingReleased.ino: This implements the solution that I gave above, which I had it almost right. I forgot to add a

buttonConfig->setFeature(ButtonConfig::kFeatureSuppressAfterClick);

to suppress the Released after the inevitable Clicked. (I will make the correction above for posterity.)

ClickVersusDoubleClickUsingSuppression.ino: This shows how to use the new kFeatureSuppressClickBeforeDoubleClick flag.

Let me know how these work out for you.

Brian

Perfect, you did it 👍, I just needed to add the new feature button adjustableButtonConfig.setFeature(ButtonConfig::kFeatureSuppressClickBeforeDoubleClick); and it was working like a charm. Really perfect, this was exactly what I was aiming and looking for and the delay time for a single click it not recognizeable to me, so I really appreciate this addional feature.
Thank you very much, I got what I was looking for :-). So this is now my default button library for all my (coming) projects. Well done!

Awesome, glad that it worked out for you. Thanks for providing a concrete example so that I could implement this feature properly.