From 755df2284821392acfb14aae71b531609ba59f71 Mon Sep 17 00:00:00 2001 From: EGJ-Moorington Date: Mon, 2 Sep 2024 12:38:38 +0200 Subject: [PATCH 1/3] Added constants to `ButtonInput` for parameter `action`. Closes #11 --- button_handler.py | 44 +++++++++---------- examples/button_handler_doublebutton.py | 16 +++---- examples/button_handler_singlebutton.py | 10 ++--- tests/test_button_handler.py | 56 ++++++++++++------------- 4 files changed, 61 insertions(+), 65 deletions(-) diff --git a/button_handler.py b/button_handler.py index b8077ac..769a9f3 100644 --- a/button_handler.py +++ b/button_handler.py @@ -41,7 +41,7 @@ def ticks_ms() -> int: except ImportError: pass -__version__ = "2.0.0" +__version__ = "3.0.0" __repo__ = "https://github.com/EGJ-Moorington/CircuitPython_Button_Handler.git" _TICKS_PERIOD = 1 << 29 @@ -301,9 +301,14 @@ def _is_held(self, current_time: int) -> bool: class ButtonInput: """Defines a button's input's characteristics.""" + SHORT_PRESS = 1 + DOUBLE_PRESS = 2 + LONG_PRESS = "L" + HOLD = "H" + def __init__( self, - action: Union[Literal["SHORT_PRESS", "LONG_PRESS", "HOLD", "DOUBLE_PRESS"], str], + action: Union[int, str], button_number: int = 0, callback: Callable[[], None] = lambda: None, timestamp: int = 0, @@ -370,22 +375,15 @@ def action(self): return self._action @action.setter - def action( - self, action: Union[Literal["SHORT_PRESS", "LONG_PRESS", "HOLD", "DOUBLE_PRESS"], str] - ): - if action in {"SHORT_PRESS", "LONG_PRESS", "HOLD"}: + def action(self, action: Union[int, str]): + if action in {ButtonInput.LONG_PRESS, ButtonInput.HOLD}: self._action = action return try: - if action == "DOUBLE_PRESS": - action = "2_MULTI_PRESS" - if not action.endswith("_MULTI_PRESS"): + if not isinstance(action, int): raise ValueError - num = int(action.split("_")[0]) - if num < 1: + if action < 1: raise ValueError - if num == 1: - action = "SHORT_PRESS" self._action = action except ValueError: raise ValueError(f"Invalid action: {action}.") @@ -562,15 +560,13 @@ def _handle_buttons(self) -> set[ButtonInput]: current_time = ticks_ms() for button in self._buttons: if button._is_held(current_time): - inputs.add(ButtonInput("HOLD", button.button_number, timestamp=current_time)) + inputs.add( + ButtonInput(ButtonInput.HOLD, button.button_number, timestamp=current_time) + ) else: num = button._check_multi_press_timeout(current_time) if num: - inputs.add( - ButtonInput( - f"{num}_MULTI_PRESS", button.button_number, timestamp=current_time - ) - ) + inputs.add(ButtonInput(num, button.button_number, timestamp=current_time)) return inputs def _handle_event(self, event: Event) -> Union[ButtonInput, None]: @@ -598,17 +594,21 @@ def _handle_event(self, event: Event) -> Union[ButtonInput, None]: < button.long_press_threshold ): # Short press if not button.enable_multi_press: - input_ = ButtonInput("SHORT_PRESS", event.key_number, timestamp=event.timestamp) + input_ = ButtonInput( + ButtonInput.SHORT_PRESS, event.key_number, timestamp=event.timestamp + ) elif button._press_count == button.max_multi_press: input_ = ButtonInput( - f"{button.max_multi_press}_MULTI_PRESS", + button.max_multi_press, event.key_number, timestamp=event.timestamp, ) else: # More short presses could follow return None else: - input_ = ButtonInput("LONG_PRESS", event.key_number, timestamp=event.timestamp) + input_ = ButtonInput( + ButtonInput.LONG_PRESS, event.key_number, timestamp=event.timestamp + ) button._is_holding = False button._last_press_time = None button._press_count = 0 diff --git a/examples/button_handler_doublebutton.py b/examples/button_handler_doublebutton.py index eac490d..618a670 100644 --- a/examples/button_handler_doublebutton.py +++ b/examples/button_handler_doublebutton.py @@ -102,14 +102,14 @@ def long_press_1_holding_0(): config = ButtonInitConfig(max_multi_press=3) scanner = Keys([board.D9, board.A2], value_when_pressed=False) callback_inputs = { - ButtonInput("DOUBLE_PRESS", 0, double_press_0), - ButtonInput("DOUBLE_PRESS", 1, double_press_1), - ButtonInput("3_MULTI_PRESS", 0, triple_press_0), - ButtonInput("3_MULTI_PRESS", 1, triple_press_1), - ButtonInput("SHORT_PRESS", 0, short_press_0), - ButtonInput("SHORT_PRESS", 1, short_press_1), - ButtonInput("LONG_PRESS", 0, long_press_0), - ButtonInput("LONG_PRESS", 1, long_press_1), + ButtonInput(ButtonInput.DOUBLE_PRESS, 0, double_press_0), + ButtonInput(ButtonInput.DOUBLE_PRESS, 1, double_press_1), + ButtonInput(3, 0, triple_press_0), + ButtonInput(3, 1, triple_press_1), + ButtonInput(ButtonInput.SHORT_PRESS, 0, short_press_0), + ButtonInput(ButtonInput.SHORT_PRESS, 1, short_press_1), + ButtonInput(ButtonInput.LONG_PRESS, 0, long_press_0), + ButtonInput(ButtonInput.LONG_PRESS, 1, long_press_1), } button_handler = ButtonHandler(scanner.events, callback_inputs, 2, {0: config, 1: config}) diff --git a/examples/button_handler_singlebutton.py b/examples/button_handler_singlebutton.py index 5f6668a..50615a3 100644 --- a/examples/button_handler_singlebutton.py +++ b/examples/button_handler_singlebutton.py @@ -8,7 +8,7 @@ import board from keypad import Keys -from button_handler import ButtonHandler, ButtonInput +from button_handler import ButtonHandler, ButtonInitConfig, ButtonInput def double_press(): @@ -28,10 +28,10 @@ def hold(): actions = { - ButtonInput("DOUBLE_PRESS", callback=double_press), - ButtonInput("SHORT_PRESS", callback=short_press), - ButtonInput("LONG_PRESS", callback=long_press), - ButtonInput("HOLD", callback=hold), + ButtonInput(ButtonInput.DOUBLE_PRESS, callback=double_press), + ButtonInput(ButtonInput.SHORT_PRESS, callback=short_press), + ButtonInput(ButtonInput.LONG_PRESS, callback=long_press), + ButtonInput(ButtonInput.HOLD, callback=hold), } scanner = Keys([board.D9], value_when_pressed=False) diff --git a/tests/test_button_handler.py b/tests/test_button_handler.py index 815d581..6e449c9 100644 --- a/tests/test_button_handler.py +++ b/tests/test_button_handler.py @@ -83,12 +83,12 @@ def button(config) -> Button: @pytest.fixture def input_() -> ButtonInput: - return ButtonInput("SHORT_PRESS", 3, callback, timestamp=time) + return ButtonInput(ButtonInput.SHORT_PRESS, 3, callback, timestamp=time) @pytest.fixture def inputs(input_) -> set[ButtonInput]: - return {input_, ButtonInput("LONG_PRESS", callback=callback)} + return {input_, ButtonInput(ButtonInput.LONG_PRESS, callback=callback)} @pytest.fixture @@ -141,34 +141,30 @@ def test__is_held(self, time, button: Button): class TestButtonInput: def test_init(self, input_): - assert input_.action == "SHORT_PRESS" + assert input_.action == ButtonInput.SHORT_PRESS assert input_.button_number == 3 assert input_.callback() == None assert input_.timestamp == time with pytest.raises(ValueError): - input_.action = "0_MULTI_PRESS" + input_.action = 0 def test_valid_action(self, input_): assert input_._action == input_.action - input_.action = "LONG_PRESS" - assert input_.action == "LONG_PRESS" - input_.action = "DOUBLE_PRESS" - assert input_.action == "2_MULTI_PRESS" - input_.action = "1_MULTI_PRESS" - assert input_.action == "SHORT_PRESS" - input_.action = "3_MULTI_PRESS" - assert input_.action == "3_MULTI_PRESS" + input_.action = ButtonInput.LONG_PRESS + assert input_.action == ButtonInput.LONG_PRESS + input_.action = 3 + assert input_.action == 3 @pytest.mark.parametrize( "action", { - "0_MULTI_PRESS", - "_MULTI_PRESS", - "w_MULTI_PRESS", - "_MULTI_PRESS_", - "-1_MULTI_PRESS", - "3.0_MULTI_PRESS", + 0, + "", + "w", + None, + -1, + 3.0, }, ) def test_invalid_action(self, input_, action): @@ -176,11 +172,11 @@ def test_invalid_action(self, input_, action): input_.action = action def test_dunder(self, input_: ButtonInput, time): - assert input_ == ButtonInput("SHORT_PRESS", 3, timestamp=time * 2) + assert input_ == ButtonInput(ButtonInput.SHORT_PRESS, 3, timestamp=time * 2) assert hash(input_) == hash((input_.action, input_.button_number)) - assert str(input_) == "SHORT_PRESS on button 3" + assert str(input_) == f"{ButtonInput.SHORT_PRESS} on button 3" class TestButtonHandler: @@ -212,7 +208,7 @@ def test_invalid_button_amount(self, amount, event_queue): def test__call_callbacks(self, inputs, button_handler: ButtonHandler): global call_amount # noqa: PLW0603 call_amount = 0 - inputs.add(ButtonInput("HOLD")) + inputs.add(ButtonInput(ButtonInput.HOLD)) button_handler._call_callbacks(inputs) assert call_amount == 2 @@ -224,13 +220,13 @@ def test__handle_buttons(self, button_handler: ButtonHandler, time): button._is_pressed = True button._press_start_time = time - button.long_press_threshold * 2 inputs = button_handler._handle_buttons() - assert inputs == {ButtonInput("HOLD", 2)} + assert inputs == {ButtonInput(ButtonInput.HOLD, 2)} button = button_handler.buttons[3] button._press_count = 3 button._last_press_time = time - button.multi_press_interval * 2 inputs = button_handler._handle_buttons() - assert inputs == {ButtonInput("3_MULTI_PRESS", 3)} + assert inputs == {ButtonInput(3, 3)} def test__handle_event(self, time, button_handler: ButtonHandler): button = button_handler.buttons[1] @@ -247,16 +243,16 @@ def test__handle_event(self, time, button_handler: ButtonHandler): button._press_start_time = time - button.long_press_threshold // 2 button.enable_multi_press = False event = MockEvent(1, False, time) - assert button_handler._handle_event(event) == ButtonInput("SHORT_PRESS", 1) + assert button_handler._handle_event(event) == ButtonInput(ButtonInput.SHORT_PRESS, 1) button.enable_multi_press = True assert button_handler._handle_event(event) == None button._press_count = 4 - assert button_handler._handle_event(event) == ButtonInput("4_MULTI_PRESS", 1) + assert button_handler._handle_event(event) == ButtonInput(4, 1) button._press_start_time = time - button.long_press_threshold * 2 - assert button_handler._handle_event(event) == ButtonInput("LONG_PRESS", 1) + assert button_handler._handle_event(event) == ButtonInput(ButtonInput.LONG_PRESS, 1) assert button._last_press_time == None assert button._press_count == 0 @@ -271,20 +267,20 @@ def test_update(self, button_handler: ButtonHandler, time): assert input_set == set() while input_set == set() and timestamp_diff(ticks_ms(), time) < 100: input_set = button_handler.update() - assert input_set.pop() == ButtonInput("SHORT_PRESS", 2) + assert input_set.pop() == ButtonInput(ButtonInput.SHORT_PRESS, 2) # Multi press disabled button = button_handler.buttons[1] assert self.sim_press(button, button_handler, "SHORT_PRESS", 4) == set() - assert button_handler.update().pop() == ButtonInput("SHORT_PRESS", 1) + assert button_handler.update().pop() == ButtonInput(ButtonInput.SHORT_PRESS, 1) # Finish max multi press self.sim_press(button, button_handler, "SHORT_PRESS") button._press_count = 4 button.enable_multi_press = True - assert button_handler.update().pop() == ButtonInput("4_MULTI_PRESS", 1) + assert button_handler.update().pop() == ButtonInput(4, 1) # Long press button._press_start_time = time - button.long_press_threshold * 2 queue.keypad_eventqueue_record(1, False, time) - assert button_handler.update().pop() == ButtonInput("LONG_PRESS", 1) + assert button_handler.update().pop() == ButtonInput(ButtonInput.LONG_PRESS, 1) From ad4aa7927880f16c8fec0b33bf1bf1ed051b84bc Mon Sep 17 00:00:00 2001 From: EGJ-Moorington Date: Mon, 2 Sep 2024 13:37:34 +0200 Subject: [PATCH 2/3] Updated the documentation to version 3.0.0 Resolves #12 --- button_handler.py | 101 +++++++++++++++++++++++++++++++--------------- 1 file changed, 69 insertions(+), 32 deletions(-) diff --git a/button_handler.py b/button_handler.py index 769a9f3..7204c1c 100644 --- a/button_handler.py +++ b/button_handler.py @@ -73,22 +73,22 @@ def __init__( ) -> None: """ :param bool enable_multi_press: Sets :attr:`.enable_multi_press` - (whether to track multi presses). + (whether to track multi-presses). :param float multi_press_interval: Sets :attr:`.multi_press_interval` - (the time frame within which two presses should occur to count as a multi press). + (the time frame within which two presses should occur to count as a multi-press). :param float long_press_threshold: Sets :attr:`.long_press_threshold` (the minimum length of a press to count as a long press). :param int max_multi_press: Sets :attr:`.max_multi_press` - (the maximum amount of presses a multi press can have). + (the maximum amount of presses a multi-press can have). .. attribute:: enable_multi_press :type: bool :value: enable_multi_press = True Whether to account for the possibility of another short press - following a short press and counting as a multi press. + following a short press and counting as a multi-press. If set to false, :meth:`ButtonHandler.update` - returns ``SHORT_PRESS`` immediately after a short press. + returns a short press :class:`ButtonInput` object immediately after a short press. .. attribute:: long_press_threshold :type: float @@ -101,16 +101,16 @@ def __init__( :type: int :value: max_multi_press = 2 - The maximum amount of button presses that a multi press can be. - :meth:`ButtonHandler.update` returns the appropiate ``_MULTI_PRESS`` immediaetly after - the button has been pressed this many times. + The maximum amount of button presses that a multi-press can be. + :meth:`ButtonHandler.update` returns the appropiate multi-press :class:`ButtonInput` + object immediaetly after the button has been pressed this many times. .. attribute:: multi_press_interval :type: float :value: multi_press_interval = 175 The time frame from a button release within which - another release should occur to count as a multi press. + another release should occur to count as a multi-press. """ self.enable_multi_press = enable_multi_press self.long_press_threshold = long_press_threshold @@ -137,8 +137,9 @@ def __init__( :value: config.enable_multi_press = True Whether to account for the possibility of another short press - following a short press and counting that as a multi press. If set to false, - :meth:`ButtonHandler.update` returns ``SHORT_PRESS`` immediately after a short press. + following a short press and counting that as a multi-press. If set to false, + :meth:`ButtonHandler.update` returns a short press :class:`ButtonInput` + object immediately after a short press. .. attribute:: long_press_threshold :type: float @@ -151,16 +152,16 @@ def __init__( :type: int :value: config.max_multi_press = 2 - The maximum amount of button presses that a multi press can be. - :meth:`ButtonHandler.update` returns the appropiate ``_MULTI_PRESS`` immediaetly after - the button has been pressed this many times. + The maximum amount of button presses that a multi-press can be. + :meth:`ButtonHandler.update` returns the appropiate multi-press :class:`ButtonInput` + object immediaetly after the button has been pressed this many times. .. attribute:: multi_press_interval :type: float :value: config.multi_press_interval = 175 The time frame from a button release within which - another release should occur to count as a multi press. + another release should occur to count as a multi-press. .. caution:: Attributes with a *leading underscore (_)* are meant for **internal use only**, and accessing them may cause **unexpected behaviour**. Please consider accessing @@ -192,7 +193,7 @@ def __init__( :value: None The time (in miliseconds, tracked by :meth:`supervisor.ticks_ms`) that has passed since - the start of the previous press of a multi press. It is set to :type:`None` + the start of the previous press of a multi-press. It is set to :type:`None` after the time specified by :attr:`.multi_press_interval` has passed. .. attribute:: _press_count @@ -200,7 +201,7 @@ def __init__( :value: 0 The amount of times the button has been pressed since the last - multi press ended. It is set to 0 if the time set + multi-press ended. It is set to 0 if the time set by :attr:`.multi_press_interval` passes after a short press. .. attribute:: _press_start_time @@ -257,12 +258,12 @@ def _check_multi_press_timeout(self, current_time: int) -> Union[int, None]: .. caution:: Methods with a *leading underscore (_)* are meant for **internal use only**, and calling them may cause **unexpected behaviour**. Please refrain from using them. - Check whether a multi press has ended. - If it has, return the amount of times the button was pressed in that multi press. + Check whether a multi-press has ended. + If it has, return the amount of times the button was pressed in that multi-press. :param int current_time: The current time, provided by :meth:`supervisor.ticks_ms`. - :return: The amount of times the button was pressed in a multi press, - if a multi press has ended. + :return: The amount of times the button was pressed in a multi-press, + if a multi-press has ended. :rtype: int or None """ if ( @@ -303,8 +304,8 @@ class ButtonInput: SHORT_PRESS = 1 DOUBLE_PRESS = 2 - LONG_PRESS = "L" HOLD = "H" + LONG_PRESS = "L" def __init__( self, @@ -322,7 +323,13 @@ def __init__( :param int timestamp: Sets :attr:`timestamp` (the time at which the input was performed). .. type:: InputAction - :canonical: Literal["SHORT_PRESS", "LONG_PRESS", "HOLD", "DOUBLE_PRESS"] | str + :canonical: int | str + + Represents the action the :class:`ButtonInput` object represents. + Using a constant defined by :class:`ButtonInput` when available is recommended. + To represent a multi-press, use the number of presses in that multi-press. + Available constants are :const:`SHORT_PRESS`, :const:`DOUBLE_PRESS`, + :const:`HOLD` and :const:`LONG_PRESS`. .. attribute:: button_number :type: int @@ -344,6 +351,37 @@ def __init__( The timestamp (in milliseconds, provided by :meth:`supervisor.ticks_ms`) at which the input was performed. + .. warning:: Variables written in *upper case with underscores* are constants and + should not be modified. Doing so may cause **unexpected behaviour**. + + .. data:: SHORT_PRESS + :type: int + :value: 1 + + Represents a short press to pass as an argument to + parameter `action` in :class:`ButtonInput`. + + .. data:: DOUBLE_PRESS + :type: int + :value: 2 + + Represents a double press to pass as an argument to + parameter `action` in :class:`ButtonInput`. + + .. data:: HOLD + :type: str + :value: "H" + + Represents a hold action to pass as an argument to + parameter `action` in :class:`ButtonInput`. + + .. data:: LONG_PRESS + :type: str + :value: "L" + + Represents a long press to pass as an argument to + parameter `action` in :class:`ButtonInput`. + .. caution:: Attributes with a *leading underscore (_)* are meant for **internal use only**, and accessing them may cause **unexpected behaviour**. Please consider accessing a *property* (if available) instead. @@ -366,10 +404,9 @@ def action(self): :type: InputAction :param InputAction action: The action associated with the input. - :raise ValueError: if action is not a valid action. Valid actions are ``LONG_PRESS``, - ``HOLD``, ``SHORT_PRESS`` (exactly the same as ``1_MULTI_PRESS``), - ``DOUBLE_PRESS`` (exactly the same as ``2_MULTI_PRESS``) and - ``x_MULTI_PRESS`` where x is an :type:`int` bigger than 0. + :raise ValueError: if *action* is not a valid action. Valid actions are + :const:`SHORT_PRESS`, :const:`DOUBLE_PRESS`, :const:`HOLD`, :const:`LONG_PRESS` + and any :type:`int` bigger than 0. """ return self._action @@ -444,15 +481,15 @@ def __init__( :param keypad.EventQueue event_queue: Sets :attr:`_event_queue` (the :class:`keypad.EventQueue` object the handler should read events from). :param set[ButtonInput] callable_inputs: Sets :attr:`callable_inputs` - (the :class:`ButtonInitConfig` objects used to define the callback functions). + (the :class:`ButtonInput` objects used to define the callback functions). :param int button_amount: The amount of buttons scanned by the :mod:`keypad` scanner - that created the event_queue parameter's argument :class:`keypad.EventQueue` object. + that created the *event_queue* parameter's argument :class:`keypad.EventQueue` object. :param dict[int, ButtonInitConfig] config: A dictionary containing :class:`ButtonInitConfig` objects used to initialise :class:`Button` objects. The dictionary's keys should be the index numbers of the target buttons. For each button that doesn't have a :class:`ButtonInitConfig` attached to it, an object containing the default values is created. - :raise ValueError: if *button_amount* is smaller than 1, or if it is not an :type:`int`.. + :raise ValueError: if *button_amount* is smaller than 1, or if it is not an :type:`int`. .. attribute:: callable_inputs :type: set[ButtonInput] @@ -505,7 +542,7 @@ def buttons(self) -> list[Button]: def update(self) -> set[ButtonInput]: """ - Check if any button ended a multi press since the last time this method was called, + Check if any button ended a multi-press since the last time this method was called, process the next :class:`keypad.Event` in :attr:`_event_queue`, call all the relevant callback functions and return a set of the detected :class:`ButtonInput`\\ s. @@ -551,7 +588,7 @@ def _handle_buttons(self) -> set[ButtonInput]: and calling them may cause **unexpected behaviour**. Please refrain from using them. Check if any button began being held down since the last time this mehod was called - and if any multi press ended, and return every detected :class:`ButtonInput`. + and if any multi-press ended, and return every detected :class:`ButtonInput`. :return: A set containing every detected :class:`ButtonInput`. :rtype: set[ButtonInput] From bf7b0fb5f1b477b16ca0677f7d9330100d79e596 Mon Sep 17 00:00:00 2001 From: EGJ-Moorington Date: Mon, 2 Sep 2024 14:05:24 +0200 Subject: [PATCH 3/3] Tested and finalised version 3.0.0 --- README.rst | 8 ++++---- examples/button_handler_doublebutton.py | 2 +- examples/button_handler_singlebutton.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 37fa4c9..03e83f0 100644 --- a/README.rst +++ b/README.rst @@ -126,10 +126,10 @@ This simple script showcases the usage of this library using a single button. actions = { - ButtonInput("DOUBLE_PRESS", callback=double_press), - ButtonInput("SHORT_PRESS", callback=short_press), - ButtonInput("LONG_PRESS", callback=long_press), - ButtonInput("HOLD", callback=hold), + ButtonInput(ButtonInput.DOUBLE_PRESS, callback=double_press), + ButtonInput(ButtonInput.SHORT_PRESS, callback=short_press), + ButtonInput(ButtonInput.LONG_PRESS, callback=long_press), + ButtonInput(ButtonInput.HOLD, callback=hold), } scanner = Keys([board.D9], value_when_pressed=False) diff --git a/examples/button_handler_doublebutton.py b/examples/button_handler_doublebutton.py index 618a670..bcb4866 100644 --- a/examples/button_handler_doublebutton.py +++ b/examples/button_handler_doublebutton.py @@ -34,7 +34,7 @@ def triple_press_0(): def triple_press_1(): if button_0.is_pressed: - triple_press_0_holding_1() + triple_press_1_holding_0() return print("Button 1 has been triple pressed!") diff --git a/examples/button_handler_singlebutton.py b/examples/button_handler_singlebutton.py index 50615a3..f56efcb 100644 --- a/examples/button_handler_singlebutton.py +++ b/examples/button_handler_singlebutton.py @@ -8,7 +8,7 @@ import board from keypad import Keys -from button_handler import ButtonHandler, ButtonInitConfig, ButtonInput +from button_handler import ButtonHandler, ButtonInput def double_press():