Move esphome coordinator to separate module (#117427)

This commit is contained in:
epenet 2024-05-14 15:16:47 +02:00 committed by GitHub
parent 92bb76ed24
commit 2e155f4de5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 84 additions and 71 deletions

View file

@ -0,0 +1,57 @@
"""Coordinator to interact with an ESPHome dashboard."""
from __future__ import annotations
from datetime import timedelta
import logging
import aiohttp
from awesomeversion import AwesomeVersion
from esphome_dashboard_api import ConfiguredDevice, ESPHomeDashboardAPI
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
MIN_VERSION_SUPPORTS_UPDATE = AwesomeVersion("2023.1.0")
class ESPHomeDashboardCoordinator(DataUpdateCoordinator[dict[str, ConfiguredDevice]]):
"""Class to interact with the ESPHome dashboard."""
def __init__(
self,
hass: HomeAssistant,
addon_slug: str,
url: str,
session: aiohttp.ClientSession,
) -> None:
"""Initialize."""
super().__init__(
hass,
_LOGGER,
name="ESPHome Dashboard",
update_interval=timedelta(minutes=5),
always_update=False,
)
self.addon_slug = addon_slug
self.url = url
self.api = ESPHomeDashboardAPI(url, session)
self.supports_update: bool | None = None
async def _async_update_data(self) -> dict:
"""Fetch device data."""
devices = await self.api.get_devices()
configured_devices = devices["configured"]
if (
self.supports_update is None
and configured_devices
and (current_version := configured_devices[0].get("current_version"))
):
self.supports_update = (
AwesomeVersion(current_version) > MIN_VERSION_SUPPORTS_UPDATE
)
return {dev["name"]: dev for dev in configured_devices}

View file

@ -1,25 +1,20 @@
"""Files to interact with a the ESPHome dashboard."""
"""Files to interact with an ESPHome dashboard."""
from __future__ import annotations
import asyncio
from datetime import timedelta
import logging
from typing import Any
import aiohttp
from awesomeversion import AwesomeVersion
from esphome_dashboard_api import ConfiguredDevice, ESPHomeDashboardAPI
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
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.singleton import singleton
from homeassistant.helpers.storage import Store
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN
from .coordinator import ESPHomeDashboardCoordinator
_LOGGER = logging.getLogger(__name__)
@ -29,8 +24,6 @@ KEY_DASHBOARD_MANAGER = "esphome_dashboard_manager"
STORAGE_KEY = "esphome.dashboard"
STORAGE_VERSION = 1
MIN_VERSION_SUPPORTS_UPDATE = AwesomeVersion("2023.1.0")
async def async_setup(hass: HomeAssistant) -> None:
"""Set up the ESPHome dashboard."""
@ -58,7 +51,7 @@ class ESPHomeDashboardManager:
self._hass = hass
self._store: Store[dict[str, Any]] = Store(hass, STORAGE_VERSION, STORAGE_KEY)
self._data: dict[str, Any] | None = None
self._current_dashboard: ESPHomeDashboard | None = None
self._current_dashboard: ESPHomeDashboardCoordinator | None = None
self._cancel_shutdown: CALLBACK_TYPE | None = None
async def async_setup(self) -> None:
@ -70,7 +63,7 @@ class ESPHomeDashboardManager:
)
@callback
def async_get(self) -> ESPHomeDashboard | None:
def async_get(self) -> ESPHomeDashboardCoordinator | None:
"""Get the current dashboard."""
return self._current_dashboard
@ -92,7 +85,7 @@ class ESPHomeDashboardManager:
self._cancel_shutdown = None
self._current_dashboard = None
dashboard = ESPHomeDashboard(
dashboard = ESPHomeDashboardCoordinator(
hass, addon_slug, url, async_get_clientsession(hass)
)
await dashboard.async_request_refresh()
@ -138,7 +131,7 @@ class ESPHomeDashboardManager:
@callback
def async_get_dashboard(hass: HomeAssistant) -> ESPHomeDashboard | None:
def async_get_dashboard(hass: HomeAssistant) -> ESPHomeDashboardCoordinator | None:
"""Get an instance of the dashboard if set.
This is only safe to call after `async_setup` has been completed.
@ -157,43 +150,3 @@ async def async_set_dashboard_info(
"""Set the dashboard info."""
manager = await async_get_or_create_dashboard_manager(hass)
await manager.async_set_dashboard_info(addon_slug, host, port)
class ESPHomeDashboard(DataUpdateCoordinator[dict[str, ConfiguredDevice]]): # pylint: disable=hass-enforce-coordinator-module
"""Class to interact with the ESPHome dashboard."""
def __init__(
self,
hass: HomeAssistant,
addon_slug: str,
url: str,
session: aiohttp.ClientSession,
) -> None:
"""Initialize."""
super().__init__(
hass,
_LOGGER,
name="ESPHome Dashboard",
update_interval=timedelta(minutes=5),
always_update=False,
)
self.addon_slug = addon_slug
self.url = url
self.api = ESPHomeDashboardAPI(url, session)
self.supports_update: bool | None = None
async def _async_update_data(self) -> dict:
"""Fetch device data."""
devices = await self.api.get_devices()
configured_devices = devices["configured"]
if (
self.supports_update is None
and configured_devices
and (current_version := configured_devices[0].get("current_version"))
):
self.supports_update = (
AwesomeVersion(current_version) > MIN_VERSION_SUPPORTS_UPDATE
)
return {dev["name"]: dev for dev in configured_devices}

View file

@ -20,7 +20,8 @@ from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .dashboard import ESPHomeDashboard, async_get_dashboard
from .coordinator import ESPHomeDashboardCoordinator
from .dashboard import async_get_dashboard
from .domain_data import DomainData
from .entry_data import RuntimeEntryData
@ -65,7 +66,7 @@ async def async_setup_entry(
]
class ESPHomeUpdateEntity(CoordinatorEntity[ESPHomeDashboard], UpdateEntity):
class ESPHomeUpdateEntity(CoordinatorEntity[ESPHomeDashboardCoordinator], UpdateEntity):
"""Defines an ESPHome update entity."""
_attr_has_entity_name = True
@ -75,7 +76,7 @@ class ESPHomeUpdateEntity(CoordinatorEntity[ESPHomeDashboard], UpdateEntity):
_attr_release_url = "https://esphome.io/changelog/"
def __init__(
self, entry_data: RuntimeEntryData, coordinator: ESPHomeDashboard
self, entry_data: RuntimeEntryData, coordinator: ESPHomeDashboardCoordinator
) -> None:
"""Initialize the update entity."""
super().__init__(coordinator=coordinator)

View file

@ -338,7 +338,7 @@ async def test_user_dashboard_has_wrong_key(
]
with patch(
"homeassistant.components.esphome.dashboard.ESPHomeDashboardAPI.get_encryption_key",
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_encryption_key",
return_value=WRONG_NOISE_PSK,
):
result = await hass.config_entries.flow.async_init(
@ -393,7 +393,7 @@ async def test_user_discovers_name_and_gets_key_from_dashboard(
await dashboard.async_get_dashboard(hass).async_refresh()
with patch(
"homeassistant.components.esphome.dashboard.ESPHomeDashboardAPI.get_encryption_key",
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_encryption_key",
return_value=VALID_NOISE_PSK,
):
result = await hass.config_entries.flow.async_init(
@ -446,7 +446,7 @@ async def test_user_discovers_name_and_gets_key_from_dashboard_fails(
await dashboard.async_get_dashboard(hass).async_refresh()
with patch(
"homeassistant.components.esphome.dashboard.ESPHomeDashboardAPI.get_encryption_key",
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_encryption_key",
side_effect=dashboard_exception,
):
result = await hass.config_entries.flow.async_init(
@ -859,7 +859,7 @@ async def test_reauth_fixed_via_dashboard(
await dashboard.async_get_dashboard(hass).async_refresh()
with patch(
"homeassistant.components.esphome.dashboard.ESPHomeDashboardAPI.get_encryption_key",
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_encryption_key",
return_value=VALID_NOISE_PSK,
) as mock_get_encryption_key:
result = await hass.config_entries.flow.async_init(
@ -902,7 +902,7 @@ async def test_reauth_fixed_via_dashboard_add_encryption_remove_password(
await dashboard.async_get_dashboard(hass).async_refresh()
with patch(
"homeassistant.components.esphome.dashboard.ESPHomeDashboardAPI.get_encryption_key",
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_encryption_key",
return_value=VALID_NOISE_PSK,
) as mock_get_encryption_key:
result = await hass.config_entries.flow.async_init(
@ -990,7 +990,7 @@ async def test_reauth_fixed_via_dashboard_at_confirm(
await dashboard.async_get_dashboard(hass).async_refresh()
with patch(
"homeassistant.components.esphome.dashboard.ESPHomeDashboardAPI.get_encryption_key",
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_encryption_key",
return_value=VALID_NOISE_PSK,
) as mock_get_encryption_key:
# We just fetch the form
@ -1211,7 +1211,7 @@ async def test_zeroconf_encryption_key_via_dashboard(
]
with patch(
"homeassistant.components.esphome.dashboard.ESPHomeDashboardAPI.get_encryption_key",
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_encryption_key",
return_value=VALID_NOISE_PSK,
) as mock_get_encryption_key:
result = await hass.config_entries.flow.async_configure(
@ -1277,7 +1277,7 @@ async def test_zeroconf_encryption_key_via_dashboard_with_api_encryption_prop(
]
with patch(
"homeassistant.components.esphome.dashboard.ESPHomeDashboardAPI.get_encryption_key",
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_encryption_key",
return_value=VALID_NOISE_PSK,
) as mock_get_encryption_key:
result = await hass.config_entries.flow.async_configure(

View file

@ -4,7 +4,7 @@ from unittest.mock import patch
from aioesphomeapi import DeviceInfo, InvalidAuthAPIError
from homeassistant.components.esphome import CONF_NOISE_PSK, dashboard
from homeassistant.components.esphome import CONF_NOISE_PSK, coordinator, dashboard
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
@ -56,7 +56,7 @@ async def test_restore_dashboard_storage_end_to_end(
"data": {"info": {"addon_slug": "test-slug", "host": "new-host", "port": 6052}},
}
with patch(
"homeassistant.components.esphome.dashboard.ESPHomeDashboardAPI"
"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()
@ -69,7 +69,7 @@ async def test_setup_dashboard_fails(
) -> MockConfigEntry:
"""Test that nothing is stored on failed dashboard setup when there was no dashboard before."""
with patch.object(
dashboard.ESPHomeDashboardAPI, "get_devices", side_effect=TimeoutError
coordinator.ESPHomeDashboardAPI, "get_devices", side_effect=TimeoutError
) as mock_get_devices:
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
@ -86,7 +86,9 @@ async def test_setup_dashboard_fails_when_already_setup(
hass: HomeAssistant, mock_config_entry: MockConfigEntry, hass_storage
) -> MockConfigEntry:
"""Test failed dashboard setup still reloads entries if one existed before."""
with patch.object(dashboard.ESPHomeDashboardAPI, "get_devices") as mock_get_devices:
with patch.object(
coordinator.ESPHomeDashboardAPI, "get_devices"
) as mock_get_devices:
await dashboard.async_set_dashboard_info(
hass, "test-slug", "working-host", 6052
)
@ -100,7 +102,7 @@ async def test_setup_dashboard_fails_when_already_setup(
with (
patch.object(
dashboard.ESPHomeDashboardAPI, "get_devices", side_effect=TimeoutError
coordinator.ESPHomeDashboardAPI, "get_devices", side_effect=TimeoutError
) as mock_get_devices,
patch(
"homeassistant.components.esphome.async_setup_entry", return_value=True
@ -145,7 +147,7 @@ async def test_new_dashboard_fix_reauth(
)
with patch(
"homeassistant.components.esphome.dashboard.ESPHomeDashboardAPI.get_encryption_key",
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_encryption_key",
return_value=VALID_NOISE_PSK,
) as mock_get_encryption_key:
result = await hass.config_entries.flow.async_init(
@ -171,7 +173,7 @@ async def test_new_dashboard_fix_reauth(
with (
patch(
"homeassistant.components.esphome.dashboard.ESPHomeDashboardAPI.get_encryption_key",
"homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_encryption_key",
return_value=VALID_NOISE_PSK,
) as mock_get_encryption_key,
patch(