Home Assistant Unofficial Reference 2024.12.1
update.py
Go to the documentation of this file.
1 """Support for TPLink Omada device firmware updates."""
2 
3 from __future__ import annotations
4 
5 from datetime import timedelta
6 from typing import Any, NamedTuple
7 
8 from tplink_omada_client import OmadaSiteClient
9 from tplink_omada_client.devices import OmadaFirmwareUpdate, OmadaListDevice
10 from tplink_omada_client.exceptions import OmadaClientException, RequestFailed
11 
13  UpdateDeviceClass,
14  UpdateEntity,
15  UpdateEntityFeature,
16 )
17 from homeassistant.core import HomeAssistant, callback
18 from homeassistant.exceptions import HomeAssistantError
19 from homeassistant.helpers.entity_platform import AddEntitiesCallback
20 
21 from . import OmadaConfigEntry
22 from .coordinator import POLL_DEVICES, OmadaCoordinator, OmadaDevicesCoordinator
23 from .entity import OmadaDeviceEntity
24 
25 POLL_DELAY_UPGRADE = 60
26 
27 
28 class FirmwareUpdateStatus(NamedTuple):
29  """Firmware update information for Omada SDN devices."""
30 
31  device: OmadaListDevice
32  firmware: OmadaFirmwareUpdate | None
33 
34 
35 class OmadaFirmwareUpdateCoordinator(OmadaCoordinator[FirmwareUpdateStatus]): # pylint: disable=hass-enforce-class-module
36  """Coordinator for getting details about available firmware updates for Omada devices."""
37 
38  def __init__(
39  self,
40  hass: HomeAssistant,
41  config_entry: OmadaConfigEntry,
42  omada_client: OmadaSiteClient,
43  devices_coordinator: OmadaDevicesCoordinator,
44  ) -> None:
45  """Initialize my coordinator."""
46  super().__init__(hass, omada_client, "Firmware Updates", poll_delay=None)
47 
48  self._devices_coordinator_devices_coordinator = devices_coordinator
49  self._config_entry_config_entry = config_entry
50 
51  config_entry.async_on_unload(
52  devices_coordinator.async_add_listener(self._handle_devices_update_handle_devices_update)
53  )
54 
55  async def _get_firmware_updates(self) -> list[FirmwareUpdateStatus]:
56  devices = self._devices_coordinator_devices_coordinator.data.values()
57 
58  updates = [
60  device=d,
61  firmware=None
62  if not d.need_upgrade
63  else await self.omada_clientomada_client.get_firmware_details(d),
64  )
65  for d in devices
66  ]
67 
68  # During a firmware upgrade, poll device list more frequently
69  self._devices_coordinator_devices_coordinator.update_interval = timedelta(
70  seconds=(
71  POLL_DELAY_UPGRADE
72  if any(u.device.fw_download for u in updates)
73  else POLL_DEVICES
74  )
75  )
76  return updates
77 
78  async def poll_update(self) -> dict[str, FirmwareUpdateStatus]:
79  """Poll the state of Omada Devices firmware update availability."""
80  return {d.device.mac: d for d in await self._get_firmware_updates_get_firmware_updates()}
81 
82  @callback
83  def _handle_devices_update(self) -> None:
84  """Handle updated data from the devices coordinator."""
85  # Trigger a refresh of our data, based on the updated device list
86  self._config_entry_config_entry.async_create_background_task(
87  self.hasshass, self.async_request_refreshasync_request_refresh(), "Omada Firmware Update Refresh"
88  )
89 
90 
92  hass: HomeAssistant,
93  config_entry: OmadaConfigEntry,
94  async_add_entities: AddEntitiesCallback,
95 ) -> None:
96  """Set up switches."""
97  controller = config_entry.runtime_data
98 
99  devices = controller.devices_coordinator.data
100 
101  coordinator = OmadaFirmwareUpdateCoordinator(
102  hass, config_entry, controller.omada_client, controller.devices_coordinator
103  )
104 
106  OmadaDeviceUpdate(coordinator, device) for device in devices.values()
107  )
108  await coordinator.async_request_refresh()
109 
110 
112  OmadaDeviceEntity[OmadaFirmwareUpdateCoordinator],
113  UpdateEntity,
114 ):
115  """Firmware update status for Omada SDN devices."""
116 
117  _attr_supported_features = (
118  UpdateEntityFeature.INSTALL
119  | UpdateEntityFeature.PROGRESS
120  | UpdateEntityFeature.RELEASE_NOTES
121  )
122  _attr_device_class = UpdateDeviceClass.FIRMWARE
123 
124  def __init__(
125  self,
126  coordinator: OmadaFirmwareUpdateCoordinator,
127  device: OmadaListDevice,
128  ) -> None:
129  """Initialize the update entity."""
130  super().__init__(coordinator, device)
131 
132  self._mac_mac = device.mac
133  self._omada_client_omada_client = coordinator.omada_client
134 
135  self._attr_unique_id_attr_unique_id = f"{device.mac}_firmware"
136 
137  def release_notes(self) -> str | None:
138  """Get the release notes for the latest update."""
139  status = self.coordinator.data[self._mac_mac]
140  if status.firmware:
141  return status.firmware.release_notes
142  return None
143 
144  async def async_install(
145  self, version: str | None, backup: bool, **kwargs: Any
146  ) -> None:
147  """Install a firmware update."""
148  try:
149  await self._omada_client_omada_client.start_firmware_upgrade(
150  self.coordinator.data[self._mac_mac].device
151  )
152  except RequestFailed as ex:
153  raise HomeAssistantError("Firmware update request rejected") from ex
154  except OmadaClientException as ex:
155  raise HomeAssistantError(
156  "Unable to send Firmware update request. Check the controller is online."
157  ) from ex
158  finally:
159  await self.coordinator.async_request_refresh()
160 
161  @callback
162  def _handle_coordinator_update(self) -> None:
163  """Handle updated data from the coordinator."""
164  status = self.coordinator.data[self._mac_mac]
165 
166  if status.firmware and status.device.need_upgrade:
167  self._attr_installed_version_attr_installed_version = status.firmware.current_version
168  self._attr_latest_version_attr_latest_version = status.firmware.latest_version
169  else:
170  self._attr_installed_version_attr_installed_version = status.device.firmware_version
171  self._attr_latest_version_attr_latest_version = status.device.firmware_version
172  self._attr_in_progress_attr_in_progress_attr_in_progress = status.device.fw_download
173 
174  self.async_write_ha_stateasync_write_ha_state()