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

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

* WIP unit tests
This commit is contained in:
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 and self._color_channel.enhanced_current_hue is not None
): ):
curr_hue = self._color_channel.enhanced_current_hue * 65535 / 360 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_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._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: if self._color_channel.color_loop_supported:
self._attr_supported_features |= light.LightEntityFeature.EFFECT self._attr_supported_features |= light.LightEntityFeature.EFFECT

View File

@ -2,12 +2,14 @@
import asyncio import asyncio
from datetime import timedelta from datetime import timedelta
import math 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
import zigpy.zcl.foundation as zcl_f import zigpy.zcl.foundation as zcl_f
import homeassistant.components.zha.core.const as zha_const 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 from homeassistant.helpers import entity_registry
import homeassistant.util.dt as dt_util 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) next_update = dt_util.utcnow() + timedelta(seconds=11)
async_fire_time_changed(hass, next_update) async_fire_time_changed(hass, next_update)
await hass.async_block_till_done() 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, FLASH_SHORT,
ColorMode, 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.core.group import GroupMember
from homeassistant.components.zha.light import FLASH_EFFECTS from homeassistant.components.zha.light import FLASH_EFFECTS
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, Platform from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, Platform
@ -26,6 +30,7 @@ from .common import (
async_test_rejoin, async_test_rejoin,
find_entity_id, find_entity_id,
get_zha_gateway, get_zha_gateway,
patch_zha_config,
send_attributes_report, send_attributes_report,
) )
from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE 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: if cluster_identify:
await async_test_flash_from_hass(hass, cluster_identify, entity_id, FLASH_SHORT) 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: if cluster_level:
await async_test_level_on_off_from_hass( await async_test_level_on_off_from_hass(
hass, cluster_on_off, cluster_level, entity_id hass, cluster_on_off, cluster_level, entity_id
@ -355,16 +364,82 @@ async def test_light(
# test rejoin # test rejoin
await async_test_off_from_hass(hass, cluster_on_off, entity_id) await async_test_off_from_hass(hass, cluster_on_off, entity_id)
clusters = [cluster_on_off] clusters = [c for c in (cluster_on_off, cluster_level, cluster_color) if c]
if cluster_level:
clusters.append(cluster_level)
if cluster_color:
clusters.append(cluster_color)
await async_test_rejoin(hass, zigpy_device, clusters, reporting) await async_test_rejoin(hass, zigpy_device, clusters, reporting)
# test long flashing the lights from the HA
if cluster_identify: @pytest.mark.parametrize(
await async_test_flash_from_hass(hass, cluster_identify, entity_id, FLASH_LONG) "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( @patch(