Add Shelly Gen2 update entity for sleeping devices (#86837)
This commit is contained in:
parent
d22e670334
commit
857df05308
4 changed files with 197 additions and 16 deletions
|
@ -74,6 +74,7 @@ RPC_PLATFORMS: Final = [
|
|||
RPC_SLEEPING_PLATFORMS: Final = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.SENSOR,
|
||||
Platform.UPDATE,
|
||||
]
|
||||
|
||||
COAP_SCHEMA: Final = vol.Schema(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue