Home Assistant Unofficial Reference 2024.12.1
dashboard.py
Go to the documentation of this file.
1 """Files to interact with an ESPHome dashboard."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 import logging
7 from typing import Any
8 
9 from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
10 from homeassistant.const import EVENT_HOMEASSISTANT_STOP
11 from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
12 from homeassistant.helpers.aiohttp_client import async_get_clientsession
13 from homeassistant.helpers.singleton import singleton
14 from homeassistant.helpers.storage import Store
15 
16 from .const import DOMAIN
17 from .coordinator import ESPHomeDashboardCoordinator
18 
19 _LOGGER = logging.getLogger(__name__)
20 
21 
22 KEY_DASHBOARD_MANAGER = "esphome_dashboard_manager"
23 
24 STORAGE_KEY = "esphome.dashboard"
25 STORAGE_VERSION = 1
26 
27 
28 async def async_setup(hass: HomeAssistant) -> None:
29  """Set up the ESPHome dashboard."""
30  # Try to restore the dashboard manager from storage
31  # to avoid reloading every ESPHome config entry after
32  # Home Assistant starts and the dashboard is discovered.
34 
35 
36 @singleton(KEY_DASHBOARD_MANAGER)
38  hass: HomeAssistant,
39 ) -> ESPHomeDashboardManager:
40  """Get the dashboard manager or create it."""
41  manager = ESPHomeDashboardManager(hass)
42  await manager.async_setup()
43  return manager
44 
45 
47  """Class to manage the dashboard and restore it from storage."""
48 
49  def __init__(self, hass: HomeAssistant) -> None:
50  """Initialize the dashboard manager."""
51  self._hass_hass = hass
52  self._store: Store[dict[str, Any]] = Store(hass, STORAGE_VERSION, STORAGE_KEY)
53  self._data_data: dict[str, Any] | None = None
54  self._current_dashboard_current_dashboard: ESPHomeDashboardCoordinator | None = None
55  self._cancel_shutdown_cancel_shutdown: CALLBACK_TYPE | None = None
56 
57  async def async_setup(self) -> None:
58  """Restore the dashboard from storage."""
59  self._data_data = await self._store.async_load()
60  if (data := self._data_data) and (info := data.get("info")):
61  await self.async_set_dashboard_infoasync_set_dashboard_info(
62  info["addon_slug"], info["host"], info["port"]
63  )
64 
65  @callback
66  def async_get(self) -> ESPHomeDashboardCoordinator | None:
67  """Get the current dashboard."""
68  return self._current_dashboard_current_dashboard
69 
71  self, addon_slug: str, host: str, port: int
72  ) -> None:
73  """Set the dashboard info."""
74  url = f"http://{host}:{port}"
75  hass = self._hass_hass
76 
77  if cur_dashboard := self._current_dashboard_current_dashboard:
78  if cur_dashboard.addon_slug == addon_slug and cur_dashboard.url == url:
79  # Do nothing if we already have this data.
80  return
81  # Clear and make way for new dashboard
82  await cur_dashboard.async_shutdown()
83  if self._cancel_shutdown_cancel_shutdown is not None:
84  self._cancel_shutdown_cancel_shutdown()
85  self._cancel_shutdown_cancel_shutdown = None
86  self._current_dashboard_current_dashboard = None
87 
88  dashboard = ESPHomeDashboardCoordinator(
89  hass, addon_slug, url, async_get_clientsession(hass)
90  )
91  await dashboard.async_request_refresh()
92 
93  self._current_dashboard_current_dashboard = dashboard
94 
95  async def on_hass_stop(_: Event) -> None:
96  await dashboard.async_shutdown()
97 
98  self._cancel_shutdown_cancel_shutdown = hass.bus.async_listen_once(
99  EVENT_HOMEASSISTANT_STOP, on_hass_stop
100  )
101 
102  new_data = {"info": {"addon_slug": addon_slug, "host": host, "port": port}}
103  if self._data_data != new_data:
104  await self._store.async_save(new_data)
105 
106  reloads = [
107  hass.config_entries.async_reload(entry.entry_id)
108  for entry in hass.config_entries.async_entries(DOMAIN)
109  if entry.state is ConfigEntryState.LOADED
110  ]
111  # Re-auth flows will check the dashboard for encryption key when the form is requested
112  # but we only trigger reauth if the dashboard is available.
113  if dashboard.last_update_success:
114  reauths = [
115  hass.config_entries.flow.async_configure(flow["flow_id"])
116  for flow in hass.config_entries.flow.async_progress()
117  if flow["handler"] == DOMAIN
118  and flow["context"]["source"] == SOURCE_REAUTH
119  ]
120  else:
121  reauths = []
122  _LOGGER.error(
123  "Dashboard unavailable; skipping reauth: %s", dashboard.last_exception
124  )
125 
126  _LOGGER.debug(
127  "Reloading %d and re-authenticating %d", len(reloads), len(reauths)
128  )
129  if reloads or reauths:
130  await asyncio.gather(*reloads, *reauths)
131 
132 
133 @callback
134 def async_get_dashboard(hass: HomeAssistant) -> ESPHomeDashboardCoordinator | None:
135  """Get an instance of the dashboard if set.
136 
137  This is only safe to call after `async_setup` has been completed.
138 
139  It should not be called from the config flow because there is a race
140  where manager can be an asyncio.Event instead of the actual manager
141  because the singleton decorator is not yet done.
142  """
143  manager: ESPHomeDashboardManager | None = hass.data.get(KEY_DASHBOARD_MANAGER)
144  return manager.async_get() if manager else None
145 
146 
148  hass: HomeAssistant, addon_slug: str, host: str, port: int
149 ) -> None:
150  """Set the dashboard info."""
151  manager = await async_get_or_create_dashboard_manager(hass)
152  await manager.async_set_dashboard_info(addon_slug, host, port)
None async_set_dashboard_info(self, str addon_slug, str host, int port)
Definition: dashboard.py:72
ESPHomeDashboardManager async_get_or_create_dashboard_manager(HomeAssistant hass)
Definition: dashboard.py:39
None async_set_dashboard_info(HomeAssistant hass, str addon_slug, str host, int port)
Definition: dashboard.py:149
None async_setup(HomeAssistant hass)
Definition: dashboard.py:28
ESPHomeDashboardCoordinator|None async_get_dashboard(HomeAssistant hass)
Definition: dashboard.py:134
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)
None async_load(HomeAssistant hass)
None async_save(self, _T data)
Definition: storage.py:424