AceButton  1.6
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 AceButton::AceButton(uint8_t pin, uint8_t defaultReleasedState, uint8_t id):
55  mButtonConfig(ButtonConfig::getSystemButtonConfig()) {
56  init(pin, defaultReleasedState, id);
57 }
58 
59 AceButton::AceButton(ButtonConfig* buttonConfig, uint8_t pin,
60  uint8_t defaultReleasedState, uint8_t id) {
61  init(buttonConfig, pin, defaultReleasedState, id);
62 }
63 
64 void AceButton::init(uint8_t pin, uint8_t defaultReleasedState, uint8_t id) {
65  mPin = pin;
66  mId = id;
67  mFlags = 0;
68  mLastButtonState = kButtonStateUnknown;
69  mLastDebounceTime = 0;
70  mLastClickTime = 0;
71  setDefaultReleasedState(defaultReleasedState);
72 }
73 
74 void AceButton::init(ButtonConfig* buttonConfig, uint8_t pin,
75  uint8_t defaultReleasedState, uint8_t id) {
76  mButtonConfig = buttonConfig;
77  init(pin, defaultReleasedState, id);
78 }
79 
80 void AceButton::setDefaultReleasedState(uint8_t state) {
81  if (state == HIGH) {
82  mFlags |= kFlagDefaultReleasedState;
83  } else {
84  mFlags &= ~kFlagDefaultReleasedState;
85  }
86 }
87 
89  return (mFlags & kFlagDefaultReleasedState) ? HIGH : LOW;
90 }
91 
92 // NOTE: It would be interesting to rewrite the check() method using a Finite
93 // State Machine.
95  uint8_t buttonState = mButtonConfig->readButton(mPin);
96  checkState(buttonState);
97 }
98 
99 void AceButton::checkState(uint8_t buttonState) {
100  // Retrieve the current time just once and use that in the various checkXxx()
101  // functions below. This provides some robustness of the various timing
102  // algorithms even if one of the event handlers takes more time than the
103  // threshold time limits such as 'debounceDelay' or longPressDelay'.
104  uint16_t now = mButtonConfig->getClock();
105 
106  // debounce the button
107  if (checkDebounced(now, buttonState)) {
108  // check if the button was initialized (i.e. UNKNOWN state)
109  if (checkInitialized(buttonState)) {
110  checkEvent(now, buttonState);
111  }
112  }
113 }
114 
115 void AceButton::checkEvent(uint16_t now, uint8_t buttonState) {
116  // We need to remove orphaned clicks even if just Click is enabled. It is not
117  // sufficient to do this for just DoubleClick. That's because it's possible
118  // for a Clicked event to be generated, then 65.536 seconds later, the
119  // ButtonConfig could be changed to enable DoubleClick. (Such real-time change
120  // of ButtonConfig is not recommended, but is sometimes convenient.) If the
121  // orphaned click is not cleared, then the next Click would be errorneously
122  // considered to be a DoubleClick. Therefore, we must clear the orphaned click
123  // even if just the Clicked event is enabled.
124  //
125  // We also need to check of any postponed clicks that got generated when
126  // kFeatureSuppressClickBeforeDoubleClick was enabled.
127  if (mButtonConfig->isFeature(ButtonConfig::kFeatureClick) ||
129  checkPostponedClick(now);
130  checkOrphanedClick(now);
131  }
132 
133  if (mButtonConfig->isFeature(ButtonConfig::kFeatureLongPress)) {
134  checkLongPress(now, buttonState);
135  }
136  if (mButtonConfig->isFeature(ButtonConfig::kFeatureRepeatPress)) {
137  checkRepeatPress(now, buttonState);
138  }
139  if (buttonState != getLastButtonState()) {
140  checkChanged(now, buttonState);
141  }
142 }
143 
144 bool AceButton::checkDebounced(uint16_t now, uint8_t buttonState) {
145  if (isDebouncing()) {
146 
147  // NOTE: This is a bit tricky. The elapsedTime will be valid even if the
148  // uint16_t representation of 'now' rolls over so that (now <
149  // mLastDebounceTime). This is true as long as the 'unsigned long'
150  // representation of 'now' is < (65536 + mLastDebounceTime). We need to cast
151  // this expression into an uint16_t before doing the '>=' comparison below
152  // for compatability with processors whose sizeof(int) == 4 instead of 2.
153  // For those processors, the expression (now - mLastDebounceTime >=
154  // getDebounceDelay()) won't work because the terms in the expression get
155  // promoted to an (int).
156  uint16_t elapsedTime = now - mLastDebounceTime;
157 
158  bool isDebouncingTimeOver =
159  (elapsedTime >= mButtonConfig->getDebounceDelay());
160 
161  if (isDebouncingTimeOver) {
162  clearDebouncing();
163  return true;
164  } else {
165  return false;
166  }
167  } else {
168  // Currently not in debouncing phase. Check for a button state change. This
169  // will also detect a transition from kButtonStateUnknown to HIGH or LOW.
170  if (buttonState == getLastButtonState()) {
171  // no change, return immediately
172  return true;
173  }
174 
175  // button has changed so, enter debouncing phase
176  setDebouncing();
177  mLastDebounceTime = now;
178  return false;
179  }
180 }
181 
182 bool AceButton::checkInitialized(uint16_t buttonState) {
183  if (mLastButtonState != kButtonStateUnknown) {
184  return true;
185  }
186 
187  // If transitioning from the initial "unknown" button state, just set the last
188  // valid button state, but don't fire off the event handler. This handles the
189  // case where a momentary switch is pressed down, then the board is rebooted.
190  // When the board comes up, it should not fire off the event handler. This
191  // also handles the case of a 2-position switch set to the "pressed"
192  // position, and the board is rebooted.
193  mLastButtonState = buttonState;
194  return false;
195 }
196 
197 void AceButton::checkLongPress(uint16_t now, uint8_t buttonState) {
198  if (buttonState == getDefaultReleasedState()) {
199  return;
200  }
201 
202  if (isPressed() && !isLongPressed()) {
203  uint16_t elapsedTime = now - mLastPressTime;
204  if (elapsedTime >= mButtonConfig->getLongPressDelay()) {
205  setLongPressed();
206  handleEvent(kEventLongPressed);
207  }
208  }
209 }
210 
211 void AceButton::checkRepeatPress(uint16_t now, uint8_t buttonState) {
212  if (buttonState == getDefaultReleasedState()) {
213  return;
214  }
215 
216  if (isPressed()) {
217  if (isRepeatPressed()) {
218  uint16_t elapsedTime = now - mLastRepeatPressTime;
219  if (elapsedTime >= mButtonConfig->getRepeatPressInterval()) {
220  handleEvent(kEventRepeatPressed);
221  mLastRepeatPressTime = now;
222  }
223  } else {
224  uint16_t elapsedTime = now - mLastPressTime;
225  if (elapsedTime >= mButtonConfig->getRepeatPressDelay()) {
226  setRepeatPressed();
227  // Trigger the RepeatPressed immedidately, instead of waiting until the
228  // first getRepeatPressInterval() has passed.
229  handleEvent(kEventRepeatPressed);
230  mLastRepeatPressTime = now;
231  }
232  }
233  }
234 }
235 
236 void AceButton::checkChanged(uint16_t now, uint8_t buttonState) {
237  mLastButtonState = buttonState;
238  checkPressed(now, buttonState);
239  checkReleased(now, buttonState);
240 }
241 
242 void AceButton::checkPressed(uint16_t now, uint8_t buttonState) {
243  if (buttonState == getDefaultReleasedState()) {
244  return;
245  }
246 
247  // button was pressed
248  mLastPressTime = now;
249  setPressed();
250  handleEvent(kEventPressed);
251 }
252 
253 void AceButton::checkReleased(uint16_t now, uint8_t buttonState) {
254  if (buttonState != getDefaultReleasedState()) {
255  return;
256  }
257 
258  // Check for click (before sending off the Released event).
259  // Make sure that we don't clearPressed() before calling this.
260  if (mButtonConfig->isFeature(ButtonConfig::kFeatureClick)
261  || mButtonConfig->isFeature(ButtonConfig::kFeatureDoubleClick)) {
262  checkClicked(now);
263  }
264 
265  // check if Released events are suppressed
266  bool suppress =
267  ((isLongPressed() &&
268  mButtonConfig->
270  (isRepeatPressed() &&
271  mButtonConfig->
273  (isClicked() &&
275  (isDoubleClicked() &&
276  mButtonConfig->
278 
279  // button was released
280  clearPressed();
281  clearDoubleClicked();
282  clearLongPressed();
283  clearRepeatPressed();
284 
285  if (!suppress) {
286  handleEvent(kEventReleased);
287  }
288 }
289 
290 void AceButton::checkClicked(uint16_t now) {
291  if (!isPressed()) {
292  // Not a Click unless the previous state was a Pressed state.
293  // This can happen if the chip was rebooted with the button Pressed. Upon
294  // Release, it shouldn't generated a click, even accidentally due to a
295  // spurious value in mLastPressTime.
296  clearClicked();
297  return;
298  }
299  uint16_t elapsedTime = now - mLastPressTime;
300  if (elapsedTime >= mButtonConfig->getClickDelay()) {
301  clearClicked();
302  return;
303  }
304 
305  // check for double click
306  if (mButtonConfig->isFeature(ButtonConfig::kFeatureDoubleClick)) {
307  checkDoubleClicked(now);
308  }
309 
310  // Suppress a second click (both buttonState change and event message) if
311  // double-click detected, which has the side-effect of preventing 3 clicks
312  // from generating another double-click at the third click.
313  if (isDoubleClicked()) {
314  clearClicked();
315  return;
316  }
317 
318  // we got a single click
319  mLastClickTime = now;
320  setClicked();
321  if (mButtonConfig->isFeature(
323  setClickPostponed();
324  } else {
325  handleEvent(kEventClicked);
326  }
327 }
328 
329 void AceButton::checkDoubleClicked(uint16_t now) {
330  if (!isClicked()) {
331  clearDoubleClicked();
332  return;
333  }
334 
335  uint16_t elapsedTime = now - mLastClickTime;
336  if (elapsedTime >= mButtonConfig->getDoubleClickDelay()) {
337  clearDoubleClicked();
338  // There should be no postponed Click at this point because
339  // checkPostponedClick() should have taken care of it.
340  return;
341  }
342 
343  // If there was a postponed click, suppress it because it could only have been
344  // postponed if kFeatureSuppressClickBeforeDoubleClick was enabled. If we got
345  // to this point, there was a DoubleClick, so we must suppress the first
346  // Click as requested.
347  if (isClickPostponed()) {
348  clearClickPostponed();
349  }
350  setDoubleClicked();
351  handleEvent(kEventDoubleClicked);
352 }
353 
354 void AceButton::checkOrphanedClick(uint16_t now) {
355  // The amount of time which must pass before a click is determined to be
356  // orphaned and reclaimed. If only DoubleClicked is supported, then I think
357  // just getDoubleClickDelay() is correct. No other higher level event uses the
358  // first Clicked event. If TripleClicked becomes supported, I think
359  // orphanedClickDelay will be either (2 * getDoubleClickDelay()) or
360  // (getDoubleClickDelay() + getTripleClickDelay()), depending on whether the
361  // TripleClick has an independent delay time, or reuses the DoubleClick delay
362  // time. But I'm not sure that I've thought through all the details.
363  uint16_t orphanedClickDelay = mButtonConfig->getDoubleClickDelay();
364 
365  uint16_t elapsedTime = now - mLastClickTime;
366  if (isClicked() && (elapsedTime >= orphanedClickDelay)) {
367  clearClicked();
368  }
369 }
370 
371 void AceButton::checkPostponedClick(uint16_t now) {
372  uint16_t postponedClickDelay = mButtonConfig->getDoubleClickDelay();
373  uint16_t elapsedTime = now - mLastClickTime;
374  if (isClickPostponed() && elapsedTime >= postponedClickDelay) {
375  handleEvent(kEventClicked);
376  clearClickPostponed();
377  }
378 }
379 
380 void AceButton::handleEvent(uint8_t eventType) {
381  mButtonConfig->dispatchEvent(this, eventType, getLastButtonState());
382 }
383 
384 }
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:223
ace_button::ButtonConfig::getLongPressDelay
uint16_t getLongPressDelay() const
Milliseconds for a long press event.
Definition: ButtonConfig.h:213
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:94
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:84
ace_button::ButtonConfig::kFeatureClick
static const FeatureFlagType kFeatureClick
Flag to activate the AceButton::kEventClicked event.
Definition: ButtonConfig.h:110
ace_button::AceButton::AceButton
AceButton(uint8_t pin=0, uint8_t defaultReleasedState=HIGH, uint8_t id=0)
Constructor defines parameters of the button that changes from button to button.
Definition: AceButton.cpp:54
ace_button::ButtonConfig::getClock
virtual unsigned long getClock()
Return the milliseconds of the internal clock.
Definition: ButtonConfig.h:276
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:58
ace_button::AceButton::getDefaultReleasedState
uint8_t getDefaultReleasedState() const
Get the initial released state of the button, HIGH or LOW.
Definition: AceButton.cpp:88
ace_button::ButtonConfig::dispatchEvent
void dispatchEvent(AceButton *button, uint8_t eventType, uint8_t buttonState) const
Dispatch the event to the handler.
Definition: ButtonConfig.h:339
ace_button::ButtonConfig::getDebounceDelay
uint16_t getDebounceDelay() const
Milliseconds to wait for debouncing.
Definition: ButtonConfig.h:199
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:294
ace_button::ButtonConfig::getDoubleClickDelay
uint16_t getDoubleClickDelay() const
Milliseconds between the first and second click to register as a double-click.
Definition: ButtonConfig.h:208
ace_button::ButtonConfig::getClickDelay
uint16_t getClickDelay() const
Milliseconds to wait for a possible click.
Definition: ButtonConfig.h:202
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:76
ace_button::ButtonConfig::getRepeatPressInterval
uint16_t getRepeatPressInterval() const
Milliseconds between two successive RepeatPressed events.
Definition: ButtonConfig.h:230
ace_button::AceButton::kEventClicked
static const uint8_t kEventClicked
Button was clicked (Pressed and Released within ButtonConfig::getClickDelay()).
Definition: AceButton.h:64
ace_button::AceButton::kEventPressed
static const uint8_t kEventPressed
Button was pressed.
Definition: AceButton.h:55
ace_button::AceButton::kButtonStateUnknown
static const uint8_t kButtonStateUnknown
Button state is unknown.
Definition: AceButton.h:92
ace_button::AceButton::getLastButtonState
uint8_t getLastButtonState() const
Return the button state that was last valid.
Definition: AceButton.h:228
ace_button::AceButton::checkState
void checkState(uint8_t buttonState)
Version of check() used by EncodedButtonConfig.
Definition: AceButton.cpp:99
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:64
ace_button::AceButton::kEventDoubleClicked
static const uint8_t kEventDoubleClicked
Button was double-clicked.
Definition: AceButton.h:70
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:286