Fix ZHA lighting initial hue/saturation attribute read (#77727)

* Handle the case of `current_hue` being `None`

* WIP unit tests
pull/77964/head
puddly 2022-09-07 11:10:24 -04:00 committed by GitHub
parent 2cfdc15c38
commit 4076f8b94e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 114 additions and 18 deletions

View File

@ -612,16 +612,18 @@ class Light(BaseLight, ZhaEntity):
and self._color_channel.enhanced_current_hue is not None
):
curr_hue = self._color_channel.enhanced_current_hue * 65535 / 360
else:
elif self._color_channel.current_hue is not None:
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:
self._attr_hs_color = (0, 0)
curr_hue = 0
if (curr_saturation := self._color_channel.current_saturation) is None:
curr_saturation = 0
self._attr_hs_color = (
int(curr_hue),
int(curr_saturation * 2.54),
)
if self._color_channel.color_loop_supported:
self._attr_supported_features |= light.LightEntityFeature.EFFECT

View File

@ -2,12 +2,14 @@
import asyncio
from datetime import timedelta
import math
from unittest.mock import AsyncMock, Mock
from typing import Any
from unittest.mock import AsyncMock, Mock, patch
import zigpy.zcl
import zigpy.zcl.foundation as zcl_f
import homeassistant.components.zha.core.const as zha_const
from homeassistant.components.zha.core.helpers import async_get_zha_config_value
from homeassistant.helpers import entity_registry
import homeassistant.util.dt as dt_util
@ -243,3 +245,20 @@ async def async_shift_time(hass):
next_update = dt_util.utcnow() + timedelta(seconds=11)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
def patch_zha_config(component: str, overrides: dict[tuple[str, str], Any]):
"""Patch the ZHA custom configuration defaults."""
def new_get_config(config_entry, section, config_key, default):
if (section, config_key) in overrides:
return overrides[section, config_key]
else:
return async_get_zha_config_value(
config_entry, section, config_key, default
)
return patch(
f"homeassistant.components.zha.{component}.async_get_zha_config_value",
side_effect=new_get_config,
)

View File

@ -14,6 +14,10 @@ from homeassistant.components.light import (
FLASH_SHORT,
ColorMode,
)
from homeassistant.components.zha.core.const import (
CONF_ALWAYS_PREFER_XY_COLOR_MODE,
ZHA_OPTIONS,
)
from homeassistant.components.zha.core.group import GroupMember
from homeassistant.components.zha.light import FLASH_EFFECTS
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, Platform
@ -26,6 +30,7 @@ from .common import (
async_test_rejoin,
find_entity_id,
get_zha_gateway,
patch_zha_config,
send_attributes_report,
)
from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE
@ -340,7 +345,11 @@ async def test_light(
if cluster_identify:
await async_test_flash_from_hass(hass, cluster_identify, entity_id, FLASH_SHORT)
# test turning the lights on and off from the HA
# test long flashing the lights from the HA
if cluster_identify:
await async_test_flash_from_hass(hass, cluster_identify, entity_id, FLASH_LONG)
# test dimming the lights on and off from the HA
if cluster_level:
await async_test_level_on_off_from_hass(
hass, cluster_on_off, cluster_level, entity_id
@ -355,16 +364,82 @@ async def test_light(
# test rejoin
await async_test_off_from_hass(hass, cluster_on_off, entity_id)
clusters = [cluster_on_off]
if cluster_level:
clusters.append(cluster_level)
if cluster_color:
clusters.append(cluster_color)
clusters = [c for c in (cluster_on_off, cluster_level, cluster_color) if c]
await async_test_rejoin(hass, zigpy_device, clusters, reporting)
# test long flashing the lights from the HA
if cluster_identify:
await async_test_flash_from_hass(hass, cluster_identify, entity_id, FLASH_LONG)
@pytest.mark.parametrize(
"plugged_attr_reads, config_override, expected_state",
[
# HS light without cached hue or saturation
(
{
"color_capabilities": (
lighting.Color.ColorCapabilities.Hue_and_saturation
),
},
{(ZHA_OPTIONS, CONF_ALWAYS_PREFER_XY_COLOR_MODE): False},
{},
),
# HS light with cached hue
(
{
"color_capabilities": (
lighting.Color.ColorCapabilities.Hue_and_saturation
),
"current_hue": 100,
},
{(ZHA_OPTIONS, CONF_ALWAYS_PREFER_XY_COLOR_MODE): False},
{},
),
# HS light with cached saturation
(
{
"color_capabilities": (
lighting.Color.ColorCapabilities.Hue_and_saturation
),
"current_saturation": 100,
},
{(ZHA_OPTIONS, CONF_ALWAYS_PREFER_XY_COLOR_MODE): False},
{},
),
# HS light with both
(
{
"color_capabilities": (
lighting.Color.ColorCapabilities.Hue_and_saturation
),
"current_hue": 100,
"current_saturation": 100,
},
{(ZHA_OPTIONS, CONF_ALWAYS_PREFER_XY_COLOR_MODE): False},
{},
),
],
)
async def test_light_initialization(
hass,
zigpy_device_mock,
zha_device_joined_restored,
plugged_attr_reads,
config_override,
expected_state,
):
"""Test zha light initialization with cached attributes and color modes."""
# create zigpy devices
zigpy_device = zigpy_device_mock(LIGHT_COLOR)
# mock attribute reads
zigpy_device.endpoints[1].light_color.PLUGGED_ATTR_READS = plugged_attr_reads
with patch_zha_config("light", config_override):
zha_device = await zha_device_joined_restored(zigpy_device)
entity_id = await find_entity_id(Platform.LIGHT, zha_device, hass)
assert entity_id is not None
# TODO ensure hue and saturation are properly set on startup
@patch(