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