├── .github └── FUNDING.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples ├── ButtonLoop │ └── ButtonLoop.ino ├── CustomButtonStateHandler │ └── CustomButtonStateHandler.ino ├── ESP32CapacitiveTouch │ └── ESP32CapacitiveTouch.ino ├── ESP32S2S3CapacitiveTouch │ └── ESP32S2S3CapacitiveTouch.ino ├── ESP32TimerInterrupt │ └── ESP32TimerInterrupt.ino ├── ESP32classicCapacitiveTouch │ └── ESP32classicCapacitiveTouch.ino ├── LongpressHandler │ └── LongpressHandler.ino ├── M5StackCore2CustomHandler │ └── M5StackCore2CustomHandler.ino ├── MultiHandler │ └── MultiHandler.ino ├── MultiHandlerTwoButtons │ └── MultiHandlerTwoButtons.ino ├── MultipleButtons │ └── MultipleButtons.ino ├── SingleButton │ └── SingleButton.ino ├── SingleButtonSimple │ └── SingleButtonSimple.ino └── TrackDualButtonClick │ └── TrackDualButtonClick.ino ├── keywords.txt ├── library.json ├── library.properties ├── src ├── Button2.cpp ├── Button2.h ├── Hardware.h └── main.cpp └── test └── Button2Test └── Button2Test.ino /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: lennart0815 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # MAC .dot folder 2 | .DS_Store 3 | # vs code files 4 | .vscode 5 | *.code-workspace 6 | # platformio ini file 7 | platformio.ini 8 | .pio 9 | # arduino_ci unit test binaries and artifacts 10 | *.bin 11 | *.bin.dSYM -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | **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. 4 | 5 | 6 | ## Unreleased 7 | 8 | nothing so far 9 | 10 | ## [2.3.5] - 2025-05-01 11 | 12 | - fixed contant name in example `M5StackCore2CustomHandler.ino` 13 | - replaced `boolean` with `bool` as requested in [#81](https://github.com/LennartHennigs/Button2/issues/81) 14 | - replaced `byte` with `uint_8` as requested in [#79](https://github.com/LennartHennigs/Button2/issues/79) 15 | 16 | ## [2.3.4] - 2025-01-26 17 | 18 | - added dummy file `main.cpp` to be able to compile lib in VSCode/PIO 19 | 20 | ## [2.3.3] - 2024-05-30 21 | 22 | - fixed bug, that first long press was not properly detected, see issue [#72](https://github.com/LennartHennigs/Button2/issues/72) 23 | - added `byte resetClickCount()` function 24 | - click count is no longer resetted in `resetPressedState()` 25 | - updated examples 26 | 27 | ## [2.3.2] - 2024-02-17 28 | 29 | - expanded condition to check for API version 2.0 (for UNO R4, RP2040, ...) 30 | 31 | 32 | ## [2.3.1] - 2024-01-02 33 | 34 | - bugfix for RP2040, as pointed out by [kilrah](https://github.com/kilrah) in issue [#60](https://github.com/LennartHennigs/Button2/issues/60) 35 | 36 | ## [2.3.0] - 2024-01-02 37 | 38 | - renamed Button2 constants, they now start with `BTN_` (BREAKING CHANGE) 39 | - added `Hardware.h` – it implements hardware pin abstraction. Needed for unit testing 40 | - added Unit Tests 41 | - added `resetPressedState()` function 42 | - added `ESP32S2S3CapacitiveTouch.ino` suggested by [ryancasler](https://github.com/ryancasler) in PR #57 43 | 44 | ## [2.2.4] - 2023-06-22 45 | 46 | - `getNumberOfClicks()` now works inside a callback and after the `wait()`statement(s). 47 | - Refactored code in `Button2.cpp` 48 | 49 | ## [2.2.3] - 2023-06-21 50 | 51 | - Included PR for issue [#54](https://github.com/LennartHennigs/Button2/issues/54) 52 | 53 | ## [2.2.2] - 2022-12-16 54 | 55 | - Another stab at the bug [#46](https://github.com/LennartHennigs/Button2/issues/46) 56 | 57 | ## [2.2.1] - 2022-12-16 58 | 59 | - Fixed bug [#46](https://github.com/LennartHennigs/Button2/issues/46) that in some instances long clicks are wrongly triggered 60 | 61 | ## [2.2.0] - 2022-12-13 62 | 63 | - Refactored the main `loop()` 64 | - Rewrote click detection 65 | - Cleaned up the long press handling 66 | - Removed compiler switches – they made the code unreadable and they only saved a few bytes (could be a BREAKING CHANGE) 67 | - Added `byte getLongClickCount()` function 68 | - Updated the [LongpressHandler](https://github.com/LennartHennigs/Button2/blob/master/examples/LongpressHandler/LongpressHandler.ino) example 69 | - Defaults (x>3)-clicks to triple 70 | - Fixed bug with button ID 71 | 72 | ## [2.1.0] - 2022-11-03 73 | 74 | - 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](https://github.com/LennartHennigs/Button2/blob/master/examples/ESP32CapacitiveTouch/ESP32CapacitiveTouch.ino). For reasons, see [#45](https://github.com/LennartHennigs/Button2/issues/45). 75 | - Added an ESP32 timer interrupt example [ESP32TimerInterrupt.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/ESP32TimerInterrupt/ESP32TimerInterrupt.ino) based on [#43](https://github.com/LennartHennigs/Button2/issues/43). 76 | - Added compiler switches in `Button.h` to remove click detection code, as mentioned in [#44](https://github.com/LennartHennigs/Button2/issues/44). 77 | - Clarified the difference between the `setLongClickHandler` and the `setLongClickDetectedHandler` in the README and the [MultiHandler](https://github.com/LennartHennigs/Button2/blob/master/examples/MultiHandler/MultiHandler.ino) example as mentioned in [#41](https://github.com/LennartHennigs/Button2/issues/41). (The handler set via `setLongClickHandler` waits until you release the button, the second one is called as soon as the defined long-click time has passed.) 78 | - Made `byte _getState()` into a `const` function. 79 | 80 | ## [2.0.3] - 2022-05-26 81 | 82 | - Fixed bug with the button ID as pointed out by [Jon](https://github.com/mscreations) in [#39](https://github.com/LennartHennigs/Button2/pull/39). 83 | 84 | ## [2.0.2] - 2022-05-21 85 | 86 | - Added example for the [M5Stack Core2](https://github.com/LennartHennigs/Button2/blob/master/examples/M5StackCore2CustomHandler/M5StackCore2CustomHandler.ino) - showing how to add a custom handler for the touch buttons. 87 | 88 | ## [2.0.1] - 2022-04-22 89 | 90 | - Fixed bug – `longclick_detected_counter` is not properly initalized as mentioned in [#37](https://github.com/LennartHennigs/Button2/pull/37). 91 | 92 | ## [2.0.0] - 2022-04-04 93 | 94 | - House keeping 95 | - Refactored `loop()` - cleaned up conditions, should be easier to understand now. 96 | - Renamed `getAttachedPin()`to `getPin()` (BREAKING CHANGE). 97 | - Fixed a bug that the first click type was falsly returned by `getClickType()`. 98 | - Possibility define your own "_getState" function for non standard buttons as suggested in [#32](https://github.com/LennartHennigs/Button2/issues/32). 99 | - Refactored `isPressedRaw()` to also use `_getState()`. 100 | - Introduced a `VIRTUAL_PIN` constant – using it in the constructor or `begin()` will skip pin initalization. 101 | - Added `setButtonStateFunction(StateCallbackFunction f)` to assign your own "_getState" function. 102 | - Added [CustomButtonStateHandler.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/CustomButtonStateHandler/CustomButtonStateHandler.ino) example. 103 | - Improved click type handling. 104 | - Added `clickType` and removed constants for determining the click type (BREAKING CHANGE) 105 | - Renamed `getClickType()` to `getType()` (BREAKING CHANGE) 106 | - Added `clickToString` function to print the `clickType` enum value 107 | - Added IDs button instances 108 | - Added `getID()`, it returns an auto incremented `int` ID for the button, as suggest in [#34](https://github.com/LennartHennigs/Button2/pull/34) 109 | - Added `setID()`, it allows you to set your own IDs – but then you need to ensure its uniqeness yourself 110 | - Added possibility to use the button class inside your main `loop()` function (instead of using callback handlers) 111 | - Added `bool wasPressed()` function 112 | - Added `read(bool keepState = false)`, it returns the button press type (as a `clickType` enum) 113 | - Added `wait(bool keepState = false)`, it combines `wasPressed()` and `read()` methods and halts execution until a button press took place 114 | - Added `waitForClick()`, `waitForDouble()`, `waitForTriple()` and `waitForLong()` as well 115 | - Added [ButtonLoop.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/ButtonLoop/ButtonLoop.ino) example to showcase the "loop" functions 116 | 117 | ## [1.6.5] - 2021-09-12 118 | 119 | - Fixed problem with `std::function` as found by [ItsFlo](https://github.com/ItsFlo) in pull request [#29](https://github.com/LennartHennigs/Button2/pull/29) 120 | 121 | ## [1.6.4] - 2021-09-12 122 | 123 | - Use `std::function` to allow C++ 11 lambda functions as suggested by [jacobdekeizer](https://github.com/jacobdekeizer) in pull request [#29](https://github.com/LennartHennigs/Button2/pull/29) 124 | 125 | ## [1.6.3] - 2021-09-12 126 | 127 | - added two new examples: [MultiHandlerTwoButtons.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/MultiHandlerTwoButtons/MultiHandlerTwoButtons.ino) and [TrackDualButtonClick.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/TrackDualButtonClick/TrackDualButtonClick.ino) 128 | - added examples to the [README.md](https://github.com/LennartHennigs/Button2/blob/master/README.md) 129 | - initialized `pin` in `_getState()` 130 | - a bit of variable clean up 131 | - updated setting of inital state of `state` and `prev_state` 132 | 133 | ## [1.6.2] - 2021-06-22 134 | 135 | - initialized `pin` property to 255 instead of -1, as pointed out by [rin67630](https://github.com/rin67630) in issue [#26](https://github.com/LennartHennigs/Button2/issues/26) 136 | 137 | ## [1.6.1] - 2021-03-22 138 | 139 | - updated [README.md](https://github.com/LennartHennigs/Button2/blob/master/README.md) 140 | - added `const` to getter functions 141 | 142 | ## [1.6.0] - 2021-02-10 143 | 144 | - added getter/setter functions for debounce, longclick and doubleclick timeouts 145 | - removed debounce timeout parameter from `contructor` and `begin()` 146 | 147 | ## [1.5.4] - 2021-02-08 148 | 149 | - Added `getAttachPin()` function, as suggested by [madivad](https://github.com/madivad) in issue [#23](https://github.com/LennartHennigs/Button2/issues/23) 150 | 151 | ## [1.5.3] - 2021-01-26 152 | 153 | - Fixed a bug in the constructor, as suggested by [alex-s-v](https://github.com/alex-s-v) in pull request [#22](https://github.com/LennartHennigs/Button2/pull/22) 154 | 155 | ## [1.5.2] - 2021-01-26 156 | 157 | - Fixed a bug in the `isPressed()` function, as suggested by [zenturacp](https://github.com/zenturacp) in [#21](https://github.com/LennartHennigs/Button2/issues/21) 158 | 159 | ## [1.5.1] - 2021-01-04 160 | 161 | - Fixed a bug in the `loop()` function 162 | 163 | ## [1.5.0] - 2021-01-03 164 | 165 | - Added default constructor and `begin()` function 166 | - Added pull request by [skelstar](https://github.com/skelstar) to add the `setLongClickDetectedHandler()` function which is triggered as soon as the longclick timeout has passed 167 | 168 | ## [1.4.1] - 2020-12-19 169 | 170 | - Moved activeLow outside of isCapacitive condition (as suggested by [Wai Lin](https://github.com/w4ilun) in [#18](https://github.com/LennartHennigs/Button2/pull/18) 171 | 172 | ## [1.4.0] - 2020-11-06 173 | 174 | - Updated LongpressHandler example - changed variable name to from `button` to `button` 175 | - toggled `pressed` and `released` (as suggesed by [TommyC81](https://github.com/TommyC81) in [#16](https://github.com/LennartHennigs/Button2/issues/16)) 176 | - added debug function `isPressedRaw()` (as suggesed by [TommyC81](https://github.com/TommyC81) in [#16](https://github.com/LennartHennigs/Button2/issues/16)) 177 | - fixed bug with `click_count` (as suggesed by [TommyC81](https://github.com/TommyC81) in [#16](https://github.com/LennartHennigs/Button2/issues/16)) 178 | - changed return types of `getNumberOfClicks()` and `getClickType()` to `byte` 179 | 180 | ## [1.3.0] - 2020-11-06 181 | 182 | - Added capacitive touch sensor capabilties (for ESP32) (as suggested by [qubolino](https://github.com/qubolino) in [#11](https://github.com/LennartHennigs/Button2/issues/11)) 183 | - Removed deprecated entry in the library.properties file (as suggested by [SangLe](https://github.com/SNL5943)) in [#15](https://github.com/LennartHennigs/Button2/issues/15) 184 | - Added `const` modifier to functions (as suggested by [Anton-V-K](https://github.com/Anton-V-K) in [#13](https://github.com/LennartHennigs/Button2/issues/13)) 185 | 186 | ## [1.2.0] - 2020-04-16 187 | 188 | - Added possibility to define your own timeouts for clicks (as suggested by [cmeldas](https://github.com/cmeldas) in [#10](https://github.com/LennartHennigs/Button2/issues/10)) 189 | - Removed `yield()` in main `loop()` since it caused some problems 190 | - Created and added CHANGELOG.md 191 | 192 | ## [1.1.0] - 2020-03-27 193 | 194 | - Changed the private functions to protected (as suggested by [Nagymadar](https://github.com/Nagymadar) in [#9](https://github.com/LennartHennigs/Button2/issues/9)) 195 | - Added support for active high buttons (as suggested by [Nagymadar](https://github.com/Nagymadar) in [#8](https://github.com/LennartHennigs/Button2/issues/8)) 196 | - Added `reset()` function to unset all functions (as suggested by [Nagymadar](https://github.com/Nagymadar) in [#7](https://github.com/LennartHennigs/Button2/issues/7)) 197 | - Added a `yield()` command to the main `loop()` 198 | - Changed the times for double and triple click 199 | - Fixed error in `SingleButton.ino` (as suggested by [alexthe-red](https://github.com/alexthe-red) in [#3](https://github.com/LennartHennigs/Button2/issues/3)) 200 | - Added the library to the Arduino IDE 201 | 202 | ## [1.0.0] - 2017-11-09 203 | 204 | - initial release 205 | 206 | ## Note 207 | 208 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 209 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 210 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2025 Lennart Hennigs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Button2 2 | 3 | Arduino/ESP library to simplify working with buttons. 4 | 5 | - Author: Lennart Hennigs () 6 | - Copyright (C) 2017-2025 Lennart Hennigs. 7 | - Released under the MIT license. 8 | 9 | ## Description 10 | 11 | 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()`. 12 | The library also takes care of debouncing. Using this lib will reduce and simplify your source code significantly. 13 | 14 | It has been tested with Arduino, ESP8266 and ESP32 devices. 15 | 16 | To see the latest changes to the library please take a look at the [Changelog](https://github.com/LennartHennigs/Button2/blob/master/CHANGELOG.md). 17 | 18 | If you find this library helpful please consider giving it a ⭐️ at [GitHub](https://github.com/LennartHennigs/Button2) and/or [buy me a ☕️](https://ko-fi.com/lennart0815). 19 | 20 | Thank you! 21 | 22 | ## How To Use 23 | 24 | This library allows you to define a button and uses callback functions to detect different types of button interactions. 25 | If you don't want to use callback there are also functions available for using it in your code's main `loop()`. 26 | 27 | ### Definition 28 | 29 | - Include the library on top 30 | 31 | ```c++ 32 | #include "Button2.h" 33 | ``` 34 | 35 | - Define the button either using the `constructor` or the `begin()` function. 36 | 37 | ```c++ 38 | void begin(byte attachTo, byte buttonMode = INPUT_PULLUP, bool activeLow = true); 39 | ``` 40 | 41 | ### Button Types 42 | 43 | - You can use the class for "real" buttons (*pullup*, *pulldown*, and *active low*). 44 | - Per default the button pins are defined as `INPUT_PULLUP`. You can override this upon creation. 45 | 46 | ```c++ 47 | #include "Button2.h" 48 | #define BUTTON_PIN D3 49 | 50 | Button2 button; 51 | 52 | void setup() { 53 | button.begin(BUTTON_PIN); 54 | } 55 | ``` 56 | 57 | - You can also the library with other types of buttons, e.g. capacitive touch or ones handled via I2C. See the section on defining custom handlers below. 58 | 59 | ### Callback Handler 60 | 61 | - Instead of frequently checking the button state in your main `loop()` this class allows you to assign callback functions. 62 | - You can define callback functions to track various types of clicks: 63 | - `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. 64 | - `setClickHandler()` will be triggered after a single click occurred. 65 | - `setChangedHandler()`, `setPressedHandler()` and `setReleasedHandler()` allow to detect basic interactions. 66 | - `setLongClickDetectedHandler()` will be triggered as soon as the long click timeout has passed. 67 | - `setLongClickHandler()` will be triggered after the button has released. 68 | - `setDoubleClickHandler()` and `setTripleClickHandler()` detect complex interactions. 69 | 70 | - **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()` 71 | 72 | - You can assign callback functions for single or for multiple buttons. 73 | - You can track individual or multiple events with a single handler. 74 | - Please take a look at the included examples (see below) to get an overview over the different callback handlers and their usage. 75 | - All callback functions need a `Button2` reference parameter. There the reference to the triggered button is stored. This can used to call status functions, e.g. `wasPressedFor()`. 76 | 77 | ### Longpress Handling 78 | 79 | - There are two possible callback functions: `setLongClickDetectedHandler()` and `setLongClickHandler()`. 80 | - `setLongClickDetectedHandler()` will be called as soon as the defined timeout has passed. 81 | - `setLongClickHandler()` will only be called after the button has been released. 82 | - `setLongClickDetectedRetriggerable(bool retriggerable)` allows you to define whether want to get multiple notifications for a **single** long click depending on the timeout. 83 | - `getLongClickCount()` gets you the number of long clicks – this is useful when `retriggerable` is set. 84 | 85 | ### The Loop 86 | 87 | - For the class to work, you need to call the button's `loop()` member function in your sketch's `loop()` function. 88 | 89 | ```c++ 90 | #include "Button2.h" 91 | #define BUTTON_PIN D3 92 | 93 | Button2 button; 94 | 95 | void handleTap(Button2& b) { 96 | // check for really long clicks 97 | if (b.wasPressedFor() > 1000) { 98 | // do something 99 | } 100 | } 101 | 102 | void setup() { 103 | button.begin(BUTTON_PIN); 104 | button.setTapHandler(handleTap); 105 | } 106 | 107 | void loop() { 108 | button.loop(); 109 | } 110 | ``` 111 | 112 | - As the `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. 113 | - Please see the *examples* below for more details. 114 | 115 | ### Using an timer interrupt instead 116 | 117 | - Alternatively, you can call the button's `loop()` function via a timer interrupt. 118 | - I haven't tried this extensively, USE THIS AT YOUR OWN RISK! 119 | - You need make sure that the interval is quick enough that it can detect your timeouts (see below). 120 | - There is an example for the ESP32 [ESP32TimerInterrupt.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/ESP32TimerInterrupt/ESP32TimerInterrupt.ino) that I tested. 121 | 122 | ### Timeouts 123 | 124 | - The default timeouts for events are (in ms): 125 | 126 | ```c++ 127 | #define BTN_DEBOUNCE_MS 50 128 | #define BTN_LONGCLICK_MS 200 129 | #define BTN_DOUBLECLICK_MS 300 130 | ``` 131 | 132 | - You can define your own timeouts by using these setter functions: 133 | - `void setDebounceTime(unsigned int ms)` 134 | - `void setLongClickTime(unsigned int ms)` 135 | - `void setDoubleClickTime(unsigned int ms)` 136 | - There are also getter functions available, if needed. 137 | 138 | ### Using Button2 in the main `loop()` 139 | 140 | - Even though I suggest to use handlers for tracking events, you can also use Button2 to check button's state in the main loop 141 | - `bool wasPressed()` allows you to check whether the button was pressed 142 | - `clickType read(bool keepState = false)` gives you the type of click that took place 143 | - `clickType wait(bool keepState = false)` combines `read()` and `wasPressed()` and halts execution until a button click was detected. Thus, it is blocking code. 144 | - The `clickType` is an enum defined as... 145 | 146 | ```c++ 147 | enum clickType { 148 | single_click, 149 | double_click, 150 | triple_click, 151 | long_click, 152 | empty 153 | }; 154 | ``` 155 | 156 | - There are also dedicated waits (`waitForClick()`, `waitForDouble()`, `waitForTriple()` and `waitForLong()`) to detect a specific type 157 | - The `read()` and the *wait* functions will reset the state of `wasPressed()` unless specified otherwise (via a `bool` parameter) 158 | - `resetPressedState()` allows you to clear value returned by `wasPressed()` – it is similar to passing `keepState = false` for `read()` or `wait()`. 159 | - Check out the [ButtonLoop.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/ButtonLoop/ButtonLoop.ino) example to see it in action 160 | 161 | ### Status Functions 162 | 163 | - There are several status functions available to check the status of a button: 164 | 165 | ``` c++ 166 | unsigned int wasPressedFor() const; 167 | byte getNumberOfClicks() const; 168 | byte getType() const; 169 | bool isPressed() const; 170 | bool isPressedRaw() const; 171 | bool wasPressed() const; 172 | ``` 173 | 174 | ### IDs for Button Instances 175 | 176 | - Each button instance gets a unique (auto incremented) ID upon creation. 177 | - You can get a buttons' ID via `getID()`. 178 | - Alternatively, you can use `setID(int newID)` to set a new one. But then you need to make sure that they are unique. 179 | 180 | ### Creating A Custom Button State Handler 181 | 182 | - Out of the box *Button2* supports regular hardware buttons. 183 | - If you want to add other button types you need to define your own function that tracks the state of the button. 184 | - Use `setButtonStateFunction()` to assign it to your *Button2* instance 185 | - Make the button pin 'VIRTUAL', i.e. by calling `button.begin(VIRTUAL_PIN);` 186 | - And don't forget to initialize the button as this cannot be handled by *Button2* 187 | - See [ESP32CapacitiveTouch.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/ESP32CapacitiveTouch/ESP32CapacitiveTouch.ino), [M5StackCore2CustomHandler.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/M5StackCore2CustomHandler/M5StackCore2CustomHandler.ino), and [CustomButtonStateHandler.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/CustomButtonStateHandler/CustomButtonStateHandler.ino) as examples. 188 | 189 | ## Examples 190 | 191 | - [SingleButtonSimple.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/SingleButtonSimple/SingleButtonSimple.ino) – the most basic example, shows how to assign event handlers 192 | - [LongpressHandler.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/LongpressHandler/LongpressHandler.ino) – shows how determine the time of a button press 193 | - [SingleButton.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/SingleButton/SingleButton.ino) – shows the different event handlers 194 | - [MultipleButtons.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/MultipleButtons/MultipleButtons.ino) – how to use two buttons 195 | - [MultiHandler.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/MultiHandler/MultiHandler.ino) – how to use a single handler for multiple events 196 | - [MultiHandlerTwoButtons.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/MultiHandlerTwoButtons/MultiHandlerTwoButtons.ino) – a single handler for multiple buttons 197 | - [TrackDualButtonClick.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/TrackDualButtonClick/TrackDualButtonClick.ino) – how to detect when two buttons are clicked at the same time 198 | - [CustomButtonStateHandler.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/CustomButtonStateHandler/CustomButtonStateHandler.ino) - how to assign your own button handler 199 | - [ESP32CapacitiveTouch.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/ESP32CapacitiveTouch/ESP32CapacitiveTouch.ino) – how to access the ESP32s capacitive touch handlers 200 | - [M5StackCore2CustomHandler.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/M5StackCore2CustomHandler/M5StackCore2CustomHandler.ino) - example for the M5Stack Core2 touch buttons 201 | - [ESP32TimerInterrupt.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/ESP32TimerInterrupt/ESP32TimerInterrupt.ino) - how to use a timer interrupt with the library. 202 | - [ButtonLoop.ino](https://github.com/LennartHennigs/Button2/blob/master/examples/ButtonLoop/ButtonLoop.ino) – how to use the button class in the main loop (I recommend using handlers, but well...) 203 | 204 | ## Class Definition 205 | 206 | The button class offers a few additional functions, please take a look at the *Class Definition* below. 207 | 208 | See below the constructors and member functions the library provides: 209 | 210 | ```c++ 211 | Button2(); 212 | Button2(byte attachTo, byte buttonMode = INPUT_PULLUP, bool activeLow = true); 213 | 214 | void begin(byte attachTo, byte buttonMode = INPUT_PULLUP, bool activeLow = true); 215 | 216 | void setDebounceTime(unsigned int ms); 217 | void setLongClickTime(unsigned int ms); 218 | void setDoubleClickTime(unsigned int ms); 219 | 220 | unsigned int getDebounceTime(); 221 | unsigned int getLongClickTime(); 222 | unsigned int getDoubleClickTime(); 223 | byte getPin(); 224 | 225 | void reset(); 226 | 227 | void setButtonStateFunction(StateCallbackFunction f); 228 | 229 | void setChangedHandler(CallbackFunction f); 230 | void setPressedHandler(CallbackFunction f); 231 | void setReleasedHandler(CallbackFunction f); 232 | 233 | void setTapHandler(CallbackFunction f); 234 | void setClickHandler(CallbackFunction f); 235 | void setDoubleClickHandler(CallbackFunction f); 236 | void setTripleClickHandler(CallbackFunction f); 237 | 238 | void setLongClickHandler(CallbackFunction f); 239 | void setLongClickDetectedHandler(CallbackFunction f); 240 | void setLongClickDetectedRetriggerable(bool retriggerable); 241 | void byte getLongClickCount() const; 242 | 243 | unsigned int wasPressedFor() const; 244 | void resetPressedState(); 245 | byte resetClickCount(); 246 | 247 | bool isPressed() const; 248 | bool isPressedRaw() const; 249 | 250 | bool wasPressed() const; 251 | clickType read(bool keepState = false); 252 | clickType wait(bool keepState = false); 253 | void waitForClick(bool keepState = false); 254 | void waitForDouble(bool keepState = false); 255 | void waitForTriple(bool keepState = false); 256 | void waitForLong(bool keepState = false); 257 | 258 | byte getNumberOfClicks() const; 259 | byte getType() const; 260 | String clickToString(clickType type) const; 261 | 262 | int getID() const; 263 | void setID(int newID); 264 | 265 | bool operator == (Button2 &rhs); 266 | 267 | void loop(); 268 | ``` 269 | 270 | ## Installation 271 | 272 | Open the Arduino IDE choose "Sketch > Include Library" and search for "Button2". 273 | Or download the ZIP archive (), and choose "Sketch > Include Library > Add .ZIP Library..." and select the downloaded file. 274 | 275 | ## License 276 | 277 | MIT License 278 | 279 | Copyright (c) 2017-2025 Lennart Hennigs 280 | 281 | Permission is hereby granted, free of charge, to any person obtaining a copy 282 | of this software and associated documentation files (the "Software"), to deal 283 | in the Software without restriction, including without limitation the rights 284 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 285 | copies of the Software, and to permit persons to whom the Software is 286 | furnished to do so, subject to the following conditions: 287 | 288 | The above copyright notice and this permission notice shall be included in all 289 | copies or substantial portions of the Software. 290 | 291 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 292 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 293 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 294 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 295 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 296 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 297 | SOFTWARE. 298 | -------------------------------------------------------------------------------- /examples/ButtonLoop/ButtonLoop.ino: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////// 2 | 3 | #include "Button2.h" 4 | 5 | ///////////////////////////////////////////////////////////////// 6 | 7 | #define BUTTON_PIN 2 8 | 9 | ///////////////////////////////////////////////////////////////// 10 | 11 | Button2 button; 12 | 13 | ///////////////////////////////////////////////////////////////// 14 | 15 | void setup() { 16 | Serial.begin(115200); 17 | delay(50); 18 | Serial.println("\n\nButton Loop Demo"); 19 | 20 | button.begin(BUTTON_PIN); 21 | } 22 | 23 | ///////////////////////////////////////////////////////////////// 24 | 25 | void loop() { 26 | button.loop(); 27 | 28 | 29 | Serial.println(button.clickToString(button.wait())); 30 | Serial.println(button.getNumberOfClicks()); 31 | 32 | /* 33 | * or replace the above line with (keep the loop): 34 | 35 | if (button.wasPressed()) { 36 | switch(button.read()) { 37 | case single_click: 38 | Serial.println("single"); 39 | break; 40 | case double_click: 41 | Serial.println("double"); 42 | break; 43 | case triple_click: 44 | Serial.println("triple"); 45 | break; 46 | case long_click: 47 | Serial.println("looong"); 48 | break; 49 | } 50 | } 51 | */ 52 | 53 | } 54 | ///////////////////////////////////////////////////////////////// 55 | -------------------------------------------------------------------------------- /examples/CustomButtonStateHandler/CustomButtonStateHandler.ino: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////// 2 | #include "Button2.h" 3 | ///////////////////////////////////////////////////////////////// 4 | 5 | Button2 button; 6 | 7 | ///////////////////////////////////////////////////////////////// 8 | 9 | uint8_t myButtonStateHandler() { 10 | return digitalRead(39); 11 | } 12 | 13 | ///////////////////////////////////////////////////////////////// 14 | 15 | void myTapHandler(Button2 &b) { 16 | Serial.println("tap"); 17 | } 18 | 19 | ///////////////////////////////////////////////////////////////// 20 | 21 | void setup() { 22 | Serial.begin(115200); 23 | delay(100); 24 | Serial.println("\n\nCustom Buttom Test"); 25 | button.begin(BTN_VIRTUAL_PIN); 26 | 27 | pinMode(39, INPUT_PULLUP); 28 | 29 | button.setButtonStateFunction(myButtonStateHandler); 30 | button.setTapHandler(myTapHandler); 31 | } 32 | 33 | ///////////////////////////////////////////////////////////////// 34 | 35 | void loop() { 36 | button.loop(); 37 | } 38 | 39 | ///////////////////////////////////////////////////////////////// -------------------------------------------------------------------------------- /examples/ESP32CapacitiveTouch/ESP32CapacitiveTouch.ino: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////// 2 | 3 | #if !defined(ESP32) 4 | #error This sketch needs an ESP32 5 | #else 6 | 7 | ///////////////////////////////////////////////////////////////// 8 | 9 | #include "Button2.h" 10 | 11 | ///////////////////////////////////////////////////////////////// 12 | 13 | Button2 button; 14 | uint8_t pin = 4; 15 | 16 | ///////////////////////////////////////////////////////////////// 17 | 18 | byte capStateHandler() { 19 | int capa = touchRead(pin); 20 | return capa < button.getDebounceTime() ? LOW : HIGH; 21 | } 22 | 23 | ///////////////////////////////////////////////////////////////// 24 | 25 | void click(Button2& btn) { 26 | Serial.println("click\n"); 27 | } 28 | 29 | ///////////////////////////////////////////////////////////////// 30 | 31 | void setup() { 32 | Serial.begin(9600); 33 | delay(50); 34 | Serial.println("\n\nCapacitive Touch Demo"); 35 | 36 | button.setDebounceTime(35); 37 | button.setButtonStateFunction(capStateHandler); 38 | button.setClickHandler(click); 39 | button.begin(BTN_VIRTUAL_PIN); 40 | } 41 | 42 | ///////////////////////////////////////////////////////////////// 43 | 44 | void loop() { 45 | button.loop(); 46 | } 47 | 48 | ///////////////////////////////////////////////////////////////// 49 | #endif 50 | ///////////////////////////////////////////////////////////////// 51 | -------------------------------------------------------------------------------- /examples/ESP32S2S3CapacitiveTouch/ESP32S2S3CapacitiveTouch.ino: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////// 2 | // see https://github.com/LennartHennigs/Button2/pull/57 3 | ///////////////////////////////////////////////////////////////// 4 | #if !defined(ESP32) 5 | #error This sketch needs an ESP32 S2 or S3 6 | #else 7 | 8 | ///////////////////////////////////////////////////////////////// 9 | 10 | #include "Button2.h" 11 | 12 | #define TOUCH_PIN T5 // Must declare the touch assignment, not the pin. 13 | 14 | int threshold = 1500; // ESP32S2 15 | bool touchdetected = false; 16 | uint8_t buttonState = HIGH;// HIGH is for unpressed, pressed = LOW 17 | ///////////////////////////////////////////////////////////////// 18 | 19 | Button2 button; 20 | 21 | ///////////////////////////////////////////////////////////////// 22 | void gotTouch() { 23 | touchdetected = true; 24 | } 25 | 26 | 27 | uint8_t capStateHandler() { 28 | return buttonState; 29 | } 30 | 31 | ///////////////////////////////////////////////////////////////// 32 | 33 | void setup() { 34 | Serial.begin(115200); 35 | delay(50); 36 | Serial.println("\n\nCapacitive Touch Demo"); 37 | touchAttachInterrupt(TOUCH_PIN, gotTouch, threshold); 38 | button.setDebounceTime(35); 39 | button.setButtonStateFunction(capStateHandler); 40 | button.setClickHandler(click); 41 | button.begin(BTN_VIRTUAL_PIN); 42 | } 43 | 44 | ///////////////////////////////////////////////////////////////// 45 | 46 | void loop() { 47 | button.loop(); 48 | if (touchdetected) { 49 | touchdetected = false; 50 | if (touchInterruptGetLastStatus(TOUCH_PIN)) { 51 | buttonState = LOW; 52 | } else { 53 | buttonState = HIGH; 54 | } 55 | } 56 | } 57 | 58 | ///////////////////////////////////////////////////////////////// 59 | 60 | void click(Button2& btn) { 61 | Serial.println("click\n"); 62 | } 63 | 64 | ///////////////////////////////////////////////////////////////// 65 | #endif 66 | ///////////////////////////////////////////////////////////////// 67 | -------------------------------------------------------------------------------- /examples/ESP32TimerInterrupt/ESP32TimerInterrupt.ino: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////// 2 | 3 | #if !defined(ESP32) 4 | #error This sketch needs an ESP32 5 | #else 6 | 7 | ///////////////////////////////////////////////////////////////// 8 | #include "Button2.h" 9 | ///////////////////////////////////////////////////////////////// 10 | 11 | #define BUTTON_PIN 39 12 | 13 | ///////////////////////////////////////////////////////////////// 14 | 15 | Button2 btn; 16 | hw_timer_t *timer = NULL; 17 | 18 | ///////////////////////////////////////////////////////////////// 19 | 20 | void IRAM_ATTR onTimer() { 21 | btn.loop(); 22 | } 23 | 24 | ///////////////////////////////////////////////////////////////// 25 | 26 | void click(Button2 &b) { 27 | Serial.println("click"); 28 | } 29 | 30 | ///////////////////////////////////////////////////////////////// 31 | 32 | void setup() { 33 | Serial.begin(115200); 34 | 35 | btn.begin(BUTTON_PIN); 36 | btn.setTapHandler(click); 37 | 38 | timer = timerBegin(0, 80, true); 39 | timerAttachInterrupt(timer, &onTimer, true); 40 | timerAlarmWrite(timer, 10000, true); // every 0.1 seconds 41 | timerAlarmEnable(timer); 42 | } 43 | 44 | ///////////////////////////////////////////////////////////////// 45 | 46 | void loop() { 47 | } 48 | 49 | ///////////////////////////////////////////////////////////////// 50 | #endif 51 | ///////////////////////////////////////////////////////////////// 52 | -------------------------------------------------------------------------------- /examples/ESP32classicCapacitiveTouch/ESP32classicCapacitiveTouch.ino: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////// 2 | 3 | #if !defined(ESP32) 4 | #error This sketch needs an ESP32 Classic 5 | #else 6 | 7 | ///////////////////////////////////////////////////////////////// 8 | 9 | #include "Button2.h" 10 | 11 | #define TOUCH_PIN T0 // Must declare the touch assignment, not the pin. For example, T0 is GPIO4 and T3 is GPIO 15. 12 | // You can look up the touch assignment in the datasheet: https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf 13 | 14 | int threshold = 40; // ESP32 15 | bool touchActive = false; 16 | bool lastTouchActive = false; 17 | bool testingLower = true; 18 | byte buttonState = HIGH;// HIGH is for unpressed, pressed = LOW 19 | ///////////////////////////////////////////////////////////////// 20 | 21 | Button2 button; 22 | 23 | ///////////////////////////////////////////////////////////////// 24 | void gotTouchEvent(){ 25 | if (lastTouchActive != testingLower) { 26 | touchActive = !touchActive; 27 | testingLower = !testingLower; 28 | // Touch ISR will be inverted: Lower <--> Higher than the Threshold after ISR event is noticed 29 | touchInterruptSetThresholdDirection(testingLower); 30 | } 31 | } 32 | ///////////////////////////////////////////////////////////////// 33 | byte capStateHandler() { 34 | return buttonState; 35 | } 36 | 37 | ///////////////////////////////////////////////////////////////// 38 | 39 | void click(Button2& btn) { 40 | Serial.println("click\n"); 41 | } 42 | 43 | ///////////////////////////////////////////////////////////////// 44 | 45 | void setup() { 46 | Serial.begin(115200); 47 | delay(50); 48 | Serial.println("\n\nCapacitive Touch Demo"); 49 | touchAttachInterrupt(TOUCH_PIN, gotTouchEvent, threshold); 50 | 51 | // Touch ISR will be activated when touchRead is lower than the Threshold 52 | touchInterruptSetThresholdDirection(testingLower); 53 | button.setDebounceTime(35); 54 | button.setButtonStateFunction(capStateHandler); 55 | button.setClickHandler(click); 56 | button.begin(BTN_VIRTUAL_PIN); 57 | } 58 | 59 | ///////////////////////////////////////////////////////////////// 60 | 61 | void loop() { 62 | button.loop(); 63 | if(lastTouchActive != touchActive){ 64 | lastTouchActive = touchActive; 65 | if (touchActive) { 66 | buttonState = LOW; 67 | } else { 68 | buttonState = HIGH; 69 | } 70 | } 71 | } 72 | 73 | ///////////////////////////////////////////////////////////////// 74 | #endif 75 | ///////////////////////////////////////////////////////////////// 76 | -------------------------------------------------------------------------------- /examples/LongpressHandler/LongpressHandler.ino: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////// 2 | 3 | #include "Button2.h" 4 | 5 | ///////////////////////////////////////////////////////////////// 6 | 7 | #define BUTTON_PIN 2 8 | 9 | ///////////////////////////////////////////////////////////////// 10 | 11 | Button2 button; 12 | 13 | ///////////////////////////////////////////////////////////////// 14 | 15 | void longClick(Button2& btn) { 16 | unsigned int time = btn.wasPressedFor(); 17 | Serial.print("You clicked "); 18 | if (time > 1500) { 19 | Serial.print("a really really long time."); 20 | } else if (time > 1000) { 21 | Serial.print("a really long time."); 22 | } else if (time > 500) { 23 | Serial.print("a long time."); 24 | } else { 25 | Serial.print("long."); 26 | } 27 | Serial.print(" ("); 28 | Serial.print(time); 29 | Serial.println(" ms)"); 30 | } 31 | 32 | ///////////////////////////////////////////////////////////////// 33 | 34 | void longClickDetected(Button2& btn) { 35 | Serial.print("long click #"); 36 | Serial.print(btn.getLongClickCount()); 37 | Serial.println(" detected"); 38 | } 39 | 40 | ///////////////////////////////////////////////////////////////// 41 | 42 | void setup() { 43 | Serial.begin(9600); 44 | while (!Serial) { 45 | delay(20); 46 | } 47 | Serial.println("\n\nLongpress Handler Demo"); 48 | button.begin(BUTTON_PIN); 49 | 50 | // button.setLongClickDetectedRetriggerable(true); 51 | 52 | button.setLongClickHandler(longClick); 53 | button.setLongClickDetectedHandler(longClickDetected); 54 | } 55 | 56 | ///////////////////////////////////////////////////////////////// 57 | 58 | void loop() { 59 | button.loop(); 60 | } 61 | 62 | ///////////////////////////////////////////////////////////////// 63 | -------------------------------------------------------------------------------- /examples/M5StackCore2CustomHandler/M5StackCore2CustomHandler.ino: -------------------------------------------------------------------------------- 1 | #include "M5Core2.h" 2 | #include "Button2.h" 3 | 4 | ///////////////////////////////////////////////////////////////// 5 | 6 | Button2 button; 7 | bool cleared = false; 8 | 9 | ///////////////////////////////////////////////////////////////// 10 | 11 | uint8_t myButtonStateHandler() { 12 | return !(M5.BtnA.isPressed()); 13 | } 14 | 15 | ///////////////////////////////////////////////////////////////// 16 | 17 | void setup() { 18 | M5.begin(); 19 | 20 | button.setDoubleClickTime(300); 21 | 22 | // button.setChangedHandler(changed); 23 | // button.setPressedHandler(pressed); 24 | button.setReleasedHandler(released); 25 | 26 | // button.setTapHandler(tap); 27 | button.setClickHandler(click); 28 | button.setLongClickDetectedHandler(longClickDetected); 29 | // button.setLongClickHandler(longClick); 30 | 31 | button.setDoubleClickHandler(doubleClick); 32 | button.setTripleClickHandler(tripleClick); 33 | 34 | button.setButtonStateFunction(myButtonStateHandler); 35 | button.begin(BTN_VIRTUAL_PIN); 36 | 37 | M5.Lcd.clear(); 38 | M5.Lcd.setTextSize(2); 39 | M5.Lcd.setTextWrap(true, true); 40 | M5.Lcd.print("Button A Test"); 41 | M5.Lcd.print("\n"); 42 | M5.Lcd.print("\n"); 43 | } 44 | 45 | void loop() { 46 | button.loop(); 47 | // clear screen if wrap happened 48 | if (M5.Lcd.getCursorY() <= 16) { 49 | if (!cleared) { 50 | M5.Lcd.clear(); 51 | Serial.println("now"); 52 | cleared = true; 53 | } 54 | } else { 55 | cleared = false; 56 | } 57 | M5.update(); 58 | } 59 | 60 | ///////////////////////////////////////////////////////////////// 61 | 62 | void pressed(Button2& btn) { 63 | M5.Lcd.print("pressed"); 64 | M5.Lcd.print("\n"); 65 | } 66 | void released(Button2& btn) { 67 | M5.Lcd.print("released: "); 68 | M5.Lcd.print(btn.wasPressedFor()); 69 | M5.Lcd.print("\n"); 70 | } 71 | void changed(Button2& btn) { 72 | M5.Lcd.print("changed"); 73 | M5.Lcd.print("\n"); 74 | } 75 | void click(Button2& btn) { 76 | M5.Lcd.print("click"); 77 | M5.Lcd.print("\n"); 78 | } 79 | void longClickDetected(Button2& btn) { 80 | M5.Lcd.print("long click detected"); 81 | M5.Lcd.print("\n"); 82 | } 83 | void longClick(Button2& btn) { 84 | M5.Lcd.print("long click"); 85 | M5.Lcd.print("\n"); 86 | } 87 | void doubleClick(Button2& btn) { 88 | M5.Lcd.print("double click"); 89 | M5.Lcd.print("\n"); 90 | } 91 | void tripleClick(Button2& btn) { 92 | M5.Lcd.print("triple click\n"); 93 | M5.Lcd.print("\n"); 94 | } 95 | void tap(Button2& btn) { 96 | M5.Lcd.print("tap"); 97 | M5.Lcd.print("\n"); 98 | } -------------------------------------------------------------------------------- /examples/MultiHandler/MultiHandler.ino: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////// 2 | 3 | #include "Button2.h" 4 | 5 | ///////////////////////////////////////////////////////////////// 6 | 7 | #define BUTTON_PIN 39 8 | 9 | ///////////////////////////////////////////////////////////////// 10 | 11 | Button2 button; 12 | 13 | ///////////////////////////////////////////////////////////////// 14 | 15 | void handler(Button2& btn) { 16 | switch (btn.getType()) { 17 | case single_click: 18 | break; 19 | case double_click: 20 | Serial.print("double "); 21 | break; 22 | case triple_click: 23 | Serial.print("triple "); 24 | break; 25 | case long_click: 26 | Serial.print("long"); 27 | break; 28 | case empty: 29 | return; 30 | } 31 | Serial.print("click"); 32 | Serial.print(" ("); 33 | Serial.print(btn.getNumberOfClicks()); 34 | Serial.println(")"); 35 | } 36 | 37 | ///////////////////////////////////////////////////////////////// 38 | 39 | void setup() { 40 | Serial.begin(115200); 41 | delay(50); 42 | Serial.println("\n\nMulti Handler Demo"); 43 | 44 | button.begin(BUTTON_PIN); 45 | button.setClickHandler(handler); 46 | //button.setLongClickHandler(handler); // this will only be called upon release 47 | button.setLongClickDetectedHandler(handler); // this will only be called upon detection 48 | button.setDoubleClickHandler(handler); 49 | button.setTripleClickHandler(handler); 50 | } 51 | 52 | ///////////////////////////////////////////////////////////////// 53 | 54 | void loop() { 55 | button.loop(); 56 | } 57 | 58 | ///////////////////////////////////////////////////////////////// 59 | -------------------------------------------------------------------------------- /examples/MultiHandlerTwoButtons/MultiHandlerTwoButtons.ino: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////// 2 | 3 | #include "Button2.h" 4 | 5 | ///////////////////////////////////////////////////////////////// 6 | 7 | #define BUTTON_PIN_1 37 8 | #define BUTTON_PIN_2 38 9 | 10 | ///////////////////////////////////////////////////////////////// 11 | 12 | Button2 button_1, button_2; 13 | 14 | ///////////////////////////////////////////////////////////////// 15 | 16 | void handler(Button2& btn) { 17 | switch (btn.getType()) { 18 | case single_click: 19 | break; 20 | case double_click: 21 | Serial.print("double "); 22 | break; 23 | default: 24 | break; 25 | } 26 | Serial.print("click "); 27 | Serial.print("on button #"); 28 | Serial.print((btn == button_1) ? "1" : "2"); 29 | Serial.println(); 30 | } 31 | 32 | ///////////////////////////////////////////////////////////////// 33 | 34 | void setup() { 35 | Serial.begin(115200); 36 | delay(50); 37 | Serial.println("\n\nMulti Handler Demo w/ 2 buttons"); 38 | 39 | button_1.begin(BUTTON_PIN_1); 40 | button_1.setClickHandler(handler); 41 | button_1.setDoubleClickHandler(handler); 42 | 43 | button_2.begin(BUTTON_PIN_2); 44 | button_2.setClickHandler(handler); 45 | button_2.setDoubleClickHandler(handler); 46 | 47 | } 48 | 49 | ///////////////////////////////////////////////////////////////// 50 | 51 | void loop() { 52 | button_1.loop(); 53 | button_2.loop(); 54 | } 55 | 56 | ///////////////////////////////////////////////////////////////// 57 | -------------------------------------------------------------------------------- /examples/MultipleButtons/MultipleButtons.ino: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////// 2 | 3 | #include "Button2.h" 4 | 5 | ///////////////////////////////////////////////////////////////// 6 | 7 | #define BUTTON_A_PIN 2 8 | #define BUTTON_B_PIN 0 9 | 10 | ///////////////////////////////////////////////////////////////// 11 | 12 | Button2 buttonA, buttonB; 13 | 14 | ///////////////////////////////////////////////////////////////// 15 | 16 | void click(Button2& btn) { 17 | if (btn == buttonA) { 18 | Serial.println("A clicked"); 19 | } else if (btn == buttonB) { 20 | Serial.println("B clicked"); 21 | } 22 | } 23 | 24 | ///////////////////////////////////////////////////////////////// 25 | 26 | void setup() { 27 | Serial.begin(9600); 28 | delay(50); 29 | Serial.println("\n\nMultiple Buttons Demo"); 30 | 31 | buttonA.begin(BUTTON_A_PIN); 32 | buttonA.setClickHandler(click); 33 | 34 | buttonB.begin(BUTTON_B_PIN); 35 | buttonB.setClickHandler(click); 36 | } 37 | 38 | ///////////////////////////////////////////////////////////////// 39 | 40 | void loop() { 41 | buttonA.loop(); 42 | buttonB.loop(); 43 | } 44 | 45 | ///////////////////////////////////////////////////////////////// 46 | -------------------------------------------------------------------------------- /examples/SingleButton/SingleButton.ino: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////// 2 | 3 | #include "Button2.h" 4 | 5 | ///////////////////////////////////////////////////////////////// 6 | 7 | #define BUTTON_PIN 37 8 | 9 | ///////////////////////////////////////////////////////////////// 10 | 11 | Button2 button; 12 | 13 | ///////////////////////////////////////////////////////////////// 14 | 15 | void pressed(Button2& btn) { 16 | Serial.println("pressed"); 17 | } 18 | void released(Button2& btn) { 19 | Serial.print("released: "); 20 | Serial.println(btn.wasPressedFor()); 21 | } 22 | void changed(Button2& btn) { 23 | Serial.println("changed"); 24 | } 25 | void click(Button2& btn) { 26 | Serial.println("click\n"); 27 | } 28 | void longClickDetected(Button2& btn) { 29 | Serial.println("long click detected"); 30 | } 31 | void longClick(Button2& btn) { 32 | Serial.println("long click\n"); 33 | } 34 | void doubleClick(Button2& btn) { 35 | Serial.println("double click\n"); 36 | } 37 | void tripleClick(Button2& btn) { 38 | Serial.println("triple click\n"); 39 | Serial.println(btn.getNumberOfClicks()); 40 | } 41 | void tap(Button2& btn) { 42 | Serial.println("tap"); 43 | } 44 | 45 | ///////////////////////////////////////////////////////////////// 46 | 47 | void setup() { 48 | Serial.begin(115200); 49 | delay(50); 50 | Serial.println("\n\nButton Demo"); 51 | 52 | button.begin(BUTTON_PIN); 53 | // button.setLongClickTime(1000); 54 | // button.setDoubleClickTime(400); 55 | 56 | Serial.println(" Longpress Time:\t" + String(button.getLongClickTime()) + "ms"); 57 | Serial.println(" DoubleClick Time:\t" + String(button.getDoubleClickTime()) + "ms"); 58 | Serial.println(); 59 | 60 | // button.setChangedHandler(changed); 61 | // button.setPressedHandler(pressed); 62 | button.setReleasedHandler(released); 63 | 64 | // button.setTapHandler(tap); 65 | button.setClickHandler(click); 66 | button.setLongClickDetectedHandler(longClickDetected); 67 | button.setLongClickHandler(longClick); 68 | button.setLongClickDetectedRetriggerable(false); 69 | 70 | button.setDoubleClickHandler(doubleClick); 71 | button.setTripleClickHandler(tripleClick); 72 | } 73 | 74 | ///////////////////////////////////////////////////////////////// 75 | 76 | void loop() { 77 | button.loop(); 78 | } 79 | 80 | ///////////////////////////////////////////////////////////////// 81 | -------------------------------------------------------------------------------- /examples/SingleButtonSimple/SingleButtonSimple.ino: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////// 2 | 3 | #include "Button2.h" 4 | #include 5 | 6 | ///////////////////////////////////////////////////////////////// 7 | 8 | #define BUTTON_PIN 39 9 | 10 | ///////////////////////////////////////////////////////////////// 11 | 12 | Button2 button; 13 | 14 | ///////////////////////////////////////////////////////////////// 15 | 16 | void pressed(Button2& btn) { 17 | Serial.println("pressed"); 18 | } 19 | void released(Button2& btn) { 20 | Serial.print("released: "); 21 | Serial.println(btn.wasPressedFor()); 22 | } 23 | void changed(Button2& btn) { 24 | Serial.println("changed"); 25 | } 26 | void tap(Button2& btn) { 27 | Serial.println("tap"); 28 | } 29 | 30 | ///////////////////////////////////////////////////////////////// 31 | 32 | void setup() { 33 | Serial.begin(115200); 34 | delay(50); 35 | Serial.println("\n\nButton Demo"); 36 | 37 | button.begin(BUTTON_PIN); 38 | button.setChangedHandler(changed); 39 | //button.setPressedHandler(pressed); 40 | //button.setReleasedHandler(released); 41 | 42 | // setTapHandler() is called by any type of click, longpress or shortpress 43 | button.setTapHandler(tap); 44 | } 45 | 46 | ///////////////////////////////////////////////////////////////// 47 | 48 | void loop() { 49 | button.loop(); 50 | } 51 | 52 | ///////////////////////////////////////////////////////////////// 53 | -------------------------------------------------------------------------------- /examples/TrackDualButtonClick/TrackDualButtonClick.ino: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////// 2 | 3 | #include "Button2.h" 4 | 5 | ///////////////////////////////////////////////////////////////// 6 | 7 | #define BUTTON_PIN_1 37 8 | #define BUTTON_PIN_2 38 9 | 10 | ///////////////////////////////////////////////////////////////// 11 | 12 | Button2 button_1, button_2; 13 | 14 | unsigned long now = 0; 15 | uint8_t counter = 0; 16 | 17 | ///////////////////////////////////////////////////////////////// 18 | 19 | void pressed(Button2& btn) { 20 | counter++; 21 | if (counter == 2) { 22 | now = millis(); 23 | Serial.println("now!"); 24 | } 25 | } 26 | 27 | ///////////////////////////////////////////////////////////////// 28 | 29 | void released(Button2& btn) { 30 | counter--; 31 | if (counter == 0) { 32 | if (now != 0) { 33 | Serial.print("Pressed for: "); 34 | Serial.print(millis() - now); 35 | Serial.println("ms"); 36 | } 37 | now = 0; 38 | } 39 | } 40 | 41 | ///////////////////////////////////////////////////////////////// 42 | 43 | void setup() { 44 | Serial.begin(115200); 45 | delay(50); 46 | Serial.println("\n\nTrack dual button press & release"); 47 | 48 | button_1.begin(BUTTON_PIN_1); 49 | button_1.setPressedHandler(pressed); 50 | button_1.setReleasedHandler(released); 51 | 52 | button_2.begin(BUTTON_PIN_2); 53 | button_2.setPressedHandler(pressed); 54 | button_2.setReleasedHandler(released); 55 | } 56 | 57 | ///////////////////////////////////////////////////////////////// 58 | 59 | void loop() { 60 | button_1.loop(); 61 | button_2.loop(); 62 | } 63 | 64 | ///////////////////////////////////////////////////////////////// 65 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | Button2 KEYWORD1 2 | begin KEYWORD2 3 | setDebounceTime KEYWORD2 4 | setLongClickTime KEYWORD2 5 | setDoubleClickTime KEYWORD2 6 | getDebounceTime KEYWORD2 7 | getLongClickTime KEYWORD2 8 | getDoubleClickTime KEYWORD2 9 | getPin KEYWORD2 10 | setLongClickDetectedRetriggerable KEYWORD2 11 | reset KEYWORD2 12 | setChangedHandler KEYWORD2 13 | setPressedHandler KEYWORD2 14 | setReleasedHandler KEYWORD2 15 | setTapHandler KEYWORD2 16 | setClickHandler KEYWORD2 17 | setDoubleClickHandler KEYWORD2 18 | setTripleClickHandler KEYWORD2 19 | setLongClickHandler KEYWORD2 20 | setLongClickDetectedHandler KEYWORD2 21 | wasPressedFor KEYWORD2 22 | isPressed KEYWORD2 23 | isPressedRaw KEYWORD2 24 | getNumberOfClicks KEYWORD2 25 | getType KEYWORD2 26 | clickToString KEYWORD2 27 | getID KEYWORD2 28 | setID KEYWORD2 29 | wasPressed KEYWORD2 30 | resetPressedState KEYWORD2 31 | resetClickCount KEYWORD2 32 | read KEYWORD2 33 | getLongClickCount KEYWORD2 34 | wait KEYWORD2 35 | waitForClick KEYWORD2 36 | waitForDouble KEYWORD2 37 | waitForTriple KEYWORD2 38 | waitForLong KEYWORD2 39 | loop KEYWORD2 40 | BTN_DEBOUNCE_MS LITERAL1 41 | BTN_LONGCLICK_MS LITERAL1 42 | BTN_DOUBLECLICK_MS LITERAL1 43 | BTN_UNDEFINED_PIN LITERAL1 44 | BTN_VIRTUAL_PIN LITERAL1 45 | clickType LITERAL1 -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Button2", 3 | "version": "2.3.5", 4 | "keywords": "button", 5 | "description": "Arduino/ESP Library to simplify working with buttons. It 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.", 6 | "homepage": "https://github.com/LennartHennigs/Button2", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/LennartHennigs/Button2" 10 | }, 11 | "authors": { 12 | "name": "Lennart Hennigs", 13 | "url": "https://lennarthennigs.de" 14 | }, 15 | "frameworks": "arduino", 16 | "platforms": "*" 17 | } 18 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Button2 2 | version=2.3.5 3 | author=Lennart Hennigs 4 | maintainer=Lennart Hennigs 5 | sentence=Arduino/ESP library to simplify working with buttons. 6 | paragraph=It 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. 7 | category=Communication 8 | url=https://github.com/LennartHennigs/Button2 9 | architectures=* 10 | -------------------------------------------------------------------------------- /src/Button2.cpp: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////// 2 | /* 3 | Button2.cpp - Arduino Library to simplify working with buttons. 4 | Copyright (C) 2017-2025 Lennart Hennigs. 5 | Released under the MIT license. 6 | */ 7 | ///////////////////////////////////////////////////////////////// 8 | 9 | #include "Button2.h" 10 | 11 | ///////////////////////////////////////////////////////////////// 12 | // initialize static counter for the IDs 13 | 14 | int Button2::_nextID = 0; 15 | 16 | ///////////////////////////////////////////////////////////////// 17 | // default constructor 18 | 19 | Button2::Button2() { 20 | pin = BTN_UNDEFINED_PIN; 21 | _setID(); 22 | } 23 | 24 | ///////////////////////////////////////////////////////////////// 25 | // constructor 26 | 27 | Button2::Button2(uint8_t attachTo, uint8_t buttonMode /* = INPUT_PULLUP */, bool activeLow /* = true */, Hardware *hardware /* = ArduinoHardware() */) { 28 | begin(attachTo, buttonMode, activeLow, hardware); 29 | _setID(); 30 | } 31 | 32 | ///////////////////////////////////////////////////////////////// 33 | 34 | void Button2::begin(uint8_t attachTo, uint8_t buttonMode /* = INPUT_PULLUP */, bool activeLow /* = true */, Hardware *hardware /* = ArduinoHardware() */) { 35 | hw = hardware; 36 | pin = attachTo; 37 | longclick_counter = 0; 38 | longclick_retriggerable = false; 39 | _pressedState = activeLow ? LOW : HIGH; 40 | 41 | if (attachTo != BTN_VIRTUAL_PIN) { 42 | hw->pinMode(attachTo, buttonMode); 43 | } 44 | // state = activeLow ? HIGH : LOW; 45 | state = _getState(); 46 | prev_state = state; 47 | } 48 | 49 | ///////////////////////////////////////////////////////////////// 50 | 51 | void Button2::setDebounceTime(unsigned int ms) { 52 | debounce_time_ms = ms; 53 | } 54 | 55 | ///////////////////////////////////////////////////////////////// 56 | 57 | void Button2::setLongClickTime(unsigned int ms) { 58 | longclick_time_ms = ms; 59 | } 60 | 61 | ///////////////////////////////////////////////////////////////// 62 | 63 | void Button2::setDoubleClickTime(unsigned int ms) { 64 | doubleclick_time_ms = ms; 65 | } 66 | 67 | ///////////////////////////////////////////////////////////////// 68 | 69 | unsigned int Button2::getDebounceTime() const { 70 | return debounce_time_ms; 71 | } 72 | 73 | ///////////////////////////////////////////////////////////////// 74 | 75 | unsigned int Button2::getLongClickTime() const { 76 | return longclick_time_ms; 77 | } 78 | 79 | ///////////////////////////////////////////////////////////////// 80 | 81 | unsigned int Button2::getDoubleClickTime() const { 82 | return doubleclick_time_ms; 83 | } 84 | 85 | ///////////////////////////////////////////////////////////////// 86 | 87 | uint8_t Button2::getPin() const { 88 | return pin; 89 | } 90 | 91 | ///////////////////////////////////////////////////////////////// 92 | 93 | void Button2::setButtonStateFunction(StateCallbackFunction f) { 94 | get_state_cb = f; 95 | } 96 | 97 | ///////////////////////////////////////////////////////////////// 98 | 99 | bool Button2::operator==(Button2 &rhs) { 100 | return (this == &rhs); 101 | } 102 | 103 | ///////////////////////////////////////////////////////////////// 104 | 105 | void Button2::setChangedHandler(CallbackFunction f) { 106 | change_cb = f; 107 | } 108 | 109 | ///////////////////////////////////////////////////////////////// 110 | 111 | void Button2::setPressedHandler(CallbackFunction f) { 112 | pressed_cb = f; 113 | } 114 | 115 | ///////////////////////////////////////////////////////////////// 116 | 117 | void Button2::setReleasedHandler(CallbackFunction f) { 118 | released_cb = f; 119 | } 120 | 121 | ///////////////////////////////////////////////////////////////// 122 | 123 | void Button2::setClickHandler(CallbackFunction f) { 124 | click_cb = f; 125 | } 126 | 127 | ///////////////////////////////////////////////////////////////// 128 | 129 | void Button2::setTapHandler(CallbackFunction f) { 130 | tap_cb = f; 131 | } 132 | 133 | ///////////////////////////////////////////////////////////////// 134 | 135 | void Button2::setLongClickHandler(CallbackFunction f) { 136 | long_cb = f; 137 | } 138 | 139 | ///////////////////////////////////////////////////////////////// 140 | 141 | void Button2::setLongClickDetectedRetriggerable(bool retriggerable) { 142 | longclick_retriggerable = retriggerable; 143 | } 144 | 145 | ///////////////////////////////////////////////////////////////// 146 | 147 | void Button2::setLongClickDetectedHandler(CallbackFunction f) { 148 | longclick_detected_cb = f; 149 | } 150 | 151 | ///////////////////////////////////////////////////////////////// 152 | 153 | void Button2::setDoubleClickHandler(CallbackFunction f) { 154 | double_cb = f; 155 | } 156 | 157 | ///////////////////////////////////////////////////////////////// 158 | 159 | void Button2::setTripleClickHandler(CallbackFunction f) { 160 | triple_cb = f; 161 | } 162 | 163 | ///////////////////////////////////////////////////////////////// 164 | 165 | unsigned int Button2::wasPressedFor() const { 166 | return down_time_ms; 167 | } 168 | 169 | ///////////////////////////////////////////////////////////////// 170 | 171 | bool Button2::isPressed() const { 172 | return (state == _pressedState); 173 | } 174 | 175 | ///////////////////////////////////////////////////////////////// 176 | 177 | bool Button2::isPressedRaw() const { 178 | return (_getState() == _pressedState); 179 | } 180 | 181 | ///////////////////////////////////////////////////////////////// 182 | 183 | uint8_t Button2::getNumberOfClicks() const { 184 | return last_click_count; 185 | } 186 | 187 | ///////////////////////////////////////////////////////////////// 188 | 189 | clickType Button2::getType() const { 190 | return last_click_type; 191 | } 192 | 193 | ///////////////////////////////////////////////////////////////// 194 | 195 | int Button2::getID() const { 196 | return id; 197 | } 198 | 199 | ///////////////////////////////////////////////////////////////// 200 | 201 | void Button2::setID(int newID) { 202 | id = newID; 203 | } 204 | 205 | ///////////////////////////////////////////////////////////////// 206 | 207 | String Button2::clickToString(clickType type) const { 208 | if (type == single_click) return "single click"; 209 | if (type == double_click) return "double click"; 210 | if (type == triple_click) return "triple click"; 211 | if (type == long_click) return "long click"; 212 | return "none"; 213 | } 214 | 215 | ///////////////////////////////////////////////////////////////// 216 | 217 | bool Button2::wasPressed() const { 218 | return was_pressed; 219 | } 220 | 221 | 222 | ///////////////////////////////////////////////////////////////// 223 | 224 | void Button2::resetPressedState() { 225 | was_pressed = false; 226 | last_click_type = empty; 227 | click_count = 0; 228 | down_time_ms = 0; 229 | pressed_triggered = false; 230 | longclick_detected = false; 231 | longclick_reported = false; 232 | longclick_counter = 0; 233 | } 234 | 235 | 236 | ///////////////////////////////////////////////////////////////// 237 | 238 | uint8_t Button2::resetClickCount() { 239 | uint8_t tmp = last_click_count; 240 | last_click_count = 0; 241 | return tmp; 242 | } 243 | 244 | ///////////////////////////////////////////////////////////////// 245 | 246 | clickType Button2::read(bool keepState /* = false */) { 247 | if (keepState) return last_click_type; 248 | 249 | clickType res = last_click_type; 250 | resetPressedState(); 251 | return res; 252 | } 253 | 254 | ///////////////////////////////////////////////////////////////// 255 | 256 | clickType Button2::wait(bool keepState /* = false */) { 257 | while (!wasPressed()) { 258 | loop(); 259 | } 260 | return read(keepState); 261 | } 262 | 263 | ///////////////////////////////////////////////////////////////// 264 | 265 | void Button2::waitForClick(bool keepState /* = false */) { 266 | do { 267 | while (!wasPressed()) { 268 | loop(); 269 | } 270 | } while (read() != single_click); 271 | } 272 | 273 | ///////////////////////////////////////////////////////////////// 274 | 275 | void Button2::waitForDouble(bool keepState /* = false */) { 276 | do { 277 | while (!wasPressed()) { 278 | loop(); 279 | } 280 | } while (read() != double_click); 281 | } 282 | 283 | ///////////////////////////////////////////////////////////////// 284 | 285 | void Button2::waitForTriple(bool keepState /* = false */) { 286 | do { 287 | while (!wasPressed()) { 288 | loop(); 289 | } 290 | } while (read() != triple_click); 291 | } 292 | 293 | ///////////////////////////////////////////////////////////////// 294 | 295 | void Button2::waitForLong(bool keepState /* = false */) { 296 | do { 297 | while (!wasPressed()) { 298 | loop(); 299 | } 300 | } while (read() != long_click); 301 | } 302 | 303 | ///////////////////////////////////////////////////////////////// 304 | 305 | void Button2::reset() { 306 | pin = BTN_UNDEFINED_PIN; 307 | 308 | resetPressedState(); 309 | 310 | pressed_cb = NULL; 311 | released_cb = NULL; 312 | change_cb = NULL; 313 | tap_cb = NULL; 314 | click_cb = NULL; 315 | long_cb = NULL; 316 | longclick_detected_cb = NULL; 317 | double_cb = NULL; 318 | triple_cb = NULL; 319 | } 320 | 321 | ///////////////////////////////////////////////////////////////// 322 | 323 | void Button2::loop() { 324 | if (pin == BTN_UNDEFINED_PIN) return; 325 | 326 | prev_state = state; 327 | state = _getState(); 328 | 329 | if (state == _pressedState) { 330 | _handlePress(millis()); 331 | } else { 332 | _handleRelease(millis()); 333 | } 334 | } 335 | 336 | ///////////////////////////////////////////////////////////////// 337 | 338 | void Button2::_handlePress(long now) { 339 | // is it pressed now? 340 | if (prev_state != _pressedState) { 341 | _pressedNow(now); 342 | return; 343 | } 344 | // is it pressed for a while? 345 | if (!pressed_triggered) { 346 | if (now - down_ms >= debounce_time_ms) { 347 | pressed_triggered = true; 348 | _validKeypress(); 349 | } 350 | } 351 | // only check for long press on the first click 352 | if (click_count == 1) { 353 | _checkForLongClick(now); 354 | } 355 | } 356 | 357 | ///////////////////////////////////////////////////////////////// 358 | 359 | void Button2::_setID() { 360 | id = _nextID; 361 | _nextID++; 362 | } 363 | 364 | ///////////////////////////////////////////////////////////////// 365 | 366 | void Button2::_handleRelease(long now) { 367 | // is it released right now? 368 | if (prev_state == _pressedState) { 369 | _releasedNow(now); 370 | return; 371 | } 372 | // report click after double click time has passed 373 | if (now - click_ms > doubleclick_time_ms) { 374 | _reportClicks(); 375 | } 376 | } 377 | 378 | ///////////////////////////////////////////////////////////////// 379 | 380 | void Button2::_pressedNow(long now) { 381 | down_ms = now; 382 | pressed_triggered = false; 383 | click_ms = down_ms; 384 | } 385 | 386 | ///////////////////////////////////////////////////////////////// 387 | 388 | void Button2::_validKeypress() { 389 | click_count++; 390 | if (change_cb != NULL) change_cb(*this); 391 | if (pressed_cb != NULL) pressed_cb(*this); 392 | } 393 | 394 | ///////////////////////////////////////////////////////////////// 395 | 396 | void Button2::_checkForLongClick(long now) { 397 | if (longclick_detected_cb == NULL) return; 398 | if (longclick_reported) return; 399 | 400 | // has the longclick_ms period has been exceeded? 401 | if (now - down_ms < (longclick_time_ms * (longclick_counter + 1))) return; 402 | // report multiple? 403 | 404 | if (!longclick_retriggerable) { 405 | longclick_reported = true; 406 | } 407 | last_click_count = 1; 408 | last_click_type = long_click; 409 | longclick_counter++; 410 | longclick_detected_cb(*this); 411 | longclick_detected = true; 412 | } 413 | 414 | ///////////////////////////////////////////////////////////////// 415 | 416 | uint8_t Button2::getLongClickCount() const { 417 | return longclick_counter; 418 | } 419 | 420 | ///////////////////////////////////////////////////////////////// 421 | 422 | void Button2::_reportClicks() { 423 | // no click 424 | if (click_count == 0) return; 425 | 426 | last_click_count = click_count; 427 | 428 | // single or long press 429 | if (click_count == 1) { 430 | // long press 431 | if (longclick_detected) { 432 | last_click_type = long_click; 433 | if (long_cb != NULL) long_cb(*this); 434 | longclick_counter = 0; 435 | // single click 436 | } else { 437 | last_click_type = single_click; 438 | if (click_cb != NULL) click_cb (*this); 439 | } 440 | 441 | // double click 442 | } else if (click_count == 2) { 443 | last_click_type = double_click; 444 | if (double_cb != NULL) double_cb(*this); 445 | 446 | // triple or x-clicks 447 | } else { 448 | last_click_type = triple_click; 449 | if (triple_cb != NULL) triple_cb(*this); 450 | } 451 | 452 | was_pressed = true; 453 | click_count = 0; 454 | click_ms = 0; 455 | longclick_detected = false; 456 | longclick_reported = false; 457 | } 458 | 459 | ///////////////////////////////////////////////////////////////// 460 | 461 | void Button2::_releasedNow(long now) { 462 | down_time_ms = now - down_ms; 463 | // is it beyond debounce time? 464 | if (down_time_ms < debounce_time_ms) return; 465 | // trigger release 466 | if (change_cb != NULL) change_cb(*this); 467 | if (released_cb != NULL) released_cb(*this); 468 | // trigger tap 469 | if (tap_cb != NULL) tap_cb(*this); 470 | // was it a longclick? (precedes single / double / triple clicks) 471 | if (down_time_ms >= longclick_time_ms) { 472 | longclick_detected = true; 473 | } 474 | } 475 | 476 | ///////////////////////////////////////////////////////////////// 477 | 478 | uint8_t Button2::_getState() const { 479 | if (get_state_cb != NULL) { 480 | return get_state_cb(); 481 | } else { 482 | return hw->digitalRead(pin); 483 | } 484 | } 485 | 486 | ///////////////////////////////////////////////////////////////// -------------------------------------------------------------------------------- /src/Button2.h: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////// 2 | /* 3 | Button2.h - Arduino Library to simplify working with buttons. 4 | Copyright (C) 2017-2025 Lennart Hennigs. 5 | Released under the MIT license. 6 | 7 | */ 8 | ///////////////////////////////////////////////////////////////// 9 | 10 | #pragma once 11 | 12 | #ifndef Button2_h 13 | #define Button2_h 14 | 15 | ///////////////////////////////////////////////////////////////// 16 | 17 | #if defined(ARDUINO_ARCH_ESP32) || defined(ESP8266) 18 | #include 19 | #endif 20 | #include 21 | 22 | #include "Hardware.h" 23 | 24 | ///////////////////////////////////////////////////////////////// 25 | 26 | const unsigned int BTN_DEBOUNCE_MS = 50; 27 | const unsigned int BTN_LONGCLICK_MS = 200; 28 | const unsigned int BTN_DOUBLECLICK_MS = 300; 29 | 30 | const unsigned int BTN_UNDEFINED_PIN = 255; 31 | const unsigned int BTN_VIRTUAL_PIN = 254; 32 | 33 | ///////////////////////////////////////////////////////////////// 34 | 35 | enum clickType { 36 | single_click, 37 | double_click, 38 | triple_click, 39 | long_click, 40 | empty 41 | }; 42 | 43 | class Button2 { 44 | protected: 45 | int id; 46 | uint8_t pin; 47 | uint8_t state; 48 | uint8_t prev_state; 49 | uint8_t click_count = 0; 50 | uint8_t last_click_count = 0; 51 | clickType last_click_type = empty; 52 | bool was_pressed = false; 53 | unsigned long click_ms; 54 | unsigned long down_ms; 55 | 56 | bool longclick_retriggerable; 57 | uint16_t longclick_counter = 0; 58 | bool longclick_detected = false; 59 | bool longclick_reported = false; 60 | 61 | unsigned int debounce_time_ms = BTN_DEBOUNCE_MS; 62 | unsigned int longclick_time_ms = BTN_LONGCLICK_MS; 63 | unsigned int doubleclick_time_ms = BTN_DOUBLECLICK_MS; 64 | 65 | unsigned int down_time_ms = 0; 66 | bool pressed_triggered = false; 67 | 68 | #if defined(ARDUINO_ARCH_ESP32) || defined(ESP8266) 69 | typedef std::function CallbackFunction; 70 | typedef std::function StateCallbackFunction; 71 | #else 72 | typedef void (*CallbackFunction)(Button2 &); 73 | typedef uint8_t (*StateCallbackFunction)(); 74 | #endif 75 | 76 | StateCallbackFunction get_state_cb = NULL; 77 | 78 | CallbackFunction pressed_cb = NULL; 79 | CallbackFunction released_cb = NULL; 80 | CallbackFunction change_cb = NULL; 81 | CallbackFunction tap_cb = NULL; 82 | CallbackFunction click_cb = NULL; 83 | CallbackFunction long_cb = NULL; 84 | CallbackFunction longclick_detected_cb = NULL; 85 | CallbackFunction double_cb = NULL; 86 | CallbackFunction triple_cb = NULL; 87 | 88 | void _handlePress(long now); 89 | void _handleRelease(long now); 90 | void _releasedNow(long now); 91 | void _pressedNow(long now); 92 | void _validKeypress(); 93 | void _checkForLongClick(long now); 94 | void _reportClicks(); 95 | void _setID(); 96 | 97 | public: 98 | Button2(); 99 | Button2(uint8_t attachTo, uint8_t buttonMode = INPUT_PULLUP, bool activeLow = true, Hardware* hardware = new ArduinoHardware()); 100 | 101 | void begin(uint8_t attachTo, uint8_t buttonMode = INPUT_PULLUP, bool activeLow = true, Hardware* hardware = new ArduinoHardware()); 102 | 103 | void setDebounceTime(unsigned int ms); 104 | void setLongClickTime(unsigned int ms); 105 | void setDoubleClickTime(unsigned int ms); 106 | 107 | unsigned int getDebounceTime() const; 108 | unsigned int getLongClickTime() const; 109 | unsigned int getDoubleClickTime() const; 110 | uint8_t getPin() const; 111 | 112 | void reset(); 113 | 114 | void setButtonStateFunction(StateCallbackFunction f); 115 | 116 | void setChangedHandler(CallbackFunction f); 117 | void setPressedHandler(CallbackFunction f); 118 | void setReleasedHandler(CallbackFunction f); 119 | 120 | void setTapHandler(CallbackFunction f); 121 | void setClickHandler(CallbackFunction f); 122 | void setDoubleClickHandler(CallbackFunction f); 123 | void setTripleClickHandler(CallbackFunction f); 124 | 125 | void setLongClickHandler(CallbackFunction f); 126 | void setLongClickDetectedHandler(CallbackFunction f); 127 | 128 | void setLongClickDetectedRetriggerable(bool retriggerable); 129 | 130 | unsigned int wasPressedFor() const; 131 | bool isPressed() const; 132 | bool isPressedRaw() const; 133 | void resetPressedState(); 134 | uint8_t resetClickCount(); 135 | 136 | bool wasPressed() const; 137 | clickType read(bool keepState = false); 138 | clickType wait(bool keepState = false); 139 | void waitForClick(bool keepState = false); 140 | void waitForDouble(bool keepState = false); 141 | void waitForTriple(bool keepState = false); 142 | void waitForLong(bool keepState = false); 143 | 144 | uint8_t getNumberOfClicks() const; 145 | uint8_t getLongClickCount() const; 146 | 147 | clickType getType() const; 148 | String clickToString(clickType type) const; 149 | 150 | int getID() const; 151 | void setID(int newID); 152 | 153 | bool operator==(Button2 &rhs); 154 | 155 | void loop(); 156 | 157 | private: 158 | static int _nextID; 159 | uint8_t _pressedState; 160 | uint8_t _getState() const; 161 | Hardware* hw; 162 | 163 | }; 164 | ///////////////////////////////////////////////////////////////// 165 | #endif 166 | ///////////////////////////////////////////////////////////////// 167 | -------------------------------------------------------------------------------- /src/Hardware.h: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////// 2 | /* 3 | Hardware.h - Implements Arduino Hardware Abstraction Layer. 4 | Part or Button2 library for Arduino. 5 | Copyright (C) 2017-2025 Lennart Hennigs. 6 | Released under the MIT license. 7 | */ 8 | ///////////////////////////////////////////////////////////////// 9 | #include 10 | ///////////////////////////////////////////////////////////////// 11 | 12 | #pragma once 13 | 14 | #ifndef AbstractHardware_h 15 | #define AbstractHardware_h 16 | 17 | ///////////////////////////////////////////////////////////////// 18 | // disable warnings for unused parameters 19 | 20 | #pragma GCC diagnostic push 21 | #pragma GCC diagnostic ignored "-Wunused-parameter" 22 | 23 | ///////////////////////////////////////////////////////////////// 24 | // virtual class for hardware abstraction 25 | 26 | class Hardware { 27 | public: 28 | virtual int digitalRead(int pin) = 0; 29 | virtual void pinMode(int pin, int mode) = 0; 30 | virtual void digitalWrite(int pin, int value) = 0; 31 | }; 32 | 33 | ///////////////////////////////////////////////////////////////// 34 | // implementation for Arduino 35 | 36 | // for Arduino API version 2.0 – used by UNO R4, RP2040, ... 37 | #if defined(ARDUINO_API_VERSION) 38 | class ArduinoHardware : public Hardware { 39 | public: 40 | int digitalRead(int pin) { 41 | return ::digitalRead(pin); 42 | } 43 | 44 | void pinMode(int pin, int mode) { 45 | ::pinMode(pin, static_cast(mode)); 46 | } 47 | 48 | void digitalWrite(int pin, int value) { 49 | ::digitalWrite(pin, static_cast(value)); 50 | } 51 | }; 52 | #else 53 | class ArduinoHardware : public Hardware { 54 | public: 55 | int digitalRead(int pin) { 56 | 57 | return ::digitalRead(pin); 58 | } 59 | void pinMode(int pin, int mode) { 60 | ::pinMode(pin, mode); 61 | } 62 | void digitalWrite(int pin, int value) { 63 | ::digitalWrite(pin, value); 64 | } 65 | }; 66 | #endif 67 | 68 | ///////////////////////////////////////////////////////////////// 69 | // implementation for testing 70 | 71 | class MockHardware : public Hardware { 72 | private: 73 | int pin_state[255]; 74 | int pin_mode[255]; 75 | 76 | public: 77 | MockHardware() { 78 | for(int i = 0; i < 255; i++) { 79 | pin_state[i] = HIGH; 80 | pin_mode[i] = OUTPUT; 81 | } 82 | } 83 | 84 | int digitalRead(int pin) override { 85 | return pin_state[pin]; 86 | } 87 | void pinMode(int pin, int mode) override { 88 | pin_mode[pin] = mode; 89 | } 90 | void digitalWrite(int pin, int value) override { 91 | pin_state[pin] = value; 92 | } 93 | int getPinMode(int pin) { 94 | return pin_mode[pin]; 95 | } 96 | }; 97 | 98 | ///////////////////////////////////////////////////////////////// 99 | #pragma GCC diagnostic pop 100 | #endif 101 | ///////////////////////////////////////////////////////////////// 102 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////// 2 | // this is needed for PlatformIO to compile the library 3 | // it serves no other purpose 4 | 5 | #if defined(PLATFORMIO) 6 | ///////////////////////////////////////////////////////////////// 7 | 8 | #include "../examples/SingleButtonSimple/SingleButtonSimple.ino" 9 | #pragma message "compiling SingleButtonSimple.ino" 10 | 11 | // #include "../examples/SingleButton/SingleButton.ino" 12 | // #pragma message "compiling SingleButton.ino" 13 | 14 | // #include "../examples/ButtonLoop/ButtonLoop.ino" 15 | // #pragma message "compiling ButtonLoop.ino" 16 | 17 | // #include "../examples/MultipleButtons/MultipleButtons.ino" 18 | // #pragma message "compiling MultipleButtons.ino" 19 | 20 | // #include "../examples/MultiHandler/MultiHandler.ino" 21 | // #pragma message "compiling MultiHandler.ino" 22 | 23 | // #include "../examples/MultiHandlerTwoButtons/MultiHandlerTwoButtons.ino" 24 | // #pragma message "compiling MultiHandlerTwoButtons.ino" 25 | 26 | // #include "../examples/LongpressHandler/LongpressHandler.ino" 27 | // #pragma message "compiling LongpressHandler.ino" 28 | 29 | // #include "../examples/TrackDualButtonClick/TrackDualButtonClick.ino" 30 | // #pragma message "compiling TrackDualButtonClick.ino" 31 | 32 | // #include "../examples/CustomButtonStateHandler/CustomButtonStateHandler.ino" 33 | // #pragma message "compiling CustomButtonStateHandler.ino" 34 | 35 | // #include "../examples/ESP32CapacitiveTouchx/ESP32CapacitiveTouch.ino" 36 | // #pragma message "compiling ESP32CapacitiveTouch.ino" 37 | 38 | // #include "../examples/M5StackCore2CustomHandler/M5StackCore2CustomHandler.ino" 39 | // #pragma message "compiling M5StackCore2CustomHandler.ino" 40 | 41 | // #include "../examples/ESP32TimerInterrupt/ESP32TimerInterrupt.ino" 42 | // #pragma message "compiling ESP32TimerInterrupt.ino" 43 | 44 | // #include "../examples/ESP32S2S3CapacitiveTouch/ESP32S2S3CapacitiveTouch.ino" 45 | // #pragma message "compiling ESP32S2S3CapacitiveTouch.ino" 46 | 47 | // #include "../examples/ESP32ClassicCapacitiveTouch/ESP32ClassicCapacitiveTouch.ino" 48 | // #pragma message "compiling ESP32ClassicCapacitiveTouch.ino" 49 | ///////////////////////////////////////////////////////////////// 50 | #endif 51 | ///////////////////////////////////////////////////////////////// 52 | -------------------------------------------------------------------------------- /test/Button2Test/Button2Test.ino: -------------------------------------------------------------------------------- 1 | #line 2 "Button2Test.ino" 2 | ///////////////////////////////////////////////////////////////// 3 | /* 4 | Unit tests for Button2 library. 5 | Arduino Library to simplify working with buttons. 6 | 7 | Created by Lennart Hennigs 8 | Tested on Wemos D1 Mini, M5Stack ESP32, Arduino UNO 9 | */ 10 | ///////////////////////////////////////////////////////////////// 11 | /* 12 | To add: 13 | - check retriggerable long-clicks (setLongClickDetectedRetriggerable getLongClickCount()) 14 | - check loop functions (read(), wait(), waitForClick(), waitForDouble(), waitForTriple()) 15 | - check custom handler 16 | */ 17 | ///////////////////////////////////////////////////////////////// 18 | 19 | #include "AUnitVerbose.h" 20 | // #include "AUnit.h" 21 | #include "Button2.h" 22 | 23 | using namespace aunit; 24 | 25 | ///////////////////////////////////////////////////////////////// 26 | 27 | #define SERIAL_SPEED 115200 28 | 29 | #define VERBOSE_CHANGED false 30 | #define VERBOSE_PRESS_RELEASE false 31 | #define VERBOSE_MAIN_EVENTS false // clicks, long presses 32 | 33 | ///////////////////////////////////////////////////////////////// 34 | 35 | #define BUTTON_PIN 37 36 | #define BUTTON_MODE INPUT_PULLUP 37 | #define BUTTON_ACTIVE LOW 38 | 39 | #define DEBOUNCE_MS BTN_DEBOUNCE_MS + 5 40 | 41 | ///////////////////////////////////////////////////////////////// 42 | 43 | MockHardware hw; 44 | Button2 button; 45 | 46 | bool pressed = false; 47 | bool released = false; 48 | bool tap = false; 49 | bool longclick = false; 50 | bool long_detected = false; 51 | int long_detected_count = 0; 52 | bool changed = false; 53 | 54 | ///////////////////////////////////////////////////////////////// 55 | 56 | void setup_test_runner() { 57 | TestRunner::setVerbosity(Verbosity::kDefault); 58 | // TestRunner::setVerbosity(Verbosity::kAssertionFailed); 59 | // TestRunner::setVerbosityVerbosity::kAll); 60 | 61 | TestRunner::list(); 62 | /* 63 | TestRunner::exclude("*"); 64 | // TestRunner::include("basics_*"); 65 | // TestRunner::include("defaults_*"); 66 | // TestRunner::include("settings_*"); 67 | // TestRunner::include("clicks_*"); 68 | // TestRunner::include("other_*"); 69 | */ 70 | } 71 | 72 | ///////////////////////////////////////////////////////////////// 73 | // helper functions 74 | ///////////////////////////////////////////////////////////////// 75 | 76 | // pressing the button 77 | void press() { 78 | hw.digitalWrite(BUTTON_PIN, BUTTON_ACTIVE); 79 | button.loop(); 80 | } 81 | 82 | // letting it go 83 | void release() { 84 | hw.digitalWrite(BUTTON_PIN, !BUTTON_ACTIVE); 85 | button.loop(); 86 | } 87 | 88 | // emulate a button click 89 | void clickFor(unsigned long duration) { 90 | press(); 91 | delay(duration); 92 | button.loop(); 93 | release(); 94 | } 95 | 96 | // resets all handler vars 97 | void resetHandlerVars() { 98 | pressed = false; 99 | released = false; 100 | tap = false; 101 | longclick = false; 102 | long_detected = false; 103 | changed = false; 104 | long_detected_count = 0; 105 | } 106 | 107 | // executes a single click 108 | int singleClick(bool waitItOut = true) { 109 | int time = DEBOUNCE_MS + 10; 110 | clickFor(time); 111 | // wait out the double click time 112 | if (waitItOut) { 113 | delay(BTN_DOUBLECLICK_MS); 114 | button.loop(); 115 | } 116 | return time; 117 | } 118 | 119 | // executes a long click 120 | int longClick() { 121 | int time = BTN_LONGCLICK_MS + 10; 122 | clickFor(time); 123 | // wait out the double click time 124 | delay(BTN_DOUBLECLICK_MS); 125 | button.loop(); 126 | return time; 127 | } 128 | 129 | 130 | ///////////////////////////////////////////////////////////////// 131 | // BASICS 132 | ///////////////////////////////////////////////////////////////// 133 | 134 | test(basics, loop) { 135 | resetHandlerVars(); 136 | button.resetPressedState(); 137 | // press the button down 138 | hw.digitalWrite(BUTTON_PIN, BUTTON_ACTIVE); 139 | button.loop(); 140 | // wait a while 141 | delay(DEBOUNCE_MS); 142 | // is there an update without the loop? 143 | assertFalse(pressed); 144 | // there should be one now 145 | button.loop(); 146 | assertTrue(pressed); 147 | 148 | // release the button 149 | hw.digitalWrite(BUTTON_PIN, !BUTTON_ACTIVE); 150 | // wait a while 151 | delay(DEBOUNCE_MS); 152 | // is there an update without the loop? 153 | assertFalse(released); 154 | // there should be one now 155 | button.loop(); 156 | assertTrue(released); 157 | } 158 | 159 | ///////////////////////////////////////////////////////////////// 160 | 161 | test(basics, equal_operator) { 162 | Button2 b; 163 | assertTrue(button == button); 164 | assertTrue(b == b); 165 | assertFalse(button == b); 166 | } 167 | 168 | ///////////////////////////////////////////////////////////////// 169 | // CLICKS 170 | ///////////////////////////////////////////////////////////////// 171 | 172 | 173 | test(clicks, changed_handler) { 174 | button.resetPressedState(); 175 | changed = false; 176 | 177 | press(); 178 | // wait a bit 179 | delay(DEBOUNCE_MS); 180 | button.loop(); 181 | // test 182 | assertTrue(changed); 183 | changed = false; 184 | // let go 185 | release(); 186 | // test 187 | assertTrue(changed); 188 | // clean up 189 | changed = false; 190 | } 191 | 192 | ///////////////////////////////////////////////////////////////// 193 | 194 | test(clicks, is_not_pressed) { 195 | release(); 196 | button.resetPressedState(); 197 | // run the tests 198 | assertFalse(button.isPressed()); 199 | } 200 | 201 | ///////////////////////////////////////////////////////////////// 202 | 203 | test(clicks, is_pressed) { 204 | release(); 205 | button.resetPressedState(); 206 | press(); 207 | // wait a bit 208 | delay(DEBOUNCE_MS); 209 | button.loop(); 210 | // test 211 | assertTrue(button.isPressed()); 212 | assertTrue(pressed); 213 | release(); 214 | // clean up 215 | pressed = false; 216 | } 217 | 218 | ///////////////////////////////////////////////////////////////// 219 | 220 | test(clicks, is_pressed_raw) { 221 | button.resetPressedState(); 222 | press(); 223 | // wait a bit 224 | delay(BTN_DEBOUNCE_MS); 225 | button.loop(); 226 | // test 227 | assertTrue(button.isPressedRaw()); 228 | release(); 229 | // clean up 230 | assertFalse(button.isPressedRaw()); 231 | pressed = false; 232 | } 233 | 234 | ///////////////////////////////////////////////////////////////// 235 | 236 | test(clicks, not_a_click) { 237 | resetHandlerVars(); 238 | button.resetPressedState(); 239 | // click, but too short 240 | clickFor(BTN_DEBOUNCE_MS - 10); 241 | delay(BTN_DOUBLECLICK_MS); 242 | button.loop(); 243 | // run the tests 244 | assertFalse(button.wasPressed()); 245 | assertFalse(tap); 246 | } 247 | 248 | ///////////////////////////////////////////////////////////////// 249 | 250 | test(clicks, single_click) { 251 | resetHandlerVars(); 252 | button.resetPressedState(); 253 | 254 | int time = singleClick(); 255 | int pressedFor = button.wasPressedFor(); 256 | 257 | // run the tests 258 | assertTrue(button.wasPressed()); 259 | assertNear(time, pressedFor, 10); 260 | assertEqual(button.getType(), single_click); 261 | assertEqual(button.getNumberOfClicks(), 1); 262 | assertTrue(tap); 263 | } 264 | 265 | ///////////////////////////////////////////////////////////////// 266 | 267 | test(clicks, long_click) { 268 | resetHandlerVars(); 269 | button.resetPressedState(); 270 | 271 | int time = longClick(); 272 | int pressedFor = button.wasPressedFor(); 273 | 274 | // run the tests 275 | assertTrue(button.wasPressed()); 276 | assertNear(time, pressedFor, 10); 277 | assertEqual(button.getType(), long_click); 278 | assertEqual(button.getNumberOfClicks(), 1); 279 | assertTrue(long_detected); 280 | assertTrue(longclick); 281 | assertTrue(tap); 282 | } 283 | 284 | ///////////////////////////////////////////////////////////////// 285 | 286 | test(clicks, first_single_then_long_click) { 287 | resetHandlerVars(); 288 | button.resetPressedState(); 289 | 290 | singleClick(); 291 | int long_time = longClick(); 292 | int pressedFor = button.wasPressedFor(); 293 | 294 | // run the tests 295 | assertTrue(button.wasPressed()); 296 | assertNear(long_time, pressedFor, 10); 297 | assertEqual(button.getType(), long_click); 298 | assertEqual(button.getNumberOfClicks(), 1); 299 | assertTrue(long_detected); 300 | assertTrue(longclick); 301 | assertTrue(tap); 302 | } 303 | 304 | ///////////////////////////////////////////////////////////////// 305 | 306 | test(clicks, first_double_then_long_click) { 307 | resetHandlerVars(); 308 | button.resetPressedState(); 309 | 310 | // double click 311 | singleClick(false); 312 | singleClick(true); 313 | int long_time = longClick(); 314 | int pressedFor = button.wasPressedFor(); 315 | 316 | // run the tests 317 | assertTrue(button.wasPressed()); 318 | assertNear(long_time, pressedFor, 10); 319 | assertEqual(button.getType(), long_click); 320 | assertEqual(button.getNumberOfClicks(), 1); 321 | assertTrue(long_detected); 322 | assertTrue(longclick); 323 | assertTrue(tap); 324 | } 325 | 326 | ///////////////////////////////////////////////////////////////// 327 | 328 | test(clicks, double_click) { 329 | button.resetPressedState(); 330 | // 2x click 331 | singleClick(false); 332 | singleClick(true); 333 | // run the tests 334 | assertTrue(button.wasPressed()); 335 | assertEqual(button.getType(), double_click); 336 | assertEqual(button.getNumberOfClicks(), 2); 337 | } 338 | 339 | ///////////////////////////////////////////////////////////////// 340 | 341 | test(clicks, triple_click) { 342 | button.resetPressedState(); 343 | // 3x click 344 | singleClick(false); 345 | singleClick(false); 346 | singleClick(true); 347 | // run the tests 348 | assertTrue(button.wasPressed()); 349 | assertEqual(button.getType(), triple_click); 350 | assertEqual(button.getNumberOfClicks(), 3); 351 | } 352 | 353 | ///////////////////////////////////////////////////////////////// 354 | 355 | test(clicks, more_than_3_click) { 356 | button.resetPressedState(); 357 | // 4x click 358 | singleClick(false); 359 | singleClick(false); 360 | singleClick(false); 361 | singleClick(true); 362 | // run the tests 363 | assertTrue(button.wasPressed()); 364 | assertEqual(button.getType(), triple_click); 365 | assertEqual(button.getNumberOfClicks(), 4); 366 | } 367 | 368 | ///////////////////////////////////////////////////////////////// 369 | 370 | test (clicks, reset) { 371 | button.resetPressedState(); 372 | // single click 373 | singleClick(); 374 | // run the tests 375 | assertTrue(button.wasPressed()); 376 | assertEqual(button.getNumberOfClicks(), 1); 377 | // now reset the "click memory" 378 | button.resetPressedState(); 379 | assertFalse(button.wasPressed()); 380 | button.resetClickCount(); 381 | assertEqual(button.getNumberOfClicks(), 0); 382 | } 383 | 384 | ///////////////////////////////////////////////////////////////// 385 | // DEFAULTS 386 | ///////////////////////////////////////////////////////////////// 387 | 388 | test(defaults, doubleclick_time) { 389 | assertEqual(button.getDoubleClickTime(), BTN_DOUBLECLICK_MS); 390 | } 391 | 392 | ///////////////////////////////////////////////////////////////// 393 | 394 | test(defaults, debounce_time) { 395 | assertEqual(button.getDebounceTime(), BTN_DEBOUNCE_MS); 396 | } 397 | 398 | ///////////////////////////////////////////////////////////////// 399 | 400 | test(defaults, longclick_time) { 401 | assertEqual(button.getLongClickTime(), BTN_LONGCLICK_MS); 402 | } 403 | 404 | ///////////////////////////////////////////////////////////////// 405 | 406 | test(defaults, id) { 407 | assertTrue(button.getID() == 0); 408 | } 409 | 410 | ///////////////////////////////////////////////////////////////// 411 | 412 | test(defaults, pin) { 413 | assertTrue(button.getPin() == BUTTON_PIN); 414 | } 415 | 416 | ///////////////////////////////////////////////////////////////// 417 | // SETTING 418 | ///////////////////////////////////////////////////////////////// 419 | 420 | test(settings, id) { 421 | int id = 10; 422 | button.setID(id); 423 | // run the tests 424 | assertTrue(button.getID() == id); 425 | // clean up 426 | button.setID(0); 427 | } 428 | 429 | ///////////////////////////////////////////////////////////////// 430 | 431 | test(settings, additional_ids) { 432 | Button2 b; 433 | assertNotEqual(b.getID(), button.getID()); 434 | } 435 | 436 | ///////////////////////////////////////////////////////////////// 437 | 438 | test(settings, doubleclick_time) { 439 | unsigned int dc_time = BTN_DOUBLECLICK_MS + 1; 440 | button.setDoubleClickTime(dc_time); 441 | // run the tests 442 | assertEqual(button.getDoubleClickTime(), dc_time); 443 | // clean up 444 | button.setDoubleClickTime(BTN_DOUBLECLICK_MS); 445 | } 446 | 447 | ///////////////////////////////////////////////////////////////// 448 | 449 | test(settings, longclick_time) { 450 | unsigned int lc_time = BTN_LONGCLICK_MS + 1; 451 | button.setLongClickTime(lc_time); 452 | // run the tests 453 | assertEqual(button.getLongClickTime(), lc_time); 454 | // clean up 455 | button.setLongClickTime(BTN_LONGCLICK_MS); 456 | } 457 | 458 | ///////////////////////////////////////////////////////////////// 459 | 460 | test(settings, debounce_time) { 461 | unsigned int debounce_time = BTN_DEBOUNCE_MS + 1; 462 | button.setDebounceTime(debounce_time); 463 | // run the tests 464 | assertEqual(button.getDebounceTime(), debounce_time); 465 | // clean up 466 | button.setDebounceTime(BTN_DEBOUNCE_MS); 467 | } 468 | 469 | ///////////////////////////////////////////////////////////////// 470 | // OTHER 471 | ///////////////////////////////////////////////////////////////// 472 | 473 | test(other, to_string) { 474 | assertStringCaseEqual(button.clickToString(clickType::single_click), String("single click")); 475 | assertStringCaseEqual(button.clickToString(clickType::double_click), String("double click")); 476 | assertStringCaseEqual(button.clickToString(clickType::triple_click), String("triple click")); 477 | assertStringCaseEqual(button.clickToString(clickType::long_click), String("long click")); 478 | } 479 | 480 | ///////////////////////////////////////////////////////////////// 481 | ///////////////////////////////////////////////////////////////// 482 | 483 | // ignore unused params in the lamda functions 484 | #pragma GCC diagnostic push 485 | #pragma GCC diagnostic ignored "-Wunused-parameter" 486 | 487 | ///////////////////////////////////////////////////////////////// 488 | 489 | void setup() { 490 | setup_test_runner(); 491 | 492 | // setup serial 493 | delay(1000); 494 | Serial.begin(SERIAL_SPEED); 495 | while(!Serial) {} 496 | Serial.println(F("\n\nButton2 Unit Tests")); 497 | // set up button 498 | button.begin(BUTTON_PIN, BUTTON_MODE, BUTTON_ACTIVE == LOW, &hw); 499 | 500 | button.setPressedHandler([](Button2& b) { 501 | if (VERBOSE_PRESS_RELEASE) Serial.println(F("-- PRESSED")); 502 | released = false; 503 | pressed = true; 504 | }); 505 | button.setReleasedHandler([](Button2& b) { 506 | if (VERBOSE_PRESS_RELEASE) Serial.println(F("-- RELEASED")); 507 | released = true; 508 | pressed = false; 509 | tap = true; 510 | }); 511 | button.setChangedHandler([](Button2& b) { 512 | if (VERBOSE_CHANGED) Serial.println(F("-- CHANGED")); 513 | changed = true; 514 | }); 515 | button.setClickHandler([](Button2& b) { 516 | if (VERBOSE_MAIN_EVENTS) Serial.println(F(" -- CLICK")); 517 | }); 518 | button.setLongClickHandler([](Button2& b) { 519 | if (VERBOSE_MAIN_EVENTS) Serial.println(F(" -- LONG")); 520 | longclick = true; 521 | }); 522 | button.setLongClickDetectedHandler([](Button2& b) { 523 | if (VERBOSE_MAIN_EVENTS) Serial.println(" -- LONG DETECTED"); 524 | long_detected = true; 525 | long_detected_count = long_detected_count + 1; 526 | }); 527 | button.setDoubleClickHandler([](Button2& b) { 528 | if (VERBOSE_MAIN_EVENTS) Serial.println(" -- DOUBLE"); 529 | }); 530 | button.setTripleClickHandler([](Button2& b) { 531 | if (VERBOSE_MAIN_EVENTS) Serial.println(" -- TRIPLE "); 532 | }); 533 | } 534 | #pragma GCC diagnostic push 535 | 536 | ///////////////////////////////////////////////////////////////// 537 | 538 | void loop() { 539 | button.loop(); 540 | aunit::TestRunner::run(); 541 | } 542 | 543 | ///////////////////////////////////////////////////////////////// 544 | --------------------------------------------------------------------------------