Home Assistant Unofficial Reference 2024.12.1
update.py
Go to the documentation of this file.
1 """Support updates for SLZB-06 ESP32 and Zigbee firmwares."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from collections.abc import Callable
7 from dataclasses import dataclass
8 from typing import Any, Final
9 
10 from pysmlight.const import Events as SmEvents
11 from pysmlight.models import Firmware, Info
12 from pysmlight.sse import MessageEvent
13 
15  UpdateDeviceClass,
16  UpdateEntity,
17  UpdateEntityDescription,
18  UpdateEntityFeature,
19 )
20 from homeassistant.const import EntityCategory
21 from homeassistant.core import HomeAssistant, callback
22 from homeassistant.exceptions import HomeAssistantError
23 from homeassistant.helpers.entity_platform import AddEntitiesCallback
24 
25 from . import SmConfigEntry
26 from .const import LOGGER
27 from .coordinator import SmFirmwareUpdateCoordinator, SmFwData
28 from .entity import SmEntity
29 
30 
31 @dataclass(frozen=True, kw_only=True)
33  """Describes SMLIGHT SLZB-06 update entity."""
34 
35  installed_version: Callable[[Info], str | None]
36  fw_list: Callable[[SmFwData], list[Firmware] | None]
37 
38 
39 UPDATE_ENTITIES: Final = [
41  key="core_update",
42  translation_key="core_update",
43  installed_version=lambda x: x.sw_version,
44  fw_list=lambda x: x.esp_firmware,
45  ),
47  key="zigbee_update",
48  translation_key="zigbee_update",
49  installed_version=lambda x: x.zb_version,
50  fw_list=lambda x: x.zb_firmware,
51  ),
52 ]
53 
54 
56  hass: HomeAssistant, entry: SmConfigEntry, async_add_entities: AddEntitiesCallback
57 ) -> None:
58  """Set up the SMLIGHT update entities."""
59  coordinator = entry.runtime_data.firmware
60 
62  SmUpdateEntity(coordinator, description) for description in UPDATE_ENTITIES
63  )
64 
65 
67  """Representation for SLZB-06 update entities."""
68 
69  coordinator: SmFirmwareUpdateCoordinator
70  entity_description: SmUpdateEntityDescription
71  _attr_entity_category = EntityCategory.CONFIG
72  _attr_device_class = UpdateDeviceClass.FIRMWARE
73  _attr_supported_features = (
74  UpdateEntityFeature.INSTALL
75  | UpdateEntityFeature.PROGRESS
76  | UpdateEntityFeature.RELEASE_NOTES
77  )
78 
79  def __init__(
80  self,
81  coordinator: SmFirmwareUpdateCoordinator,
82  description: SmUpdateEntityDescription,
83  ) -> None:
84  """Initialize the entity."""
85  super().__init__(coordinator)
86 
87  self.entity_descriptionentity_description = description
88  self._attr_unique_id_attr_unique_id = f"{coordinator.unique_id}-{description.key}"
89 
90  self._finished_event_finished_event = asyncio.Event()
91  self._firmware_firmware: Firmware | None = None
92  self._unload: list[Callable] = []
93 
94  @property
95  def installed_version(self) -> str | None:
96  """Version installed.."""
97  data = self.coordinator.data
98 
99  version = self.entity_descriptionentity_description.installed_version(data.info)
100  return version if version != "-1" else None
101 
102  @property
103  def latest_version(self) -> str | None:
104  """Latest version available for install."""
105  data = self.coordinator.data
106  if self.coordinator.legacy_api == 2:
107  return None
108 
109  fw = self.entity_descriptionentity_description.fw_list(data)
110 
111  if fw and self.entity_descriptionentity_description.key == "zigbee_update":
112  fw = [f for f in fw if f.type == data.info.zb_type]
113 
114  if fw:
115  self._firmware_firmware = fw[0]
116  return self._firmware_firmware.ver
117 
118  return None
119 
120  def register_callbacks(self) -> None:
121  """Register callbacks for SSE update events."""
122  self._unload.append(
123  self.coordinator.client.sse.register_callback(
124  SmEvents.ZB_FW_prgs, self._update_progress_update_progress
125  )
126  )
127  self._unload.append(
128  self.coordinator.client.sse.register_callback(
129  SmEvents.FW_UPD_done, self._update_finished_update_finished
130  )
131  )
132  if self.coordinator.legacy_api == 1:
133  self._unload.append(
134  self.coordinator.client.sse.register_callback(
135  SmEvents.ESP_UPD_done, self._update_finished_update_finished
136  )
137  )
138  self._unload.append(
139  self.coordinator.client.sse.register_callback(
140  SmEvents.ZB_FW_err, self._update_failed_update_failed
141  )
142  )
143 
144  def release_notes(self) -> str | None:
145  """Return release notes for firmware."""
146 
147  if self._firmware_firmware and self._firmware_firmware.notes:
148  return self._firmware_firmware.notes
149 
150  return None
151 
152  @callback
153  def _update_progress(self, progress: MessageEvent) -> None:
154  """Update install progress on event."""
155 
156  progress = int(progress.data)
157  self._attr_update_percentage_attr_update_percentage = progress
158  self.async_write_ha_stateasync_write_ha_state()
159 
160  def _update_done(self) -> None:
161  """Handle cleanup for update done."""
162  self._finished_event_finished_event.set()
163 
164  for remove_cb in self._unload:
165  remove_cb()
166  self._unload.clear()
167 
168  self._attr_in_progress_attr_in_progress_attr_in_progress = False
169  self._attr_update_percentage_attr_update_percentage = None
170  self.async_write_ha_stateasync_write_ha_state()
171 
172  @callback
173  def _update_finished(self, event: MessageEvent) -> None:
174  """Handle event for update finished."""
175 
176  self._update_done_update_done()
177 
178  @callback
179  def _update_failed(self, event: MessageEvent) -> None:
180  self._update_done_update_done()
181  self.coordinator.in_progress = False
182  raise HomeAssistantError(f"Update failed for {self.name}")
183 
184  async def async_install(
185  self, version: str | None, backup: bool, **kwargs: Any
186  ) -> None:
187  """Install firmware update."""
188 
189  if not self.coordinator.in_progress and self._firmware_firmware:
190  self.coordinator.in_progress = True
191  self._attr_in_progress_attr_in_progress_attr_in_progress = True
192  self._attr_update_percentage_attr_update_percentage = None
193  self.register_callbacksregister_callbacks()
194 
195  await self.coordinator.client.fw_update(self._firmware_firmware)
196 
197  # block until update finished event received
198  await self._finished_event_finished_event.wait()
199 
200  # allow time for SLZB-06 to reboot before updating coordinator data
201  try:
202  async with asyncio.timeout(180):
203  while (
204  self.coordinator.in_progress
205  and self.installed_versioninstalled_versioninstalled_versioninstalled_version != self._firmware_firmware.ver
206  ):
207  await self.coordinator.async_refresh()
208  await asyncio.sleep(1)
209  except TimeoutError:
210  LOGGER.warning(
211  "Timeout waiting for %s to reboot after update",
212  self.coordinator.data.info.hostname,
213  )
214 
215  self.coordinator.in_progress = False
216  self._finished_event_finished_event.clear()
None async_install(self, str|None version, bool backup, **Any kwargs)
Definition: update.py:186
None _update_finished(self, MessageEvent event)
Definition: update.py:173
None __init__(self, SmFirmwareUpdateCoordinator coordinator, SmUpdateEntityDescription description)
Definition: update.py:83
None _update_failed(self, MessageEvent event)
Definition: update.py:179
None _update_progress(self, MessageEvent progress)
Definition: update.py:153
None async_setup_entry(HomeAssistant hass, SmConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: update.py:57