Move esphome coordinator to separate module (#117427)
This commit is contained in:
parent
92bb76ed24
commit
2e155f4de5
5 changed files with 84 additions and 71 deletions
57
homeassistant/components/esphome/coordinator.py
Normal file
57
homeassistant/components/esphome/coordinator.py
Normal 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}
|
|
@ -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}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue