I'm looking to use the EasyButton library to handle some basic button functionality i.e press, press and hold, rapid tap etc.
This library uses callbacks attached to the button object to simplify the application programming.
#define BUTTON_PIN 26
EasyButton button(BUTTON_PIN);
// Callback.
void onPressed() {
Serial.println("Button has been pressed!");
}
void setup() {
// Initialize the button.
button.begin();
// Attach callback.
button.onPressed(onPressed);
}
void loop() {
// Continuously update the button state.
button.read();
}
Now my device will have 6 buttons, so to avoid duplication I wish to set up an array of 6 buttons and attach the same callback function.
How do I access which button made the callback from within that function, or do I need to modify the callback signature to pass an instance pointer i.e "this", "self" etc
A callback function is an interrupt service routine (ISR). You want to spend as little time in an ISR as possible because until you return from the ISR, no other interrupt can be processed. (A higher-priority interrupt can be processed or else millis() and micros() wouldn't be accurate). Printing from within an ISR is strongly discouraged because Serial.print uses interrupts.
The correct (an certainly not the only) way to handle an interrupt is to set a flag (sometimes called a semaphore) then exit the ISR. In loop() you test the flag. If true, you handle it (don't forget to clear the flag).
It might avoid duplication but it will create messy code.
The best way is, in my opinion, to have a struct that combines an EasyButton and a function pointer for each of the button functions. Use an array of those. You can loop through those to eliminate duplication
As far as I can see, easyButton library does not have that feature. The only way to tell which button was pressed is to have a different callback function for each button. These callback functions could all call another, common function and pass a parameter to it indicating which button was pressed.
You would need to modify the library. Allow Callback with parameter, add an ID or something similar to each button object, call the callback with parameter from the object...
But why not just add 6 functions in the user sketch which can be addressed as callback by the library and do the individual logic within the 6 functions and the general logic by a separate function called by these 6 functions?
Thanks for the raid replies which are much appreciated.
As has been pointed out, the button states are being set by polling rather than interrupts so unless I investigate that option I will stay with a polling solution.
The callback will be setting state on an array of "network variables" i.e. some values that will be set in the callback and then another asynch process will propagate those changes over a communication network.
So either the callback could provide the index of the original button array (requires the button to be aware of that index) or send a pointer to the button that fired the callback.
Either way I will need to alter my fork of the library. I have engaged with the original author of the library. See Enhancement: instance pointer in callbacks · Issue #73 · evert-arias/EasyButton · GitHub
P.S. there are 4 callbacks x 6 buttons. I do not wish to clutter my application code with 24 almost identical pieces of code - that becomes a maintenance/comprehension nightmare.
And another one, that was especially written with many buttons in mind. My MobaTools library contains a class MoToButtons where you can handle up to 32 Buttons within only one instance.
If you don't want to install the complete library, you can copy the MoToButtons.h file into your sketch directory and include it from there. It contains everything you need.
You can find some examples here. The class doesn't use callbacks, but simple methods to see the state of the buttons ( pressed, clicked, long pressed ... and others ).
My use case specifically requires single tap (for momentary or toggling state), rapid press (# of presses within a time period, triggers a panic setting) and the ability to press and hold to enter a dim up/down sequence. The EasyButton library offers all of these out of the box.
But, if I reuse the same 4 callbacks for each button, the callback does not know what button triggered the execution. So I'm no further ahead and would need to provide 4 callbacks x 6 buttons i.e. 24 functions.
therefore I suggested to make ONE general function and use the others as a wrapper to forward the right parameters. there is no duplicated code in that proposal!
Or modify the lib
Or use another lib
Or create your own button class.
More great suggestions - thanks for the valuable links. Answering some specific questions / comments.
Yeah, but you've still got the 24 functions to write/maintain/understand. The problem just got moved around, not eliminated. However I do like your other suggestion which I'll get to in a moment.
That's a nice library, I particularly like the state machine diagram the author provided on his page. Unfortunately due to the way he handles the double/multi click, the onClick callback is not fired until after the period to detect the start of the double click has elapsed. This will introduce an unacceptable delay between the use clicking the button and the lights being activated (safety implications etc)
That's another nice library but does not handle the difference between firing a callback after the elapsed start of a long press and then at a user defined period during the long press. I like the id property added to the button though !
Each button has 4 "events" that can be fired, essentially: OnClick, OnSequence, StartLongPress, DuringLongPress. The only difference in the code I will write for a given callback for any button is some "install time" configuration. E.g button #1 can only set an analog output to 50%, the other can set it to 100%. This config data is stored in another structure which I will modify to take pointer to whatever button object I use.
It's actually about code maintainability. In my day job I'm responsible for a health care API that has millions of lines of code sitting behind it. Although this Arduino Zero based project is much, much simpler, getting the code structure right is one of the key things to allow later revisions to be made at speed and with confidence. Encapsulation and abstraction are important.
I'll run with your first suggestion:
add an id byte field to the underlying button, and
add an optional parameter to the callbacks.
I'll post a link to my modified fork of the base library when done. Once again thanks for the input and suggestions.
if he would fire onClick immediately how would you be able to distinguish from StartLongPress? Or would you accept, that before a StartLongPress you geht an onClick event also?
I began to read the documentation of your preferred EasyButton library ... they state for the onPressed "Triggers a callback function when the button has been pressed and released. "
Therefore I wouldn't expect that OneButton onClick is reacting "slower" than EasyButton onPressed if you set the timing parametrs of OneButton to your needs.
Imho if you have reasonable requirements regarding timing and behaviour you will need to write your own class with only the events you really need, according to the timing diagram you can accept (including debouncing).
On further investigation I think the single click is only triggered if the button is released, so if the user is holding the button down the longPressStart callback is triggered.
I prefer the way the EasyButton handles the multiple click event: specify the min number of clicks within the time period. OneButton just fires the event and leaves it up to the application writer to do the counting and keep track of the duration.