Note: Unreleased changes are checked in but not part of an official release (available through the Arduino IDE or PlatfomIO) yet. This allows you to test WiP features and give feedback to them.
- Added:
setContext(void*)/getContext()— attach arbitrary caller data to a button instance and retrieve it inside any callback handler, without globals. Useful on AVR (no lambda captures) and for the explicit context pattern on any platform. Context is not cleared byreset()orresetPressedState() - Added:
CallbackContextexample — two buttons sharing the same handler functions, each with a differentButtonCtxstruct attached viasetContext() - Added:
setButtonStateFunction(StateCallbackFunctionBtn f)overload — state callback now optionally receives aconst Button2&reference, enabling multiple virtual buttons to share one state function and differentiate by ID or config. Useful for capacitive touch arrays (e.g. ESP32touchRead) and I2C expanders - Added:
ESP32MultiCapTouchexample demonstrating two capacitive touch buttons sharing a single state handler viabtn.getID() - Tests: Added consecutive double-click and bounce-injection investigation tests;
injectBounce()helper intest_helpers.h - Internal: Standardised
TestRunner::setTimeout(90)across all six test suites to prevent sporadic timeouts onepoxy-nano
- Fixed (Issue #88): Compilation error on Arduino Pico (RP2040) —
INPUT_PULLUP,OUTPUT, andINPUTare defined as aPinModeenum on Pico, not as macros. The previous#ifndefguards only detect macros, so they failed to protect against redefining these constants, causing type mismatch errors withPinMode. Wrapped the fallback defines in#ifndef ARDUINOso they only apply in non-Arduino environments (e.g. native test builds) - Updated: Decoupled long-click retrigger interval from initial threshold
- Fixed (Issue #96):
wasPressedFor()always returned 0 when called afterread().down_time_msis a completed measurement and must not be reset byresetPressedState(); removed the erroneous zero from that function - Internal: Added Claude Code project skills (
/commit,/release,/test,/compile,/readme), auto-test and.pio/guard hooks, and updated CLAUDE.md withread()contract, runner timeout note, and changelog/README rules - Tests: Added
read()contract tests — verifying return value, state reset onread(), and state preservation onread(true)
- Fixed (CRITICAL): Type mismatch in internal timing methods —
_handlePress(),_handleRelease(),_pressedNow(),_releasedNow(), and_checkForLongClick()now acceptunsigned longinstead oflong, matching the return type ofmillis(). The previouslongparameter caused incorrect timing arithmetic after ~24.8 days of uptime on AVR and ESP platforms - Fixed:
waitForClick(),waitForDouble(),waitForTriple(), andwaitForLong()were silently ignoring theirkeepStateparameter — it is now correctly forwarded toread() - Fixed:
_pressedStateandlongclick_retriggerablemember variables had no default initializers; they are now initialized toLOWandfalserespectively, preventing undefined behavior if query methods are called beforebegin() - Fixed: Wrong initialization order in
test_configuration.cppwherebegin()was called beforesetButtonStateFunction(), causing the initial state to be read viadigitalRead()instead of the injected state function - Changed:
getLongClickCount()return type changed fromuint8_ttouint16_tto match theuint16_t longclick_counterstorage — with a 200ms retriggerable interval the previousuint8_twrapped after ~51 seconds - Changed:
_nextIDstatic counter type changed frominttouint8_t, saving 1 byte of static RAM on AVR - Added:
setLongClickDetectedRetriggerable(bool retriggerable, unsigned int interval_ms)overload — allows setting the retrigger interval in a single call - Internal: Extracted shared test infrastructure into
test/shared/test_helpers.h, removing ~80 lines of duplicated boilerplate from each of the 6 test suites and ensuring a single, consistentclick()implementation that callsloop()continuously during the hold period
This release focuses on code quality improvements, bug fixes, comprehensive testing infrastructure, and enhanced support for I2C port expanders and virtual buttons.
- CRITICAL: Fixed integer overflow in long click counter calculation on AVR platforms (Arduino Nano/Uno). The multiplication
longclick_time_ms * (longclick_counter + 1)now uses proper casting to prevent 16-bit overflow that could cause incorrect long click timing [Button2.cpp:415] - Issue #82: Fixed ambiguous reference to
emptyenum value when compiling with libraries that useusing namespace std;. The library now uses qualified references (clickType::empty) internally to avoid conflicts withstd::empty()from C++17 standard library. This fix maintains 100% backward compatibility - user code does not need to change [Button2.h:117, Button2.cpp:225] - Fixed uninitialized member variables (
click_ms,down_ms,state,prev_state) in constructor that could cause phantom button presses and incorrect click detection on startup [Button2.h:69-76] - Fixed
resetPressedState()to properly reset all timing variables (click_ms,down_ms,last_click_count) for complete state reset [Button2.cpp:223-235] - Fixed
operator==const correctness - now properly usesconst Button2¶meter andconstmethod qualifier [Button2.h:177, Button2.cpp:98] - Fixed test initialization order bug where
setButtonStateFunction()was called afterbegin(), causing phantom press/release transitions - Fixed compile_examples.sh: Removed unnecessary AUnit dependency from example compilation
- Fixed compile_examples.sh: Corrected M5StackCore2CustomHandler exclusion logic - now properly runs on M5Stack platform
- Fixed compile_examples.sh: Improved platformio.ini generation for M5Stack dependencies
- Issue #69: Added optional initialization callback parameter to
begin()method for virtual buttons. This allows hardware initialization (I2C/SPI expanders, touch sensors, etc.) to be encapsulated within the button setup. The callback is invoked immediately whenbegin()is called, ensuring hardware is ready before button polling starts. Example:button.begin(BTN_VIRTUAL_PIN, INPUT, true, initCallback);[Button2.h:141, Button2.cpp:34-43] - Issue #70: Added
I2CPortExpanderButtons.inoexample demonstrating the efficient caching pattern for multiple buttons on I2C port expanders (PCF8574, MCP23017). The example shows how to read the entire port once per loop cycle and use bit masking in state handlers, reducing I2C bus traffic from N transactions to just 1 per cycle. This pattern scales efficiently to 8+ buttons while minimizing I2C overhead - Comprehensive Test Suite: Added 70 tests across 6 test suites using AUnit framework:
- test_basics (10 tests): Initialization, configuration, default values, init callback
- test_clicks (11 tests): Click detection - single, double, triple, long
- test_callbacks (12 tests): All event handler callbacks
- test_states (19 tests): State management, queries, timing edge cases
- test_configuration (7 tests): Runtime settings and configuration
- test_multiple (11 tests): Multiple button interactions
- Added EpoxyDuino-based native testing (no hardware required) - tests run on host machine emulating ESP8266/ESP32
- Added automated compilation testing script (
test/compile_examples.sh) that tests all examples across multiple platforms (ESP8266, ESP32, AVR) - Added comprehensive test documentation in
test/README.mdandtest/CLAUDE.md - Added extensive inline code documentation explaining:
- Asymmetric debouncing strategy (press vs release edge handling)
- Long click detection design (only on first click to avoid multi-click ambiguity)
- Critical importance of calling
loop()regularly (recommended 1-10ms frequency) - Virtual button usage and I2C port expander patterns
- Added documentation for
wasPressedFor()behavior in multi-click scenarios (returns duration of most recent click only) [Issue #35]
- OPTIMIZATION: Reordered struct members for better memory packing, reducing padding overhead on 32-bit platforms (ESP32/ESP8266) [Button2.h:66-122]
- PERFORMANCE: Changed
clickToString()to returnconst char*instead ofStringto avoid heap allocation and memory fragmentation on low-memory devices (AVR) [Button2.h:172, Button2.cpp:206] - CODE QUALITY: Replaced NULL with BUTTON2_NULL macro (nullptr on C++11+, NULL on AVR) for consistent modern C++ practices while maintaining Arduino compatibility
- Breaking Change (Minor): Return type of
clickToString()changed fromStringtoconst char*- most code will work unchanged, but assignments toStringvariables may need explicit casting - Improved test reliability through proper initialization order and state management
- TEST PERFORMANCE: Reduced setup delays in all test suites from 1000ms to 100ms, improving native test execution time by ~5 seconds and reducing intermittent timeouts
- Enhanced README.md with:
- Virtual buttons and custom state handlers section
- Efficient pattern for multiple I2C buttons with caching example
- Updated std::function platform support documentation [Issue #58]
- Added scoped enum usage recommendation for
clickType::empty[Issue #82]
- Updated CustomButtonStateHandler.ino example to demonstrate initialization callback usage
- Updated documentation (CLAUDE.md) to reflect current testing approach using
setButtonStateFunction()
- Removed Hardware.h abstraction layer (replaced with simpler
setButtonStateFunction()approach) - Removed unused Button2TestHelper utility class
- Documented current behavior of
wasPressedFor()for multi-click scenarios [Issue #35] - Documented std::function support now works on all C++11+ platforms except AVR [Issue #58]
- Added comprehensive virtual button documentation with I2C port expander examples
- Added notes about
clickType::emptyscoped syntax to avoid naming conflicts [Issue #82]
- Issue #58: Improved detection and enabling of
std::functionsupport for callback handlers. Switched from platform-specific checks to language-version-based detection (__cplusplus >= 201103L), excluding AVR platforms. This provides broader compatibility for modern boards including ESP32, ESP8266, Teensy, ARM, RP2040, SAMD, STM32, and other C++11+ platforms.
- Removed workaround file
main.cppto prevent compile warnings and undefined behavior in PlatformIO. The library now compiles correctly as a dependency in PlatformIO projects. PR #83 - Improved callback handler performance and compatibility by using
std::moveforstd::functionassignments on supported platforms (ESP32/ESP8266). Users can now force-enable or disablestd::functionsupport via macros. PR #84
- fixed contant name in example
M5StackCore2CustomHandler.ino - replaced
booleanwithboolas requested in #81 - replaced
bytewithuint_8as requested in #79
- added dummy file
main.cppto be able to compile lib in VSCode/PIO
- fixed bug, that first long press was not properly detected, see issue #72
- added
byte resetClickCount()function - click count is no longer resetted in
resetPressedState() - updated examples
- expanded condition to check for API version 2.0 (for UNO R4, RP2040, ...)
- renamed Button2 constants, they now start with
BTN_(BREAKING CHANGE) - added
Hardware.h– it implements hardware pin abstraction. Needed for unit testing - added Unit Tests
- added
resetPressedState()function - added
ESP32S2S3CapacitiveTouch.inosuggested by ryancasler in PR #57
getNumberOfClicks()now works inside a callback and after thewait()statement(s).- Refactored code in
Button2.cpp
- Included PR for issue #54
- Another stab at the bug #46
- Fixed bug #46 that in some instances long clicks are wrongly triggered
- Refactored the main
loop() - Rewrote click detection
- Cleaned up the long press handling
- Removed compiler switches – they made the code unreadable and they only saved a few bytes (could be a BREAKING CHANGE)
- Added
byte getLongClickCount()function - Updated the LongpressHandler example
- Defaults (x>3)-clicks to triple
- Fixed bug with button ID
- Removed the capacitive touch functionality out of main library. (BREAKING CHANGE). The constructor and
begin()lost a parameter. Instead I provide a custom handler example for cap. touch ESP32CapacitiveTouch.ino. For reasons, see #45. - Added an ESP32 timer interrupt example ESP32TimerInterrupt.ino based on #43.
- Added compiler switches in
Button.hto remove click detection code, as mentioned in #44. - Clarified the difference between the
setLongClickHandlerand thesetLongClickDetectedHandlerin the README and the MultiHandler example as mentioned in #41. (The handler set viasetLongClickHandlerwaits until you release the button, the second one is called as soon as the defined long-click time has passed.) - Made
byte _getState()into aconstfunction.
- Added example for the M5Stack Core2 - showing how to add a custom handler for the touch buttons.
- Fixed bug –
longclick_detected_counteris not properly initalized as mentioned in #37.
- House keeping
- Refactored
loop()- cleaned up conditions, should be easier to understand now. - Renamed
getAttachedPin()togetPin()(BREAKING CHANGE). - Fixed a bug that the first click type was falsly returned by
getClickType().
- Refactored
- Possibility define your own "_getState" function for non standard buttons as suggested in #32.
- Refactored
isPressedRaw()to also use_getState(). - Introduced a
VIRTUAL_PINconstant – using it in the constructor orbegin()will skip pin initalization. - Added
setButtonStateFunction(StateCallbackFunction f)to assign your own "_getState" function. - Added CustomButtonStateHandler.ino example.
- Refactored
- Improved click type handling.
- Added
clickTypeand removed constants for determining the click type (BREAKING CHANGE) - Renamed
getClickType()togetType()(BREAKING CHANGE) - Added
clickToStringfunction to print theclickTypeenum value
- Added
- Added IDs button instances
- Added
getID(), it returns an auto incrementedintID for the button, as suggest in #34 - Added
setID(), it allows you to set your own IDs – but then you need to ensure its uniqeness yourself
- Added
- Added possibility to use the button class inside your main
loop()function (instead of using callback handlers)- Added
bool wasPressed()function - Added
read(bool keepState = false), it returns the button press type (as aclickTypeenum) - Added
wait(bool keepState = false), it combineswasPressed()andread()methods and halts execution until a button press took place - Added
waitForClick(),waitForDouble(),waitForTriple()andwaitForLong()as well - Added ButtonLoop.ino example to showcase the "loop" functions
- Added
- Use
std::functionto allow C++ 11 lambda functions as suggested by jacobdekeizer in pull request #29
- added two new examples: MultiHandlerTwoButtons.ino and TrackDualButtonClick.ino
- added examples to the README.md
- initialized
pinin_getState() - a bit of variable clean up
- updated setting of inital state of
stateandprev_state
- updated README.md
- added
constto getter functions
- added getter/setter functions for debounce, longclick and doubleclick timeouts
- removed debounce timeout parameter from
contructorandbegin()
- Fixed a bug in the
loop()function
- Added default constructor and
begin()function - Added pull request by skelstar to add the
setLongClickDetectedHandler()function which is triggered as soon as the longclick timeout has passed
- Updated LongpressHandler example - changed variable name to from
buttontobutton - toggled
pressedandreleased(as suggesed by TommyC81 in #16) - added debug function
isPressedRaw()(as suggesed by TommyC81 in #16) - fixed bug with
click_count(as suggesed by TommyC81 in #16) - changed return types of
getNumberOfClicks()andgetClickType()tobyte
- Added capacitive touch sensor capabilties (for ESP32) (as suggested by qubolino in #11)
- Removed deprecated entry in the library.properties file (as suggested by SangLe) in #15
- Added
constmodifier to functions (as suggested by Anton-V-K in #13)
- Added possibility to define your own timeouts for clicks (as suggested by cmeldas in #10)
- Removed
yield()in mainloop()since it caused some problems - Created and added CHANGELOG.md
- Changed the private functions to protected (as suggested by Nagymadar in #9)
- Added support for active high buttons (as suggested by Nagymadar in #8)
- Added
reset()function to unset all functions (as suggested by Nagymadar in #7) - Added a
yield()command to the mainloop() - Changed the times for double and triple click
- Fixed error in
SingleButton.ino(as suggested by alexthe-red in #3) - Added the library to the Arduino IDE
- initial release
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.