diff --git a/homeassistant/components/zha/button.py b/homeassistant/components/zha/button.py index abfa94f5906..f130936df02 100644 --- a/homeassistant/components/zha/button.py +++ b/homeassistant/components/zha/button.py @@ -41,7 +41,6 @@ async def async_setup_entry( discovery.async_add_entities, async_add_entities, entities_to_create, - update_before_add=False, ), ) config_entry.async_on_unload(unsub) diff --git a/homeassistant/components/zha/core/discovery.py b/homeassistant/components/zha/core/discovery.py index 8d7d53468e2..cdad57834eb 100644 --- a/homeassistant/components/zha/core/discovery.py +++ b/homeassistant/components/zha/core/discovery.py @@ -54,14 +54,13 @@ async def async_add_entities( tuple[str, ZHADevice, list[base.ZigbeeChannel]], ] ], - update_before_add: bool = True, ) -> None: """Add entities helper.""" if not entities: return to_add = [ent_cls.create_entity(*args) for ent_cls, args in entities] entities_to_add = [entity for entity in to_add if entity is not None] - _async_add_entities(entities_to_add, update_before_add=update_before_add) + _async_add_entities(entities_to_add, update_before_add=False) entities.clear() diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index 13e43aa9ff0..50ffb8f4fcd 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -291,6 +291,7 @@ class ZhaGroupEntity(BaseZhaEntity): async def async_added_to_hass(self) -> None: """Register callbacks.""" await super().async_added_to_hass() + await self.async_update() self.async_accept_signal( None, diff --git a/homeassistant/components/zha/fan.py b/homeassistant/components/zha/fan.py index bad1d8e94f1..1c2f52c6038 100644 --- a/homeassistant/components/zha/fan.py +++ b/homeassistant/components/zha/fan.py @@ -67,7 +67,6 @@ async def async_setup_entry( discovery.async_add_entities, async_add_entities, entities_to_create, - update_before_add=False, ), ) config_entry.async_on_unload(unsub) diff --git a/homeassistant/components/zha/number.py b/homeassistant/components/zha/number.py index 3c5a374e588..e1191b4ece4 100644 --- a/homeassistant/components/zha/number.py +++ b/homeassistant/components/zha/number.py @@ -250,7 +250,6 @@ async def async_setup_entry( discovery.async_add_entities, async_add_entities, entities_to_create, - update_before_add=False, ), ) config_entry.async_on_unload(unsub) diff --git a/homeassistant/components/zha/select.py b/homeassistant/components/zha/select.py index 0a67f1eac5f..b9237e24eef 100644 --- a/homeassistant/components/zha/select.py +++ b/homeassistant/components/zha/select.py @@ -3,6 +3,7 @@ from __future__ import annotations from enum import Enum import functools +import logging from zigpy.zcl.clusters.general import OnOff from zigpy.zcl.clusters.security import IasWd @@ -30,6 +31,7 @@ from .entity import ZhaEntity CONFIG_DIAGNOSTIC_MATCH = functools.partial( ZHA_ENTITIES.config_diagnostic_match, Platform.SELECT ) +_LOGGER = logging.getLogger(__name__) async def async_setup_entry( @@ -47,7 +49,6 @@ async def async_setup_entry( discovery.async_add_entities, async_add_entities, entities_to_create, - update_before_add=False, ), ) config_entry.async_on_unload(unsub) @@ -163,7 +164,15 @@ class ZCLEnumSelectEntity(ZhaEntity, SelectEntity): Return entity if it is a supported configuration, otherwise return None """ channel = channels[0] - if cls._select_attr in channel.cluster.unsupported_attributes: + if ( + cls._select_attr in channel.cluster.unsupported_attributes + or channel.cluster.get(cls._select_attr) is None + ): + _LOGGER.debug( + "%s is not supported - skipping %s entity creation", + cls._select_attr, + cls.__name__, + ) return None return cls(unique_id, zha_device, channels, **kwargs) diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index f150304c33d..0a5fe204648 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -103,7 +103,6 @@ async def async_setup_entry( discovery.async_add_entities, async_add_entities, entities_to_create, - update_before_add=False, ), ) config_entry.async_on_unload(unsub) diff --git a/homeassistant/components/zha/siren.py b/homeassistant/components/zha/siren.py index 587efc49c9c..38b58b8dc54 100644 --- a/homeassistant/components/zha/siren.py +++ b/homeassistant/components/zha/siren.py @@ -60,7 +60,6 @@ async def async_setup_entry( discovery.async_add_entities, async_add_entities, entities_to_create, - update_before_add=False, ), ) config_entry.async_on_unload(unsub) diff --git a/tests/components/zha/test_cover.py b/tests/components/zha/test_cover.py index 60a4fab25be..3c00b5d3109 100644 --- a/tests/components/zha/test_cover.py +++ b/tests/components/zha/test_cover.py @@ -104,17 +104,14 @@ def zigpy_keen_vent(zigpy_device_mock): ) -@patch( - "homeassistant.components.zha.core.channels.closures.WindowCovering.async_initialize" -) -async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device): +async def test_cover(hass, zha_device_joined_restored, zigpy_cover_device): """Test zha cover platform.""" # load up cover domain cluster = zigpy_cover_device.endpoints.get(1).window_covering cluster.PLUGGED_ATTR_READS = {"current_position_lift_percentage": 100} zha_device = await zha_device_joined_restored(zigpy_cover_device) - assert cluster.read_attributes.call_count == 2 + assert cluster.read_attributes.call_count == 1 assert "current_position_lift_percentage" in cluster.read_attributes.call_args[0][0] entity_id = await find_entity_id(Platform.COVER, zha_device, hass) @@ -193,6 +190,7 @@ async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device): assert cluster.request.call_args[1]["expect_reply"] is True # test rejoin + cluster.PLUGGED_ATTR_READS = {"current_position_lift_percentage": 0} await async_test_rejoin(hass, zigpy_cover_device, [cluster], (1,)) assert hass.states.get(entity_id).state == STATE_OPEN diff --git a/tests/components/zha/test_device_tracker.py b/tests/components/zha/test_device_tracker.py index e345918179e..06caac91cb2 100644 --- a/tests/components/zha/test_device_tracker.py +++ b/tests/components/zha/test_device_tracker.py @@ -52,7 +52,7 @@ async def test_device_tracker(hass, zha_device_joined_restored, zigpy_device_dt) entity_id = await find_entity_id(Platform.DEVICE_TRACKER, zha_device, hass) assert entity_id is not None - assert hass.states.get(entity_id).state == STATE_HOME + assert hass.states.get(entity_id).state == STATE_NOT_HOME await async_enable_traffic(hass, [zha_device], enabled=False) # test that the device tracker was created and that it is unavailable assert hass.states.get(entity_id).state == STATE_UNAVAILABLE diff --git a/tests/components/zha/test_select.py b/tests/components/zha/test_select.py index 452939420bf..70b943d5ea2 100644 --- a/tests/components/zha/test_select.py +++ b/tests/components/zha/test_select.py @@ -1,5 +1,7 @@ """Test ZHA select entities.""" +from unittest.mock import call + import pytest from zigpy.const import SIG_EP_PROFILE import zigpy.profiles.zha as zha @@ -50,6 +52,7 @@ async def light(hass, zigpy_device_mock): SIG_EP_OUTPUT: [general.Ota.cluster_id], } }, + node_descriptor=b"\x02@\x84_\x11\x7fd\x00\x00,d\x00\x00", ) return zigpy_device @@ -173,15 +176,15 @@ async def test_select_restore_state( assert state.state == security.IasWd.Warning.WarningMode.Burglar.name -async def test_on_off_select(hass, light, zha_device_joined_restored): - """Test zha on off select.""" +async def test_on_off_select_new_join(hass, light, zha_device_joined): + """Test zha on off select - new join.""" entity_registry = er.async_get(hass) on_off_cluster = light.endpoints[1].on_off on_off_cluster.PLUGGED_ATTR_READS = { "start_up_on_off": general.OnOff.StartUpOnOff.On } - zha_device = await zha_device_joined_restored(light) + zha_device = await zha_device_joined(light) select_name = general.OnOff.StartUpOnOff.__name__ entity_id = await find_entity_id( Platform.SELECT, @@ -191,12 +194,19 @@ async def test_on_off_select(hass, light, zha_device_joined_restored): ) assert entity_id is not None + assert on_off_cluster.read_attributes.call_count == 2 + assert ( + call(["start_up_on_off"], allow_cache=True, only_cache=False, manufacturer=None) + in on_off_cluster.read_attributes.call_args_list + ) + assert ( + call(["on_off"], allow_cache=False, only_cache=False, manufacturer=None) + in on_off_cluster.read_attributes.call_args_list + ) + state = hass.states.get(entity_id) assert state - if zha_device_joined_restored.name == "zha_device_joined": - assert state.state == general.OnOff.StartUpOnOff.On.name - else: - assert state.state == STATE_UNKNOWN + assert state.state == general.OnOff.StartUpOnOff.On.name assert state.attributes["options"] == ["Off", "On", "Toggle", "PreviousValue"] @@ -225,6 +235,58 @@ async def test_on_off_select(hass, light, zha_device_joined_restored): assert state.state == general.OnOff.StartUpOnOff.Off.name +async def test_on_off_select_restored(hass, light, zha_device_restored): + """Test zha on off select - restored.""" + + entity_registry = er.async_get(hass) + on_off_cluster = light.endpoints[1].on_off + on_off_cluster.PLUGGED_ATTR_READS = { + "start_up_on_off": general.OnOff.StartUpOnOff.On + } + zha_device = await zha_device_restored(light) + + assert zha_device.is_mains_powered + + assert on_off_cluster.read_attributes.call_count == 4 + # first 2 calls hit cache only + assert ( + call(["start_up_on_off"], allow_cache=True, only_cache=True, manufacturer=None) + in on_off_cluster.read_attributes.call_args_list + ) + assert ( + call(["on_off"], allow_cache=True, only_cache=True, manufacturer=None) + in on_off_cluster.read_attributes.call_args_list + ) + + # 2nd set of calls can actually read from the device + assert ( + call(["start_up_on_off"], allow_cache=True, only_cache=False, manufacturer=None) + in on_off_cluster.read_attributes.call_args_list + ) + assert ( + call(["on_off"], allow_cache=False, only_cache=False, manufacturer=None) + in on_off_cluster.read_attributes.call_args_list + ) + + select_name = general.OnOff.StartUpOnOff.__name__ + entity_id = await find_entity_id( + Platform.SELECT, + zha_device, + hass, + qualifier=select_name.lower(), + ) + assert entity_id is not None + + state = hass.states.get(entity_id) + assert state + assert state.state == general.OnOff.StartUpOnOff.On.name + assert state.attributes["options"] == ["Off", "On", "Toggle", "PreviousValue"] + + entity_entry = entity_registry.async_get(entity_id) + assert entity_entry + assert entity_entry.entity_category == ENTITY_CATEGORY_CONFIG + + async def test_on_off_select_unsupported(hass, light, zha_device_joined_restored): """Test zha on off select unsupported."""