From 8fb7789ea8ddb6ed2a287aed5010374c0452f6c9 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Tue, 27 Aug 2024 21:36:46 -0400 Subject: [PATCH 01/11] Always prefer XY color mode in ZHA Remove a few more HS remnants --- homeassistant/components/zha/const.py | 1 - homeassistant/components/zha/helpers.py | 3 --- homeassistant/components/zha/light.py | 21 +++++++++++---------- homeassistant/components/zha/strings.json | 1 - tests/components/zha/data.py | 14 -------------- 5 files changed, 11 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/zha/const.py b/homeassistant/components/zha/const.py index 3986a99cf3f34c..0e62837782e33a 100644 --- a/homeassistant/components/zha/const.py +++ b/homeassistant/components/zha/const.py @@ -43,7 +43,6 @@ CONF_DEFAULT_LIGHT_TRANSITION = "default_light_transition" CONF_ENABLE_ENHANCED_LIGHT_TRANSITION = "enhanced_light_transition" CONF_ENABLE_LIGHT_TRANSITIONING_FLAG = "light_transitioning_flag" -CONF_ALWAYS_PREFER_XY_COLOR_MODE = "always_prefer_xy_color_mode" CONF_GROUP_MEMBERS_ASSUME_STATE = "group_members_assume_state" CONF_ENABLE_IDENTIFY_ON_JOIN = "enable_identify_on_join" diff --git a/homeassistant/components/zha/helpers.py b/homeassistant/components/zha/helpers.py index a5446af7e76e7d..61a0e380729638 100644 --- a/homeassistant/components/zha/helpers.py +++ b/homeassistant/components/zha/helpers.py @@ -140,7 +140,6 @@ CONF_ALARM_ARM_REQUIRES_CODE, CONF_ALARM_FAILED_TRIES, CONF_ALARM_MASTER_CODE, - CONF_ALWAYS_PREFER_XY_COLOR_MODE, CONF_BAUDRATE, CONF_CONSIDER_UNAVAILABLE_BATTERY, CONF_CONSIDER_UNAVAILABLE_MAINS, @@ -1152,7 +1151,6 @@ def _clean_serial_port_path(path: str) -> str: ), vol.Required(CONF_ENABLE_ENHANCED_LIGHT_TRANSITION, default=False): cv.boolean, vol.Required(CONF_ENABLE_LIGHT_TRANSITIONING_FLAG, default=True): cv.boolean, - vol.Required(CONF_ALWAYS_PREFER_XY_COLOR_MODE, default=True): cv.boolean, vol.Required(CONF_GROUP_MEMBERS_ASSUME_STATE, default=True): cv.boolean, vol.Required(CONF_ENABLE_IDENTIFY_ON_JOIN, default=True): cv.boolean, vol.Optional( @@ -1227,7 +1225,6 @@ def create_zha_config(hass: HomeAssistant, ha_zha_data: HAZHAData) -> ZHAData: enable_light_transitioning_flag=zha_options.get( CONF_ENABLE_LIGHT_TRANSITIONING_FLAG ), - always_prefer_xy_color_mode=zha_options.get(CONF_ALWAYS_PREFER_XY_COLOR_MODE), group_members_assume_state=zha_options.get(CONF_GROUP_MEMBERS_ASSUME_STATE), ) device_options: DeviceOptions = DeviceOptions( diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index 4a36030a0dde4f..def63256cde9cf 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -45,7 +45,6 @@ ZhaColorMode.ONOFF: ColorMode.ONOFF, ZhaColorMode.BRIGHTNESS: ColorMode.BRIGHTNESS, ZhaColorMode.COLOR_TEMP: ColorMode.COLOR_TEMP, - ZhaColorMode.HS: ColorMode.HS, ZhaColorMode.XY: ColorMode.XY, ZhaColorMode.RGB: ColorMode.RGB, ZhaColorMode.RGBW: ColorMode.RGBW, @@ -143,11 +142,6 @@ def max_mireds(self) -> int: """Return the warmest color_temp that this light supports.""" return self.entity_data.entity.max_mireds - @property - def hs_color(self) -> tuple[float, float] | None: - """Return the hs color value [int, int].""" - return self.entity_data.entity.hs_color - @property def xy_color(self) -> tuple[float, float] | None: """Return the xy color value [float, float].""" @@ -185,7 +179,6 @@ async def async_turn_on(self, **kwargs: Any) -> None: flash=kwargs.get(ATTR_FLASH), color_temp=kwargs.get(ATTR_COLOR_TEMP), xy_color=kwargs.get(ATTR_XY_COLOR), - hs_color=kwargs.get(ATTR_HS_COLOR), ) self.async_write_ha_state() @@ -200,6 +193,16 @@ async def async_turn_off(self, **kwargs: Any) -> None: @callback def restore_external_state_attributes(self, state: State) -> None: """Restore entity state.""" + + color_mode = None + + if state.attributes.get(ATTR_COLOR_MODE) is not None: + color_mode = ColorMode(state.attributes[ATTR_COLOR_MODE]) + + # Remove in 2025.1: ZHA no longer uses HS color mode, only XY + if color_mode is ColorMode.HS: + color_mode = ColorMode.XY + self.entity_data.entity.restore_external_state_attributes( state=(state.state == STATE_ON), off_with_transition=state.attributes.get(OFF_WITH_TRANSITION), @@ -209,9 +212,7 @@ def restore_external_state_attributes(self, state: State) -> None: xy_color=state.attributes.get(ATTR_XY_COLOR), hs_color=state.attributes.get(ATTR_HS_COLOR), color_mode=( - HA_TO_ZHA_COLOR_MODE[ColorMode(state.attributes[ATTR_COLOR_MODE])] - if state.attributes.get(ATTR_COLOR_MODE) is not None - else None + HA_TO_ZHA_COLOR_MODE[color_mode] if color_mode is not None else None ), effect=state.attributes.get(ATTR_EFFECT), ) diff --git a/homeassistant/components/zha/strings.json b/homeassistant/components/zha/strings.json index 5d81556564ae80..8b6754a24221ef 100644 --- a/homeassistant/components/zha/strings.json +++ b/homeassistant/components/zha/strings.json @@ -178,7 +178,6 @@ "title": "Global Options", "enhanced_light_transition": "Enable enhanced light color/temperature transition from an off-state", "light_transitioning_flag": "Enable enhanced brightness slider during light transition", - "always_prefer_xy_color_mode": "Always prefer XY color mode", "group_members_assume_state": "Group members assume state of group", "enable_identify_on_join": "Enable identify effect when devices join the network", "default_light_transition": "Default light transition time (seconds)", diff --git a/tests/components/zha/data.py b/tests/components/zha/data.py index eb135c7e8fe0f9..b50c7a18a06309 100644 --- a/tests/components/zha/data.py +++ b/tests/components/zha/data.py @@ -23,12 +23,6 @@ "required": True, "default": True, }, - { - "type": "boolean", - "name": "always_prefer_xy_color_mode", - "required": True, - "default": True, - }, { "type": "boolean", "name": "group_members_assume_state", @@ -62,7 +56,6 @@ "enhanced_light_transition": True, "default_light_transition": 0, "light_transitioning_flag": True, - "always_prefer_xy_color_mode": True, "group_members_assume_state": False, "enable_identify_on_join": True, "consider_unavailable_mains": 7200, @@ -94,12 +87,6 @@ "required": True, "default": True, }, - { - "type": "boolean", - "name": "always_prefer_xy_color_mode", - "required": True, - "default": True, - }, { "type": "boolean", "name": "group_members_assume_state", @@ -154,7 +141,6 @@ "enhanced_light_transition": True, "default_light_transition": 0, "light_transitioning_flag": True, - "always_prefer_xy_color_mode": True, "group_members_assume_state": False, "enable_identify_on_join": True, "consider_unavailable_mains": 7200, From 363247e738365ed5a559316ca1dca357f511d684 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Tue, 27 Aug 2024 21:17:14 -0400 Subject: [PATCH 02/11] Use new ZHA OTA format --- homeassistant/components/zha/update.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zha/update.py b/homeassistant/components/zha/update.py index e12d048b1908fe..3a857f9d89b901 100644 --- a/homeassistant/components/zha/update.py +++ b/homeassistant/components/zha/update.py @@ -95,6 +95,7 @@ class ZHAFirmwareUpdateEntity( UpdateEntityFeature.INSTALL | UpdateEntityFeature.PROGRESS | UpdateEntityFeature.SPECIFIC_VERSION + | UpdateEntityFeature.RELEASE_NOTES ) def __init__(self, entity_data: EntityData, **kwargs: Any) -> None: @@ -143,6 +144,14 @@ def release_summary(self) -> str | None: """ return self.entity_data.entity.release_summary + async def async_release_notes(self) -> str | None: + """Return full release notes. + + This is suitable for a long changelog that does not fit in the release_summary + property. The returned string can contain markdown. + """ + return self.entity_data.entity.release_notes + @property def release_url(self) -> str | None: """URL to the full release notes of the latest version available.""" @@ -155,7 +164,7 @@ async def async_install( ) -> None: """Install an update.""" try: - await self.entity_data.entity.async_install(version=version, backup=backup) + await self.entity_data.entity.async_install(version=version) except ZHAException as exc: raise HomeAssistantError(exc) from exc finally: From ab7af858d77b6a4474bf0968bc2ad8814f5bd63e Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Wed, 28 Aug 2024 11:24:14 -0400 Subject: [PATCH 03/11] Bump ZHA to 0.0.32 --- homeassistant/components/zha/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index a5e57fcb1ec020..df60829a1e2e99 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -21,7 +21,7 @@ "zha", "universal_silabs_flasher" ], - "requirements": ["universal-silabs-flasher==0.0.22", "zha==0.0.31"], + "requirements": ["universal-silabs-flasher==0.0.22", "zha==0.0.32"], "usb": [ { "vid": "10C4", diff --git a/requirements_all.txt b/requirements_all.txt index f29766ad260284..40632d7f9e596e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -3001,7 +3001,7 @@ zeroconf==0.133.0 zeversolar==0.3.1 # homeassistant.components.zha -zha==0.0.31 +zha==0.0.32 # homeassistant.components.zhong_hong zhong-hong-hvac==1.0.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 68460b9ec9875a..f4ec648a9cc0ea 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2378,7 +2378,7 @@ zeroconf==0.133.0 zeversolar==0.3.1 # homeassistant.components.zha -zha==0.0.31 +zha==0.0.32 # homeassistant.components.zwave_js zwave-js-server-python==0.57.0 From 15cf11161994b9efbc64ec990402c5833d46b333 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Wed, 28 Aug 2024 11:45:20 -0400 Subject: [PATCH 04/11] Fix existing OTA unit tests --- tests/components/zha/test_update.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/components/zha/test_update.py b/tests/components/zha/test_update.py index 6a1a19b407f7e7..d8a85dfc0b3af1 100644 --- a/tests/components/zha/test_update.py +++ b/tests/components/zha/test_update.py @@ -4,7 +4,7 @@ import pytest from zigpy.exceptions import DeliveryError -from zigpy.ota import OtaImageWithMetadata +from zigpy.ota import OtaImagesResult, OtaImageWithMetadata import zigpy.ota.image as firmware from zigpy.ota.providers import BaseOtaImageMetadata from zigpy.profiles import zha @@ -119,8 +119,11 @@ async def setup_test_data( ), ) - cluster.endpoint.device.application.ota.get_ota_image = AsyncMock( - return_value=None if file_not_found else fw_image + cluster.endpoint.device.application.ota.get_ota_images = AsyncMock( + return_value=OtaImagesResult( + upgrades=() if file_not_found else (fw_image,), + downgrades=(), + ) ) zha_device_proxy: ZHADeviceProxy = gateway_proxy.get_device_proxy(zigpy_device.ieee) zha_device_proxy.device.async_update_sw_build_id(installed_fw_version) From 4d3e42fd09b5636651d98a426e56377a5cbb03bb Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Wed, 28 Aug 2024 11:48:18 -0400 Subject: [PATCH 05/11] Fix schema conversion test to account for new command parameters --- tests/components/zha/test_helpers.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/components/zha/test_helpers.py b/tests/components/zha/test_helpers.py index 13c03c17cf7a1d..d33926854378d8 100644 --- a/tests/components/zha/test_helpers.py +++ b/tests/components/zha/test_helpers.py @@ -60,16 +60,14 @@ async def test_zcl_schema_conversions(hass: HomeAssistant) -> None: "required": True, }, { - "type": "integer", - "valueMin": 0, - "valueMax": 255, + "type": "multi_select", + "options": ["Execute if off present"], "name": "options_mask", "optional": True, }, { - "type": "integer", - "valueMin": 0, - "valueMax": 255, + "type": "multi_select", + "options": ["Execute if off"], "name": "options_override", "optional": True, }, From b3b4df6fcc42fde8ebaf4e84200fcca0ae06c611 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Wed, 28 Aug 2024 11:49:19 -0400 Subject: [PATCH 06/11] Update snapshot with new `zcl_type` kwarg --- .../zha/snapshots/test_diagnostics.ambr | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/components/zha/snapshots/test_diagnostics.ambr b/tests/components/zha/snapshots/test_diagnostics.ambr index 67655aebc8c6cf..e0da54e2492ded 100644 --- a/tests/components/zha/snapshots/test_diagnostics.ambr +++ b/tests/components/zha/snapshots/test_diagnostics.ambr @@ -162,19 +162,19 @@ '0x0500': dict({ 'attributes': dict({ '0x0000': dict({ - 'attribute': "ZCLAttributeDef(id=0x0000, name='zone_state', type=, access=, mandatory=True, is_manufacturer_specific=False)", + 'attribute': "ZCLAttributeDef(id=0x0000, name='zone_state', type=, zcl_type=, access=, mandatory=True, is_manufacturer_specific=False)", 'value': None, }), '0x0001': dict({ - 'attribute': "ZCLAttributeDef(id=0x0001, name='zone_type', type=, access=, mandatory=True, is_manufacturer_specific=False)", + 'attribute': "ZCLAttributeDef(id=0x0001, name='zone_type', type=, zcl_type=, access=, mandatory=True, is_manufacturer_specific=False)", 'value': None, }), '0x0002': dict({ - 'attribute': "ZCLAttributeDef(id=0x0002, name='zone_status', type=, access=, mandatory=True, is_manufacturer_specific=False)", + 'attribute': "ZCLAttributeDef(id=0x0002, name='zone_status', type=, zcl_type=, access=, mandatory=True, is_manufacturer_specific=False)", 'value': None, }), '0x0010': dict({ - 'attribute': "ZCLAttributeDef(id=0x0010, name='cie_addr', type=, access=, mandatory=True, is_manufacturer_specific=False)", + 'attribute': "ZCLAttributeDef(id=0x0010, name='cie_addr', type=, zcl_type=, access=, mandatory=True, is_manufacturer_specific=False)", 'value': list([ 50, 79, @@ -187,15 +187,15 @@ ]), }), '0x0011': dict({ - 'attribute': "ZCLAttributeDef(id=0x0011, name='zone_id', type=, access=, mandatory=True, is_manufacturer_specific=False)", + 'attribute': "ZCLAttributeDef(id=0x0011, name='zone_id', type=, zcl_type=, access=, mandatory=True, is_manufacturer_specific=False)", 'value': None, }), '0x0012': dict({ - 'attribute': "ZCLAttributeDef(id=0x0012, name='num_zone_sensitivity_levels_supported', type=, access=, mandatory=False, is_manufacturer_specific=False)", + 'attribute': "ZCLAttributeDef(id=0x0012, name='num_zone_sensitivity_levels_supported', type=, zcl_type=, access=, mandatory=False, is_manufacturer_specific=False)", 'value': None, }), '0x0013': dict({ - 'attribute': "ZCLAttributeDef(id=0x0013, name='current_zone_sensitivity_level', type=, access=, mandatory=False, is_manufacturer_specific=False)", + 'attribute': "ZCLAttributeDef(id=0x0013, name='current_zone_sensitivity_level', type=, zcl_type=, access=, mandatory=False, is_manufacturer_specific=False)", 'value': None, }), }), @@ -208,11 +208,11 @@ '0x0501': dict({ 'attributes': dict({ '0xfffd': dict({ - 'attribute': "ZCLAttributeDef(id=0xFFFD, name='cluster_revision', type=, access=, mandatory=True, is_manufacturer_specific=False)", + 'attribute': "ZCLAttributeDef(id=0xFFFD, name='cluster_revision', type=, zcl_type=, access=, mandatory=True, is_manufacturer_specific=False)", 'value': None, }), '0xfffe': dict({ - 'attribute': "ZCLAttributeDef(id=0xFFFE, name='reporting_status', type=, access=, mandatory=False, is_manufacturer_specific=False)", + 'attribute': "ZCLAttributeDef(id=0xFFFE, name='reporting_status', type=, zcl_type=, access=, mandatory=False, is_manufacturer_specific=False)", 'value': None, }), }), From aa815c09bbbac6ba7a72573112b55949acb7c94d Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Wed, 28 Aug 2024 12:20:04 -0400 Subject: [PATCH 07/11] Migrate existing entities to icon translations --- homeassistant/components/zha/icons.json | 39 +++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/homeassistant/components/zha/icons.json b/homeassistant/components/zha/icons.json index 9b060e8105a295..8dc370a6e52fea 100644 --- a/homeassistant/components/zha/icons.json +++ b/homeassistant/components/zha/icons.json @@ -86,6 +86,18 @@ }, "presence_detection_timeout": { "default": "mdi:timer-edit" + }, + "exercise_trigger_time": { + "default": "mdi:clock" + }, + "external_temperature_sensor": { + "default": "mdi:thermometer" + }, + "load_room_mean": { + "default": "mdi:scale-balance" + }, + "regulation_setpoint_offset": { + "default": "mdi:thermostat" } }, "select": { @@ -94,6 +106,9 @@ }, "keypad_lockout": { "default": "mdi:lock" + }, + "exercise_day_of_week": { + "default": "mdi:wrench-clock" } }, "sensor": { @@ -132,6 +147,15 @@ }, "hooks_state": { "default": "mdi:hook" + }, + "open_window_detected": { + "default": "mdi:window-open" + }, + "load_estimate": { + "default": "mdi:scale-balance" + }, + "preheat_time": { + "default": "mdi:radiator" } }, "switch": { @@ -158,6 +182,21 @@ }, "hooks_locked": { "default": "mdi:lock" + }, + "external_window_sensor": { + "default": "mdi:window-open" + }, + "use_internal_window_detection": { + "default": "mdi:window-open" + }, + "prioritize_external_temperature_sensor": { + "default": "mdi:thermometer" + }, + "heat_available": { + "default": "mdi:water-boiler" + }, + "use_load_balancing": { + "default": "mdi:scale-balance" } } }, From c87183d61c8ba7c987527d6f0263ca4efb059676 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:57:51 -0400 Subject: [PATCH 08/11] Remove "no longer compatible" test --- tests/components/zha/test_update.py | 80 ----------------------------- 1 file changed, 80 deletions(-) diff --git a/tests/components/zha/test_update.py b/tests/components/zha/test_update.py index d8a85dfc0b3af1..56d53a4aff5a61 100644 --- a/tests/components/zha/test_update.py +++ b/tests/components/zha/test_update.py @@ -545,83 +545,3 @@ async def endpoint_reply(cluster_id, tsn, data, command_id): }, blocking=True, ) - - -async def test_firmware_update_no_longer_compatible( - hass: HomeAssistant, - setup_zha, - zigpy_device_mock, -) -> None: - """Test ZHA update platform - firmware update is no longer valid.""" - await setup_zha() - zha_device, cluster, fw_image, installed_fw_version = await setup_test_data( - hass, zigpy_device_mock - ) - - entity_id = find_entity_id(Platform.UPDATE, zha_device, hass) - assert entity_id is not None - - assert hass.states.get(entity_id).state == STATE_UNKNOWN - - # simulate an image available notification - await cluster._handle_query_next_image( - foundation.ZCLHeader.cluster( - tsn=0x12, command_id=general.Ota.ServerCommandDefs.query_next_image.id - ), - general.QueryNextImageCommand( - fw_image.firmware.header.field_control, - zha_device.device.manufacturer_code, - fw_image.firmware.header.image_type, - installed_fw_version, - fw_image.firmware.header.header_version, - ), - ) - - await hass.async_block_till_done() - state = hass.states.get(entity_id) - assert state.state == STATE_ON - attrs = state.attributes - assert attrs[ATTR_INSTALLED_VERSION] == f"0x{installed_fw_version:08x}" - assert not attrs[ATTR_IN_PROGRESS] - assert ( - attrs[ATTR_LATEST_VERSION] == f"0x{fw_image.firmware.header.file_version:08x}" - ) - - new_version = 0x99999999 - - async def endpoint_reply(cluster_id, tsn, data, command_id): - if cluster_id == general.Ota.cluster_id: - hdr, cmd = cluster.deserialize(data) - if isinstance(cmd, general.Ota.ImageNotifyCommand): - zha_device.device.device.packet_received( - make_packet( - zha_device.device.device, - cluster, - general.Ota.ServerCommandDefs.query_next_image.name, - field_control=general.Ota.QueryNextImageCommand.FieldControl.HardwareVersion, - manufacturer_code=fw_image.firmware.header.manufacturer_id, - image_type=fw_image.firmware.header.image_type, - # The device reports that it is no longer compatible! - current_file_version=new_version, - hardware_version=1, - ) - ) - - cluster.endpoint.reply = AsyncMock(side_effect=endpoint_reply) - with pytest.raises(HomeAssistantError): - await hass.services.async_call( - UPDATE_DOMAIN, - SERVICE_INSTALL, - { - ATTR_ENTITY_ID: entity_id, - }, - blocking=True, - ) - - # We updated the currently installed firmware version, as it is no longer valid - state = hass.states.get(entity_id) - assert state.state == STATE_OFF - attrs = state.attributes - assert attrs[ATTR_INSTALLED_VERSION] == f"0x{new_version:08x}" - assert not attrs[ATTR_IN_PROGRESS] - assert attrs[ATTR_LATEST_VERSION] == f"0x{new_version:08x}" From 0b35285a16054b260a60e372820208cdf64f317c Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:59:05 -0400 Subject: [PATCH 09/11] Test that the library release summary is correctly exposed to ZHA --- tests/components/zha/test_update.py | 49 +++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/components/zha/test_update.py b/tests/components/zha/test_update.py index 56d53a4aff5a61..7546984089c833 100644 --- a/tests/components/zha/test_update.py +++ b/tests/components/zha/test_update.py @@ -3,6 +3,9 @@ from unittest.mock import AsyncMock, call, patch import pytest +from zha.application.platforms.update import ( + FirmwareUpdateEntity as ZhaFirmwareUpdateEntity, +) from zigpy.exceptions import DeliveryError from zigpy.ota import OtaImagesResult, OtaImageWithMetadata import zigpy.ota.image as firmware @@ -20,6 +23,7 @@ ATTR_IN_PROGRESS, ATTR_INSTALLED_VERSION, ATTR_LATEST_VERSION, + ATTR_RELEASE_SUMMARY, DOMAIN as UPDATE_DOMAIN, SERVICE_INSTALL, ) @@ -545,3 +549,48 @@ async def endpoint_reply(cluster_id, tsn, data, command_id): }, blocking=True, ) + + +async def test_update_release_summary( + hass: HomeAssistant, + setup_zha, + zigpy_device_mock, +) -> None: + """Test ZHA update platform.""" + await setup_zha() + + gateway = get_zha_gateway(hass) + gateway_proxy: ZHAGatewayProxy = get_zha_gateway_proxy(hass) + + zigpy_device = zigpy_device_mock( + { + 1: { + SIG_EP_INPUT: [general.Basic.cluster_id, general.OnOff.cluster_id], + SIG_EP_OUTPUT: [general.Ota.cluster_id], + SIG_EP_TYPE: zha.DeviceType.ON_OFF_SWITCH, + SIG_EP_PROFILE: zha.PROFILE_ID, + } + }, + node_descriptor=b"\x02@\x84_\x11\x7fd\x00\x00,d\x00\x00", + ) + + gateway.get_or_create_device(zigpy_device) + await gateway.async_device_initialized(zigpy_device) + await hass.async_block_till_done(wait_background_tasks=True) + + zha_device: ZHADeviceProxy = gateway_proxy.get_device_proxy(zigpy_device.ieee) + zha_lib_entity = next( + e + for e in zha_device.device.platform_entities.values() + if isinstance(e, ZhaFirmwareUpdateEntity) + ) + zha_lib_entity._attr_release_summary = "Some release summary" + zha_lib_entity.maybe_emit_state_changed_event() + await hass.async_block_till_done() + + entity_id = find_entity_id(Platform.UPDATE, zha_device, hass) + assert entity_id is not None + assert ( + hass.states.get(entity_id).attributes[ATTR_RELEASE_SUMMARY] + == "Some release summary" + ) From 2bb386e2183801fc9a115acf4b6ec297fd28a1a6 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Thu, 29 Aug 2024 17:23:45 -0400 Subject: [PATCH 10/11] Revert "Always prefer XY color mode in ZHA" This reverts commit 8fb7789ea8ddb6ed2a287aed5010374c0452f6c9. --- homeassistant/components/zha/const.py | 1 + homeassistant/components/zha/helpers.py | 3 +++ homeassistant/components/zha/light.py | 21 ++++++++++----------- homeassistant/components/zha/strings.json | 1 + tests/components/zha/data.py | 14 ++++++++++++++ 5 files changed, 29 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/zha/const.py b/homeassistant/components/zha/const.py index 0e62837782e33a..3986a99cf3f34c 100644 --- a/homeassistant/components/zha/const.py +++ b/homeassistant/components/zha/const.py @@ -43,6 +43,7 @@ CONF_DEFAULT_LIGHT_TRANSITION = "default_light_transition" CONF_ENABLE_ENHANCED_LIGHT_TRANSITION = "enhanced_light_transition" CONF_ENABLE_LIGHT_TRANSITIONING_FLAG = "light_transitioning_flag" +CONF_ALWAYS_PREFER_XY_COLOR_MODE = "always_prefer_xy_color_mode" CONF_GROUP_MEMBERS_ASSUME_STATE = "group_members_assume_state" CONF_ENABLE_IDENTIFY_ON_JOIN = "enable_identify_on_join" diff --git a/homeassistant/components/zha/helpers.py b/homeassistant/components/zha/helpers.py index 61a0e380729638..a5446af7e76e7d 100644 --- a/homeassistant/components/zha/helpers.py +++ b/homeassistant/components/zha/helpers.py @@ -140,6 +140,7 @@ CONF_ALARM_ARM_REQUIRES_CODE, CONF_ALARM_FAILED_TRIES, CONF_ALARM_MASTER_CODE, + CONF_ALWAYS_PREFER_XY_COLOR_MODE, CONF_BAUDRATE, CONF_CONSIDER_UNAVAILABLE_BATTERY, CONF_CONSIDER_UNAVAILABLE_MAINS, @@ -1151,6 +1152,7 @@ def _clean_serial_port_path(path: str) -> str: ), vol.Required(CONF_ENABLE_ENHANCED_LIGHT_TRANSITION, default=False): cv.boolean, vol.Required(CONF_ENABLE_LIGHT_TRANSITIONING_FLAG, default=True): cv.boolean, + vol.Required(CONF_ALWAYS_PREFER_XY_COLOR_MODE, default=True): cv.boolean, vol.Required(CONF_GROUP_MEMBERS_ASSUME_STATE, default=True): cv.boolean, vol.Required(CONF_ENABLE_IDENTIFY_ON_JOIN, default=True): cv.boolean, vol.Optional( @@ -1225,6 +1227,7 @@ def create_zha_config(hass: HomeAssistant, ha_zha_data: HAZHAData) -> ZHAData: enable_light_transitioning_flag=zha_options.get( CONF_ENABLE_LIGHT_TRANSITIONING_FLAG ), + always_prefer_xy_color_mode=zha_options.get(CONF_ALWAYS_PREFER_XY_COLOR_MODE), group_members_assume_state=zha_options.get(CONF_GROUP_MEMBERS_ASSUME_STATE), ) device_options: DeviceOptions = DeviceOptions( diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index def63256cde9cf..4a36030a0dde4f 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -45,6 +45,7 @@ ZhaColorMode.ONOFF: ColorMode.ONOFF, ZhaColorMode.BRIGHTNESS: ColorMode.BRIGHTNESS, ZhaColorMode.COLOR_TEMP: ColorMode.COLOR_TEMP, + ZhaColorMode.HS: ColorMode.HS, ZhaColorMode.XY: ColorMode.XY, ZhaColorMode.RGB: ColorMode.RGB, ZhaColorMode.RGBW: ColorMode.RGBW, @@ -142,6 +143,11 @@ def max_mireds(self) -> int: """Return the warmest color_temp that this light supports.""" return self.entity_data.entity.max_mireds + @property + def hs_color(self) -> tuple[float, float] | None: + """Return the hs color value [int, int].""" + return self.entity_data.entity.hs_color + @property def xy_color(self) -> tuple[float, float] | None: """Return the xy color value [float, float].""" @@ -179,6 +185,7 @@ async def async_turn_on(self, **kwargs: Any) -> None: flash=kwargs.get(ATTR_FLASH), color_temp=kwargs.get(ATTR_COLOR_TEMP), xy_color=kwargs.get(ATTR_XY_COLOR), + hs_color=kwargs.get(ATTR_HS_COLOR), ) self.async_write_ha_state() @@ -193,16 +200,6 @@ async def async_turn_off(self, **kwargs: Any) -> None: @callback def restore_external_state_attributes(self, state: State) -> None: """Restore entity state.""" - - color_mode = None - - if state.attributes.get(ATTR_COLOR_MODE) is not None: - color_mode = ColorMode(state.attributes[ATTR_COLOR_MODE]) - - # Remove in 2025.1: ZHA no longer uses HS color mode, only XY - if color_mode is ColorMode.HS: - color_mode = ColorMode.XY - self.entity_data.entity.restore_external_state_attributes( state=(state.state == STATE_ON), off_with_transition=state.attributes.get(OFF_WITH_TRANSITION), @@ -212,7 +209,9 @@ def restore_external_state_attributes(self, state: State) -> None: xy_color=state.attributes.get(ATTR_XY_COLOR), hs_color=state.attributes.get(ATTR_HS_COLOR), color_mode=( - HA_TO_ZHA_COLOR_MODE[color_mode] if color_mode is not None else None + HA_TO_ZHA_COLOR_MODE[ColorMode(state.attributes[ATTR_COLOR_MODE])] + if state.attributes.get(ATTR_COLOR_MODE) is not None + else None ), effect=state.attributes.get(ATTR_EFFECT), ) diff --git a/homeassistant/components/zha/strings.json b/homeassistant/components/zha/strings.json index 8b6754a24221ef..5d81556564ae80 100644 --- a/homeassistant/components/zha/strings.json +++ b/homeassistant/components/zha/strings.json @@ -178,6 +178,7 @@ "title": "Global Options", "enhanced_light_transition": "Enable enhanced light color/temperature transition from an off-state", "light_transitioning_flag": "Enable enhanced brightness slider during light transition", + "always_prefer_xy_color_mode": "Always prefer XY color mode", "group_members_assume_state": "Group members assume state of group", "enable_identify_on_join": "Enable identify effect when devices join the network", "default_light_transition": "Default light transition time (seconds)", diff --git a/tests/components/zha/data.py b/tests/components/zha/data.py index b50c7a18a06309..eb135c7e8fe0f9 100644 --- a/tests/components/zha/data.py +++ b/tests/components/zha/data.py @@ -23,6 +23,12 @@ "required": True, "default": True, }, + { + "type": "boolean", + "name": "always_prefer_xy_color_mode", + "required": True, + "default": True, + }, { "type": "boolean", "name": "group_members_assume_state", @@ -56,6 +62,7 @@ "enhanced_light_transition": True, "default_light_transition": 0, "light_transitioning_flag": True, + "always_prefer_xy_color_mode": True, "group_members_assume_state": False, "enable_identify_on_join": True, "consider_unavailable_mains": 7200, @@ -87,6 +94,12 @@ "required": True, "default": True, }, + { + "type": "boolean", + "name": "always_prefer_xy_color_mode", + "required": True, + "default": True, + }, { "type": "boolean", "name": "group_members_assume_state", @@ -141,6 +154,7 @@ "enhanced_light_transition": True, "default_light_transition": 0, "light_transitioning_flag": True, + "always_prefer_xy_color_mode": True, "group_members_assume_state": False, "enable_identify_on_join": True, "consider_unavailable_mains": 7200, From bbdaa386ddf7073550cec495af09c53cbd9354f1 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Thu, 29 Aug 2024 19:40:21 -0400 Subject: [PATCH 11/11] Test `release_notes`, not `release_summary` --- tests/components/zha/test_update.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/tests/components/zha/test_update.py b/tests/components/zha/test_update.py index 7546984089c833..e2a614915f9587 100644 --- a/tests/components/zha/test_update.py +++ b/tests/components/zha/test_update.py @@ -23,7 +23,6 @@ ATTR_IN_PROGRESS, ATTR_INSTALLED_VERSION, ATTR_LATEST_VERSION, - ATTR_RELEASE_SUMMARY, DOMAIN as UPDATE_DOMAIN, SERVICE_INSTALL, ) @@ -47,6 +46,8 @@ from .common import find_entity_id, update_attribute_cache from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE +from tests.typing import WebSocketGenerator + @pytest.fixture(autouse=True) def update_platform_only(): @@ -551,12 +552,13 @@ async def endpoint_reply(cluster_id, tsn, data, command_id): ) -async def test_update_release_summary( +async def test_update_release_notes( hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, setup_zha, zigpy_device_mock, ) -> None: - """Test ZHA update platform.""" + """Test ZHA update platform release notes.""" await setup_zha() gateway = get_zha_gateway(hass) @@ -584,13 +586,22 @@ async def test_update_release_summary( for e in zha_device.device.platform_entities.values() if isinstance(e, ZhaFirmwareUpdateEntity) ) - zha_lib_entity._attr_release_summary = "Some release summary" + zha_lib_entity._attr_release_notes = "Some lengthy release notes" zha_lib_entity.maybe_emit_state_changed_event() await hass.async_block_till_done() entity_id = find_entity_id(Platform.UPDATE, zha_device, hass) assert entity_id is not None - assert ( - hass.states.get(entity_id).attributes[ATTR_RELEASE_SUMMARY] - == "Some release summary" + + ws_client = await hass_ws_client(hass) + await ws_client.send_json( + { + "id": 1, + "type": "update/release_notes", + "entity_id": entity_id, + } ) + + result = await ws_client.receive_json() + assert result["success"] is True + assert result["result"] == "Some lengthy release notes"