Check for Z-Wave firmware updates of sleeping devices (#150123)

This commit is contained in:
AlCalzone
2025-08-06 18:08:51 +02:00
committed by GitHub
parent d0cc9990dd
commit 4e2fe63182
2 changed files with 8 additions and 69 deletions
@@ -10,7 +10,6 @@ from datetime import datetime, timedelta
from typing import Any, Final, cast
from awesomeversion import AwesomeVersion
from zwave_js_server.const import NodeStatus
from zwave_js_server.exceptions import BaseZwaveJSServerError, FailedZWaveCommand
from zwave_js_server.model.driver import Driver
from zwave_js_server.model.firmware import (
@@ -192,7 +191,6 @@ class ZWaveFirmwareUpdateEntity(UpdateEntity):
self.entity_description = entity_description
self.node = node
self._latest_version_firmware: FirmwareUpdateInfo | None = None
self._status_unsub: Callable[[], None] | None = None
self._poll_unsub: Callable[[], None] | None = None
self._progress_unsub: Callable[[], None] | None = None
self._finished_unsub: Callable[[], None] | None = None
@@ -213,12 +211,6 @@ class ZWaveFirmwareUpdateEntity(UpdateEntity):
"""Return ZWave Node Firmware Update specific state data to be restored."""
return ZWaveFirmwareUpdateExtraStoredData(self._latest_version_firmware)
@callback
def _update_on_status_change(self, _: dict[str, Any]) -> None:
"""Update the entity when node is awake."""
self._status_unsub = None
self.hass.async_create_task(self._async_update())
@callback
def update_progress(self, event: dict[str, Any]) -> None:
"""Update install progress on event."""
@@ -270,14 +262,6 @@ class ZWaveFirmwareUpdateEntity(UpdateEntity):
)
return
# If device is asleep, wait for it to wake up before attempting an update
if self.node.status == NodeStatus.ASLEEP:
if not self._status_unsub:
self._status_unsub = self.node.once(
"wake up", self._update_on_status_change
)
return
try:
# Retrieve all firmware updates including non-stable ones but filter
# non-stable channels out
@@ -436,10 +420,6 @@ class ZWaveFirmwareUpdateEntity(UpdateEntity):
async def async_will_remove_from_hass(self) -> None:
"""Call when entity will be removed."""
if self._status_unsub:
self._status_unsub()
self._status_unsub = None
if self._poll_unsub:
self._poll_unsub()
self._poll_unsub = None
+8 -49
View File
@@ -269,7 +269,7 @@ async def test_update_entity_sleep(
zen_31: Node,
integration: MockConfigEntry,
) -> None:
"""Test update occurs when device is asleep after it wakes up."""
"""Test update occurs when device is asleep."""
event = Event(
"sleep",
data={"source": "node", "event": "sleep", "nodeId": zen_31.node_id},
@@ -283,29 +283,13 @@ async def test_update_entity_sleep(
await hass.async_block_till_done()
# Two nodes in total, the controller node and the zen_31 node.
# The zen_31 node is asleep,
# so we should only check for updates for the controller node.
assert client.async_send_command.call_count == 1
args = client.async_send_command.call_args[0][0]
assert args["command"] == "controller.get_available_firmware_updates"
assert args["nodeId"] == 1
client.async_send_command.reset_mock()
event = Event(
"wake up",
data={"source": "node", "event": "wake up", "nodeId": zen_31.node_id},
)
zen_31.receive_event(event)
await hass.async_block_till_done()
# Now that the zen_31 node is awake we can check for updates for it.
# The controller node has already been checked,
# so won't get another check now.
assert client.async_send_command.call_count == 1
args = client.async_send_command.call_args[0][0]
assert args["command"] == "controller.get_available_firmware_updates"
assert args["nodeId"] == 94
# We should check for updates for both nodes, including the sleeping one
# since the firmware check no longer requires device communication first.
assert client.async_send_command.call_count == 2
# Check calls were made for both nodes
call_args = [call[0][0] for call in client.async_send_command.call_args_list]
assert any(args["nodeId"] == 1 for args in call_args) # Controller node
assert any(args["nodeId"] == 94 for args in call_args) # zen_31 node
async def test_update_entity_dead(
@@ -1158,28 +1142,3 @@ async def test_update_entity_no_latest_version(
assert state.state == STATE_OFF
assert state.attributes[ATTR_SKIPPED_VERSION] is None
assert state.attributes[ATTR_LATEST_VERSION] == latest_version
async def test_update_entity_unload_asleep_node(
hass: HomeAssistant,
client: MagicMock,
wallmote_central_scene: Node,
integration: MockConfigEntry,
) -> None:
"""Test unloading config entry after attempting an update for an asleep node."""
config_entry = integration
assert client.async_send_command.call_count == 0
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"updates": []}
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
await hass.async_block_till_done()
# Once call completed for the (awake) controller node.
assert client.async_send_command.call_count == 1
assert len(wallmote_central_scene._listeners["wake up"]) == 1
await hass.config_entries.async_unload(config_entry.entry_id)
assert client.async_send_command.call_count == 1
assert len(wallmote_central_scene._listeners["wake up"]) == 0