From a7e098eaa0a9e98ecbdcf1ce9056c2980fe50af4 Mon Sep 17 00:00:00 2001 From: Emily Charles Date: Mon, 2 May 2022 17:03:00 -0400 Subject: [PATCH 1/2] add type hints, restrict return type of monitored callable --- adafruit_debouncer.py | 73 +++++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 31 deletions(-) diff --git a/adafruit_debouncer.py b/adafruit_debouncer.py index 868a217..deb3326 100644 --- a/adafruit_debouncer.py +++ b/adafruit_debouncer.py @@ -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 @@ -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) @@ -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 @@ -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 @@ -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() @@ -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 From 455241472e2c3285d2330f9b621dbbafc4be3850 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 23 May 2022 12:25:57 -0500 Subject: [PATCH 2/2] use new protocol. add return nones --- adafruit_debouncer.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/adafruit_debouncer.py b/adafruit_debouncer.py index deb3326..67037f7 100644 --- a/adafruit_debouncer.py +++ b/adafruit_debouncer.py @@ -33,9 +33,9 @@ try: from typing import Callable, Optional, Union + from circuitpython_typing.io import ROValueIO except ImportError: pass -from digitalio import DigitalInOut _DEBOUNCED_STATE: int = const(0x01) _UNSTABLE_STATE: int = const(0x02) @@ -49,7 +49,7 @@ class Debouncer: def __init__( self, - io_or_predicate: Union[DigitalInOut, Callable[[], bool]], + io_or_predicate: Union[ROValueIO, Callable[[], bool]], interval: float = 0.010, ) -> None: """Make an instance. @@ -58,7 +58,7 @@ def __init__( :param float interval: bounce threshold in seconds (default is 0.010, i.e. 10 milliseconds) """ self.state = 0x00 - if isinstance(io_or_predicate, DigitalInOut): + if hasattr(io_or_predicate, "value"): self.function = lambda: io_or_predicate.value else: self.function = io_or_predicate @@ -113,7 +113,7 @@ def interval(self) -> float: @interval.setter def interval(self, new_interval_s: float) -> None: - self._interval_ticks = int(new_interval_s * _TICKS_PER_SEC) + self._interval_ticks = new_interval_s * _TICKS_PER_SEC @property def value(self) -> bool: @@ -159,12 +159,12 @@ class Button(Debouncer): def __init__( self, - pin: DigitalInOut, + pin: Union[ROValueIO, Callable[[], bool]], short_duration_ms: int = 200, long_duration_ms: int = 500, value_when_pressed: bool = False, **kwargs - ): + ) -> None: self.short_duration_ms = short_duration_ms self.long_duration_ms = long_duration_ms self.value_when_pressed = value_when_pressed @@ -189,7 +189,7 @@ def released(self) -> bool: not self.value_when_pressed and self.rose ) - def update(self, new_state: Optional[int] = None): + def update(self, new_state: Optional[int] = None) -> None: super().update(new_state) if self.pressed: self.last_change_ms = ticks_ms()