From e861cab7275df8e12c33b714c26c77172a558913 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 21 Oct 2024 15:31:48 +0200 Subject: [PATCH] Add update_percentage state attribute to update entity (#128877) * Add update_percentage state attribute to update entity * Update tests * Update tests --- homeassistant/components/update/__init__.py | 14 ++- homeassistant/components/update/const.py | 1 + .../airgradient/snapshots/test_update.ambr | 1 + tests/components/demo/test_update.py | 60 +++++++++---- .../snapshots/test_update.ambr | 1 + tests/components/esphome/test_update.py | 3 +- .../fritz/snapshots/test_update.ambr | 3 + .../lamarzocco/snapshots/test_update.ambr | 2 + tests/components/matter/test_update.py | 6 +- .../nextcloud/snapshots/test_update.ambr | 1 + tests/components/shelly/test_update.py | 34 ++++++- .../smlight/snapshots/test_update.ambr | 2 + tests/components/smlight/test_update.py | 5 +- .../teslemetry/snapshots/test_update.ambr | 2 + .../tessie/snapshots/test_update.ambr | 1 + .../unifi/snapshots/test_update.ambr | 4 + tests/components/update/test_init.py | 89 ++++++++++++++++++- tests/components/update/test_recorder.py | 5 +- tests/components/zha/test_update.py | 22 +++-- tests/components/zwave_js/test_update.py | 17 +++- 20 files changed, 232 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/update/__init__.py b/homeassistant/components/update/__init__.py index 82f2792afa3..8d4a5614f94 100644 --- a/homeassistant/components/update/__init__.py +++ b/homeassistant/components/update/__init__.py @@ -34,6 +34,7 @@ from .const import ( ATTR_RELEASE_URL, ATTR_SKIPPED_VERSION, ATTR_TITLE, + ATTR_UPDATE_PERCENTAGE, ATTR_VERSION, DOMAIN, SERVICE_INSTALL, @@ -207,7 +208,12 @@ class UpdateEntity( """Representation of an update entity.""" _entity_component_unrecorded_attributes = frozenset( - {ATTR_ENTITY_PICTURE, ATTR_IN_PROGRESS, ATTR_RELEASE_SUMMARY} + { + ATTR_ENTITY_PICTURE, + ATTR_IN_PROGRESS, + ATTR_RELEASE_SUMMARY, + ATTR_UPDATE_PERCENTAGE, + } ) entity_description: UpdateEntityDescription @@ -418,12 +424,17 @@ class UpdateEntity( if (release_summary := self.release_summary) is not None: release_summary = release_summary[:255] + update_percentage = None + # If entity supports progress, return the in_progress value. # Otherwise, we use the internal progress value. if UpdateEntityFeature.PROGRESS in self.supported_features_compat: in_progress = self.in_progress else: in_progress = self.__in_progress + if type(in_progress) is not bool and isinstance(in_progress, int): + update_percentage = in_progress + in_progress = True installed_version = self.installed_version latest_version = self.latest_version @@ -445,6 +456,7 @@ class UpdateEntity( ATTR_RELEASE_URL: self.release_url, ATTR_SKIPPED_VERSION: skipped_version, ATTR_TITLE: self.title, + ATTR_UPDATE_PERCENTAGE: update_percentage, } @final diff --git a/homeassistant/components/update/const.py b/homeassistant/components/update/const.py index 0d7da94f656..00b8cfa76b2 100644 --- a/homeassistant/components/update/const.py +++ b/homeassistant/components/update/const.py @@ -30,4 +30,5 @@ ATTR_RELEASE_SUMMARY: Final = "release_summary" ATTR_RELEASE_URL: Final = "release_url" ATTR_SKIPPED_VERSION: Final = "skipped_version" ATTR_TITLE: Final = "title" +ATTR_UPDATE_PERCENTAGE: Final = "update_percentage" ATTR_VERSION: Final = "version" diff --git a/tests/components/airgradient/snapshots/test_update.ambr b/tests/components/airgradient/snapshots/test_update.ambr index c639a97d5dd..f76a8fc1196 100644 --- a/tests/components/airgradient/snapshots/test_update.ambr +++ b/tests/components/airgradient/snapshots/test_update.ambr @@ -47,6 +47,7 @@ 'skipped_version': None, 'supported_features': , 'title': None, + 'update_percentage': None, }), 'context': , 'entity_id': 'update.airgradient_firmware', diff --git a/tests/components/demo/test_update.py b/tests/components/demo/test_update.py index 37fa5a7a2f6..1fa34ef0a13 100644 --- a/tests/components/demo/test_update.py +++ b/tests/components/demo/test_update.py @@ -11,6 +11,7 @@ from homeassistant.components.update import ( ATTR_RELEASE_SUMMARY, ATTR_RELEASE_URL, ATTR_TITLE, + ATTR_UPDATE_PERCENTAGE, DOMAIN as UPDATE_DOMAIN, SERVICE_INSTALL, UpdateDeviceClass, @@ -131,6 +132,7 @@ async def test_update_with_progress(hass: HomeAssistant) -> None: assert state assert state.state == STATE_ON assert state.attributes[ATTR_IN_PROGRESS] is False + assert state.attributes[ATTR_UPDATE_PERCENTAGE] is None events = [] async_track_state_change_event( @@ -148,19 +150,31 @@ async def test_update_with_progress(hass: HomeAssistant) -> None: blocking=True, ) - assert len(events) == 10 + assert len(events) == 11 assert events[0].data["new_state"].state == STATE_ON - assert events[0].data["new_state"].attributes[ATTR_IN_PROGRESS] == 10 - assert events[1].data["new_state"].attributes[ATTR_IN_PROGRESS] == 20 - assert events[2].data["new_state"].attributes[ATTR_IN_PROGRESS] == 30 - assert events[3].data["new_state"].attributes[ATTR_IN_PROGRESS] == 40 - assert events[4].data["new_state"].attributes[ATTR_IN_PROGRESS] == 50 - assert events[5].data["new_state"].attributes[ATTR_IN_PROGRESS] == 60 - assert events[6].data["new_state"].attributes[ATTR_IN_PROGRESS] == 70 - assert events[7].data["new_state"].attributes[ATTR_IN_PROGRESS] == 80 - assert events[8].data["new_state"].attributes[ATTR_IN_PROGRESS] == 90 - assert events[9].data["new_state"].attributes[ATTR_IN_PROGRESS] is False - assert events[9].data["new_state"].state == STATE_OFF + assert events[0].data["new_state"].attributes[ATTR_IN_PROGRESS] is True + assert events[0].data["new_state"].attributes[ATTR_UPDATE_PERCENTAGE] == 0 + assert events[1].data["new_state"].attributes[ATTR_IN_PROGRESS] is True + assert events[1].data["new_state"].attributes[ATTR_UPDATE_PERCENTAGE] == 10 + assert events[2].data["new_state"].attributes[ATTR_IN_PROGRESS] is True + assert events[2].data["new_state"].attributes[ATTR_UPDATE_PERCENTAGE] == 20 + assert events[3].data["new_state"].attributes[ATTR_IN_PROGRESS] is True + assert events[3].data["new_state"].attributes[ATTR_UPDATE_PERCENTAGE] == 30 + assert events[4].data["new_state"].attributes[ATTR_IN_PROGRESS] is True + assert events[4].data["new_state"].attributes[ATTR_UPDATE_PERCENTAGE] == 40 + assert events[5].data["new_state"].attributes[ATTR_IN_PROGRESS] is True + assert events[5].data["new_state"].attributes[ATTR_UPDATE_PERCENTAGE] == 50 + assert events[6].data["new_state"].attributes[ATTR_IN_PROGRESS] is True + assert events[6].data["new_state"].attributes[ATTR_UPDATE_PERCENTAGE] == 60 + assert events[7].data["new_state"].attributes[ATTR_IN_PROGRESS] is True + assert events[7].data["new_state"].attributes[ATTR_UPDATE_PERCENTAGE] == 70 + assert events[8].data["new_state"].attributes[ATTR_IN_PROGRESS] is True + assert events[8].data["new_state"].attributes[ATTR_UPDATE_PERCENTAGE] == 80 + assert events[9].data["new_state"].attributes[ATTR_IN_PROGRESS] is True + assert events[9].data["new_state"].attributes[ATTR_UPDATE_PERCENTAGE] == 90 + assert events[10].data["new_state"].attributes[ATTR_IN_PROGRESS] is False + assert events[10].data["new_state"].attributes[ATTR_UPDATE_PERCENTAGE] is None + assert events[10].data["new_state"].state == STATE_OFF async def test_update_with_progress_raising(hass: HomeAssistant) -> None: @@ -169,6 +183,7 @@ async def test_update_with_progress_raising(hass: HomeAssistant) -> None: assert state assert state.state == STATE_ON assert state.attributes[ATTR_IN_PROGRESS] is False + assert state.attributes[ATTR_UPDATE_PERCENTAGE] is None events = [] async_track_state_change_event( @@ -194,11 +209,18 @@ async def test_update_with_progress_raising(hass: HomeAssistant) -> None: await hass.async_block_till_done() assert fake_sleep.call_count == 5 - assert len(events) == 5 + assert len(events) == 6 assert events[0].data["new_state"].state == STATE_ON - assert events[0].data["new_state"].attributes[ATTR_IN_PROGRESS] == 10 - assert events[1].data["new_state"].attributes[ATTR_IN_PROGRESS] == 20 - assert events[2].data["new_state"].attributes[ATTR_IN_PROGRESS] == 30 - assert events[3].data["new_state"].attributes[ATTR_IN_PROGRESS] == 40 - assert events[4].data["new_state"].attributes[ATTR_IN_PROGRESS] is False - assert events[4].data["new_state"].state == STATE_ON + assert events[0].data["new_state"].attributes[ATTR_IN_PROGRESS] is True + assert events[0].data["new_state"].attributes[ATTR_UPDATE_PERCENTAGE] == 0 + assert events[1].data["new_state"].attributes[ATTR_IN_PROGRESS] is True + assert events[1].data["new_state"].attributes[ATTR_UPDATE_PERCENTAGE] == 10 + assert events[2].data["new_state"].attributes[ATTR_IN_PROGRESS] is True + assert events[2].data["new_state"].attributes[ATTR_UPDATE_PERCENTAGE] == 20 + assert events[3].data["new_state"].attributes[ATTR_IN_PROGRESS] is True + assert events[3].data["new_state"].attributes[ATTR_UPDATE_PERCENTAGE] == 30 + assert events[4].data["new_state"].attributes[ATTR_IN_PROGRESS] is True + assert events[4].data["new_state"].attributes[ATTR_UPDATE_PERCENTAGE] == 40 + assert events[5].data["new_state"].attributes[ATTR_IN_PROGRESS] is False + assert events[5].data["new_state"].attributes[ATTR_UPDATE_PERCENTAGE] is None + assert events[5].data["new_state"].state == STATE_ON diff --git a/tests/components/devolo_home_network/snapshots/test_update.ambr b/tests/components/devolo_home_network/snapshots/test_update.ambr index 83ca84c82e8..de6a67d5e3d 100644 --- a/tests/components/devolo_home_network/snapshots/test_update.ambr +++ b/tests/components/devolo_home_network/snapshots/test_update.ambr @@ -14,6 +14,7 @@ 'skipped_version': None, 'supported_features': , 'title': None, + 'update_percentage': None, }), 'context': , 'entity_id': 'update.mock_title_firmware', diff --git a/tests/components/esphome/test_update.py b/tests/components/esphome/test_update.py index 83e89b1de00..7593ab21838 100644 --- a/tests/components/esphome/test_update.py +++ b/tests/components/esphome/test_update.py @@ -531,7 +531,8 @@ async def test_generic_device_update_entity_has_update( state = hass.states.get("update.test_myupdate") assert state is not None assert state.state == STATE_ON - assert state.attributes["in_progress"] == 50 + assert state.attributes["in_progress"] is True + assert state.attributes["update_percentage"] == 50 await hass.services.async_call( HOMEASSISTANT_DOMAIN, diff --git a/tests/components/fritz/snapshots/test_update.ambr b/tests/components/fritz/snapshots/test_update.ambr index 5544c972499..4914ba85269 100644 --- a/tests/components/fritz/snapshots/test_update.ambr +++ b/tests/components/fritz/snapshots/test_update.ambr @@ -46,6 +46,7 @@ 'skipped_version': None, 'supported_features': , 'title': 'FRITZ!OS', + 'update_percentage': None, }), 'context': , 'entity_id': 'update.mock_title_fritz_os', @@ -102,6 +103,7 @@ 'skipped_version': None, 'supported_features': , 'title': 'FRITZ!OS', + 'update_percentage': None, }), 'context': , 'entity_id': 'update.mock_title_fritz_os', @@ -158,6 +160,7 @@ 'skipped_version': None, 'supported_features': , 'title': 'FRITZ!OS', + 'update_percentage': None, }), 'context': , 'entity_id': 'update.mock_title_fritz_os', diff --git a/tests/components/lamarzocco/snapshots/test_update.ambr b/tests/components/lamarzocco/snapshots/test_update.ambr index f08b9249f50..c40677a80ca 100644 --- a/tests/components/lamarzocco/snapshots/test_update.ambr +++ b/tests/components/lamarzocco/snapshots/test_update.ambr @@ -14,6 +14,7 @@ 'skipped_version': None, 'supported_features': , 'title': None, + 'update_percentage': None, }), 'context': , 'entity_id': 'update.gs01234_gateway_firmware', @@ -71,6 +72,7 @@ 'skipped_version': None, 'supported_features': , 'title': None, + 'update_percentage': None, }), 'context': , 'entity_id': 'update.gs01234_machine_firmware', diff --git a/tests/components/matter/test_update.py b/tests/components/matter/test_update.py index ad73bd38723..92576fa69e2 100644 --- a/tests/components/matter/test_update.py +++ b/tests/components/matter/test_update.py @@ -202,7 +202,8 @@ async def test_update_install( state = hass.states.get("update.mock_dimmable_light") assert state assert state.state == STATE_ON - assert state.attributes.get("in_progress") + assert state.attributes["in_progress"] is True + assert state.attributes["update_percentage"] is None set_node_attribute_typed( matter_node, @@ -215,7 +216,8 @@ async def test_update_install( state = hass.states.get("update.mock_dimmable_light") assert state assert state.state == STATE_ON - assert state.attributes.get("in_progress") == 50 + assert state.attributes["in_progress"] is True + assert state.attributes["update_percentage"] == 50 set_node_attribute_typed( matter_node, diff --git a/tests/components/nextcloud/snapshots/test_update.ambr b/tests/components/nextcloud/snapshots/test_update.ambr index 1ee6264c204..be94339b41a 100644 --- a/tests/components/nextcloud/snapshots/test_update.ambr +++ b/tests/components/nextcloud/snapshots/test_update.ambr @@ -46,6 +46,7 @@ 'skipped_version': None, 'supported_features': , 'title': None, + 'update_percentage': None, }), 'context': , 'entity_id': 'update.my_nc_url_local_none', diff --git a/tests/components/shelly/test_update.py b/tests/components/shelly/test_update.py index a89dfcd1e71..cd4cdf877a5 100644 --- a/tests/components/shelly/test_update.py +++ b/tests/components/shelly/test_update.py @@ -16,6 +16,7 @@ from homeassistant.components.update import ( ATTR_INSTALLED_VERSION, ATTR_LATEST_VERSION, ATTR_RELEASE_URL, + ATTR_UPDATE_PERCENTAGE, DOMAIN as UPDATE_DOMAIN, SERVICE_INSTALL, UpdateEntityFeature, @@ -64,6 +65,7 @@ async def test_block_update( assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert state.attributes[ATTR_LATEST_VERSION] == "2.0.0" assert state.attributes[ATTR_IN_PROGRESS] is False + assert state.attributes[ATTR_UPDATE_PERCENTAGE] is None supported_feat = state.attributes[ATTR_SUPPORTED_FEATURES] assert supported_feat == UpdateEntityFeature.INSTALL | UpdateEntityFeature.PROGRESS @@ -80,6 +82,7 @@ async def test_block_update( assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert state.attributes[ATTR_LATEST_VERSION] == "2.0.0" assert state.attributes[ATTR_IN_PROGRESS] is True + assert state.attributes[ATTR_UPDATE_PERCENTAGE] is None assert state.attributes[ATTR_RELEASE_URL] == GEN1_RELEASE_URL monkeypatch.setitem(mock_block_device.status["update"], "old_version", "2.0.0") @@ -90,6 +93,7 @@ async def test_block_update( assert state.attributes[ATTR_INSTALLED_VERSION] == "2.0.0" assert state.attributes[ATTR_LATEST_VERSION] == "2.0.0" assert state.attributes[ATTR_IN_PROGRESS] is False + assert state.attributes[ATTR_UPDATE_PERCENTAGE] is None entry = entity_registry.async_get(entity_id) assert entry @@ -117,6 +121,7 @@ async def test_block_beta_update( assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert state.attributes[ATTR_LATEST_VERSION] == "1.0.0" assert state.attributes[ATTR_IN_PROGRESS] is False + assert state.attributes[ATTR_UPDATE_PERCENTAGE] is None monkeypatch.setitem( mock_block_device.status["update"], "beta_version", "2.0.0-beta" @@ -128,6 +133,7 @@ async def test_block_beta_update( assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert state.attributes[ATTR_LATEST_VERSION] == "2.0.0-beta" assert state.attributes[ATTR_IN_PROGRESS] is False + assert state.attributes[ATTR_UPDATE_PERCENTAGE] is None assert state.attributes[ATTR_RELEASE_URL] is None await hass.services.async_call( @@ -143,6 +149,7 @@ async def test_block_beta_update( assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert state.attributes[ATTR_LATEST_VERSION] == "2.0.0-beta" assert state.attributes[ATTR_IN_PROGRESS] is True + assert state.attributes[ATTR_UPDATE_PERCENTAGE] is None monkeypatch.setitem(mock_block_device.status["update"], "old_version", "2.0.0-beta") await mock_rest_update(hass, freezer) @@ -152,6 +159,7 @@ async def test_block_beta_update( assert state.attributes[ATTR_INSTALLED_VERSION] == "2.0.0-beta" assert state.attributes[ATTR_LATEST_VERSION] == "2.0.0-beta" assert state.attributes[ATTR_IN_PROGRESS] is False + assert state.attributes[ATTR_UPDATE_PERCENTAGE] is None entry = entity_registry.async_get(entity_id) assert entry @@ -292,6 +300,7 @@ async def test_rpc_update( 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_UPDATE_PERCENTAGE] is None supported_feat = state.attributes[ATTR_SUPPORTED_FEATURES] assert supported_feat == UpdateEntityFeature.INSTALL | UpdateEntityFeature.PROGRESS @@ -309,6 +318,7 @@ async def test_rpc_update( assert state.attributes[ATTR_INSTALLED_VERSION] == "1" assert state.attributes[ATTR_LATEST_VERSION] == "2" assert state.attributes[ATTR_IN_PROGRESS] is True + assert state.attributes[ATTR_UPDATE_PERCENTAGE] is None assert state.attributes[ATTR_RELEASE_URL] == GEN2_RELEASE_URL inject_rpc_device_event( @@ -326,7 +336,9 @@ async def test_rpc_update( }, ) - assert hass.states.get(entity_id).attributes[ATTR_IN_PROGRESS] == 0 + state = hass.states.get(entity_id) + assert state.attributes[ATTR_IN_PROGRESS] is True + assert state.attributes[ATTR_UPDATE_PERCENTAGE] == 0 inject_rpc_device_event( monkeypatch, @@ -344,7 +356,9 @@ async def test_rpc_update( }, ) - assert hass.states.get(entity_id).attributes[ATTR_IN_PROGRESS] == 50 + state = hass.states.get(entity_id) + assert state.attributes[ATTR_IN_PROGRESS] is True + assert state.attributes[ATTR_UPDATE_PERCENTAGE] == 50 inject_rpc_device_event( monkeypatch, @@ -368,6 +382,7 @@ async def test_rpc_update( 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_UPDATE_PERCENTAGE] is None entry = entity_registry.async_get(entity_id) assert entry @@ -406,6 +421,7 @@ async def test_rpc_sleeping_update( 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_UPDATE_PERCENTAGE] is None assert state.attributes[ATTR_SUPPORTED_FEATURES] == UpdateEntityFeature(0) assert state.attributes[ATTR_RELEASE_URL] == GEN2_RELEASE_URL @@ -417,6 +433,7 @@ async def test_rpc_sleeping_update( 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_UPDATE_PERCENTAGE] is None assert state.attributes[ATTR_SUPPORTED_FEATURES] == UpdateEntityFeature(0) entry = entity_registry.async_get(entity_id) @@ -456,6 +473,7 @@ async def test_rpc_restored_sleeping_update( 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_UPDATE_PERCENTAGE] is None assert state.attributes[ATTR_SUPPORTED_FEATURES] == UpdateEntityFeature(0) # Make device online @@ -472,6 +490,7 @@ async def test_rpc_restored_sleeping_update( 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_UPDATE_PERCENTAGE] is None assert state.attributes[ATTR_SUPPORTED_FEATURES] == UpdateEntityFeature(0) @@ -522,6 +541,7 @@ async def test_rpc_restored_sleeping_update_no_last_state( 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_UPDATE_PERCENTAGE] is None assert state.attributes[ATTR_SUPPORTED_FEATURES] == UpdateEntityFeature(0) @@ -551,6 +571,7 @@ async def test_rpc_beta_update( assert state.attributes[ATTR_INSTALLED_VERSION] == "1" assert state.attributes[ATTR_LATEST_VERSION] == "1" assert state.attributes[ATTR_IN_PROGRESS] is False + assert state.attributes[ATTR_UPDATE_PERCENTAGE] is None assert state.attributes[ATTR_RELEASE_URL] is None monkeypatch.setitem( @@ -568,6 +589,7 @@ async def test_rpc_beta_update( assert state.attributes[ATTR_INSTALLED_VERSION] == "1" assert state.attributes[ATTR_LATEST_VERSION] == "2b" assert state.attributes[ATTR_IN_PROGRESS] is False + assert state.attributes[ATTR_UPDATE_PERCENTAGE] is None await hass.services.async_call( UPDATE_DOMAIN, @@ -596,7 +618,8 @@ async def test_rpc_beta_update( assert state.state == STATE_ON assert state.attributes[ATTR_INSTALLED_VERSION] == "1" assert state.attributes[ATTR_LATEST_VERSION] == "2b" - assert state.attributes[ATTR_IN_PROGRESS] == 0 + assert state.attributes[ATTR_IN_PROGRESS] is True + assert state.attributes[ATTR_UPDATE_PERCENTAGE] == 0 inject_rpc_device_event( monkeypatch, @@ -614,7 +637,9 @@ async def test_rpc_beta_update( }, ) - assert hass.states.get(entity_id).attributes[ATTR_IN_PROGRESS] == 40 + state = hass.states.get(entity_id) + assert state.attributes[ATTR_IN_PROGRESS] is True + assert state.attributes[ATTR_UPDATE_PERCENTAGE] == 40 inject_rpc_device_event( monkeypatch, @@ -638,6 +663,7 @@ async def test_rpc_beta_update( assert state.attributes[ATTR_INSTALLED_VERSION] == "2b" assert state.attributes[ATTR_LATEST_VERSION] == "2b" assert state.attributes[ATTR_IN_PROGRESS] is False + assert state.attributes[ATTR_UPDATE_PERCENTAGE] is None entry = entity_registry.async_get(entity_id) assert entry diff --git a/tests/components/smlight/snapshots/test_update.ambr b/tests/components/smlight/snapshots/test_update.ambr index 755c9bc7312..e5f7c34ccf5 100644 --- a/tests/components/smlight/snapshots/test_update.ambr +++ b/tests/components/smlight/snapshots/test_update.ambr @@ -47,6 +47,7 @@ 'skipped_version': None, 'supported_features': , 'title': None, + 'update_percentage': None, }), 'context': , 'entity_id': 'update.mock_title_core_firmware', @@ -104,6 +105,7 @@ 'skipped_version': None, 'supported_features': , 'title': None, + 'update_percentage': None, }), 'context': , 'entity_id': 'update.mock_title_zigbee_firmware', diff --git a/tests/components/smlight/test_update.py b/tests/components/smlight/test_update.py index 7bff12bb027..714caefd91c 100644 --- a/tests/components/smlight/test_update.py +++ b/tests/components/smlight/test_update.py @@ -14,6 +14,7 @@ from homeassistant.components.update import ( ATTR_IN_PROGRESS, ATTR_INSTALLED_VERSION, ATTR_LATEST_VERSION, + ATTR_UPDATE_PERCENTAGE, DOMAIN as PLATFORM, SERVICE_INSTALL, ) @@ -114,7 +115,8 @@ async def test_update_firmware( event_function(MOCK_FIRMWARE_PROGRESS) state = hass.states.get(entity_id) - assert state.attributes[ATTR_IN_PROGRESS] == 50 + assert state.attributes[ATTR_IN_PROGRESS] is True + assert state.attributes[ATTR_UPDATE_PERCENTAGE] == 50 event_function = get_mock_event_function(mock_smlight_client, SmEvents.FW_UPD_done) @@ -211,6 +213,7 @@ async def test_update_firmware_failed( await _call_event_function(MOCK_FIRMWARE_FAIL) state = hass.states.get(entity_id) assert state.attributes[ATTR_IN_PROGRESS] is False + assert state.attributes[ATTR_UPDATE_PERCENTAGE] is None async def test_update_release_notes( diff --git a/tests/components/teslemetry/snapshots/test_update.ambr b/tests/components/teslemetry/snapshots/test_update.ambr index 19dac161516..ef66720a0ed 100644 --- a/tests/components/teslemetry/snapshots/test_update.ambr +++ b/tests/components/teslemetry/snapshots/test_update.ambr @@ -46,6 +46,7 @@ 'skipped_version': None, 'supported_features': , 'title': None, + 'update_percentage': None, }), 'context': , 'entity_id': 'update.test_update', @@ -102,6 +103,7 @@ 'skipped_version': None, 'supported_features': , 'title': None, + 'update_percentage': None, }), 'context': , 'entity_id': 'update.test_update', diff --git a/tests/components/tessie/snapshots/test_update.ambr b/tests/components/tessie/snapshots/test_update.ambr index 622cf69c7f0..5f795007901 100644 --- a/tests/components/tessie/snapshots/test_update.ambr +++ b/tests/components/tessie/snapshots/test_update.ambr @@ -46,6 +46,7 @@ 'skipped_version': None, 'supported_features': , 'title': None, + 'update_percentage': None, }), 'context': , 'entity_id': 'update.test_update', diff --git a/tests/components/unifi/snapshots/test_update.ambr b/tests/components/unifi/snapshots/test_update.ambr index 99a403a8f21..77fd2c7d8bc 100644 --- a/tests/components/unifi/snapshots/test_update.ambr +++ b/tests/components/unifi/snapshots/test_update.ambr @@ -47,6 +47,7 @@ 'skipped_version': None, 'supported_features': , 'title': None, + 'update_percentage': None, }), 'context': , 'entity_id': 'update.device_1', @@ -104,6 +105,7 @@ 'skipped_version': None, 'supported_features': , 'title': None, + 'update_percentage': None, }), 'context': , 'entity_id': 'update.device_2', @@ -161,6 +163,7 @@ 'skipped_version': None, 'supported_features': , 'title': None, + 'update_percentage': None, }), 'context': , 'entity_id': 'update.device_1', @@ -218,6 +221,7 @@ 'skipped_version': None, 'supported_features': , 'title': None, + 'update_percentage': None, }), 'context': , 'entity_id': 'update.device_2', diff --git a/tests/components/update/test_init.py b/tests/components/update/test_init.py index 6082e0ecfe7..f19b009456a 100644 --- a/tests/components/update/test_init.py +++ b/tests/components/update/test_init.py @@ -25,11 +25,15 @@ from homeassistant.components.update.const import ( ATTR_RELEASE_URL, ATTR_SKIPPED_VERSION, ATTR_TITLE, + ATTR_UPDATE_PERCENTAGE, UpdateEntityFeature, ) from homeassistant.config_entries import ConfigEntry, ConfigFlow from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_ENTITY_PICTURE, + ATTR_FRIENDLY_NAME, + ATTR_SUPPORTED_FEATURES, CONF_PLATFORM, STATE_OFF, STATE_ON, @@ -95,6 +99,7 @@ async def test_update(hass: HomeAssistant) -> None: ATTR_RELEASE_URL: "https://example.com", ATTR_SKIPPED_VERSION: None, ATTR_TITLE: "Title", + ATTR_UPDATE_PERCENTAGE: None, } # Test no update available @@ -557,7 +562,8 @@ async def test_entity_already_in_progress( assert state.state == STATE_ON assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert state.attributes[ATTR_LATEST_VERSION] == "1.0.1" - assert state.attributes[ATTR_IN_PROGRESS] == 50 + assert state.attributes[ATTR_IN_PROGRESS] is True + assert state.attributes[ATTR_UPDATE_PERCENTAGE] == 50 with pytest.raises( HomeAssistantError, @@ -997,3 +1003,84 @@ async def test_custom_version_is_newer(hass: HomeAssistant) -> None: assert update.installed_version == BETA assert update.latest_version == STABLE assert update.state == STATE_OFF + + +@pytest.mark.parametrize( + ("supported_features", "extra_expected_attributes"), + [ + ( + 0, + [ + {}, + {}, + {}, + {}, + {}, + {}, + {}, + ], + ), + ( + UpdateEntityFeature.PROGRESS, + [ + {ATTR_IN_PROGRESS: False}, + {ATTR_IN_PROGRESS: False}, + {ATTR_IN_PROGRESS: True, ATTR_UPDATE_PERCENTAGE: 0}, + {ATTR_IN_PROGRESS: True}, + {ATTR_IN_PROGRESS: True, ATTR_UPDATE_PERCENTAGE: 1}, + {ATTR_IN_PROGRESS: True, ATTR_UPDATE_PERCENTAGE: 10}, + {ATTR_IN_PROGRESS: True, ATTR_UPDATE_PERCENTAGE: 100}, + ], + ), + ], +) +async def test_update_percentage_backwards_compatibility( + hass: HomeAssistant, + supported_features: UpdateEntityFeature, + extra_expected_attributes: list[dict], +) -> None: + """Test deriving update percentage from deprecated in_progress.""" + update = MockUpdateEntity() + + update._attr_installed_version = "1.0.0" + update._attr_latest_version = "1.0.1" + update._attr_name = "legacy" + update._attr_release_summary = "Summary" + update._attr_release_url = "https://example.com" + update._attr_supported_features = supported_features + update._attr_title = "Title" + + setup_test_component_platform(hass, DOMAIN, [update]) + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() + + expected_attributes = { + ATTR_AUTO_UPDATE: False, + ATTR_ENTITY_PICTURE: "https://brands.home-assistant.io/_/test/icon.png", + ATTR_FRIENDLY_NAME: "legacy", + ATTR_INSTALLED_VERSION: "1.0.0", + ATTR_IN_PROGRESS: False, + ATTR_LATEST_VERSION: "1.0.1", + ATTR_RELEASE_SUMMARY: "Summary", + ATTR_RELEASE_URL: "https://example.com", + ATTR_SKIPPED_VERSION: None, + ATTR_SUPPORTED_FEATURES: supported_features, + ATTR_TITLE: "Title", + ATTR_UPDATE_PERCENTAGE: None, + } + + state = hass.states.get("update.legacy") + assert state is not None + assert state.state == STATE_ON + assert state.attributes == expected_attributes | extra_expected_attributes[0] + + in_progress_list = [False, 0, True, 1, 10, 100] + + for i, in_progress in enumerate(in_progress_list): + update._attr_in_progress = in_progress + update.async_write_ha_state() + state = hass.states.get("update.legacy") + assert state.state == STATE_ON + assert ( + state.attributes == expected_attributes | extra_expected_attributes[i + 1] + ) diff --git a/tests/components/update/test_recorder.py b/tests/components/update/test_recorder.py index 0bd209ce1c2..847a08cfd9c 100644 --- a/tests/components/update/test_recorder.py +++ b/tests/components/update/test_recorder.py @@ -10,6 +10,7 @@ from homeassistant.components.update.const import ( ATTR_IN_PROGRESS, ATTR_INSTALLED_VERSION, ATTR_RELEASE_SUMMARY, + ATTR_UPDATE_PERCENTAGE, DOMAIN, ) from homeassistant.const import ATTR_ENTITY_PICTURE, CONF_PLATFORM @@ -34,7 +35,8 @@ async def test_exclude_attributes( assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() state = hass.states.get("update.update_already_in_progress") - assert state.attributes[ATTR_IN_PROGRESS] == 50 + assert state.attributes[ATTR_IN_PROGRESS] is True + assert state.attributes[ATTR_UPDATE_PERCENTAGE] == 50 assert ( state.attributes[ATTR_ENTITY_PICTURE] == "https://brands.home-assistant.io/_/test/icon.png" @@ -56,3 +58,4 @@ async def test_exclude_attributes( assert ATTR_IN_PROGRESS not in state.attributes assert ATTR_RELEASE_SUMMARY not in state.attributes assert ATTR_INSTALLED_VERSION in state.attributes + assert ATTR_UPDATE_PERCENTAGE not in state.attributes diff --git a/tests/components/zha/test_update.py b/tests/components/zha/test_update.py index bb25f0a444d..4b6dff4fc6b 100644 --- a/tests/components/zha/test_update.py +++ b/tests/components/zha/test_update.py @@ -23,6 +23,7 @@ from homeassistant.components.update import ( ATTR_IN_PROGRESS, ATTR_INSTALLED_VERSION, ATTR_LATEST_VERSION, + ATTR_UPDATE_PERCENTAGE, DOMAIN as UPDATE_DOMAIN, SERVICE_INSTALL, ) @@ -172,7 +173,8 @@ async def test_firmware_update_notification_from_zigpy( assert state.state == STATE_ON attrs = state.attributes assert attrs[ATTR_INSTALLED_VERSION] == f"0x{installed_fw_version:08x}" - assert not attrs[ATTR_IN_PROGRESS] + assert attrs[ATTR_IN_PROGRESS] is False + assert attrs[ATTR_UPDATE_PERCENTAGE] is None assert ( attrs[ATTR_LATEST_VERSION] == f"0x{fw_image.firmware.header.file_version:08x}" ) @@ -231,7 +233,8 @@ async def test_firmware_update_notification_from_service_call( assert state.state == STATE_ON attrs = state.attributes assert attrs[ATTR_INSTALLED_VERSION] == f"0x{installed_fw_version:08x}" - assert not attrs[ATTR_IN_PROGRESS] + assert attrs[ATTR_IN_PROGRESS] is False + assert attrs[ATTR_UPDATE_PERCENTAGE] is None assert ( attrs[ATTR_LATEST_VERSION] == f"0x{fw_image.firmware.header.file_version:08x}" @@ -301,7 +304,8 @@ async def test_firmware_update_success( assert state.state == STATE_ON attrs = state.attributes assert attrs[ATTR_INSTALLED_VERSION] == f"0x{installed_fw_version:08x}" - assert not attrs[ATTR_IN_PROGRESS] + assert attrs[ATTR_IN_PROGRESS] is False + assert attrs[ATTR_UPDATE_PERCENTAGE] is None assert ( attrs[ATTR_LATEST_VERSION] == f"0x{fw_image.firmware.header.file_version:08x}" ) @@ -389,7 +393,8 @@ async def test_firmware_update_success( assert ( attrs[ATTR_INSTALLED_VERSION] == f"0x{installed_fw_version:08x}" ) - assert attrs[ATTR_IN_PROGRESS] == 58 + assert attrs[ATTR_IN_PROGRESS] is True + assert attrs[ATTR_UPDATE_PERCENTAGE] == 58 assert ( attrs[ATTR_LATEST_VERSION] == f"0x{fw_image.firmware.header.file_version:08x}" @@ -446,7 +451,8 @@ async def test_firmware_update_success( attrs[ATTR_INSTALLED_VERSION] == f"0x{fw_image.firmware.header.file_version:08x}" ) - assert not attrs[ATTR_IN_PROGRESS] + assert attrs[ATTR_IN_PROGRESS] is False + assert attrs[ATTR_UPDATE_PERCENTAGE] is None assert attrs[ATTR_LATEST_VERSION] == attrs[ATTR_INSTALLED_VERSION] # If we send a progress notification incorrectly, it won't be handled @@ -454,7 +460,8 @@ async def test_firmware_update_success( entity.entity_data.entity._update_progress(50, 100, 0.50) state = hass.states.get(entity_id) - assert not attrs[ATTR_IN_PROGRESS] + assert attrs[ATTR_IN_PROGRESS] is False + assert attrs[ATTR_UPDATE_PERCENTAGE] is None assert state.state == STATE_OFF @@ -493,7 +500,8 @@ async def test_firmware_update_raises( assert state.state == STATE_ON attrs = state.attributes assert attrs[ATTR_INSTALLED_VERSION] == f"0x{installed_fw_version:08x}" - assert not attrs[ATTR_IN_PROGRESS] + assert attrs[ATTR_IN_PROGRESS] is False + assert attrs[ATTR_UPDATE_PERCENTAGE] is None assert ( attrs[ATTR_LATEST_VERSION] == f"0x{fw_image.firmware.header.file_version:08x}" ) diff --git a/tests/components/zwave_js/test_update.py b/tests/components/zwave_js/test_update.py index abdceb155f7..d6683fa24cb 100644 --- a/tests/components/zwave_js/test_update.py +++ b/tests/components/zwave_js/test_update.py @@ -16,6 +16,7 @@ from homeassistant.components.update import ( ATTR_LATEST_VERSION, ATTR_RELEASE_URL, ATTR_SKIPPED_VERSION, + ATTR_UPDATE_PERCENTAGE, DOMAIN as UPDATE_DOMAIN, SERVICE_INSTALL, SERVICE_SKIP, @@ -155,9 +156,10 @@ async def test_update_entity_states( attrs = state.attributes assert not attrs[ATTR_AUTO_UPDATE] assert attrs[ATTR_INSTALLED_VERSION] == "10.7" - assert not attrs[ATTR_IN_PROGRESS] + assert attrs[ATTR_IN_PROGRESS] is False assert attrs[ATTR_LATEST_VERSION] == "11.2.4" assert attrs[ATTR_RELEASE_URL] is None + assert attrs[ATTR_UPDATE_PERCENTAGE] is None await ws_client.send_json( { @@ -417,6 +419,7 @@ async def test_update_entity_progress( assert state attrs = state.attributes assert attrs[ATTR_IN_PROGRESS] is True + assert attrs[ATTR_UPDATE_PERCENTAGE] is None event = Event( type="firmware update progress", @@ -439,7 +442,8 @@ async def test_update_entity_progress( state = hass.states.get(UPDATE_ENTITY) assert state attrs = state.attributes - assert attrs[ATTR_IN_PROGRESS] == 5 + assert attrs[ATTR_IN_PROGRESS] is True + assert attrs[ATTR_UPDATE_PERCENTAGE] == 5 event = Event( type="firmware update finished", @@ -463,6 +467,7 @@ async def test_update_entity_progress( assert state attrs = state.attributes assert attrs[ATTR_IN_PROGRESS] is False + assert attrs[ATTR_UPDATE_PERCENTAGE] is None assert attrs[ATTR_INSTALLED_VERSION] == "11.2.4" assert attrs[ATTR_LATEST_VERSION] == "11.2.4" assert state.state == STATE_OFF @@ -532,7 +537,8 @@ async def test_update_entity_install_failed( state = hass.states.get(UPDATE_ENTITY) assert state attrs = state.attributes - assert attrs[ATTR_IN_PROGRESS] == 5 + assert attrs[ATTR_IN_PROGRESS] is True + assert attrs[ATTR_UPDATE_PERCENTAGE] == 5 event = Event( type="firmware update finished", @@ -556,6 +562,7 @@ async def test_update_entity_install_failed( assert state attrs = state.attributes assert attrs[ATTR_IN_PROGRESS] is False + assert attrs[ATTR_UPDATE_PERCENTAGE] is None assert attrs[ATTR_INSTALLED_VERSION] == "10.7" assert attrs[ATTR_LATEST_VERSION] == "11.2.4" assert state.state == STATE_ON @@ -594,7 +601,8 @@ async def test_update_entity_reload( attrs = state.attributes assert not attrs[ATTR_AUTO_UPDATE] assert attrs[ATTR_INSTALLED_VERSION] == "10.7" - assert not attrs[ATTR_IN_PROGRESS] + assert attrs[ATTR_IN_PROGRESS] is False + assert attrs[ATTR_UPDATE_PERCENTAGE] is None assert attrs[ATTR_LATEST_VERSION] == "11.2.4" assert attrs[ATTR_RELEASE_URL] is None @@ -833,6 +841,7 @@ async def test_update_entity_full_restore_data_update_available( assert state attrs = state.attributes assert attrs[ATTR_IN_PROGRESS] is True + assert attrs[ATTR_UPDATE_PERCENTAGE] is None assert len(client.async_send_command.call_args_list) == 2 assert client.async_send_command.call_args_list[1][0][0] == {