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