From 9d0dd0b7843f6d6058a220be36e7cf71a16bf26f Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Thu, 27 Apr 2023 18:35:07 -0400 Subject: [PATCH] Fix ZHA startup failure with the Konke button (#92144) * Ensure devices with bad cluster subclasses do not prevent startup * Explicitly unit test an affected SML001 device * Do not use invalid `hue_occupancy` attribute name * Actually remove `hue_occupancy` * Bump ZHA dependencies --- homeassistant/components/zha/binary_sensor.py | 6 + .../zha/core/cluster_handlers/general.py | 11 +- homeassistant/components/zha/core/const.py | 1 + homeassistant/components/zha/manifest.json | 4 +- homeassistant/components/zha/select.py | 6 +- requirements_all.txt | 4 +- requirements_test_all.txt | 4 +- tests/components/zha/conftest.py | 9 + tests/components/zha/zha_devices_list.py | 164 +++++++++++++++++- 9 files changed, 198 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index 6b080db081e..1c29f619719 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -22,6 +22,7 @@ from .core import discovery from .core.const import ( CLUSTER_HANDLER_ACCELEROMETER, CLUSTER_HANDLER_BINARY_INPUT, + CLUSTER_HANDLER_HUE_OCCUPANCY, CLUSTER_HANDLER_OCCUPANCY, CLUSTER_HANDLER_ON_OFF, CLUSTER_HANDLER_ZONE, @@ -130,6 +131,11 @@ class Occupancy(BinarySensor): _attr_device_class: BinarySensorDeviceClass = BinarySensorDeviceClass.OCCUPANCY +@MULTI_MATCH(cluster_handler_names=CLUSTER_HANDLER_HUE_OCCUPANCY) +class HueOccupancy(Occupancy): + """ZHA Hue occupancy.""" + + @STRICT_MATCH(cluster_handler_names=CLUSTER_HANDLER_ON_OFF) class Opening(BinarySensor): """ZHA OnOff BinarySensor.""" diff --git a/homeassistant/components/zha/core/cluster_handlers/general.py b/homeassistant/components/zha/core/cluster_handlers/general.py index 12b1f636866..d4014bbf697 100644 --- a/homeassistant/components/zha/core/cluster_handlers/general.py +++ b/homeassistant/components/zha/core/cluster_handlers/general.py @@ -347,7 +347,7 @@ class OnOffClientClusterHandler(ClientClusterHandler): class OnOffClusterHandler(ClusterHandler): """Cluster handler for the OnOff Zigbee cluster.""" - ON_OFF = 0 + ON_OFF = general.OnOff.attributes_by_name["on_off"].id REPORT_CONFIG = (AttrReportConfig(attr="on_off", config=REPORT_CONFIG_IMMEDIATE),) ZCL_INIT_ATTRS = { "start_up_on_off": True, @@ -374,6 +374,15 @@ class OnOffClusterHandler(ClusterHandler): if self.cluster.endpoint.model == "TS011F": self.ZCL_INIT_ATTRS["child_lock"] = True + @classmethod + def matches(cls, cluster: zigpy.zcl.Cluster, endpoint: Endpoint) -> bool: + """Filter the cluster match for specific devices.""" + return not ( + cluster.endpoint.device.manufacturer == "Konke" + and cluster.endpoint.device.model + in ("3AFE280100510001", "3AFE170100510001") + ) + @property def on_off(self) -> bool | None: """Return cached value of on/off attribute.""" diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index ad4bfd7a690..c90c78243d1 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -78,6 +78,7 @@ CLUSTER_HANDLER_ELECTRICAL_MEASUREMENT = "electrical_measurement" CLUSTER_HANDLER_EVENT_RELAY = "event_relay" CLUSTER_HANDLER_FAN = "fan" CLUSTER_HANDLER_HUMIDITY = "humidity" +CLUSTER_HANDLER_HUE_OCCUPANCY = "philips_occupancy" CLUSTER_HANDLER_SOIL_MOISTURE = "soil_moisture" CLUSTER_HANDLER_LEAF_WETNESS = "leaf_wetness" CLUSTER_HANDLER_IAS_ACE = "ias_ace" diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 2e08b0cc6d8..2063a9906ec 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -20,10 +20,10 @@ "zigpy_znp" ], "requirements": [ - "bellows==0.35.1", + "bellows==0.35.2", "pyserial==3.5", "pyserial-asyncio==0.6", - "zha-quirks==0.0.97", + "zha-quirks==0.0.98", "zigpy-deconz==0.21.0", "zigpy==0.55.0", "zigpy-xbee==0.18.0", diff --git a/homeassistant/components/zha/select.py b/homeassistant/components/zha/select.py index 1bab8a3f2c3..2453f40af44 100644 --- a/homeassistant/components/zha/select.py +++ b/homeassistant/components/zha/select.py @@ -20,9 +20,9 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .core import discovery from .core.const import ( + CLUSTER_HANDLER_HUE_OCCUPANCY, CLUSTER_HANDLER_IAS_WD, CLUSTER_HANDLER_INOVELLI, - CLUSTER_HANDLER_OCCUPANCY, CLUSTER_HANDLER_ON_OFF, DATA_ZHA, SIGNAL_ADD_ENTITIES, @@ -367,7 +367,7 @@ class HueV1MotionSensitivities(types.enum8): @CONFIG_DIAGNOSTIC_MATCH( - cluster_handler_names=CLUSTER_HANDLER_OCCUPANCY, + cluster_handler_names=CLUSTER_HANDLER_HUE_OCCUPANCY, manufacturers={"Philips", "Signify Netherlands B.V."}, models={"SML001"}, ) @@ -390,7 +390,7 @@ class HueV2MotionSensitivities(types.enum8): @CONFIG_DIAGNOSTIC_MATCH( - cluster_handler_names=CLUSTER_HANDLER_OCCUPANCY, + cluster_handler_names=CLUSTER_HANDLER_HUE_OCCUPANCY, manufacturers={"Philips", "Signify Netherlands B.V."}, models={"SML002", "SML003", "SML004"}, ) diff --git a/requirements_all.txt b/requirements_all.txt index c52da3e3f5b..f2321b4e251 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -428,7 +428,7 @@ beautifulsoup4==4.11.1 # beewi_smartclim==0.0.10 # homeassistant.components.zha -bellows==0.35.1 +bellows==0.35.2 # homeassistant.components.bmw_connected_drive bimmer_connected==0.13.0 @@ -2709,7 +2709,7 @@ zeroconf==0.58.2 zeversolar==0.3.1 # homeassistant.components.zha -zha-quirks==0.0.97 +zha-quirks==0.0.98 # homeassistant.components.zhong_hong zhong_hong_hvac==1.0.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 96db981b8d6..8fb969ec23a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -361,7 +361,7 @@ base36==0.1.1 beautifulsoup4==4.11.1 # homeassistant.components.zha -bellows==0.35.1 +bellows==0.35.2 # homeassistant.components.bmw_connected_drive bimmer_connected==0.13.0 @@ -1964,7 +1964,7 @@ zeroconf==0.58.2 zeversolar==0.3.1 # homeassistant.components.zha -zha-quirks==0.0.97 +zha-quirks==0.0.98 # homeassistant.components.zha zigpy-deconz==0.21.0 diff --git a/tests/components/zha/conftest.py b/tests/components/zha/conftest.py index 0621c6521a9..271108496b2 100644 --- a/tests/components/zha/conftest.py +++ b/tests/components/zha/conftest.py @@ -240,6 +240,15 @@ def zigpy_device_mock(zigpy_app_controller): ): common.patch_cluster(cluster) + if attributes is not None: + for ep_id, clusters in attributes.items(): + for cluster_name, attrs in clusters.items(): + cluster = getattr(device.endpoints[ep_id], cluster_name) + + for name, value in attrs.items(): + attr_id = cluster.find_attribute(name).id + cluster._attr_cache[attr_id] = value + return device return _mock_dev diff --git a/tests/components/zha/zha_devices_list.py b/tests/components/zha/zha_devices_list.py index 0ec1ae8aa14..a45d02a3aa2 100644 --- a/tests/components/zha/zha_devices_list.py +++ b/tests/components/zha/zha_devices_list.py @@ -10,16 +10,26 @@ from zigpy.const import ( SIG_MODEL, SIG_NODE_DESC, ) -from zigpy.profiles import zha +from zigpy.profiles import zha, zll +from zigpy.types import Bool, uint8_t from zigpy.zcl.clusters.closures import DoorLock from zigpy.zcl.clusters.general import ( Basic, Groups, Identify, + LevelControl, MultistateInput, + OnOff, Ota, + PowerConfiguration, Scenes, ) +from zigpy.zcl.clusters.lighting import Color +from zigpy.zcl.clusters.measurement import ( + IlluminanceMeasurement, + OccupancySensing, + TemperatureMeasurement, +) DEV_SIG_CLUSTER_HANDLERS = "cluster_handlers" DEV_SIG_DEV_NO = "device_no" @@ -5373,4 +5383,156 @@ DEVICES = [ }, }, }, + { + DEV_SIG_DEV_NO: 100, + SIG_MANUFACTURER: "Konke", + SIG_MODEL: "3AFE170100510001", + SIG_NODE_DESC: b"\x02@\x80\x02\x10RR\x00\x00,R\x00\x00", + SIG_ENDPOINTS: { + 1: { + PROFILE_ID: 260, + DEVICE_TYPE: zha.DeviceType.ON_OFF_OUTPUT, + INPUT_CLUSTERS: [ + Basic.cluster_id, + PowerConfiguration.cluster_id, + Identify.cluster_id, + Groups.cluster_id, + Scenes.cluster_id, + OnOff.cluster_id, + ], + OUTPUT_CLUSTERS: [ + Identify.cluster_id, + ], + } + }, + DEV_SIG_EVT_CLUSTER_HANDLERS: [], + DEV_SIG_ENT_MAP: { + ("button", "00:11:22:33:44:55:66:77-1-3"): { + DEV_SIG_CLUSTER_HANDLERS: ["identify"], + DEV_SIG_ENT_MAP_CLASS: "ZHAIdentifyButton", + DEV_SIG_ENT_MAP_ID: "button.konke_3afe170100510001_identify", + }, + ("sensor", "00:11:22:33:44:55:66:77-1-1"): { + DEV_SIG_CLUSTER_HANDLERS: ["power"], + DEV_SIG_ENT_MAP_CLASS: "Battery", + DEV_SIG_ENT_MAP_ID: "sensor.konke_3afe170100510001_battery", + }, + ("sensor", "00:11:22:33:44:55:66:77-1-0-rssi"): { + DEV_SIG_CLUSTER_HANDLERS: ["basic"], + DEV_SIG_ENT_MAP_CLASS: "RSSISensor", + DEV_SIG_ENT_MAP_ID: "sensor.konke_3afe170100510001_rssi", + }, + ("sensor", "00:11:22:33:44:55:66:77-1-0-lqi"): { + DEV_SIG_CLUSTER_HANDLERS: ["basic"], + DEV_SIG_ENT_MAP_CLASS: "LQISensor", + DEV_SIG_ENT_MAP_ID: "sensor.konke_3afe170100510001_lqi", + }, + }, + }, + { + DEV_SIG_DEV_NO: 101, + SIG_MANUFACTURER: "Philips", + SIG_MODEL: "SML001", + SIG_NODE_DESC: b"\x02@\x80\x0b\x10Y?\x00\x00\x00?\x00\x00", + SIG_ENDPOINTS: { + 1: { + PROFILE_ID: zll.PROFILE_ID, + DEVICE_TYPE: zll.DeviceType.ON_OFF_SENSOR, + INPUT_CLUSTERS: [Basic.cluster_id], + OUTPUT_CLUSTERS: [ + Basic.cluster_id, + Identify.cluster_id, + Groups.cluster_id, + Scenes.cluster_id, + OnOff.cluster_id, + LevelControl.cluster_id, + Color.cluster_id, + ], + }, + 2: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR, + INPUT_CLUSTERS: [ + Basic.cluster_id, + PowerConfiguration.cluster_id, + Identify.cluster_id, + IlluminanceMeasurement.cluster_id, + TemperatureMeasurement.cluster_id, + OccupancySensing.cluster_id, + ], + OUTPUT_CLUSTERS: [ + Ota.cluster_id, + ], + }, + }, + DEV_SIG_ATTRIBUTES: { + 2: { + "basic": { + "trigger_indicator": Bool(False), + }, + "philips_occupancy": { + "sensitivity": uint8_t(1), + }, + } + }, + DEV_SIG_EVT_CLUSTER_HANDLERS: [ + "1:0x0005", + "1:0x0006", + "1:0x0008", + "1:0x0300", + "2:0x0019", + ], + DEV_SIG_ENT_MAP: { + ("button", "00:11:22:33:44:55:66:77-2-3"): { + DEV_SIG_CLUSTER_HANDLERS: ["identify"], + DEV_SIG_ENT_MAP_CLASS: "ZHAIdentifyButton", + DEV_SIG_ENT_MAP_ID: "button.philips_sml001_identify", + }, + ("sensor", "00:11:22:33:44:55:66:77-2-1"): { + DEV_SIG_CLUSTER_HANDLERS: ["power"], + DEV_SIG_ENT_MAP_CLASS: "Battery", + DEV_SIG_ENT_MAP_ID: "sensor.philips_sml001_battery", + }, + ("sensor", "00:11:22:33:44:55:66:77-1-0-rssi"): { + DEV_SIG_CLUSTER_HANDLERS: ["basic"], + DEV_SIG_ENT_MAP_CLASS: "RSSISensor", + DEV_SIG_ENT_MAP_ID: "sensor.philips_sml001_rssi", + }, + ("sensor", "00:11:22:33:44:55:66:77-1-0-lqi"): { + DEV_SIG_CLUSTER_HANDLERS: ["basic"], + DEV_SIG_ENT_MAP_CLASS: "LQISensor", + DEV_SIG_ENT_MAP_ID: "sensor.philips_sml001_lqi", + }, + ("binary_sensor", "00:11:22:33:44:55:66:77-1-6"): { + DEV_SIG_CLUSTER_HANDLERS: ["on_off"], + DEV_SIG_ENT_MAP_CLASS: "Motion", + DEV_SIG_ENT_MAP_ID: "binary_sensor.philips_sml001_motion", + }, + ("sensor", "00:11:22:33:44:55:66:77-2-1024"): { + DEV_SIG_CLUSTER_HANDLERS: ["illuminance"], + DEV_SIG_ENT_MAP_CLASS: "Illuminance", + DEV_SIG_ENT_MAP_ID: "sensor.philips_sml001_illuminance", + }, + ("binary_sensor", "00:11:22:33:44:55:66:77-2-1030"): { + DEV_SIG_CLUSTER_HANDLERS: ["philips_occupancy"], + DEV_SIG_ENT_MAP_CLASS: "HueOccupancy", + DEV_SIG_ENT_MAP_ID: "binary_sensor.philips_sml001_occupancy", + }, + ("sensor", "00:11:22:33:44:55:66:77-2-1026"): { + DEV_SIG_CLUSTER_HANDLERS: ["temperature"], + DEV_SIG_ENT_MAP_CLASS: "Temperature", + DEV_SIG_ENT_MAP_ID: "sensor.philips_sml001_temperature", + }, + ("switch", "00:11:22:33:44:55:66:77-2-0-trigger_indicator"): { + DEV_SIG_CLUSTER_HANDLERS: ["basic"], + DEV_SIG_ENT_MAP_CLASS: "HueMotionTriggerIndicatorSwitch", + DEV_SIG_ENT_MAP_ID: "switch.philips_sml001_led_trigger_indicator", + }, + ("select", "00:11:22:33:44:55:66:77-2-1030-motion_sensitivity"): { + DEV_SIG_CLUSTER_HANDLERS: ["philips_occupancy"], + DEV_SIG_ENT_MAP_CLASS: "HueV1MotionSensitivity", + DEV_SIG_ENT_MAP_ID: "select.philips_sml001_hue_motion_sensitivity", + }, + }, + }, ]