diff --git a/zha/application/platforms/light/__init__.py b/zha/application/platforms/light/__init__.py index 9dbdfc3eb..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 @@ -991,6 +992,75 @@ 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, + CLUSTER_HANDLER_PHILIPS_HUE_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} + + """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_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 interrupted + if kwargs.get(ATTR_BRIGHTNESS) is not None and all( + kwargs.get(attr) is None for attr in (kwargs.keys() - {ATTR_BRIGHTNESS}) + ): + 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_light_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_light_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}, 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