mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 07:37:34 +00:00
Add Shelly Gen2 update entity for sleeping devices (#86837)
This commit is contained in:
parent
d22e670334
commit
857df05308
@ -74,6 +74,7 @@ RPC_PLATFORMS: Final = [
|
|||||||
RPC_SLEEPING_PLATFORMS: Final = [
|
RPC_SLEEPING_PLATFORMS: Final = [
|
||||||
Platform.BINARY_SENSOR,
|
Platform.BINARY_SENSOR,
|
||||||
Platform.SENSOR,
|
Platform.SENSOR,
|
||||||
|
Platform.UPDATE,
|
||||||
]
|
]
|
||||||
|
|
||||||
COAP_SCHEMA: Final = vol.Schema(
|
COAP_SCHEMA: Final = vol.Schema(
|
||||||
|
@ -9,6 +9,8 @@ from typing import Any, Final, cast
|
|||||||
from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError, RpcCallError
|
from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError, RpcCallError
|
||||||
|
|
||||||
from homeassistant.components.update import (
|
from homeassistant.components.update import (
|
||||||
|
ATTR_INSTALLED_VERSION,
|
||||||
|
ATTR_LATEST_VERSION,
|
||||||
UpdateDeviceClass,
|
UpdateDeviceClass,
|
||||||
UpdateEntity,
|
UpdateEntity,
|
||||||
UpdateEntityDescription,
|
UpdateEntityDescription,
|
||||||
@ -27,6 +29,7 @@ from .entity import (
|
|||||||
RpcEntityDescription,
|
RpcEntityDescription,
|
||||||
ShellyRestAttributeEntity,
|
ShellyRestAttributeEntity,
|
||||||
ShellyRpcAttributeEntity,
|
ShellyRpcAttributeEntity,
|
||||||
|
ShellySleepingRpcAttributeEntity,
|
||||||
async_setup_entry_rest,
|
async_setup_entry_rest,
|
||||||
async_setup_entry_rpc,
|
async_setup_entry_rpc,
|
||||||
)
|
)
|
||||||
@ -95,7 +98,6 @@ RPC_UPDATES: Final = {
|
|||||||
beta=False,
|
beta=False,
|
||||||
device_class=UpdateDeviceClass.FIRMWARE,
|
device_class=UpdateDeviceClass.FIRMWARE,
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
),
|
),
|
||||||
"fwupdate_beta": RpcUpdateDescription(
|
"fwupdate_beta": RpcUpdateDescription(
|
||||||
name="Beta firmware update",
|
name="Beta firmware update",
|
||||||
@ -117,9 +119,19 @@ async def async_setup_entry(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Set up update entities for Shelly component."""
|
"""Set up update entities for Shelly component."""
|
||||||
if get_device_entry_gen(config_entry) == 2:
|
if get_device_entry_gen(config_entry) == 2:
|
||||||
return async_setup_entry_rpc(
|
if config_entry.data[CONF_SLEEP_PERIOD]:
|
||||||
hass, config_entry, async_add_entities, RPC_UPDATES, RpcUpdateEntity
|
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]:
|
if not config_entry.data[CONF_SLEEP_PERIOD]:
|
||||||
async_setup_entry_rest(
|
async_setup_entry_rest(
|
||||||
@ -268,3 +280,35 @@ class RpcUpdateEntity(ShellyRpcAttributeEntity, UpdateEntity):
|
|||||||
self.coordinator.entry.async_start_reauth(self.hass)
|
self.coordinator.entry.async_start_reauth(self.hass)
|
||||||
else:
|
else:
|
||||||
LOGGER.debug("OTA update call successful")
|
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__ = [
|
__all__ = [
|
||||||
"ATTR_BACKUP",
|
"ATTR_BACKUP",
|
||||||
|
"ATTR_INSTALLED_VERSION",
|
||||||
|
"ATTR_LATEST_VERSION",
|
||||||
"ATTR_VERSION",
|
"ATTR_VERSION",
|
||||||
"DEVICE_CLASSES_SCHEMA",
|
"DEVICE_CLASSES_SCHEMA",
|
||||||
"DOMAIN",
|
"DOMAIN",
|
||||||
|
@ -11,14 +11,29 @@ from homeassistant.components.update import (
|
|||||||
ATTR_LATEST_VERSION,
|
ATTR_LATEST_VERSION,
|
||||||
DOMAIN as UPDATE_DOMAIN,
|
DOMAIN as UPDATE_DOMAIN,
|
||||||
SERVICE_INSTALL,
|
SERVICE_INSTALL,
|
||||||
|
UpdateEntityFeature,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON
|
from homeassistant.const import (
|
||||||
from homeassistant.core import HomeAssistant
|
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.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.entity_registry import async_get
|
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):
|
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_INSTALLED_VERSION] == "1"
|
||||||
assert state.attributes[ATTR_LATEST_VERSION] == "2"
|
assert state.attributes[ATTR_LATEST_VERSION] == "2"
|
||||||
assert state.attributes[ATTR_IN_PROGRESS] is False
|
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(
|
await hass.services.async_call(
|
||||||
UPDATE_DOMAIN,
|
UPDATE_DOMAIN,
|
||||||
@ -196,14 +213,6 @@ async def test_block_update_auth_error(
|
|||||||
|
|
||||||
async def test_rpc_update(hass: HomeAssistant, mock_rpc_device, monkeypatch):
|
async def test_rpc_update(hass: HomeAssistant, mock_rpc_device, monkeypatch):
|
||||||
"""Test RPC device update entity."""
|
"""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.shelly, "ver", "1")
|
||||||
monkeypatch.setitem(
|
monkeypatch.setitem(
|
||||||
mock_rpc_device.status["sys"],
|
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_INSTALLED_VERSION] == "1"
|
||||||
assert state.attributes[ATTR_LATEST_VERSION] == "2"
|
assert state.attributes[ATTR_LATEST_VERSION] == "2"
|
||||||
assert state.attributes[ATTR_IN_PROGRESS] is False
|
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(
|
await hass.services.async_call(
|
||||||
UPDATE_DOMAIN,
|
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
|
assert state.attributes[ATTR_IN_PROGRESS] is True
|
||||||
|
|
||||||
monkeypatch.setitem(mock_rpc_device.shelly, "ver", "2")
|
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")
|
state = hass.states.get("update.test_name_firmware_update")
|
||||||
assert state.state == STATE_OFF
|
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
|
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):
|
async def test_rpc_beta_update(hass: HomeAssistant, mock_rpc_device, monkeypatch):
|
||||||
"""Test RPC device beta update entity."""
|
"""Test RPC device beta update entity."""
|
||||||
entity_registry = async_get(hass)
|
entity_registry = async_get(hass)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user