acid-drop

- Hacking the planet from a LilyGo T-Deck using custom firmware
git clone git://git.acid.vegas/acid-drop.git
Log | Files | Refs | Archive | README | LICENSE

AceButton.cpp (12555B)

      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 AceButton::AceButton(ButtonConfig* buttonConfig):
     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 
     67 uint8_t AceButton::getDefaultReleasedState() {
     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.
     73 void AceButton::check() {
     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) ||
    113       mButtonConfig->isFeature(ButtonConfig::kFeatureDoubleClick)) {
    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->
    254               isFeature(ButtonConfig::kFeatureSuppressAfterLongPress)) ||
    255       (isRepeatPressed() &&
    256           mButtonConfig->
    257               isFeature(ButtonConfig::kFeatureSuppressAfterRepeatPress)) ||
    258       (isClicked() &&
    259           mButtonConfig->isFeature(ButtonConfig::kFeatureSuppressAfterClick)) ||
    260       (isDoubleClicked() &&
    261           mButtonConfig->
    262               isFeature(ButtonConfig::kFeatureSuppressAfterDoubleClick)));
    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(
    307       ButtonConfig::kFeatureSuppressClickBeforeDoubleClick)) {
    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 }