AceButton  1.10.0
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 //-----------------------------------------------------------------------------
30 
31 // Macros to perform compile-time assertions. See
32 // https://www.embedded.com/electronics-blogs/programming-pointers/4025549/Catching-errors-early-with-compile-time-assertions
33 // and https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html
34 #define CONCAT_(x, y) x##y
35 #define CONCAT(x,y) CONCAT_(x,y)
36 #define COMPILE_TIME_ASSERT(cond, msg) \
37  extern char CONCAT(compile_time_assert, __LINE__)[(cond) ? 1 : -1];
38 
39 // Check that the Arduino constants HIGH and LOW are defined to be 1 and 0,
40 // respectively. Otherwise, this library won't work.
41 COMPILE_TIME_ASSERT(HIGH == 1, "HIGH must be 1")
42 COMPILE_TIME_ASSERT(LOW == 0, "LOW must be 0")
43 
44 // On boards using the new PinStatus API, check that kButtonStateUnknown is
45 // different from all other PinStatus enums.
46 #if ARDUINO_API_VERSION >= 10000
47  COMPILE_TIME_ASSERT(\
50  && AceButton::kButtonStateUnknown != CHANGE \
51  && AceButton::kButtonStateUnknown != FALLING \
52  && AceButton::kButtonStateUnknown != RISING, \
53  "kButtonStateUnknown conflicts with PinStatus enum")
54 #endif
55 
56 //-----------------------------------------------------------------------------
57 
58 static const char sEventPressed[] PROGMEM = "Pressed";
59 static const char sEventReleased[] PROGMEM = "Released";
60 static const char sEventClicked[] PROGMEM = "Clicked";
61 static const char sEventDoubleClicked[] PROGMEM = "DoubleClicked";
62 static const char sEventLongPressed[] PROGMEM = "LongPressed";
63 static const char sEventRepeatPressed[] PROGMEM = "RepeatPressed";
64 static const char sEventLongReleased[] PROGMEM = "LongReleased";
65 static const char sEventHeartBeat[] PROGMEM = "HeartBeat";
66 static const char sEventUnknown[] PROGMEM = "(unknown)";
67 
68 static const char* const sEventNames[] PROGMEM = {
69  sEventPressed,
70  sEventReleased,
71  sEventClicked,
72  sEventDoubleClicked,
73  sEventLongPressed,
74  sEventRepeatPressed,
75  sEventLongReleased,
76  sEventHeartBeat,
77 };
78 
79 __FlashStringHelper* AceButton::eventName(uint8_t event) {
80  const char* name = (event >= sizeof(sEventNames) / sizeof(const char*))
81  ? sEventUnknown
82  : (const char*) pgm_read_ptr(sEventNames + event);
83  return (__FlashStringHelper*) name;
84 }
85 
86 //-----------------------------------------------------------------------------
87 
88 void AceButton::init(uint8_t pin, uint8_t defaultReleasedState, uint8_t id) {
89  mPin = pin;
90  mId = id;
91  mFlags = 0;
92  mLastButtonState = kButtonStateUnknown;
93  setDefaultReleasedState(defaultReleasedState);
94 }
95 
96 void AceButton::init(ButtonConfig* buttonConfig, uint8_t pin,
97  uint8_t defaultReleasedState, uint8_t id) {
98  mButtonConfig = buttonConfig;
99  init(pin, defaultReleasedState, id);
100 }
101 
102 void AceButton::setDefaultReleasedState(uint8_t state) {
103  if (state == HIGH) {
104  mFlags |= kFlagDefaultReleasedState;
105  } else {
106  mFlags &= ~kFlagDefaultReleasedState;
107  }
108 }
109 
111  return (mFlags & kFlagDefaultReleasedState) ? HIGH : LOW;
112 }
113 
114 // NOTE: It would be interesting to rewrite the check() method using a Finite
115 // State Machine.
117  uint8_t buttonState = mButtonConfig->readButton(mPin);
118  checkState(buttonState);
119 }
120 
121 void AceButton::checkState(uint8_t buttonState) {
122  // Retrieve the current time just once and use that in the various checkXxx()
123  // functions below. This provides some robustness of the various timing
124  // algorithms even if one of the event handlers takes more time than the
125  // threshold time limits such as 'debounceDelay' or longPressDelay'.
126  uint16_t now = mButtonConfig->getClock();
127 
128  // Send heart beat if enabled and needed. Purposely placed outside of the
129  // checkDebounced() guard so that it can fire regardless of the state of the
130  // debouncing logic.
131  checkHeartBeat(now);
132 
133  // Debounce the button, and send any events detected.
134  if (checkDebounced(now, buttonState)) {
135  // check if the button was initialized (i.e. UNKNOWN state)
136  if (checkInitialized(buttonState)) {
137  checkEvent(now, buttonState);
138  }
139  }
140 }
141 
142 void AceButton::checkEvent(uint16_t now, uint8_t buttonState) {
143  // We need to remove orphaned clicks even if just Click is enabled. It is not
144  // sufficient to do this for just DoubleClick. That's because it's possible
145  // for a Clicked event to be generated, then 65.536 seconds later, the
146  // ButtonConfig could be changed to enable DoubleClick. (Such real-time change
147  // of ButtonConfig is not recommended, but is sometimes convenient.) If the
148  // orphaned click is not cleared, then the next Click would be errorneously
149  // considered to be a DoubleClick. Therefore, we must clear the orphaned click
150  // even if just the Clicked event is enabled.
151  //
152  // We also need to check of any postponed clicks that got generated when
153  // kFeatureSuppressClickBeforeDoubleClick was enabled.
154  if (mButtonConfig->isFeature(ButtonConfig::kFeatureClick) ||
156  checkPostponedClick(now);
157  checkOrphanedClick(now);
158  }
159 
160  if (mButtonConfig->isFeature(ButtonConfig::kFeatureLongPress)) {
161  checkLongPress(now, buttonState);
162  }
163  if (mButtonConfig->isFeature(ButtonConfig::kFeatureRepeatPress)) {
164  checkRepeatPress(now, buttonState);
165  }
166  if (buttonState != getLastButtonState()) {
167  checkChanged(now, buttonState);
168  }
169 }
170 
171 bool AceButton::checkDebounced(uint16_t now, uint8_t buttonState) {
172  if (isFlag(kFlagDebouncing)) {
173 
174  // NOTE: This is a bit tricky. The elapsedTime will be valid even if the
175  // uint16_t representation of 'now' rolls over so that (now <
176  // mLastDebounceTime). This is true as long as the 'unsigned long'
177  // representation of 'now' is < (65536 + mLastDebounceTime). We need to cast
178  // this expression into an uint16_t before doing the '>=' comparison below
179  // for compatability with processors whose sizeof(int) == 4 instead of 2.
180  // For those processors, the expression (now - mLastDebounceTime >=
181  // getDebounceDelay()) won't work because the terms in the expression get
182  // promoted to an (int).
183  uint16_t elapsedTime = now - mLastDebounceTime;
184 
185  bool isDebouncingTimeOver =
186  (elapsedTime >= mButtonConfig->getDebounceDelay());
187 
188  if (isDebouncingTimeOver) {
189  clearFlag(kFlagDebouncing);
190  return true;
191  } else {
192  return false;
193  }
194  } else {
195  // Currently not in debouncing phase. Check for a button state change. This
196  // will also detect a transition from kButtonStateUnknown to HIGH or LOW.
197  if (buttonState == getLastButtonState()) {
198  // no change, return immediately
199  return true;
200  }
201 
202  // button has changed so, enter debouncing phase
203  setFlag(kFlagDebouncing);
204  mLastDebounceTime = now;
205  return false;
206  }
207 }
208 
209 bool AceButton::checkInitialized(uint16_t buttonState) {
210  if (mLastButtonState != kButtonStateUnknown) {
211  return true;
212  }
213 
214  // If transitioning from the initial "unknown" button state, just set the last
215  // valid button state, but don't fire off the event handler. This handles the
216  // case where a momentary switch is pressed down, then the board is rebooted.
217  // When the board comes up, it should not fire off the event handler. This
218  // also handles the case of a 2-position switch set to the "pressed"
219  // position, and the board is rebooted.
220  mLastButtonState = buttonState;
221  return false;
222 }
223 
224 void AceButton::checkLongPress(uint16_t now, uint8_t buttonState) {
225  if (buttonState == getDefaultReleasedState()) {
226  return;
227  }
228 
229  if (isFlag(kFlagPressed) && !isFlag(kFlagLongPressed)) {
230  uint16_t elapsedTime = now - mLastPressTime;
231  if (elapsedTime >= mButtonConfig->getLongPressDelay()) {
232  setFlag(kFlagLongPressed);
233  handleEvent(kEventLongPressed);
234  }
235  }
236 }
237 
238 void AceButton::checkRepeatPress(uint16_t now, uint8_t buttonState) {
239  if (buttonState == getDefaultReleasedState()) {
240  return;
241  }
242 
243  if (isFlag(kFlagPressed)) {
244  if (isFlag(kFlagRepeatPressed)) {
245  uint16_t elapsedTime = now - mLastRepeatPressTime;
246  if (elapsedTime >= mButtonConfig->getRepeatPressInterval()) {
247  handleEvent(kEventRepeatPressed);
248  mLastRepeatPressTime = now;
249  }
250  } else {
251  uint16_t elapsedTime = now - mLastPressTime;
252  if (elapsedTime >= mButtonConfig->getRepeatPressDelay()) {
253  setFlag(kFlagRepeatPressed);
254  // Trigger the RepeatPressed immedidately, instead of waiting until the
255  // first getRepeatPressInterval() has passed.
256  handleEvent(kEventRepeatPressed);
257  mLastRepeatPressTime = now;
258  }
259  }
260  }
261 }
262 
263 void AceButton::checkChanged(uint16_t now, uint8_t buttonState) {
264  mLastButtonState = buttonState;
265  checkPressed(now, buttonState);
266  checkReleased(now, buttonState);
267 }
268 
269 void AceButton::checkPressed(uint16_t now, uint8_t buttonState) {
270  if (buttonState == getDefaultReleasedState()) {
271  return;
272  }
273 
274  // button was pressed
275  mLastPressTime = now;
276  setFlag(kFlagPressed);
277  handleEvent(kEventPressed);
278 }
279 
280 void AceButton::checkReleased(uint16_t now, uint8_t buttonState) {
281  if (buttonState != getDefaultReleasedState()) {
282  return;
283  }
284 
285  // Check for click (before sending off the Released event).
286  // Make sure that we don't clearPressed() before calling this.
287  if (mButtonConfig->isFeature(ButtonConfig::kFeatureClick)
288  || mButtonConfig->isFeature(ButtonConfig::kFeatureDoubleClick)) {
289  checkClicked(now);
290  }
291 
292  // Save whether this was generated from a long press.
293  bool wasLongPressed = isFlag(kFlagLongPressed);
294 
295  // Check if Released events are suppressed.
296  bool suppress =
297  ((isFlag(kFlagLongPressed) &&
298  mButtonConfig->
300  (isFlag(kFlagRepeatPressed) &&
301  mButtonConfig->
303  (isFlag(kFlagClicked) &&
305  (isFlag(kFlagDoubleClicked) &&
306  mButtonConfig->
308 
309  // Button was released, so clear current flags. Note that the compiler will
310  // optimize the following 4 statements to be equivalent to this single one:
311  // mFlags &= ~kFlagPressed & ~kFlagDoubleClicked & ~kFlagLongPressed
312  // & ~kFlagRepeatPressed;
313  clearFlag(kFlagPressed);
314  clearFlag(kFlagDoubleClicked);
315  clearFlag(kFlagLongPressed);
316  clearFlag(kFlagRepeatPressed);
317 
318  // Fire off a Released event, unless suppressed. Replace Released with
319  // LongReleased if this was a LongPressed.
320  if (suppress) {
321  if (wasLongPressed) {
322  handleEvent(kEventLongReleased);
323  }
324  } else {
325  handleEvent(kEventReleased);
326  }
327 }
328 
329 void AceButton::checkClicked(uint16_t now) {
330  if (!isFlag(kFlagPressed)) {
331  // Not a Click unless the previous state was a Pressed state.
332  // This can happen if the chip was rebooted with the button Pressed. Upon
333  // Release, it shouldn't generated a click, even accidentally due to a
334  // spurious value in mLastPressTime.
335  clearFlag(kFlagClicked);
336  return;
337  }
338  uint16_t elapsedTime = now - mLastPressTime;
339  if (elapsedTime >= mButtonConfig->getClickDelay()) {
340  clearFlag(kFlagClicked);
341  return;
342  }
343 
344  // check for double click
345  if (mButtonConfig->isFeature(ButtonConfig::kFeatureDoubleClick)) {
346  checkDoubleClicked(now);
347  }
348 
349  // Suppress a second click (both buttonState change and event message) if
350  // double-click detected, which has the side-effect of preventing 3 clicks
351  // from generating another double-click at the third click.
352  if (isFlag(kFlagDoubleClicked)) {
353  clearFlag(kFlagClicked);
354  return;
355  }
356 
357  // we got a single click
358  mLastClickTime = now;
359  setFlag(kFlagClicked);
360  if (mButtonConfig->isFeature(
362  setFlag(kFlagClickPostponed);
363  } else {
364  handleEvent(kEventClicked);
365  }
366 }
367 
368 void AceButton::checkDoubleClicked(uint16_t now) {
369  if (!isFlag(kFlagClicked)) {
370  clearFlag(kFlagDoubleClicked);
371  return;
372  }
373 
374  uint16_t elapsedTime = now - mLastClickTime;
375  if (elapsedTime >= mButtonConfig->getDoubleClickDelay()) {
376  clearFlag(kFlagDoubleClicked);
377  // There should be no postponed Click at this point because
378  // checkPostponedClick() should have taken care of it.
379  return;
380  }
381 
382  // If there was a postponed click, suppress it because it could only have been
383  // postponed if kFeatureSuppressClickBeforeDoubleClick was enabled. If we got
384  // to this point, there was a DoubleClick, so we must suppress the first
385  // Click as requested.
386  if (isFlag(kFlagClickPostponed)) {
387  clearFlag(kFlagClickPostponed);
388  }
389  setFlag(kFlagDoubleClicked);
390  handleEvent(kEventDoubleClicked);
391 }
392 
393 void AceButton::checkOrphanedClick(uint16_t now) {
394  // The amount of time which must pass before a click is determined to be
395  // orphaned and reclaimed. If only DoubleClicked is supported, then I think
396  // just getDoubleClickDelay() is correct. No other higher level event uses the
397  // first Clicked event. If TripleClicked becomes supported, I think
398  // orphanedClickDelay will be either (2 * getDoubleClickDelay()) or
399  // (getDoubleClickDelay() + getTripleClickDelay()), depending on whether the
400  // TripleClick has an independent delay time, or reuses the DoubleClick delay
401  // time. But I'm not sure that I've thought through all the details.
402  uint16_t orphanedClickDelay = mButtonConfig->getDoubleClickDelay();
403 
404  uint16_t elapsedTime = now - mLastClickTime;
405  if (isFlag(kFlagClicked) && (elapsedTime >= orphanedClickDelay)) {
406  clearFlag(kFlagClicked);
407  }
408 }
409 
410 void AceButton::checkPostponedClick(uint16_t now) {
411  uint16_t postponedClickDelay = mButtonConfig->getDoubleClickDelay();
412  uint16_t elapsedTime = now - mLastClickTime;
413  if (isFlag(kFlagClickPostponed) && elapsedTime >= postponedClickDelay) {
414  handleEvent(kEventClicked);
415  clearFlag(kFlagClickPostponed);
416  }
417 }
418 
419 void AceButton::checkHeartBeat(uint16_t now) {
420  if (! mButtonConfig->isFeature(ButtonConfig::kFeatureHeartBeat)) return;
421 
422  // On first call, set the last heart beat time.
423  if (! isFlag(kFlagHeartRunning)) {
424  setFlag(kFlagHeartRunning);
425  mLastHeartBeatTime = now;
426  return;
427  }
428 
429  uint16_t elapsedTime = now - mLastHeartBeatTime;
430  if (elapsedTime >= mButtonConfig->getHeartBeatInterval()) {
431  // This causes the kEventHeartBeat to be sent with the last validated button
432  // state, not the current button state. I think that makes more sense, but
433  // there might be situations where it doesn't.
434  handleEvent(kEventHeartBeat);
435  mLastHeartBeatTime = now;
436  }
437 }
438 
439 void AceButton::handleEvent(uint8_t eventType) {
440  mButtonConfig->dispatchEvent(this, eventType, getLastButtonState());
441 }
442 
443 }
static const uint8_t kEventDoubleClicked
Button was double-clicked.
Definition: AceButton.h:74
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:88
static const uint8_t kEventClicked
Button was clicked (Pressed and Released within ButtonConfig::getClickDelay()).
Definition: AceButton.h:68
static __FlashStringHelper * eventName(uint8_t event)
Return the human-readable name of the event.
Definition: AceButton.cpp:79
void checkState(uint8_t buttonState)
Version of check() used by EncodedButtonConfig.
Definition: AceButton.cpp:121
uint8_t getDefaultReleasedState() const
Get the initial released state of the button, HIGH or LOW.
Definition: AceButton.cpp:110
static const uint8_t kEventLongPressed
Button was held down for longer than ButtonConfig::getLongPressDelay()).
Definition: AceButton.h:80
static const uint8_t kEventRepeatPressed
Button was held down and auto generated multiple presses.
Definition: AceButton.h:88
static const uint8_t kEventLongReleased
Button was released after a long press.
Definition: AceButton.h:99
uint8_t getLastButtonState() const
Return the button state that was last valid.
Definition: AceButton.h:292
static const uint8_t kEventHeartBeat
An event that fires every time interval defined by getHeartBeatInterval().
Definition: AceButton.h:111
static const uint8_t kEventReleased
Button was released.
Definition: AceButton.h:62
static const uint8_t kEventPressed
Button was pressed.
Definition: AceButton.h:59
void check()
Check state of button and trigger event processing.
Definition: AceButton.cpp:116
static const uint8_t kButtonStateUnknown
Button state is unknown.
Definition: AceButton.h:119
Class that defines the timing parameters and event handler of an AceButton or a group of AceButton in...
Definition: ButtonConfig.h:66
uint16_t getDoubleClickDelay() const
Milliseconds between the first and second click to register as a double-click.
Definition: ButtonConfig.h:231
static const FeatureFlagType kFeatureSuppressAfterLongPress
Flag to suppress kEventReleased after a kEventLongPressed.
Definition: ButtonConfig.h:139
uint16_t getDebounceDelay() const
Milliseconds to wait for debouncing.
Definition: ButtonConfig.h:222
uint16_t getClickDelay() const
Milliseconds to wait for a possible click.
Definition: ButtonConfig.h:225
uint16_t getLongPressDelay() const
Milliseconds for a long press event.
Definition: ButtonConfig.h:236
virtual unsigned long getClock()
Return the milliseconds of the internal clock.
Definition: ButtonConfig.h:307
static const FeatureFlagType kFeatureLongPress
Flag to activate the AceButton::kEventLongPress event.
Definition: ButtonConfig.h:123
static const FeatureFlagType kFeatureSuppressAfterClick
Flag to suppress kEventReleased after a kEventClicked.
Definition: ButtonConfig.h:129
uint16_t getRepeatPressInterval() const
Milliseconds between two successive RepeatPressed events.
Definition: ButtonConfig.h:251
virtual int readButton(uint8_t pin)
Return the HIGH or LOW state of the button.
Definition: ButtonConfig.h:317
static const FeatureFlagType kFeatureDoubleClick
Flag to activate the AceButton::kEventDoubleClicked event.
Definition: ButtonConfig.h:120
static const FeatureFlagType kFeatureSuppressAfterRepeatPress
Flag to suppress kEventReleased after a kEventRepeatPressed.
Definition: ButtonConfig.h:142
static const FeatureFlagType kFeatureSuppressAfterDoubleClick
Flag to suppress kEventReleased after a kEventDoubleClicked.
Definition: ButtonConfig.h:136
static const FeatureFlagType kFeatureSuppressClickBeforeDoubleClick
Flag to suppress kEventClicked before a kEventDoubleClicked.
Definition: ButtonConfig.h:150
void dispatchEvent(AceButton *button, uint8_t eventType, uint8_t buttonState) const
Dispatch the event to the handler.
Definition: ButtonConfig.h:370
static const FeatureFlagType kFeatureClick
Flag to activate the AceButton::kEventClicked event.
Definition: ButtonConfig.h:113
static const FeatureFlagType kFeatureRepeatPress
Flag to activate the AceButton::kEventRepeatPressed event.
Definition: ButtonConfig.h:126
bool isFeature(FeatureFlagType features) const
Check if the given features are enabled.
Definition: ButtonConfig.h:325
uint16_t getRepeatPressDelay() const
Milliseconds that a button needs to be Pressed down before the start of the sequence of RepeatPressed...
Definition: ButtonConfig.h:246
static const FeatureFlagType kFeatureHeartBeat
Flag to enable periodic kEventHeartBeat.
Definition: ButtonConfig.h:153
uint16_t getHeartBeatInterval() const
Milliseconds between two successive HeartBeat events.
Definition: ButtonConfig.h:256