diff --git a/apps/controllerx/cx_const.py b/apps/controllerx/cx_const.py index ac216115..d875c70c 100644 --- a/apps/controllerx/cx_const.py +++ b/apps/controllerx/cx_const.py @@ -98,3 +98,15 @@ class Cover: STOP = "stop" TOGGLE_OPEN = "toggle_open" TOGGLE_CLOSE = "toggle_close" + + +class StepperDir: + UP = "up" + DOWN = "down" + TOGGLE = "toggle" + + +class StepperMode: + STOP = "stop" + LOOP = "loop" + BOUNCE = "bounce" diff --git a/apps/controllerx/cx_core/stepper/__init__.py b/apps/controllerx/cx_core/stepper/__init__.py index b046bc72..eaa527af 100644 --- a/apps/controllerx/cx_core/stepper/__init__.py +++ b/apps/controllerx/cx_core/stepper/__init__.py @@ -2,7 +2,7 @@ from typing import Optional from attr import dataclass -from cx_const import Number +from cx_const import Number, StepperDir class MinMax: @@ -52,18 +52,15 @@ def exceeded(self) -> bool: class Stepper(abc.ABC): - UP = "up" - DOWN = "down" - TOGGLE = "toggle" - sign_mapping = {UP: 1, DOWN: -1} + sign_mapping = {StepperDir.UP: 1, StepperDir.DOWN: -1} - previous_direction: str = DOWN + previous_direction: str = StepperDir.DOWN min_max: MinMax steps: Number @staticmethod def invert_direction(direction: str) -> str: - return Stepper.UP if direction == Stepper.DOWN else Stepper.DOWN + return StepperDir.UP if direction == StepperDir.DOWN else StepperDir.DOWN @staticmethod def sign(direction: str) -> int: @@ -74,7 +71,7 @@ def __init__(self, min_max: MinMax, steps: Number) -> None: self.steps = steps def get_direction(self, value: Number, direction: str) -> str: - if direction == Stepper.TOGGLE: + if direction == StepperDir.TOGGLE: direction = Stepper.invert_direction(self.previous_direction) self.previous_direction = direction return direction diff --git a/apps/controllerx/cx_core/stepper/stop_stepper.py b/apps/controllerx/cx_core/stepper/stop_stepper.py index 25dbd668..ccf2f3c1 100644 --- a/apps/controllerx/cx_core/stepper/stop_stepper.py +++ b/apps/controllerx/cx_core/stepper/stop_stepper.py @@ -1,15 +1,15 @@ -from cx_const import Number +from cx_const import Number, StepperDir from cx_core.stepper import Stepper, StepperOutput class StopStepper(Stepper): def get_direction(self, value: Number, direction: str) -> str: value = self.min_max.clip(value) - if direction == Stepper.TOGGLE and self.min_max.in_min_boundaries(value): - self.previous_direction = Stepper.UP + if direction == StepperDir.TOGGLE and self.min_max.in_min_boundaries(value): + self.previous_direction = StepperDir.UP return self.previous_direction - if direction == Stepper.TOGGLE and self.min_max.in_max_boundaries(value): - self.previous_direction = Stepper.DOWN + if direction == StepperDir.TOGGLE and self.min_max.in_max_boundaries(value): + self.previous_direction = StepperDir.DOWN return self.previous_direction return super().get_direction(value, direction) diff --git a/apps/controllerx/cx_core/type/light_controller.py b/apps/controllerx/cx_core/type/light_controller.py index d7d974db..8fa19a03 100644 --- a/apps/controllerx/cx_core/type/light_controller.py +++ b/apps/controllerx/cx_core/type/light_controller.py @@ -1,7 +1,7 @@ from functools import lru_cache from typing import Any, Dict, List, Optional, Set, Type -from cx_const import Light, Number, PredefinedActionsMapping +from cx_const import Light, Number, PredefinedActionsMapping, StepperDir, StepperMode from cx_core.color_helper import Color, get_color_wheel from cx_core.controller import action from cx_core.feature_support.light import LightSupport @@ -35,9 +35,9 @@ COLOR_MODES = {"hs", "xy", "rgb", "rgbw", "rgbww"} STEPPER_MODES: Dict[str, Type[Stepper]] = { - "stop": StopStepper, - "loop": LoopStepper, - "bounce": BounceStepper, + StepperMode.STOP: StopStepper, + StepperMode.LOOP: LoopStepper, + StepperMode.BOUNCE: BounceStepper, } @@ -222,70 +222,70 @@ def get_predefined_actions_mapping(self) -> PredefinedActionsMapping: self.click, ( LightController.ATTRIBUTE_BRIGHTNESS, - Stepper.UP, + StepperDir.UP, ), ), Light.CLICK_BRIGHTNESS_DOWN: ( self.click, ( LightController.ATTRIBUTE_BRIGHTNESS, - Stepper.DOWN, + StepperDir.DOWN, ), ), Light.CLICK_WHITE_VALUE_UP: ( self.click, ( LightController.ATTRIBUTE_WHITE_VALUE, - Stepper.UP, + StepperDir.UP, ), ), Light.CLICK_WHITE_VALUE_DOWN: ( self.click, ( LightController.ATTRIBUTE_WHITE_VALUE, - Stepper.DOWN, + StepperDir.DOWN, ), ), Light.CLICK_COLOR_UP: ( self.click, ( LightController.ATTRIBUTE_COLOR, - Stepper.UP, + StepperDir.UP, ), ), Light.CLICK_COLOR_DOWN: ( self.click, ( LightController.ATTRIBUTE_COLOR, - Stepper.DOWN, + StepperDir.DOWN, ), ), Light.CLICK_COLOR_TEMP_UP: ( self.click, ( LightController.ATTRIBUTE_COLOR_TEMP, - Stepper.UP, + StepperDir.UP, ), ), Light.CLICK_COLOR_TEMP_DOWN: ( self.click, ( LightController.ATTRIBUTE_COLOR_TEMP, - Stepper.DOWN, + StepperDir.DOWN, ), ), Light.CLICK_XY_COLOR_UP: ( self.click, ( LightController.ATTRIBUTE_XY_COLOR, - Stepper.UP, + StepperDir.UP, ), ), Light.CLICK_XY_COLOR_DOWN: ( self.click, ( LightController.ATTRIBUTE_XY_COLOR, - Stepper.DOWN, + StepperDir.DOWN, ), ), Light.HOLD: self.hold, @@ -293,105 +293,105 @@ def get_predefined_actions_mapping(self) -> PredefinedActionsMapping: self.hold, ( LightController.ATTRIBUTE_BRIGHTNESS, - Stepper.UP, + StepperDir.UP, ), ), Light.HOLD_BRIGHTNESS_DOWN: ( self.hold, ( LightController.ATTRIBUTE_BRIGHTNESS, - Stepper.DOWN, + StepperDir.DOWN, ), ), Light.HOLD_BRIGHTNESS_TOGGLE: ( self.hold, ( LightController.ATTRIBUTE_BRIGHTNESS, - Stepper.TOGGLE, + StepperDir.TOGGLE, ), ), Light.HOLD_WHITE_VALUE_UP: ( self.hold, ( LightController.ATTRIBUTE_WHITE_VALUE, - Stepper.UP, + StepperDir.UP, ), ), Light.HOLD_WHITE_VALUE_DOWN: ( self.hold, ( LightController.ATTRIBUTE_WHITE_VALUE, - Stepper.DOWN, + StepperDir.DOWN, ), ), Light.HOLD_WHITE_VALUE_TOGGLE: ( self.hold, ( LightController.ATTRIBUTE_WHITE_VALUE, - Stepper.TOGGLE, + StepperDir.TOGGLE, ), ), Light.HOLD_COLOR_UP: ( self.hold, ( LightController.ATTRIBUTE_COLOR, - Stepper.UP, + StepperDir.UP, ), ), Light.HOLD_COLOR_DOWN: ( self.hold, ( LightController.ATTRIBUTE_COLOR, - Stepper.DOWN, + StepperDir.DOWN, ), ), Light.HOLD_COLOR_TOGGLE: ( self.hold, ( LightController.ATTRIBUTE_COLOR, - Stepper.TOGGLE, + StepperDir.TOGGLE, ), ), Light.HOLD_COLOR_TEMP_UP: ( self.hold, ( LightController.ATTRIBUTE_COLOR_TEMP, - Stepper.UP, + StepperDir.UP, ), ), Light.HOLD_COLOR_TEMP_DOWN: ( self.hold, ( LightController.ATTRIBUTE_COLOR_TEMP, - Stepper.DOWN, + StepperDir.DOWN, ), ), Light.HOLD_COLOR_TEMP_TOGGLE: ( self.hold, ( LightController.ATTRIBUTE_COLOR_TEMP, - Stepper.TOGGLE, + StepperDir.TOGGLE, ), ), Light.HOLD_XY_COLOR_UP: ( self.hold, ( LightController.ATTRIBUTE_XY_COLOR, - Stepper.UP, + StepperDir.UP, ), ), Light.HOLD_XY_COLOR_DOWN: ( self.hold, ( LightController.ATTRIBUTE_XY_COLOR, - Stepper.DOWN, + StepperDir.DOWN, ), ), Light.HOLD_XY_COLOR_TOGGLE: ( self.hold, ( LightController.ATTRIBUTE_XY_COLOR, - Stepper.TOGGLE, + StepperDir.TOGGLE, ), ), Light.XYCOLOR_FROM_CONTROLLER: self.xycolor_from_controller, @@ -626,7 +626,7 @@ def check_smooth_power_on( self, attribute: str, direction: str, light_state: str ) -> bool: return ( - direction != Stepper.DOWN + direction != StepperDir.DOWN and attribute == self.ATTRIBUTE_BRIGHTNESS and self.smooth_power_on and light_state == "off" @@ -664,16 +664,18 @@ async def click( self, attribute: str, direction: str, - mode: str = "stop", + mode: str = StepperMode.STOP, steps: Optional[Number] = None, ) -> None: attribute = self.get_option( attribute, LightController.ATTRIBUTES_LIST, "`click` action" ) direction = self.get_option( - direction, [Stepper.UP, Stepper.DOWN], "`click` action" + direction, [StepperDir.UP, StepperDir.DOWN], "`click` action" + ) + mode = self.get_option( + mode, [StepperMode.STOP, StepperMode.LOOP], "`click` action" ) - mode = self.get_option(mode, ["stop", "loop"], "`click` action") attribute = await self.get_attribute(attribute) self.value_attribute = await self.get_value_attribute(attribute) await self.change_light_state( @@ -689,16 +691,22 @@ async def hold( # type: ignore self, attribute: str, direction: str, - mode: str = "stop", + mode: str = StepperMode.STOP, steps: Optional[Number] = None, ) -> None: attribute = self.get_option( attribute, LightController.ATTRIBUTES_LIST, "`hold` action" ) direction = self.get_option( - direction, [Stepper.UP, Stepper.DOWN, Stepper.TOGGLE], "`hold` action" + direction, + [StepperDir.UP, StepperDir.DOWN, StepperDir.TOGGLE], + "`hold` action", + ) + mode = self.get_option( + mode, + [StepperMode.STOP, StepperMode.LOOP, StepperMode.BOUNCE], + "`hold` action", ) - mode = self.get_option(mode, ["stop", "loop", "bounce"], "`hold` action") attribute = await self.get_attribute(attribute) self.value_attribute = await self.get_value_attribute(attribute) self.log( @@ -706,7 +714,7 @@ async def hold( # type: ignore level="DEBUG", ) stepper = self.get_stepper(attribute, steps or self.automatic_steps, mode) - if direction == Stepper.TOGGLE: + if direction == StepperDir.TOGGLE: self.log( f"Previous direction: {stepper.previous_direction}", level="DEBUG", diff --git a/apps/controllerx/cx_core/type/media_player_controller.py b/apps/controllerx/cx_core/type/media_player_controller.py index 5498b504..b88413b3 100644 --- a/apps/controllerx/cx_core/type/media_player_controller.py +++ b/apps/controllerx/cx_core/type/media_player_controller.py @@ -1,10 +1,10 @@ from typing import Any, Dict, List, Optional, Type -from cx_const import MediaPlayer, Number, PredefinedActionsMapping +from cx_const import MediaPlayer, Number, PredefinedActionsMapping, StepperDir from cx_core.controller import action from cx_core.feature_support.media_player import MediaPlayerSupport from cx_core.release_hold_controller import ReleaseHoldController -from cx_core.stepper import MinMax, Stepper +from cx_core.stepper import MinMax from cx_core.stepper.index_loop_stepper import IndexLoopStepper from cx_core.stepper.stop_stepper import StopStepper from cx_core.type_controller import Entity, TypeController @@ -28,8 +28,8 @@ def _get_entity_type(self) -> Type[Entity]: def get_predefined_actions_mapping(self) -> PredefinedActionsMapping: return { - MediaPlayer.HOLD_VOLUME_DOWN: (self.hold, (Stepper.DOWN,)), - MediaPlayer.HOLD_VOLUME_UP: (self.hold, (Stepper.UP,)), + MediaPlayer.HOLD_VOLUME_DOWN: (self.hold, (StepperDir.DOWN,)), + MediaPlayer.HOLD_VOLUME_UP: (self.hold, (StepperDir.UP,)), MediaPlayer.CLICK_VOLUME_DOWN: self.volume_down, MediaPlayer.CLICK_VOLUME_UP: self.volume_up, MediaPlayer.VOLUME_SET: self.volume_set, @@ -39,8 +39,8 @@ def get_predefined_actions_mapping(self) -> PredefinedActionsMapping: MediaPlayer.PLAY_PAUSE: self.play_pause, MediaPlayer.NEXT_TRACK: self.next_track, MediaPlayer.PREVIOUS_TRACK: self.previous_track, - MediaPlayer.NEXT_SOURCE: (self.change_source_list, (Stepper.UP,)), - MediaPlayer.PREVIOUS_SOURCE: (self.change_source_list, (Stepper.DOWN,)), + MediaPlayer.NEXT_SOURCE: (self.change_source_list, (StepperDir.UP,)), + MediaPlayer.PREVIOUS_SOURCE: (self.change_source_list, (StepperDir.DOWN,)), MediaPlayer.MUTE: self.volume_mute, MediaPlayer.TTS: self.tts, } @@ -109,12 +109,12 @@ async def volume_set(self, volume_level: float) -> None: @action async def volume_up(self) -> None: await self.prepare_volume_change() - await self.volume_change(Stepper.UP) + await self.volume_change(StepperDir.UP) @action async def volume_down(self) -> None: await self.prepare_volume_change() - await self.volume_change(Stepper.DOWN) + await self.volume_change(StepperDir.DOWN) @action async def volume_mute(self) -> None: @@ -155,7 +155,7 @@ async def volume_change(self, direction: str) -> bool: await self.volume_set(self.volume_level) return stepper_output.exceeded else: - if direction == Stepper.UP: + if direction == StepperDir.UP: await self.call_service( "media_player/volume_up", entity_id=self.entity.name ) diff --git a/tests/integ_tests/muller_licht_z2m/colot_temp_from_controller_test.yaml b/tests/integ_tests/muller_licht_z2m/colot_temp_from_controller_test.yaml new file mode 100644 index 00000000..29fe03fa --- /dev/null +++ b/tests/integ_tests/muller_licht_z2m/colot_temp_from_controller_test.yaml @@ -0,0 +1,10 @@ + +entity_state: "off" +fired_actions: ["color_temp"] +extra: + action_color_temperature: 200 +expected_calls: + - service: light/turn_on + data: + entity_id: light.my_light + color_temp: 200 diff --git a/tests/unit_tests/cx_core/stepper/bounce_stepper_test.py b/tests/unit_tests/cx_core/stepper/bounce_stepper_test.py index c8aa1926..3ca45a48 100644 --- a/tests/unit_tests/cx_core/stepper/bounce_stepper_test.py +++ b/tests/unit_tests/cx_core/stepper/bounce_stepper_test.py @@ -1,5 +1,6 @@ import pytest -from cx_core.stepper import MinMax, Stepper +from cx_const import StepperDir +from cx_core.stepper import MinMax from cx_core.stepper.bounce_stepper import BounceStepper from typing_extensions import Literal @@ -7,18 +8,18 @@ @pytest.mark.parametrize( "min_max, value, steps, direction, expected_value, expected_direction", [ - (MinMax(0, 10), 5, 10, Stepper.DOWN, 4, Stepper.DOWN), - (MinMax(0, 10), 5, 10, Stepper.UP, 6, Stepper.UP), - (MinMax(0, 10), 1, 10, Stepper.DOWN, 0, Stepper.UP), - (MinMax(0, 10), 9, 10, Stepper.UP, 10, Stepper.DOWN), - (MinMax(0, 10), 0, 10, Stepper.DOWN, 1, Stepper.UP), - (MinMax(0, 10), 0, 10, Stepper.UP, 1, Stepper.UP), - (MinMax(0, 10), 10, 10, Stepper.UP, 9, Stepper.DOWN), - (MinMax(0, 10), 10, 10, Stepper.DOWN, 9, Stepper.DOWN), - (MinMax(0, 10), -1, 10, Stepper.DOWN, 1, Stepper.UP), - (MinMax(0, 10), 11, 10, Stepper.UP, 9, Stepper.DOWN), - (MinMax(0, 10), 6, 5, Stepper.DOWN, 4, Stepper.DOWN), - (MinMax(0, 10), 4, 5, Stepper.UP, 6, Stepper.UP), + (MinMax(0, 10), 5, 10, StepperDir.DOWN, 4, StepperDir.DOWN), + (MinMax(0, 10), 5, 10, StepperDir.UP, 6, StepperDir.UP), + (MinMax(0, 10), 1, 10, StepperDir.DOWN, 0, StepperDir.UP), + (MinMax(0, 10), 9, 10, StepperDir.UP, 10, StepperDir.DOWN), + (MinMax(0, 10), 0, 10, StepperDir.DOWN, 1, StepperDir.UP), + (MinMax(0, 10), 0, 10, StepperDir.UP, 1, StepperDir.UP), + (MinMax(0, 10), 10, 10, StepperDir.UP, 9, StepperDir.DOWN), + (MinMax(0, 10), 10, 10, StepperDir.DOWN, 9, StepperDir.DOWN), + (MinMax(0, 10), -1, 10, StepperDir.DOWN, 1, StepperDir.UP), + (MinMax(0, 10), 11, 10, StepperDir.UP, 9, StepperDir.DOWN), + (MinMax(0, 10), 6, 5, StepperDir.DOWN, 4, StepperDir.DOWN), + (MinMax(0, 10), 4, 5, StepperDir.UP, 6, StepperDir.UP), ], ) def test_bounce_stepper( diff --git a/tests/unit_tests/cx_core/stepper/index_loop_stepper_test.py b/tests/unit_tests/cx_core/stepper/index_loop_stepper_test.py index 138a8998..1be138cc 100644 --- a/tests/unit_tests/cx_core/stepper/index_loop_stepper_test.py +++ b/tests/unit_tests/cx_core/stepper/index_loop_stepper_test.py @@ -1,5 +1,5 @@ import pytest -from cx_core.stepper import Stepper +from cx_const import StepperDir from cx_core.stepper.index_loop_stepper import IndexLoopStepper from typing_extensions import Literal @@ -7,13 +7,13 @@ @pytest.mark.parametrize( "size, value, direction, expected_value", [ - (10, 5, Stepper.DOWN, 4), - (10, 5, Stepper.UP, 6), - (10, 1, Stepper.DOWN, 0), - (10, 9, Stepper.UP, 0), - (10, 0, Stepper.DOWN, 9), - (10, 10, Stepper.UP, 0), - (10, -1, Stepper.DOWN, 9), + (10, 5, StepperDir.DOWN, 4), + (10, 5, StepperDir.UP, 6), + (10, 1, StepperDir.DOWN, 0), + (10, 9, StepperDir.UP, 0), + (10, 0, StepperDir.DOWN, 9), + (10, 10, StepperDir.UP, 0), + (10, -1, StepperDir.DOWN, 9), ], ) def test_index_loop_stepper( diff --git a/tests/unit_tests/cx_core/stepper/loop_stepper_test.py b/tests/unit_tests/cx_core/stepper/loop_stepper_test.py index bf79a9cb..27b5d721 100644 --- a/tests/unit_tests/cx_core/stepper/loop_stepper_test.py +++ b/tests/unit_tests/cx_core/stepper/loop_stepper_test.py @@ -1,5 +1,6 @@ import pytest -from cx_core.stepper import MinMax, Stepper +from cx_const import StepperDir +from cx_core.stepper import MinMax from cx_core.stepper.loop_stepper import LoopStepper from typing_extensions import Literal @@ -7,20 +8,20 @@ @pytest.mark.parametrize( "min_max, value, steps, direction, expected_value", [ - (MinMax(0, 10), 5, 10, Stepper.DOWN, 4), - (MinMax(0, 10), 5, 10, Stepper.UP, 6), - (MinMax(0, 10), 1, 10, Stepper.DOWN, 0), - (MinMax(0, 10), 9, 10, Stepper.UP, 0), - (MinMax(0, 10), 0, 10, Stepper.DOWN, 9), - (MinMax(0, 10), 10, 10, Stepper.UP, 1), - (MinMax(0, 10), -1, 10, Stepper.DOWN, 9), - (MinMax(0, 10), 11, 10, Stepper.UP, 1), - (MinMax(0, 10), 6, 5, Stepper.DOWN, 4), - (MinMax(0, 10), 4, 5, Stepper.UP, 6), - (MinMax(0, 1), 0.2, 10, Stepper.UP, 0.3), - (MinMax(0, 1), 0.1, 5, Stepper.DOWN, 0.9), - (MinMax(153, 500), 160, 10, Stepper.DOWN, 472.3), - (MinMax(153, 500), 490, 5, Stepper.UP, 212.4), + (MinMax(0, 10), 5, 10, StepperDir.DOWN, 4), + (MinMax(0, 10), 5, 10, StepperDir.UP, 6), + (MinMax(0, 10), 1, 10, StepperDir.DOWN, 0), + (MinMax(0, 10), 9, 10, StepperDir.UP, 0), + (MinMax(0, 10), 0, 10, StepperDir.DOWN, 9), + (MinMax(0, 10), 10, 10, StepperDir.UP, 1), + (MinMax(0, 10), -1, 10, StepperDir.DOWN, 9), + (MinMax(0, 10), 11, 10, StepperDir.UP, 1), + (MinMax(0, 10), 6, 5, StepperDir.DOWN, 4), + (MinMax(0, 10), 4, 5, StepperDir.UP, 6), + (MinMax(0, 1), 0.2, 10, StepperDir.UP, 0.3), + (MinMax(0, 1), 0.1, 5, StepperDir.DOWN, 0.9), + (MinMax(153, 500), 160, 10, StepperDir.DOWN, 472.3), + (MinMax(153, 500), 490, 5, StepperDir.UP, 212.4), ], ) def test_loop_stepper( diff --git a/tests/unit_tests/cx_core/stepper/stepper_test.py b/tests/unit_tests/cx_core/stepper/stepper_test.py index 76a21299..387cacd1 100644 --- a/tests/unit_tests/cx_core/stepper/stepper_test.py +++ b/tests/unit_tests/cx_core/stepper/stepper_test.py @@ -1,5 +1,5 @@ import pytest -from cx_const import Number +from cx_const import Number, StepperDir from cx_core.stepper import MinMax, Stepper, StepperOutput @@ -14,12 +14,12 @@ def step(self, value: Number, direction: str) -> StepperOutput: @pytest.mark.parametrize( "direction_input, previous_direction, expected_direction", [ - (Stepper.UP, Stepper.UP, Stepper.UP), - (Stepper.DOWN, Stepper.DOWN, Stepper.DOWN), - (Stepper.UP, Stepper.DOWN, Stepper.UP), - (Stepper.DOWN, Stepper.UP, Stepper.DOWN), - (Stepper.TOGGLE, Stepper.UP, Stepper.DOWN), - (Stepper.TOGGLE, Stepper.DOWN, Stepper.UP), + (StepperDir.UP, StepperDir.UP, StepperDir.UP), + (StepperDir.DOWN, StepperDir.DOWN, StepperDir.DOWN), + (StepperDir.UP, StepperDir.DOWN, StepperDir.UP), + (StepperDir.DOWN, StepperDir.UP, StepperDir.DOWN), + (StepperDir.TOGGLE, StepperDir.UP, StepperDir.DOWN), + (StepperDir.TOGGLE, StepperDir.DOWN, StepperDir.UP), ], ) def test_get_direction( @@ -36,10 +36,10 @@ def test_get_direction( @pytest.mark.parametrize( "direction_input, expected_sign", [ - (Stepper.UP, 1), - (Stepper.DOWN, -1), - (Stepper.UP, 1), - (Stepper.DOWN, -1), + (StepperDir.UP, 1), + (StepperDir.DOWN, -1), + (StepperDir.UP, 1), + (StepperDir.DOWN, -1), ], ) def test_sign(direction_input: str, expected_sign: int): diff --git a/tests/unit_tests/cx_core/stepper/stop_stepper_test.py b/tests/unit_tests/cx_core/stepper/stop_stepper_test.py index 8f17801c..1bc7d105 100644 --- a/tests/unit_tests/cx_core/stepper/stop_stepper_test.py +++ b/tests/unit_tests/cx_core/stepper/stop_stepper_test.py @@ -1,5 +1,6 @@ import pytest -from cx_core.stepper import MinMax, Stepper +from cx_const import StepperDir +from cx_core.stepper import MinMax from cx_core.stepper.stop_stepper import StopStepper from typing_extensions import Literal @@ -7,98 +8,98 @@ @pytest.mark.parametrize( "min_max, value, direction, previous_direction, expected_direction, expected_new_previous_direction", [ - (MinMax(0, 10), 10, Stepper.DOWN, None, Stepper.DOWN, None), - (MinMax(0, 10), 11, Stepper.DOWN, None, Stepper.DOWN, None), - (MinMax(0, 10), -1, Stepper.DOWN, None, Stepper.DOWN, None), - (MinMax(0, 10), 5, Stepper.UP, None, Stepper.UP, None), - (MinMax(0, 10), 5, Stepper.UP, None, Stepper.UP, None), + (MinMax(0, 10), 10, StepperDir.DOWN, None, StepperDir.DOWN, None), + (MinMax(0, 10), 11, StepperDir.DOWN, None, StepperDir.DOWN, None), + (MinMax(0, 10), -1, StepperDir.DOWN, None, StepperDir.DOWN, None), + (MinMax(0, 10), 5, StepperDir.UP, None, StepperDir.UP, None), + (MinMax(0, 10), 5, StepperDir.UP, None, StepperDir.UP, None), ( MinMax(0, 10), 5, - Stepper.TOGGLE, - Stepper.DOWN, - Stepper.UP, - Stepper.UP, + StepperDir.TOGGLE, + StepperDir.DOWN, + StepperDir.UP, + StepperDir.UP, ), ( MinMax(0, 10), 5, - Stepper.TOGGLE, - Stepper.UP, - Stepper.DOWN, - Stepper.DOWN, + StepperDir.TOGGLE, + StepperDir.UP, + StepperDir.DOWN, + StepperDir.DOWN, ), ( MinMax(0, 10), 10, - Stepper.TOGGLE, - Stepper.UP, - Stepper.DOWN, - Stepper.DOWN, + StepperDir.TOGGLE, + StepperDir.UP, + StepperDir.DOWN, + StepperDir.DOWN, ), ( MinMax(0, 10), 10, - Stepper.TOGGLE, - Stepper.DOWN, - Stepper.DOWN, - Stepper.DOWN, + StepperDir.TOGGLE, + StepperDir.DOWN, + StepperDir.DOWN, + StepperDir.DOWN, ), ( MinMax(0, 10), 0, - Stepper.TOGGLE, - Stepper.DOWN, - Stepper.UP, - Stepper.UP, + StepperDir.TOGGLE, + StepperDir.DOWN, + StepperDir.UP, + StepperDir.UP, ), ( MinMax(0, 10), 0, - Stepper.TOGGLE, - Stepper.UP, - Stepper.UP, - Stepper.UP, + StepperDir.TOGGLE, + StepperDir.UP, + StepperDir.UP, + StepperDir.UP, ), ( MinMax(1, 255), 255, - Stepper.TOGGLE, - Stepper.UP, - Stepper.DOWN, - Stepper.DOWN, + StepperDir.TOGGLE, + StepperDir.UP, + StepperDir.DOWN, + StepperDir.DOWN, ), ( MinMax(1, 255), 254, - Stepper.TOGGLE, - Stepper.UP, - Stepper.DOWN, - Stepper.DOWN, + StepperDir.TOGGLE, + StepperDir.UP, + StepperDir.DOWN, + StepperDir.DOWN, ), ( MinMax(1, 255), 253, - Stepper.TOGGLE, - Stepper.UP, - Stepper.DOWN, - Stepper.DOWN, + StepperDir.TOGGLE, + StepperDir.UP, + StepperDir.DOWN, + StepperDir.DOWN, ), ( MinMax(1, 255), 1, - Stepper.TOGGLE, - Stepper.UP, - Stepper.UP, - Stepper.UP, + StepperDir.TOGGLE, + StepperDir.UP, + StepperDir.UP, + StepperDir.UP, ), ( MinMax(1, 255), 5, - Stepper.TOGGLE, - Stepper.UP, - Stepper.UP, - Stepper.UP, + StepperDir.TOGGLE, + StepperDir.UP, + StepperDir.UP, + StepperDir.UP, ), ], ) @@ -124,16 +125,16 @@ def test_stop_stepper_get_direction( @pytest.mark.parametrize( "min_max, value, steps, direction, expected_value, expected_exceeded", [ - (MinMax(0, 10), 5, 10, Stepper.DOWN, 4, False), - (MinMax(0, 10), 5, 10, Stepper.UP, 6, False), - (MinMax(0, 10), 1, 10, Stepper.DOWN, 0, True), - (MinMax(0, 10), 9, 10, Stepper.UP, 10, True), - (MinMax(0, 10), 0, 10, Stepper.DOWN, 0, True), - (MinMax(0, 10), 10, 10, Stepper.UP, 10, True), - (MinMax(0, 10), -1, 10, Stepper.DOWN, 0, True), - (MinMax(0, 10), 11, 10, Stepper.UP, 10, True), - (MinMax(0, 10), 6, 5, Stepper.DOWN, 4, False), - (MinMax(0, 10), 4, 5, Stepper.UP, 6, False), + (MinMax(0, 10), 5, 10, StepperDir.DOWN, 4, False), + (MinMax(0, 10), 5, 10, StepperDir.UP, 6, False), + (MinMax(0, 10), 1, 10, StepperDir.DOWN, 0, True), + (MinMax(0, 10), 9, 10, StepperDir.UP, 10, True), + (MinMax(0, 10), 0, 10, StepperDir.DOWN, 0, True), + (MinMax(0, 10), 10, 10, StepperDir.UP, 10, True), + (MinMax(0, 10), -1, 10, StepperDir.DOWN, 0, True), + (MinMax(0, 10), 11, 10, StepperDir.UP, 10, True), + (MinMax(0, 10), 6, 5, StepperDir.DOWN, 4, False), + (MinMax(0, 10), 4, 5, StepperDir.UP, 6, False), ], ) def test_stop_stepper_step( diff --git a/tests/unit_tests/cx_core/type/light_controller_test.py b/tests/unit_tests/cx_core/type/light_controller_test.py index ac36bb43..3fed81c0 100644 --- a/tests/unit_tests/cx_core/type/light_controller_test.py +++ b/tests/unit_tests/cx_core/type/light_controller_test.py @@ -1,11 +1,14 @@ -from typing import Any, Dict, Set, Union +from typing import Any, Dict, Set, Type, Union import pytest from _pytest.monkeypatch import MonkeyPatch +from cx_const import StepperDir, StepperMode from cx_core import LightController, ReleaseHoldController from cx_core.controller import Controller from cx_core.feature_support.light import LightSupport from cx_core.stepper import MinMax, Stepper +from cx_core.stepper.bounce_stepper import BounceStepper +from cx_core.stepper.index_loop_stepper import IndexLoopStepper from cx_core.stepper.loop_stepper import LoopStepper from cx_core.stepper.stop_stepper import StopStepper from cx_core.type.light_controller import ColorMode, LightEntity @@ -210,13 +213,48 @@ async def test_get_value_attribute( assert output == float(expected_output) +@pytest.mark.parametrize( + "attribute, mode, expected_stepper, error_expected", + [ + (LightController.ATTRIBUTE_BRIGHTNESS, StepperMode.STOP, StopStepper, False), + (LightController.ATTRIBUTE_COLOR_TEMP, StepperMode.LOOP, LoopStepper, False), + ( + LightController.ATTRIBUTE_WHITE_VALUE, + StepperMode.BOUNCE, + BounceStepper, + False, + ), + (LightController.ATTRIBUTE_XY_COLOR, StepperMode.STOP, IndexLoopStepper, False), + ( + LightController.ATTRIBUTE_BRIGHTNESS, + "this-stepper-mode-does-not-exist", + StopStepper, + True, + ), + ], +) +def test_get_stepper( + sut: LightController, + attribute: str, + mode: str, + expected_stepper: Type[Stepper], + error_expected: bool, +) -> None: + with wrap_execution(error_expected=error_expected, exception=ValueError): + output_stepper = sut.get_stepper(attribute, 10, mode) + + assert isinstance(output_stepper, expected_stepper) + if attribute != LightController.ATTRIBUTE_XY_COLOR: + assert output_stepper.min_max == sut.min_max_attributes[attribute] + + @pytest.mark.parametrize( "old, attribute, direction, stepper, smooth_power_on_check, stop_expected, expected_value_attribute", [ ( 50, LightController.ATTRIBUTE_BRIGHTNESS, - Stepper.UP, + StepperDir.UP, StopStepper(MinMax(1, 255), 254), False, False, @@ -225,7 +263,7 @@ async def test_get_value_attribute( ( 0, "xy_color", - Stepper.UP, + StepperDir.UP, LoopStepper(MinMax(0, 30), 30), False, False, @@ -234,7 +272,7 @@ async def test_get_value_attribute( ( 499, "color_temp", - Stepper.UP, + StepperDir.UP, StopStepper(MinMax(153, 500), 10), False, True, @@ -243,7 +281,7 @@ async def test_get_value_attribute( ( 0, LightController.ATTRIBUTE_BRIGHTNESS, - Stepper.UP, + StepperDir.UP, StopStepper(MinMax(1, 255), 254), True, True, @@ -534,11 +572,62 @@ async def fake_get_attribute(*args, **kwargs): @pytest.mark.parametrize( - "attribute_input, direction_input, light_state, smooth_power_on, expected_calls", + "attribute_input, direction_input, mode, light_state, smooth_power_on, expected_calls, error_expected", [ - (LightController.ATTRIBUTE_BRIGHTNESS, Stepper.UP, "off", True, 1), - (LightController.ATTRIBUTE_COLOR_TEMP, Stepper.UP, "off", True, 0), - (LightController.ATTRIBUTE_COLOR_TEMP, Stepper.UP, "on", True, 1), + ( + LightController.ATTRIBUTE_BRIGHTNESS, + StepperDir.UP, + StepperMode.STOP, + "off", + True, + 1, + False, + ), + ( + LightController.ATTRIBUTE_COLOR_TEMP, + StepperDir.UP, + StepperMode.STOP, + "off", + True, + 0, + False, + ), + ( + LightController.ATTRIBUTE_COLOR_TEMP, + StepperDir.UP, + StepperMode.STOP, + "on", + True, + 1, + False, + ), + ( + "this-attr-does-not-exist", + StepperDir.UP, + StepperMode.STOP, + "on", + False, + 0, + True, + ), + ( + LightController.ATTRIBUTE_BRIGHTNESS, + "toggle", + StepperMode.STOP, + "on", + False, + 0, + True, + ), + ( + LightController.ATTRIBUTE_BRIGHTNESS, + StepperDir.UP, + StepperMode.BOUNCE, + "on", + True, + 0, + True, + ), ], ) @pytest.mark.asyncio @@ -548,9 +637,11 @@ async def test_click( mocker: MockerFixture, attribute_input: str, direction_input: Literal["up", "down"], + mode: Literal["stop", "loop"], light_state: Literal["on", "off"], smooth_power_on: bool, expected_calls: int, + error_expected: bool, ): value_attribute = 10 monkeypatch.setattr( @@ -568,33 +659,91 @@ async def test_click( mocker.patch.object(sut, "get_stepper", return_value=StopStepper(MinMax(1, 10), 10)) - await sut.click(attribute_input, direction_input) + with wrap_execution(error_expected=error_expected, exception=ValueError): + await sut.click(attribute_input, direction_input, mode=mode) assert change_light_state_patch.call_count == expected_calls @pytest.mark.parametrize( - "attribute_input, direction_input, previous_direction, light_state, smooth_power_on, expected_calls, expected_direction", + "attribute_input, direction_input, mode, previous_direction, light_state, smooth_power_on, expected_calls, expected_direction, error_expected", [ ( LightController.ATTRIBUTE_BRIGHTNESS, - Stepper.UP, - Stepper.UP, + StepperDir.UP, + StepperMode.STOP, + StepperDir.UP, "off", True, 1, - Stepper.UP, + StepperDir.UP, + False, ), - ("color_temp", Stepper.UP, Stepper.UP, "off", True, 0, Stepper.UP), - ("color_temp", Stepper.UP, Stepper.UP, "on", True, 1, Stepper.UP), ( - "color_temp", - Stepper.TOGGLE, - Stepper.DOWN, + LightController.ATTRIBUTE_COLOR_TEMP, + StepperDir.UP, + StepperMode.STOP, + StepperDir.UP, + "off", + True, + 0, + StepperDir.UP, + False, + ), + ( + LightController.ATTRIBUTE_COLOR_TEMP, + StepperDir.UP, + StepperMode.STOP, + StepperDir.UP, + "on", + True, + 1, + StepperDir.UP, + False, + ), + ( + LightController.ATTRIBUTE_COLOR_TEMP, + StepperDir.TOGGLE, + StepperMode.STOP, + StepperDir.DOWN, "on", True, 1, - Stepper.DOWN, + StepperDir.DOWN, + False, + ), + ( + "this-attr-does-not-exist", + StepperDir.UP, + StepperMode.STOP, + StepperDir.UP, + "on", + True, + 0, + StepperDir.UP, + True, + ), + ( + LightController.ATTRIBUTE_BRIGHTNESS, + "this-dir-does-not-exist", + StepperMode.STOP, + StepperDir.UP, + "on", + True, + 0, + StepperDir.UP, + True, + ), + ( + LightController.ATTRIBUTE_BRIGHTNESS, + StepperDir.UP, + "this-mode-does-not-exist", + StepperDir.UP, + "on", + True, + 0, + StepperDir.UP, + True, ), ], ) @@ -604,12 +753,14 @@ async def test_hold( monkeypatch: MonkeyPatch, mocker: MockerFixture, attribute_input: str, - direction_input: str, + direction_input: Literal["up", "down", "toogle"], + mode: Literal["stop", "loop", "bounce"], previous_direction: str, light_state: Literal["on", "off"], smooth_power_on: bool, expected_calls: int, expected_direction: str, + error_expected: bool, ): value_attribute = 10 monkeypatch.setattr( @@ -628,7 +779,8 @@ async def test_hold( mocker.patch.object(sut, "get_stepper", return_value=stepper) super_hold_patch = mocker.patch.object(ReleaseHoldController, "hold") - await sut.hold(attribute_input, direction_input) + with wrap_execution(error_expected=error_expected, exception=ValueError): + await sut.hold(attribute_input, direction_input, mode=mode) assert super_hold_patch.call_count == expected_calls if expected_calls > 0: @@ -643,7 +795,7 @@ async def test_hold_loop( sut: LightController, mocker: MockerFixture, value_attribute: int ): attribute = "test_attribute" - direction = Stepper.UP + direction = StepperDir.UP sut.smooth_power_on_check = False sut.value_attribute = value_attribute change_light_state_patch = mocker.patch.object(sut, "change_light_state") diff --git a/tests/unit_tests/cx_core/type/media_player_controller_test.py b/tests/unit_tests/cx_core/type/media_player_controller_test.py index f8ca04be..75d8cdab 100644 --- a/tests/unit_tests/cx_core/type/media_player_controller_test.py +++ b/tests/unit_tests/cx_core/type/media_player_controller_test.py @@ -2,10 +2,10 @@ import pytest from _pytest.monkeypatch import MonkeyPatch +from cx_const import StepperDir from cx_core import MediaPlayerController, ReleaseHoldController from cx_core.controller import Controller from cx_core.feature_support.media_player import MediaPlayerSupport -from cx_core.stepper import Stepper from pytest_mock.plugin import MockerFixture from typing_extensions import Literal @@ -171,10 +171,10 @@ async def test_hold(sut: MediaPlayerController, mocker: MockerFixture): @pytest.mark.parametrize( "direction_input, volume_set_support, volume_level, expected_volume_level", [ - (Stepper.UP, True, 0, 0.1), - (Stepper.DOWN, True, 0.5, 0.4), - (Stepper.UP, False, None, None), - (Stepper.DOWN, False, None, None), + (StepperDir.UP, True, 0, 0.1), + (StepperDir.DOWN, True, 0.5, 0.4), + (StepperDir.UP, False, None, None), + (StepperDir.DOWN, False, None, None), ], ) @pytest.mark.asyncio @@ -209,15 +209,15 @@ async def test_hold_loop( @pytest.mark.parametrize( "direction_input, source_list, active_source, expected_calls, expected_source", [ - (Stepper.UP, ["radio1", "radio2", "radio3"], "radio1", 1, "radio2"), - (Stepper.UP, ["radio1", "radio2", "radio3"], "radio3", 1, "radio1"), - (Stepper.DOWN, ["radio1", "radio2", "radio3"], "radio1", 1, "radio3"), - (Stepper.UP, ["radio1"], "radio1", 1, "radio1"), - (Stepper.DOWN, ["radio1"], "radio1", 1, "radio1"), - (Stepper.UP, ["radio1", "radio2", "radio3"], None, 1, "radio1"), - (Stepper.DOWN, ["radio1", "radio2", "radio3"], None, 1, "radio1"), - (Stepper.UP, [], None, 0, None), - (Stepper.DOWN, [], None, 0, None), + (StepperDir.UP, ["radio1", "radio2", "radio3"], "radio1", 1, "radio2"), + (StepperDir.UP, ["radio1", "radio2", "radio3"], "radio3", 1, "radio1"), + (StepperDir.DOWN, ["radio1", "radio2", "radio3"], "radio1", 1, "radio3"), + (StepperDir.UP, ["radio1"], "radio1", 1, "radio1"), + (StepperDir.DOWN, ["radio1"], "radio1", 1, "radio1"), + (StepperDir.UP, ["radio1", "radio2", "radio3"], None, 1, "radio1"), + (StepperDir.DOWN, ["radio1", "radio2", "radio3"], None, 1, "radio1"), + (StepperDir.UP, [], None, 0, None), + (StepperDir.DOWN, [], None, 0, None), ], ) @pytest.mark.asyncio