Browse by type
Arduino/ESP library to simplify working with buttons.
This library allows you to use callback functions to track single, double, triple and long clicks. Alternatively, it provides function to use in your main loop().
The library also takes care of debouncing. Using this lib will reduce and simplify your source code significantly.
It has been tested with Arduino, ESP8266 and ESP32 devices.
To see the latest changes to the library please take a look at the Changelog.
If you find this library helpful please consider giving it a ⭐️ at GitHub and/or buy me a ☕️.
Thank you!
This library allows you to define a button and uses callback functions to detect different types of button interactions.
If you don't want to use callback there are also functions available for using it in your code's main loop().
#include "Button2.h"
constructor or the begin() function. void begin(uint8_t attachTo, uint8_t buttonMode = INPUT_PULLUP, bool activeLow = true);
INPUT_PULLUP. You can override this upon creation. #include "Button2.h"
#define BUTTON_PIN D3
Button2 button;
void setup() {
button.begin(BUTTON_PIN);
}
loop() this class allows you to assign callback functions.setTapHandler() will be be called when any click occurs. This is the most basic handler. It ignores all timings built-in the library for double or triple click detection.setClickHandler() will be triggered after a single click occurred.setChangedHandler(), setPressedHandler() and setReleasedHandler() allow to detect basic interactions.setLongClickDetectedHandler() will be triggered as soon as the long click timeout has passed.setLongClickHandler() will be triggered after the button has released.setDoubleClickHandler() and setTripleClickHandler() detect complex interactions.
Note: You will experience a short delay with setClickHandler() and setLongClickHandler() as need to check whether a long or multi-click is in progress. For immediate feedback use setTapHandler()or setLongClickDetectedHandler()
You can assign callback functions for single or for multiple buttons.
Button2 reference parameter. There the reference to the triggered button is stored. This can used to call status functions, e.g. wasPressedFor().setLongClickDetectedHandler() and setLongClickHandler().setLongClickDetectedHandler() will be called as soon as the defined timeout has passed.setLongClickHandler() will only be called after the button has been released.setLongClickDetectedRetriggerable(bool retriggerable) allows you to define whether want to get multiple notifications for a single long click depending on the timeout.setLongClickDetectedRetriggerable(bool retriggerable, unsigned int retrigger_ms) overload lets you set the retrigger interval in the same call instead of relying on the longclick timeout.getLongClickCount() gets you the number of long clicks – this is useful when retriggerable is set.loop() member function in your sketch's loop() function. #include "Button2.h"
#define BUTTON_PIN D3
Button2 button;
void handleTap(Button2& b) {
// check for really long clicks
if (b.wasPressedFor() > 1000) {
// do something
}
}
void setup() {
button.begin(BUTTON_PIN);
button.setTapHandler(handleTap);
}
void loop() {
button.loop();
}
loop()function needs to be called continuously, delay() and other blocking functions will interfere with the detection of clicks. Consider cleaning up your loop or call the loop() function via an interrupt.loop() function via a timer interrupt. #define BTN_DEBOUNCE_MS 50
#define BTN_LONGCLICK_MS 200
#define BTN_DOUBLECLICK_MS 300
void setDebounceTime(unsigned int ms)void setLongClickTime(unsigned int ms)void setDoubleClickTime(unsigned int ms)loop()bool wasPressed() allows you to check whether the button was pressedclickType read(bool keepState = false) gives you the type of click that took placeclickType wait(bool keepState = false) combines read() and wasPressed() and halts execution until a button click was detected. Thus, it is blocking code.clickType is an enum defined as... enum clickType {
single_click,
double_click,
triple_click,
long_click,
empty
};
empty enum value in your code, it's recommended to use the scoped syntax clickType::empty to avoid potential naming conflicts with other libraries (particularly when using libraries that do using namespace std; which imports std::empty() from the C++17 standard library).// Recommended - explicit scope avoids ambiguity
if (button.getType() == clickType::empty) {
// handle no click
}
// Also works, but may cause compilation errors with some library combinations
if (button.getType() == empty) {
// handle no click
}
waitForClick(), waitForDouble(), waitForTriple() and waitForLong()) to detect a specific typeread() and the wait functions will reset the state of wasPressed() unless specified otherwise (via a bool parameter)resetPressedState() allows you to clear value returned by wasPressed() – it is similar to passing keepState = false for read() or wait().unsigned int wasPressedFor() const;
uint8_t getNumberOfClicks() const;
clickType getType() const;
bool isPressed() const;
bool isPressedRaw() const;
bool wasPressed() const;
Returns the duration (in milliseconds) that the button was held down during the most recent press.
Important: For multi-click scenarios (double/triple clicks), this returns the duration of the most recent click only, not the cumulative time across all clicks.
Examples:
wasPressedFor() returns 500wasPressedFor() returns 1200wasPressedFor() returns 80wasPressedFor() returns 70This behavior was confirmed in issue #35.
getID().setID(int newID) to set a new one. But then you need to make sure that they are unique.setContext(void*) and retrieve it inside any callback via getContext().reset() or resetPressedState().struct LedCtx { uint8_t pin; const char* label; };
void onClick(Button2& btn) {
LedCtx* ctx = (LedCtx*)btn.getContext();
digitalWrite(ctx->pin, !digitalRead(ctx->pin));
Serial.println(ctx->label);
}
LedCtx ctx = { 13, "my button" };
button.setContext(&ctx);
button.setClickHandler(onClick);
Button2 supports "virtual buttons" - buttons not directly connected to GPIO pins. This is useful for:
To use a virtual button, you need:
BTN_VIRTUAL_PIN instead of a real pin number when calling begin()begin() accepts an optional initialization callback parameter. This is especially useful for virtual buttons that require hardware setup (I2C, SPI, touch sensors, etc.). The callback is invoked immediately by begin(), ensuring your hardware is ready before the button starts polling.
Important: When using multiple buttons on an I2C port expander (PCF8574, MCP23017, etc.), read the entire port once per loop cycle and cache the value. Each button's state handler then reads from the cache using bit masking. This minimizes I2C bus traffic from N transactions per cycle to just 1.
See I2CPortExpanderButtons.ino for a complete example showing this efficient caching pattern (issue #70).
btn.getID()This feature was enhanced in issue #69 to support initialization callbacks.
Button2 uses callback handlers for button events. On platforms that support C++11 and <functional> (such as ESP32 and ESP8266), Button2 uses std::function for maximum flexibility, allowing you to use lambdas and other advanced C++ features as handlers.
On platforms that do not support std::function (such as AVR/Arduino Uno), Button2 falls back to using regular function pointers for handlers.
std::function SupportButton2 automatically detects and enables std::function support on platforms with C++11 or later, except AVR (Arduino Uno, Nano, Mega).
Supported platforms:
Not supported:
The library automatically detects support using #if __cplusplus >= 201103L && !defined(__AVR__).
This improvement was implemented in response to issue #58.
std::function SupportYou can override the automatic detection by defining these macros before including Button2:
#define BUTTON2_HAS_STD_FUNCTION$ claude mcp add Button2 \
-- python -m otcore.mcp_server <graph>