mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 14:17:45 +00:00
Improve ESPHome dashboard diagnostics (#143914)
This commit is contained in:
parent
dc02c37413
commit
4ee3290929
@ -10,10 +10,18 @@ from homeassistant.const import CONF_PASSWORD
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from . import CONF_NOISE_PSK
|
from . import CONF_NOISE_PSK
|
||||||
|
from .const import CONF_DEVICE_NAME
|
||||||
from .dashboard import async_get_dashboard
|
from .dashboard import async_get_dashboard
|
||||||
from .entry_data import ESPHomeConfigEntry
|
from .entry_data import ESPHomeConfigEntry
|
||||||
|
|
||||||
REDACT_KEYS = {CONF_NOISE_PSK, CONF_PASSWORD, "mac_address", "bluetooth_mac_address"}
|
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(
|
async def async_get_config_entry_diagnostics(
|
||||||
@ -26,6 +34,9 @@ async def async_get_config_entry_diagnostics(
|
|||||||
|
|
||||||
entry_data = config_entry.runtime_data
|
entry_data = config_entry.runtime_data
|
||||||
device_info = entry_data.device_info
|
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:
|
if (storage_data := await entry_data.store.async_load()) is not None:
|
||||||
diag["storage_data"] = storage_data
|
diag["storage_data"] = storage_data
|
||||||
@ -45,7 +56,19 @@ async def async_get_config_entry_diagnostics(
|
|||||||
"scanner": await scanner.async_diagnostics(),
|
"scanner": await scanner.async_diagnostics(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diag_dashboard: dict[str, Any] = {"configured": False}
|
||||||
|
diag["dashboard"] = diag_dashboard
|
||||||
if dashboard := async_get_dashboard(hass):
|
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)
|
return async_redact_data(diag, REDACT_KEYS)
|
||||||
|
@ -1,15 +1,38 @@
|
|||||||
"""ESPHome test common code."""
|
"""ESPHome test common code."""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from homeassistant.components import assist_satellite
|
from homeassistant.components import assist_satellite
|
||||||
from homeassistant.components.assist_satellite import AssistSatelliteEntity
|
from homeassistant.components.assist_satellite import AssistSatelliteEntity
|
||||||
|
|
||||||
# pylint: disable-next=hass-component-root-import
|
# pylint: disable-next=hass-component-root-import
|
||||||
from homeassistant.components.esphome import DOMAIN
|
from homeassistant.components.esphome import DOMAIN
|
||||||
from homeassistant.components.esphome.assist_satellite import EsphomeAssistSatellite
|
from homeassistant.components.esphome.assist_satellite import EsphomeAssistSatellite
|
||||||
|
from homeassistant.components.esphome.coordinator import REFRESH_INTERVAL
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
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(
|
def get_satellite_entity(
|
||||||
|
@ -26,6 +26,85 @@
|
|||||||
'unique_id': '11:22:33:44:55:aa',
|
'unique_id': '11:22:33:44:55:aa',
|
||||||
'version': 1,
|
'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([
|
||||||
|
]),
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
"""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
|
||||||
|
|
||||||
@ -8,34 +7,16 @@ 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 .common import MockDashboardRefresh
|
||||||
from .conftest import MockESPHomeDeviceType
|
from .conftest import MockESPHomeDeviceType
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
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")
|
@pytest.mark.usefixtures("init_integration", "mock_dashboard")
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import ANY
|
from unittest.mock import ANY
|
||||||
|
|
||||||
|
from aioesphomeapi import APIClient
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
from syrupy.filters import props
|
from syrupy.filters import props
|
||||||
@ -10,7 +11,8 @@ from syrupy.filters import props
|
|||||||
from homeassistant.components import bluetooth
|
from homeassistant.components import bluetooth
|
||||||
from homeassistant.core import HomeAssistant
|
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.common import MockConfigEntry
|
||||||
from tests.components.diagnostics import get_diagnostics_for_config_entry
|
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"))
|
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(
|
async def test_diagnostics_with_bluetooth(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
hass_client: ClientSessionGenerator,
|
hass_client: ClientSessionGenerator,
|
||||||
@ -43,6 +76,9 @@ async def test_diagnostics_with_bluetooth(
|
|||||||
entry = mock_bluetooth_entry_with_raw_adv.entry
|
entry = mock_bluetooth_entry_with_raw_adv.entry
|
||||||
result = await get_diagnostics_for_config_entry(hass, hass_client, entry)
|
result = await get_diagnostics_for_config_entry(hass, hass_client, entry)
|
||||||
assert result == {
|
assert result == {
|
||||||
|
"dashboard": {
|
||||||
|
"configured": False,
|
||||||
|
},
|
||||||
"bluetooth": {
|
"bluetooth": {
|
||||||
"available": True,
|
"available": True,
|
||||||
"connections_free": 0,
|
"connections_free": 0,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user