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