1 """Update entities for Shelly devices."""
3 from __future__
import annotations
5 from collections.abc
import Callable
6 from dataclasses
import dataclass
8 from typing
import Any, Final, cast
10 from aioshelly.const
import RPC_GENERATIONS
11 from aioshelly.exceptions
import DeviceConnectionError, InvalidAuthError, RpcCallError
12 from awesomeversion
import AwesomeVersion, AwesomeVersionStrategy
15 ATTR_INSTALLED_VERSION,
19 UpdateEntityDescription,
28 from .const
import CONF_SLEEP_PERIOD, OTA_BEGIN, OTA_ERROR, OTA_PROGRESS, OTA_SUCCESS
29 from .coordinator
import ShellyBlockCoordinator, ShellyConfigEntry, ShellyRpcCoordinator
31 RestEntityDescription,
33 ShellyRestAttributeEntity,
34 ShellyRpcAttributeEntity,
35 ShellySleepingRpcAttributeEntity,
36 async_setup_entry_rest,
37 async_setup_entry_rpc,
39 from .utils
import get_device_entry_gen, get_release_url
41 LOGGER = logging.getLogger(__name__)
44 @dataclass(frozen=True, kw_only=True)
46 """Class to describe a RPC update."""
48 latest_version: Callable[[dict], Any]
52 @dataclass(frozen=True, kw_only=True)
54 """Class to describe a REST update."""
56 latest_version: Callable[[dict], Any]
60 REST_UPDATES: Final = {
64 latest_version=
lambda status: status[
"update"][
"new_version"],
66 device_class=UpdateDeviceClass.FIRMWARE,
67 entity_category=EntityCategory.CONFIG,
68 entity_registry_enabled_default=
False,
73 latest_version=
lambda status: status[
"update"].
get(
"beta_version"),
75 device_class=UpdateDeviceClass.FIRMWARE,
76 entity_category=EntityCategory.CONFIG,
77 entity_registry_enabled_default=
False,
81 RPC_UPDATES: Final = {
85 sub_key=
"available_updates",
86 latest_version=
lambda status: status.get(
"stable", {
"version":
""})[
"version"],
88 device_class=UpdateDeviceClass.FIRMWARE,
89 entity_category=EntityCategory.CONFIG,
94 sub_key=
"available_updates",
95 latest_version=
lambda status: status.get(
"beta", {
"version":
""})[
"version"],
97 device_class=UpdateDeviceClass.FIRMWARE,
98 entity_category=EntityCategory.CONFIG,
99 entity_registry_enabled_default=
False,
106 config_entry: ShellyConfigEntry,
107 async_add_entities: AddEntitiesCallback,
109 """Set up update entities for Shelly component."""
111 if config_entry.data[CONF_SLEEP_PERIOD]:
117 RpcSleepingUpdateEntity,
121 hass, config_entry, async_add_entities, RPC_UPDATES, RpcUpdateEntity
125 if not config_entry.data[CONF_SLEEP_PERIOD]:
136 """Represent a REST update entity."""
138 _attr_supported_features = (
139 UpdateEntityFeature.INSTALL | UpdateEntityFeature.PROGRESS
141 entity_description: RestUpdateDescription
145 block_coordinator: ShellyBlockCoordinator,
147 description: RestUpdateDescription,
149 """Initialize update entity."""
150 super().
__init__(block_coordinator, attribute, description)
152 block_coordinator.device.gen,
153 block_coordinator.model,
160 """Version currently in use."""
161 return cast(str, self.
block_coordinatorblock_coordinator.device.status[
"update"][
"old_version"])
165 """Latest version available for install."""
170 return cast(str, new_version)
176 """Update installation in progress."""
180 self, version: str |
None, backup: bool, **kwargs: Any
182 """Install the latest firmware version."""
185 update_data = self.coordinator.device.status[
"update"]
186 LOGGER.debug(
"OTA update service - update_data: %s", update_data)
188 new_version = update_data[
"new_version"]
190 new_version = update_data[
"beta_version"]
193 "Starting OTA update of device %s from '%s' to '%s'",
195 self.coordinator.device.firmware_version,
199 result = await self.coordinator.device.trigger_ota_update(beta=beta)
200 except DeviceConnectionError
as err:
202 except InvalidAuthError:
205 LOGGER.debug(
"Result of OTA update call: %s", result)
208 """Return True if available version is newer then installed version.
210 Default strategy generate an exception with Shelly firmware format
211 thus making the entity state always true.
213 return AwesomeVersion(
215 find_first_match=
True,
216 ensure_strategy=[AwesomeVersionStrategy.SEMVER],
219 find_first_match=
True,
220 ensure_strategy=[AwesomeVersionStrategy.SEMVER],
225 """Represent a RPC update entity."""
227 _attr_supported_features = (
228 UpdateEntityFeature.INSTALL | UpdateEntityFeature.PROGRESS
230 entity_description: RpcUpdateDescription
234 coordinator: ShellyRpcCoordinator,
237 description: RpcUpdateDescription,
239 """Initialize update entity."""
240 super().
__init__(coordinator, key, attribute, description)
244 coordinator.device.gen, coordinator.model, description.beta
248 """When entity is added to hass."""
256 """Handle device OTA progress."""
258 event_type = event[
"event"]
259 if event_type == OTA_BEGIN:
261 elif event_type == OTA_PROGRESS:
263 elif event_type
in (OTA_ERROR, OTA_SUCCESS):
270 """Version currently in use."""
271 return cast(str, self.coordinator.device.shelly[
"ver"])
275 """Latest version available for install."""
278 return cast(str, new_version)
284 """Update installation in progress."""
289 """Update installation progress."""
293 self, version: str |
None, backup: bool, **kwargs: Any
295 """Install the latest firmware version."""
297 update_data = self.coordinator.device.status[
"sys"][
"available_updates"]
298 LOGGER.debug(
"OTA update service - update_data: %s", update_data)
300 new_version = update_data.get(
"stable", {
"version":
""})[
"version"]
302 new_version = update_data.get(
"beta", {
"version":
""})[
"version"]
305 "Starting OTA update of device %s from '%s' to '%s'",
306 self.coordinator.name,
307 self.coordinator.device.firmware_version,
311 await self.coordinator.device.trigger_ota_update(beta=beta)
312 except DeviceConnectionError
as err:
314 except RpcCallError
as err:
316 except InvalidAuthError:
321 LOGGER.debug(
"OTA update call for %s successful", self.coordinator.name)
325 ShellySleepingRpcAttributeEntity, UpdateEntity, RestoreEntity
327 """Represent a RPC sleeping update entity."""
329 entity_description: RpcUpdateDescription
332 """Handle entity which will be added."""
338 """Version currently in use."""
340 return cast(str, self.
coordinatorcoordinator.device.shelly[
"ver"])
345 return self.
last_statelast_state.attributes.get(ATTR_INSTALLED_VERSION)
349 """Latest version available for install."""
353 return cast(str, new_version)
360 return self.
last_statelast_state.attributes.get(ATTR_LATEST_VERSION)
364 """URL to the full release notes."""
365 if not self.
coordinatorcoordinator.device.initialized:
CALLBACK_TYPE async_subscribe_ota_events(self, Callable[[dict[str, Any]], None] ota_event_callback)
None __init__(self, ShellyBlockCoordinator block_coordinator, str attribute, RestUpdateDescription description)
str|None installed_version(self)
str|None latest_version(self)
None async_install(self, str|None version, bool backup, **Any kwargs)
bool version_is_newer(self, str latest_version, str installed_version)
str|None release_url(self)
str|None installed_version(self)
str|None latest_version(self)
None async_added_to_hass(self)
str|None installed_version(self)
None async_install(self, str|None version, bool backup, **Any kwargs)
str|None latest_version(self)
None _ota_progress_callback(self, dict[str, Any] event)
int|None update_percentage(self)
None __init__(self, ShellyRpcCoordinator coordinator, str key, str attribute, RpcUpdateDescription description)
None async_added_to_hass(self)
str|None installed_version(self)
bool|int|None in_progress(self)
None async_write_ha_state(self)
None async_on_remove(self, CALLBACK_TYPE func)
str|UndefinedType|None name(self)
State|None async_get_last_state(self)
web.Response get(self, web.Request request, str config_key)
None async_shutdown_device_and_start_reauth(self)
None async_setup_entry_rpc(HomeAssistant hass, ShellyConfigEntry config_entry, AddEntitiesCallback async_add_entities, Mapping[str, RpcEntityDescription] sensors, Callable sensor_class)
None async_setup_entry_rest(HomeAssistant hass, ShellyConfigEntry config_entry, AddEntitiesCallback async_add_entities, Mapping[str, RestEntityDescription] sensors, Callable sensor_class)
None async_setup_entry(HomeAssistant hass, ShellyConfigEntry config_entry, AddEntitiesCallback async_add_entities)
int get_device_entry_gen(ConfigEntry entry)
str|None get_release_url(int gen, str model, bool beta)