From 679562773442d40cb4cc3d41b787a5a87308aed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20L=C3=B6hr?= Date: Mon, 24 Oct 2022 00:46:47 +0200 Subject: [PATCH] Add ZHA StartUpColorTemperature configuration entity (#80853) * Add ZHA start up color temperature entity * Use device reported min max values * Add color number test * Fix code style --- .../components/zha/core/channels/lighting.py | 1 + homeassistant/components/zha/number.py | 26 ++++ tests/components/zha/test_number.py | 114 +++++++++++++++++- 3 files changed, 140 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zha/core/channels/lighting.py b/homeassistant/components/zha/core/channels/lighting.py index 1754b9aff68..e70eea11a87 100644 --- a/homeassistant/components/zha/core/channels/lighting.py +++ b/homeassistant/components/zha/core/channels/lighting.py @@ -44,6 +44,7 @@ class ColorChannel(ZigbeeChannel): "color_temp_physical_max": True, "color_capabilities": True, "color_loop_active": False, + "start_up_color_temperature": True, } @cached_property diff --git a/homeassistant/components/zha/number.py b/homeassistant/components/zha/number.py index 9203986057d..1776cabf125 100644 --- a/homeassistant/components/zha/number.py +++ b/homeassistant/components/zha/number.py @@ -19,6 +19,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .core import discovery from .core.const import ( CHANNEL_ANALOG_OUTPUT, + CHANNEL_COLOR, CHANNEL_INOVELLI, CHANNEL_LEVEL, DATA_ZHA, @@ -528,6 +529,31 @@ class StartUpCurrentLevelConfigurationEntity( _attr_name = "Start-up current level" +@CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_COLOR) +class StartUpColorTemperatureConfigurationEntity( + ZHANumberConfigurationEntity, id_suffix="start_up_color_temperature" +): + """Representation of a ZHA startup color temperature configuration entity.""" + + _attr_native_min_value: float = 153 + _attr_native_max_value: float = 500 + _zcl_attribute: str = "start_up_color_temperature" + _attr_name = "Start-up color temperature" + + def __init__( + self, + unique_id: str, + zha_device: ZHADevice, + channels: list[ZigbeeChannel], + **kwargs: Any, + ) -> None: + """Init this ZHA startup color temperature entity.""" + super().__init__(unique_id, zha_device, channels, **kwargs) + if self._channel: + self._attr_native_min_value: float = self._channel.min_mireds + self._attr_native_max_value: float = self._channel.max_mireds + + @CONFIG_DIAGNOSTIC_MATCH( channel_names="tuya_manufacturer", manufacturers={ diff --git a/tests/components/zha/test_number.py b/tests/components/zha/test_number.py index 6af98b35e09..219c77f76d7 100644 --- a/tests/components/zha/test_number.py +++ b/tests/components/zha/test_number.py @@ -5,6 +5,7 @@ import pytest from zigpy.exceptions import ZigbeeException from zigpy.profiles import zha import zigpy.zcl.clusters.general as general +import zigpy.zcl.clusters.lighting as lighting import zigpy.zcl.foundation as zcl_f from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN @@ -64,12 +65,13 @@ async def light(zigpy_device_mock): { 1: { SIG_EP_PROFILE: zha.PROFILE_ID, - SIG_EP_TYPE: zha.DeviceType.ON_OFF_LIGHT, + SIG_EP_TYPE: zha.DeviceType.COLOR_DIMMABLE_LIGHT, SIG_EP_INPUT: [ general.Basic.cluster_id, general.Identify.cluster_id, general.OnOff.cluster_id, general.LevelControl.cluster_id, + lighting.Color.cluster_id, ], SIG_EP_OUTPUT: [general.Ota.cluster_id], } @@ -322,3 +324,113 @@ async def test_level_control_number( attr: new_value, } assert hass.states.get(entity_id).state == str(initial_value) + + +@pytest.mark.parametrize( + "attr, initial_value, new_value", + (("start_up_color_temperature", 500, 350),), +) +async def test_color_number( + hass, light, zha_device_joined, attr, initial_value, new_value +): + """Test zha color number entities - new join.""" + + entity_registry = er.async_get(hass) + color_cluster = light.endpoints[1].light_color + color_cluster.PLUGGED_ATTR_READS = { + attr: initial_value, + } + zha_device = await zha_device_joined(light) + + entity_id = await find_entity_id( + Platform.NUMBER, + zha_device, + hass, + qualifier=attr, + ) + assert entity_id is not None + + assert color_cluster.read_attributes.call_count == 3 + assert ( + call( + [ + "color_temp_physical_min", + "color_temp_physical_max", + "color_capabilities", + "start_up_color_temperature", + ], + allow_cache=True, + only_cache=False, + manufacturer=None, + ) + in color_cluster.read_attributes.call_args_list + ) + + state = hass.states.get(entity_id) + assert state + assert state.state == str(initial_value) + + entity_entry = entity_registry.async_get(entity_id) + assert entity_entry + assert entity_entry.entity_category == EntityCategory.CONFIG + + # Test number set_value + await hass.services.async_call( + "number", + "set_value", + { + "entity_id": entity_id, + "value": new_value, + }, + blocking=True, + ) + + assert color_cluster.write_attributes.call_count == 1 + assert color_cluster.write_attributes.call_args[0][0] == { + attr: new_value, + } + + state = hass.states.get(entity_id) + assert state + assert state.state == str(new_value) + + color_cluster.read_attributes.reset_mock() + await async_setup_component(hass, "homeassistant", {}) + await hass.async_block_till_done() + + await hass.services.async_call( + "homeassistant", "update_entity", {"entity_id": entity_id}, blocking=True + ) + # the mocking doesn't update the attr cache so this flips back to initial value + assert hass.states.get(entity_id).state == str(initial_value) + assert color_cluster.read_attributes.call_count == 1 + assert ( + call( + [ + attr, + ], + allow_cache=False, + only_cache=False, + manufacturer=None, + ) + in color_cluster.read_attributes.call_args_list + ) + + color_cluster.write_attributes.reset_mock() + color_cluster.write_attributes.side_effect = ZigbeeException + + await hass.services.async_call( + "number", + "set_value", + { + "entity_id": entity_id, + "value": new_value, + }, + blocking=True, + ) + + assert color_cluster.write_attributes.call_count == 1 + assert color_cluster.write_attributes.call_args[0][0] == { + attr: new_value, + } + assert hass.states.get(entity_id).state == str(initial_value)