Improve ESPHome dashboard diagnostics (#143914)

This commit is contained in:
J. Nick Koston 2025-04-30 08:19:16 +02:00 committed by GitHub
parent dc02c37413
commit 4ee3290929
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 166 additions and 24 deletions

View File

@ -10,10 +10,18 @@ from homeassistant.const import CONF_PASSWORD
from homeassistant.core import HomeAssistant
from . import CONF_NOISE_PSK
from .const import CONF_DEVICE_NAME
from .dashboard import async_get_dashboard
from .entry_data import ESPHomeConfigEntry
REDACT_KEYS = {CONF_NOISE_PSK, CONF_PASSWORD, "mac_address", "bluetooth_mac_address"}
CONFIGURED_DEVICE_KEYS = (
"configuration",
"current_version",
"deployed_version",
"loaded_integrations",
"target_platform",
)
async def async_get_config_entry_diagnostics(
@ -26,6 +34,9 @@ async def async_get_config_entry_diagnostics(
entry_data = config_entry.runtime_data
device_info = entry_data.device_info
device_name: str | None = (
device_info.name if device_info else config_entry.data.get(CONF_DEVICE_NAME)
)
if (storage_data := await entry_data.store.async_load()) is not None:
diag["storage_data"] = storage_data
@ -45,7 +56,19 @@ async def async_get_config_entry_diagnostics(
"scanner": await scanner.async_diagnostics(),
}
diag_dashboard: dict[str, Any] = {"configured": False}
diag["dashboard"] = diag_dashboard
if dashboard := async_get_dashboard(hass):
diag["dashboard"] = dashboard.addon_slug
diag_dashboard["configured"] = True
diag_dashboard["supports_update"] = dashboard.supports_update
diag_dashboard["last_update_success"] = dashboard.last_update_success
diag_dashboard["last_exception"] = dashboard.last_exception
diag_dashboard["addon"] = dashboard.addon_slug
if device_name and dashboard.data:
diag_dashboard["has_matching_name"] = device_name in dashboard.data
if data := dashboard.data.get(device_name):
diag_dashboard["device"] = {
key: data.get(key) for key in CONFIGURED_DEVICE_KEYS
}
return async_redact_data(diag, REDACT_KEYS)

View File

@ -1,15 +1,38 @@
"""ESPHome test common code."""
from datetime import datetime
from homeassistant.components import assist_satellite
from homeassistant.components.assist_satellite import AssistSatelliteEntity
# pylint: disable-next=hass-component-root-import
from homeassistant.components.esphome import DOMAIN
from homeassistant.components.esphome.assist_satellite import EsphomeAssistSatellite
from homeassistant.components.esphome.coordinator import REFRESH_INTERVAL
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.util import dt as dt_util
from tests.common import 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()
def get_satellite_entity(

View File

@ -26,6 +26,85 @@
'unique_id': '11:22:33:44:55:aa',
'version': 1,
}),
'dashboard': 'mock-slug',
'dashboard': dict({
'addon': 'mock-slug',
'configured': True,
'last_exception': None,
'last_update_success': True,
'supports_update': None,
}),
})
# ---
# name: test_diagnostics_with_dashboard_data
dict({
'config': dict({
'data': dict({
'device_name': 'test',
'host': 'test.local',
'password': '',
'port': 6053,
}),
'disabled_by': None,
'discovery_keys': dict({
}),
'domain': 'esphome',
'minor_version': 1,
'options': dict({
'allow_service_calls': False,
}),
'pref_disable_new_entities': False,
'pref_disable_polling': False,
'source': 'user',
'subentries': list([
]),
'title': 'Mock Title',
'unique_id': '11:22:33:44:55:aa',
'version': 1,
}),
'dashboard': dict({
'addon': 'mock-slug',
'configured': True,
'device': dict({
'configuration': 'test.yaml',
'current_version': '2023.1.0',
'deployed_version': None,
'loaded_integrations': None,
'target_platform': None,
}),
'has_matching_name': True,
'last_exception': None,
'last_update_success': True,
'supports_update': False,
}),
'storage_data': dict({
'api_version': dict({
'major': 99,
'minor': 99,
}),
'device_info': dict({
'bluetooth_mac_address': '',
'bluetooth_proxy_feature_flags': 0,
'compilation_time': '',
'esphome_version': '1.0.0',
'friendly_name': 'Test',
'has_deep_sleep': False,
'legacy_bluetooth_proxy_version': 0,
'legacy_voice_assistant_version': 0,
'mac_address': '**REDACTED**',
'manufacturer': '',
'model': '',
'name': 'test',
'project_name': '',
'project_version': '',
'suggested_area': '',
'uses_password': False,
'voice_assistant_feature_flags': 0,
'webserver_port': 0,
}),
'services': list([
]),
'update': list([
]),
}),
})
# ---

View File

@ -1,6 +1,5 @@
"""Test ESPHome dashboard features."""
from datetime import datetime
from typing import Any
from unittest.mock import patch
@ -8,34 +7,16 @@ from aioesphomeapi import APIClient, DeviceInfo, InvalidAuthAPIError
import pytest
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.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
from . import VALID_NOISE_PSK
from .common import MockDashboardRefresh
from .conftest import MockESPHomeDeviceType
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()
from tests.common import MockConfigEntry
@pytest.mark.usefixtures("init_integration", "mock_dashboard")

View File

@ -3,6 +3,7 @@
from typing import Any
from unittest.mock import ANY
from aioesphomeapi import APIClient
import pytest
from syrupy import SnapshotAssertion
from syrupy.filters import props
@ -10,7 +11,8 @@ from syrupy.filters import props
from homeassistant.components import bluetooth
from homeassistant.core import HomeAssistant
from .conftest import MockESPHomeDevice
from .common import MockDashboardRefresh
from .conftest import MockESPHomeDevice, MockESPHomeDeviceType
from tests.common import MockConfigEntry
from tests.components.diagnostics import get_diagnostics_for_config_entry
@ -31,6 +33,37 @@ async def test_diagnostics(
assert result == snapshot(exclude=props("created_at", "modified_at"))
@pytest.mark.usefixtures("enable_bluetooth")
async def test_diagnostics_with_dashboard_data(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
mock_esphome_device: MockESPHomeDeviceType,
mock_dashboard: dict[str, Any],
mock_client: APIClient,
snapshot: SnapshotAssertion,
) -> None:
"""Test diagnostics for config entry with dashboard data."""
mock_dashboard["configured"].append(
{
"name": "test",
"configuration": "test.yaml",
"current_version": "2023.1.0",
}
)
mock_device = await mock_esphome_device(
mock_client=mock_client,
entity_info=[],
user_service=[],
states=[],
)
await MockDashboardRefresh(hass).async_refresh()
result = await get_diagnostics_for_config_entry(
hass, hass_client, mock_device.entry
)
assert result == snapshot(exclude=props("entry_id", "created_at", "modified_at"))
async def test_diagnostics_with_bluetooth(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
@ -43,6 +76,9 @@ async def test_diagnostics_with_bluetooth(
entry = mock_bluetooth_entry_with_raw_adv.entry
result = await get_diagnostics_for_config_entry(hass, hass_client, entry)
assert result == {
"dashboard": {
"configured": False,
},
"bluetooth": {
"available": True,
"connections_free": 0,