235 lines
7.3 KiB
Python
235 lines
7.3 KiB
Python
"""Update entities for Shelly devices."""
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
from dataclasses import dataclass
|
|
import logging
|
|
from typing import Any, Final, cast
|
|
|
|
from homeassistant.components.update import (
|
|
UpdateDeviceClass,
|
|
UpdateEntity,
|
|
UpdateEntityDescription,
|
|
UpdateEntityFeature,
|
|
)
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.entity import EntityCategory
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
|
|
from .const import CONF_SLEEP_PERIOD
|
|
from .coordinator import ShellyBlockCoordinator, ShellyRpcCoordinator, get_entry_data
|
|
from .entity import (
|
|
RestEntityDescription,
|
|
RpcEntityDescription,
|
|
ShellyRestAttributeEntity,
|
|
ShellyRpcAttributeEntity,
|
|
async_setup_entry_rest,
|
|
async_setup_entry_rpc,
|
|
)
|
|
from .utils import get_device_entry_gen
|
|
|
|
LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass
|
|
class RpcUpdateRequiredKeysMixin:
|
|
"""Class for RPC update required keys."""
|
|
|
|
latest_version: Callable[[dict], Any]
|
|
install: Callable
|
|
|
|
|
|
@dataclass
|
|
class RestUpdateRequiredKeysMixin:
|
|
"""Class for REST update required keys."""
|
|
|
|
latest_version: Callable[[dict], Any]
|
|
install: Callable
|
|
|
|
|
|
@dataclass
|
|
class RpcUpdateDescription(
|
|
RpcEntityDescription, UpdateEntityDescription, RpcUpdateRequiredKeysMixin
|
|
):
|
|
"""Class to describe a RPC update."""
|
|
|
|
|
|
@dataclass
|
|
class RestUpdateDescription(
|
|
RestEntityDescription, UpdateEntityDescription, RestUpdateRequiredKeysMixin
|
|
):
|
|
"""Class to describe a REST update."""
|
|
|
|
|
|
REST_UPDATES: Final = {
|
|
"fwupdate": RestUpdateDescription(
|
|
name="Firmware Update",
|
|
key="fwupdate",
|
|
latest_version=lambda status: status["update"]["new_version"],
|
|
install=lambda coordinator: coordinator.async_trigger_ota_update(),
|
|
device_class=UpdateDeviceClass.FIRMWARE,
|
|
entity_category=EntityCategory.CONFIG,
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
"fwupdate_beta": RestUpdateDescription(
|
|
name="Beta Firmware Update",
|
|
key="fwupdate",
|
|
latest_version=lambda status: status["update"].get("beta_version"),
|
|
install=lambda coordinator: coordinator.async_trigger_ota_update(beta=True),
|
|
device_class=UpdateDeviceClass.FIRMWARE,
|
|
entity_category=EntityCategory.CONFIG,
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
}
|
|
|
|
RPC_UPDATES: Final = {
|
|
"fwupdate": RpcUpdateDescription(
|
|
name="Firmware Update",
|
|
key="sys",
|
|
sub_key="available_updates",
|
|
latest_version=lambda status: status.get("stable", {"version": None})[
|
|
"version"
|
|
],
|
|
install=lambda coordinator: coordinator.async_trigger_ota_update(),
|
|
device_class=UpdateDeviceClass.FIRMWARE,
|
|
entity_category=EntityCategory.CONFIG,
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
"fwupdate_beta": RpcUpdateDescription(
|
|
name="Beta Firmware Update",
|
|
key="sys",
|
|
sub_key="available_updates",
|
|
latest_version=lambda status: status.get("beta", {"version": None})["version"],
|
|
install=lambda coordinator: coordinator.async_trigger_ota_update(beta=True),
|
|
device_class=UpdateDeviceClass.FIRMWARE,
|
|
entity_category=EntityCategory.CONFIG,
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
}
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
config_entry: ConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
"""Set up update entities for Shelly component."""
|
|
if get_device_entry_gen(config_entry) == 2:
|
|
return async_setup_entry_rpc(
|
|
hass, config_entry, async_add_entities, RPC_UPDATES, RpcUpdateEntity
|
|
)
|
|
|
|
if not config_entry.data[CONF_SLEEP_PERIOD]:
|
|
async_setup_entry_rest(
|
|
hass,
|
|
config_entry,
|
|
async_add_entities,
|
|
REST_UPDATES,
|
|
RestUpdateEntity,
|
|
)
|
|
|
|
|
|
class RestUpdateEntity(ShellyRestAttributeEntity, UpdateEntity):
|
|
"""Represent a REST update entity."""
|
|
|
|
_attr_supported_features = (
|
|
UpdateEntityFeature.INSTALL | UpdateEntityFeature.PROGRESS
|
|
)
|
|
entity_description: RestUpdateDescription
|
|
|
|
def __init__(
|
|
self,
|
|
block_coordinator: ShellyBlockCoordinator,
|
|
attribute: str,
|
|
description: RestEntityDescription,
|
|
) -> None:
|
|
"""Initialize update entity."""
|
|
super().__init__(block_coordinator, attribute, description)
|
|
self._in_progress_old_version: str | None = None
|
|
|
|
@property
|
|
def installed_version(self) -> str | None:
|
|
"""Version currently in use."""
|
|
version = self.block_coordinator.device.status["update"]["old_version"]
|
|
if version is None:
|
|
return None
|
|
|
|
return cast(str, version)
|
|
|
|
@property
|
|
def latest_version(self) -> str | None:
|
|
"""Latest version available for install."""
|
|
new_version = self.entity_description.latest_version(
|
|
self.block_coordinator.device.status,
|
|
)
|
|
if new_version not in (None, ""):
|
|
return cast(str, new_version)
|
|
|
|
return self.installed_version
|
|
|
|
@property
|
|
def in_progress(self) -> bool:
|
|
"""Update installation in progress."""
|
|
return self._in_progress_old_version == self.installed_version
|
|
|
|
async def async_install(
|
|
self, version: str | None, backup: bool, **kwargs: Any
|
|
) -> None:
|
|
"""Install the latest firmware version."""
|
|
config_entry = self.block_coordinator.entry
|
|
coordinator = get_entry_data(self.hass)[config_entry.entry_id].block
|
|
self._in_progress_old_version = self.installed_version
|
|
await self.entity_description.install(coordinator)
|
|
|
|
|
|
class RpcUpdateEntity(ShellyRpcAttributeEntity, UpdateEntity):
|
|
"""Represent a RPC update entity."""
|
|
|
|
_attr_supported_features = (
|
|
UpdateEntityFeature.INSTALL | UpdateEntityFeature.PROGRESS
|
|
)
|
|
entity_description: RpcUpdateDescription
|
|
|
|
def __init__(
|
|
self,
|
|
coordinator: ShellyRpcCoordinator,
|
|
key: str,
|
|
attribute: str,
|
|
description: RpcEntityDescription,
|
|
) -> None:
|
|
"""Initialize update entity."""
|
|
super().__init__(coordinator, key, attribute, description)
|
|
self._in_progress_old_version: str | None = None
|
|
|
|
@property
|
|
def installed_version(self) -> str | None:
|
|
"""Version currently in use."""
|
|
if self.coordinator.device.shelly is None:
|
|
return None
|
|
|
|
return cast(str, self.coordinator.device.shelly["ver"])
|
|
|
|
@property
|
|
def latest_version(self) -> str | None:
|
|
"""Latest version available for install."""
|
|
new_version = self.entity_description.latest_version(
|
|
self.coordinator.device.status[self.key][self.entity_description.sub_key],
|
|
)
|
|
if new_version is not None:
|
|
return cast(str, new_version)
|
|
|
|
return self.installed_version
|
|
|
|
@property
|
|
def in_progress(self) -> bool:
|
|
"""Update installation in progress."""
|
|
return self._in_progress_old_version == self.installed_version
|
|
|
|
async def async_install(
|
|
self, version: str | None, backup: bool, **kwargs: Any
|
|
) -> None:
|
|
"""Install the latest firmware version."""
|
|
self._in_progress_old_version = self.installed_version
|
|
await self.entity_description.install(self.coordinator)
|