From 4f660cc5f5b4d6bc1f91b616c13e35f4b7d91175 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Wed, 26 Apr 2023 15:24:06 -0400 Subject: [PATCH] Allow the ZHA default light transition time to be configured as a float (#92075) --- homeassistant/components/zha/core/const.py | 4 +- homeassistant/components/zha/light.py | 48 ++++++++++++---------- tests/components/zha/data.py | 6 ++- tests/components/zha/test_light.py | 6 +-- 4 files changed, 37 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index de4272032d8834..ad4bfd7a6906ea 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -151,7 +151,9 @@ CONF_ZHA_OPTIONS_SCHEMA = vol.Schema( { - vol.Optional(CONF_DEFAULT_LIGHT_TRANSITION, default=0): cv.positive_int, + vol.Optional(CONF_DEFAULT_LIGHT_TRANSITION, default=0): vol.All( + vol.Coerce(float), vol.Range(min=0, max=2**16 / 10) + ), vol.Required(CONF_ENABLE_ENHANCED_LIGHT_TRANSITION, default=False): cv.boolean, vol.Required(CONF_ENABLE_LIGHT_TRANSITIONING_FLAG, default=True): cv.boolean, vol.Required(CONF_ALWAYS_PREFER_XY_COLOR_MODE, default=True): cv.boolean, diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index 3d3412784f747c..705176ceda44b4 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -113,7 +113,7 @@ class BaseLight(LogMixin, light.LightEntity): """Operations common to all light entities.""" _FORCE_ON = False - _DEFAULT_MIN_TRANSITION_TIME = 0 + _DEFAULT_MIN_TRANSITION_TIME: float = 0 def __init__(self, *args, **kwargs): """Initialize the light.""" @@ -181,9 +181,7 @@ async def async_turn_on(self, **kwargs: Any) -> None: """Turn the entity on.""" transition = kwargs.get(light.ATTR_TRANSITION) duration = ( - transition * 10 - if transition is not None - else self._zha_config_transition * 10 + transition if transition is not None else self._zha_config_transition ) or ( # if 0 is passed in some devices still need the minimum default self._DEFAULT_MIN_TRANSITION_TIME @@ -210,7 +208,7 @@ async def async_turn_on(self, **kwargs: Any) -> None: ) and self._zha_config_enable_light_transitioning_flag transition_time = ( ( - duration / 10 + DEFAULT_EXTRA_TRANSITION_DELAY_SHORT + duration + DEFAULT_EXTRA_TRANSITION_DELAY_SHORT if ( (brightness is not None or transition is not None) and brightness_supported(self._attr_supported_color_modes) @@ -297,7 +295,7 @@ async def async_turn_on(self, **kwargs: Any) -> None: # After that, we set it to the desired color/temperature with no transition. result = await self._level_cluster_handler.move_to_level_with_on_off( level=DEFAULT_MIN_BRIGHTNESS, - transition_time=self._DEFAULT_MIN_TRANSITION_TIME, + transition_time=int(10 * self._DEFAULT_MIN_TRANSITION_TIME), ) t_log["move_to_level_with_on_off"] = result if isinstance(result, Exception) or result[1] is not Status.SUCCESS: @@ -337,7 +335,7 @@ async def async_turn_on(self, **kwargs: Any) -> None: ): result = await self._level_cluster_handler.move_to_level_with_on_off( level=level, - transition_time=duration, + transition_time=int(10 * duration), ) t_log["move_to_level_with_on_off"] = result if isinstance(result, Exception) or result[1] is not Status.SUCCESS: @@ -390,7 +388,7 @@ async def async_turn_on(self, **kwargs: Any) -> None: # The light is has the correct color, so we can now transition # it to the correct brightness level. result = await self._level_cluster_handler.move_to_level( - level=level, transition_time=duration + level=level, transition_time=int(10 * duration) ) t_log["move_to_level_if_color"] = result if isinstance(result, Exception) or result[1] is not Status.SUCCESS: @@ -465,7 +463,9 @@ async def async_turn_off(self, **kwargs: Any) -> None: if transition is not None and supports_level: result = await self._level_cluster_handler.move_to_level_with_on_off( level=0, - transition_time=(transition * 10 or self._DEFAULT_MIN_TRANSITION_TIME), + transition_time=int( + 10 * (transition or self._DEFAULT_MIN_TRANSITION_TIME) + ), ) else: result = await self._on_off_cluster_handler.off() @@ -511,7 +511,7 @@ async def async_handle_color_commands( if temperature is not None: result = await self._color_cluster_handler.move_to_color_temp( color_temp_mireds=temperature, - transition_time=transition_time, + transition_time=int(10 * transition_time), ) t_log["move_to_color_temp"] = result if isinstance(result, Exception) or result[1] is not Status.SUCCESS: @@ -529,14 +529,14 @@ async def async_handle_color_commands( result = await self._color_cluster_handler.enhanced_move_to_hue_and_saturation( enhanced_hue=int(hs_color[0] * 65535 / 360), saturation=int(hs_color[1] * 2.54), - transition_time=transition_time, + transition_time=int(10 * transition_time), ) t_log["enhanced_move_to_hue_and_saturation"] = result else: result = await self._color_cluster_handler.move_to_hue_and_saturation( hue=int(hs_color[0] * 254 / 360), saturation=int(hs_color[1] * 2.54), - transition_time=transition_time, + transition_time=int(10 * transition_time), ) t_log["move_to_hue_and_saturation"] = result if isinstance(result, Exception) or result[1] is not Status.SUCCESS: @@ -551,7 +551,7 @@ async def async_handle_color_commands( result = await self._color_cluster_handler.move_to_color( color_x=int(xy_color[0] * 65535), color_y=int(xy_color[1] * 65535), - transition_time=transition_time, + transition_time=int(10 * transition_time), ) t_log["move_to_color"] = result if isinstance(result, Exception) or result[1] is not Status.SUCCESS: @@ -1091,7 +1091,9 @@ class MinTransitionLight(Light): """Representation of a light which does not react to any "move to" calls with 0 as a transition.""" _attr_name: str = "Light" - _DEFAULT_MIN_TRANSITION_TIME = 1 + + # Transitions are counted in 1/10th of a second increments, so this is the smallest + _DEFAULT_MIN_TRANSITION_TIME = 0.1 @GROUP_MATCH() @@ -1111,10 +1113,18 @@ def __init__( group = self.zha_device.gateway.get_group(self._group_id) self._GROUP_SUPPORTS_EXECUTE_IF_OFF = True # pylint: disable=invalid-name - # Check all group members to see if they support execute_if_off. - # If at least one member has a color cluster and doesn't support it, - # it's not used. + for member in group.members: + # Ensure we do not send group commands that violate the minimum transition + # time of any members. + if member.device.manufacturer in DEFAULT_MIN_TRANSITION_MANUFACTURERS: + self._DEFAULT_MIN_TRANSITION_TIME = ( # pylint: disable=invalid-name + MinTransitionLight._DEFAULT_MIN_TRANSITION_TIME + ) + + # Check all group members to see if they support execute_if_off. + # If at least one member has a color cluster and doesn't support it, + # it's not used. for endpoint in member.device._endpoints.values(): for cluster_handler in endpoint.all_cluster_handlers.values(): if ( @@ -1124,10 +1134,6 @@ def __init__( self._GROUP_SUPPORTS_EXECUTE_IF_OFF = False break - self._DEFAULT_MIN_TRANSITION_TIME = any( # pylint: disable=invalid-name - member.device.manufacturer in DEFAULT_MIN_TRANSITION_MANUFACTURERS - for member in group.members - ) self._on_off_cluster_handler = group.endpoint[OnOff.cluster_id] self._level_cluster_handler = group.endpoint[LevelControl.cluster_id] self._color_cluster_handler = group.endpoint[Color.cluster_id] diff --git a/tests/components/zha/data.py b/tests/components/zha/data.py index 024a5e75fbc01d..eb135c7e8fe0f9 100644 --- a/tests/components/zha/data.py +++ b/tests/components/zha/data.py @@ -4,8 +4,9 @@ "schemas": { "zha_options": [ { - "type": "integer", + "type": "float", "valueMin": 0, + "valueMax": 6553.6, "name": "default_light_transition", "optional": True, "default": 0, @@ -74,8 +75,9 @@ "schemas": { "zha_options": [ { - "type": "integer", + "type": "float", "valueMin": 0, + "valueMax": 6553.6, "name": "default_light_transition", "optional": True, "default": 0, diff --git a/tests/components/zha/test_light.py b/tests/components/zha/test_light.py index 0053258d09d2ea..c4751f7e7f6ee0 100644 --- a/tests/components/zha/test_light.py +++ b/tests/components/zha/test_light.py @@ -569,7 +569,7 @@ async def test_transitions( "turn_on", { "entity_id": device_1_entity_id, - "transition": 3, + "transition": 3.5, "brightness": 18, "color_temp": 432, }, @@ -586,7 +586,7 @@ async def test_transitions( dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].id, dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].schema, level=18, - transition_time=30, + transition_time=35, expect_reply=True, manufacturer=None, tries=1, @@ -597,7 +597,7 @@ async def test_transitions( dev1_cluster_color.commands_by_name["move_to_color_temp"].id, dev1_cluster_color.commands_by_name["move_to_color_temp"].schema, color_temp_mireds=432, - transition_time=30.0, + transition_time=35, expect_reply=True, manufacturer=None, tries=1,