AceButton  1.9.1
An adjustable, compact, event-driven button library for Arduino.
AceButton.cpp
1 /*
2 MIT License
3 
4 Copyright (c) 2018 Brian T. Park
5 
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12 
13 The above copyright notice and this permission notice shall be included in all
14 copies or substantial portions of the Software.
15 
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 SOFTWARE.
23 */
24 
25 #include "AceButton.h"
26 
27 namespace ace_button {
28 
29 // Macros to perform compile-time assertions. See
30 // https://www.embedded.com/electronics-blogs/programming-pointers/4025549/Catching-errors-early-with-compile-time-assertions
31 // and https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html
32 #define CONCAT_(x, y) x##y
33 #define CONCAT(x,y) CONCAT_(x,y)
34 #define COMPILE_TIME_ASSERT(cond, msg) \
35  extern char CONCAT(compile_time_assert, __LINE__)[(cond) ? 1 : -1];
36 
37 // Check that the Arduino constants HIGH and LOW are defined to be 1 and 0,
38 // respectively. Otherwise, this library won't work.
39 COMPILE_TIME_ASSERT(HIGH == 1, "HIGH must be 1")
40 COMPILE_TIME_ASSERT(LOW == 0, "LOW must be 0")
41 
42 // On boards using the new PinStatus API, check that kButtonStateUnknown is
43 // different from all other PinStatus enums.
44 #if ARDUINO_API_VERSION >= 10000
45  COMPILE_TIME_ASSERT(\
48  && AceButton::kButtonStateUnknown != CHANGE \
49  && AceButton::kButtonStateUnknown != FALLING \
50  && AceButton::kButtonStateUnknown != RISING, \
51  "kButtonStateUnknown conflicts with PinStatus enum")
52 #endif
53 
54 void AceButton::init(uint8_t pin, uint8_t defaultReleasedState, uint8_t id) {
55  mPin = pin;
56  mId = id;
57  mFlags = 0;
58  mLastButtonState = kButtonStateUnknown;
59  mLastDebounceTime = 0;
60  mLastClickTime = 0;
61  setDefaultReleasedState(defaultReleasedState);
62 }
63 
64 void AceButton::init(ButtonConfig* buttonConfig, uint8_t pin,
65  uint8_t defaultReleasedState, uint8_t id) {
66  mButtonConfig = buttonConfig;
67  init(pin, defaultReleasedState, id);
68 }
69 
70 void AceButton::setDefaultReleasedState(uint8_t state) {
71  if (state == HIGH) {
72  mFlags |= kFlagDefaultReleasedState;
73  } else {
74  mFlags &= ~kFlagDefaultReleasedState;
75  }
76 }
77 
79  return (mFlags & kFlagDefaultReleasedState) ? HIGH : LOW;
80 }
81 
82 // NOTE: It would be interesting to rewrite the check() method using a Finite
83 // State Machine.
85  uint8_t buttonState = mButtonConfig->readButton(mPin);
86  checkState(buttonState);
87 }
88 
89 void AceButton::checkState(uint8_t buttonState) {
90  // Retrieve the current time just once and use that in the various checkXxx()
91  // functions below. This provides some robustness of the various timing
92  // algorithms even if one of the event handlers takes more time than the
93  // threshold time limits such as 'debounceDelay' or longPressDelay'.
94  uint16_t now = mButtonConfig->getClock();
95 
96  // debounce the button
97  if (checkDebounced(now, buttonState)) {
98  // check if the button was initialized (i.e. UNKNOWN state)
99  if (checkInitialized(buttonState)) {
100  checkEvent(now, buttonState);
101  }
102  }
103 }
104 
105 void AceButton::checkEvent(uint16_t now, uint8_t buttonState) {
106  // We need to remove orphaned clicks even if just Click is enabled. It is not
107  // sufficient to do this for just DoubleClick. That's because it's possible
108  // for a Clicked event to be generated, then 65.536 seconds later, the
109  // ButtonConfig could be changed to enable DoubleClick. (Such real-time change
110  // of ButtonConfig is not recommended, but is sometimes convenient.) If the
111  // orphaned click is not cleared, then the next Click would be errorneously
112  // considered to be a DoubleClick. Therefore, we must clear the orphaned click
113  // even if just the Clicked event is enabled.
114  //
115  // We also need to check of any postponed clicks that got generated when
116  // kFeatureSuppressClickBeforeDoubleClick was enabled.
117  if (mButtonConfig->isFeature(ButtonConfig::kFeatureClick) ||
119  checkPostponedClick(now);
120  checkOrphanedClick(now);
121  }
122 
123  if (mButtonConfig->isFeature(ButtonConfig::kFeatureLongPress)) {
124  checkLongPress(now, buttonState);
125  }
126  if (mButtonConfig->isFeature(ButtonConfig::kFeatureRepeatPress)) {
127  checkRepeatPress(now, buttonState);
128  }
129  if (buttonState != getLastButtonState()) {
130  checkChanged(now, buttonState);
131  }
132 }
133 
134 bool AceButton::checkDebounced(uint16_t now, uint8_t buttonState) {
135  if (isDebouncing()) {
136 
137  // NOTE: This is a bit tricky. The elapsedTime will be valid even if the
138  // uint16_t representation of 'now' rolls over so that (now <
139  // mLastDebounceTime). This is true as long as the 'unsigned long'
140  // representation of 'now' is < (65536 + mLastDebounceTime). We need to cast
141  // this expression into an uint16_t before doing the '>=' comparison below
142  // for compatability with processors whose sizeof(int) == 4 instead of 2.
143  // For those processors, the expression (now - mLastDebounceTime >=
144  // getDebounceDelay()) won't work because the terms in the expression get
145  // promoted to an (int).
146  uint16_t elapsedTime = now - mLastDebounceTime;
147 
148  bool isDebouncingTimeOver =
149  (elapsedTime >= mButtonConfig->getDebounceDelay());
150 
151  if (isDebouncingTimeOver) {
152  clearDebouncing();
153  return true;
154  } else {
155  return false;
156  }
157  } else {
158  // Currently not in debouncing phase. Check for a button state change. This
159  // will also detect a transition from kButtonStateUnknown to HIGH or LOW.
160  if (buttonState == getLastButtonState()) {
161  // no change, return immediately
162  return true;
163  }
164 
165  // button has changed so, enter debouncing phase
166  setDebouncing();
167  mLastDebounceTime = now;
168  return false;
169  }
170 }
171 
172 bool AceButton::checkInitialized(uint16_t buttonState) {
173  if (mLastButtonState != kButtonStateUnknown) {
174  return true;
175  }
176 
177  // If transitioning from the initial "unknown" button state, just set the last
178  // valid button state, but don't fire off the event handler. This handles the
179  // case where a momentary switch is pressed down, then the board is rebooted.
180  // When the board comes up, it should not fire off the event handler. This
181  // also handles the case of a 2-position switch set to the "pressed"
182  // position, and the board is rebooted.
183  mLastButtonState = buttonState;
184  return false;
185 }
186 
187 void AceButton::checkLongPress(uint16_t now, uint8_t buttonState) {
188  if (buttonState == getDefaultReleasedState()) {
189  return;
190  }
191 
192  if (isPressed() && !isLongPressed()) {
193  uint16_t elapsedTime = now - mLastPressTime;
194  if (elapsedTime >= mButtonConfig->getLongPressDelay()) {
195  setLongPressed();
196  handleEvent(kEventLongPressed);
197  }
198  }
199 }
200 
201 void AceButton::checkRepeatPress(uint16_t now, uint8_t buttonState) {
202  if (buttonState == getDefaultReleasedState()) {
203  return;
204  }
205 
206  if (isPressed()) {
207  if (isRepeatPressed()) {
208  uint16_t elapsedTime = now - mLastRepeatPressTime;
209  if (elapsedTime >= mButtonConfig->getRepeatPressInterval()) {
210  handleEvent(kEventRepeatPressed);
211  mLastRepeatPressTime = now;
212  }
213  } else {
214  uint16_t elapsedTime = now - mLastPressTime;
215  if (elapsedTime >= mButtonConfig->getRepeatPressDelay()) {
216  setRepeatPressed();
217  // Trigger the RepeatPressed immedidately, instead of waiting until the
218  // first getRepeatPressInterval() has passed.
219  handleEvent(kEventRepeatPressed);
220  mLastRepeatPressTime = now;
221  }
222  }
223  }
224 }
225 
226 void AceButton::checkChanged(uint16_t now, uint8_t buttonState) {
227  mLastButtonState = buttonState;
228  checkPressed(now, buttonState);
229  checkReleased(now, buttonState);
230 }
231 
232 void AceButton::checkPressed(uint16_t now, uint8_t buttonState) {
233  if (buttonState == getDefaultReleasedState()) {
234  return;
235  }
236 
237  // button was pressed
238  mLastPressTime = now;
239  setPressed();
240  handleEvent(kEventPressed);
241 }
242 
243 void AceButton::checkReleased(uint16_t now, uint8_t buttonState) {
244  if (buttonState != getDefaultReleasedState()) {
245  return;
246  }
247 
248  // Check for click (before sending off the Released event).
249  // Make sure that we don't clearPressed() before calling this.
250  if (mButtonConfig->isFeature(ButtonConfig::kFeatureClick)
251  || mButtonConfig->isFeature(ButtonConfig::kFeatureDoubleClick)) {
252  checkClicked(now);
253  }
254 
255  // Save whether this was generated from a long press.
256  bool wasLongPressed = isLongPressed();
257 
258  // Check if Released events are suppressed.
259  bool suppress =
260  ((isLongPressed() &&
261  mButtonConfig->
263  (isRepeatPressed() &&
264  mButtonConfig->
266  (isClicked() &&
268  (isDoubleClicked() &&
269  mButtonConfig->
271 
272  // Button was released, so clear current flags. Note that the compiler will
273  // optimize the following 4 statements to be equivalent to this single one:
274  // mFlags &= ~kFlagPressed & ~kFlagDoubleClicked & ~kFlagLongPressed
275  // & ~kFlagRepeatPressed;
276  clearPressed();
277  clearDoubleClicked();
278  clearLongPressed();
279  clearRepeatPressed();
280 
281  // Fire off a Released event, unless suppressed. Replace Released with
282  // LongReleased if this was a LongPressed.
283  if (suppress) {
284  if (wasLongPressed) {
285  handleEvent(kEventLongReleased);
286  }
287  } else {
288  handleEvent(kEventReleased);
289  }
290 }
291 
292 void AceButton::checkClicked(uint16_t now) {
293  if (!isPressed()) {
294  // Not a Click unless the previous state was a Pressed state.
295  // This can happen if the chip was rebooted with the button Pressed. Upon
296  // Release, it shouldn't generated a click, even accidentally due to a
297  // spurious value in mLastPressTime.
298  clearClicked();
299  return;
300  }
301  uint16_t elapsedTime = now - mLastPressTime;
302  if (elapsedTime >= mButtonConfig->getClickDelay()) {
303  clearClicked();
304  return;
305  }
306 
307  // check for double click
308  if (mButtonConfig->isFeature(ButtonConfig::kFeatureDoubleClick)) {
309  checkDoubleClicked(now);
310  }
311 
312  // Suppress a second click (both buttonState change and event message) if
313  // double-click detected, which has the side-effect of preventing 3 clicks
314  // from generating another double-click at the third click.
315  if (isDoubleClicked()) {
316  clearClicked();
317  return;
318  }
319 
320  // we got a single click
321  mLastClickTime = now;
322  setClicked();
323  if (mButtonConfig->isFeature(
325  setClickPostponed();
326  } else {
327  handleEvent(kEventClicked);
328  }
329 }
330 
331 void AceButton::checkDoubleClicked(uint16_t now) {
332  if (!isClicked()) {
333  clearDoubleClicked();
334  return;
335  }
336 
337  uint16_t elapsedTime = now - mLastClickTime;
338  if (elapsedTime >= mButtonConfig->getDoubleClickDelay()) {
339  clearDoubleClicked();
340  // There should be no postponed Click at this point because
341  // checkPostponedClick() should have taken care of it.
342  return;
343  }
344 
345  // If there was a postponed click, suppress it because it could only have been
346  // postponed if kFeatureSuppressClickBeforeDoubleClick was enabled. If we got
347  // to this point, there was a DoubleClick, so we must suppress the first
348  // Click as requested.
349  if (isClickPostponed()) {
350  clearClickPostponed();
351  }
352  setDoubleClicked();
353  handleEvent(kEventDoubleClicked);
354 }
355 
356 void AceButton::checkOrphanedClick(uint16_t now) {
357  // The amount of time which must pass before a click is determined to be
358  // orphaned and reclaimed. If only DoubleClicked is supported, then I think
359  // just getDoubleClickDelay() is correct. No other higher level event uses the
360  // first Clicked event. If TripleClicked becomes supported, I think
361  // orphanedClickDelay will be either (2 * getDoubleClickDelay()) or
362  // (getDoubleClickDelay() + getTripleClickDelay()), depending on whether the
363  // TripleClick has an independent delay time, or reuses the DoubleClick delay
364  // time. But I'm not sure that I've thought through all the details.
365  uint16_t orphanedClickDelay = mButtonConfig->getDoubleClickDelay();
366 
367  uint16_t elapsedTime = now - mLastClickTime;
368  if (isClicked() && (elapsedTime >= orphanedClickDelay)) {
369  clearClicked();
370  }
371 }
372 
373 void AceButton::checkPostponedClick(uint16_t now) {
374  uint16_t postponedClickDelay = mButtonConfig->getDoubleClickDelay();
375  uint16_t elapsedTime = now - mLastClickTime;
376  if (isClickPostponed() && elapsedTime >= postponedClickDelay) {
377  handleEvent(kEventClicked);
378  clearClickPostponed();
379  }
380 }
381 
382 void AceButton::handleEvent(uint8_t eventType) {
383  mButtonConfig->dispatchEvent(this, eventType, getLastButtonState());
384 }
385 
386 }
ace_button::ButtonConfig::getRepeatPressDelay
uint16_t getRepeatPressDelay() const
Milliseconds that a button needs to be Pressed down before the start of the sequence of RepeatPressed...
Definition: ButtonConfig.h:240
ace_button::ButtonConfig::getLongPressDelay
uint16_t getLongPressDelay() const
Milliseconds for a long press event.
Definition: ButtonConfig.h:230
ace_button::ButtonConfig::kFeatureSuppressClickBeforeDoubleClick
static const FeatureFlagType kFeatureSuppressClickBeforeDoubleClick
Flag to suppress kEventClicked before a kEventDoubleClicked.
Definition: ButtonConfig.h:147
ace_button::AceButton::check
void check()
Check state of button and trigger event processing.
Definition: AceButton.cpp:84
ace_button::ButtonConfig::kFeatureSuppressAfterClick
static const FeatureFlagType kFeatureSuppressAfterClick
Flag to suppress kEventReleased after a kEventClicked.
Definition: ButtonConfig.h:126
ace_button::AceButton::kEventRepeatPressed
static const uint8_t kEventRepeatPressed
Button was held down and auto generated multiple presses.
Definition: AceButton.h:85
ace_button::ButtonConfig::kFeatureClick
static const FeatureFlagType kFeatureClick
Flag to activate the AceButton::kEventClicked event.
Definition: ButtonConfig.h:110
ace_button::AceButton::kEventLongReleased
static const uint8_t kEventLongReleased
Button was released after a long press.
Definition: AceButton.h:96
ace_button::ButtonConfig::getClock
virtual unsigned long getClock()
Return the milliseconds of the internal clock.
Definition: ButtonConfig.h:293
ace_button::ButtonConfig::kFeatureRepeatPress
static const FeatureFlagType kFeatureRepeatPress
Flag to activate the AceButton::kEventRepeatPressed event.
Definition: ButtonConfig.h:123
ace_button::AceButton::kEventReleased
static const uint8_t kEventReleased
Button was released.
Definition: AceButton.h:59
ace_button::AceButton::getDefaultReleasedState
uint8_t getDefaultReleasedState() const
Get the initial released state of the button, HIGH or LOW.
Definition: AceButton.cpp:78
ace_button::ButtonConfig::dispatchEvent
void dispatchEvent(AceButton *button, uint8_t eventType, uint8_t buttonState) const
Dispatch the event to the handler.
Definition: ButtonConfig.h:356
ace_button::ButtonConfig::getDebounceDelay
uint16_t getDebounceDelay() const
Milliseconds to wait for debouncing.
Definition: ButtonConfig.h:216
ace_button::ButtonConfig::kFeatureSuppressAfterLongPress
static const FeatureFlagType kFeatureSuppressAfterLongPress
Flag to suppress kEventReleased after a kEventLongPressed.
Definition: ButtonConfig.h:136
ace_button::ButtonConfig::isFeature
bool isFeature(FeatureFlagType features) const
Check if the given features are enabled.
Definition: ButtonConfig.h:311
ace_button::ButtonConfig::getDoubleClickDelay
uint16_t getDoubleClickDelay() const
Milliseconds between the first and second click to register as a double-click.
Definition: ButtonConfig.h:225
ace_button::ButtonConfig::getClickDelay
uint16_t getClickDelay() const
Milliseconds to wait for a possible click.
Definition: ButtonConfig.h:219
ace_button::ButtonConfig::kFeatureSuppressAfterDoubleClick
static const FeatureFlagType kFeatureSuppressAfterDoubleClick
Flag to suppress kEventReleased after a kEventDoubleClicked.
Definition: ButtonConfig.h:133
ace_button::ButtonConfig
Class that defines the timing parameters and event handler of an AceButton or a group of AceButton in...
Definition: ButtonConfig.h:66
ace_button::AceButton::kEventLongPressed
static const uint8_t kEventLongPressed
Button was held down for longer than ButtonConfig::getLongPressDelay()).
Definition: AceButton.h:77
ace_button::ButtonConfig::getRepeatPressInterval
uint16_t getRepeatPressInterval() const
Milliseconds between two successive RepeatPressed events.
Definition: ButtonConfig.h:247
ace_button::AceButton::kEventClicked
static const uint8_t kEventClicked
Button was clicked (Pressed and Released within ButtonConfig::getClickDelay()).
Definition: AceButton.h:65
ace_button::AceButton::kEventPressed
static const uint8_t kEventPressed
Button was pressed.
Definition: AceButton.h:56
ace_button::AceButton::kButtonStateUnknown
static const uint8_t kButtonStateUnknown
Button state is unknown.
Definition: AceButton.h:104
ace_button::AceButton::getLastButtonState
uint8_t getLastButtonState() const
Return the button state that was last valid.
Definition: AceButton.h:270
ace_button::AceButton::checkState
void checkState(uint8_t buttonState)
Version of check() used by EncodedButtonConfig.
Definition: AceButton.cpp:89
ace_button::ButtonConfig::kFeatureDoubleClick
static const FeatureFlagType kFeatureDoubleClick
Flag to activate the AceButton::kEventDoubleClicked event.
Definition: ButtonConfig.h:117
ace_button::AceButton::init
void init(uint8_t pin=0, uint8_t defaultReleasedState=HIGH, uint8_t id=0)
Reset the button to the initial constructed state.
Definition: AceButton.cpp:54
ace_button::AceButton::kEventDoubleClicked
static const uint8_t kEventDoubleClicked
Button was double-clicked.
Definition: AceButton.h:71
ace_button::ButtonConfig::kFeatureLongPress
static const FeatureFlagType kFeatureLongPress
Flag to activate the AceButton::kEventLongPress event.
Definition: ButtonConfig.h:120
ace_button::ButtonConfig::kFeatureSuppressAfterRepeatPress
static const FeatureFlagType kFeatureSuppressAfterRepeatPress
Flag to suppress kEventReleased after a kEventRepeatPressed.
Definition: ButtonConfig.h:139
ace_button::ButtonConfig::readButton
virtual int readButton(uint8_t pin)
Return the HIGH or LOW state of the button.
Definition: ButtonConfig.h:303