Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix device availability updating entities and enhance tests #152

Merged
merged 2 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 47 additions & 9 deletions tests/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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",
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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",
Expand Down
10 changes: 5 additions & 5 deletions zha/zigbee/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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",
Expand Down
Loading