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