diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index af04b3f7..f5b1b124 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -8,6 +8,7 @@ PRERELEASE_NOTE ## :pencil2: Features +- Add Zigbee2MQTT Light Controller (`Z2MLightController`). Until now we had an option to listen from MQTT, but light commands will always go through HA Light integration. This new controller allows you to interact directly with Zigbe2MQTT commands to interact with your lights. This means that you can leverage the `hold` actions that Zigbee2MQTT offers with barely no lag and much more smoother than `Light Controller` hold actions. However, it is not as flexible and does not offer as many options as `Light Controller` does. Many of the existing devices now have support to `Z2MLightController`, and you can use it in the `class` as you can now use `LightController` as well. You can read more about it [here](https://BASE_URL/controllerx/others/zigbee2mqtt-light-controller). [ #118, #168 ] - Allow passing the delay time (in seconds) to `release_delay` attribute. [ #497 ] ## :hammer: Fixes diff --git a/apps/controllerx/cx_const.py b/apps/controllerx/cx_const.py index 268dcb66..478a7ccb 100644 --- a/apps/controllerx/cx_const.py +++ b/apps/controllerx/cx_const.py @@ -92,8 +92,14 @@ class Z2MLight: HOLD = "hold" HOLD_BRIGHTNESS_UP = "hold_brightness_up" HOLD_BRIGHTNESS_DOWN = "hold_brightness_down" + HOLD_BRIGHTNESS_TOGGLE = "hold_brightness_toggle" HOLD_COLOR_TEMP_UP = "hold_colortemp_up" HOLD_COLOR_TEMP_DOWN = "hold_colortemp_down" + HOLD_COLOR_TEMP_TOGGLE = "hold_colortemp_toggle" + XYCOLOR_FROM_CONTROLLER = "xycolor_from_controller" + COLORTEMP_FROM_CONTROLLER = "colortemp_from_controller" + BRIGHTNESS_FROM_CONTROLLER_LEVEL = "brightness_from_controller_level" + BRIGHTNESS_FROM_CONTROLLER_ANGLE = "brightness_from_controller_angle" class MediaPlayer: diff --git a/apps/controllerx/cx_core/stepper/__init__.py b/apps/controllerx/cx_core/stepper/__init__.py index 7d42538b..5d4caa47 100644 --- a/apps/controllerx/cx_core/stepper/__init__.py +++ b/apps/controllerx/cx_core/stepper/__init__.py @@ -92,3 +92,8 @@ def step(self, value: Number, direction: str) -> StepperOutput: None, the loop will stop executing. """ raise NotImplementedError + + +class InvertStepper(Stepper): + def step(self, value: Number, direction: str) -> StepperOutput: + return StepperOutput(self.apply_sign(value, direction), next_direction=None) diff --git a/apps/controllerx/cx_core/type/light_controller.py b/apps/controllerx/cx_core/type/light_controller.py index 3ede9b2a..43380239 100644 --- a/apps/controllerx/cx_core/type/light_controller.py +++ b/apps/controllerx/cx_core/type/light_controller.py @@ -646,7 +646,9 @@ async def is_colortemp_supported(self) -> bool: return "color_temp" in await self.supported_color_modes @lru_cache(maxsize=None) - def get_stepper(self, attribute: str, steps: Number, mode: str) -> Stepper: + def get_stepper( + self, attribute: str, steps: Number, mode: str, *, tag: str + ) -> Stepper: previous_direction = Stepper.invert_direction(self.hold_toggle_direction_init) if attribute == LightController.ATTRIBUTE_XY_COLOR: return IndexLoopStepper(len(self.color_wheel), previous_direction) @@ -764,7 +766,7 @@ async def click( self.value_attribute, attribute, direction, - self.get_stepper(attribute, steps or self.manual_steps, mode), + self.get_stepper(attribute, steps or self.manual_steps, mode, tag="click"), "click", ) @@ -804,7 +806,9 @@ async def _hold( f"Attribute value before running the hold action: {self.value_attribute}", level="DEBUG", ) - stepper = self.get_stepper(attribute, steps or self.automatic_steps, mode) + stepper = self.get_stepper( + attribute, steps or self.automatic_steps, mode, tag="hold" + ) if direction == StepperDir.TOGGLE: self.log( f"Previous direction: {stepper.previous_direction}", diff --git a/apps/controllerx/cx_core/type/z2m_light_controller.py b/apps/controllerx/cx_core/type/z2m_light_controller.py index 2b4146fa..75504506 100644 --- a/apps/controllerx/cx_core/type/z2m_light_controller.py +++ b/apps/controllerx/cx_core/type/z2m_light_controller.py @@ -1,10 +1,13 @@ import asyncio import json +from functools import lru_cache from typing import Any, Dict, List, Optional, Set, Type from cx_const import PredefinedActionsMapping, StepperDir, Z2MLight from cx_core.controller import action -from cx_core.stepper import MinMax, Stepper +from cx_core.integration import EventData +from cx_core.integration.z2m import Z2MIntegration +from cx_core.stepper import InvertStepper, MinMax from cx_core.type_controller import Entity, TypeController DEFAULT_CLICK_STEPS = 70 @@ -159,6 +162,13 @@ def get_predefined_actions_mapping(self) -> PredefinedActionsMapping: StepperDir.DOWN, ), ), + Z2MLight.HOLD_BRIGHTNESS_TOGGLE: ( + self.hold, + ( + Z2MLightController.ATTRIBUTE_BRIGHTNESS, + StepperDir.TOGGLE, + ), + ), Z2MLight.HOLD_COLOR_TEMP_DOWN: ( self.hold, ( @@ -166,6 +176,17 @@ def get_predefined_actions_mapping(self) -> PredefinedActionsMapping: StepperDir.DOWN, ), ), + Z2MLight.HOLD_COLOR_TEMP_TOGGLE: ( + self.hold, + ( + Z2MLightController.ATTRIBUTE_COLOR_TEMP, + StepperDir.TOGGLE, + ), + ), + Z2MLight.XYCOLOR_FROM_CONTROLLER: self.xycolor_from_controller, + Z2MLight.COLORTEMP_FROM_CONTROLLER: self.colortemp_from_controller, + Z2MLight.BRIGHTNESS_FROM_CONTROLLER_LEVEL: self.brightness_from_controller_level, + Z2MLight.BRIGHTNESS_FROM_CONTROLLER_ANGLE: self.brightness_from_controller_angle, } async def _mqtt_call(self, payload: Dict[str, Any]) -> None: @@ -222,27 +243,28 @@ async def _on_min(self, attribute: str) -> None: async def on_min(self, attribute: str) -> None: await self._on_min(attribute) + @lru_cache(maxsize=None) + def get_stepper(self, attribute: str, steps: float, *, tag: str) -> InvertStepper: + previous_direction = StepperDir.DOWN + return InvertStepper(self.MIN_MAX_ATTR[attribute], steps, previous_direction) + async def _change_light_state( self, *, attribute: str, direction: str, - steps: float, + stepper: InvertStepper, transition: float, use_onoff: bool, mode: str, ) -> None: - attribute = self.get_option(attribute, self.ATTRIBUTES_LIST, "`click` action") - direction = self.get_option( - direction, [StepperDir.UP, StepperDir.DOWN], "`click` action" - ) - onoff_cmd = ( "_onoff" if use_onoff and attribute == self.ATTRIBUTE_BRIGHTNESS else "" ) + stepper_output = stepper.step(stepper.steps, direction) await self._mqtt_call( { - f"{attribute}_{mode}{onoff_cmd}": Stepper.apply_sign(steps, direction), + f"{attribute}_{mode}{onoff_cmd}": stepper_output.next_value, "transition": transition, } ) @@ -256,32 +278,56 @@ async def click( transition: Optional[float] = None, use_onoff: Optional[bool] = None, ) -> None: + attribute = self.get_option(attribute, self.ATTRIBUTES_LIST, "`click` action") + direction = self.get_option( + direction, [StepperDir.UP, StepperDir.DOWN], "`click` action" + ) + steps = steps if steps is not None else self.click_steps + stepper = self.get_stepper(attribute, steps, tag="click") await self._change_light_state( attribute=attribute, direction=direction, - steps=steps if steps is not None else self.click_steps, + stepper=stepper, transition=transition if transition is not None else self.transition, use_onoff=use_onoff if use_onoff is not None else self.use_onoff, mode="step", ) - @action - async def hold( + async def _hold( self, attribute: str, direction: str, steps: Optional[float] = None, use_onoff: Optional[bool] = None, ) -> None: + attribute = self.get_option(attribute, self.ATTRIBUTES_LIST, "`hold` action") + direction = self.get_option( + direction, + [StepperDir.UP, StepperDir.DOWN, StepperDir.TOGGLE], + "`hold` action", + ) + steps = steps if steps is not None else self.hold_steps + stepper = self.get_stepper(attribute, steps, tag="hold") + direction = stepper.get_direction(steps, direction) await self._change_light_state( attribute=attribute, direction=direction, - steps=steps if steps is not None else self.click_steps, + stepper=stepper, transition=self.transition, use_onoff=use_onoff if use_onoff is not None else self.use_onoff, mode="move", ) + @action + async def hold( + self, + attribute: str, + direction: str, + steps: Optional[float] = None, + use_onoff: Optional[bool] = None, + ) -> None: + await self._hold(attribute, direction, steps, use_onoff) + @action async def release(self) -> None: await asyncio.gather( @@ -290,3 +336,72 @@ async def release(self) -> None: for attribute in self.ATTRIBUTES_LIST ] ) + + @action + async def xycolor_from_controller(self, extra: Optional[EventData] = None) -> None: + if extra is None: + self.log("No event data present", level="WARNING") + return + if isinstance(self.integration, Z2MIntegration): + if "action_color" not in extra: + self.log( + "`action_color` is not present in the MQTT payload", level="WARNING" + ) + return + xy_color = extra["action_color"] + await self._on(color={"x": xy_color["x"], "y": xy_color["y"]}) + + @action + async def colortemp_from_controller( + self, extra: Optional[EventData] = None + ) -> None: + if extra is None: + self.log("No event data present", level="WARNING") + return + if isinstance(self.integration, Z2MIntegration): + if "action_color_temperature" not in extra: + self.log( + "`action_color_temperature` is not present in the MQTT payload", + level="WARNING", + ) + return + await self._on(color_temp=extra["action_color_temperature"]) + + @action + async def brightness_from_controller_level( + self, extra: Optional[EventData] = None + ) -> None: + if extra is None: + self.log("No event data present", level="WARNING") + return + if isinstance(self.integration, Z2MIntegration): + if "action_level" not in extra: + self.log( + "`action_level` is not present in the MQTT payload", + level="WARNING", + ) + return + await self._on(brightness=extra["action_level"]) + + @action + async def brightness_from_controller_angle( + self, + steps: Optional[float] = None, + use_onoff: Optional[bool] = None, + extra: Optional[EventData] = None, + ) -> None: + if extra is None: + self.log("No event data present", level="WARNING") + return + if isinstance(self.integration, Z2MIntegration): + if "action_rotation_angle" not in extra: + self.log( + "`action_rotation_angle` is not present in the MQTT payload", + level="WARNING", + ) + return + angle = extra["action_rotation_angle"] + direction = StepperDir.UP if angle > 0 else StepperDir.DOWN + await self._hold( + self.ATTRIBUTE_BRIGHTNESS, direction, steps=steps, use_onoff=use_onoff + ) diff --git a/apps/controllerx/cx_devices/aqara.py b/apps/controllerx/cx_devices/aqara.py index 80b82c83..b2d0c1b3 100644 --- a/apps/controllerx/cx_devices/aqara.py +++ b/apps/controllerx/cx_devices/aqara.py @@ -1,5 +1,10 @@ -from cx_const import DefaultActionsMapping, Light, MediaPlayer, Switch -from cx_core import LightController, MediaPlayerController, SwitchController +from cx_const import DefaultActionsMapping, Light, MediaPlayer, Switch, Z2MLight +from cx_core import ( + LightController, + MediaPlayerController, + SwitchController, + Z2MLightController, +) from cx_core.integration import EventData @@ -48,6 +53,21 @@ def get_zha_action(self, data: EventData) -> str: return command +class WXKG02LMZ2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "single_both": Z2MLight.TOGGLE, + "double_both": Z2MLight.CLICK_BRIGHTNESS_UP, + "hold_both": Z2MLight.CLICK_BRIGHTNESS_DOWN, + "single_left": Z2MLight.TOGGLE, + "double_left": Z2MLight.CLICK_BRIGHTNESS_UP, + "hold_left": Z2MLight.CLICK_BRIGHTNESS_DOWN, + "single_right": Z2MLight.TOGGLE, + "double_right": Z2MLight.CLICK_BRIGHTNESS_UP, + "hold_right": Z2MLight.CLICK_BRIGHTNESS_DOWN, + } + + class WXKG02LMSwitchController(SwitchController): def get_z2m_actions_mapping(self) -> DefaultActionsMapping: return { @@ -119,6 +139,19 @@ def get_zha_action(self, data: EventData) -> str: return data["command"] +class WXKG01LMZ2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "single": Z2MLight.TOGGLE, + "double": Z2MLight.ON_FULL_BRIGHTNESS, + "triple": Z2MLight.ON_MIN_BRIGHTNESS, + "quadruple": Z2MLight.SET_HALF_BRIGHTNESS, + # "many": "", # Nothing + "hold": Z2MLight.HOLD_BRIGHTNESS_TOGGLE, + "release": Z2MLight.RELEASE, + } + + class WXKG11LMRemoteLightController(LightController): def get_z2m_actions_mapping(self) -> DefaultActionsMapping: return { @@ -149,6 +182,16 @@ def get_zha_action(self, data: EventData) -> str: return command +class WXKG11LMRemoteZ2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "single": Z2MLight.TOGGLE, + "double": Z2MLight.ON_FULL_BRIGHTNESS, + "hold": Z2MLight.HOLD_BRIGHTNESS_TOGGLE, + "release": Z2MLight.RELEASE, + } + + class WXKG11LMSensorSwitchLightController(LightController): def get_deconz_actions_mapping(self) -> DefaultActionsMapping: return { @@ -202,6 +245,17 @@ def get_deconz_actions_mapping(self) -> DefaultActionsMapping: } +class WXKG12LMZ2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "single": Z2MLight.TOGGLE, + "double": Z2MLight.ON_FULL_BRIGHTNESS, + "shake": Z2MLight.ON_MIN_BRIGHTNESS, + "hold": Z2MLight.HOLD_BRIGHTNESS_TOGGLE, + "release": Z2MLight.RELEASE, + } + + class MFKZQ01LMLightController(LightController): """ This controller allows movement actions for Xiaomi Aqara Smart Cube as @@ -270,6 +324,20 @@ def get_z2m_actions_mapping(self) -> DefaultActionsMapping: } +class WXCJKG11LMZ2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "button_1_single": Z2MLight.OFF, + "button_1_double": Z2MLight.ON_MIN_BRIGHTNESS, + "button_1_hold": Z2MLight.HOLD_BRIGHTNESS_DOWN, + "button_1_release": Z2MLight.RELEASE, + "button_2_single": Z2MLight.ON, + "button_2_double": Z2MLight.ON_FULL_BRIGHTNESS, + "button_2_hold": Z2MLight.HOLD_BRIGHTNESS_UP, + "button_2_release": Z2MLight.RELEASE, + } + + class WXCJKG12LMLightController(LightController): def get_z2m_actions_mapping(self) -> DefaultActionsMapping: return { @@ -324,6 +392,32 @@ def get_zha_action(self, data: EventData) -> str: return command +class WXCJKG12LMZ2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "button_1_single": Z2MLight.OFF, + "button_1_double": Z2MLight.ON_MIN_COLOR_TEMP, + # "button_1_triple": "", + "button_1_hold": Z2MLight.HOLD_COLOR_TEMP_DOWN, + "button_1_release": Z2MLight.RELEASE, + "button_2_single": Z2MLight.ON, + "button_2_double": Z2MLight.ON_FULL_COLOR_TEMP, + # "button_2_triple": "", + "button_2_hold": Z2MLight.HOLD_COLOR_TEMP_UP, + "button_2_release": Z2MLight.RELEASE, + "button_3_single": Z2MLight.CLICK_BRIGHTNESS_DOWN, + "button_3_double": Z2MLight.ON_MIN_BRIGHTNESS, + # "button_3_triple": "", + "button_3_hold": Z2MLight.HOLD_BRIGHTNESS_DOWN, + "button_3_release": Z2MLight.RELEASE, + "button_4_single": Z2MLight.CLICK_BRIGHTNESS_UP, + "button_4_double": Z2MLight.ON_FULL_BRIGHTNESS, + # "button_4_triple": "", + "button_4_hold": Z2MLight.HOLD_BRIGHTNESS_UP, + "button_4_release": Z2MLight.RELEASE, + } + + class WXCJKG13LMLightController(LightController): def get_z2m_actions_mapping(self) -> DefaultActionsMapping: return { @@ -432,6 +526,42 @@ def get_zha_action(self, data: EventData) -> str: return command +class WXCJKG13LMZ2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "button_1_single": Z2MLight.OFF, + "button_1_double": Z2MLight.ON_FULL_BRIGHTNESS, + # "button_1_triple": "", # Nothing + # "button_1_hold": "", # Nothing + # "button_1_release": "", # Nothing + "button_2_single": Z2MLight.ON, + "button_2_double": Z2MLight.ON_FULL_BRIGHTNESS, + # "button_2_triple": "", # Nothing + # "button_2_hold": "", # Nothing + # "button_2_release": "", # Nothing + "button_3_single": Z2MLight.CLICK_BRIGHTNESS_DOWN, + "button_3_double": Z2MLight.ON_MIN_BRIGHTNESS, + # "button_3_triple": "", # Nothing + "button_3_hold": Z2MLight.HOLD_BRIGHTNESS_DOWN, + "button_3_release": Z2MLight.RELEASE, + "button_4_single": Z2MLight.CLICK_BRIGHTNESS_UP, + "button_4_double": Z2MLight.ON_FULL_BRIGHTNESS, + # "button_4_triple": "", # Nothing + "button_4_hold": Z2MLight.HOLD_BRIGHTNESS_UP, + "button_4_release": Z2MLight.RELEASE, + "button_5_single": Z2MLight.CLICK_COLOR_TEMP_DOWN, + "button_5_double": Z2MLight.ON_MIN_COLOR_TEMP, + # "button_5_triple": "", # Nothing + "button_5_hold": Z2MLight.HOLD_COLOR_TEMP_DOWN, + "button_5_release": Z2MLight.RELEASE, + "button_6_single": Z2MLight.CLICK_COLOR_TEMP_UP, + "button_6_double": Z2MLight.ON_FULL_COLOR_TEMP, + # "button_6_triple": "", # Nothing + "button_6_hold": Z2MLight.HOLD_COLOR_TEMP_UP, + "button_6_release": Z2MLight.RELEASE, + } + + class WXKG06LMLightController(LightController): def get_z2m_actions_mapping(self) -> DefaultActionsMapping: return { diff --git a/apps/controllerx/cx_devices/ikea.py b/apps/controllerx/cx_devices/ikea.py index bd2a15be..b6e7ee6e 100644 --- a/apps/controllerx/cx_devices/ikea.py +++ b/apps/controllerx/cx_devices/ikea.py @@ -191,6 +191,17 @@ def get_zha_actions_mapping(self) -> DefaultActionsMapping: } +class E1743Z2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "on": Z2MLight.ON, + "off": Z2MLight.OFF, + "brightness_move_up": Z2MLight.HOLD_BRIGHTNESS_UP, + "brightness_move_down": Z2MLight.HOLD_BRIGHTNESS_DOWN, + "brightness_stop": Z2MLight.RELEASE, + } + + class E1743MediaPlayerController(MediaPlayerController): # Different states reported from the controller: # on, off, brightness_up, brightness_down, brightness_stop @@ -421,6 +432,18 @@ def default_delay(self) -> int: return 500 +class E1744Z2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "brightness_move_down": Z2MLight.HOLD_BRIGHTNESS_DOWN, + "brightness_move_up": Z2MLight.HOLD_BRIGHTNESS_UP, + "brightness_stop": Z2MLight.RELEASE, + "toggle": Z2MLight.TOGGLE, + "brightness_step_up": Z2MLight.ON_FULL_BRIGHTNESS, + "brightness_step_down": Z2MLight.ON_MIN_BRIGHTNESS, + } + + class E1744MediaPlayerController(MediaPlayerController): # Different states reported from the controller: # brightness_move_down, brightness_move_up, brightness_stop, @@ -479,6 +502,11 @@ def get_zha_actions_mapping(self) -> DefaultActionsMapping: } +class E1766Z2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return {"open": Z2MLight.ON, "close": Z2MLight.OFF} + + class E1766SwitchController(SwitchController): def get_z2m_actions_mapping(self) -> DefaultActionsMapping: return {"open": Switch.ON, "close": Switch.OFF} @@ -541,6 +569,15 @@ def get_zha_action(self, data: EventData) -> str: return command +class E1812Z2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "on": Z2MLight.TOGGLE, + "brightness_move_up": Z2MLight.HOLD_BRIGHTNESS_TOGGLE, + "brightness_stop": Z2MLight.RELEASE, + } + + class E1812SwitchController(SwitchController): def get_z2m_actions_mapping(self) -> DefaultActionsMapping: return {"on": Switch.TOGGLE} diff --git a/apps/controllerx/cx_devices/legrand.py b/apps/controllerx/cx_devices/legrand.py index 25308084..680d21d6 100644 --- a/apps/controllerx/cx_devices/legrand.py +++ b/apps/controllerx/cx_devices/legrand.py @@ -1,7 +1,7 @@ from typing import Any, Dict -from cx_const import DefaultActionsMapping, Light -from cx_core import LightController +from cx_const import DefaultActionsMapping, Light, Z2MLight +from cx_core import LightController, Z2MLightController from cx_core.integration import EventData @@ -40,6 +40,17 @@ def get_zha_action(self, data: EventData) -> str: return get_zha_action_LegrandWallController(data) +class Legrand600083Z2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "on": Z2MLight.ON, + "off": Z2MLight.OFF, + "brightness_move_up": Z2MLight.HOLD_BRIGHTNESS_UP, + "brightness_move_down": Z2MLight.HOLD_BRIGHTNESS_DOWN, + "brightness_stop": Z2MLight.RELEASE, + } + + class Legrand600088LightController(LightController): def get_z2m_actions_mapping(self) -> DefaultActionsMapping: return { @@ -71,3 +82,19 @@ def get_zha_actions_mapping(self) -> DefaultActionsMapping: def get_zha_action(self, data: EventData) -> str: return get_zha_action_LegrandWallController(data) + + +class Legrand600088Z2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "on_left": Z2MLight.ON, + "off_left": Z2MLight.OFF, + "brightness_move_up_left": Z2MLight.HOLD_COLOR_TEMP_UP, + "brightness_move_down_left": Z2MLight.HOLD_COLOR_TEMP_DOWN, + "brightness_stop_left": Z2MLight.RELEASE, + "on_right": Z2MLight.ON_FULL_BRIGHTNESS, + "off_right": Z2MLight.ON_MIN_BRIGHTNESS, + "brightness_move_up_right": Z2MLight.HOLD_BRIGHTNESS_UP, + "brightness_move_down_right": Z2MLight.HOLD_BRIGHTNESS_DOWN, + "brightness_stop_right": Z2MLight.RELEASE, + } diff --git a/apps/controllerx/cx_devices/linkind.py b/apps/controllerx/cx_devices/linkind.py index 5e978292..4759ecc4 100644 --- a/apps/controllerx/cx_devices/linkind.py +++ b/apps/controllerx/cx_devices/linkind.py @@ -1,5 +1,5 @@ -from cx_const import DefaultActionsMapping, Light -from cx_core import LightController +from cx_const import DefaultActionsMapping, Light, Z2MLight +from cx_core import LightController, Z2MLightController class ZS23000278LightController(LightController): @@ -18,3 +18,21 @@ def get_z2m_actions_mapping(self) -> DefaultActionsMapping: "color_temperature_move_down": Light.CLICK_COLOR_TEMP_DOWN, "color_move": Light.XYCOLOR_FROM_CONTROLLER, } + + +class ZS23000278Z2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "on": Z2MLight.ON, + "off": Z2MLight.OFF, + "brightness_step_up": Z2MLight.CLICK_BRIGHTNESS_UP, + "brightness_step_down": Z2MLight.CLICK_BRIGHTNESS_DOWN, + "brightness_move_to_level": Z2MLight.BRIGHTNESS_FROM_CONTROLLER_LEVEL, + "brightness_move_up": Z2MLight.HOLD_BRIGHTNESS_UP, + "brightness_move_down": Z2MLight.HOLD_BRIGHTNESS_DOWN, + "brightness_stop": Z2MLight.RELEASE, + "color_temperature_move": Z2MLight.COLORTEMP_FROM_CONTROLLER, + "color_temperature_move_up": Z2MLight.CLICK_COLOR_TEMP_UP, + "color_temperature_move_down": Z2MLight.CLICK_COLOR_TEMP_DOWN, + "color_move": Z2MLight.XYCOLOR_FROM_CONTROLLER, + } diff --git a/apps/controllerx/cx_devices/livarno.py b/apps/controllerx/cx_devices/livarno.py index 2f287cd8..055dd71b 100644 --- a/apps/controllerx/cx_devices/livarno.py +++ b/apps/controllerx/cx_devices/livarno.py @@ -1,5 +1,5 @@ -from cx_const import DefaultActionsMapping, Light -from cx_core import LightController +from cx_const import DefaultActionsMapping, Light, Z2MLight +from cx_core import LightController, Z2MLightController class HG06323LightController(LightController): @@ -28,3 +28,20 @@ def get_zha_actions_mapping(self) -> DefaultActionsMapping: "move_1_51": Light.HOLD_BRIGHTNESS_DOWN, "off": Light.OFF, } + + +class HG06323Z2MLightController(Z2MLightController): + # Different states reported from the controller: + # on, off, brightness_step_up, brightness_move_up, + # brightness_step_down, brightness_move_down, brightness_stop + + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "on": Z2MLight.ON, + "brightness_step_up": Z2MLight.CLICK_BRIGHTNESS_UP, + "brightness_move_up": Z2MLight.HOLD_BRIGHTNESS_UP, + "brightness_stop": Z2MLight.RELEASE, + "brightness_step_down": Z2MLight.CLICK_BRIGHTNESS_DOWN, + "brightness_move_down": Z2MLight.HOLD_BRIGHTNESS_DOWN, + "off": Z2MLight.OFF, + } diff --git a/apps/controllerx/cx_devices/muller_licht.py b/apps/controllerx/cx_devices/muller_licht.py index fa0ccd59..9fa34420 100644 --- a/apps/controllerx/cx_devices/muller_licht.py +++ b/apps/controllerx/cx_devices/muller_licht.py @@ -1,5 +1,5 @@ -from cx_const import DefaultActionsMapping, Light -from cx_core import LightController +from cx_const import DefaultActionsMapping, Light, Z2MLight +from cx_core import LightController, Z2MLightController from cx_core.controller import Controller from cx_core.integration import EventData @@ -46,6 +46,28 @@ def get_deconz_actions_mapping(self) -> DefaultActionsMapping: } +class MLI404011Z2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "on": Z2MLight.TOGGLE, + "off": Z2MLight.TOGGLE, + "brightness_down_click": Z2MLight.CLICK_BRIGHTNESS_DOWN, + "brightness_down_hold": Z2MLight.HOLD_BRIGHTNESS_DOWN, + "brightness_down_release": Z2MLight.RELEASE, + "brightness_up_click": Z2MLight.CLICK_BRIGHTNESS_UP, + "brightness_up_hold": Z2MLight.HOLD_BRIGHTNESS_UP, + "brightness_up_release": Z2MLight.RELEASE, + "color_wheel": Z2MLight.XYCOLOR_FROM_CONTROLLER, # Color ring press + "color_temp": Z2MLight.COLORTEMP_FROM_CONTROLLER, # warm or cold + # "scene_3": "", # reading button + # "scene_1": "", # sunset button + # "scene_2": "", # party button + # "scene_6": "", # night button + # "scene_4": "", # fire button + # "scene_5": "", # heart button + } + + class MLI404002Controller(Controller): def get_zha_action(self, data: EventData) -> str: command: str = data["command"] @@ -80,3 +102,17 @@ def get_zha_actions_mapping(self) -> DefaultActionsMapping: "step_down": Light.CLICK_BRIGHTNESS_DOWN, "recall": Light.ON_FULL_BRIGHTNESS, } + + +class MLI404002Z2MLightController(MLI404002Controller, Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "on": Z2MLight.TOGGLE, + "off": Z2MLight.TOGGLE, + "brightness_step_up": Z2MLight.CLICK_BRIGHTNESS_UP, + "brightness_step_down": Z2MLight.CLICK_BRIGHTNESS_DOWN, + "brightness_move_up": Z2MLight.HOLD_BRIGHTNESS_UP, + "brightness_move_down": Z2MLight.HOLD_BRIGHTNESS_DOWN, + "brightness_stop": Z2MLight.RELEASE, + "recall_1": Z2MLight.ON_FULL_BRIGHTNESS, + } diff --git a/apps/controllerx/cx_devices/philips.py b/apps/controllerx/cx_devices/philips.py index 7b8c1097..b9170694 100644 --- a/apps/controllerx/cx_devices/philips.py +++ b/apps/controllerx/cx_devices/philips.py @@ -1,5 +1,5 @@ -from cx_const import DefaultActionsMapping, Light -from cx_core import LightController +from cx_const import DefaultActionsMapping, Light, Z2MLight +from cx_core import LightController, Z2MLightController from cx_core.integration import EventData @@ -57,6 +57,24 @@ def get_zha_action(self, data: EventData) -> str: return command +class HueDimmerZ2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "on-press": Z2MLight.ON, + "on-hold": Z2MLight.HOLD_COLOR_TEMP_UP, + "on-hold-release": Z2MLight.RELEASE, + "up-press": Z2MLight.CLICK_BRIGHTNESS_UP, + "up-hold": Z2MLight.HOLD_BRIGHTNESS_UP, + "up-hold-release": Z2MLight.RELEASE, + "down-press": Z2MLight.CLICK_BRIGHTNESS_DOWN, + "down-hold": Z2MLight.HOLD_BRIGHTNESS_DOWN, + "down-hold-release": Z2MLight.RELEASE, + "off-press": Z2MLight.OFF, + "off-hold": Z2MLight.HOLD_COLOR_TEMP_DOWN, + "off-hold-release": Z2MLight.RELEASE, + } + + class Philips929002398602LightController(LightController): def get_z2m_actions_mapping(self) -> DefaultActionsMapping: return { @@ -75,6 +93,24 @@ def get_z2m_actions_mapping(self) -> DefaultActionsMapping: } +class Philips929002398602Z2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "on_press_release": Z2MLight.ON, + "on_hold": Z2MLight.HOLD_COLOR_TEMP_UP, + "on_hold_release": Z2MLight.RELEASE, + "up_press_release": Z2MLight.CLICK_BRIGHTNESS_UP, + "up_hold": Z2MLight.HOLD_BRIGHTNESS_UP, + "up_hold_release": Z2MLight.RELEASE, + "down_press_release": Z2MLight.CLICK_BRIGHTNESS_DOWN, + "down_hold": Z2MLight.HOLD_BRIGHTNESS_DOWN, + "down_hold_release": Z2MLight.RELEASE, + "off_press_release": Z2MLight.OFF, + "off_hold": Z2MLight.HOLD_COLOR_TEMP_DOWN, + "off_hold_release": Z2MLight.RELEASE, + } + + class PTM215XLightController(LightController): def get_z2m_actions_mapping(self) -> DefaultActionsMapping: return { @@ -156,3 +192,17 @@ def get_deconz_actions_mapping(self) -> DefaultActionsMapping: 2002: Light.TOGGLE, 2003: Light.RELEASE, } + + +class Philips929003017102Z2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "left_press": Z2MLight.TOGGLE, + # "left_press_release": "", + "left_hold": Z2MLight.HOLD_BRIGHTNESS_TOGGLE, + "left_hold_release": Z2MLight.RELEASE, + "right_press": Z2MLight.TOGGLE, + # "right_press_release": "", + "right_hold": Z2MLight.HOLD_BRIGHTNESS_TOGGLE, + "right_hold_release": Z2MLight.RELEASE, + } diff --git a/apps/controllerx/cx_devices/rgb_genie.py b/apps/controllerx/cx_devices/rgb_genie.py index feb6ee86..47921681 100644 --- a/apps/controllerx/cx_devices/rgb_genie.py +++ b/apps/controllerx/cx_devices/rgb_genie.py @@ -1,5 +1,5 @@ -from cx_const import DefaultActionsMapping, Light -from cx_core import LightController +from cx_const import DefaultActionsMapping, Light, Z2MLight +from cx_core import LightController, Z2MLightController from cx_core.controller import action from cx_core.integration import EventData from cx_core.integration.zha import ZHAIntegration @@ -69,3 +69,22 @@ def get_z2m_actions_mapping(self) -> DefaultActionsMapping: # "recall_3": "", # Scene 2 # "recall_2": "", # Scene 3 } + + +class ZB3009Z2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "on": Z2MLight.TOGGLE, + "off": Z2MLight.TOGGLE, + "brightness_move_up": Z2MLight.HOLD_BRIGHTNESS_UP, + "brightness_move_down": Z2MLight.HOLD_BRIGHTNESS_DOWN, + "brightness_stop": Z2MLight.RELEASE, + "color_temperature_move_down": Z2MLight.CLICK_COLOR_TEMP_DOWN, + "color_temperature_move_up": Z2MLight.CLICK_COLOR_TEMP_UP, + "color_temperature_move": Z2MLight.COLORTEMP_FROM_CONTROLLER, + "color_move": Z2MLight.XYCOLOR_FROM_CONTROLLER, + # "hue_move": "", # Play/pause button + # "recall_1": "", # Scene 1 + # "recall_3": "", # Scene 2 + # "recall_2": "", # Scene 3 + } diff --git a/apps/controllerx/cx_devices/robb.py b/apps/controllerx/cx_devices/robb.py index 8c821f59..6bf4e738 100644 --- a/apps/controllerx/cx_devices/robb.py +++ b/apps/controllerx/cx_devices/robb.py @@ -1,5 +1,5 @@ -from cx_const import DefaultActionsMapping, Light -from cx_core import LightController +from cx_const import DefaultActionsMapping, Light, Z2MLight +from cx_core import LightController, Z2MLightController class ROB2000070LightController(LightController): @@ -26,3 +26,29 @@ def get_z2m_actions_mapping(self) -> DefaultActionsMapping: "brightness_move_down_4": Light.HOLD_BRIGHTNESS_DOWN, "brightness_stop_4": Light.RELEASE, } + + +class ROB2000070Z2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "on_1": Z2MLight.ON, + "off_1": Z2MLight.OFF, + "brightness_move_up_1": Z2MLight.HOLD_BRIGHTNESS_UP, + "brightness_move_down_1": Z2MLight.HOLD_BRIGHTNESS_DOWN, + "brightness_stop_1": Z2MLight.RELEASE, + "on_2": Z2MLight.ON, + "off_2": Z2MLight.OFF, + "brightness_move_up_2": Z2MLight.HOLD_BRIGHTNESS_UP, + "brightness_move_down_2": Z2MLight.HOLD_BRIGHTNESS_DOWN, + "brightness_stop_2": Z2MLight.RELEASE, + "on_3": Z2MLight.ON, + "off_3": Z2MLight.OFF, + "brightness_move_up_3": Z2MLight.HOLD_BRIGHTNESS_UP, + "brightness_move_down_3": Z2MLight.HOLD_BRIGHTNESS_DOWN, + "brightness_stop_3": Z2MLight.RELEASE, + "on_4": Z2MLight.ON, + "off_4": Z2MLight.OFF, + "brightness_move_up_4": Z2MLight.HOLD_BRIGHTNESS_UP, + "brightness_move_down_4": Z2MLight.HOLD_BRIGHTNESS_DOWN, + "brightness_stop_4": Z2MLight.RELEASE, + } diff --git a/apps/controllerx/cx_devices/sengled.py b/apps/controllerx/cx_devices/sengled.py index fa76b56f..51e0859b 100644 --- a/apps/controllerx/cx_devices/sengled.py +++ b/apps/controllerx/cx_devices/sengled.py @@ -1,5 +1,5 @@ -from cx_const import DefaultActionsMapping, Light -from cx_core import LightController +from cx_const import DefaultActionsMapping, Light, Z2MLight +from cx_core import LightController, Z2MLightController class E1EG7FLightController(LightController): @@ -16,3 +16,19 @@ def get_z2m_actions_mapping(self) -> DefaultActionsMapping: "off_long": Light.CLICK_COLOR_DOWN, "off_double": Light.ON_MIN_COLOR_TEMP, } + + +class E1EG7FZ2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "on": Z2MLight.ON, + "on_long": Z2MLight.CLICK_COLOR_TEMP_UP, + "on_double": Z2MLight.ON_FULL_COLOR_TEMP, + "up": Z2MLight.CLICK_BRIGHTNESS_UP, + "up_long": Z2MLight.ON_FULL_BRIGHTNESS, + "down": Z2MLight.CLICK_BRIGHTNESS_DOWN, + "down_long": Z2MLight.ON_MIN_BRIGHTNESS, + "off": Z2MLight.OFF, + "off_long": Z2MLight.CLICK_COLOR_TEMP_DOWN, + "off_double": Z2MLight.ON_MIN_COLOR_TEMP, + } diff --git a/apps/controllerx/cx_devices/trust.py b/apps/controllerx/cx_devices/trust.py index ddeb3d45..2caf2137 100644 --- a/apps/controllerx/cx_devices/trust.py +++ b/apps/controllerx/cx_devices/trust.py @@ -1,5 +1,5 @@ -from cx_const import DefaultActionsMapping, Light, MediaPlayer -from cx_core import LightController, MediaPlayerController +from cx_const import DefaultActionsMapping, Light, MediaPlayer, Z2MLight +from cx_core import LightController, MediaPlayerController, Z2MLightController class ZYCT202LightController(LightController): @@ -17,6 +17,17 @@ def get_z2m_actions_mapping(self) -> DefaultActionsMapping: } +class ZYCT202Z2MLightController(Z2MLightController): + def get_z2m_actions_mapping(self) -> DefaultActionsMapping: + return { + "on": Z2MLight.ON, + "up-press": Z2MLight.HOLD_BRIGHTNESS_UP, + "down-press": Z2MLight.HOLD_BRIGHTNESS_DOWN, + "off": Z2MLight.OFF, + "stop": Z2MLight.RELEASE, + } + + class ZYCT202MediaPlayerController(MediaPlayerController): def get_z2m_actions_mapping(self) -> DefaultActionsMapping: return { diff --git a/docs/docs/advanced/predefined-actions.md b/docs/docs/advanced/predefined-actions.md index d72a5324..520a0057 100644 --- a/docs/docs/advanced/predefined-actions.md +++ b/docs/docs/advanced/predefined-actions.md @@ -73,28 +73,34 @@ When using a [light controller](/controllerx/start/type-configuration#light-cont When using a [Zigbee2MQTT light controller](/controllerx/start/type-configuration#zigbee2mqtt-light-controller) (e.g. `E1743Z2MLightController`) or `Z2MLightController`, the following actions can be used as a predefined action: -| value | description | parameters | -| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | -| `"on"` | It turns on the light | - `attributes`: a mapping with attribute and value | -| `"off"` | It turns off the light | | -| `toggle` | It toggles the light | | -| `release` | It stops `hold` actions | | -| `on_full_brightness` | It puts the brightness to the maximum value | | -| `on_full_color_temp` | It puts the color temp to the maximum value | | -| `on_min_brightness` | It puts the brightness to the minimum value | | -| `on_min_color_temp` | It puts the color temp to the minimum value | | -| `set_half_brightness` | It sets the brightness to 50% | | -| `set_half_color_temp` | It sets the color temp to 50% | | -| `click` | It brights up/down accordingly with the `click_steps` attribute, and allow to pass parameters through YAML config. You can read more about it [here](../hold-click-modes). | - `attribute`
- `direction`
- `steps`
- `transition`
- `use_onoff` | -| `click_brightness_up` | It brights up accordingly with the `manual_steps` attribute | | -| `click_brightness_down` | It brights down accordingly with the `manual_steps` attribute | | -| `click_colortemp_up` | It turns the color temp up accordingly with the `manual_steps` attribute | | -| `click_colortemp_down` | It turns the color temp down accordingly with the `manual_steps` attribute | | -| `hold` | It brights up/down until release accordingly with the `hold_steps` attribute, and allow to pass parameters through YAML config. You can read more about it [here](../hold-click-modes). | - `attribute`
- `direction`
- `steps`
- `use_onoff` | -| `hold_brightness_up` | It brights up until release accordingly with the `automatic_steps` attribute | | -| `hold_brightness_down` | It brights down until release accordingly with the `automatic_steps` attribute | | -| `hold_colortemp_up` | It turns the color temp up until release accordingly with the `automatic_steps` attribute | | -| `hold_colortemp_down` | It turns the color temp down until release accordingly with the `automatic_steps` attribute | | +| value | description | parameters | +| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | +| `"on"` | It turns on the light | - `attributes`: a mapping with attribute and value | +| `"off"` | It turns off the light | | +| `toggle` | It toggles the light | | +| `release` | It stops `hold` actions | | +| `on_full_brightness` | It puts the brightness to the maximum value | | +| `on_full_color_temp` | It puts the color temp to the maximum value | | +| `on_min_brightness` | It puts the brightness to the minimum value | | +| `on_min_color_temp` | It puts the color temp to the minimum value | | +| `set_half_brightness` | It sets the brightness to 50% | | +| `set_half_color_temp` | It sets the color temp to 50% | | +| `click` | It brights up/down accordingly with the `click_steps` attribute, and allow to pass parameters through YAML config. You can read more about it [here](../hold-click-modes). | - `attribute`
- `direction`
- `steps`
- `transition`
- `use_onoff` | +| `click_brightness_up` | It brights up accordingly with the `click_steps` attribute | | +| `click_brightness_down` | It brights down accordingly with the `click_steps` attribute | | +| `click_colortemp_up` | It turns the color temp up accordingly with the `click_steps` attribute | | +| `click_colortemp_down` | It turns the color temp down accordingly with the `click_steps` attribute | | +| `hold` | It brights up/down until release accordingly with the `hold_steps` attribute, and allow to pass parameters through YAML config. You can read more about it [here](../hold-click-modes). | - `attribute`
- `direction`
- `steps`
- `use_onoff` | +| `hold_brightness_up` | It brights up until release accordingly with the `hold_steps` attribute | | +| `hold_brightness_down` | It brights down until release accordingly with the `hold_steps` attribute | | +| `hold_brightness_toggle` | It brights up/down until release accordingly with the `hold_steps` attribute and alternates in each click | | +| `hold_colortemp_up` | It turns the color temp up until release accordingly with the `hold_steps` attribute | | +| `hold_colortemp_down` | It turns the color temp down until release accordingly with the `hold_steps` attribute | | +| `hold_colortemp_toggle` | It turns the color temp up/down until release accordingly with the `hold_steps` attribute and alternates in each click | | +| `xycolor_from_controller` | It changes the xy color of the light from the value sent by the controller (if supported) | | +| `colortemp_from_controller` | It changes the color temperature of the light from the value sent by the controller (if supported) | | +| `brightness_from_controller_level` | It changes the brightness of the light from the value sent by the controller `action_level` (if supported) | | +| `brightness_from_controller_angle` | It changes the brightness of the light from the value sent by the controller `action_rotation_angle` (if supported). This fires a `hold` action, so a `release` one will be needed to stop brightness change. | - `steps`
- `use_onoff` | ## Media Player diff --git a/tests/integ_tests/z2m_light_controller/config.yaml b/tests/integ_tests/z2m_light_controller/config.yaml index 42879857..fb20e3c6 100644 --- a/tests/integ_tests/z2m_light_controller/config.yaml +++ b/tests/integ_tests/z2m_light_controller/config.yaml @@ -27,3 +27,4 @@ example_app: attribute: brightness direction: down use_onoff: true + arrow_right_hold: hold_colortemp_toggle diff --git a/tests/integ_tests/z2m_light_controller/hold_toggle_test.yaml b/tests/integ_tests/z2m_light_controller/hold_toggle_test.yaml new file mode 100644 index 00000000..8acf1ca8 --- /dev/null +++ b/tests/integ_tests/z2m_light_controller/hold_toggle_test.yaml @@ -0,0 +1,33 @@ +fired_actions: + - arrow_right_hold + - 0.450 + - brightness_up_release + - 0.01 + - arrow_right_hold + - 0.450 + - brightness_up_release +expected_calls: + - service: mqtt/publish + data: + topic: zigbee2mqtt/livingroom_lamp/set + payload: '{"color_temp_move": 70, "transition": 0.5}' + - service: mqtt/publish + data: + topic: zigbee2mqtt/livingroom_lamp/set + payload: '{"brightness_move": "stop"}' + - service: mqtt/publish + data: + topic: zigbee2mqtt/livingroom_lamp/set + payload: '{"color_temp_move": "stop"}' + - service: mqtt/publish + data: + topic: zigbee2mqtt/livingroom_lamp/set + payload: '{"color_temp_move": -70, "transition": 0.5}' + - service: mqtt/publish + data: + topic: zigbee2mqtt/livingroom_lamp/set + payload: '{"brightness_move": "stop"}' + - service: mqtt/publish + data: + topic: zigbee2mqtt/livingroom_lamp/set + payload: '{"color_temp_move": "stop"}' diff --git a/tests/integ_tests/z2m_light_controller_attr_from_controller/brightness_from_controller_angle_test.yaml b/tests/integ_tests/z2m_light_controller_attr_from_controller/brightness_from_controller_angle_test.yaml new file mode 100644 index 00000000..31b63096 --- /dev/null +++ b/tests/integ_tests/z2m_light_controller_attr_from_controller/brightness_from_controller_angle_test.yaml @@ -0,0 +1,18 @@ +fired_actions: + - brithgness_from_angle + - 0.450 + - brithgness_from_angle_release +extra: { action_rotation_angle: -42 } +expected_calls: + - service: mqtt/publish + data: + topic: zigbee2mqtt/livingroom_lamp/set + payload: '{"brightness_move": -70, "transition": 0.5}' + - service: mqtt/publish + data: + topic: zigbee2mqtt/livingroom_lamp/set + payload: '{"brightness_move": "stop"}' + - service: mqtt/publish + data: + topic: zigbee2mqtt/livingroom_lamp/set + payload: '{"color_temp_move": "stop"}' diff --git a/tests/integ_tests/z2m_light_controller_attr_from_controller/brightness_from_controller_level_test.yaml b/tests/integ_tests/z2m_light_controller_attr_from_controller/brightness_from_controller_level_test.yaml new file mode 100644 index 00000000..fd88d5ff --- /dev/null +++ b/tests/integ_tests/z2m_light_controller_attr_from_controller/brightness_from_controller_level_test.yaml @@ -0,0 +1,8 @@ +fired_actions: ["brightness_move_to_level"] +extra: + action_level: 200 +expected_calls: + - service: mqtt/publish + data: + topic: zigbee2mqtt/livingroom_lamp/set + payload: '{"state": "ON", "brightness": 200}' diff --git a/tests/integ_tests/z2m_light_controller_attr_from_controller/colot_temp_from_controller_test.yaml b/tests/integ_tests/z2m_light_controller_attr_from_controller/colot_temp_from_controller_test.yaml new file mode 100644 index 00000000..589eb4fa --- /dev/null +++ b/tests/integ_tests/z2m_light_controller_attr_from_controller/colot_temp_from_controller_test.yaml @@ -0,0 +1,8 @@ +fired_actions: ["color_temperature_move"] +extra: + action_color_temperature: 300 +expected_calls: + - service: mqtt/publish + data: + topic: zigbee2mqtt/livingroom_lamp/set + payload: '{"state": "ON", "color_temp": 300}' diff --git a/tests/integ_tests/z2m_light_controller_attr_from_controller/config.yaml b/tests/integ_tests/z2m_light_controller_attr_from_controller/config.yaml new file mode 100644 index 00000000..647822ee --- /dev/null +++ b/tests/integ_tests/z2m_light_controller_attr_from_controller/config.yaml @@ -0,0 +1,11 @@ +example_app: + module: controllerx + class: ZS23000278Z2MLightController + integration: + name: z2m + listen_to: mqtt + controller: sensor.livingroom_controller_action + light: livingroom_lamp + merge_mapping: + brithgness_from_angle: brightness_from_controller_angle + brithgness_from_angle_release: release diff --git a/tests/integ_tests/z2m_light_controller_attr_from_controller/xy_color_from_controller_test.yaml b/tests/integ_tests/z2m_light_controller_attr_from_controller/xy_color_from_controller_test.yaml new file mode 100644 index 00000000..d9c64e68 --- /dev/null +++ b/tests/integ_tests/z2m_light_controller_attr_from_controller/xy_color_from_controller_test.yaml @@ -0,0 +1,8 @@ +fired_actions: ["color_move"] +extra: + action_color: { "x": 0.12, "y": 0.08 } +expected_calls: + - service: mqtt/publish + data: + topic: zigbee2mqtt/livingroom_lamp/set + payload: '{"state": "ON", "color": {"x": 0.12, "y": 0.08}}' 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 d221589a..f4d9a2cb 100644 --- a/tests/unit_tests/cx_core/type/light_controller_test.py +++ b/tests/unit_tests/cx_core/type/light_controller_test.py @@ -234,7 +234,7 @@ def test_get_stepper( error_expected: bool, ) -> None: with wrap_execution(error_expected=error_expected, exception=ValueError): - output_stepper = sut.get_stepper(attribute, 10, mode) + output_stepper = sut.get_stepper(attribute, 10, mode, tag="my_tag") assert isinstance(output_stepper, expected_stepper) if attribute != LightController.ATTRIBUTE_XY_COLOR: