diff --git a/homeassistant/components/zha/diagnostics.py b/homeassistant/components/zha/diagnostics.py index 5ae2ff23e96..697e7be336c 100644 --- a/homeassistant/components/zha/diagnostics.py +++ b/homeassistant/components/zha/diagnostics.py @@ -8,6 +8,8 @@ import bellows import pkg_resources import zigpy from zigpy.config import CONF_NWK_EXTENDED_PAN_ID +from zigpy.profiles import PROFILES +from zigpy.zcl import Cluster import zigpy_deconz import zigpy_xbee import zigpy_zigate @@ -15,11 +17,24 @@ 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.const import CONF_ID, CONF_NAME, 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.const import ( + ATTR_ATTRIBUTE_NAME, + ATTR_DEVICE_TYPE, + ATTR_IEEE, + ATTR_IN_CLUSTERS, + ATTR_OUT_CLUSTERS, + ATTR_PROFILE_ID, + ATTR_VALUE, + CONF_ALARM_MASTER_CODE, + DATA_ZHA, + DATA_ZHA_CONFIG, + DATA_ZHA_GATEWAY, + UNKNOWN, +) from .core.device import ZHADevice from .core.gateway import ZHAGateway from .core.helpers import async_get_zha_device @@ -27,11 +42,16 @@ from .core.helpers import async_get_zha_device KEYS_TO_REDACT = { ATTR_IEEE, CONF_UNIQUE_ID, + CONF_ALARM_MASTER_CODE, "network_key", CONF_NWK_EXTENDED_PAN_ID, "partner_ieee", } +ATTRIBUTES = "attributes" +CLUSTER_DETAILS = "cluster_details" +UNSUPPORTED_ATTRIBUTES = "unsupported_attributes" + def shallow_asdict(obj: Any) -> dict: """Return a shallow copy of a dataclass as a dict.""" @@ -77,4 +97,62 @@ async def async_get_device_diagnostics( ) -> dict: """Return diagnostics for a device.""" zha_device: ZHADevice = async_get_zha_device(hass, device.id) - return async_redact_data(zha_device.zha_device_info, KEYS_TO_REDACT) + device_info: dict[str, Any] = zha_device.zha_device_info + device_info[CLUSTER_DETAILS] = get_endpoint_cluster_attr_data(zha_device) + return async_redact_data(device_info, KEYS_TO_REDACT) + + +def get_endpoint_cluster_attr_data(zha_device: ZHADevice) -> dict: + """Return endpoint cluster attribute data.""" + cluster_details = {} + for ep_id, endpoint in zha_device.device.endpoints.items(): + if ep_id == 0: + continue + endpoint_key = ( + f"{PROFILES.get(endpoint.profile_id).DeviceType(endpoint.device_type).name}" + if PROFILES.get(endpoint.profile_id) is not None + and endpoint.device_type is not None + else UNKNOWN + ) + cluster_details[ep_id] = { + ATTR_DEVICE_TYPE: { + CONF_NAME: endpoint_key, + CONF_ID: endpoint.device_type, + }, + ATTR_PROFILE_ID: endpoint.profile_id, + ATTR_IN_CLUSTERS: { + f"0x{cluster_id:04x}": { + "endpoint_attribute": cluster.ep_attribute, + **get_cluster_attr_data(cluster), + } + for cluster_id, cluster in endpoint.in_clusters.items() + }, + ATTR_OUT_CLUSTERS: { + f"0x{cluster_id:04x}": { + "endpoint_attribute": cluster.ep_attribute, + **get_cluster_attr_data(cluster), + } + for cluster_id, cluster in endpoint.out_clusters.items() + }, + } + return cluster_details + + +def get_cluster_attr_data(cluster: Cluster) -> dict: + """Return cluster attribute data.""" + return { + ATTRIBUTES: { + f"0x{attr_id:04x}": { + ATTR_ATTRIBUTE_NAME: attr_def.name, + ATTR_VALUE: attr_value, + } + for attr_id, attr_def in cluster.attributes.items() + if (attr_value := cluster.get(attr_def.name)) is not None + }, + UNSUPPORTED_ATTRIBUTES: { + f"0x{cluster.find_attribute(u_attr).id:04x}": { + ATTR_ATTRIBUTE_NAME: cluster.find_attribute(u_attr).name + } + for u_attr in cluster.unsupported_attributes + }, + }