Add Shelly Gen2 update entity for sleeping devices (#86837)

This commit is contained in:
Shay Levy 2023-01-30 19:05:13 +02:00 committed by GitHub
parent d22e670334
commit 857df05308
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 197 additions and 16 deletions

View file

@ -74,6 +74,7 @@ RPC_PLATFORMS: Final = [
RPC_SLEEPING_PLATFORMS: Final = [
Platform.BINARY_SENSOR,
Platform.SENSOR,
Platform.UPDATE,
]
COAP_SCHEMA: Final = vol.Schema(

View file

@ -9,6 +9,8 @@ from typing import Any, Final, cast
from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError, RpcCallError
from homeassistant.components.update import (
ATTR_INSTALLED_VERSION,
ATTR_LATEST_VERSION,
UpdateDeviceClass,
UpdateEntity,
UpdateEntityDescription,
@ -27,6 +29,7 @@ from .entity import (
RpcEntityDescription,
ShellyRestAttributeEntity,
ShellyRpcAttributeEntity,
ShellySleepingRpcAttributeEntity,
async_setup_entry_rest,
async_setup_entry_rpc,
)
@ -95,7 +98,6 @@ RPC_UPDATES: Final = {
beta=False,
device_class=UpdateDeviceClass.FIRMWARE,
entity_category=EntityCategory.CONFIG,
entity_registry_enabled_default=False,
),
"fwupdate_beta": RpcUpdateDescription(
name="Beta firmware update",
@ -117,9 +119,19 @@ async def async_setup_entry(
) -> 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 config_entry.data[CONF_SLEEP_PERIOD]:
async_setup_entry_rpc(
hass,
config_entry,
async_add_entities,
RPC_UPDATES,
RpcSleepingUpdateEntity,
)
else:
async_setup_entry_rpc(
hass, config_entry, async_add_entities, RPC_UPDATES, RpcUpdateEntity
)
return
if not config_entry.data[CONF_SLEEP_PERIOD]:
async_setup_entry_rest(
@ -268,3 +280,35 @@ class RpcUpdateEntity(ShellyRpcAttributeEntity, UpdateEntity):
self.coordinator.entry.async_start_reauth(self.hass)
else:
LOGGER.debug("OTA update call successful")
class RpcSleepingUpdateEntity(ShellySleepingRpcAttributeEntity, UpdateEntity):
"""Represent a RPC sleeping update entity."""
entity_description: RpcUpdateDescription
@property
def installed_version(self) -> str | None:
"""Version currently in use."""
if self.coordinator.device.initialized:
return cast(str, self.coordinator.device.shelly["ver"])
if self.last_state is None:
return None
return self.last_state.attributes.get(ATTR_INSTALLED_VERSION)
@property
def latest_version(self) -> str | None:
"""Latest version available for install."""
if self.coordinator.device.initialized:
new_version = self.entity_description.latest_version(self.sub_status)
if new_version:
return cast(str, new_version)
return self.installed_version
if self.last_state is None:
return None
return self.last_state.attributes.get(ATTR_LATEST_VERSION)

View file

@ -60,6 +60,8 @@ DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.Coerce(UpdateDeviceClass))
__all__ = [
"ATTR_BACKUP",
"ATTR_INSTALLED_VERSION",
"ATTR_LATEST_VERSION",
"ATTR_VERSION",
"DEVICE_CLASSES_SCHEMA",
"DOMAIN",

View file

@ -11,14 +11,29 @@ from homeassistant.components.update import (
ATTR_LATEST_VERSION,
DOMAIN as UPDATE_DOMAIN,
SERVICE_INSTALL,
UpdateEntityFeature,
)
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES,
STATE_OFF,
STATE_ON,
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant, State
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_registry import async_get
from . import MOCK_MAC, init_integration, mock_rest_update
from . import (
MOCK_MAC,
init_integration,
mock_rest_update,
register_device,
register_entity,
)
from tests.common import mock_restore_cache
async def test_block_update(hass: HomeAssistant, mock_block_device, monkeypatch):
@ -40,6 +55,8 @@ async def test_block_update(hass: HomeAssistant, mock_block_device, monkeypatch)
assert state.attributes[ATTR_INSTALLED_VERSION] == "1"
assert state.attributes[ATTR_LATEST_VERSION] == "2"
assert state.attributes[ATTR_IN_PROGRESS] is False
supported_feat = state.attributes[ATTR_SUPPORTED_FEATURES]
assert supported_feat == UpdateEntityFeature.INSTALL | UpdateEntityFeature.PROGRESS
await hass.services.async_call(
UPDATE_DOMAIN,
@ -196,14 +213,6 @@ async def test_block_update_auth_error(
async def test_rpc_update(hass: HomeAssistant, mock_rpc_device, monkeypatch):
"""Test RPC device update entity."""
entity_registry = async_get(hass)
entity_registry.async_get_or_create(
UPDATE_DOMAIN,
DOMAIN,
f"{MOCK_MAC}-sys-fwupdate",
suggested_object_id="test_name_firmware_update",
disabled_by=None,
)
monkeypatch.setitem(mock_rpc_device.shelly, "ver", "1")
monkeypatch.setitem(
mock_rpc_device.status["sys"],
@ -219,6 +228,8 @@ async def test_rpc_update(hass: HomeAssistant, mock_rpc_device, monkeypatch):
assert state.attributes[ATTR_INSTALLED_VERSION] == "1"
assert state.attributes[ATTR_LATEST_VERSION] == "2"
assert state.attributes[ATTR_IN_PROGRESS] is False
supported_feat = state.attributes[ATTR_SUPPORTED_FEATURES]
assert supported_feat == UpdateEntityFeature.INSTALL | UpdateEntityFeature.PROGRESS
await hass.services.async_call(
UPDATE_DOMAIN,
@ -235,7 +246,7 @@ async def test_rpc_update(hass: HomeAssistant, mock_rpc_device, monkeypatch):
assert state.attributes[ATTR_IN_PROGRESS] is True
monkeypatch.setitem(mock_rpc_device.shelly, "ver", "2")
await mock_rest_update(hass)
mock_rpc_device.mock_update()
state = hass.states.get("update.test_name_firmware_update")
assert state.state == STATE_OFF
@ -244,6 +255,129 @@ async def test_rpc_update(hass: HomeAssistant, mock_rpc_device, monkeypatch):
assert state.attributes[ATTR_IN_PROGRESS] is False
async def test_rpc_sleeping_update(hass: HomeAssistant, mock_rpc_device, monkeypatch):
"""Test RPC sleeping device update entity."""
monkeypatch.setitem(mock_rpc_device.shelly, "ver", "1")
monkeypatch.setitem(
mock_rpc_device.status["sys"],
"available_updates",
{
"stable": {"version": "2"},
},
)
entity_id = f"{UPDATE_DOMAIN}.test_name_firmware_update"
await init_integration(hass, 2, sleep_period=1000)
# Entity should be created when device is online
assert hass.states.get(entity_id) is None
# Make device online
mock_rpc_device.mock_update()
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state.state == STATE_ON
assert state.attributes[ATTR_INSTALLED_VERSION] == "1"
assert state.attributes[ATTR_LATEST_VERSION] == "2"
assert state.attributes[ATTR_IN_PROGRESS] is False
assert state.attributes[ATTR_SUPPORTED_FEATURES] == UpdateEntityFeature(0)
monkeypatch.setitem(mock_rpc_device.shelly, "ver", "2")
mock_rpc_device.mock_update()
state = hass.states.get(entity_id)
assert state.state == STATE_OFF
assert state.attributes[ATTR_INSTALLED_VERSION] == "2"
assert state.attributes[ATTR_LATEST_VERSION] == "2"
assert state.attributes[ATTR_IN_PROGRESS] is False
assert state.attributes[ATTR_SUPPORTED_FEATURES] == UpdateEntityFeature(0)
async def test_rpc_restored_sleeping_update(
hass, mock_rpc_device, device_reg, monkeypatch
):
"""Test RPC restored update entity."""
entry = await init_integration(hass, 2, sleep_period=1000, skip_setup=True)
register_device(device_reg, entry)
entity_id = register_entity(
hass,
UPDATE_DOMAIN,
"test_name_firmware_update",
"sys-fwupdate",
entry,
)
attr = {ATTR_INSTALLED_VERSION: "1", ATTR_LATEST_VERSION: "2"}
mock_restore_cache(hass, [State(entity_id, STATE_ON, attributes=attr)])
monkeypatch.setitem(mock_rpc_device.shelly, "ver", "2")
monkeypatch.setitem(mock_rpc_device.status["sys"], "available_updates", {})
monkeypatch.setattr(mock_rpc_device, "initialized", False)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state.state == STATE_ON
assert state.attributes[ATTR_INSTALLED_VERSION] == "1"
assert state.attributes[ATTR_LATEST_VERSION] == "2"
assert state.attributes[ATTR_IN_PROGRESS] is False
assert state.attributes[ATTR_SUPPORTED_FEATURES] == UpdateEntityFeature(0)
# Make device online
monkeypatch.setattr(mock_rpc_device, "initialized", True)
mock_rpc_device.mock_update()
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state.state == STATE_OFF
assert state.attributes[ATTR_INSTALLED_VERSION] == "2"
assert state.attributes[ATTR_LATEST_VERSION] == "2"
assert state.attributes[ATTR_IN_PROGRESS] is False
assert state.attributes[ATTR_SUPPORTED_FEATURES] == UpdateEntityFeature(0)
async def test_rpc_restored_sleeping_update_no_last_state(
hass, mock_rpc_device, device_reg, monkeypatch
):
"""Test RPC restored update entity missing last state."""
monkeypatch.setitem(mock_rpc_device.shelly, "ver", "1")
monkeypatch.setitem(
mock_rpc_device.status["sys"],
"available_updates",
{
"stable": {"version": "2"},
},
)
entry = await init_integration(hass, 2, sleep_period=1000, skip_setup=True)
register_device(device_reg, entry)
entity_id = register_entity(
hass,
UPDATE_DOMAIN,
"test_name_firmware_update",
"sys-fwupdate",
entry,
)
monkeypatch.setattr(mock_rpc_device, "initialized", False)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state.state == STATE_UNKNOWN
# Make device online
monkeypatch.setattr(mock_rpc_device, "initialized", True)
mock_rpc_device.mock_update()
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state.state == STATE_ON
assert state.attributes[ATTR_INSTALLED_VERSION] == "1"
assert state.attributes[ATTR_LATEST_VERSION] == "2"
assert state.attributes[ATTR_IN_PROGRESS] is False
assert state.attributes[ATTR_SUPPORTED_FEATURES] == UpdateEntityFeature(0)
async def test_rpc_beta_update(hass: HomeAssistant, mock_rpc_device, monkeypatch):
"""Test RPC device beta update entity."""
entity_registry = async_get(hass)