From 8417f22904949c257b918fcb8f51a87cd9a019b1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 31 Jan 2023 22:13:41 -0500 Subject: [PATCH] Trigger update of ESPHome update entity when static info updates (#87058) Trigger update of update entity when static info updates --- homeassistant/components/esphome/__init__.py | 5 +- .../components/esphome/entry_data.py | 8 +++- homeassistant/components/esphome/update.py | 21 ++++++++- tests/components/esphome/test_update.py | 47 ++++++++++++++++++- 4 files changed, 73 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index 4c9d8b6362b..d2c987d56ba 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -646,9 +646,10 @@ async def platform_async_setup_entry( # Add entities to Home Assistant async_add_entities(add_entities) - signal = f"esphome_{entry.entry_id}_on_list" entry_data.cleanup_callbacks.append( - async_dispatcher_connect(hass, signal, async_list_entities) + async_dispatcher_connect( + hass, entry_data.signal_static_info_updated, async_list_entities + ) ) diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index 24322fdac47..b7443eea211 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -107,6 +107,11 @@ class RuntimeEntryData: return self.device_info.friendly_name return self.name + @property + def signal_static_info_updated(self) -> str: + """Return the signal to listen to for updates on static info.""" + return f"esphome_{self.entry_id}_on_list" + @callback def async_update_ble_connection_limits(self, free: int, limit: int) -> None: """Update the BLE connection limits.""" @@ -168,8 +173,7 @@ class RuntimeEntryData: await self._ensure_platforms_loaded(hass, entry, needed_platforms) # Then send dispatcher event - signal = f"esphome_{self.entry_id}_on_list" - async_dispatcher_send(hass, signal, infos) + async_dispatcher_send(hass, self.signal_static_info_updated, infos) @callback def async_subscribe_state_update( diff --git a/homeassistant/components/esphome/update.py b/homeassistant/components/esphome/update.py index de7a7463191..be525a6f3b8 100644 --- a/homeassistant/components/esphome/update.py +++ b/homeassistant/components/esphome/update.py @@ -5,7 +5,7 @@ import asyncio import logging from typing import Any, cast -from aioesphomeapi import DeviceInfo as ESPHomeDeviceInfo +from aioesphomeapi import DeviceInfo as ESPHomeDeviceInfo, EntityInfo from homeassistant.components.update import ( UpdateDeviceClass, @@ -13,7 +13,7 @@ from homeassistant.components.update import ( UpdateEntityFeature, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo @@ -115,6 +115,23 @@ class ESPHomeUpdateEntity(CoordinatorEntity[ESPHomeDashboard], UpdateEntity): """URL to the full release notes of the latest version available.""" return "https://esphome.io/changelog/" + async def async_added_to_hass(self) -> None: + """Handle entity added to Home Assistant.""" + await super().async_added_to_hass() + + @callback + def _static_info_updated(infos: list[EntityInfo]) -> None: + """Handle static info update.""" + self.async_write_ha_state() + + self.async_on_remove( + async_dispatcher_connect( + self.hass, + self._entry_data.signal_static_info_updated, + _static_info_updated, + ) + ) + async def async_install( self, version: str | None, backup: bool, **kwargs: Any ) -> None: diff --git a/tests/components/esphome/test_update.py b/tests/components/esphome/test_update.py index a263f4ab0cd..d7ad83697af 100644 --- a/tests/components/esphome/test_update.py +++ b/tests/components/esphome/test_update.py @@ -1,9 +1,11 @@ """Test ESPHome update entities.""" +import dataclasses from unittest.mock import Mock, patch import pytest from homeassistant.components.esphome.dashboard import async_get_dashboard +from homeassistant.helpers.dispatcher import async_dispatcher_send @pytest.fixture(autouse=True) @@ -57,8 +59,6 @@ async def test_update_entity( mock_dashboard["configured"] = devices_payload await async_get_dashboard(hass).async_refresh() - mock_config_entry.add_to_hass(hass) - with patch( "homeassistant.components.esphome.update.DomainData.get_entry_data", return_value=Mock(available=True, device_info=mock_device_info), @@ -93,3 +93,46 @@ async def test_update_entity( assert len(mock_upload.mock_calls) == 1 assert mock_upload.mock_calls[0][1][0] == "test.yaml" + + +async def test_update_static_info( + hass, + mock_config_entry, + mock_device_info, + mock_dashboard, +): + """Test ESPHome update entity.""" + mock_dashboard["configured"] = [ + { + "name": "test", + "current_version": "1.2.3", + }, + ] + await async_get_dashboard(hass).async_refresh() + + signal_static_info_updated = f"esphome_{mock_config_entry.entry_id}_on_list" + runtime_data = Mock( + available=True, + device_info=mock_device_info, + signal_static_info_updated=signal_static_info_updated, + ) + + with patch( + "homeassistant.components.esphome.update.DomainData.get_entry_data", + return_value=runtime_data, + ): + assert await hass.config_entries.async_forward_entry_setup( + mock_config_entry, "update" + ) + + state = hass.states.get("update.none_firmware") + assert state is not None + assert state.state == "on" + + runtime_data.device_info = dataclasses.replace( + runtime_data.device_info, esphome_version="1.2.3" + ) + async_dispatcher_send(hass, signal_static_info_updated, []) + + state = hass.states.get("update.none_firmware") + assert state.state == "off"