Adafruit_Learning_System_Gu.../CLUE_Light_Painter/richbutton.py
2022-02-18 17:07:25 -05:00

98 lines
4.1 KiB
Python
Executable file

# SPDX-FileCopyrightText: 2019 Phillip Burgess for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""Glorified button class with debounced tap, double-tap, hold and release"""
# pylint: disable=import-error
from time import monotonic
from digitalio import DigitalInOut, Direction, Pull
# pylint: disable=too-many-instance-attributes, too-few-public-methods
class RichButton:
"""
A button class handling more than basic taps: adds debounced tap,
double-tap, hold and release.
"""
TAP = 0
DOUBLE_TAP = 1
HOLD = 2
RELEASE = 3
def __init__(self, pin, *, debounce_period=0.05, hold_period=0.75,
double_tap_period=0.3):
"""
Constructor for RichButton class.
Arguments:
pin (int) : Digital pin connected to button
(opposite leg to GND). Pin will be
configured as INPUT with pullup.
Keyword arguments:
debounce_period (float) : interval, in seconds, in which multiple
presses are ignored (debounced)
(default = 0.05 seconds).
hold_period (float) : interval, in seconds, when a held
button will return a HOLD value from
the action() function (default = 0.75).
double_tap_period (float): interval, in seconds, when a double-
tap can be sensed (vs returning
a second single-tap) (default = 0.3).
Longer double-tap periods will make
single-taps less responsive.
"""
self.in_out = DigitalInOut(pin)
self.in_out.direction = Direction.INPUT
self.in_out.pull = Pull.UP
self._debounce_period = debounce_period
self._hold_period = hold_period
self._double_tap_period = double_tap_period
self._holding = False
self._tap_time = -self._double_tap_period
self._press_time = monotonic()
self._prior_state = self.in_out.value
def action(self):
"""
Process pin input. This MUST be called frequently for debounce, etc.
to work, since interrupts are not available.
Returns:
None, TAP, DOUBLE_TAP, HOLD or RELEASE.
"""
new_state = self.in_out.value
if new_state != self._prior_state:
# Button state changed since last call
self._prior_state = new_state
if not new_state:
# Button initially pressed (TAP not returned until debounce)
self._press_time = monotonic()
else:
# Button initially released
if self._holding:
# Button released after hold
self._holding = False
return self.RELEASE
if (monotonic() - self._press_time) >= self._debounce_period:
# Button released after valid debounce time
if monotonic() - self._tap_time < self._double_tap_period:
# Followed another recent tap, reset double timer
self._tap_time = 0
return self.DOUBLE_TAP
# Else regular debounced release, maybe 1st tap, keep time
self._tap_time = monotonic()
else:
# Button is in same state as last call
if self._prior_state:
# Is not pressed
if (self._tap_time > 0 and
(monotonic() - self._tap_time) > self._double_tap_period):
# Enough time since last tap that it's not a double
self._tap_time = 0
return self.TAP
elif (not self._holding and
(monotonic() - self._press_time) >= self._hold_period):
# Is pressed, and has been for the holding period
self._holding = True
return self.HOLD
return None