|
| 1 | +/** |
| 2 | + * @file OneButton.cpp |
| 3 | + * |
| 4 | + * @brief Library for detecting button clicks, doubleclicks and long press |
| 5 | + * pattern on a single button. |
| 6 | + * |
| 7 | + * @author Matthias Hertel, https://www.mathertel.de |
| 8 | + * @Copyright Copyright (c) by Matthias Hertel, https://www.mathertel.de. |
| 9 | + * |
| 10 | + * This work is licensed under a BSD style license. See |
| 11 | + * http://www.mathertel.de/License.aspx |
| 12 | + * |
| 13 | + * More information on: https://www.mathertel.de/Arduino/OneButtonLibrary.aspx |
| 14 | + * |
| 15 | + * Changelog: see OneButton.h |
| 16 | + */ |
| 17 | + |
| 18 | +#include "OneButton.h" |
| 19 | + |
| 20 | +// ----- Initialization and Default Values ----- |
| 21 | + |
| 22 | +/** |
| 23 | + * @brief Construct a new OneButton object but not (yet) initialize the IO pin. |
| 24 | + */ |
| 25 | +OneButton::OneButton() |
| 26 | +{ |
| 27 | + _pin = -1; |
| 28 | + // further initialization has moved to OneButton.h |
| 29 | +} |
| 30 | + |
| 31 | +/** |
| 32 | + * Initialize the OneButton library. |
| 33 | + * @param pin The pin to be used for input from a momentary button. |
| 34 | + * @param activeLow Set to true when the input level is LOW when the button is pressed, Default is true. |
| 35 | + * @param pullupActive Activate the internal pullup when available. Default is true. |
| 36 | + */ |
| 37 | +OneButton::OneButton(const int pin, const boolean activeLow, const bool pullupActive) |
| 38 | +{ |
| 39 | + // OneButton(); |
| 40 | + _pin = pin; |
| 41 | + |
| 42 | + if (activeLow) { |
| 43 | + // the button connects the input pin to GND when pressed. |
| 44 | + _buttonPressed = LOW; |
| 45 | + |
| 46 | + } else { |
| 47 | + // the button connects the input pin to VCC when pressed. |
| 48 | + _buttonPressed = HIGH; |
| 49 | + } // if |
| 50 | + |
| 51 | + if (pullupActive) { |
| 52 | + // use the given pin as input and activate internal PULLUP resistor. |
| 53 | + pinMode(pin, INPUT_PULLUP); |
| 54 | + } else { |
| 55 | + // use the given pin as input |
| 56 | + pinMode(pin, INPUT); |
| 57 | + } // if |
| 58 | +} // OneButton |
| 59 | + |
| 60 | + |
| 61 | +// explicitly set the number of millisec that have to pass by before a click is assumed stable. |
| 62 | +void OneButton::setDebounceTicks(const int ticks) |
| 63 | +{ |
| 64 | + _debounceTicks = ticks; |
| 65 | +} // setDebounceTicks |
| 66 | + |
| 67 | + |
| 68 | +// explicitly set the number of millisec that have to pass by before a click is detected. |
| 69 | +void OneButton::setClickTicks(const int ticks) |
| 70 | +{ |
| 71 | + _clickTicks = ticks; |
| 72 | +} // setClickTicks |
| 73 | + |
| 74 | + |
| 75 | +// explicitly set the number of millisec that have to pass by before a long button press is detected. |
| 76 | +void OneButton::setPressTicks(const int ticks) |
| 77 | +{ |
| 78 | + _pressTicks = ticks; |
| 79 | +} // setPressTicks |
| 80 | + |
| 81 | + |
| 82 | +// save function for click event |
| 83 | +void OneButton::attachClick(callbackFunction newFunction) |
| 84 | +{ |
| 85 | + _clickFunc = newFunction; |
| 86 | +} // attachClick |
| 87 | + |
| 88 | + |
| 89 | +// save function for parameterized click event |
| 90 | +void OneButton::attachClick(parameterizedCallbackFunction newFunction, void *parameter) |
| 91 | +{ |
| 92 | + _paramClickFunc = newFunction; |
| 93 | + _clickFuncParam = parameter; |
| 94 | +} // attachClick |
| 95 | + |
| 96 | + |
| 97 | +// save function for doubleClick event |
| 98 | +void OneButton::attachDoubleClick(callbackFunction newFunction) |
| 99 | +{ |
| 100 | + _doubleClickFunc = newFunction; |
| 101 | + _maxClicks = max(_maxClicks, 2); |
| 102 | +} // attachDoubleClick |
| 103 | + |
| 104 | + |
| 105 | +// save function for parameterized doubleClick event |
| 106 | +void OneButton::attachDoubleClick(parameterizedCallbackFunction newFunction, void *parameter) |
| 107 | +{ |
| 108 | + _paramDoubleClickFunc = newFunction; |
| 109 | + _doubleClickFuncParam = parameter; |
| 110 | + _maxClicks = max(_maxClicks, 2); |
| 111 | +} // attachDoubleClick |
| 112 | + |
| 113 | + |
| 114 | +// save function for multiClick event |
| 115 | +void OneButton::attachMultiClick(callbackFunction newFunction) |
| 116 | +{ |
| 117 | + _multiClickFunc = newFunction; |
| 118 | + _maxClicks = max(_maxClicks, 100); |
| 119 | +} // attachMultiClick |
| 120 | + |
| 121 | + |
| 122 | +// save function for parameterized MultiClick event |
| 123 | +void OneButton::attachMultiClick(parameterizedCallbackFunction newFunction, void *parameter) |
| 124 | +{ |
| 125 | + _paramMultiClickFunc = newFunction; |
| 126 | + _multiClickFuncParam = parameter; |
| 127 | + _maxClicks = max(_maxClicks, 100); |
| 128 | +} // attachMultiClick |
| 129 | + |
| 130 | + |
| 131 | +// save function for longPressStart event |
| 132 | +void OneButton::attachLongPressStart(callbackFunction newFunction) |
| 133 | +{ |
| 134 | + _longPressStartFunc = newFunction; |
| 135 | +} // attachLongPressStart |
| 136 | + |
| 137 | + |
| 138 | +// save function for parameterized longPressStart event |
| 139 | +void OneButton::attachLongPressStart(parameterizedCallbackFunction newFunction, void *parameter) |
| 140 | +{ |
| 141 | + _paramLongPressStartFunc = newFunction; |
| 142 | + _longPressStartFuncParam = parameter; |
| 143 | +} // attachLongPressStart |
| 144 | + |
| 145 | + |
| 146 | +// save function for longPressStop event |
| 147 | +void OneButton::attachLongPressStop(callbackFunction newFunction) |
| 148 | +{ |
| 149 | + _longPressStopFunc = newFunction; |
| 150 | +} // attachLongPressStop |
| 151 | + |
| 152 | + |
| 153 | +// save function for parameterized longPressStop event |
| 154 | +void OneButton::attachLongPressStop(parameterizedCallbackFunction newFunction, void *parameter) |
| 155 | +{ |
| 156 | + _paramLongPressStopFunc = newFunction; |
| 157 | + _longPressStopFuncParam = parameter; |
| 158 | +} // attachLongPressStop |
| 159 | + |
| 160 | + |
| 161 | +// save function for during longPress event |
| 162 | +void OneButton::attachDuringLongPress(callbackFunction newFunction) |
| 163 | +{ |
| 164 | + _duringLongPressFunc = newFunction; |
| 165 | +} // attachDuringLongPress |
| 166 | + |
| 167 | + |
| 168 | +// save function for parameterized during longPress event |
| 169 | +void OneButton::attachDuringLongPress(parameterizedCallbackFunction newFunction, void *parameter) |
| 170 | +{ |
| 171 | + _paramDuringLongPressFunc = newFunction; |
| 172 | + _duringLongPressFuncParam = parameter; |
| 173 | +} // attachDuringLongPress |
| 174 | + |
| 175 | + |
| 176 | +void OneButton::reset(void) |
| 177 | +{ |
| 178 | + _state = OneButton::OCS_INIT; |
| 179 | + _lastState = OneButton::OCS_INIT; |
| 180 | + _nClicks = 0; |
| 181 | + _startTime = 0; |
| 182 | +} |
| 183 | + |
| 184 | + |
| 185 | +// ShaggyDog ---- return number of clicks in any case: single or multiple clicks |
| 186 | +int OneButton::getNumberClicks(void) |
| 187 | +{ |
| 188 | + return _nClicks; |
| 189 | +} |
| 190 | + |
| 191 | + |
| 192 | +/** |
| 193 | + * @brief Check input of the configured pin and then advance the finite state |
| 194 | + * machine (FSM). |
| 195 | + */ |
| 196 | +void OneButton::tick(void) |
| 197 | +{ |
| 198 | + if (_pin >= 0) { |
| 199 | + tick(digitalRead(_pin) == _buttonPressed); |
| 200 | + } |
| 201 | +} |
| 202 | + |
| 203 | + |
| 204 | +/** |
| 205 | + * @brief Advance to a new state and save the last one to come back in cas of bouncing detection. |
| 206 | + */ |
| 207 | +void OneButton::_newState(stateMachine_t nextState) |
| 208 | +{ |
| 209 | + _lastState = _state; |
| 210 | + _state = nextState; |
| 211 | +} // _newState() |
| 212 | + |
| 213 | + |
| 214 | +/** |
| 215 | + * @brief Run the finite state machine (FSM) using the given level. |
| 216 | + */ |
| 217 | +void OneButton::tick(bool activeLevel) |
| 218 | +{ |
| 219 | + unsigned long now = millis(); // current (relative) time in msecs. |
| 220 | + unsigned long waitTime = (now - _startTime); |
| 221 | + |
| 222 | + // Implementation of the state machine |
| 223 | + switch (_state) { |
| 224 | + case OneButton::OCS_INIT: |
| 225 | + // waiting for level to become active. |
| 226 | + if (activeLevel) { |
| 227 | + _newState(OneButton::OCS_DOWN); |
| 228 | + _startTime = now; // remember starting time |
| 229 | + _nClicks = 0; |
| 230 | + } // if |
| 231 | + break; |
| 232 | + |
| 233 | + case OneButton::OCS_DOWN: |
| 234 | + // waiting for level to become inactive. |
| 235 | + |
| 236 | + if ((!activeLevel) && (waitTime < _debounceTicks)) { |
| 237 | + // button was released to quickly so I assume some bouncing. |
| 238 | + _newState(_lastState); |
| 239 | + |
| 240 | + } else if (!activeLevel) { |
| 241 | + _newState(OneButton::OCS_UP); |
| 242 | + _startTime = now; // remember starting time |
| 243 | + |
| 244 | + } else if ((activeLevel) && (waitTime > _pressTicks)) { |
| 245 | + if (_longPressStartFunc) _longPressStartFunc(); |
| 246 | + if (_paramLongPressStartFunc) _paramLongPressStartFunc(_longPressStartFuncParam); |
| 247 | + _newState(OneButton::OCS_PRESS); |
| 248 | + } // if |
| 249 | + break; |
| 250 | + |
| 251 | + case OneButton::OCS_UP: |
| 252 | + // level is inactive |
| 253 | + |
| 254 | + if ((activeLevel) && (waitTime < _debounceTicks)) { |
| 255 | + // button was pressed to quickly so I assume some bouncing. |
| 256 | + _newState(_lastState); // go back |
| 257 | + |
| 258 | + } else if (waitTime >= _debounceTicks) { |
| 259 | + // count as a short button down |
| 260 | + _nClicks++; |
| 261 | + _newState(OneButton::OCS_COUNT); |
| 262 | + } // if |
| 263 | + break; |
| 264 | + |
| 265 | + case OneButton::OCS_COUNT: |
| 266 | + // dobounce time is over, count clicks |
| 267 | + |
| 268 | + if (activeLevel) { |
| 269 | + // button is down again |
| 270 | + _newState(OneButton::OCS_DOWN); |
| 271 | + _startTime = now; // remember starting time |
| 272 | + |
| 273 | + } else if ((waitTime > _clickTicks) || (_nClicks == _maxClicks)) { |
| 274 | + // now we know how many clicks have been made. |
| 275 | + |
| 276 | + if (_nClicks == 1) { |
| 277 | + // this was 1 click only. |
| 278 | + if (_clickFunc) _clickFunc(); |
| 279 | + if (_paramClickFunc) _paramClickFunc(_clickFuncParam); |
| 280 | + |
| 281 | + } else if (_nClicks == 2) { |
| 282 | + // this was a 2 click sequence. |
| 283 | + if (_doubleClickFunc) _doubleClickFunc(); |
| 284 | + if (_paramDoubleClickFunc) _paramDoubleClickFunc(_doubleClickFuncParam); |
| 285 | + |
| 286 | + } else { |
| 287 | + // this was a multi click sequence. |
| 288 | + if (_multiClickFunc) _multiClickFunc(); |
| 289 | + if (_paramMultiClickFunc) _paramMultiClickFunc(_multiClickFuncParam); |
| 290 | + } // if |
| 291 | + |
| 292 | + reset(); |
| 293 | + } // if |
| 294 | + break; |
| 295 | + |
| 296 | + case OneButton::OCS_PRESS: |
| 297 | + // waiting for menu pin being release after long press. |
| 298 | + |
| 299 | + if (!activeLevel) { |
| 300 | + _newState(OneButton::OCS_PRESSEND); |
| 301 | + _startTime = now; |
| 302 | + |
| 303 | + } else { |
| 304 | + // still the button is pressed |
| 305 | + if (_duringLongPressFunc) _duringLongPressFunc(); |
| 306 | + if (_paramDuringLongPressFunc) _paramDuringLongPressFunc(_duringLongPressFuncParam); |
| 307 | + } // if |
| 308 | + break; |
| 309 | + |
| 310 | + case OneButton::OCS_PRESSEND: |
| 311 | + // button was released. |
| 312 | + |
| 313 | + if ((activeLevel) && (waitTime < _debounceTicks)) { |
| 314 | + // button was released to quickly so I assume some bouncing. |
| 315 | + _newState(_lastState); // go back |
| 316 | + |
| 317 | + } else if (waitTime >= _debounceTicks) { |
| 318 | + if (_longPressStopFunc) _longPressStopFunc(); |
| 319 | + if (_paramLongPressStopFunc) _paramLongPressStopFunc(_longPressStopFuncParam); |
| 320 | + reset(); |
| 321 | + } |
| 322 | + break; |
| 323 | + |
| 324 | + default: |
| 325 | + // unknown state detected -> reset state machine |
| 326 | + _newState(OneButton::OCS_INIT); |
| 327 | + break; |
| 328 | + } // if |
| 329 | + |
| 330 | +} // OneButton.tick() |
| 331 | + |
| 332 | + |
| 333 | +// end. |
0 commit comments