Use async_release_notes in ESPHome update entity (#144440)

This commit is contained in:
Jesse Hills 2025-05-09 09:52:43 +12:00 committed by GitHub
parent 1322d54371
commit bdf4a21976
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 150 additions and 20 deletions

View File

@ -134,6 +134,22 @@ def esphome_state_property[_R, _EntityT: EsphomeEntity[Any, Any]](
return _wrapper
def async_esphome_state_property[_R, _EntityT: EsphomeEntity[Any, Any]](
func: Callable[[_EntityT], Awaitable[_R | None]],
) -> Callable[[_EntityT], Coroutine[Any, Any, _R | None]]:
"""Wrap a state property of an esphome entity.
This checks if the state object in the entity is set
and returns None if it is not set.
"""
@functools.wraps(func)
async def _wrapper(self: _EntityT) -> _R | None:
return await func(self) if self._has_state else None
return _wrapper
def esphome_float_state_property[_EntityT: EsphomeEntity[Any, Any]](
func: Callable[[_EntityT], float | None],
) -> Callable[[_EntityT], float | None]:

View File

@ -31,6 +31,7 @@ from .coordinator import ESPHomeDashboardCoordinator
from .dashboard import async_get_dashboard
from .entity import (
EsphomeEntity,
async_esphome_state_property,
convert_api_error_ha_error,
esphome_state_property,
platform_async_setup_entry,
@ -270,7 +271,9 @@ class ESPHomeUpdateEntity(EsphomeEntity[UpdateInfo, UpdateState], UpdateEntity):
"""A update implementation for esphome."""
_attr_supported_features = (
UpdateEntityFeature.INSTALL | UpdateEntityFeature.PROGRESS
UpdateEntityFeature.INSTALL
| UpdateEntityFeature.PROGRESS
| UpdateEntityFeature.RELEASE_NOTES
)
@callback
@ -300,11 +303,12 @@ class ESPHomeUpdateEntity(EsphomeEntity[UpdateInfo, UpdateState], UpdateEntity):
"""Return the latest version."""
return self._state.latest_version
@property
@esphome_state_property
def release_summary(self) -> str:
"""Return the release summary."""
return self._state.release_summary
@async_esphome_state_property
async def async_release_notes(self) -> str | None:
"""Return the release notes."""
if self._state.release_summary:
return self._state.release_summary
return None
@property
@esphome_state_property

View File

@ -13,6 +13,8 @@ from homeassistant.components.homeassistant import (
SERVICE_UPDATE_ENTITY,
)
from homeassistant.components.update import (
ATTR_IN_PROGRESS,
ATTR_UPDATE_PERCENTAGE,
DOMAIN as UPDATE_DOMAIN,
SERVICE_INSTALL,
UpdateEntityFeature,
@ -29,6 +31,12 @@ from homeassistant.exceptions import HomeAssistantError
from .conftest import MockESPHomeDeviceType, MockGenericDeviceEntryType
from tests.typing import WebSocketGenerator
RELEASE_SUMMARY = "This is a release summary"
RELEASE_URL = "https://esphome.io/changelog"
ENTITY_ID = "update.test_myupdate"
@pytest.fixture(autouse=True)
def enable_entity(entity_registry_enabled_by_default: None) -> None:
@ -461,8 +469,8 @@ async def test_generic_device_update_entity(
current_version="2024.6.0",
latest_version="2024.6.0",
title="ESPHome Project",
release_summary="This is a release summary",
release_url="https://esphome.io/changelog",
release_summary=RELEASE_SUMMARY,
release_url=RELEASE_URL,
)
]
user_service = []
@ -472,7 +480,7 @@ async def test_generic_device_update_entity(
user_service=user_service,
states=states,
)
state = hass.states.get("update.test_myupdate")
state = hass.states.get(ENTITY_ID)
assert state is not None
assert state.state == STATE_OFF
@ -497,8 +505,8 @@ async def test_generic_device_update_entity_has_update(
current_version="2024.6.0",
latest_version="2024.6.1",
title="ESPHome Project",
release_summary="This is a release summary",
release_url="https://esphome.io/changelog",
release_summary=RELEASE_SUMMARY,
release_url=RELEASE_URL,
)
]
user_service = []
@ -508,14 +516,14 @@ async def test_generic_device_update_entity_has_update(
user_service=user_service,
states=states,
)
state = hass.states.get("update.test_myupdate")
state = hass.states.get(ENTITY_ID)
assert state is not None
assert state.state == STATE_ON
await hass.services.async_call(
UPDATE_DOMAIN,
SERVICE_INSTALL,
{ATTR_ENTITY_ID: "update.test_myupdate"},
{ATTR_ENTITY_ID: ENTITY_ID},
blocking=True,
)
@ -528,27 +536,129 @@ async def test_generic_device_update_entity_has_update(
current_version="2024.6.0",
latest_version="2024.6.1",
title="ESPHome Project",
release_summary="This is a release summary",
release_url="https://esphome.io/changelog",
release_summary=RELEASE_SUMMARY,
release_url=RELEASE_URL,
)
)
state = hass.states.get("update.test_myupdate")
state = hass.states.get(ENTITY_ID)
assert state is not None
assert state.state == STATE_ON
assert state.attributes["in_progress"] is True
assert state.attributes["update_percentage"] == 50
assert state.attributes[ATTR_IN_PROGRESS] is True
assert state.attributes[ATTR_UPDATE_PERCENTAGE] == 50
await hass.services.async_call(
HOMEASSISTANT_DOMAIN,
SERVICE_UPDATE_ENTITY,
{ATTR_ENTITY_ID: "update.test_myupdate"},
{ATTR_ENTITY_ID: ENTITY_ID},
blocking=True,
)
mock_device.set_state(
UpdateState(
key=1,
in_progress=True,
has_progress=False,
current_version="2024.6.0",
latest_version="2024.6.1",
title="ESPHome Project",
release_summary=RELEASE_SUMMARY,
release_url=RELEASE_URL,
)
)
state = hass.states.get(ENTITY_ID)
assert state is not None
assert state.state == STATE_ON
assert state.attributes[ATTR_IN_PROGRESS] is True
assert state.attributes[ATTR_UPDATE_PERCENTAGE] is None
mock_client.update_command.assert_called_with(key=1, command=UpdateCommand.CHECK)
async def test_update_entity_release_notes(
hass: HomeAssistant,
mock_client: APIClient,
mock_esphome_device: MockESPHomeDeviceType,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test ESPHome update entity release notes."""
entity_info = [
UpdateInfo(
object_id="myupdate",
key=1,
name="my update",
unique_id="my_update",
)
]
user_service = []
mock_device = await mock_esphome_device(
mock_client=mock_client,
entity_info=entity_info,
user_service=user_service,
states=[],
)
# release notes
client = await hass_ws_client(hass)
await hass.async_block_till_done()
await client.send_json(
{
"id": 1,
"type": "update/release_notes",
"entity_id": ENTITY_ID,
}
)
result = await client.receive_json()
assert result["result"] is None
mock_device.set_state(
UpdateState(
key=1,
current_version="2024.6.0",
latest_version="2024.6.1",
title="ESPHome Project",
release_summary="",
release_url=RELEASE_URL,
)
)
await client.send_json(
{
"id": 2,
"type": "update/release_notes",
"entity_id": ENTITY_ID,
}
)
result = await client.receive_json()
assert result["result"] is None
mock_device.set_state(
UpdateState(
key=1,
current_version="2024.6.0",
latest_version="2024.6.1",
title="ESPHome Project",
release_summary=RELEASE_SUMMARY,
release_url=RELEASE_URL,
)
)
await client.send_json(
{
"id": 3,
"type": "update/release_notes",
"entity_id": ENTITY_ID,
}
)
result = await client.receive_json()
assert result["result"] == RELEASE_SUMMARY
async def test_attempt_to_update_twice(
hass: HomeAssistant,
mock_client: APIClient,