1
0
mirror of https://github.com/home-assistant/core.git synced 2026-07-04 21:25:26 +01:00

Fix ESPHome update entity stuck on for project versions with build suffix (#172571)

This commit is contained in:
J. Nick Koston
2026-05-30 08:50:26 -05:00
committed by GitHub
parent 83c35b8b4d
commit 1ec5e25b6b
2 changed files with 137 additions and 0 deletions
@@ -284,6 +284,19 @@ class ESPHomeUpdateEntity(EsphomeEntity[UpdateInfo, UpdateState], UpdateEntity):
UpdateDeviceClass, static_info.device_class
)
def version_is_newer(self, latest_version: str, installed_version: str) -> bool:
"""Return True if latest_version is newer than installed_version.
ESPHome project versions can carry a build suffix (e.g.
2025.11.5_c51f7548) that AwesomeVersion cannot parse. Without stripping
it the base comparison raises and the entity is forced on for every
build mismatch. Drop the suffix so the versions compare cleanly and we
only report genuinely newer firmware.
"""
return super().version_is_newer(
latest_version.partition("_")[0], installed_version.partition("_")[0]
)
@property
@esphome_state_property
def installed_version(self) -> str:
+124
View File
@@ -5,6 +5,8 @@ from typing import Any
from unittest.mock import patch
from aioesphomeapi import APIClient, UpdateCommand, UpdateInfo, UpdateState
from awesomeversion import AwesomeVersion
from awesomeversion.exceptions import AwesomeVersionCompareException
import pytest
from homeassistant.components.esphome.dashboard import async_get_dashboard
@@ -547,6 +549,128 @@ async def test_generic_device_update_entity_has_update(
)
@pytest.mark.parametrize(
("current_version", "latest_version"),
[
("2025.11.5_c51f7548", "2025.11.6_aabbccdd"),
("2025.11.5_c51f7548", "2025.11.5_aabbccdd"),
("2025.11.6_aabbccdd", "2025.11.5_c51f7548"),
],
ids=["newer_base", "same_base_new_build", "older_base"],
)
def test_awesomeversion_cannot_compare_project_versions(
current_version: str, latest_version: str
) -> None:
"""Prove AwesomeVersion raises on ESPHome project versions.
ESPHome project versions carry a build suffix (e.g. 2025.11.5_c51f7548).
AwesomeVersion cannot parse these, so the base UpdateEntity comparison would
raise and force the entity on, which is why ESPHomeUpdateEntity mirrors the
device by comparing with a plain string inequality instead.
"""
with pytest.raises(AwesomeVersionCompareException):
assert AwesomeVersion(latest_version) > current_version
@pytest.mark.parametrize(
("current_version", "latest_version", "expected_state"),
[
("2025.11.5_c51f7548", "2025.11.6_aabbccdd", STATE_ON),
("2025.11.5_c51f7548", "2025.11.5_aabbccdd", STATE_OFF),
("2025.11.6_aabbccdd", "2025.11.5_c51f7548", STATE_OFF),
("2025.11.5_c51f7548", "2025.11.5_c51f7548", STATE_OFF),
],
ids=["newer_base", "same_base_new_build", "older_base", "identical"],
)
async def test_generic_device_update_entity_project_version(
hass: HomeAssistant,
mock_client: APIClient,
mock_generic_device_entry: MockGenericDeviceEntryType,
current_version: str,
latest_version: str,
expected_state: str,
) -> None:
"""Test version comparison for ESPHome project versions.
AwesomeVersion cannot parse the build suffix, so the entity strips it and
compares the real versions: only a genuinely newer base version is offered;
a different build of the same version or an older version is not.
"""
entity_info = [
UpdateInfo(
object_id="myupdate",
key=1,
name="my update",
)
]
states = [
UpdateState(
key=1,
current_version=current_version,
latest_version=latest_version,
title="ESPHome Project",
release_summary=RELEASE_SUMMARY,
release_url=RELEASE_URL,
)
]
await mock_generic_device_entry(
mock_client=mock_client,
entity_info=entity_info,
states=states,
)
state = hass.states.get(ENTITY_ID)
assert state is not None
assert state.state == expected_state
async def test_generic_device_update_entity_clears_after_ota(
hass: HomeAssistant,
mock_client: APIClient,
mock_esphome_device: MockESPHomeDeviceType,
) -> None:
"""Test a project version update clears once the device runs the new build."""
entity_info = [
UpdateInfo(
object_id="myupdate",
key=1,
name="my update",
)
]
states = [
UpdateState(
key=1,
current_version="2025.11.5_c51f7548",
latest_version="2025.11.6_aabbccdd",
title="ESPHome Project",
release_summary=RELEASE_SUMMARY,
release_url=RELEASE_URL,
)
]
mock_device = await mock_esphome_device(
mock_client=mock_client,
entity_info=entity_info,
states=states,
)
state = hass.states.get(ENTITY_ID)
assert state is not None
assert state.state == STATE_ON
mock_device.set_state(
UpdateState(
key=1,
current_version="2025.11.6_aabbccdd",
latest_version="2025.11.6_aabbccdd",
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_OFF
async def test_update_entity_release_notes(
hass: HomeAssistant,
mock_client: APIClient,