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