Skip to content

add type hints, restrict return type of monitored callable #39

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 3, 2022
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 42 additions & 31 deletions adafruit_debouncer.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,34 @@
from micropython import const
from adafruit_ticks import ticks_ms, ticks_diff

_DEBOUNCED_STATE = const(0x01)
_UNSTABLE_STATE = const(0x02)
_CHANGED_STATE = const(0x04)
try:
from typing import Callable, Optional, Union
except ImportError:
pass
from digitalio import DigitalInOut

_TICKS_PER_SEC = const(1000)
_DEBOUNCED_STATE: int = const(0x01)
_UNSTABLE_STATE: int = const(0x02)
_CHANGED_STATE: int = const(0x04)

_TICKS_PER_SEC: int = const(1000)


class Debouncer:
"""Debounce an input pin or an arbitrary predicate"""

def __init__(self, io_or_predicate, interval=0.010):
def __init__(
self,
io_or_predicate: Union[DigitalInOut, Callable[[], bool]],
interval: float = 0.010,
) -> None:
"""Make an instance.
:param DigitalInOut/function io_or_predicate: the DigitalIO or function to debounce
:param int interval: bounce threshold in seconds (default is 0.010, i.e. 10 milliseconds)
:param DigitalInOut/function io_or_predicate: the DigitalIO or
function that returns a boolean to debounce
:param float interval: bounce threshold in seconds (default is 0.010, i.e. 10 milliseconds)
"""
self.state = 0x00
if hasattr(io_or_predicate, "value"):
if isinstance(io_or_predicate, DigitalInOut):
self.function = lambda: io_or_predicate.value
else:
self.function = io_or_predicate
Expand All @@ -59,21 +70,21 @@ def __init__(self, io_or_predicate, interval=0.010):

# Could use the .interval setter, but pylint prefers that we explicitly
# set the real underlying attribute:
self._interval_ticks = interval * _TICKS_PER_SEC
self._interval_ticks = int(interval * _TICKS_PER_SEC)

def _set_state(self, bits):
def _set_state(self, bits: int) -> None:
self.state |= bits

def _unset_state(self, bits):
def _unset_state(self, bits: int) -> None:
self.state &= ~bits

def _toggle_state(self, bits):
def _toggle_state(self, bits: int) -> None:
self.state ^= bits

def _get_state(self, bits):
def _get_state(self, bits: int) -> bool:
return (self.state & bits) != 0

def update(self, new_state=None):
def update(self, new_state: Optional[int] = None) -> None:
"""Update the debouncer state. MUST be called frequently"""
now_ticks = ticks_ms()
self._unset_state(_CHANGED_STATE)
Expand All @@ -96,38 +107,38 @@ def update(self, new_state=None):
self._state_changed_ticks = now_ticks

@property
def interval(self):
def interval(self) -> float:
"""The debounce delay, in seconds"""
return self._interval_ticks / _TICKS_PER_SEC

@interval.setter
def interval(self, new_interval_s):
self._interval_ticks = new_interval_s * _TICKS_PER_SEC
def interval(self, new_interval_s: float) -> None:
self._interval_ticks = int(new_interval_s * _TICKS_PER_SEC)

@property
def value(self):
def value(self) -> bool:
"""Return the current debounced value."""
return self._get_state(_DEBOUNCED_STATE)

@property
def rose(self):
def rose(self) -> bool:
"""Return whether the debounced value went from low to high at the most recent update."""
return self._get_state(_DEBOUNCED_STATE) and self._get_state(_CHANGED_STATE)

@property
def fell(self):
def fell(self) -> bool:
"""Return whether the debounced value went from high to low at the most recent update."""
return (not self._get_state(_DEBOUNCED_STATE)) and self._get_state(
_CHANGED_STATE
)

@property
def last_duration(self):
def last_duration(self) -> float:
"""Return the number of seconds the state was stable prior to the most recent transition."""
return self._last_duration_ticks / _TICKS_PER_SEC

@property
def current_duration(self):
def current_duration(self) -> float:
"""Return the number of seconds since the most recent transition."""
return ticks_diff(ticks_ms(), self._state_changed_ticks) / _TICKS_PER_SEC

Expand All @@ -148,10 +159,10 @@ class Button(Debouncer):

def __init__(
self,
pin,
short_duration_ms=200,
long_duration_ms=500,
value_when_pressed=False,
pin: DigitalInOut,
short_duration_ms: int = 200,
long_duration_ms: int = 500,
value_when_pressed: bool = False,
**kwargs
):
self.short_duration_ms = short_duration_ms
Expand All @@ -165,20 +176,20 @@ def __init__(
super().__init__(pin, **kwargs)

@property
def pressed(self):
def pressed(self) -> bool:
"""Return whether the button was pressed or not at the last update."""
return (self.value_when_pressed and self.rose) or (
not self.value_when_pressed and self.fell
)

@property
def released(self):
def released(self) -> bool:
"""Return whether the button was release or not at the last update."""
return (self.value_when_pressed and self.fell) or (
not self.value_when_pressed and self.rose
)

def update(self, new_state=None):
def update(self, new_state: Optional[int] = None):
super().update(new_state)
if self.pressed:
self.last_change_ms = ticks_ms()
Expand Down Expand Up @@ -210,12 +221,12 @@ def update(self, new_state=None):
self.short_to_show = 0

@property
def short_count(self):
def short_count(self) -> int:
"""Return the number of short press if a series of short presses has
ended at the last update."""
return self.short_to_show

@property
def long_press(self):
def long_press(self) -> bool:
"""Return whether a long press has occured at the last update."""
return self.long_to_show