From ab9dfceede3aef71bf950ac801fda54aa78d5ef3 Mon Sep 17 00:00:00 2001 From: Xavier Moreno Date: Sun, 7 Feb 2021 01:47:05 +0100 Subject: [PATCH] feat(controller): add `mode` parameter adds the possibility to user to define what to do in case of an action still being executed. Based on HA automation modes. related to #242 --- apps/controllerx/cx_core/controller.py | 40 +++++++++++++++++-- .../brightness_down_hold_test.yaml | 9 ----- tests/integ_tests/action-types/config.yaml | 4 -- .../integ_tests/controller-modes/config.yaml | 27 +++++++++++++ .../controller-modes/parallel_mode_test.yaml | 7 ++++ .../controller-modes/queued_mode_test.yaml | 7 ++++ .../controller-modes/restart_mode_test.yaml | 6 +++ .../controller-modes/single_mode_test.yaml | 5 +++ tests/integ_tests/integ_test.py | 4 +- 9 files changed, 92 insertions(+), 17 deletions(-) delete mode 100644 tests/integ_tests/action-types/brightness_down_hold_test.yaml create mode 100644 tests/integ_tests/controller-modes/config.yaml create mode 100644 tests/integ_tests/controller-modes/parallel_mode_test.yaml create mode 100644 tests/integ_tests/controller-modes/queued_mode_test.yaml create mode 100644 tests/integ_tests/controller-modes/restart_mode_test.yaml create mode 100644 tests/integ_tests/controller-modes/single_mode_test.yaml diff --git a/apps/controllerx/cx_core/controller.py b/apps/controllerx/cx_core/controller.py index 65c13481..1d51a94c 100644 --- a/apps/controllerx/cx_core/controller.py +++ b/apps/controllerx/cx_core/controller.py @@ -41,6 +41,11 @@ DEFAULT_MULTIPLE_CLICK_DELAY = 500 # In milliseconds MULTIPLE_CLICK_TOKEN = "$" +MODE_SINGLE = "single" +MODE_RESTART = "restart" +MODE_QUEUED = "queued" +MODE_PARALLEL = "parallel" + T = TypeVar("T") @@ -151,6 +156,11 @@ async def init(self) -> None: self.click_counter = Counter() self.multiple_click_action_delay_tasks = defaultdict(lambda: None) + # Mode + self.mode = self.get_mapping_per_action( + self.actions_mapping, custom=self.args.get("mode"), default=MODE_SINGLE + ) + # Listen for device changes for controller_id in controllers_ids: self.integration.listen_changes(controller_id) @@ -348,14 +358,38 @@ async def call_action( else: await self.action_timer_callback({"action_key": action_key, "extra": extra}) + async def _apply_mode_strategy(self, action_key: ActionEvent) -> bool: + previous_task = self.action_handles[action_key] + if previous_task is None: + return False + if self.mode[action_key] == MODE_SINGLE: + self.log( + "There is already an action executing for `action_key`. " + "If you want a different behaviour change `mode` parameter.", + level="WARNING", + ) + return True + elif self.mode[action_key] == MODE_RESTART: + previous_task.cancel() + elif self.mode[action_key] == MODE_QUEUED: + await previous_task + elif self.mode[action_key] == MODE_PARALLEL: + pass + else: + raise ValueError( + f"`{self.mode[action_key]}` is not a possible value for `mode` parameter." + "Possible values: `single`, `restart`, `queued` and `parallel`." + ) + return False + async def action_timer_callback(self, kwargs: Dict[str, Any]): action_key: ActionEvent = kwargs["action_key"] extra: EventData = kwargs["extra"] self.action_delay_handles[action_key] = None + skip = await self._apply_mode_strategy(action_key) + if skip: + return action_types = self.actions_mapping[action_key] - previous_task = self.action_handles[action_key] - if previous_task is not None: - previous_task.cancel() task = asyncio.ensure_future(self.call_action_types(action_types, extra)) self.action_handles[action_key] = task try: diff --git a/tests/integ_tests/action-types/brightness_down_hold_test.yaml b/tests/integ_tests/action-types/brightness_down_hold_test.yaml deleted file mode 100644 index 68fb7232..00000000 --- a/tests/integ_tests/action-types/brightness_down_hold_test.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Testing the restart task functionality -entity_state_attributes: - supported_features: 191 -entity_state: "off" -fired_actions: [brightness_down_hold, 0.4, brightness_down_hold] -expected_calls: - - service: my_service - - service: my_service - - service: my_other_service diff --git a/tests/integ_tests/action-types/config.yaml b/tests/integ_tests/action-types/config.yaml index 7d997b47..32ba690b 100644 --- a/tests/integ_tests/action-types/config.yaml +++ b/tests/integ_tests/action-types/config.yaml @@ -25,8 +25,4 @@ livingroom_controller: - action: toggle - delay: 1 - scene: scene.my_other_scene - - service: my_other_service - brightness_down_hold: - - service: my_service - - delay: 1 - service: my_other_service \ No newline at end of file diff --git a/tests/integ_tests/controller-modes/config.yaml b/tests/integ_tests/controller-modes/config.yaml new file mode 100644 index 00000000..3a963040 --- /dev/null +++ b/tests/integ_tests/controller-modes/config.yaml @@ -0,0 +1,27 @@ +livingroom_controller: + module: controllerx + class: Controller + controller: my_controller + integration: z2m + mode: + action_single: single + action_restart: restart + action_queued: queued + action_parallel: parallel + mapping: + action_single: + - service: my_service + - delay: 1 + - service: my_other_service + action_restart: + - service: my_service + - delay: 1 + - service: my_other_service + action_queued: + - service: my_service + - delay: 1 + - service: my_other_service + action_parallel: + - service: my_service + - delay: 1 + - service: my_other_service diff --git a/tests/integ_tests/controller-modes/parallel_mode_test.yaml b/tests/integ_tests/controller-modes/parallel_mode_test.yaml new file mode 100644 index 00000000..60b14776 --- /dev/null +++ b/tests/integ_tests/controller-modes/parallel_mode_test.yaml @@ -0,0 +1,7 @@ +# Testing the parallel mode task functionality +fired_actions: [action_parallel, 0.4, action_parallel] +expected_calls: + - service: my_service + - service: my_service + - service: my_other_service + - service: my_other_service diff --git a/tests/integ_tests/controller-modes/queued_mode_test.yaml b/tests/integ_tests/controller-modes/queued_mode_test.yaml new file mode 100644 index 00000000..c1737d7c --- /dev/null +++ b/tests/integ_tests/controller-modes/queued_mode_test.yaml @@ -0,0 +1,7 @@ +# Testing the queued mode task functionality +fired_actions: [action_queued, 0.4, action_queued] +expected_calls: + - service: my_service + - service: my_other_service + - service: my_service + - service: my_other_service diff --git a/tests/integ_tests/controller-modes/restart_mode_test.yaml b/tests/integ_tests/controller-modes/restart_mode_test.yaml new file mode 100644 index 00000000..a7303d6a --- /dev/null +++ b/tests/integ_tests/controller-modes/restart_mode_test.yaml @@ -0,0 +1,6 @@ +# Testing the restart mode task functionality +fired_actions: [action_restart, 0.4, action_restart] +expected_calls: + - service: my_service + - service: my_service + - service: my_other_service diff --git a/tests/integ_tests/controller-modes/single_mode_test.yaml b/tests/integ_tests/controller-modes/single_mode_test.yaml new file mode 100644 index 00000000..cc09a2c9 --- /dev/null +++ b/tests/integ_tests/controller-modes/single_mode_test.yaml @@ -0,0 +1,5 @@ +# Testing the single mode task functionality +fired_actions: [action_single, 0.4, action_single] +expected_calls: + - service: my_service + - service: my_other_service diff --git a/tests/integ_tests/integ_test.py b/tests/integ_tests/integ_test.py index 42842542..8c0b4aa0 100644 --- a/tests/integ_tests/integ_test.py +++ b/tests/integ_tests/integ_test.py @@ -6,6 +6,7 @@ import pytest import yaml from appdaemon.plugins.hass.hassapi import Hass # type: ignore +from cx_core.type_controller import TypeController from pytest_mock.plugin import MockerFixture from tests.test_utils import get_controller @@ -59,7 +60,8 @@ async def test_integ_configs( controller.args = config fake_entity_states = get_fake_entity_states(entity_state, entity_state_attributes) - mocker.patch.object(controller, "get_entity_state", fake_entity_states) + if isinstance(controller, TypeController): + mocker.patch.object(controller, "get_entity_state", fake_entity_states) call_service_stub = mocker.patch.object(Hass, "call_service") await controller.initialize()