Add diagnostics support for ZHA (#69756)

pull/69897/head
David F. Mulcahey 2022-04-12 01:35:29 -04:00 committed by GitHub
parent c93c7e8eff
commit 5f37f58673
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 167 additions and 0 deletions

View File

@ -0,0 +1,79 @@
"""Provides diagnostics for ZHA."""
from __future__ import annotations
import dataclasses
from typing import Any
import bellows
import pkg_resources
import zigpy
from zigpy.config import CONF_NWK_EXTENDED_PAN_ID
import zigpy_deconz
import zigpy_xbee
import zigpy_zigate
import zigpy_znp
from homeassistant.components.diagnostics.util import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_UNIQUE_ID
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from .core.const import ATTR_IEEE, DATA_ZHA, DATA_ZHA_CONFIG, DATA_ZHA_GATEWAY
from .core.device import ZHADevice
from .core.gateway import ZHAGateway
from .core.helpers import async_get_zha_device
KEYS_TO_REDACT = {
ATTR_IEEE,
CONF_UNIQUE_ID,
"network_key",
CONF_NWK_EXTENDED_PAN_ID,
}
def shallow_asdict(obj: Any) -> dict:
"""Return a shallow copy of a dataclass as a dict."""
if hasattr(obj, "__dataclass_fields__"):
result = {}
for field in dataclasses.fields(obj):
result[field.name] = shallow_asdict(getattr(obj, field.name))
return result
if hasattr(obj, "as_dict"):
return obj.as_dict()
return obj
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, config_entry: ConfigEntry
) -> dict:
"""Return diagnostics for a config entry."""
config: dict = hass.data[DATA_ZHA][DATA_ZHA_CONFIG]
gateway: ZHAGateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY]
return async_redact_data(
{
"config": config,
"config_entry": config_entry.as_dict(),
"application_state": shallow_asdict(gateway.application_controller.state),
"versions": {
"bellows": bellows.__version__,
"zigpy": zigpy.__version__,
"zigpy_deconz": zigpy_deconz.__version__,
"zigpy_xbee": zigpy_xbee.__version__,
"zigpy_znp": zigpy_znp.__version__,
"zigpy_zigate": zigpy_zigate.__version__,
"zhaquirks": pkg_resources.get_distribution("zha-quirks").version,
},
},
KEYS_TO_REDACT,
)
async def async_get_device_diagnostics(
hass: HomeAssistant, config_entry: ConfigEntry, device: dr.DeviceEntry
) -> dict:
"""Return diagnostics for a device."""
zha_device: ZHADevice = await async_get_zha_device(hass, device.id)
return async_redact_data(zha_device.zha_device_info, KEYS_TO_REDACT)

View File

@ -11,6 +11,7 @@ from zigpy.const import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE
import zigpy.device
import zigpy.group
import zigpy.profiles
from zigpy.state import State
import zigpy.types
import zigpy.zdo.types as zdo_t
@ -54,6 +55,7 @@ def zigpy_app_controller():
app.ieee.return_value = zigpy.types.EUI64.convert("00:15:8d:00:02:32:4f:32")
type(app).nwk = PropertyMock(return_value=zigpy.types.NWK(0x0000))
type(app).devices = PropertyMock(return_value={})
type(app).state = PropertyMock(return_value=State())
return app

View File

@ -0,0 +1,86 @@
"""Tests for the diagnostics data provided by the ESPHome integration."""
import pytest
import zigpy.profiles.zha as zha
import zigpy.zcl.clusters.security as security
from homeassistant.components.diagnostics.const import REDACTED
from homeassistant.components.zha.core.device import ZHADevice
from homeassistant.components.zha.diagnostics import KEYS_TO_REDACT
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import async_get
from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE
from tests.components.diagnostics import (
get_diagnostics_for_config_entry,
get_diagnostics_for_device,
)
CONFIG_ENTRY_DIAGNOSTICS_KEYS = [
"config",
"config_entry",
"application_state",
"versions",
]
@pytest.fixture
def zigpy_device(zigpy_device_mock):
"""Device tracker zigpy device."""
endpoints = {
1: {
SIG_EP_INPUT: [security.IasAce.cluster_id],
SIG_EP_OUTPUT: [],
SIG_EP_TYPE: zha.DeviceType.IAS_ANCILLARY_CONTROL,
SIG_EP_PROFILE: zha.PROFILE_ID,
}
}
return zigpy_device_mock(
endpoints, node_descriptor=b"\x02@\x8c\x02\x10RR\x00\x00\x00R\x00\x00"
)
async def test_diagnostics_for_config_entry(
hass: HomeAssistant,
hass_client,
config_entry,
zha_device_joined,
zigpy_device,
):
"""Test diagnostics for config entry."""
await zha_device_joined(zigpy_device)
diagnostics_data = await get_diagnostics_for_config_entry(
hass, hass_client, config_entry
)
assert diagnostics_data
for key in CONFIG_ENTRY_DIAGNOSTICS_KEYS:
assert key in diagnostics_data
assert diagnostics_data[key] is not None
async def test_diagnostics_for_device(
hass: HomeAssistant,
hass_client,
config_entry,
zha_device_joined,
zigpy_device,
):
"""Test diagnostics for device."""
zha_device: ZHADevice = await zha_device_joined(zigpy_device)
dev_reg = async_get(hass)
device = dev_reg.async_get_device({("zha", str(zha_device.ieee))})
assert device
diagnostics_data = await get_diagnostics_for_device(
hass, hass_client, config_entry, device
)
assert diagnostics_data
device_info: dict = zha_device.zha_device_info
for key, value in device_info.items():
assert key in diagnostics_data
if key not in KEYS_TO_REDACT:
assert key in diagnostics_data
else:
assert diagnostics_data[key] == REDACTED