mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
ZHA light entity cleanup (#75573)
* use base class attributes * initial hue and saturation support * spec is 65536 not 65535 * fixes * enhanced current hue * fix comparison * clean up * fix channel test * oops * report enhanced current hue
This commit is contained in:
parent
6cb1794720
commit
04c6b9c519
@ -31,6 +31,9 @@ class ColorChannel(ZigbeeChannel):
|
|||||||
REPORT_CONFIG = (
|
REPORT_CONFIG = (
|
||||||
AttrReportConfig(attr="current_x", config=REPORT_CONFIG_DEFAULT),
|
AttrReportConfig(attr="current_x", config=REPORT_CONFIG_DEFAULT),
|
||||||
AttrReportConfig(attr="current_y", config=REPORT_CONFIG_DEFAULT),
|
AttrReportConfig(attr="current_y", config=REPORT_CONFIG_DEFAULT),
|
||||||
|
AttrReportConfig(attr="current_hue", config=REPORT_CONFIG_DEFAULT),
|
||||||
|
AttrReportConfig(attr="enhanced_current_hue", config=REPORT_CONFIG_DEFAULT),
|
||||||
|
AttrReportConfig(attr="current_saturation", config=REPORT_CONFIG_DEFAULT),
|
||||||
AttrReportConfig(attr="color_temperature", config=REPORT_CONFIG_DEFAULT),
|
AttrReportConfig(attr="color_temperature", config=REPORT_CONFIG_DEFAULT),
|
||||||
)
|
)
|
||||||
MAX_MIREDS: int = 500
|
MAX_MIREDS: int = 500
|
||||||
@ -52,6 +55,14 @@ class ColorChannel(ZigbeeChannel):
|
|||||||
return self.CAPABILITIES_COLOR_XY | self.CAPABILITIES_COLOR_TEMP
|
return self.CAPABILITIES_COLOR_XY | self.CAPABILITIES_COLOR_TEMP
|
||||||
return self.CAPABILITIES_COLOR_XY
|
return self.CAPABILITIES_COLOR_XY
|
||||||
|
|
||||||
|
@property
|
||||||
|
def zcl_color_capabilities(self) -> lighting.Color.ColorCapabilities:
|
||||||
|
"""Return ZCL color capabilities of the light."""
|
||||||
|
color_capabilities = self.cluster.get("color_capabilities")
|
||||||
|
if color_capabilities is None:
|
||||||
|
return lighting.Color.ColorCapabilities(self.CAPABILITIES_COLOR_XY)
|
||||||
|
return lighting.Color.ColorCapabilities(color_capabilities)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def color_mode(self) -> int | None:
|
def color_mode(self) -> int | None:
|
||||||
"""Return cached value of the color_mode attribute."""
|
"""Return cached value of the color_mode attribute."""
|
||||||
@ -77,6 +88,21 @@ class ColorChannel(ZigbeeChannel):
|
|||||||
"""Return cached value of the current_y attribute."""
|
"""Return cached value of the current_y attribute."""
|
||||||
return self.cluster.get("current_y")
|
return self.cluster.get("current_y")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_hue(self) -> int | None:
|
||||||
|
"""Return cached value of the current_hue attribute."""
|
||||||
|
return self.cluster.get("current_hue")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def enhanced_current_hue(self) -> int | None:
|
||||||
|
"""Return cached value of the enhanced_current_hue attribute."""
|
||||||
|
return self.cluster.get("enhanced_current_hue")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_saturation(self) -> int | None:
|
||||||
|
"""Return cached value of the current_saturation attribute."""
|
||||||
|
return self.cluster.get("current_saturation")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_mireds(self) -> int:
|
def min_mireds(self) -> int:
|
||||||
"""Return the coldest color_temp that this channel supports."""
|
"""Return the coldest color_temp that this channel supports."""
|
||||||
@ -86,3 +112,48 @@ class ColorChannel(ZigbeeChannel):
|
|||||||
def max_mireds(self) -> int:
|
def max_mireds(self) -> int:
|
||||||
"""Return the warmest color_temp that this channel supports."""
|
"""Return the warmest color_temp that this channel supports."""
|
||||||
return self.cluster.get("color_temp_physical_max", self.MAX_MIREDS)
|
return self.cluster.get("color_temp_physical_max", self.MAX_MIREDS)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hs_supported(self) -> bool:
|
||||||
|
"""Return True if the channel supports hue and saturation."""
|
||||||
|
return (
|
||||||
|
self.zcl_color_capabilities is not None
|
||||||
|
and lighting.Color.ColorCapabilities.Hue_and_saturation
|
||||||
|
in self.zcl_color_capabilities
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def enhanced_hue_supported(self) -> bool:
|
||||||
|
"""Return True if the channel supports enhanced hue and saturation."""
|
||||||
|
return (
|
||||||
|
self.zcl_color_capabilities is not None
|
||||||
|
and lighting.Color.ColorCapabilities.Enhanced_hue
|
||||||
|
in self.zcl_color_capabilities
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def xy_supported(self) -> bool:
|
||||||
|
"""Return True if the channel supports xy."""
|
||||||
|
return (
|
||||||
|
self.zcl_color_capabilities is not None
|
||||||
|
and lighting.Color.ColorCapabilities.XY_attributes
|
||||||
|
in self.zcl_color_capabilities
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def color_temp_supported(self) -> bool:
|
||||||
|
"""Return True if the channel supports color temperature."""
|
||||||
|
return (
|
||||||
|
self.zcl_color_capabilities is not None
|
||||||
|
and lighting.Color.ColorCapabilities.Color_temperature
|
||||||
|
in self.zcl_color_capabilities
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def color_loop_supported(self) -> bool:
|
||||||
|
"""Return True if the channel supports color loop."""
|
||||||
|
return (
|
||||||
|
self.zcl_color_capabilities is not None
|
||||||
|
and lighting.Color.ColorCapabilities.Color_loop
|
||||||
|
in self.zcl_color_capabilities
|
||||||
|
)
|
||||||
|
@ -15,15 +15,6 @@ from zigpy.zcl.foundation import Status
|
|||||||
|
|
||||||
from homeassistant.components import light
|
from homeassistant.components import light
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS,
|
|
||||||
ATTR_COLOR_MODE,
|
|
||||||
ATTR_COLOR_TEMP,
|
|
||||||
ATTR_EFFECT,
|
|
||||||
ATTR_EFFECT_LIST,
|
|
||||||
ATTR_HS_COLOR,
|
|
||||||
ATTR_MAX_MIREDS,
|
|
||||||
ATTR_MIN_MIREDS,
|
|
||||||
ATTR_SUPPORTED_COLOR_MODES,
|
|
||||||
ColorMode,
|
ColorMode,
|
||||||
brightness_supported,
|
brightness_supported,
|
||||||
filter_supported_color_modes,
|
filter_supported_color_modes,
|
||||||
@ -43,7 +34,6 @@ from homeassistant.helpers.dispatcher import (
|
|||||||
)
|
)
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.event import async_track_time_interval
|
from homeassistant.helpers.event import async_track_time_interval
|
||||||
import homeassistant.util.color as color_util
|
|
||||||
|
|
||||||
from .core import discovery, helpers
|
from .core import discovery, helpers
|
||||||
from .core.const import (
|
from .core.const import (
|
||||||
@ -69,10 +59,6 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CAPABILITIES_COLOR_LOOP = 0x4
|
|
||||||
CAPABILITIES_COLOR_XY = 0x08
|
|
||||||
CAPABILITIES_COLOR_TEMP = 0x10
|
|
||||||
|
|
||||||
DEFAULT_MIN_BRIGHTNESS = 2
|
DEFAULT_MIN_BRIGHTNESS = 2
|
||||||
|
|
||||||
UPDATE_COLORLOOP_ACTION = 0x1
|
UPDATE_COLORLOOP_ACTION = 0x1
|
||||||
@ -88,7 +74,7 @@ GROUP_MATCH = functools.partial(ZHA_ENTITIES.group_match, Platform.LIGHT)
|
|||||||
PARALLEL_UPDATES = 0
|
PARALLEL_UPDATES = 0
|
||||||
SIGNAL_LIGHT_GROUP_STATE_CHANGED = "zha_light_group_state_changed"
|
SIGNAL_LIGHT_GROUP_STATE_CHANGED = "zha_light_group_state_changed"
|
||||||
|
|
||||||
COLOR_MODES_GROUP_LIGHT = {ColorMode.COLOR_TEMP, ColorMode.HS}
|
COLOR_MODES_GROUP_LIGHT = {ColorMode.COLOR_TEMP, ColorMode.XY}
|
||||||
SUPPORT_GROUP_LIGHT = (
|
SUPPORT_GROUP_LIGHT = (
|
||||||
light.LightEntityFeature.EFFECT
|
light.LightEntityFeature.EFFECT
|
||||||
| light.LightEntityFeature.FLASH
|
| light.LightEntityFeature.FLASH
|
||||||
@ -123,24 +109,18 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Initialize the light."""
|
"""Initialize the light."""
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self._available: bool = False
|
self._attr_min_mireds: int | None = 153
|
||||||
self._brightness: int | None = None
|
self._attr_max_mireds: int | None = 500
|
||||||
|
self._attr_color_mode = ColorMode.UNKNOWN # Set by sub classes
|
||||||
|
self._attr_supported_features: int = 0
|
||||||
|
self._attr_state: bool | None
|
||||||
self._off_with_transition: bool = False
|
self._off_with_transition: bool = False
|
||||||
self._off_brightness: int | None = None
|
self._off_brightness: int | None = None
|
||||||
self._hs_color: tuple[float, float] | None = None
|
self._zha_config_transition = self._DEFAULT_MIN_TRANSITION_TIME
|
||||||
self._color_temp: int | None = None
|
|
||||||
self._min_mireds: int | None = 153
|
|
||||||
self._max_mireds: int | None = 500
|
|
||||||
self._effect_list: list[str] | None = None
|
|
||||||
self._effect: str | None = None
|
|
||||||
self._supported_features: int = 0
|
|
||||||
self._state: bool = False
|
|
||||||
self._on_off_channel = None
|
self._on_off_channel = None
|
||||||
self._level_channel = None
|
self._level_channel = None
|
||||||
self._color_channel = None
|
self._color_channel = None
|
||||||
self._identify_channel = None
|
self._identify_channel = None
|
||||||
self._zha_config_transition = self._DEFAULT_MIN_TRANSITION_TIME
|
|
||||||
self._attr_color_mode = ColorMode.UNKNOWN # Set by sub classes
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self) -> dict[str, Any]:
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
@ -154,24 +134,9 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return true if entity is on."""
|
"""Return true if entity is on."""
|
||||||
if self._state is None:
|
if self._attr_state is None:
|
||||||
return False
|
return False
|
||||||
return self._state
|
return self._attr_state
|
||||||
|
|
||||||
@property
|
|
||||||
def brightness(self):
|
|
||||||
"""Return the brightness of this light."""
|
|
||||||
return self._brightness
|
|
||||||
|
|
||||||
@property
|
|
||||||
def min_mireds(self):
|
|
||||||
"""Return the coldest color_temp that this light supports."""
|
|
||||||
return self._min_mireds
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_mireds(self):
|
|
||||||
"""Return the warmest color_temp that this light supports."""
|
|
||||||
return self._max_mireds
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def set_level(self, value):
|
def set_level(self, value):
|
||||||
@ -182,34 +147,9 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
level
|
level
|
||||||
"""
|
"""
|
||||||
value = max(0, min(254, value))
|
value = max(0, min(254, value))
|
||||||
self._brightness = value
|
self._attr_brightness = value
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@property
|
|
||||||
def hs_color(self):
|
|
||||||
"""Return the hs color value [int, int]."""
|
|
||||||
return self._hs_color
|
|
||||||
|
|
||||||
@property
|
|
||||||
def color_temp(self):
|
|
||||||
"""Return the CT color value in mireds."""
|
|
||||||
return self._color_temp
|
|
||||||
|
|
||||||
@property
|
|
||||||
def effect_list(self):
|
|
||||||
"""Return the list of supported effects."""
|
|
||||||
return self._effect_list
|
|
||||||
|
|
||||||
@property
|
|
||||||
def effect(self):
|
|
||||||
"""Return the current effect."""
|
|
||||||
return self._effect
|
|
||||||
|
|
||||||
@property
|
|
||||||
def supported_features(self):
|
|
||||||
"""Flag supported features."""
|
|
||||||
return self._supported_features
|
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs):
|
async def async_turn_on(self, **kwargs):
|
||||||
"""Turn the entity on."""
|
"""Turn the entity on."""
|
||||||
transition = kwargs.get(light.ATTR_TRANSITION)
|
transition = kwargs.get(light.ATTR_TRANSITION)
|
||||||
@ -222,6 +162,7 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
effect = kwargs.get(light.ATTR_EFFECT)
|
effect = kwargs.get(light.ATTR_EFFECT)
|
||||||
flash = kwargs.get(light.ATTR_FLASH)
|
flash = kwargs.get(light.ATTR_FLASH)
|
||||||
temperature = kwargs.get(light.ATTR_COLOR_TEMP)
|
temperature = kwargs.get(light.ATTR_COLOR_TEMP)
|
||||||
|
xy_color = kwargs.get(light.ATTR_XY_COLOR)
|
||||||
hs_color = kwargs.get(light.ATTR_HS_COLOR)
|
hs_color = kwargs.get(light.ATTR_HS_COLOR)
|
||||||
|
|
||||||
# If the light is currently off but a turn_on call with a color/temperature is sent,
|
# If the light is currently off but a turn_on call with a color/temperature is sent,
|
||||||
@ -235,19 +176,26 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
new_color_provided_while_off = (
|
new_color_provided_while_off = (
|
||||||
not isinstance(self, LightGroup)
|
not isinstance(self, LightGroup)
|
||||||
and not self._FORCE_ON
|
and not self._FORCE_ON
|
||||||
and not self._state
|
and not self._attr_state
|
||||||
and (
|
and (
|
||||||
(
|
(
|
||||||
temperature is not None
|
temperature is not None
|
||||||
and (
|
and (
|
||||||
self._color_temp != temperature
|
self._attr_color_temp != temperature
|
||||||
or self._attr_color_mode != ColorMode.COLOR_TEMP
|
or self._attr_color_mode != ColorMode.COLOR_TEMP
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
or (
|
||||||
|
xy_color is not None
|
||||||
|
and (
|
||||||
|
self._attr_xy_color != xy_color
|
||||||
|
or self._attr_color_mode != ColorMode.XY
|
||||||
|
)
|
||||||
|
)
|
||||||
or (
|
or (
|
||||||
hs_color is not None
|
hs_color is not None
|
||||||
and (
|
and (
|
||||||
self.hs_color != hs_color
|
self._attr_hs_color != hs_color
|
||||||
or self._attr_color_mode != ColorMode.HS
|
or self._attr_color_mode != ColorMode.HS
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -265,7 +213,7 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
if brightness is not None:
|
if brightness is not None:
|
||||||
level = min(254, brightness)
|
level = min(254, brightness)
|
||||||
else:
|
else:
|
||||||
level = self._brightness or 254
|
level = self._attr_brightness or 254
|
||||||
|
|
||||||
t_log = {}
|
t_log = {}
|
||||||
|
|
||||||
@ -280,7 +228,7 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
self.debug("turned on: %s", t_log)
|
self.debug("turned on: %s", t_log)
|
||||||
return
|
return
|
||||||
# Currently only setting it to "on", as the correct level state will be set at the second move_to_level call
|
# Currently only setting it to "on", as the correct level state will be set at the second move_to_level call
|
||||||
self._state = True
|
self._attr_state = True
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(brightness is not None or transition)
|
(brightness is not None or transition)
|
||||||
@ -294,9 +242,9 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
||||||
self.debug("turned on: %s", t_log)
|
self.debug("turned on: %s", t_log)
|
||||||
return
|
return
|
||||||
self._state = bool(level)
|
self._attr_state = bool(level)
|
||||||
if level:
|
if level:
|
||||||
self._brightness = level
|
self._attr_brightness = level
|
||||||
|
|
||||||
if (
|
if (
|
||||||
brightness is None
|
brightness is None
|
||||||
@ -310,7 +258,7 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
||||||
self.debug("turned on: %s", t_log)
|
self.debug("turned on: %s", t_log)
|
||||||
return
|
return
|
||||||
self._state = True
|
self._attr_state = True
|
||||||
|
|
||||||
if temperature is not None:
|
if temperature is not None:
|
||||||
result = await self._color_channel.move_to_color_temp(
|
result = await self._color_channel.move_to_color_temp(
|
||||||
@ -324,11 +272,39 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
self.debug("turned on: %s", t_log)
|
self.debug("turned on: %s", t_log)
|
||||||
return
|
return
|
||||||
self._attr_color_mode = ColorMode.COLOR_TEMP
|
self._attr_color_mode = ColorMode.COLOR_TEMP
|
||||||
self._color_temp = temperature
|
self._attr_color_temp = temperature
|
||||||
self._hs_color = None
|
self._attr_xy_color = None
|
||||||
|
self._attr_hs_color = None
|
||||||
|
|
||||||
if hs_color is not None:
|
if hs_color is not None:
|
||||||
xy_color = color_util.color_hs_to_xy(*hs_color)
|
if self._color_channel.enhanced_hue_supported:
|
||||||
|
result = await self._color_channel.enhanced_move_to_hue_and_saturation(
|
||||||
|
int(hs_color[0] * 65535 / 360),
|
||||||
|
int(hs_color[1] * 2.54),
|
||||||
|
self._DEFAULT_MIN_TRANSITION_TIME
|
||||||
|
if new_color_provided_while_off
|
||||||
|
else duration,
|
||||||
|
)
|
||||||
|
t_log["enhanced_move_to_hue_and_saturation"] = result
|
||||||
|
else:
|
||||||
|
result = await self._color_channel.move_to_hue_and_saturation(
|
||||||
|
int(hs_color[0] * 254 / 360),
|
||||||
|
int(hs_color[1] * 2.54),
|
||||||
|
self._DEFAULT_MIN_TRANSITION_TIME
|
||||||
|
if new_color_provided_while_off
|
||||||
|
else duration,
|
||||||
|
)
|
||||||
|
t_log["move_to_hue_and_saturation"] = result
|
||||||
|
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
||||||
|
self.debug("turned on: %s", t_log)
|
||||||
|
return
|
||||||
|
self._attr_color_mode = ColorMode.HS
|
||||||
|
self._attr_hs_color = hs_color
|
||||||
|
self._attr_xy_color = None
|
||||||
|
self._attr_color_temp = None
|
||||||
|
xy_color = None # don't set xy_color if it is also present
|
||||||
|
|
||||||
|
if xy_color is not None:
|
||||||
result = await self._color_channel.move_to_color(
|
result = await self._color_channel.move_to_color(
|
||||||
int(xy_color[0] * 65535),
|
int(xy_color[0] * 65535),
|
||||||
int(xy_color[1] * 65535),
|
int(xy_color[1] * 65535),
|
||||||
@ -340,9 +316,10 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
||||||
self.debug("turned on: %s", t_log)
|
self.debug("turned on: %s", t_log)
|
||||||
return
|
return
|
||||||
self._attr_color_mode = ColorMode.HS
|
self._attr_color_mode = ColorMode.XY
|
||||||
self._hs_color = hs_color
|
self._attr_xy_color = xy_color
|
||||||
self._color_temp = None
|
self._attr_color_temp = None
|
||||||
|
self._attr_hs_color = None
|
||||||
|
|
||||||
if new_color_provided_while_off:
|
if new_color_provided_while_off:
|
||||||
# The light is has the correct color, so we can now transition it to the correct brightness level.
|
# The light is has the correct color, so we can now transition it to the correct brightness level.
|
||||||
@ -351,9 +328,9 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
||||||
self.debug("turned on: %s", t_log)
|
self.debug("turned on: %s", t_log)
|
||||||
return
|
return
|
||||||
self._state = bool(level)
|
self._attr_state = bool(level)
|
||||||
if level:
|
if level:
|
||||||
self._brightness = level
|
self._attr_brightness = level
|
||||||
|
|
||||||
if effect == light.EFFECT_COLORLOOP:
|
if effect == light.EFFECT_COLORLOOP:
|
||||||
result = await self._color_channel.color_loop_set(
|
result = await self._color_channel.color_loop_set(
|
||||||
@ -366,9 +343,10 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
0, # no hue
|
0, # no hue
|
||||||
)
|
)
|
||||||
t_log["color_loop_set"] = result
|
t_log["color_loop_set"] = result
|
||||||
self._effect = light.EFFECT_COLORLOOP
|
self._attr_effect = light.EFFECT_COLORLOOP
|
||||||
elif (
|
elif (
|
||||||
self._effect == light.EFFECT_COLORLOOP and effect != light.EFFECT_COLORLOOP
|
self._attr_effect == light.EFFECT_COLORLOOP
|
||||||
|
and effect != light.EFFECT_COLORLOOP
|
||||||
):
|
):
|
||||||
result = await self._color_channel.color_loop_set(
|
result = await self._color_channel.color_loop_set(
|
||||||
UPDATE_COLORLOOP_ACTION,
|
UPDATE_COLORLOOP_ACTION,
|
||||||
@ -378,7 +356,7 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
0x0, # update action only, action off, no dir, time, hue
|
0x0, # update action only, action off, no dir, time, hue
|
||||||
)
|
)
|
||||||
t_log["color_loop_set"] = result
|
t_log["color_loop_set"] = result
|
||||||
self._effect = None
|
self._attr_effect = None
|
||||||
|
|
||||||
if flash is not None:
|
if flash is not None:
|
||||||
result = await self._identify_channel.trigger_effect(
|
result = await self._identify_channel.trigger_effect(
|
||||||
@ -406,12 +384,12 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
self.debug("turned off: %s", result)
|
self.debug("turned off: %s", result)
|
||||||
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
||||||
return
|
return
|
||||||
self._state = False
|
self._attr_state = False
|
||||||
|
|
||||||
if supports_level:
|
if supports_level:
|
||||||
# store current brightness so that the next turn_on uses it.
|
# store current brightness so that the next turn_on uses it.
|
||||||
self._off_with_transition = transition is not None
|
self._off_with_transition = transition is not None
|
||||||
self._off_brightness = self._brightness
|
self._off_brightness = self._attr_brightness
|
||||||
|
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@ -427,44 +405,59 @@ class Light(BaseLight, ZhaEntity):
|
|||||||
"""Initialize the ZHA light."""
|
"""Initialize the ZHA light."""
|
||||||
super().__init__(unique_id, zha_device, channels, **kwargs)
|
super().__init__(unique_id, zha_device, channels, **kwargs)
|
||||||
self._on_off_channel = self.cluster_channels[CHANNEL_ON_OFF]
|
self._on_off_channel = self.cluster_channels[CHANNEL_ON_OFF]
|
||||||
self._state = bool(self._on_off_channel.on_off)
|
self._attr_state = bool(self._on_off_channel.on_off)
|
||||||
self._level_channel = self.cluster_channels.get(CHANNEL_LEVEL)
|
self._level_channel = self.cluster_channels.get(CHANNEL_LEVEL)
|
||||||
self._color_channel = self.cluster_channels.get(CHANNEL_COLOR)
|
self._color_channel = self.cluster_channels.get(CHANNEL_COLOR)
|
||||||
self._identify_channel = self.zha_device.channels.identify_ch
|
self._identify_channel = self.zha_device.channels.identify_ch
|
||||||
if self._color_channel:
|
if self._color_channel:
|
||||||
self._min_mireds: int | None = self._color_channel.min_mireds
|
self._attr_min_mireds: int = self._color_channel.min_mireds
|
||||||
self._max_mireds: int | None = self._color_channel.max_mireds
|
self._attr_max_mireds: int = self._color_channel.max_mireds
|
||||||
self._cancel_refresh_handle = None
|
self._cancel_refresh_handle = None
|
||||||
effect_list = []
|
effect_list = []
|
||||||
|
|
||||||
self._attr_supported_color_modes = {ColorMode.ONOFF}
|
self._attr_supported_color_modes = {ColorMode.ONOFF}
|
||||||
if self._level_channel:
|
if self._level_channel:
|
||||||
self._attr_supported_color_modes.add(ColorMode.BRIGHTNESS)
|
self._attr_supported_color_modes.add(ColorMode.BRIGHTNESS)
|
||||||
self._supported_features |= light.LightEntityFeature.TRANSITION
|
self._attr_supported_features |= light.LightEntityFeature.TRANSITION
|
||||||
self._brightness = self._level_channel.current_level
|
self._attr_brightness = self._level_channel.current_level
|
||||||
|
|
||||||
if self._color_channel:
|
if self._color_channel:
|
||||||
color_capabilities = self._color_channel.color_capabilities
|
if self._color_channel.color_temp_supported:
|
||||||
if color_capabilities & CAPABILITIES_COLOR_TEMP:
|
|
||||||
self._attr_supported_color_modes.add(ColorMode.COLOR_TEMP)
|
self._attr_supported_color_modes.add(ColorMode.COLOR_TEMP)
|
||||||
self._color_temp = self._color_channel.color_temperature
|
self._attr_color_temp = self._color_channel.color_temperature
|
||||||
|
|
||||||
if color_capabilities & CAPABILITIES_COLOR_XY:
|
if (
|
||||||
self._attr_supported_color_modes.add(ColorMode.HS)
|
self._color_channel.xy_supported
|
||||||
|
and not self._color_channel.hs_supported
|
||||||
|
):
|
||||||
|
self._attr_supported_color_modes.add(ColorMode.XY)
|
||||||
curr_x = self._color_channel.current_x
|
curr_x = self._color_channel.current_x
|
||||||
curr_y = self._color_channel.current_y
|
curr_y = self._color_channel.current_y
|
||||||
if curr_x is not None and curr_y is not None:
|
if curr_x is not None and curr_y is not None:
|
||||||
self._hs_color = color_util.color_xy_to_hs(
|
self._attr_xy_color = (curr_x / 65535, curr_y / 65535)
|
||||||
float(curr_x / 65535), float(curr_y / 65535)
|
else:
|
||||||
|
self._attr_xy_color = (0, 0)
|
||||||
|
|
||||||
|
if self._color_channel.hs_supported:
|
||||||
|
self._attr_supported_color_modes.add(ColorMode.HS)
|
||||||
|
if self._color_channel.enhanced_hue_supported:
|
||||||
|
curr_hue = self._color_channel.enhanced_current_hue * 65535 / 360
|
||||||
|
else:
|
||||||
|
curr_hue = self._color_channel.current_hue * 254 / 360
|
||||||
|
curr_saturation = self._color_channel.current_saturation
|
||||||
|
if curr_hue is not None and curr_saturation is not None:
|
||||||
|
self._attr_hs_color = (
|
||||||
|
int(curr_hue),
|
||||||
|
int(curr_saturation * 2.54),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self._hs_color = (0, 0)
|
self._attr_hs_color = (0, 0)
|
||||||
|
|
||||||
if color_capabilities & CAPABILITIES_COLOR_LOOP:
|
if self._color_channel.color_loop_supported:
|
||||||
self._supported_features |= light.LightEntityFeature.EFFECT
|
self._attr_supported_features |= light.LightEntityFeature.EFFECT
|
||||||
effect_list.append(light.EFFECT_COLORLOOP)
|
effect_list.append(light.EFFECT_COLORLOOP)
|
||||||
if self._color_channel.color_loop_active == 1:
|
if self._color_channel.color_loop_active == 1:
|
||||||
self._effect = light.EFFECT_COLORLOOP
|
self._attr_effect = light.EFFECT_COLORLOOP
|
||||||
self._attr_supported_color_modes = filter_supported_color_modes(
|
self._attr_supported_color_modes = filter_supported_color_modes(
|
||||||
self._attr_supported_color_modes
|
self._attr_supported_color_modes
|
||||||
)
|
)
|
||||||
@ -475,13 +468,13 @@ class Light(BaseLight, ZhaEntity):
|
|||||||
if self._color_channel.color_mode == Color.ColorMode.Color_temperature:
|
if self._color_channel.color_mode == Color.ColorMode.Color_temperature:
|
||||||
self._attr_color_mode = ColorMode.COLOR_TEMP
|
self._attr_color_mode = ColorMode.COLOR_TEMP
|
||||||
else:
|
else:
|
||||||
self._attr_color_mode = ColorMode.HS
|
self._attr_color_mode = ColorMode.XY
|
||||||
|
|
||||||
if self._identify_channel:
|
if self._identify_channel:
|
||||||
self._supported_features |= light.LightEntityFeature.FLASH
|
self._attr_supported_features |= light.LightEntityFeature.FLASH
|
||||||
|
|
||||||
if effect_list:
|
if effect_list:
|
||||||
self._effect_list = effect_list
|
self._attr_effect_list = effect_list
|
||||||
|
|
||||||
self._zha_config_transition = async_get_zha_config_value(
|
self._zha_config_transition = async_get_zha_config_value(
|
||||||
zha_device.gateway.config_entry,
|
zha_device.gateway.config_entry,
|
||||||
@ -493,7 +486,7 @@ class Light(BaseLight, ZhaEntity):
|
|||||||
@callback
|
@callback
|
||||||
def async_set_state(self, attr_id, attr_name, value):
|
def async_set_state(self, attr_id, attr_name, value):
|
||||||
"""Set the state."""
|
"""Set the state."""
|
||||||
self._state = bool(value)
|
self._attr_state = bool(value)
|
||||||
if value:
|
if value:
|
||||||
self._off_with_transition = False
|
self._off_with_transition = False
|
||||||
self._off_brightness = None
|
self._off_brightness = None
|
||||||
@ -529,9 +522,9 @@ class Light(BaseLight, ZhaEntity):
|
|||||||
@callback
|
@callback
|
||||||
def async_restore_last_state(self, last_state):
|
def async_restore_last_state(self, last_state):
|
||||||
"""Restore previous state."""
|
"""Restore previous state."""
|
||||||
self._state = last_state.state == STATE_ON
|
self._attr_state = last_state.state == STATE_ON
|
||||||
if "brightness" in last_state.attributes:
|
if "brightness" in last_state.attributes:
|
||||||
self._brightness = last_state.attributes["brightness"]
|
self._attr_brightness = last_state.attributes["brightness"]
|
||||||
if "off_with_transition" in last_state.attributes:
|
if "off_with_transition" in last_state.attributes:
|
||||||
self._off_with_transition = last_state.attributes["off_with_transition"]
|
self._off_with_transition = last_state.attributes["off_with_transition"]
|
||||||
if "off_brightness" in last_state.attributes:
|
if "off_brightness" in last_state.attributes:
|
||||||
@ -539,15 +532,17 @@ class Light(BaseLight, ZhaEntity):
|
|||||||
if "color_mode" in last_state.attributes:
|
if "color_mode" in last_state.attributes:
|
||||||
self._attr_color_mode = ColorMode(last_state.attributes["color_mode"])
|
self._attr_color_mode = ColorMode(last_state.attributes["color_mode"])
|
||||||
if "color_temp" in last_state.attributes:
|
if "color_temp" in last_state.attributes:
|
||||||
self._color_temp = last_state.attributes["color_temp"]
|
self._attr_color_temp = last_state.attributes["color_temp"]
|
||||||
|
if "xy_color" in last_state.attributes:
|
||||||
|
self._attr_xy_color = last_state.attributes["xy_color"]
|
||||||
if "hs_color" in last_state.attributes:
|
if "hs_color" in last_state.attributes:
|
||||||
self._hs_color = last_state.attributes["hs_color"]
|
self._attr_hs_color = last_state.attributes["hs_color"]
|
||||||
if "effect" in last_state.attributes:
|
if "effect" in last_state.attributes:
|
||||||
self._effect = last_state.attributes["effect"]
|
self._attr_effect = last_state.attributes["effect"]
|
||||||
|
|
||||||
async def async_get_state(self):
|
async def async_get_state(self):
|
||||||
"""Attempt to retrieve the state from the light."""
|
"""Attempt to retrieve the state from the light."""
|
||||||
if not self.available:
|
if not self._attr_available:
|
||||||
return
|
return
|
||||||
self.debug("polling current state")
|
self.debug("polling current state")
|
||||||
if self._on_off_channel:
|
if self._on_off_channel:
|
||||||
@ -555,21 +550,32 @@ class Light(BaseLight, ZhaEntity):
|
|||||||
"on_off", from_cache=False
|
"on_off", from_cache=False
|
||||||
)
|
)
|
||||||
if state is not None:
|
if state is not None:
|
||||||
self._state = state
|
self._attr_state = state
|
||||||
if self._level_channel:
|
if self._level_channel:
|
||||||
level = await self._level_channel.get_attribute_value(
|
level = await self._level_channel.get_attribute_value(
|
||||||
"current_level", from_cache=False
|
"current_level", from_cache=False
|
||||||
)
|
)
|
||||||
if level is not None:
|
if level is not None:
|
||||||
self._brightness = level
|
self._attr_brightness = level
|
||||||
if self._color_channel:
|
if self._color_channel:
|
||||||
attributes = [
|
attributes = [
|
||||||
"color_mode",
|
"color_mode",
|
||||||
"color_temperature",
|
|
||||||
"current_x",
|
"current_x",
|
||||||
"current_y",
|
"current_y",
|
||||||
"color_loop_active",
|
|
||||||
]
|
]
|
||||||
|
if self._color_channel.enhanced_hue_supported:
|
||||||
|
attributes.append("enhanced_current_hue")
|
||||||
|
attributes.append("current_saturation")
|
||||||
|
if (
|
||||||
|
self._color_channel.hs_supported
|
||||||
|
and not self._color_channel.enhanced_hue_supported
|
||||||
|
):
|
||||||
|
attributes.append("current_hue")
|
||||||
|
attributes.append("current_saturation")
|
||||||
|
if self._color_channel.color_temp_supported:
|
||||||
|
attributes.append("color_temperature")
|
||||||
|
if self._color_channel.color_loop_supported:
|
||||||
|
attributes.append("color_loop_active")
|
||||||
|
|
||||||
results = await self._color_channel.get_attributes(
|
results = await self._color_channel.get_attributes(
|
||||||
attributes, from_cache=False, only_cache=False
|
attributes, from_cache=False, only_cache=False
|
||||||
@ -580,24 +586,40 @@ class Light(BaseLight, ZhaEntity):
|
|||||||
self._attr_color_mode = ColorMode.COLOR_TEMP
|
self._attr_color_mode = ColorMode.COLOR_TEMP
|
||||||
color_temp = results.get("color_temperature")
|
color_temp = results.get("color_temperature")
|
||||||
if color_temp is not None and color_mode:
|
if color_temp is not None and color_mode:
|
||||||
self._color_temp = color_temp
|
self._attr_color_temp = color_temp
|
||||||
self._hs_color = None
|
self._attr_xy_color = None
|
||||||
else:
|
self._attr_hs_color = None
|
||||||
|
elif color_mode == Color.ColorMode.Hue_and_saturation:
|
||||||
self._attr_color_mode = ColorMode.HS
|
self._attr_color_mode = ColorMode.HS
|
||||||
|
if self._color_channel.enhanced_hue_supported:
|
||||||
|
current_hue = results.get("enhanced_current_hue")
|
||||||
|
else:
|
||||||
|
current_hue = results.get("current_hue")
|
||||||
|
current_saturation = results.get("current_saturation")
|
||||||
|
if current_hue is not None and current_saturation is not None:
|
||||||
|
self._attr_hs_color = (
|
||||||
|
int(current_hue * 360 / 65535)
|
||||||
|
if self._color_channel.enhanced_hue_supported
|
||||||
|
else int(current_hue * 360 / 254),
|
||||||
|
int(current_saturation / 254),
|
||||||
|
)
|
||||||
|
self._attr_xy_color = None
|
||||||
|
self._attr_color_temp = None
|
||||||
|
else:
|
||||||
|
self._attr_color_mode = Color.ColorMode.X_and_Y
|
||||||
color_x = results.get("current_x")
|
color_x = results.get("current_x")
|
||||||
color_y = results.get("current_y")
|
color_y = results.get("current_y")
|
||||||
if color_x is not None and color_y is not None:
|
if color_x is not None and color_y is not None:
|
||||||
self._hs_color = color_util.color_xy_to_hs(
|
self._attr_xy_color = (color_x / 65535, color_y / 65535)
|
||||||
float(color_x / 65535), float(color_y / 65535)
|
self._attr_color_temp = None
|
||||||
)
|
self._attr_hs_color = None
|
||||||
self._color_temp = None
|
|
||||||
|
|
||||||
color_loop_active = results.get("color_loop_active")
|
color_loop_active = results.get("color_loop_active")
|
||||||
if color_loop_active is not None:
|
if color_loop_active is not None:
|
||||||
if color_loop_active == 1:
|
if color_loop_active == 1:
|
||||||
self._effect = light.EFFECT_COLORLOOP
|
self._attr_effect = light.EFFECT_COLORLOOP
|
||||||
else:
|
else:
|
||||||
self._effect = None
|
self._attr_effect = None
|
||||||
|
|
||||||
async def async_update(self):
|
async def async_update(self):
|
||||||
"""Update to the latest state."""
|
"""Update to the latest state."""
|
||||||
@ -671,6 +693,12 @@ class LightGroup(BaseLight, ZhaGroupEntity):
|
|||||||
)
|
)
|
||||||
self._attr_color_mode = None
|
self._attr_color_mode = None
|
||||||
|
|
||||||
|
# remove this when all ZHA platforms and base entities are updated
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Return entity availability."""
|
||||||
|
return self._attr_available
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Run when about to be added to hass."""
|
"""Run when about to be added to hass."""
|
||||||
await super().async_added_to_hass()
|
await super().async_added_to_hass()
|
||||||
@ -700,39 +728,49 @@ class LightGroup(BaseLight, ZhaGroupEntity):
|
|||||||
states: list[State] = list(filter(None, all_states))
|
states: list[State] = list(filter(None, all_states))
|
||||||
on_states = [state for state in states if state.state == STATE_ON]
|
on_states = [state for state in states if state.state == STATE_ON]
|
||||||
|
|
||||||
self._state = len(on_states) > 0
|
self._attr_state = len(on_states) > 0
|
||||||
self._available = any(state.state != STATE_UNAVAILABLE for state in states)
|
self._attr_available = any(state.state != STATE_UNAVAILABLE for state in states)
|
||||||
|
|
||||||
self._brightness = helpers.reduce_attribute(on_states, ATTR_BRIGHTNESS)
|
self._attr_brightness = helpers.reduce_attribute(
|
||||||
|
on_states, light.ATTR_BRIGHTNESS
|
||||||
self._hs_color = helpers.reduce_attribute(
|
|
||||||
on_states, ATTR_HS_COLOR, reduce=helpers.mean_tuple
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self._color_temp = helpers.reduce_attribute(on_states, ATTR_COLOR_TEMP)
|
self._attr_xy_color = helpers.reduce_attribute(
|
||||||
self._min_mireds = helpers.reduce_attribute(
|
on_states, light.ATTR_XY_COLOR, reduce=helpers.mean_tuple
|
||||||
states, ATTR_MIN_MIREDS, default=153, reduce=min
|
|
||||||
)
|
|
||||||
self._max_mireds = helpers.reduce_attribute(
|
|
||||||
states, ATTR_MAX_MIREDS, default=500, reduce=max
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self._effect_list = None
|
self._attr_hs_color = helpers.reduce_attribute(
|
||||||
all_effect_lists = list(helpers.find_state_attributes(states, ATTR_EFFECT_LIST))
|
on_states, light.ATTR_HS_COLOR, reduce=helpers.mean_tuple
|
||||||
|
)
|
||||||
|
|
||||||
|
self._attr_color_temp = helpers.reduce_attribute(
|
||||||
|
on_states, light.ATTR_COLOR_TEMP
|
||||||
|
)
|
||||||
|
self._attr_min_mireds = helpers.reduce_attribute(
|
||||||
|
states, light.ATTR_MIN_MIREDS, default=153, reduce=min
|
||||||
|
)
|
||||||
|
self._attr_max_mireds = helpers.reduce_attribute(
|
||||||
|
states, light.ATTR_MAX_MIREDS, default=500, reduce=max
|
||||||
|
)
|
||||||
|
|
||||||
|
self._attr_effect_list = None
|
||||||
|
all_effect_lists = list(
|
||||||
|
helpers.find_state_attributes(states, light.ATTR_EFFECT_LIST)
|
||||||
|
)
|
||||||
if all_effect_lists:
|
if all_effect_lists:
|
||||||
# Merge all effects from all effect_lists with a union merge.
|
# Merge all effects from all effect_lists with a union merge.
|
||||||
self._effect_list = list(set().union(*all_effect_lists))
|
self._attr_effect_list = list(set().union(*all_effect_lists))
|
||||||
|
|
||||||
self._effect = None
|
self._attr_effect = None
|
||||||
all_effects = list(helpers.find_state_attributes(on_states, ATTR_EFFECT))
|
all_effects = list(helpers.find_state_attributes(on_states, light.ATTR_EFFECT))
|
||||||
if all_effects:
|
if all_effects:
|
||||||
# Report the most common effect.
|
# Report the most common effect.
|
||||||
effects_count = Counter(itertools.chain(all_effects))
|
effects_count = Counter(itertools.chain(all_effects))
|
||||||
self._effect = effects_count.most_common(1)[0][0]
|
self._attr_effect = effects_count.most_common(1)[0][0]
|
||||||
|
|
||||||
self._attr_color_mode = None
|
self._attr_color_mode = None
|
||||||
all_color_modes = list(
|
all_color_modes = list(
|
||||||
helpers.find_state_attributes(on_states, ATTR_COLOR_MODE)
|
helpers.find_state_attributes(on_states, light.ATTR_COLOR_MODE)
|
||||||
)
|
)
|
||||||
if all_color_modes:
|
if all_color_modes:
|
||||||
# Report the most common color mode, select brightness and onoff last
|
# Report the most common color mode, select brightness and onoff last
|
||||||
@ -745,7 +783,7 @@ class LightGroup(BaseLight, ZhaGroupEntity):
|
|||||||
|
|
||||||
self._attr_supported_color_modes = None
|
self._attr_supported_color_modes = None
|
||||||
all_supported_color_modes = list(
|
all_supported_color_modes = list(
|
||||||
helpers.find_state_attributes(states, ATTR_SUPPORTED_COLOR_MODES)
|
helpers.find_state_attributes(states, light.ATTR_SUPPORTED_COLOR_MODES)
|
||||||
)
|
)
|
||||||
if all_supported_color_modes:
|
if all_supported_color_modes:
|
||||||
# Merge all color modes.
|
# Merge all color modes.
|
||||||
@ -753,14 +791,14 @@ class LightGroup(BaseLight, ZhaGroupEntity):
|
|||||||
set[str], set().union(*all_supported_color_modes)
|
set[str], set().union(*all_supported_color_modes)
|
||||||
)
|
)
|
||||||
|
|
||||||
self._supported_features = 0
|
self._attr_supported_features = 0
|
||||||
for support in helpers.find_state_attributes(states, ATTR_SUPPORTED_FEATURES):
|
for support in helpers.find_state_attributes(states, ATTR_SUPPORTED_FEATURES):
|
||||||
# Merge supported features by emulating support for every feature
|
# Merge supported features by emulating support for every feature
|
||||||
# we find.
|
# we find.
|
||||||
self._supported_features |= support
|
self._attr_supported_features |= support
|
||||||
# Bitwise-and the supported features with the GroupedLight's features
|
# Bitwise-and the supported features with the GroupedLight's features
|
||||||
# so that we don't break in the future when a new feature is added.
|
# so that we don't break in the future when a new feature is added.
|
||||||
self._supported_features &= SUPPORT_GROUP_LIGHT
|
self._attr_supported_features &= SUPPORT_GROUP_LIGHT
|
||||||
|
|
||||||
async def _force_member_updates(self):
|
async def _force_member_updates(self):
|
||||||
"""Force the update of member entities to ensure the states are correct for bulbs that don't report their state."""
|
"""Force the update of member entities to ensure the states are correct for bulbs that don't report their state."""
|
||||||
|
@ -151,7 +151,18 @@ async def poll_control_device(zha_device_restored, zigpy_device_mock):
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
(0x0202, 1, {"fan_mode"}),
|
(0x0202, 1, {"fan_mode"}),
|
||||||
(0x0300, 1, {"current_x", "current_y", "color_temperature"}),
|
(
|
||||||
|
0x0300,
|
||||||
|
1,
|
||||||
|
{
|
||||||
|
"current_x",
|
||||||
|
"current_y",
|
||||||
|
"color_temperature",
|
||||||
|
"current_hue",
|
||||||
|
"enhanced_current_hue",
|
||||||
|
"current_saturation",
|
||||||
|
},
|
||||||
|
),
|
||||||
(0x0400, 1, {"measured_value"}),
|
(0x0400, 1, {"measured_value"}),
|
||||||
(0x0401, 1, {"level_status"}),
|
(0x0401, 1, {"level_status"}),
|
||||||
(0x0402, 1, {"measured_value"}),
|
(0x0402, 1, {"measured_value"}),
|
||||||
|
@ -15,11 +15,7 @@ from homeassistant.components.light import (
|
|||||||
ColorMode,
|
ColorMode,
|
||||||
)
|
)
|
||||||
from homeassistant.components.zha.core.group import GroupMember
|
from homeassistant.components.zha.core.group import GroupMember
|
||||||
from homeassistant.components.zha.light import (
|
from homeassistant.components.zha.light import FLASH_EFFECTS
|
||||||
CAPABILITIES_COLOR_TEMP,
|
|
||||||
CAPABILITIES_COLOR_XY,
|
|
||||||
FLASH_EFFECTS,
|
|
||||||
)
|
|
||||||
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, Platform
|
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, Platform
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
@ -148,7 +144,8 @@ async def device_light_1(hass, zigpy_device_mock, zha_device_joined):
|
|||||||
)
|
)
|
||||||
color_cluster = zigpy_device.endpoints[1].light_color
|
color_cluster = zigpy_device.endpoints[1].light_color
|
||||||
color_cluster.PLUGGED_ATTR_READS = {
|
color_cluster.PLUGGED_ATTR_READS = {
|
||||||
"color_capabilities": CAPABILITIES_COLOR_TEMP | CAPABILITIES_COLOR_XY
|
"color_capabilities": lighting.Color.ColorCapabilities.Color_temperature
|
||||||
|
| lighting.Color.ColorCapabilities.XY_attributes
|
||||||
}
|
}
|
||||||
zha_device = await zha_device_joined(zigpy_device)
|
zha_device = await zha_device_joined(zigpy_device)
|
||||||
zha_device.available = True
|
zha_device.available = True
|
||||||
@ -180,7 +177,8 @@ async def device_light_2(hass, zigpy_device_mock, zha_device_joined):
|
|||||||
)
|
)
|
||||||
color_cluster = zigpy_device.endpoints[1].light_color
|
color_cluster = zigpy_device.endpoints[1].light_color
|
||||||
color_cluster.PLUGGED_ATTR_READS = {
|
color_cluster.PLUGGED_ATTR_READS = {
|
||||||
"color_capabilities": CAPABILITIES_COLOR_TEMP | CAPABILITIES_COLOR_XY
|
"color_capabilities": lighting.Color.ColorCapabilities.Color_temperature
|
||||||
|
| lighting.Color.ColorCapabilities.XY_attributes
|
||||||
}
|
}
|
||||||
zha_device = await zha_device_joined(zigpy_device)
|
zha_device = await zha_device_joined(zigpy_device)
|
||||||
zha_device.available = True
|
zha_device.available = True
|
||||||
@ -239,7 +237,8 @@ async def eWeLink_light(hass, zigpy_device_mock, zha_device_joined):
|
|||||||
)
|
)
|
||||||
color_cluster = zigpy_device.endpoints[1].light_color
|
color_cluster = zigpy_device.endpoints[1].light_color
|
||||||
color_cluster.PLUGGED_ATTR_READS = {
|
color_cluster.PLUGGED_ATTR_READS = {
|
||||||
"color_capabilities": CAPABILITIES_COLOR_TEMP | CAPABILITIES_COLOR_XY
|
"color_capabilities": lighting.Color.ColorCapabilities.Color_temperature
|
||||||
|
| lighting.Color.ColorCapabilities.XY_attributes
|
||||||
}
|
}
|
||||||
zha_device = await zha_device_joined(zigpy_device)
|
zha_device = await zha_device_joined(zigpy_device)
|
||||||
zha_device.available = True
|
zha_device.available = True
|
||||||
@ -302,7 +301,7 @@ async def test_light_refresh(hass, zigpy_device_mock, zha_device_joined_restored
|
|||||||
)
|
)
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"device, reporting",
|
"device, reporting",
|
||||||
[(LIGHT_ON_OFF, (1, 0, 0)), (LIGHT_LEVEL, (1, 1, 0)), (LIGHT_COLOR, (1, 1, 3))],
|
[(LIGHT_ON_OFF, (1, 0, 0)), (LIGHT_LEVEL, (1, 1, 0)), (LIGHT_COLOR, (1, 1, 6))],
|
||||||
)
|
)
|
||||||
async def test_light(
|
async def test_light(
|
||||||
hass, zigpy_device_mock, zha_device_joined_restored, device, reporting
|
hass, zigpy_device_mock, zha_device_joined_restored, device, reporting
|
||||||
@ -1400,7 +1399,7 @@ async def test_zha_group_light_entity(
|
|||||||
assert group_state.state == STATE_OFF
|
assert group_state.state == STATE_OFF
|
||||||
assert group_state.attributes["supported_color_modes"] == [
|
assert group_state.attributes["supported_color_modes"] == [
|
||||||
ColorMode.COLOR_TEMP,
|
ColorMode.COLOR_TEMP,
|
||||||
ColorMode.HS,
|
ColorMode.XY,
|
||||||
]
|
]
|
||||||
# Light which is off has no color mode
|
# Light which is off has no color mode
|
||||||
assert "color_mode" not in group_state.attributes
|
assert "color_mode" not in group_state.attributes
|
||||||
@ -1431,9 +1430,9 @@ async def test_zha_group_light_entity(
|
|||||||
assert group_state.state == STATE_ON
|
assert group_state.state == STATE_ON
|
||||||
assert group_state.attributes["supported_color_modes"] == [
|
assert group_state.attributes["supported_color_modes"] == [
|
||||||
ColorMode.COLOR_TEMP,
|
ColorMode.COLOR_TEMP,
|
||||||
ColorMode.HS,
|
ColorMode.XY,
|
||||||
]
|
]
|
||||||
assert group_state.attributes["color_mode"] == ColorMode.HS
|
assert group_state.attributes["color_mode"] == ColorMode.XY
|
||||||
|
|
||||||
# test long flashing the lights from the HA
|
# test long flashing the lights from the HA
|
||||||
await async_test_flash_from_hass(
|
await async_test_flash_from_hass(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user