diff --git a/homeassistant/components/esphome/dashboard.py b/homeassistant/components/esphome/dashboard.py index 290feec1e2a..bbe4698f278 100644 --- a/homeassistant/components/esphome/dashboard.py +++ b/homeassistant/components/esphome/dashboard.py @@ -10,6 +10,7 @@ from homeassistant.config_entries import SOURCE_REAUTH from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.hassio import is_hassio from homeassistant.helpers.singleton import singleton from homeassistant.helpers.storage import Store from homeassistant.util.hass_dict import HassKey @@ -60,11 +61,26 @@ class ESPHomeDashboardManager: async def async_setup(self) -> None: """Restore the dashboard from storage.""" self._data = await self._store.async_load() - if (data := self._data) and (info := data.get("info")): - await self.async_set_dashboard_info( - info["addon_slug"], info["host"], info["port"] + if not (data := self._data) or not (info := data.get("info")): + return + if is_hassio(self._hass): + from homeassistant.components.hassio import ( # pylint: disable=import-outside-toplevel + get_addons_info, ) + if (addons := get_addons_info(self._hass)) is not None and info[ + "addon_slug" + ] not in addons: + # The addon is not installed anymore, but it make come back + # so we don't want to remove the dashboard, but for now + # we don't want to use it. + _LOGGER.debug("Addon %s is no longer installed", info["addon_slug"]) + return + + await self.async_set_dashboard_info( + info["addon_slug"], info["host"], info["port"] + ) + @callback def async_get(self) -> ESPHomeDashboardCoordinator | None: """Get the current dashboard.""" diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index b82d90b10e5..84b7472ad2b 100644 --- a/homeassistant/components/esphome/manifest.json +++ b/homeassistant/components/esphome/manifest.json @@ -1,7 +1,7 @@ { "domain": "esphome", "name": "ESPHome", - "after_dependencies": ["zeroconf", "tag"], + "after_dependencies": ["hassio", "zeroconf", "tag"], "codeowners": ["@OttoWinter", "@jesserockz", "@kbx81", "@bdraco"], "config_flow": true, "dependencies": ["assist_pipeline", "bluetooth", "intent", "ffmpeg", "http"], diff --git a/tests/components/esphome/test_dashboard.py b/tests/components/esphome/test_dashboard.py index 1641804e458..c3913c3ba9b 100644 --- a/tests/components/esphome/test_dashboard.py +++ b/tests/components/esphome/test_dashboard.py @@ -4,6 +4,7 @@ from typing import Any from unittest.mock import patch from aioesphomeapi import DeviceInfo, InvalidAuthAPIError +import pytest from homeassistant.components.esphome import CONF_NOISE_PSK, coordinator, dashboard from homeassistant.config_entries import ConfigEntryState @@ -63,15 +64,52 @@ async def test_restore_dashboard_storage_end_to_end( "key": dashboard.STORAGE_KEY, "data": {"info": {"addon_slug": "test-slug", "host": "new-host", "port": 6052}}, } - with patch( - "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI" - ) as mock_dashboard_api: + with ( + patch( + "homeassistant.components.esphome.dashboard.is_hassio", return_value=False + ), + patch( + "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI" + ) as mock_dashboard_api, + ): await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() assert mock_config_entry.state is ConfigEntryState.LOADED assert mock_dashboard_api.mock_calls[0][1][0] == "http://new-host:6052" +async def test_restore_dashboard_storage_skipped_if_addon_uninstalled( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + hass_storage: dict[str, Any], + caplog: pytest.LogCaptureFixture, +) -> None: + """Restore dashboard restore is skipped if the addon is uninstalled.""" + hass_storage[dashboard.STORAGE_KEY] = { + "version": dashboard.STORAGE_VERSION, + "minor_version": dashboard.STORAGE_VERSION, + "key": dashboard.STORAGE_KEY, + "data": {"info": {"addon_slug": "test-slug", "host": "new-host", "port": 6052}}, + } + with ( + patch( + "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI" + ) as mock_dashboard_api, + patch( + "homeassistant.components.esphome.dashboard.is_hassio", return_value=True + ), + patch( + "homeassistant.components.hassio.get_addons_info", + return_value={}, + ), + ): + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + assert mock_config_entry.state is ConfigEntryState.LOADED + assert "test-slug is no longer installed" in caplog.text + assert not mock_dashboard_api.called + + async def test_setup_dashboard_fails( hass: HomeAssistant, mock_config_entry: MockConfigEntry,