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.h (18480B)

      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 #ifndef ACE_BUTTON_ACE_BUTTON_H
     26 #define ACE_BUTTON_ACE_BUTTON_H
     27 
     28 #if !defined(RASPBERRY_PI)
     29 #include <Arduino.h>
     30 #else
     31 #include <raspi/raspi.h>
     32 #endif /* RASPBERRY_PI */
     33 
     34 #include "ButtonConfig.h"
     35 
     36 namespace ace_button {
     37 
     38 /**
     39  * An Adjustable Compact Event-driven (ACE) Button library that debounces and
     40  * dispatches button events to a user-defined event handler. Supported events
     41  * types are:
     42  *
     43  * - kEventPressed
     44  * - kEventReleased
     45  * - kEventClicked
     46  * - kEventDoubleClicked
     47  * - kEventLongPressed
     48  * - kEventRepeatPressed
     49  *
     50  * The check() method should be called from the loop() at least 2-3 times during
     51  * the debouncing time period. For 20 ms delay, the check() method should be
     52  * called at a minimum of every 5 ms. The execution time of check() on a 16
     53  * MHz Arduino ATmega328P MCU seems to about about 12-14 microseconds.
     54  */
     55 class AceButton {
     56   public:
     57     // The supported event types.
     58 
     59     /** Button was pressed. */
     60     static const uint8_t kEventPressed = 0;
     61 
     62     /** Button was released. */
     63     static const uint8_t kEventReleased = 1;
     64 
     65     /**
     66      * Button was clicked (Pressed and Released within
     67      * ButtonConfig::getClickDelay()).
     68      */
     69     static const uint8_t kEventClicked = 2;
     70 
     71     /**
     72      * Button was double-clicked. (Two clicks within
     73      * ButtonConfig::getDoubleClickDelay()).
     74      */
     75     static const uint8_t kEventDoubleClicked = 3;
     76 
     77     /**
     78      * Button was held down for longer than
     79      * ButtonConfig::getLongPressDelay()).
     80      */
     81     static const uint8_t kEventLongPressed = 4;
     82 
     83     /**
     84      * Button was held down and auto generated multiple presses. The first event
     85      * is triggered after ButtonConfig::getRepeatPressDelay(), then the event
     86      * fires repeatedly every ButtonConfig::getRepeatPressInterval() until the
     87      * button is released.
     88      */
     89     static const uint8_t kEventRepeatPressed = 5;
     90 
     91     /**
     92      * Button state is unknown. This is a third state (different from LOW or
     93      * HIGH) used when the class is first initialized upon reboot.
     94      */
     95     static const uint8_t kButtonStateUnknown = 2;
     96 
     97     /**
     98      * Constructor defines parameters of the button that changes from button to
     99      * button. These parameters don't change during the runtime of the program.
    100      * Another way to initialize the object is to create an instance using
    101      * an empty constructor, then use the init() method to initialize the
    102      * object with these parameters.
    103      *
    104      * Using the constructor often reads better for simple situations where only
    105      * a single button is used, and it doesn't need to be configured
    106      * significantly. Using the init() method can make the code be more readable
    107      * when multiple buttons are used, and they need to be significantly
    108      * customized. The init() method allows the button configuration code to
    109      * appear in close proximity to the pinMode() methods which sets up the
    110      * hardware pins.
    111      *
    112      * @param pin The pin number of the button. Default 0. Normally the pin
    113      * number should be given at construction time. However, the pin number
    114      * the pin number can be omitted so that the pin number can be assigned at
    115      * setup() time using the setPin() method.
    116      *
    117      * @param defaultReleasedState The pin state when the button is in the
    118      * initial released position. Default HIGH. When using a pullup resistor
    119      * (either external or internal) and the button is connected to ground, this
    120      * should be set to HIGH. When using an external pulldown resistor and the
    121      * button is connected to Vcc (5V or 3.3V), this should be set to LOW. The
    122      * defaultReleasedState can be assigned using the constructor or the
    123      * init() method.
    124      *
    125      * @param id This is an optional user-defined identifier for the
    126      * button. For example, this could be an index into an array of data that is
    127      * associated with the button.
    128      */
    129     explicit AceButton(uint8_t pin = 0, uint8_t defaultReleasedState = HIGH,
    130         uint8_t id = 0);
    131 
    132     /**
    133      * Constructor that accepts a ButtonConfig as a dependency. Dependency
    134      * injection using this constructor is now recommended over using the
    135      * setButtonConfig() method because it makes the dependency more clear.
    136      */
    137     explicit AceButton(ButtonConfig* buttonConfig);
    138 
    139     /**
    140      * Reset the button to the initial constructed state. In particular,
    141      * getLastButtonState() returns kButtonStateUnknown. The parameters are
    142      * identical as the parameters in the AceButton() constructor.
    143      */
    144     void init(uint8_t pin = 0, uint8_t defaultReleasedState = HIGH,
    145         uint8_t id = 0);
    146 
    147     /** Get the ButtonConfig associated with this Button. */
    148     ButtonConfig* getButtonConfig() ACE_BUTTON_INLINE {
    149       return mButtonConfig;
    150     }
    151 
    152     /**
    153      * Set the ButtonConfig associated with this Button. It is recommended that
    154      * the AceButton(ButtonConfig*) constructor is used instead to make the
    155      * dependency to ButtonConfig more explicit.
    156      */
    157     void setButtonConfig(ButtonConfig* buttonConfig) ACE_BUTTON_INLINE {
    158       mButtonConfig = buttonConfig;
    159     }
    160 
    161     /**
    162      * Convenience method to set the event handler. Event handlers are stored in
    163      * the ButtonConfig object, not in the AceButton object, to save
    164      * memory. (Multiple buttons are likely to share the same event handler.) So
    165      * this method is just a pass-through to ButtonConfig::setEventHandler(). If
    166      * you are using multiple ButtonConfig objects, you should call the
    167      * ButtonConfig::setEventHandler() method on those objects directly, instead
    168      * of using this method.
    169      */
    170     void setEventHandler(ButtonConfig::EventHandler eventHandler)
    171         ACE_BUTTON_INLINE {
    172       mButtonConfig->setEventHandler(eventHandler);
    173     }
    174 
    175     /** Get the button's pin number. */
    176     uint8_t getPin() ACE_BUTTON_INLINE { return mPin; }
    177 
    178     /** Get the custom identifier of the button. */
    179     uint8_t getId() ACE_BUTTON_INLINE { return mId; }
    180 
    181     /** Get the initial released state of the button, HIGH or LOW. */
    182     uint8_t getDefaultReleasedState();
    183 
    184     /**
    185      * Return the button state that was last valid. This is a tri-state
    186      * function. It may return HIGH, LOW or kButtonStateUnknown to indicate that
    187      * the last state is not known. This method is **not** for public
    188      * consumption, it is exposed only for testing purposes. Consider it to be a
    189      * private method. Use the buttonState parameter provided to the
    190      * EventHandler.
    191      *
    192      * In a more general multi-threaded environment (which the Arduino is not,
    193      * fortunately or unfortunately), the getLastButtonState() may have changed
    194      * from the value of buttonState provided to the event handler. In other
    195      * words, there is a race-condition.
    196      */
    197     uint8_t getLastButtonState() ACE_BUTTON_INLINE {
    198       return mLastButtonState;
    199     }
    200 
    201     /**
    202      * Check state of button and trigger event processing. This method should be
    203      * called from the loop() method in Arduino every 4-5 times during the
    204      * getDebounceDelay() time (default 20 ms), so about every 5 ms. If this
    205      * is called less often than that, the debouncing algorithm may not work
    206      * correctly, which may cause other event detection algorithms to fail.
    207      */
    208     void check();
    209 
    210     /**
    211      * Returns true if the given buttonState represents a 'Released' state for
    212      * the button. Returns false if the buttonState is 'Pressed' or
    213      * kButtonStateUnknown.
    214      *
    215      * The HIGH or LOW logical value of buttonState represents different a
    216      * button position depending on whether the button is wired with a pull-up
    217      * or a pull-down resistor. This method translates the logical level to the
    218      * physical position which allows the client code to be independent of the
    219      * physical wiring.
    220      *
    221      * Normally, the eventType given to the EventHandler should be sufficient
    222      * because the value of the eventType already encodes this information.
    223      * This method is provided just in case.
    224      */
    225     bool isReleased(uint8_t buttonState) ACE_BUTTON_INLINE {
    226       return buttonState == getDefaultReleasedState();
    227     }
    228 
    229     /**
    230      * Read the button state directly using ButtonConfig and return true if the
    231      * button is in the Pressed state. This method is intended to be used in the
    232      * global setup() to determine if the button was pressed while the device
    233      * was booted. This method does not use the check() method, does not perform
    234      * any debouncing, and does not dispatch events to the EventHandler.
    235      */
    236     bool isPressedRaw() ACE_BUTTON_INLINE {
    237       return !isReleased(mButtonConfig->readButton(mPin));
    238     }
    239 
    240   // Some of these private methods may be useful to the calling client but I
    241   // don't want to release them to the public because I want to keep the API as
    242   // small as possible for easier long term maintenance. (Once a method is
    243   // released to the public, it must be supported forever to ensure backwards
    244   // compatibility with older client code.)
    245 
    246   private:
    247     // Disable copy-constructor and assignment operator
    248     AceButton(const AceButton&) = delete;
    249     AceButton& operator=(const AceButton&) = delete;
    250 
    251     /** Set the pin number of the button. */
    252     void setPin(uint8_t pin) ACE_BUTTON_INLINE { mPin = pin; }
    253 
    254     /**
    255      * Set the initial released state of the button.
    256      *
    257      * @param state If a pull up resistor is used, this should be HIGH. If a
    258      * pull down resistor is used, this should be LOW. The behavior is undefined
    259      * for any other values of 'state'.
    260      */
    261     void setDefaultReleasedState(uint8_t state);
    262 
    263     /** Set the identifier of the button. */
    264     void setId(uint8_t id) ACE_BUTTON_INLINE { mId = id; }
    265 
    266     // Various bit masks to store a boolean flag in the 'mFlags' field.
    267     // We use bit masks to save static RAM. If we had used a 'bool' type, each
    268     // of these would consume one byte.
    269     static const uint8_t kFlagDefaultReleasedState = 0x01;
    270     static const uint8_t kFlagDebouncing = 0x02;
    271     static const uint8_t kFlagPressed = 0x04;
    272     static const uint8_t kFlagClicked = 0x08;
    273     static const uint8_t kFlagDoubleClicked = 0x10;
    274     static const uint8_t kFlagLongPressed = 0x20;
    275     static const uint8_t kFlagRepeatPressed = 0x40;
    276     static const uint8_t kFlagClickPostponed = 0x80;
    277 
    278     // Methods for accessing the button's internal states.
    279     // I don't expect these to be useful to the outside world.
    280 
    281     // If this is set, then mLastDebounceTime is valid.
    282     bool isDebouncing() ACE_BUTTON_INLINE {
    283       return mFlags & kFlagDebouncing;
    284     }
    285 
    286     void setDebouncing() ACE_BUTTON_INLINE {
    287       mFlags |= kFlagDebouncing;
    288     }
    289 
    290     void clearDebouncing() ACE_BUTTON_INLINE {
    291       mFlags &= ~kFlagDebouncing;
    292     }
    293 
    294     // If this is set, then mLastPressTime is valid.
    295     bool isPressed() ACE_BUTTON_INLINE {
    296       return mFlags & kFlagPressed;
    297     }
    298 
    299     void setPressed() ACE_BUTTON_INLINE {
    300       mFlags |= kFlagPressed;
    301     }
    302 
    303     void clearPressed() ACE_BUTTON_INLINE {
    304       mFlags &= ~kFlagPressed;
    305     }
    306 
    307     // If this is set, then mLastClickTime is valid.
    308     bool isClicked() ACE_BUTTON_INLINE {
    309       return mFlags & kFlagClicked;
    310     }
    311 
    312     void setClicked() ACE_BUTTON_INLINE {
    313       mFlags |= kFlagClicked;
    314     }
    315 
    316     void clearClicked() ACE_BUTTON_INLINE {
    317       mFlags &= ~kFlagClicked;
    318     }
    319 
    320     // A double click was detected. No need to store the last double-clicked
    321     // time because we don't support a triple-click event (yet).
    322     bool isDoubleClicked() ACE_BUTTON_INLINE {
    323       return mFlags & kFlagDoubleClicked;
    324     }
    325 
    326     void setDoubleClicked() ACE_BUTTON_INLINE {
    327       mFlags |= kFlagDoubleClicked;
    328     }
    329 
    330     void clearDoubleClicked() ACE_BUTTON_INLINE {
    331       mFlags &= ~kFlagDoubleClicked;
    332     }
    333 
    334     // If this is set, then mLastPressTime can be treated as the start
    335     // of a long press.
    336     bool isLongPressed() ACE_BUTTON_INLINE {
    337       return mFlags & kFlagLongPressed;
    338     }
    339 
    340     void setLongPressed() ACE_BUTTON_INLINE {
    341       mFlags |= kFlagLongPressed;
    342     }
    343 
    344     void clearLongPressed() ACE_BUTTON_INLINE {
    345       mFlags &= ~kFlagLongPressed;
    346     }
    347 
    348     // If this is set, then mLastRepeatPressTime is valid.
    349     bool isRepeatPressed() ACE_BUTTON_INLINE {
    350       return mFlags & kFlagRepeatPressed;
    351     }
    352 
    353     void setRepeatPressed() ACE_BUTTON_INLINE {
    354       mFlags |= kFlagRepeatPressed;
    355     }
    356 
    357     void clearRepeatPressed() ACE_BUTTON_INLINE {
    358       mFlags &= ~kFlagRepeatPressed;
    359     }
    360 
    361     bool isClickPostponed() ACE_BUTTON_INLINE {
    362       return mFlags & kFlagClickPostponed;
    363     }
    364 
    365     void setClickPostponed() ACE_BUTTON_INLINE {
    366       mFlags |= kFlagClickPostponed;
    367     }
    368 
    369     void clearClickPostponed() ACE_BUTTON_INLINE {
    370       mFlags &= ~kFlagClickPostponed;
    371     }
    372 
    373     /**
    374      * Return true if debouncing succeeded and the buttonState value can be
    375      * used. Return false if buttonState should be ignored until debouncing
    376      * phase is complete.
    377      */
    378     bool checkDebounced(uint16_t now, uint8_t buttonState);
    379 
    380     /**
    381      * Return true if the button was already initialzed and determined to be in
    382      * a HIGH or LOW state. Return false if the button was previously in
    383      * kButtonStateUnknown state which implies that the event handler should
    384      * NOT be fired.
    385      */
    386     bool checkInitialized(uint16_t buttonState);
    387 
    388     /** Categorize the button event. */
    389     void checkEvent(uint16_t now, uint8_t buttonState);
    390 
    391     /** Check for a long press event and dispatch to event handler. */
    392     void checkLongPress(uint16_t now, uint8_t buttonState);
    393 
    394     /** Check for a repeat press event and dispatch to event handler. */
    395     void checkRepeatPress(uint16_t now, uint8_t buttonState);
    396 
    397     /** Check for onChange event and check for Press or Release events. */
    398     void checkChanged(uint16_t now, uint8_t buttonState);
    399 
    400     /**
    401      * Check for Released and Click events and dispatch to respective
    402      * handlers.
    403      */
    404     void checkReleased(uint16_t now, uint8_t buttonState);
    405 
    406     /** Check for Pressed event and dispatch to handler. */
    407     void checkPressed(uint16_t now, uint8_t buttonState);
    408 
    409     /** Check for a single click event and dispatch to handler. */
    410     void checkClicked(uint16_t now);
    411 
    412     /**
    413      * Check for a double click event and dispatch to handler. Return true if
    414      * double click detected.
    415      */
    416     void checkDoubleClicked(uint16_t now);
    417 
    418     /**
    419      * Check for an orphaned click that did not generate a double click and
    420      * clean up internal state. If we don't do this, the second click may be
    421      * generated after the uint16_t rolls over in 65.5 seconds, causing an
    422      * unwanted double-click. Even if we used the full 'unsigned long' to store
    423      * the 'lastClickTime', we'd still need this function to prevent a rollover
    424      * of the 32-bit number in 49.7 days.
    425      */
    426     void checkOrphanedClick(uint16_t now);
    427 
    428     /**
    429      * Check if a click message has been postponed because of
    430      * ButtonConfig::kFeatureSuppressClickBeforeDoubleClick.
    431      */
    432     void checkPostponedClick(uint16_t now);
    433 
    434     /**
    435      * Dispatch to the event handler defined in the mButtonConfig.
    436      *
    437      * This method will always be called and it's up to the user-provided
    438      * handler to ignore the events which aren't interesting.
    439      *
    440      * An alternative might be to provide a bitmask filter to select only
    441      * events which should are registered to trigger the event handler. For
    442      * example, add the following method:
    443      *
    444      * @code
    445      *    setEventSelection(uint8_t eventSelection) {
    446      *      mEventSelection = eventSelection;
    447      *    }
    448      * @endcode
    449      *
    450      * Set the event selector at setup():
    451      *
    452      * @code
    453      *    setEventSelection(kEventSelectPressed | kEventSelectReleased);
    454      * @endcode
    455      *
    456      * where
    457      *
    458      * @code
    459      *  kEventSelectPressed = (0x1 << kEventPressed);
    460      *  kEventSelectReleased = (0x1 << kEventReleased);
    461      *  ...
    462      * @endcode
    463      *
    464      * Then change the handleEvent() method to something like:
    465      *
    466      * @code
    467      * void handleEvent(uint8_t eventType) {
    468      *    if (mEventHandler && (eventSelections & (0x1 << eventType))) {
    469      *      handleEvent(this, eventType);
    470      *    }
    471      * }
    472      * @endcode
    473      *
    474      * But it is possible that the evaluation of the if-condition above takes
    475      * longer to evaluate than an empty function call, so we should do some
    476      * profiling before making this change.
    477      *
    478      * @param eventType the type of event given by the kEvent* constants
    479      */
    480     void handleEvent(uint8_t eventType);
    481 
    482     uint8_t mPin; // button pin number
    483     uint8_t mId; // identifier, e.g. an index into an array
    484 
    485     // Internal states of the button debouncing and event handling.
    486     // NOTE: We don't keep track of the lastDoubleClickTime, because we
    487     // don't support a TripleClicked event. That may change in the future.
    488     uint16_t mLastDebounceTime; // ms
    489     uint16_t mLastClickTime; // ms
    490     uint16_t mLastPressTime; // ms
    491     uint16_t mLastRepeatPressTime; // ms
    492 
    493     /** Internal flags. Bit masks are defined by the kFlag* constants. */
    494     uint8_t mFlags;
    495 
    496     /**
    497      * Last button state. This is a tri-state variable: LOW, HIGH or
    498      * kButtonStateUnknown.
    499      */
    500     uint8_t mLastButtonState;
    501 
    502     /** ButtonConfig associated with this button. */
    503     ButtonConfig* mButtonConfig;
    504 };
    505 
    506 }
    507 #endif