mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Quality improvements for the ESPHome dashboard coordinator (#143619)
This commit is contained in:
parent
2abe2f7d59
commit
fab70a80bb
@ -5,43 +5,38 @@ from __future__ import annotations
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
from awesomeversion import AwesomeVersion
|
from awesomeversion import AwesomeVersion
|
||||||
from esphome_dashboard_api import ConfiguredDevice, ESPHomeDashboardAPI
|
from esphome_dashboard_api import ConfiguredDevice, ESPHomeDashboardAPI
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
MIN_VERSION_SUPPORTS_UPDATE = AwesomeVersion("2023.1.0")
|
MIN_VERSION_SUPPORTS_UPDATE = AwesomeVersion("2023.1.0")
|
||||||
|
REFRESH_INTERVAL = timedelta(minutes=5)
|
||||||
|
|
||||||
|
|
||||||
class ESPHomeDashboardCoordinator(DataUpdateCoordinator[dict[str, ConfiguredDevice]]):
|
class ESPHomeDashboardCoordinator(DataUpdateCoordinator[dict[str, ConfiguredDevice]]):
|
||||||
"""Class to interact with the ESPHome dashboard."""
|
"""Class to interact with the ESPHome dashboard."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, hass: HomeAssistant, addon_slug: str, url: str) -> None:
|
||||||
self,
|
"""Initialize the dashboard coordinator."""
|
||||||
hass: HomeAssistant,
|
|
||||||
addon_slug: str,
|
|
||||||
url: str,
|
|
||||||
session: aiohttp.ClientSession,
|
|
||||||
) -> None:
|
|
||||||
"""Initialize."""
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
hass,
|
hass,
|
||||||
_LOGGER,
|
_LOGGER,
|
||||||
config_entry=None,
|
config_entry=None,
|
||||||
name="ESPHome Dashboard",
|
name="ESPHome Dashboard",
|
||||||
update_interval=timedelta(minutes=5),
|
update_interval=REFRESH_INTERVAL,
|
||||||
always_update=False,
|
always_update=False,
|
||||||
)
|
)
|
||||||
self.addon_slug = addon_slug
|
self.addon_slug = addon_slug
|
||||||
self.url = url
|
self.url = url
|
||||||
self.api = ESPHomeDashboardAPI(url, session)
|
self.api = ESPHomeDashboardAPI(url, async_get_clientsession(hass))
|
||||||
self.supports_update: bool | None = None
|
self.supports_update: bool | None = None
|
||||||
|
|
||||||
async def _async_update_data(self) -> dict:
|
async def _async_update_data(self) -> dict[str, ConfiguredDevice]:
|
||||||
"""Fetch device data."""
|
"""Fetch device data."""
|
||||||
devices = await self.api.get_devices()
|
devices = await self.api.get_devices()
|
||||||
configured_devices = devices["configured"]
|
configured_devices = devices["configured"]
|
||||||
|
@ -9,7 +9,6 @@ from typing import Any
|
|||||||
from homeassistant.config_entries import SOURCE_REAUTH
|
from homeassistant.config_entries import SOURCE_REAUTH
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
|
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.hassio import is_hassio
|
||||||
from homeassistant.helpers.singleton import singleton
|
from homeassistant.helpers.singleton import singleton
|
||||||
from homeassistant.helpers.storage import Store
|
from homeassistant.helpers.storage import Store
|
||||||
@ -104,9 +103,7 @@ class ESPHomeDashboardManager:
|
|||||||
self._cancel_shutdown = None
|
self._cancel_shutdown = None
|
||||||
self._current_dashboard = None
|
self._current_dashboard = None
|
||||||
|
|
||||||
dashboard = ESPHomeDashboardCoordinator(
|
dashboard = ESPHomeDashboardCoordinator(hass, addon_slug, url)
|
||||||
hass, addon_slug, url, async_get_clientsession(hass)
|
|
||||||
)
|
|
||||||
await dashboard.async_request_refresh()
|
await dashboard.async_request_refresh()
|
||||||
|
|
||||||
self._current_dashboard = dashboard
|
self._current_dashboard = dashboard
|
||||||
|
@ -70,7 +70,6 @@ async def async_setup_entry(
|
|||||||
@callback
|
@callback
|
||||||
def _async_setup_update_entity() -> None:
|
def _async_setup_update_entity() -> None:
|
||||||
"""Set up the update entity."""
|
"""Set up the update entity."""
|
||||||
nonlocal unsubs
|
|
||||||
assert dashboard is not None
|
assert dashboard is not None
|
||||||
# Keep listening until device is available
|
# Keep listening until device is available
|
||||||
if not entry_data.available or not dashboard.last_update_success:
|
if not entry_data.available or not dashboard.last_update_success:
|
||||||
@ -95,10 +94,12 @@ async def async_setup_entry(
|
|||||||
_async_setup_update_entity()
|
_async_setup_update_entity()
|
||||||
return
|
return
|
||||||
|
|
||||||
unsubs = [
|
unsubs.extend(
|
||||||
|
[
|
||||||
entry_data.async_subscribe_device_updated(_async_setup_update_entity),
|
entry_data.async_subscribe_device_updated(_async_setup_update_entity),
|
||||||
dashboard.async_add_listener(_async_setup_update_entity),
|
dashboard.async_add_listener(_async_setup_update_entity),
|
||||||
]
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ESPHomeDashboardUpdateEntity(
|
class ESPHomeDashboardUpdateEntity(
|
||||||
|
@ -1,26 +1,46 @@
|
|||||||
"""Test ESPHome dashboard features."""
|
"""Test ESPHome dashboard features."""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from aioesphomeapi import DeviceInfo, InvalidAuthAPIError
|
from aioesphomeapi import APIClient, DeviceInfo, InvalidAuthAPIError
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.esphome import CONF_NOISE_PSK, DOMAIN, dashboard
|
from homeassistant.components.esphome import CONF_NOISE_PSK, DOMAIN, dashboard
|
||||||
|
from homeassistant.components.esphome.coordinator import REFRESH_INTERVAL
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.data_entry_flow import FlowResultType
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from . import VALID_NOISE_PSK
|
from . import VALID_NOISE_PSK
|
||||||
|
from .conftest import MockESPHomeDeviceType
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
|
class MockDashboardRefresh:
|
||||||
|
"""Mock dashboard refresh."""
|
||||||
|
|
||||||
|
def __init__(self, hass: HomeAssistant) -> None:
|
||||||
|
"""Initialize the mock dashboard refresh."""
|
||||||
|
self.hass = hass
|
||||||
|
self.last_time: datetime | None = None
|
||||||
|
|
||||||
|
async def async_refresh(self) -> None:
|
||||||
|
"""Refresh the dashboard."""
|
||||||
|
if self.last_time is None:
|
||||||
|
self.last_time = dt_util.utcnow()
|
||||||
|
self.last_time += REFRESH_INTERVAL
|
||||||
|
async_fire_time_changed(self.hass, self.last_time)
|
||||||
|
await self.hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("init_integration", "mock_dashboard")
|
||||||
async def test_dashboard_storage(
|
async def test_dashboard_storage(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
init_integration,
|
|
||||||
mock_dashboard: dict[str, Any],
|
|
||||||
hass_storage: dict[str, Any],
|
hass_storage: dict[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test dashboard storage."""
|
"""Test dashboard storage."""
|
||||||
@ -165,8 +185,9 @@ async def test_setup_dashboard_fails_when_already_setup(
|
|||||||
assert len(mock_setup.mock_calls) == 1
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("mock_dashboard")
|
||||||
async def test_new_info_reload_config_entries(
|
async def test_new_info_reload_config_entries(
|
||||||
hass: HomeAssistant, init_integration, mock_dashboard
|
hass: HomeAssistant, init_integration: MockConfigEntry
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test config entries are reloaded when new info is set."""
|
"""Test config entries are reloaded when new info is set."""
|
||||||
assert init_integration.state is ConfigEntryState.LOADED
|
assert init_integration.state is ConfigEntryState.LOADED
|
||||||
@ -185,7 +206,10 @@ async def test_new_info_reload_config_entries(
|
|||||||
|
|
||||||
|
|
||||||
async def test_new_dashboard_fix_reauth(
|
async def test_new_dashboard_fix_reauth(
|
||||||
hass: HomeAssistant, mock_client, mock_config_entry: MockConfigEntry, mock_dashboard
|
hass: HomeAssistant,
|
||||||
|
mock_client: APIClient,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
mock_dashboard: dict[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test config entries waiting for reauth are triggered."""
|
"""Test config entries waiting for reauth are triggered."""
|
||||||
mock_client.device_info.side_effect = (
|
mock_client.device_info.side_effect = (
|
||||||
@ -209,7 +233,7 @@ async def test_new_dashboard_fix_reauth(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
await dashboard.async_get_dashboard(hass).async_refresh()
|
await MockDashboardRefresh(hass).async_refresh()
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch(
|
patch(
|
||||||
@ -229,15 +253,29 @@ async def test_new_dashboard_fix_reauth(
|
|||||||
|
|
||||||
|
|
||||||
async def test_dashboard_supports_update(
|
async def test_dashboard_supports_update(
|
||||||
hass: HomeAssistant, mock_dashboard: dict[str, Any]
|
hass: HomeAssistant,
|
||||||
|
mock_dashboard: dict[str, Any],
|
||||||
|
mock_client: APIClient,
|
||||||
|
mock_esphome_device: MockESPHomeDeviceType,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test dashboard supports update."""
|
"""Test dashboard supports update."""
|
||||||
dash = dashboard.async_get_dashboard(hass)
|
dash = dashboard.async_get_dashboard(hass)
|
||||||
|
mock_refresh = MockDashboardRefresh(hass)
|
||||||
|
|
||||||
|
entity_info = []
|
||||||
|
states = []
|
||||||
|
user_service = []
|
||||||
|
await mock_esphome_device(
|
||||||
|
mock_client=mock_client,
|
||||||
|
entity_info=entity_info,
|
||||||
|
user_service=user_service,
|
||||||
|
states=states,
|
||||||
|
)
|
||||||
|
|
||||||
# No data
|
# No data
|
||||||
assert not dash.supports_update
|
assert not dash.supports_update
|
||||||
|
|
||||||
await dash.async_refresh()
|
await mock_refresh.async_refresh()
|
||||||
assert dash.supports_update is None
|
assert dash.supports_update is None
|
||||||
|
|
||||||
# supported version
|
# supported version
|
||||||
@ -248,12 +286,44 @@ async def test_dashboard_supports_update(
|
|||||||
"current_version": "2023.2.0-dev",
|
"current_version": "2023.2.0-dev",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
await dash.async_refresh()
|
|
||||||
|
await mock_refresh.async_refresh()
|
||||||
assert dash.supports_update is True
|
assert dash.supports_update is True
|
||||||
|
|
||||||
# unsupported version
|
|
||||||
dash.supports_update = None
|
|
||||||
mock_dashboard["configured"][0]["current_version"] = "2023.1.0"
|
|
||||||
await dash.async_refresh()
|
|
||||||
|
|
||||||
|
async def test_dashboard_unsupported_version(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_dashboard: dict[str, Any],
|
||||||
|
mock_client: APIClient,
|
||||||
|
mock_esphome_device: MockESPHomeDeviceType,
|
||||||
|
) -> None:
|
||||||
|
"""Test dashboard with unsupported version."""
|
||||||
|
dash = dashboard.async_get_dashboard(hass)
|
||||||
|
mock_refresh = MockDashboardRefresh(hass)
|
||||||
|
|
||||||
|
entity_info = []
|
||||||
|
states = []
|
||||||
|
user_service = []
|
||||||
|
await mock_esphome_device(
|
||||||
|
mock_client=mock_client,
|
||||||
|
entity_info=entity_info,
|
||||||
|
user_service=user_service,
|
||||||
|
states=states,
|
||||||
|
)
|
||||||
|
|
||||||
|
# No data
|
||||||
|
assert not dash.supports_update
|
||||||
|
|
||||||
|
await mock_refresh.async_refresh()
|
||||||
|
assert dash.supports_update is None
|
||||||
|
|
||||||
|
# unsupported version
|
||||||
|
mock_dashboard["configured"].append(
|
||||||
|
{
|
||||||
|
"name": "test",
|
||||||
|
"configuration": "test.yaml",
|
||||||
|
"current_version": "2023.1.0",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
await mock_refresh.async_refresh()
|
||||||
assert dash.supports_update is False
|
assert dash.supports_update is False
|
||||||
|
Loading…
x
Reference in New Issue
Block a user