From e18f5c4b124966069a39986abaf3eb12dd322b24 Mon Sep 17 00:00:00 2001 From: BetaRavener Date: Wed, 22 Jan 2025 19:22:36 +0100 Subject: [PATCH 1/4] Provide effects on supported Philips Hue lights. --- zha/application/platforms/light/__init__.py | 63 +++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/zha/application/platforms/light/__init__.py b/zha/application/platforms/light/__init__.py index 9dbdfc3eb..6a703b76f 100644 --- a/zha/application/platforms/light/__init__.py +++ b/zha/application/platforms/light/__init__.py @@ -991,6 +991,69 @@ class HueLight(Light): _REFRESH_INTERVAL = (180, 300) +@STRICT_MATCH( + cluster_handler_names=CLUSTER_HANDLER_ON_OFF, + aux_cluster_handlers={ + CLUSTER_HANDLER_COLOR, + CLUSTER_HANDLER_LEVEL, + "philips_hue_cluster", + }, + manufacturers={"Philips", "Signify Netherlands B.V."}, +) +class HueEffectLight(HueLight): + # Supported effects and their ID used in commands + HUE_EFFECTS = {"candle": 1, "fireplace": 2, "prism": 3} + + """Representation of a HUE light with effects.""" + + def __init__( + self, + unique_id: str, + cluster_handlers: list[ClusterHandler], + endpoint: Endpoint, + device: Device, + **kwargs, + ) -> None: + """Initialize the ZHA light.""" + super().__init__(unique_id, cluster_handlers, endpoint, device, **kwargs) + self._hue_cluster = self.cluster_handlers.get("philips_hue_cluster") + self._effect_list.extend(self.HUE_EFFECTS.keys()) + + async def async_turn_on(self, **kwargs: Any) -> None: + # If only change of brightness is requested, the effect doesn't have to be interupted + if kwargs.get(ATTR_BRIGHTNESS) is not None and all( + attr == ATTR_BRIGHTNESS or kwargs.get(attr) is None + for attr in kwargs.keys() + ): + effect = self._effect + else: + effect = kwargs.get(ATTR_EFFECT) + + await super().async_turn_on(**kwargs) + + if effect == self._effect: + return + + if effect in self.HUE_EFFECTS: + effect_id = self.HUE_EFFECTS[effect] + await self._hue_cluster.multicolor( + data=bytearray([0x22, 0x00, self._brightness, effect_id]) + ) + self._effect = effect + elif ( + effect is None or effect == EFFECT_OFF and self._effect in self.HUE_EFFECTS + ): + # Only stop effect if it was started by us + # Following command will stop the effect while preserving brightness + await self._hue_cluster.multicolor(data=bytearray([0x20, 0x00, 0x00, 0x00])) + self._effect = EFFECT_OFF + else: + # Don't react on unknown effects, for example 'colorloop' + return + + self.maybe_emit_state_changed_event() + + @STRICT_MATCH( cluster_handler_names=CLUSTER_HANDLER_ON_OFF, aux_cluster_handlers={CLUSTER_HANDLER_COLOR, CLUSTER_HANDLER_LEVEL}, From f8fc831757636d22f954cd3fb9d7e266f09b4047 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 18:39:55 +0000 Subject: [PATCH 2/4] Apply pre-commit auto fixes --- zha/application/platforms/light/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/zha/application/platforms/light/__init__.py b/zha/application/platforms/light/__init__.py index 6a703b76f..c86a95b80 100644 --- a/zha/application/platforms/light/__init__.py +++ b/zha/application/platforms/light/__init__.py @@ -1022,8 +1022,7 @@ def __init__( async def async_turn_on(self, **kwargs: Any) -> None: # If only change of brightness is requested, the effect doesn't have to be interupted if kwargs.get(ATTR_BRIGHTNESS) is not None and all( - attr == ATTR_BRIGHTNESS or kwargs.get(attr) is None - for attr in kwargs.keys() + attr == ATTR_BRIGHTNESS or kwargs.get(attr) is None for attr in kwargs ): effect = self._effect else: From 4cc25f9b95303aded146d84ac70f90aba516bbd0 Mon Sep 17 00:00:00 2001 From: BetaRavener Date: Wed, 22 Jan 2025 19:48:24 +0100 Subject: [PATCH 3/4] Ensure correct precedence and add docstrings. --- zha/application/platforms/light/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/zha/application/platforms/light/__init__.py b/zha/application/platforms/light/__init__.py index c86a95b80..3b2ed936d 100644 --- a/zha/application/platforms/light/__init__.py +++ b/zha/application/platforms/light/__init__.py @@ -1001,6 +1001,8 @@ class HueLight(Light): manufacturers={"Philips", "Signify Netherlands B.V."}, ) class HueEffectLight(HueLight): + """Specialization of a HUE light with effects.""" + # Supported effects and their ID used in commands HUE_EFFECTS = {"candle": 1, "fireplace": 2, "prism": 3} @@ -1020,6 +1022,7 @@ def __init__( self._effect_list.extend(self.HUE_EFFECTS.keys()) async def async_turn_on(self, **kwargs: Any) -> None: + """Turn the entity on.""" # If only change of brightness is requested, the effect doesn't have to be interupted if kwargs.get(ATTR_BRIGHTNESS) is not None and all( attr == ATTR_BRIGHTNESS or kwargs.get(attr) is None for attr in kwargs @@ -1040,8 +1043,8 @@ async def async_turn_on(self, **kwargs: Any) -> None: ) self._effect = effect elif ( - effect is None or effect == EFFECT_OFF and self._effect in self.HUE_EFFECTS - ): + effect is None or effect == EFFECT_OFF + ) and self._effect in self.HUE_EFFECTS: # Only stop effect if it was started by us # Following command will stop the effect while preserving brightness await self._hue_cluster.multicolor(data=bytearray([0x20, 0x00, 0x00, 0x00])) From 00a3125062b4ff43d1c7e18659e66682c6389c23 Mon Sep 17 00:00:00 2001 From: BetaRavener Date: Wed, 12 Feb 2025 15:30:25 +0100 Subject: [PATCH 4/4] Change cluster name to match dev. --- zha/application/platforms/light/__init__.py | 17 +++++++++++------ zha/zigbee/cluster_handlers/const.py | 1 + 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/zha/application/platforms/light/__init__.py b/zha/application/platforms/light/__init__.py index 3b2ed936d..9e44009b5 100644 --- a/zha/application/platforms/light/__init__.py +++ b/zha/application/platforms/light/__init__.py @@ -72,6 +72,7 @@ CLUSTER_HANDLER_LEVEL, CLUSTER_HANDLER_LEVEL_CHANGED, CLUSTER_HANDLER_ON_OFF, + CLUSTER_HANDLER_PHILIPS_HUE_LIGHT, ) from zha.zigbee.cluster_handlers.general import LevelChangeEvent @@ -996,7 +997,7 @@ class HueLight(Light): aux_cluster_handlers={ CLUSTER_HANDLER_COLOR, CLUSTER_HANDLER_LEVEL, - "philips_hue_cluster", + CLUSTER_HANDLER_PHILIPS_HUE_LIGHT, }, manufacturers={"Philips", "Signify Netherlands B.V."}, ) @@ -1018,14 +1019,16 @@ def __init__( ) -> None: """Initialize the ZHA light.""" super().__init__(unique_id, cluster_handlers, endpoint, device, **kwargs) - self._hue_cluster = self.cluster_handlers.get("philips_hue_cluster") + self._hue_light_cluster = self.cluster_handlers.get( + CLUSTER_HANDLER_PHILIPS_HUE_LIGHT + ) self._effect_list.extend(self.HUE_EFFECTS.keys()) async def async_turn_on(self, **kwargs: Any) -> None: """Turn the entity on.""" - # If only change of brightness is requested, the effect doesn't have to be interupted + # If only change of brightness is requested, the effect doesn't have to be interrupted if kwargs.get(ATTR_BRIGHTNESS) is not None and all( - attr == ATTR_BRIGHTNESS or kwargs.get(attr) is None for attr in kwargs + kwargs.get(attr) is None for attr in (kwargs.keys() - {ATTR_BRIGHTNESS}) ): effect = self._effect else: @@ -1038,7 +1041,7 @@ async def async_turn_on(self, **kwargs: Any) -> None: if effect in self.HUE_EFFECTS: effect_id = self.HUE_EFFECTS[effect] - await self._hue_cluster.multicolor( + await self._hue_light_cluster.multicolor( data=bytearray([0x22, 0x00, self._brightness, effect_id]) ) self._effect = effect @@ -1047,7 +1050,9 @@ async def async_turn_on(self, **kwargs: Any) -> None: ) and self._effect in self.HUE_EFFECTS: # Only stop effect if it was started by us # Following command will stop the effect while preserving brightness - await self._hue_cluster.multicolor(data=bytearray([0x20, 0x00, 0x00, 0x00])) + await self._hue_light_cluster.multicolor( + data=bytearray([0x20, 0x00, 0x00, 0x00]) + ) self._effect = EFFECT_OFF else: # Don't react on unknown effects, for example 'colorloop' diff --git a/zha/zigbee/cluster_handlers/const.py b/zha/zigbee/cluster_handlers/const.py index 2ea2a047d..1e1b0cc18 100644 --- a/zha/zigbee/cluster_handlers/const.py +++ b/zha/zigbee/cluster_handlers/const.py @@ -76,6 +76,7 @@ CLUSTER_HANDLER_ZONE: Final[str] = "ias_zone" ZONE: Final[str] = CLUSTER_HANDLER_ZONE CLUSTER_HANDLER_INOVELLI = "inovelli_vzm31sn_cluster" +CLUSTER_HANDLER_PHILIPS_HUE_LIGHT: Final[str] = "philips_hue_light_cluster" AQARA_OPPLE_CLUSTER: Final[int] = 0xFCC0 IKEA_AIR_PURIFIER_CLUSTER: Final[int] = 0xFC7D