mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Fix ZHA light turn on issues (#75220)
* rename variable * default transition is for color commands not level * no extra command for groups * don't transition color change when light off -> on * clean up * update condition * fix condition again... * simplify * simplify * missed one * rename * simplify * rename * tests * color_provided_while_off with no changes * fix missing flag clear * more tests for transition scenarios * add to comment * fix comment * don't transition when force on is set * stale comment * dont transition when colors don't change * remove extra line * remove debug print :) * fix colors * restore color to 65535 until investigated
This commit is contained in:
parent
9d0c91d648
commit
6f5e4ca503
@ -73,7 +73,6 @@ CAPABILITIES_COLOR_LOOP = 0x4
|
|||||||
CAPABILITIES_COLOR_XY = 0x08
|
CAPABILITIES_COLOR_XY = 0x08
|
||||||
CAPABILITIES_COLOR_TEMP = 0x10
|
CAPABILITIES_COLOR_TEMP = 0x10
|
||||||
|
|
||||||
DEFAULT_TRANSITION = 1
|
|
||||||
DEFAULT_MIN_BRIGHTNESS = 2
|
DEFAULT_MIN_BRIGHTNESS = 2
|
||||||
|
|
||||||
UPDATE_COLORLOOP_ACTION = 0x1
|
UPDATE_COLORLOOP_ACTION = 0x1
|
||||||
@ -119,7 +118,7 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
"""Operations common to all light entities."""
|
"""Operations common to all light entities."""
|
||||||
|
|
||||||
_FORCE_ON = False
|
_FORCE_ON = False
|
||||||
_DEFAULT_COLOR_FROM_OFF_TRANSITION = 0
|
_DEFAULT_MIN_TRANSITION_TIME = 0
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Initialize the light."""
|
"""Initialize the light."""
|
||||||
@ -140,7 +139,7 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
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._default_transition = None
|
self._zha_config_transition = self._DEFAULT_MIN_TRANSITION_TIME
|
||||||
self._attr_color_mode = ColorMode.UNKNOWN # Set by sub classes
|
self._attr_color_mode = ColorMode.UNKNOWN # Set by sub classes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -216,33 +215,49 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
transition = kwargs.get(light.ATTR_TRANSITION)
|
transition = kwargs.get(light.ATTR_TRANSITION)
|
||||||
duration = (
|
duration = (
|
||||||
transition * 10
|
transition * 10
|
||||||
if transition
|
if transition is not None
|
||||||
else self._default_transition * 10
|
else self._zha_config_transition * 10
|
||||||
if self._default_transition
|
) or self._DEFAULT_MIN_TRANSITION_TIME # if 0 is passed in some devices still need the minimum default
|
||||||
else DEFAULT_TRANSITION
|
|
||||||
)
|
|
||||||
brightness = kwargs.get(light.ATTR_BRIGHTNESS)
|
brightness = kwargs.get(light.ATTR_BRIGHTNESS)
|
||||||
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)
|
||||||
|
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,
|
||||||
# the light needs to be turned on first at a low brightness level where the light is immediately transitioned
|
# the light needs to be turned on first at a low brightness level where the light is immediately transitioned
|
||||||
# to the correct color. Afterwards, the transition is only from the low brightness to the new brightness.
|
# to the correct color. Afterwards, the transition is only from the low brightness to the new brightness.
|
||||||
# Otherwise, the transition is from the color the light had before being turned on to the new color.
|
# Otherwise, the transition is from the color the light had before being turned on to the new color.
|
||||||
# This can look especially bad with transitions longer than a second.
|
# This can look especially bad with transitions longer than a second. We do not want to do this for
|
||||||
color_provided_from_off = (
|
# devices that need to be forced to use the on command because we would end up with 4 commands sent:
|
||||||
not self._state
|
# move to level, on, color, move to level... We also will not set this if the bulb is already in the
|
||||||
|
# desired color mode with the desired color or color temperature.
|
||||||
|
new_color_provided_while_off = (
|
||||||
|
not isinstance(self, LightGroup)
|
||||||
|
and not self._FORCE_ON
|
||||||
|
and not self._state
|
||||||
|
and (
|
||||||
|
(
|
||||||
|
temperature is not None
|
||||||
|
and (
|
||||||
|
self._color_temp != temperature
|
||||||
|
or self._attr_color_mode != ColorMode.COLOR_TEMP
|
||||||
|
)
|
||||||
|
)
|
||||||
|
or (
|
||||||
|
hs_color is not None
|
||||||
|
and (
|
||||||
|
self.hs_color != hs_color
|
||||||
|
or self._attr_color_mode != ColorMode.HS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
and brightness_supported(self._attr_supported_color_modes)
|
and brightness_supported(self._attr_supported_color_modes)
|
||||||
and (light.ATTR_COLOR_TEMP in kwargs or light.ATTR_HS_COLOR in kwargs)
|
|
||||||
)
|
)
|
||||||
final_duration = duration
|
|
||||||
if color_provided_from_off:
|
|
||||||
# Set the duration for the color changing commands to 0.
|
|
||||||
duration = 0
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
brightness is None
|
brightness is None
|
||||||
and (self._off_with_transition or color_provided_from_off)
|
and (self._off_with_transition or new_color_provided_while_off)
|
||||||
and self._off_brightness is not None
|
and self._off_brightness is not None
|
||||||
):
|
):
|
||||||
brightness = self._off_brightness
|
brightness = self._off_brightness
|
||||||
@ -254,11 +269,11 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
|
|
||||||
t_log = {}
|
t_log = {}
|
||||||
|
|
||||||
if color_provided_from_off:
|
if new_color_provided_while_off:
|
||||||
# If the light is currently off, we first need to turn it on at a low brightness level with no transition.
|
# If the light is currently off, we first need to turn it on at a low brightness level with no transition.
|
||||||
# After that, we set it to the desired color/temperature with no transition.
|
# After that, we set it to the desired color/temperature with no transition.
|
||||||
result = await self._level_channel.move_to_level_with_on_off(
|
result = await self._level_channel.move_to_level_with_on_off(
|
||||||
DEFAULT_MIN_BRIGHTNESS, self._DEFAULT_COLOR_FROM_OFF_TRANSITION
|
DEFAULT_MIN_BRIGHTNESS, self._DEFAULT_MIN_TRANSITION_TIME
|
||||||
)
|
)
|
||||||
t_log["move_to_level_with_on_off"] = result
|
t_log["move_to_level_with_on_off"] = result
|
||||||
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
||||||
@ -269,7 +284,7 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
(brightness is not None or transition)
|
(brightness is not None or transition)
|
||||||
and not color_provided_from_off
|
and not new_color_provided_while_off
|
||||||
and brightness_supported(self._attr_supported_color_modes)
|
and brightness_supported(self._attr_supported_color_modes)
|
||||||
):
|
):
|
||||||
result = await self._level_channel.move_to_level_with_on_off(
|
result = await self._level_channel.move_to_level_with_on_off(
|
||||||
@ -285,7 +300,7 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
brightness is None
|
brightness is None
|
||||||
and not color_provided_from_off
|
and not new_color_provided_while_off
|
||||||
or (self._FORCE_ON and brightness)
|
or (self._FORCE_ON and brightness)
|
||||||
):
|
):
|
||||||
# since some lights don't always turn on with move_to_level_with_on_off,
|
# since some lights don't always turn on with move_to_level_with_on_off,
|
||||||
@ -297,9 +312,13 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
return
|
return
|
||||||
self._state = True
|
self._state = True
|
||||||
|
|
||||||
if light.ATTR_COLOR_TEMP in kwargs:
|
if temperature is not None:
|
||||||
temperature = kwargs[light.ATTR_COLOR_TEMP]
|
result = await self._color_channel.move_to_color_temp(
|
||||||
result = await self._color_channel.move_to_color_temp(temperature, duration)
|
temperature,
|
||||||
|
self._DEFAULT_MIN_TRANSITION_TIME
|
||||||
|
if new_color_provided_while_off
|
||||||
|
else duration,
|
||||||
|
)
|
||||||
t_log["move_to_color_temp"] = result
|
t_log["move_to_color_temp"] = result
|
||||||
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)
|
||||||
@ -308,11 +327,14 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
self._color_temp = temperature
|
self._color_temp = temperature
|
||||||
self._hs_color = None
|
self._hs_color = None
|
||||||
|
|
||||||
if light.ATTR_HS_COLOR in kwargs:
|
if hs_color is not None:
|
||||||
hs_color = kwargs[light.ATTR_HS_COLOR]
|
|
||||||
xy_color = color_util.color_hs_to_xy(*hs_color)
|
xy_color = color_util.color_hs_to_xy(*hs_color)
|
||||||
result = await self._color_channel.move_to_color(
|
result = await self._color_channel.move_to_color(
|
||||||
int(xy_color[0] * 65535), int(xy_color[1] * 65535), duration
|
int(xy_color[0] * 65535),
|
||||||
|
int(xy_color[1] * 65535),
|
||||||
|
self._DEFAULT_MIN_TRANSITION_TIME
|
||||||
|
if new_color_provided_while_off
|
||||||
|
else duration,
|
||||||
)
|
)
|
||||||
t_log["move_to_color"] = result
|
t_log["move_to_color"] = result
|
||||||
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
|
||||||
@ -322,9 +344,9 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
self._hs_color = hs_color
|
self._hs_color = hs_color
|
||||||
self._color_temp = None
|
self._color_temp = None
|
||||||
|
|
||||||
if color_provided_from_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.
|
||||||
result = await self._level_channel.move_to_level(level, final_duration)
|
result = await self._level_channel.move_to_level(level, duration)
|
||||||
t_log["move_to_level_if_color"] = result
|
t_log["move_to_level_if_color"] = result
|
||||||
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)
|
||||||
@ -371,12 +393,13 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
|
|
||||||
async def async_turn_off(self, **kwargs):
|
async def async_turn_off(self, **kwargs):
|
||||||
"""Turn the entity off."""
|
"""Turn the entity off."""
|
||||||
duration = kwargs.get(light.ATTR_TRANSITION)
|
transition = kwargs.get(light.ATTR_TRANSITION)
|
||||||
supports_level = brightness_supported(self._attr_supported_color_modes)
|
supports_level = brightness_supported(self._attr_supported_color_modes)
|
||||||
|
|
||||||
if duration and supports_level:
|
# is not none looks odd here but it will override built in bulb transition times if we pass 0 in here
|
||||||
|
if transition is not None and supports_level:
|
||||||
result = await self._level_channel.move_to_level_with_on_off(
|
result = await self._level_channel.move_to_level_with_on_off(
|
||||||
0, duration * 10
|
0, transition * 10
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
result = await self._on_off_channel.off()
|
result = await self._on_off_channel.off()
|
||||||
@ -387,7 +410,7 @@ class BaseLight(LogMixin, light.LightEntity):
|
|||||||
|
|
||||||
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 = bool(duration)
|
self._off_with_transition = transition is not None
|
||||||
self._off_brightness = self._brightness
|
self._off_brightness = self._brightness
|
||||||
|
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
@ -460,7 +483,7 @@ class Light(BaseLight, ZhaEntity):
|
|||||||
if effect_list:
|
if effect_list:
|
||||||
self._effect_list = effect_list
|
self._effect_list = effect_list
|
||||||
|
|
||||||
self._default_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,
|
||||||
ZHA_OPTIONS,
|
ZHA_OPTIONS,
|
||||||
CONF_DEFAULT_LIGHT_TRANSITION,
|
CONF_DEFAULT_LIGHT_TRANSITION,
|
||||||
@ -472,6 +495,7 @@ class Light(BaseLight, ZhaEntity):
|
|||||||
"""Set the state."""
|
"""Set the state."""
|
||||||
self._state = bool(value)
|
self._state = bool(value)
|
||||||
if value:
|
if value:
|
||||||
|
self._off_with_transition = False
|
||||||
self._off_brightness = None
|
self._off_brightness = None
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@ -605,7 +629,7 @@ class HueLight(Light):
|
|||||||
@STRICT_MATCH(
|
@STRICT_MATCH(
|
||||||
channel_names=CHANNEL_ON_OFF,
|
channel_names=CHANNEL_ON_OFF,
|
||||||
aux_channels={CHANNEL_COLOR, CHANNEL_LEVEL},
|
aux_channels={CHANNEL_COLOR, CHANNEL_LEVEL},
|
||||||
manufacturers={"Jasco", "Quotra-Vision"},
|
manufacturers={"Jasco", "Quotra-Vision", "eWeLight", "eWeLink"},
|
||||||
)
|
)
|
||||||
class ForceOnLight(Light):
|
class ForceOnLight(Light):
|
||||||
"""Representation of a light which does not respect move_to_level_with_on_off."""
|
"""Representation of a light which does not respect move_to_level_with_on_off."""
|
||||||
@ -621,7 +645,7 @@ class ForceOnLight(Light):
|
|||||||
class SengledLight(Light):
|
class SengledLight(Light):
|
||||||
"""Representation of a Sengled light which does not react to move_to_color_temp with 0 as a transition."""
|
"""Representation of a Sengled light which does not react to move_to_color_temp with 0 as a transition."""
|
||||||
|
|
||||||
_DEFAULT_COLOR_FROM_OFF_TRANSITION = 1
|
_DEFAULT_MIN_TRANSITION_TIME = 1
|
||||||
|
|
||||||
|
|
||||||
@GROUP_MATCH()
|
@GROUP_MATCH()
|
||||||
@ -639,7 +663,7 @@ class LightGroup(BaseLight, ZhaGroupEntity):
|
|||||||
self._color_channel = group.endpoint[Color.cluster_id]
|
self._color_channel = group.endpoint[Color.cluster_id]
|
||||||
self._identify_channel = group.endpoint[Identify.cluster_id]
|
self._identify_channel = group.endpoint[Identify.cluster_id]
|
||||||
self._debounced_member_refresh = None
|
self._debounced_member_refresh = None
|
||||||
self._default_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,
|
||||||
ZHA_OPTIONS,
|
ZHA_OPTIONS,
|
||||||
CONF_DEFAULT_LIGHT_TRANSITION,
|
CONF_DEFAULT_LIGHT_TRANSITION,
|
||||||
|
@ -15,7 +15,11 @@ 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 FLASH_EFFECTS
|
from homeassistant.components.zha.light import (
|
||||||
|
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
|
||||||
|
|
||||||
@ -142,6 +146,10 @@ async def device_light_1(hass, zigpy_device_mock, zha_device_joined):
|
|||||||
ieee=IEEE_GROUPABLE_DEVICE,
|
ieee=IEEE_GROUPABLE_DEVICE,
|
||||||
nwk=0xB79D,
|
nwk=0xB79D,
|
||||||
)
|
)
|
||||||
|
color_cluster = zigpy_device.endpoints[1].light_color
|
||||||
|
color_cluster.PLUGGED_ATTR_READS = {
|
||||||
|
"color_capabilities": CAPABILITIES_COLOR_TEMP | CAPABILITIES_COLOR_XY
|
||||||
|
}
|
||||||
zha_device = await zha_device_joined(zigpy_device)
|
zha_device = await zha_device_joined(zigpy_device)
|
||||||
zha_device.available = True
|
zha_device.available = True
|
||||||
return zha_device
|
return zha_device
|
||||||
@ -167,8 +175,13 @@ async def device_light_2(hass, zigpy_device_mock, zha_device_joined):
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
ieee=IEEE_GROUPABLE_DEVICE2,
|
ieee=IEEE_GROUPABLE_DEVICE2,
|
||||||
|
manufacturer="Sengled",
|
||||||
nwk=0xC79E,
|
nwk=0xC79E,
|
||||||
)
|
)
|
||||||
|
color_cluster = zigpy_device.endpoints[1].light_color
|
||||||
|
color_cluster.PLUGGED_ATTR_READS = {
|
||||||
|
"color_capabilities": CAPABILITIES_COLOR_TEMP | CAPABILITIES_COLOR_XY
|
||||||
|
}
|
||||||
zha_device = await zha_device_joined(zigpy_device)
|
zha_device = await zha_device_joined(zigpy_device)
|
||||||
zha_device.available = True
|
zha_device.available = True
|
||||||
return zha_device
|
return zha_device
|
||||||
@ -201,6 +214,38 @@ async def device_light_3(hass, zigpy_device_mock, zha_device_joined):
|
|||||||
return zha_device
|
return zha_device
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def eWeLink_light(hass, zigpy_device_mock, zha_device_joined):
|
||||||
|
"""Mock eWeLink light."""
|
||||||
|
|
||||||
|
zigpy_device = zigpy_device_mock(
|
||||||
|
{
|
||||||
|
1: {
|
||||||
|
SIG_EP_INPUT: [
|
||||||
|
general.OnOff.cluster_id,
|
||||||
|
general.LevelControl.cluster_id,
|
||||||
|
lighting.Color.cluster_id,
|
||||||
|
general.Groups.cluster_id,
|
||||||
|
general.Identify.cluster_id,
|
||||||
|
],
|
||||||
|
SIG_EP_OUTPUT: [],
|
||||||
|
SIG_EP_TYPE: zha.DeviceType.COLOR_DIMMABLE_LIGHT,
|
||||||
|
SIG_EP_PROFILE: zha.PROFILE_ID,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ieee="03:2d:6f:00:0a:90:69:e3",
|
||||||
|
manufacturer="eWeLink",
|
||||||
|
nwk=0xB79D,
|
||||||
|
)
|
||||||
|
color_cluster = zigpy_device.endpoints[1].light_color
|
||||||
|
color_cluster.PLUGGED_ATTR_READS = {
|
||||||
|
"color_capabilities": CAPABILITIES_COLOR_TEMP | CAPABILITIES_COLOR_XY
|
||||||
|
}
|
||||||
|
zha_device = await zha_device_joined(zigpy_device)
|
||||||
|
zha_device.available = True
|
||||||
|
return zha_device
|
||||||
|
|
||||||
|
|
||||||
async def test_light_refresh(hass, zigpy_device_mock, zha_device_joined_restored):
|
async def test_light_refresh(hass, zigpy_device_mock, zha_device_joined_restored):
|
||||||
"""Test zha light platform refresh."""
|
"""Test zha light platform refresh."""
|
||||||
|
|
||||||
@ -323,6 +368,758 @@ async def test_light(
|
|||||||
await async_test_flash_from_hass(hass, cluster_identify, entity_id, FLASH_LONG)
|
await async_test_flash_from_hass(hass, cluster_identify, entity_id, FLASH_LONG)
|
||||||
|
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
"zigpy.zcl.clusters.lighting.Color.request",
|
||||||
|
new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
|
||||||
|
)
|
||||||
|
@patch(
|
||||||
|
"zigpy.zcl.clusters.general.Identify.request",
|
||||||
|
new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
|
||||||
|
)
|
||||||
|
@patch(
|
||||||
|
"zigpy.zcl.clusters.general.LevelControl.request",
|
||||||
|
new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
|
||||||
|
)
|
||||||
|
@patch(
|
||||||
|
"zigpy.zcl.clusters.general.OnOff.request",
|
||||||
|
new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]),
|
||||||
|
)
|
||||||
|
async def test_transitions(
|
||||||
|
hass, device_light_1, device_light_2, eWeLink_light, coordinator
|
||||||
|
):
|
||||||
|
"""Test ZHA light transition code."""
|
||||||
|
zha_gateway = get_zha_gateway(hass)
|
||||||
|
assert zha_gateway is not None
|
||||||
|
zha_gateway.coordinator_zha_device = coordinator
|
||||||
|
coordinator._zha_gateway = zha_gateway
|
||||||
|
device_light_1._zha_gateway = zha_gateway
|
||||||
|
device_light_2._zha_gateway = zha_gateway
|
||||||
|
member_ieee_addresses = [device_light_1.ieee, device_light_2.ieee]
|
||||||
|
members = [GroupMember(device_light_1.ieee, 1), GroupMember(device_light_2.ieee, 1)]
|
||||||
|
|
||||||
|
assert coordinator.is_coordinator
|
||||||
|
|
||||||
|
# test creating a group with 2 members
|
||||||
|
zha_group = await zha_gateway.async_create_zigpy_group("Test Group", members)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert zha_group is not None
|
||||||
|
assert len(zha_group.members) == 2
|
||||||
|
for member in zha_group.members:
|
||||||
|
assert member.device.ieee in member_ieee_addresses
|
||||||
|
assert member.group == zha_group
|
||||||
|
assert member.endpoint is not None
|
||||||
|
|
||||||
|
device_1_entity_id = await find_entity_id(Platform.LIGHT, device_light_1, hass)
|
||||||
|
device_2_entity_id = await find_entity_id(Platform.LIGHT, device_light_2, hass)
|
||||||
|
eWeLink_light_entity_id = await find_entity_id(Platform.LIGHT, eWeLink_light, hass)
|
||||||
|
assert device_1_entity_id != device_2_entity_id
|
||||||
|
|
||||||
|
group_entity_id = async_find_group_entity_id(hass, Platform.LIGHT, zha_group)
|
||||||
|
assert hass.states.get(group_entity_id) is not None
|
||||||
|
|
||||||
|
assert device_1_entity_id in zha_group.member_entity_ids
|
||||||
|
assert device_2_entity_id in zha_group.member_entity_ids
|
||||||
|
|
||||||
|
dev1_cluster_on_off = device_light_1.device.endpoints[1].on_off
|
||||||
|
dev2_cluster_on_off = device_light_2.device.endpoints[1].on_off
|
||||||
|
eWeLink_cluster_on_off = eWeLink_light.device.endpoints[1].on_off
|
||||||
|
|
||||||
|
dev1_cluster_level = device_light_1.device.endpoints[1].level
|
||||||
|
dev2_cluster_level = device_light_2.device.endpoints[1].level
|
||||||
|
eWeLink_cluster_level = eWeLink_light.device.endpoints[1].level
|
||||||
|
|
||||||
|
dev1_cluster_color = device_light_1.device.endpoints[1].light_color
|
||||||
|
dev2_cluster_color = device_light_2.device.endpoints[1].light_color
|
||||||
|
eWeLink_cluster_color = eWeLink_light.device.endpoints[1].light_color
|
||||||
|
|
||||||
|
# allow traffic to flow through the gateway and device
|
||||||
|
await async_enable_traffic(hass, [device_light_1, device_light_2])
|
||||||
|
await async_wait_for_updates(hass)
|
||||||
|
|
||||||
|
# test that the lights were created and are off
|
||||||
|
group_state = hass.states.get(group_entity_id)
|
||||||
|
assert group_state.state == STATE_OFF
|
||||||
|
light1_state = hass.states.get(device_1_entity_id)
|
||||||
|
assert light1_state.state == STATE_OFF
|
||||||
|
light2_state = hass.states.get(device_2_entity_id)
|
||||||
|
assert light2_state.state == STATE_OFF
|
||||||
|
|
||||||
|
# first test 0 length transition with no color provided
|
||||||
|
dev1_cluster_on_off.request.reset_mock()
|
||||||
|
dev1_cluster_level.request.reset_mock()
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": device_1_entity_id, "transition": 0, "brightness": 50},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert dev1_cluster_on_off.request.call_count == 0
|
||||||
|
assert dev1_cluster_on_off.request.await_count == 0
|
||||||
|
assert dev1_cluster_color.request.call_count == 0
|
||||||
|
assert dev1_cluster_color.request.await_count == 0
|
||||||
|
assert dev1_cluster_level.request.call_count == 1
|
||||||
|
assert dev1_cluster_level.request.await_count == 1
|
||||||
|
assert dev1_cluster_level.request.call_args == call(
|
||||||
|
False,
|
||||||
|
4,
|
||||||
|
dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
|
||||||
|
50, # brightness (level in ZCL)
|
||||||
|
0, # transition time
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
light1_state = hass.states.get(device_1_entity_id)
|
||||||
|
assert light1_state.state == STATE_ON
|
||||||
|
assert light1_state.attributes["brightness"] == 50
|
||||||
|
|
||||||
|
dev1_cluster_level.request.reset_mock()
|
||||||
|
|
||||||
|
# test non 0 length transition with color provided while light is on
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
"turn_on",
|
||||||
|
{
|
||||||
|
"entity_id": device_1_entity_id,
|
||||||
|
"transition": 3,
|
||||||
|
"brightness": 18,
|
||||||
|
"color_temp": 432,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert dev1_cluster_on_off.request.call_count == 0
|
||||||
|
assert dev1_cluster_on_off.request.await_count == 0
|
||||||
|
assert dev1_cluster_color.request.call_count == 1
|
||||||
|
assert dev1_cluster_color.request.await_count == 1
|
||||||
|
assert dev1_cluster_level.request.call_count == 1
|
||||||
|
assert dev1_cluster_level.request.await_count == 1
|
||||||
|
assert dev1_cluster_level.request.call_args == call(
|
||||||
|
False,
|
||||||
|
4,
|
||||||
|
dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
|
||||||
|
18, # brightness (level in ZCL)
|
||||||
|
30, # transition time (ZCL time in 10ths of a second)
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
assert dev1_cluster_color.request.call_args == call(
|
||||||
|
False,
|
||||||
|
10,
|
||||||
|
dev1_cluster_color.commands_by_name["move_to_color_temp"].schema,
|
||||||
|
432, # color temp mireds
|
||||||
|
30.0, # transition time (ZCL time in 10ths of a second)
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
light1_state = hass.states.get(device_1_entity_id)
|
||||||
|
assert light1_state.state == STATE_ON
|
||||||
|
assert light1_state.attributes["brightness"] == 18
|
||||||
|
assert light1_state.attributes["color_temp"] == 432
|
||||||
|
assert light1_state.attributes["color_mode"] == ColorMode.COLOR_TEMP
|
||||||
|
|
||||||
|
dev1_cluster_level.request.reset_mock()
|
||||||
|
dev1_cluster_color.request.reset_mock()
|
||||||
|
|
||||||
|
# test 0 length transition to turn light off
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
"turn_off",
|
||||||
|
{
|
||||||
|
"entity_id": device_1_entity_id,
|
||||||
|
"transition": 0,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert dev1_cluster_on_off.request.call_count == 0
|
||||||
|
assert dev1_cluster_on_off.request.await_count == 0
|
||||||
|
assert dev1_cluster_color.request.call_count == 0
|
||||||
|
assert dev1_cluster_color.request.await_count == 0
|
||||||
|
assert dev1_cluster_level.request.call_count == 1
|
||||||
|
assert dev1_cluster_level.request.await_count == 1
|
||||||
|
assert dev1_cluster_level.request.call_args == call(
|
||||||
|
False,
|
||||||
|
4,
|
||||||
|
dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
|
||||||
|
0, # brightness (level in ZCL)
|
||||||
|
0, # transition time (ZCL time in 10ths of a second)
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
light1_state = hass.states.get(device_1_entity_id)
|
||||||
|
assert light1_state.state == STATE_OFF
|
||||||
|
|
||||||
|
dev1_cluster_level.request.reset_mock()
|
||||||
|
|
||||||
|
# test non 0 length transition and color temp while turning light on (color_provided_while_off)
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
"turn_on",
|
||||||
|
{
|
||||||
|
"entity_id": device_1_entity_id,
|
||||||
|
"transition": 1,
|
||||||
|
"brightness": 25,
|
||||||
|
"color_temp": 235,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert dev1_cluster_on_off.request.call_count == 0
|
||||||
|
assert dev1_cluster_on_off.request.await_count == 0
|
||||||
|
assert dev1_cluster_color.request.call_count == 1
|
||||||
|
assert dev1_cluster_color.request.await_count == 1
|
||||||
|
assert dev1_cluster_level.request.call_count == 2
|
||||||
|
assert dev1_cluster_level.request.await_count == 2
|
||||||
|
|
||||||
|
# first it comes on with no transition at 2 brightness
|
||||||
|
assert dev1_cluster_level.request.call_args_list[0] == call(
|
||||||
|
False,
|
||||||
|
4,
|
||||||
|
dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
|
||||||
|
2, # brightness (level in ZCL)
|
||||||
|
0, # transition time (ZCL time in 10ths of a second)
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
assert dev1_cluster_color.request.call_args == call(
|
||||||
|
False,
|
||||||
|
10,
|
||||||
|
dev1_cluster_color.commands_by_name["move_to_color_temp"].schema,
|
||||||
|
235, # color temp mireds
|
||||||
|
0, # transition time (ZCL time in 10ths of a second) - no transition when color_provided_while_off
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
assert dev1_cluster_level.request.call_args_list[1] == call(
|
||||||
|
False,
|
||||||
|
0,
|
||||||
|
dev1_cluster_level.commands_by_name["move_to_level"].schema,
|
||||||
|
25, # brightness (level in ZCL)
|
||||||
|
10.0, # transition time (ZCL time in 10ths of a second)
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
light1_state = hass.states.get(device_1_entity_id)
|
||||||
|
assert light1_state.state == STATE_ON
|
||||||
|
assert light1_state.attributes["brightness"] == 25
|
||||||
|
assert light1_state.attributes["color_temp"] == 235
|
||||||
|
assert light1_state.attributes["color_mode"] == ColorMode.COLOR_TEMP
|
||||||
|
|
||||||
|
dev1_cluster_level.request.reset_mock()
|
||||||
|
dev1_cluster_color.request.reset_mock()
|
||||||
|
|
||||||
|
# turn light 1 back off
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
"turn_off",
|
||||||
|
{
|
||||||
|
"entity_id": device_1_entity_id,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert dev1_cluster_on_off.request.call_count == 1
|
||||||
|
assert dev1_cluster_on_off.request.await_count == 1
|
||||||
|
assert dev1_cluster_color.request.call_count == 0
|
||||||
|
assert dev1_cluster_color.request.await_count == 0
|
||||||
|
assert dev1_cluster_level.request.call_count == 0
|
||||||
|
assert dev1_cluster_level.request.await_count == 0
|
||||||
|
group_state = hass.states.get(group_entity_id)
|
||||||
|
assert group_state.state == STATE_OFF
|
||||||
|
|
||||||
|
dev1_cluster_on_off.request.reset_mock()
|
||||||
|
dev1_cluster_color.request.reset_mock()
|
||||||
|
dev1_cluster_level.request.reset_mock()
|
||||||
|
|
||||||
|
# test no transition provided and color temp while turning light on (color_provided_while_off)
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
"turn_on",
|
||||||
|
{
|
||||||
|
"entity_id": device_1_entity_id,
|
||||||
|
"brightness": 25,
|
||||||
|
"color_temp": 236,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert dev1_cluster_on_off.request.call_count == 0
|
||||||
|
assert dev1_cluster_on_off.request.await_count == 0
|
||||||
|
assert dev1_cluster_color.request.call_count == 1
|
||||||
|
assert dev1_cluster_color.request.await_count == 1
|
||||||
|
assert dev1_cluster_level.request.call_count == 2
|
||||||
|
assert dev1_cluster_level.request.await_count == 2
|
||||||
|
|
||||||
|
# first it comes on with no transition at 2 brightness
|
||||||
|
assert dev1_cluster_level.request.call_args_list[0] == call(
|
||||||
|
False,
|
||||||
|
4,
|
||||||
|
dev1_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
|
||||||
|
2, # brightness (level in ZCL)
|
||||||
|
0, # transition time (ZCL time in 10ths of a second)
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
assert dev1_cluster_color.request.call_args == call(
|
||||||
|
False,
|
||||||
|
10,
|
||||||
|
dev1_cluster_color.commands_by_name["move_to_color_temp"].schema,
|
||||||
|
236, # color temp mireds
|
||||||
|
0, # transition time (ZCL time in 10ths of a second) - no transition when color_provided_while_off
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
assert dev1_cluster_level.request.call_args_list[1] == call(
|
||||||
|
False,
|
||||||
|
0,
|
||||||
|
dev1_cluster_level.commands_by_name["move_to_level"].schema,
|
||||||
|
25, # brightness (level in ZCL)
|
||||||
|
0, # transition time (ZCL time in 10ths of a second)
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
light1_state = hass.states.get(device_1_entity_id)
|
||||||
|
assert light1_state.state == STATE_ON
|
||||||
|
assert light1_state.attributes["brightness"] == 25
|
||||||
|
assert light1_state.attributes["color_temp"] == 236
|
||||||
|
assert light1_state.attributes["color_mode"] == ColorMode.COLOR_TEMP
|
||||||
|
|
||||||
|
dev1_cluster_level.request.reset_mock()
|
||||||
|
dev1_cluster_color.request.reset_mock()
|
||||||
|
|
||||||
|
# turn light 1 back off to setup group test
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
"turn_off",
|
||||||
|
{
|
||||||
|
"entity_id": device_1_entity_id,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert dev1_cluster_on_off.request.call_count == 1
|
||||||
|
assert dev1_cluster_on_off.request.await_count == 1
|
||||||
|
assert dev1_cluster_color.request.call_count == 0
|
||||||
|
assert dev1_cluster_color.request.await_count == 0
|
||||||
|
assert dev1_cluster_level.request.call_count == 0
|
||||||
|
assert dev1_cluster_level.request.await_count == 0
|
||||||
|
group_state = hass.states.get(group_entity_id)
|
||||||
|
assert group_state.state == STATE_OFF
|
||||||
|
|
||||||
|
dev1_cluster_on_off.request.reset_mock()
|
||||||
|
dev1_cluster_color.request.reset_mock()
|
||||||
|
dev1_cluster_level.request.reset_mock()
|
||||||
|
|
||||||
|
# test no transition when the same color temp is provided from off
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
"turn_on",
|
||||||
|
{
|
||||||
|
"entity_id": device_1_entity_id,
|
||||||
|
"color_temp": 236,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert dev1_cluster_on_off.request.call_count == 1
|
||||||
|
assert dev1_cluster_on_off.request.await_count == 1
|
||||||
|
assert dev1_cluster_color.request.call_count == 1
|
||||||
|
assert dev1_cluster_color.request.await_count == 1
|
||||||
|
assert dev1_cluster_level.request.call_count == 0
|
||||||
|
assert dev1_cluster_level.request.await_count == 0
|
||||||
|
|
||||||
|
assert dev1_cluster_on_off.request.call_args == call(
|
||||||
|
False,
|
||||||
|
1,
|
||||||
|
dev1_cluster_on_off.commands_by_name["on"].schema,
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert dev1_cluster_color.request.call_args == call(
|
||||||
|
False,
|
||||||
|
10,
|
||||||
|
dev1_cluster_color.commands_by_name["move_to_color_temp"].schema,
|
||||||
|
236, # color temp mireds
|
||||||
|
0, # transition time (ZCL time in 10ths of a second) - no transition when color_provided_while_off
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
light1_state = hass.states.get(device_1_entity_id)
|
||||||
|
assert light1_state.state == STATE_ON
|
||||||
|
assert light1_state.attributes["brightness"] == 25
|
||||||
|
assert light1_state.attributes["color_temp"] == 236
|
||||||
|
assert light1_state.attributes["color_mode"] == ColorMode.COLOR_TEMP
|
||||||
|
|
||||||
|
dev1_cluster_on_off.request.reset_mock()
|
||||||
|
dev1_cluster_color.request.reset_mock()
|
||||||
|
|
||||||
|
# turn light 1 back off to setup group test
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
"turn_off",
|
||||||
|
{
|
||||||
|
"entity_id": device_1_entity_id,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert dev1_cluster_on_off.request.call_count == 1
|
||||||
|
assert dev1_cluster_on_off.request.await_count == 1
|
||||||
|
assert dev1_cluster_color.request.call_count == 0
|
||||||
|
assert dev1_cluster_color.request.await_count == 0
|
||||||
|
assert dev1_cluster_level.request.call_count == 0
|
||||||
|
assert dev1_cluster_level.request.await_count == 0
|
||||||
|
group_state = hass.states.get(group_entity_id)
|
||||||
|
assert group_state.state == STATE_OFF
|
||||||
|
|
||||||
|
dev1_cluster_on_off.request.reset_mock()
|
||||||
|
dev1_cluster_color.request.reset_mock()
|
||||||
|
dev1_cluster_level.request.reset_mock()
|
||||||
|
|
||||||
|
# test sengled light uses default minimum transition time
|
||||||
|
dev2_cluster_on_off.request.reset_mock()
|
||||||
|
dev2_cluster_color.request.reset_mock()
|
||||||
|
dev2_cluster_level.request.reset_mock()
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": device_2_entity_id, "transition": 0, "brightness": 100},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert dev2_cluster_on_off.request.call_count == 0
|
||||||
|
assert dev2_cluster_on_off.request.await_count == 0
|
||||||
|
assert dev2_cluster_color.request.call_count == 0
|
||||||
|
assert dev2_cluster_color.request.await_count == 0
|
||||||
|
assert dev2_cluster_level.request.call_count == 1
|
||||||
|
assert dev2_cluster_level.request.await_count == 1
|
||||||
|
assert dev2_cluster_level.request.call_args == call(
|
||||||
|
False,
|
||||||
|
4,
|
||||||
|
dev2_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
|
||||||
|
100, # brightness (level in ZCL)
|
||||||
|
1, # transition time - sengled light uses default minimum
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
light2_state = hass.states.get(device_2_entity_id)
|
||||||
|
assert light2_state.state == STATE_ON
|
||||||
|
assert light2_state.attributes["brightness"] == 100
|
||||||
|
|
||||||
|
dev2_cluster_level.request.reset_mock()
|
||||||
|
|
||||||
|
# turn the sengled light back off
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
"turn_off",
|
||||||
|
{
|
||||||
|
"entity_id": device_2_entity_id,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert dev2_cluster_on_off.request.call_count == 1
|
||||||
|
assert dev2_cluster_on_off.request.await_count == 1
|
||||||
|
assert dev2_cluster_color.request.call_count == 0
|
||||||
|
assert dev2_cluster_color.request.await_count == 0
|
||||||
|
assert dev2_cluster_level.request.call_count == 0
|
||||||
|
assert dev2_cluster_level.request.await_count == 0
|
||||||
|
light2_state = hass.states.get(device_2_entity_id)
|
||||||
|
assert light2_state.state == STATE_OFF
|
||||||
|
|
||||||
|
dev2_cluster_on_off.request.reset_mock()
|
||||||
|
|
||||||
|
# test non 0 length transition and color temp while turning light on and sengled (color_provided_while_off)
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
"turn_on",
|
||||||
|
{
|
||||||
|
"entity_id": device_2_entity_id,
|
||||||
|
"transition": 1,
|
||||||
|
"brightness": 25,
|
||||||
|
"color_temp": 235,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert dev2_cluster_on_off.request.call_count == 0
|
||||||
|
assert dev2_cluster_on_off.request.await_count == 0
|
||||||
|
assert dev2_cluster_color.request.call_count == 1
|
||||||
|
assert dev2_cluster_color.request.await_count == 1
|
||||||
|
assert dev2_cluster_level.request.call_count == 2
|
||||||
|
assert dev2_cluster_level.request.await_count == 2
|
||||||
|
|
||||||
|
# first it comes on with no transition at 2 brightness
|
||||||
|
assert dev2_cluster_level.request.call_args_list[0] == call(
|
||||||
|
False,
|
||||||
|
4,
|
||||||
|
dev2_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
|
||||||
|
2, # brightness (level in ZCL)
|
||||||
|
1, # transition time (ZCL time in 10ths of a second)
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
assert dev2_cluster_color.request.call_args == call(
|
||||||
|
False,
|
||||||
|
10,
|
||||||
|
dev2_cluster_color.commands_by_name["move_to_color_temp"].schema,
|
||||||
|
235, # color temp mireds
|
||||||
|
1, # transition time (ZCL time in 10ths of a second) - sengled transition == 1 when color_provided_while_off
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
assert dev2_cluster_level.request.call_args_list[1] == call(
|
||||||
|
False,
|
||||||
|
0,
|
||||||
|
dev2_cluster_level.commands_by_name["move_to_level"].schema,
|
||||||
|
25, # brightness (level in ZCL)
|
||||||
|
10.0, # transition time (ZCL time in 10ths of a second)
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
light2_state = hass.states.get(device_2_entity_id)
|
||||||
|
assert light2_state.state == STATE_ON
|
||||||
|
assert light2_state.attributes["brightness"] == 25
|
||||||
|
assert light2_state.attributes["color_temp"] == 235
|
||||||
|
assert light2_state.attributes["color_mode"] == ColorMode.COLOR_TEMP
|
||||||
|
|
||||||
|
dev2_cluster_level.request.reset_mock()
|
||||||
|
dev2_cluster_color.request.reset_mock()
|
||||||
|
|
||||||
|
# turn the sengled light back off
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
"turn_off",
|
||||||
|
{
|
||||||
|
"entity_id": device_2_entity_id,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert dev2_cluster_on_off.request.call_count == 1
|
||||||
|
assert dev2_cluster_on_off.request.await_count == 1
|
||||||
|
assert dev2_cluster_color.request.call_count == 0
|
||||||
|
assert dev2_cluster_color.request.await_count == 0
|
||||||
|
assert dev2_cluster_level.request.call_count == 0
|
||||||
|
assert dev2_cluster_level.request.await_count == 0
|
||||||
|
light2_state = hass.states.get(device_2_entity_id)
|
||||||
|
assert light2_state.state == STATE_OFF
|
||||||
|
|
||||||
|
dev2_cluster_on_off.request.reset_mock()
|
||||||
|
|
||||||
|
# test non 0 length transition and color temp while turning group light on (color_provided_while_off)
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
"turn_on",
|
||||||
|
{
|
||||||
|
"entity_id": group_entity_id,
|
||||||
|
"transition": 1,
|
||||||
|
"brightness": 25,
|
||||||
|
"color_temp": 235,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
group_on_off_channel = zha_group.endpoint[general.OnOff.cluster_id]
|
||||||
|
group_level_channel = zha_group.endpoint[general.LevelControl.cluster_id]
|
||||||
|
group_color_channel = zha_group.endpoint[lighting.Color.cluster_id]
|
||||||
|
assert group_on_off_channel.request.call_count == 0
|
||||||
|
assert group_on_off_channel.request.await_count == 0
|
||||||
|
assert group_color_channel.request.call_count == 1
|
||||||
|
assert group_color_channel.request.await_count == 1
|
||||||
|
assert group_level_channel.request.call_count == 1
|
||||||
|
assert group_level_channel.request.await_count == 1
|
||||||
|
|
||||||
|
# groups are omitted from the 3 call dance for color_provided_while_off
|
||||||
|
assert group_color_channel.request.call_args == call(
|
||||||
|
False,
|
||||||
|
10,
|
||||||
|
dev2_cluster_color.commands_by_name["move_to_color_temp"].schema,
|
||||||
|
235, # color temp mireds
|
||||||
|
10.0, # transition time (ZCL time in 10ths of a second) - sengled transition == 1 when color_provided_while_off
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
assert group_level_channel.request.call_args == call(
|
||||||
|
False,
|
||||||
|
4,
|
||||||
|
dev2_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
|
||||||
|
25, # brightness (level in ZCL)
|
||||||
|
10.0, # transition time (ZCL time in 10ths of a second)
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
group_state = hass.states.get(group_entity_id)
|
||||||
|
assert group_state.state == STATE_ON
|
||||||
|
assert group_state.attributes["brightness"] == 25
|
||||||
|
assert group_state.attributes["color_temp"] == 235
|
||||||
|
assert group_state.attributes["color_mode"] == ColorMode.COLOR_TEMP
|
||||||
|
|
||||||
|
group_on_off_channel.request.reset_mock()
|
||||||
|
group_color_channel.request.reset_mock()
|
||||||
|
group_level_channel.request.reset_mock()
|
||||||
|
|
||||||
|
# turn the sengled light back on
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
"turn_on",
|
||||||
|
{
|
||||||
|
"entity_id": device_2_entity_id,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert dev2_cluster_on_off.request.call_count == 1
|
||||||
|
assert dev2_cluster_on_off.request.await_count == 1
|
||||||
|
assert dev2_cluster_color.request.call_count == 0
|
||||||
|
assert dev2_cluster_color.request.await_count == 0
|
||||||
|
assert dev2_cluster_level.request.call_count == 0
|
||||||
|
assert dev2_cluster_level.request.await_count == 0
|
||||||
|
light2_state = hass.states.get(device_2_entity_id)
|
||||||
|
assert light2_state.state == STATE_ON
|
||||||
|
|
||||||
|
dev2_cluster_on_off.request.reset_mock()
|
||||||
|
|
||||||
|
# turn the light off with a transition
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
"turn_off",
|
||||||
|
{"entity_id": device_2_entity_id, "transition": 2},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert dev2_cluster_on_off.request.call_count == 0
|
||||||
|
assert dev2_cluster_on_off.request.await_count == 0
|
||||||
|
assert dev2_cluster_color.request.call_count == 0
|
||||||
|
assert dev2_cluster_color.request.await_count == 0
|
||||||
|
assert dev2_cluster_level.request.call_count == 1
|
||||||
|
assert dev2_cluster_level.request.await_count == 1
|
||||||
|
assert dev2_cluster_level.request.call_args == call(
|
||||||
|
False,
|
||||||
|
4,
|
||||||
|
dev2_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
|
||||||
|
0, # brightness (level in ZCL)
|
||||||
|
20, # transition time
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
light2_state = hass.states.get(device_2_entity_id)
|
||||||
|
assert light2_state.state == STATE_OFF
|
||||||
|
|
||||||
|
dev2_cluster_level.request.reset_mock()
|
||||||
|
|
||||||
|
# turn the light back on with no args should use a transition and last known brightness
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": device_2_entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert dev2_cluster_on_off.request.call_count == 0
|
||||||
|
assert dev2_cluster_on_off.request.await_count == 0
|
||||||
|
assert dev2_cluster_color.request.call_count == 0
|
||||||
|
assert dev2_cluster_color.request.await_count == 0
|
||||||
|
assert dev2_cluster_level.request.call_count == 1
|
||||||
|
assert dev2_cluster_level.request.await_count == 1
|
||||||
|
assert dev2_cluster_level.request.call_args == call(
|
||||||
|
False,
|
||||||
|
4,
|
||||||
|
dev2_cluster_level.commands_by_name["move_to_level_with_on_off"].schema,
|
||||||
|
25, # brightness (level in ZCL) - this is the last brightness we set a few tests above
|
||||||
|
1, # transition time - sengled light uses default minimum
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
light2_state = hass.states.get(device_2_entity_id)
|
||||||
|
assert light2_state.state == STATE_ON
|
||||||
|
|
||||||
|
dev2_cluster_level.request.reset_mock()
|
||||||
|
|
||||||
|
# test eWeLink color temp while turning light on from off (color_provided_while_off)
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
"turn_on",
|
||||||
|
{
|
||||||
|
"entity_id": eWeLink_light_entity_id,
|
||||||
|
"color_temp": 235,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert eWeLink_cluster_on_off.request.call_count == 1
|
||||||
|
assert eWeLink_cluster_on_off.request.await_count == 1
|
||||||
|
assert eWeLink_cluster_color.request.call_count == 1
|
||||||
|
assert eWeLink_cluster_color.request.await_count == 1
|
||||||
|
assert eWeLink_cluster_level.request.call_count == 0
|
||||||
|
assert eWeLink_cluster_level.request.await_count == 0
|
||||||
|
|
||||||
|
# first it comes on
|
||||||
|
assert eWeLink_cluster_on_off.request.call_args_list[0] == call(
|
||||||
|
False,
|
||||||
|
1,
|
||||||
|
eWeLink_cluster_on_off.commands_by_name["on"].schema,
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
assert dev1_cluster_color.request.call_args == call(
|
||||||
|
False,
|
||||||
|
10,
|
||||||
|
dev1_cluster_color.commands_by_name["move_to_color_temp"].schema,
|
||||||
|
235, # color temp mireds
|
||||||
|
0, # transition time (ZCL time in 10ths of a second)
|
||||||
|
expect_reply=True,
|
||||||
|
manufacturer=None,
|
||||||
|
tries=1,
|
||||||
|
tsn=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
eWeLink_state = hass.states.get(eWeLink_light_entity_id)
|
||||||
|
assert eWeLink_state.state == STATE_ON
|
||||||
|
assert eWeLink_state.attributes["color_temp"] == 235
|
||||||
|
assert eWeLink_state.attributes["color_mode"] == ColorMode.COLOR_TEMP
|
||||||
|
|
||||||
|
|
||||||
async def async_test_on_off_from_light(hass, cluster, entity_id):
|
async def async_test_on_off_from_light(hass, cluster, entity_id):
|
||||||
"""Test on off functionality from the light."""
|
"""Test on off functionality from the light."""
|
||||||
# turn on at light
|
# turn on at light
|
||||||
@ -463,7 +1260,7 @@ async def async_test_level_on_off_from_hass(
|
|||||||
4,
|
4,
|
||||||
level_cluster.commands_by_name["move_to_level_with_on_off"].schema,
|
level_cluster.commands_by_name["move_to_level_with_on_off"].schema,
|
||||||
10,
|
10,
|
||||||
1,
|
0,
|
||||||
expect_reply=True,
|
expect_reply=True,
|
||||||
manufacturer=None,
|
manufacturer=None,
|
||||||
tries=1,
|
tries=1,
|
||||||
@ -601,7 +1398,10 @@ async def test_zha_group_light_entity(
|
|||||||
# test that the lights were created and are off
|
# test that the lights were created and are off
|
||||||
group_state = hass.states.get(group_entity_id)
|
group_state = hass.states.get(group_entity_id)
|
||||||
assert group_state.state == STATE_OFF
|
assert group_state.state == STATE_OFF
|
||||||
assert group_state.attributes["supported_color_modes"] == [ColorMode.HS]
|
assert group_state.attributes["supported_color_modes"] == [
|
||||||
|
ColorMode.COLOR_TEMP,
|
||||||
|
ColorMode.HS,
|
||||||
|
]
|
||||||
# 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
|
||||||
|
|
||||||
@ -629,7 +1429,10 @@ async def test_zha_group_light_entity(
|
|||||||
# Check state
|
# Check state
|
||||||
group_state = hass.states.get(group_entity_id)
|
group_state = hass.states.get(group_entity_id)
|
||||||
assert group_state.state == STATE_ON
|
assert group_state.state == STATE_ON
|
||||||
assert group_state.attributes["supported_color_modes"] == [ColorMode.HS]
|
assert group_state.attributes["supported_color_modes"] == [
|
||||||
|
ColorMode.COLOR_TEMP,
|
||||||
|
ColorMode.HS,
|
||||||
|
]
|
||||||
assert group_state.attributes["color_mode"] == ColorMode.HS
|
assert group_state.attributes["color_mode"] == ColorMode.HS
|
||||||
|
|
||||||
# test long flashing the lights from the HA
|
# test long flashing the lights from the HA
|
||||||
|
Loading…
x
Reference in New Issue
Block a user