/* * Paul's Button Library * * Handles recognition of button presses, short, long, double-click and special sequences * Used to send specific commands to the ESP8 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "PButton.h" // Constructor PButton::PButton() { // Initialization of default properties Debounce_ = 40; // Debounce period to prevent flickering when pressing or releasing the button (in ms) DblClickDelay_ = 250; // Max period between clicks for a double click event (in ms) LongPressDelay_ = 9500; // Hold period for a long press event (in ms) VLongPressDelay_ = 20000; // Hold period for a very long press event (in ms) cb_onClick = nullptr; cb_onDblClick = nullptr; cb_onLongPress = nullptr; cb_onVLongPress = nullptr; // Initialization of variables state_ = true; // Value read from button lastState_ = true; // Last value of button state dblClickWaiting_ = false; // whether we're waiting for a double click (down) dblClickOnNextUp_ = false; // whether to register a double click on next release, or whether to wait and click singleClickOK_ = true; // whether it's OK to do a single click (default is true) downTime_ = -1; // time the button was pressed down upTime_ = -1; // time the button was released ignoreUP_ = true; // whether to ignore the button release because the click+hold was triggered, changed from false for ESP32 waitForUP_ = false; // when held, whether to wait for the up event longPressHappened_ = false; // whether or not the hold event happened already vLongPressHappened_ = false; // whether or not the long hold event happened already buttonBusy_ = false; // idle pin_ = 255; // undefined } bool PButton::init(uint8_t pin, bool pullMode) { pin_ = pin; pullMode_ = pullMode; // 1=HIGH (pullup) 0=LOW (pulldown) #if defined(ESP32) #if CONFIG_IDF_TARGET_ESP32 if (pin_ == 34 || pin_ == 35 || pin_ == 36 || pin_ == 39) { pinMode(pin_, INPUT); } else { pinMode(pin_, pullMode ? INPUT_PULLUP : INPUT_PULLDOWN); } #else pinMode(pin_, pullMode ? INPUT_PULLUP : INPUT_PULLDOWN); #endif #else // esp8266 and standalone pinMode(pin_, pullMode ? INPUT_PULLUP : INPUT); #endif enabled_ = (digitalRead(pin_) == pullMode); // see if a button is connected return enabled_; } void PButton::onClick(uint16_t t, buttonEventHandler handler) { Debounce_ = t; // Debounce period to prevent flickering when pressing or releasing the button (in ms) cb_onClick = handler; } void PButton::onDblClick(uint16_t t, buttonEventHandler handler) { DblClickDelay_ = t; // Max period between clicks for a double click event (in ms) cb_onDblClick = handler; } void PButton::onLongPress(uint16_t t, buttonEventHandler handler) { LongPressDelay_ = t; // Hold period for a long press event (in ms) cb_onLongPress = handler; } void PButton::onVLongPress(uint16_t t, buttonEventHandler handler) { VLongPressDelay_ = t; // Hold period for a very long press event (in ms) cb_onVLongPress = handler; } bool PButton::check(void) { if (!enabled_ || pin_ == 255) { return false; } int resultEvent = 0; long millisRes = millis(); // make sure the pin is still input #if defined(ESP32) #if CONFIG_IDF_TARGET_ESP32 if (pin_ == 34 || pin_ == 35 || pin_ == 36 || pin_ == 39) { pinMode(pin_, INPUT); } else { pinMode(pin_, pullMode_ ? INPUT_PULLUP : INPUT_PULLDOWN); } #else pinMode(pin_, pullMode_ ? INPUT_PULLUP : INPUT_PULLDOWN); #endif #else // esp8266 and standalone pinMode(pin_, pullMode_ ? INPUT_PULLUP : INPUT); #endif state_ = digitalRead(pin_) == pullMode_; // Button pressed down if (state_ != pullMode_ && lastState_ == pullMode_ && (millisRes - upTime_) > Debounce_) { downTime_ = millisRes; ignoreUP_ = false; waitForUP_ = false; singleClickOK_ = true; longPressHappened_ = false; vLongPressHappened_ = false; if ((millisRes - upTime_) < DblClickDelay_ && dblClickOnNextUp_ == false && dblClickWaiting_ == true) { dblClickOnNextUp_ = true; } else { dblClickOnNextUp_ = false; } dblClickWaiting_ = false; buttonBusy_ = true; // something is happening so we'll wait and see what action it is } // Button released else if (state_ == pullMode_ && lastState_ != pullMode_ && (millisRes - downTime_) > Debounce_) { if (ignoreUP_ == false) { upTime_ = millisRes; if (dblClickOnNextUp_ == false) { dblClickWaiting_ = true; } else { resultEvent = 2; dblClickOnNextUp_ = false; dblClickWaiting_ = false; singleClickOK_ = false; } } } // Test for normal click event: DblClickDelay expired if (state_ == pullMode_ && (millisRes - upTime_) >= DblClickDelay_ && dblClickWaiting_ == true && dblClickOnNextUp_ == false && singleClickOK_ == true && resultEvent != 2) { resultEvent = 1; dblClickWaiting_ = false; } // added code: raise OnLongPress event when the button is released if (state_ == pullMode_ && longPressHappened_ && !vLongPressHappened_) { resultEvent = 3; longPressHappened_ = false; } // added code: raise OnVLongPress event when the button is released, only for pin 0 if (state_ == pullMode_ && vLongPressHappened_) { resultEvent = 4; vLongPressHappened_ = false; longPressHappened_ = false; } // Test for hold if (state_ != pullMode_ && (millisRes - downTime_) >= LongPressDelay_) { // Trigger "normal" hold if (longPressHappened_ == false) { // resultEvent = 3; waitForUP_ = true; ignoreUP_ = true; dblClickOnNextUp_ = false; dblClickWaiting_ = false; // _downTime = millis(); longPressHappened_ = true; } // Trigger "long" hold if ((millisRes - downTime_) >= VLongPressDelay_) { if (vLongPressHappened_ == false) { // raise OnVLongPress, only for pin 0 wait for release if (pin_) { resultEvent = 4; } vLongPressHappened_ = true; // longPressHappened_ = false; } } } lastState_ = state_; if (resultEvent == 1 && cb_onClick) { cb_onClick(*this); } else if (resultEvent == 2 && cb_onDblClick) { cb_onDblClick(*this); } else if (resultEvent == 3 && cb_onLongPress) { cb_onLongPress(*this); } else if (resultEvent == 4 && cb_onVLongPress) { cb_onVLongPress(*this); } // if any action has been preformed we can stop waiting, and become idle if (resultEvent >= 1 && resultEvent <= 4) { buttonBusy_ = false; } return buttonBusy_; }