diff --git a/homeassistant/components/zha/select.py b/homeassistant/components/zha/select.py index d9074acecfc..b4cbce55403 100644 --- a/homeassistant/components/zha/select.py +++ b/homeassistant/components/zha/select.py @@ -26,6 +26,7 @@ from .core.const import ( CHANNEL_ON_OFF, DATA_ZHA, SIGNAL_ADD_ENTITIES, + SIGNAL_ATTR_UPDATED, Strobe, ) from .core.registries import ZHA_ENTITIES @@ -212,6 +213,18 @@ class ZCLEnumSelectEntity(ZhaEntity, SelectEntity): ) self.async_write_ha_state() + async def async_added_to_hass(self) -> None: + """Run when about to be added to hass.""" + await super().async_added_to_hass() + self.async_accept_signal( + self._channel, SIGNAL_ATTR_UPDATED, self.async_set_state + ) + + @callback + def async_set_state(self, attr_id: int, attr_name: str, value: Any): + """Handle state update from channel.""" + self.async_write_ha_state() + @CONFIG_DIAGNOSTIC_MATCH(channel_names=CHANNEL_ON_OFF) class ZHAStartupOnOffSelectEntity( diff --git a/tests/components/zha/test_select.py b/tests/components/zha/test_select.py index 37738e0fd4e..714e27147bb 100644 --- a/tests/components/zha/test_select.py +++ b/tests/components/zha/test_select.py @@ -2,17 +2,28 @@ from unittest.mock import call, patch import pytest +from zhaquirks import ( + DEVICE_TYPE, + ENDPOINTS, + INPUT_CLUSTERS, + OUTPUT_CLUSTERS, + PROFILE_ID, +) from zigpy.const import SIG_EP_PROFILE import zigpy.profiles.zha as zha +from zigpy.quirks import CustomCluster, CustomDevice +import zigpy.types as t import zigpy.zcl.clusters.general as general +from zigpy.zcl.clusters.manufacturer_specific import ManufacturerSpecificCluster import zigpy.zcl.clusters.security as security +from homeassistant.components.zha.select import AqaraMotionSensitivities from homeassistant.const import STATE_UNKNOWN, EntityCategory, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er, restore_state from homeassistant.util import dt as dt_util -from .common import find_entity_id +from .common import async_enable_traffic, find_entity_id, send_attributes_report from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_TYPE @@ -29,6 +40,7 @@ def select_select_only(): Platform.NUMBER, Platform.SELECT, Platform.SENSOR, + Platform.SWITCH, ), ): yield @@ -323,3 +335,79 @@ async def test_on_off_select_unsupported( qualifier=select_name.lower(), ) assert entity_id is None + + +class MotionSensitivityQuirk(CustomDevice): + """Quirk with motion sensitivity attribute.""" + + class OppleCluster(CustomCluster, ManufacturerSpecificCluster): + """Aqara manufacturer specific cluster.""" + + cluster_id = 0xFCC0 + ep_attribute = "opple_cluster" + attributes = { + 0x010C: ("motion_sensitivity", t.uint8_t, True), + } + + def __init__(self, *args, **kwargs): + """Initialize.""" + super().__init__(*args, **kwargs) + # populate cache to create config entity + self._attr_cache.update({0x010C: AqaraMotionSensitivities.Medium}) + + replacement = { + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.OCCUPANCY_SENSOR, + INPUT_CLUSTERS: [general.Basic.cluster_id, OppleCluster], + OUTPUT_CLUSTERS: [], + }, + } + } + + +@pytest.fixture +async def zigpy_device_aqara_sensor(hass, zigpy_device_mock, zha_device_joined): + """Device tracker zigpy Aqara motion sensor device.""" + + zigpy_device = zigpy_device_mock( + { + 1: { + SIG_EP_INPUT: [general.Basic.cluster_id], + SIG_EP_OUTPUT: [], + SIG_EP_TYPE: zha.DeviceType.OCCUPANCY_SENSOR, + } + }, + manufacturer="LUMI", + model="lumi.motion.ac02", + quirk=MotionSensitivityQuirk, + ) + + zha_device = await zha_device_joined(zigpy_device) + zha_device.available = True + await hass.async_block_till_done() + return zigpy_device + + +async def test_on_off_select_attribute_report( + hass: HomeAssistant, light, zha_device_restored, zigpy_device_aqara_sensor +) -> None: + """Test ZHA attribute report parsing for select platform.""" + + zha_device = await zha_device_restored(zigpy_device_aqara_sensor) + cluster = zigpy_device_aqara_sensor.endpoints.get(1).opple_cluster + entity_id = await find_entity_id(Platform.SELECT, zha_device, hass) + assert entity_id is not None + + # allow traffic to flow through the gateway and device + await async_enable_traffic(hass, [zha_device]) + + # test that the state is in default medium state + assert hass.states.get(entity_id).state == AqaraMotionSensitivities.Medium.name + + # send attribute report from device + await send_attributes_report( + hass, cluster, {"motion_sensitivity": AqaraMotionSensitivities.Low} + ) + assert hass.states.get(entity_id).state == AqaraMotionSensitivities.Low.name