1 """Representation of Z-Wave updates."""
3 from __future__
import annotations
6 from collections
import Counter
7 from collections.abc
import Callable
8 from dataclasses
import dataclass
9 from datetime
import datetime, timedelta
10 from typing
import Any, Final
12 from awesomeversion
import AwesomeVersion
13 from zwave_js_server.client
import Client
as ZwaveClient
14 from zwave_js_server.const
import NodeStatus
15 from zwave_js_server.exceptions
import BaseZwaveJSServerError, FailedZWaveCommand
16 from zwave_js_server.model.driver
import Driver
17 from zwave_js_server.model.node
import Node
as ZwaveNode
18 from zwave_js_server.model.node.firmware
import (
19 NodeFirmwareUpdateInfo,
20 NodeFirmwareUpdateProgress,
21 NodeFirmwareUpdateResult,
39 from .const
import API_KEY_FIRMWARE_UPDATE_SERVICE, DATA_CLIENT, DOMAIN, LOGGER
40 from .helpers
import get_device_info, get_valueless_base_unique_id
44 UPDATE_DELAY_STRING =
"delay"
45 UPDATE_DELAY_INTERVAL = 5
46 ATTR_LATEST_VERSION_FIRMWARE =
"latest_version_firmware"
51 """Extra stored data for Z-Wave node firmware update entity."""
53 latest_version_firmware: NodeFirmwareUpdateInfo |
None
56 """Return a dict representation of the extra data."""
58 ATTR_LATEST_VERSION_FIRMWARE: self.latest_version_firmware.to_dict()
59 if self.latest_version_firmware
64 def from_dict(cls, data: dict[str, Any]) -> ZWaveNodeFirmwareUpdateExtraStoredData:
65 """Initialize the extra data from a dict."""
69 not (firmware_dict := data[ATTR_LATEST_VERSION_FIRMWARE])
70 or "normalizedVersion" not in firmware_dict
74 return cls(NodeFirmwareUpdateInfo.from_dict(firmware_dict))
79 config_entry: ConfigEntry,
80 async_add_entities: AddEntitiesCallback,
82 """Set up Z-Wave update entity from config entry."""
83 client: ZwaveClient = config_entry.runtime_data[DATA_CLIENT]
84 cnt: Counter = Counter()
87 def async_add_firmware_update_entity(node: ZwaveNode) ->
None:
88 """Add firmware update entity."""
92 cnt[UPDATE_DELAY_STRING] += 1
93 delay =
timedelta(minutes=(cnt[UPDATE_DELAY_STRING] * UPDATE_DELAY_INTERVAL))
94 driver = client.driver
95 assert driver
is not None
98 config_entry.async_on_unload(
101 f
"{DOMAIN}_{config_entry.entry_id}_add_firmware_update_entity",
102 async_add_firmware_update_entity,
108 """Representation of a firmware update entity."""
110 _attr_entity_category = EntityCategory.CONFIG
111 _attr_device_class = UpdateDeviceClass.FIRMWARE
112 _attr_supported_features = (
113 UpdateEntityFeature.INSTALL
114 | UpdateEntityFeature.RELEASE_NOTES
115 | UpdateEntityFeature.PROGRESS
117 _attr_has_entity_name =
True
118 _attr_should_poll =
False
120 def __init__(self, driver: Driver, node: ZwaveNode, delay: timedelta) ->
None:
121 """Initialize a Z-Wave device firmware update entity."""
125 self.
_status_unsub_status_unsub: Callable[[],
None] |
None =
None
126 self.
_poll_unsub_poll_unsub: Callable[[],
None] |
None =
None
130 self.
_result_result: NodeFirmwareUpdateResult |
None =
None
131 self._delay: Final[timedelta] = delay
143 """Return ZWave Node Firmware Update specific state data to be restored."""
148 """Update the entity when node is awake."""
154 """Update install progress on event."""
155 progress: NodeFirmwareUpdateProgress = event[
"firmware_update_progress"]
164 """Update install progress on event."""
165 result: NodeFirmwareUpdateResult = event[
"firmware_update_finished"]
171 self, write_state: bool =
True
173 """Unsubscribe from firmware events and reset update install progress."""
189 async
def _async_update(self, _: HomeAssistant | datetime |
None =
None) ->
None:
190 """Update the entity."""
197 if self.
hasshass.state
is not CoreState.running:
205 for status, event_name
in (
206 (NodeStatus.ASLEEP,
"wake up"),
207 (NodeStatus.DEAD,
"alive"),
209 if self.
nodenode.status == status:
219 available_firmware_updates = [
221 for update
in await self.
driverdriver.controller.async_get_available_firmware_updates(
222 self.
nodenode, API_KEY_FIRMWARE_UPDATE_SERVICE,
True
224 if update.channel ==
"stable"
226 except FailedZWaveCommand
as err:
228 "Failed to get firmware updates for node %s: %s",
229 self.
nodenode.node_id,
237 available_firmware_updates
239 latest_firmware :=
max(
240 available_firmware_updates,
241 key=
lambda x: AwesomeVersion(x.version),
244 and AwesomeVersion(latest_firmware.version)
245 > AwesomeVersion(self.
nodenode.firmware_version)
259 """Get release notes."""
265 self, version: str |
None, backup: bool, **kwargs: Any
267 """Install an update."""
283 await self.
driverdriver.controller.async_firmware_update_ota(self.
nodenode, firmware)
284 except BaseZwaveJSServerError
as err:
290 assert self.
_result_result
is not None
294 if not self.
_result_result.success:
295 error_msg = self.
_result_result.status.name.replace(
"_",
" ").
title()
310 "There is no value to refresh for this entity so the zwave_js.refresh_value"
311 " service won't work for it"
315 """Call when entity is added."""
319 f
"{DOMAIN}_{self.unique_id}_poll_value",
327 f
"{DOMAIN}_{self._base_unique_id}_remove_entity",
335 f
"{DOMAIN}_{self._base_unique_id}_remove_entity_on_interview_started",
342 latest_version =
None
347 and (latest_version := state.attributes.get(ATTR_LATEST_VERSION))
351 latest_version_firmware
352 := ZWaveNodeFirmwareUpdateExtraStoredData.from_dict(
354 ).latest_version_firmware
367 or not latest_version
378 """Call when entity will be removed."""
None async_will_remove_from_hass(self)
None _async_update(self, HomeAssistant|datetime|None _=None)
None async_poll_value(self, bool _)
str|None async_release_notes(self)
None async_install(self, str|None version, bool backup, **Any kwargs)
None _update_progress(self, dict[str, Any] event)
None __init__(self, Driver driver, ZwaveNode node, timedelta delay)
None _update_on_status_change(self, dict[str, Any] _)
None _update_finished(self, dict[str, Any] event)
None _unsub_firmware_events_and_reset_progress(self, bool write_state=True)
ZWaveNodeFirmwareUpdateExtraStoredData extra_restore_state_data(self)
None async_added_to_hass(self)
None async_write_ha_state(self)
None async_on_remove(self, CALLBACK_TYPE func)
None async_remove(self, *bool force_remove=False)
State|None async_get_last_state(self)
ExtraStoredData|None async_get_last_extra_data(self)
DeviceInfo get_device_info(str coordinates, str name)
str get_valueless_base_unique_id(Driver driver, ZwaveNode node)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
CALLBACK_TYPE async_call_later(HomeAssistant hass, float|timedelta delay, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action)