diff --git a/tests/test_device.py b/tests/test_device.py index 4e97bd28..18a2fa57 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -162,6 +162,12 @@ def _update_last_seen(*args, **kwargs): # pylint: disable=unused-argument basic_ch.read_attributes.side_effect = _update_last_seen + for entity in zha_device.platform_entities.values(): + entity.emit = mock.MagicMock(wraps=entity.emit) + + # we want to test the device availability handling alone + zha_gateway.global_updater.stop() + # successfully ping zigpy device, but zha_device is not yet available await _send_time_changed( zha_gateway, zha_gateway._device_availability_checker.__polling_interval + 1 @@ -170,6 +176,11 @@ def _update_last_seen(*args, **kwargs): # pylint: disable=unused-argument assert basic_ch.read_attributes.await_args[0][0] == ["manufacturer"] assert zha_device.available is False + for entity in zha_device.platform_entities.values(): + entity.emit.assert_not_called() + assert not entity.available + entity.emit.reset_mock() + # There was traffic from the device: pings, but not yet available await _send_time_changed( zha_gateway, zha_gateway._device_availability_checker.__polling_interval + 1 @@ -178,6 +189,11 @@ def _update_last_seen(*args, **kwargs): # pylint: disable=unused-argument assert basic_ch.read_attributes.await_args[0][0] == ["manufacturer"] assert zha_device.available is False + for entity in zha_device.platform_entities.values(): + entity.emit.assert_not_called() + assert not entity.available + entity.emit.reset_mock() + # There was traffic from the device: don't try to ping, marked as available await _send_time_changed( zha_gateway, zha_gateway._device_availability_checker.__polling_interval + 1 @@ -187,23 +203,24 @@ def _update_last_seen(*args, **kwargs): # pylint: disable=unused-argument assert zha_device.available is True assert zha_device.on_network is True + for entity in zha_device.platform_entities.values(): + entity.emit.assert_called() + assert entity.available + entity.emit.reset_mock() + assert "Device is not on the network, marking unavailable" not in caplog.text zha_device.on_network = False assert zha_device.available is False assert zha_device.on_network is False - sleep_time = max( - zha_gateway.global_updater.__polling_interval, - zha_gateway._device_availability_checker.__polling_interval, - ) - sleep_time += 2 - - await asyncio.sleep(sleep_time) - await zha_gateway.async_block_till_done(wait_background_tasks=True) - assert "Device is not on the network, marking unavailable" in caplog.text + for entity in zha_device.platform_entities.values(): + entity.emit.assert_called() + assert not entity.available + entity.emit.reset_mock() + @patch( "zha.zigbee.cluster_handlers.general.BasicClusterHandler.async_initialize", @@ -227,6 +244,12 @@ async def test_check_available_unsuccessful( time.time() - zha_device.consider_unavailable_time - 2 ) + for entity in zha_device.platform_entities.values(): + entity.emit = mock.MagicMock(wraps=entity.emit) + + # we want to test the device availability handling alone + zha_gateway.global_updater.stop() + # unsuccessfully ping zigpy device, but zha_device is still available await _send_time_changed( zha_gateway, zha_gateway._device_availability_checker.__polling_interval + 1 @@ -236,6 +259,11 @@ async def test_check_available_unsuccessful( assert basic_ch.read_attributes.await_args[0][0] == ["manufacturer"] assert zha_device.available is True + for entity in zha_device.platform_entities.values(): + entity.emit.assert_not_called() + assert entity.available + entity.emit.reset_mock() + # still no traffic, but zha_device is still available await _send_time_changed( zha_gateway, zha_gateway._device_availability_checker.__polling_interval + 1 @@ -245,6 +273,11 @@ async def test_check_available_unsuccessful( assert basic_ch.read_attributes.await_args[0][0] == ["manufacturer"] assert zha_device.available is True + for entity in zha_device.platform_entities.values(): + entity.emit.assert_not_called() + assert entity.available + entity.emit.reset_mock() + # not even trying to update, device is unavailable await _send_time_changed( zha_gateway, zha_gateway._device_availability_checker.__polling_interval + 1 @@ -254,6 +287,11 @@ async def test_check_available_unsuccessful( assert basic_ch.read_attributes.await_args[0][0] == ["manufacturer"] assert zha_device.available is False + for entity in zha_device.platform_entities.values(): + entity.emit.assert_called() + assert not entity.available + entity.emit.reset_mock() + @patch( "zha.zigbee.cluster_handlers.general.BasicClusterHandler.async_initialize", diff --git a/zha/zigbee/device.py b/zha/zigbee/device.py index 8cec4a27..4c3d1003 100644 --- a/zha/zigbee/device.py +++ b/zha/zigbee/device.py @@ -408,8 +408,10 @@ def on_network(self): @on_network.setter def on_network(self, new_on_network: bool) -> None: """Set device on_network flag.""" - self._on_network = new_on_network self.update_available(new_on_network) + self._on_network = new_on_network + if not new_on_network: + self.debug("Device is not on the network, marking unavailable") @property def power_configuration_ch(self) -> ClusterHandler | None: @@ -510,10 +512,6 @@ async def _check_available(self, *_: Any) -> None: # don't flip the availability state of the coordinator if self.is_active_coordinator: return - if not self._on_network: - self.debug("Device is not on the network, marking unavailable") - self.update_available(False) - return if self.last_seen is None: self.debug("last_seen is None, marking the device unavailable") self.update_available(False) @@ -586,6 +584,8 @@ def update_available(self, available: bool) -> None: return if availability_changed and not available: self.debug("Device availability changed and device became unavailable") + for entity in self.platform_entities.values(): + entity.maybe_emit_state_changed_event() self.emit_zha_event( { "device_event_type": "device_offline",