333 lines
8.7 KiB
C++
333 lines
8.7 KiB
C++
/**
|
|
* @file OneButton.cpp
|
|
*
|
|
* @brief Library for detecting button clicks, doubleclicks and long press
|
|
* pattern on a single button.
|
|
*
|
|
* @author Matthias Hertel, https://www.mathertel.de
|
|
* @Copyright Copyright (c) by Matthias Hertel, https://www.mathertel.de.
|
|
*
|
|
* This work is licensed under a BSD style license. See
|
|
* http://www.mathertel.de/License.aspx
|
|
*
|
|
* More information on: https://www.mathertel.de/Arduino/OneButtonLibrary.aspx
|
|
*
|
|
* Changelog: see OneButton.h
|
|
*/
|
|
|
|
#include "OneButton.h"
|
|
|
|
// ----- Initialization and Default Values -----
|
|
|
|
/**
|
|
* @brief Construct a new OneButton object but not (yet) initialize the IO pin.
|
|
*/
|
|
OneButton::OneButton()
|
|
{
|
|
_pin = -1;
|
|
// further initialization has moved to OneButton.h
|
|
}
|
|
|
|
/**
|
|
* Initialize the OneButton library.
|
|
* @param pin The pin to be used for input from a momentary button.
|
|
* @param activeLow Set to true when the input level is LOW when the button is pressed, Default is true.
|
|
* @param pullupActive Activate the internal pullup when available. Default is true.
|
|
*/
|
|
OneButton::OneButton(const int pin, const boolean activeLow, const bool pullupActive)
|
|
{
|
|
// OneButton();
|
|
_pin = pin;
|
|
|
|
if (activeLow) {
|
|
// the button connects the input pin to GND when pressed.
|
|
_buttonPressed = LOW;
|
|
|
|
} else {
|
|
// the button connects the input pin to VCC when pressed.
|
|
_buttonPressed = HIGH;
|
|
} // if
|
|
|
|
if (pullupActive) {
|
|
// use the given pin as input and activate internal PULLUP resistor.
|
|
pinMode(pin, INPUT_PULLUP);
|
|
} else {
|
|
// use the given pin as input
|
|
pinMode(pin, INPUT);
|
|
} // if
|
|
} // OneButton
|
|
|
|
|
|
// explicitly set the number of millisec that have to pass by before a click is assumed stable.
|
|
void OneButton::setDebounceTicks(const int ticks)
|
|
{
|
|
_debounceTicks = ticks;
|
|
} // setDebounceTicks
|
|
|
|
|
|
// explicitly set the number of millisec that have to pass by before a click is detected.
|
|
void OneButton::setClickTicks(const int ticks)
|
|
{
|
|
_clickTicks = ticks;
|
|
} // setClickTicks
|
|
|
|
|
|
// explicitly set the number of millisec that have to pass by before a long button press is detected.
|
|
void OneButton::setPressTicks(const int ticks)
|
|
{
|
|
_pressTicks = ticks;
|
|
} // setPressTicks
|
|
|
|
|
|
// save function for click event
|
|
void OneButton::attachClick(callbackFunction newFunction)
|
|
{
|
|
_clickFunc = newFunction;
|
|
} // attachClick
|
|
|
|
|
|
// save function for parameterized click event
|
|
void OneButton::attachClick(parameterizedCallbackFunction newFunction, void *parameter)
|
|
{
|
|
_paramClickFunc = newFunction;
|
|
_clickFuncParam = parameter;
|
|
} // attachClick
|
|
|
|
|
|
// save function for doubleClick event
|
|
void OneButton::attachDoubleClick(callbackFunction newFunction)
|
|
{
|
|
_doubleClickFunc = newFunction;
|
|
_maxClicks = max(_maxClicks, 2);
|
|
} // attachDoubleClick
|
|
|
|
|
|
// save function for parameterized doubleClick event
|
|
void OneButton::attachDoubleClick(parameterizedCallbackFunction newFunction, void *parameter)
|
|
{
|
|
_paramDoubleClickFunc = newFunction;
|
|
_doubleClickFuncParam = parameter;
|
|
_maxClicks = max(_maxClicks, 2);
|
|
} // attachDoubleClick
|
|
|
|
|
|
// save function for multiClick event
|
|
void OneButton::attachMultiClick(callbackFunction newFunction)
|
|
{
|
|
_multiClickFunc = newFunction;
|
|
_maxClicks = max(_maxClicks, 100);
|
|
} // attachMultiClick
|
|
|
|
|
|
// save function for parameterized MultiClick event
|
|
void OneButton::attachMultiClick(parameterizedCallbackFunction newFunction, void *parameter)
|
|
{
|
|
_paramMultiClickFunc = newFunction;
|
|
_multiClickFuncParam = parameter;
|
|
_maxClicks = max(_maxClicks, 100);
|
|
} // attachMultiClick
|
|
|
|
|
|
// save function for longPressStart event
|
|
void OneButton::attachLongPressStart(callbackFunction newFunction)
|
|
{
|
|
_longPressStartFunc = newFunction;
|
|
} // attachLongPressStart
|
|
|
|
|
|
// save function for parameterized longPressStart event
|
|
void OneButton::attachLongPressStart(parameterizedCallbackFunction newFunction, void *parameter)
|
|
{
|
|
_paramLongPressStartFunc = newFunction;
|
|
_longPressStartFuncParam = parameter;
|
|
} // attachLongPressStart
|
|
|
|
|
|
// save function for longPressStop event
|
|
void OneButton::attachLongPressStop(callbackFunction newFunction)
|
|
{
|
|
_longPressStopFunc = newFunction;
|
|
} // attachLongPressStop
|
|
|
|
|
|
// save function for parameterized longPressStop event
|
|
void OneButton::attachLongPressStop(parameterizedCallbackFunction newFunction, void *parameter)
|
|
{
|
|
_paramLongPressStopFunc = newFunction;
|
|
_longPressStopFuncParam = parameter;
|
|
} // attachLongPressStop
|
|
|
|
|
|
// save function for during longPress event
|
|
void OneButton::attachDuringLongPress(callbackFunction newFunction)
|
|
{
|
|
_duringLongPressFunc = newFunction;
|
|
} // attachDuringLongPress
|
|
|
|
|
|
// save function for parameterized during longPress event
|
|
void OneButton::attachDuringLongPress(parameterizedCallbackFunction newFunction, void *parameter)
|
|
{
|
|
_paramDuringLongPressFunc = newFunction;
|
|
_duringLongPressFuncParam = parameter;
|
|
} // attachDuringLongPress
|
|
|
|
|
|
void OneButton::reset(void)
|
|
{
|
|
_state = OneButton::OCS_INIT;
|
|
_lastState = OneButton::OCS_INIT;
|
|
_nClicks = 0;
|
|
_startTime = 0;
|
|
}
|
|
|
|
|
|
// ShaggyDog ---- return number of clicks in any case: single or multiple clicks
|
|
int OneButton::getNumberClicks(void)
|
|
{
|
|
return _nClicks;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Check input of the configured pin and then advance the finite state
|
|
* machine (FSM).
|
|
*/
|
|
void OneButton::tick(void)
|
|
{
|
|
if (_pin >= 0) {
|
|
tick(digitalRead(_pin) == _buttonPressed);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Advance to a new state and save the last one to come back in cas of bouncing detection.
|
|
*/
|
|
void OneButton::_newState(stateMachine_t nextState)
|
|
{
|
|
_lastState = _state;
|
|
_state = nextState;
|
|
} // _newState()
|
|
|
|
|
|
/**
|
|
* @brief Run the finite state machine (FSM) using the given level.
|
|
*/
|
|
void OneButton::tick(bool activeLevel)
|
|
{
|
|
unsigned long now = millis(); // current (relative) time in msecs.
|
|
unsigned long waitTime = (now - _startTime);
|
|
|
|
// Implementation of the state machine
|
|
switch (_state) {
|
|
case OneButton::OCS_INIT:
|
|
// waiting for level to become active.
|
|
if (activeLevel) {
|
|
_newState(OneButton::OCS_DOWN);
|
|
_startTime = now; // remember starting time
|
|
_nClicks = 0;
|
|
} // if
|
|
break;
|
|
|
|
case OneButton::OCS_DOWN:
|
|
// waiting for level to become inactive.
|
|
|
|
if ((!activeLevel) && (waitTime < _debounceTicks)) {
|
|
// button was released to quickly so I assume some bouncing.
|
|
_newState(_lastState);
|
|
|
|
} else if (!activeLevel) {
|
|
_newState(OneButton::OCS_UP);
|
|
_startTime = now; // remember starting time
|
|
|
|
} else if ((activeLevel) && (waitTime > _pressTicks)) {
|
|
if (_longPressStartFunc) _longPressStartFunc();
|
|
if (_paramLongPressStartFunc) _paramLongPressStartFunc(_longPressStartFuncParam);
|
|
_newState(OneButton::OCS_PRESS);
|
|
} // if
|
|
break;
|
|
|
|
case OneButton::OCS_UP:
|
|
// level is inactive
|
|
|
|
if ((activeLevel) && (waitTime < _debounceTicks)) {
|
|
// button was pressed to quickly so I assume some bouncing.
|
|
_newState(_lastState); // go back
|
|
|
|
} else if (waitTime >= _debounceTicks) {
|
|
// count as a short button down
|
|
_nClicks++;
|
|
_newState(OneButton::OCS_COUNT);
|
|
} // if
|
|
break;
|
|
|
|
case OneButton::OCS_COUNT:
|
|
// dobounce time is over, count clicks
|
|
|
|
if (activeLevel) {
|
|
// button is down again
|
|
_newState(OneButton::OCS_DOWN);
|
|
_startTime = now; // remember starting time
|
|
|
|
} else if ((waitTime > _clickTicks) || (_nClicks == _maxClicks)) {
|
|
// now we know how many clicks have been made.
|
|
|
|
if (_nClicks == 1) {
|
|
// this was 1 click only.
|
|
if (_clickFunc) _clickFunc();
|
|
if (_paramClickFunc) _paramClickFunc(_clickFuncParam);
|
|
|
|
} else if (_nClicks == 2) {
|
|
// this was a 2 click sequence.
|
|
if (_doubleClickFunc) _doubleClickFunc();
|
|
if (_paramDoubleClickFunc) _paramDoubleClickFunc(_doubleClickFuncParam);
|
|
|
|
} else {
|
|
// this was a multi click sequence.
|
|
if (_multiClickFunc) _multiClickFunc();
|
|
if (_paramMultiClickFunc) _paramMultiClickFunc(_doubleClickFuncParam);
|
|
} // if
|
|
|
|
reset();
|
|
} // if
|
|
break;
|
|
|
|
case OneButton::OCS_PRESS:
|
|
// waiting for menu pin being release after long press.
|
|
|
|
if (!activeLevel) {
|
|
_newState(OneButton::OCS_PRESSEND);
|
|
_startTime = now;
|
|
|
|
} else {
|
|
// still the button is pressed
|
|
if (_duringLongPressFunc) _duringLongPressFunc();
|
|
if (_paramDuringLongPressFunc) _paramDuringLongPressFunc(_duringLongPressFuncParam);
|
|
} // if
|
|
break;
|
|
|
|
case OneButton::OCS_PRESSEND:
|
|
// button was released.
|
|
|
|
if ((activeLevel) && (waitTime < _debounceTicks)) {
|
|
// button was released to quickly so I assume some bouncing.
|
|
_newState(_lastState); // go back
|
|
|
|
} else if (waitTime >= _debounceTicks) {
|
|
if (_longPressStopFunc) _longPressStopFunc();
|
|
if (_paramLongPressStopFunc) _paramLongPressStopFunc(_longPressStopFuncParam);
|
|
reset();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// nothing to do.
|
|
break;
|
|
} // if
|
|
|
|
} // OneButton.tick()
|
|
|
|
|
|
// end.
|