Home Assistant Unofficial Reference 2024.12.1
update.py
Go to the documentation of this file.
1 """Representation of ZHA updates."""
2 
3 from __future__ import annotations
4 
5 import functools
6 import logging
7 from typing import Any
8 
9 from zha.exceptions import ZHAException
10 from zigpy.application import ControllerApplication
11 
13  UpdateDeviceClass,
14  UpdateEntity,
15  UpdateEntityFeature,
16 )
17 from homeassistant.config_entries import ConfigEntry
18 from homeassistant.const import Platform
19 from homeassistant.core import HomeAssistant
20 from homeassistant.exceptions import HomeAssistantError
21 from homeassistant.helpers.dispatcher import async_dispatcher_connect
22 from homeassistant.helpers.entity_platform import AddEntitiesCallback
24  CoordinatorEntity,
25  DataUpdateCoordinator,
26 )
27 
28 from .entity import ZHAEntity
29 from .helpers import (
30  SIGNAL_ADD_ENTITIES,
31  EntityData,
32  async_add_entities as zha_async_add_entities,
33  get_zha_data,
34  get_zha_gateway,
35 )
36 
37 _LOGGER = logging.getLogger(__name__)
38 
39 OTA_MESSAGE_BATTERY_POWERED = (
40  "Battery powered devices can sometimes take multiple hours to update and you may"
41  " need to wake the device for the update to begin."
42 )
43 
44 ZHA_DOCS_NETWORK_RELIABILITY = "https://www.home-assistant.io/integrations/zha/#zigbee-interference-avoidance-and-network-rangecoverage-optimization"
45 OTA_MESSAGE_RELIABILITY = (
46  "If you are having issues updating a specific device, make sure that you've"
47  f" eliminated [common environmental issues]({ZHA_DOCS_NETWORK_RELIABILITY}) that"
48  " could be affecting network reliability. OTA updates require a reliable network."
49 )
50 
51 
53  hass: HomeAssistant,
54  config_entry: ConfigEntry,
55  async_add_entities: AddEntitiesCallback,
56 ) -> None:
57  """Set up the Zigbee Home Automation update from config entry."""
58  zha_data = get_zha_data(hass)
59  if zha_data.update_coordinator is None:
60  zha_data.update_coordinator = ZHAFirmwareUpdateCoordinator(
61  hass, get_zha_gateway(hass).application_controller
62  )
63  entities_to_create = zha_data.platforms[Platform.UPDATE]
64 
66  hass,
67  SIGNAL_ADD_ENTITIES,
68  functools.partial(
69  zha_async_add_entities,
70  async_add_entities,
71  ZHAFirmwareUpdateEntity,
72  entities_to_create,
73  ),
74  )
75  config_entry.async_on_unload(unsub)
76 
77 
78 class ZHAFirmwareUpdateCoordinator(DataUpdateCoordinator[None]): # pylint: disable=hass-enforce-class-module
79  """Firmware update coordinator that broadcasts updates network-wide."""
80 
81  def __init__(
82  self, hass: HomeAssistant, controller_application: ControllerApplication
83  ) -> None:
84  """Initialize the coordinator."""
85  super().__init__(
86  hass,
87  _LOGGER,
88  name="ZHA firmware update coordinator",
89  update_method=self.async_update_dataasync_update_data,
90  )
91  self.controller_applicationcontroller_application = controller_application
92 
93  async def async_update_data(self) -> None:
94  """Fetch the latest firmware update data."""
95  # Broadcast to all devices
96  await self.controller_applicationcontroller_application.ota.broadcast_notify(jitter=100)
97 
98 
100  ZHAEntity, CoordinatorEntity[ZHAFirmwareUpdateCoordinator], UpdateEntity
101 ):
102  """Representation of a ZHA firmware update entity."""
103 
104  _attr_device_class = UpdateDeviceClass.FIRMWARE
105  _attr_supported_features = (
106  UpdateEntityFeature.INSTALL
107  | UpdateEntityFeature.PROGRESS
108  | UpdateEntityFeature.SPECIFIC_VERSION
109  | UpdateEntityFeature.RELEASE_NOTES
110  )
111  _attr_display_precision = 2 # 40 byte chunks with ~200KB files increments by 0.02%
112 
113  def __init__(self, entity_data: EntityData, **kwargs: Any) -> None:
114  """Initialize the ZHA siren."""
115  zha_data = get_zha_data(entity_data.device_proxy.gateway_proxy.hass)
116  assert zha_data.update_coordinator is not None
117 
118  super().__init__(entity_data, coordinator=zha_data.update_coordinator, **kwargs)
119  CoordinatorEntity.__init__(self, zha_data.update_coordinator)
120 
121  @property
122  def installed_version(self) -> str | None:
123  """Version installed and in use."""
124  return self.entity_data.entity.installed_version
125 
126  @property
127  def in_progress(self) -> bool | int | None:
128  """Update installation progress.
129 
130  Should return a boolean (True if in progress, False if not).
131  """
132  return self.entity_data.entity.in_progress
133 
134  @property
135  def update_percentage(self) -> int | float | None:
136  """Update installation progress.
137 
138  Needs UpdateEntityFeature.PROGRESS flag to be set for it to be used.
139 
140  Can either return a number to indicate the progress from 0 to 100% or None.
141  """
142  return self.entity_data.entity.update_percentage
143 
144  @property
145  def latest_version(self) -> str | None:
146  """Latest version available for install."""
147  return self.entity_data.entity.latest_version
148 
149  @property
150  def release_summary(self) -> str | None:
151  """Summary of the release notes or changelog.
152 
153  This is not suitable for long changelogs, but merely suitable
154  for a short excerpt update description of max 255 characters.
155  """
156  return self.entity_data.entity.release_summary
157 
158  async def async_release_notes(self) -> str | None:
159  """Return full release notes.
160 
161  This is suitable for a long changelog that does not fit in the release_summary
162  property. The returned string can contain markdown.
163  """
164 
165  if self.entity_data.device_proxy.device.is_mains_powered:
166  header = (
167  "<ha-alert alert-type='info'>"
168  f"{OTA_MESSAGE_RELIABILITY}"
169  "</ha-alert>"
170  )
171  else:
172  header = (
173  "<ha-alert alert-type='info'>"
174  f"{OTA_MESSAGE_BATTERY_POWERED} {OTA_MESSAGE_RELIABILITY}"
175  "</ha-alert>"
176  )
177 
178  return f"{header}\n\n{self.entity_data.entity.release_notes or ''}"
179 
180  @property
181  def release_url(self) -> str | None:
182  """URL to the full release notes of the latest version available."""
183  return self.entity_data.entity.release_url
184 
185  # We explicitly convert ZHA exceptions to HA exceptions here so there is no need to
186  # use the `@convert_zha_error_to_ha_error` decorator.
187  async def async_install(
188  self, version: str | None, backup: bool, **kwargs: Any
189  ) -> None:
190  """Install an update."""
191  try:
192  await self.entity_data.entity.async_install(version=version)
193  except ZHAException as exc:
194  raise HomeAssistantError(exc) from exc
195  finally:
196  self.async_write_ha_stateasync_write_ha_state()
197 
198  async def async_update(self) -> None:
199  """Update the entity."""
200  await CoordinatorEntity.async_update(self)
201  await super().async_update()
None __init__(self, HomeAssistant hass, ControllerApplication controller_application)
Definition: update.py:83
None async_install(self, str|None version, bool backup, **Any kwargs)
Definition: update.py:189
None __init__(self, EntityData entity_data, **Any kwargs)
Definition: update.py:113
Gateway get_zha_gateway(HomeAssistant hass)
Definition: helpers.py:1028
HAZHAData get_zha_data(HomeAssistant hass)
Definition: helpers.py:1020
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: update.py:56
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103