Home Assistant Unofficial Reference 2024.12.1
diagnostics.py
Go to the documentation of this file.
1 """Provides diagnostics for ZHA."""
2 
3 from __future__ import annotations
4 
5 import dataclasses
6 from importlib.metadata import version
7 from typing import Any
8 
9 from zha.application.const import (
10  ATTR_ATTRIBUTE,
11  ATTR_DEVICE_TYPE,
12  ATTR_IEEE,
13  ATTR_IN_CLUSTERS,
14  ATTR_OUT_CLUSTERS,
15  ATTR_PROFILE_ID,
16  ATTR_VALUE,
17  UNKNOWN,
18 )
19 from zha.application.gateway import Gateway
20 from zha.zigbee.device import Device
21 from zigpy.config import CONF_NWK_EXTENDED_PAN_ID
22 from zigpy.profiles import PROFILES
23 from zigpy.types import Channels
24 from zigpy.zcl import Cluster
25 
26 from homeassistant.components.diagnostics import async_redact_data
27 from homeassistant.config_entries import ConfigEntry
28 from homeassistant.const import CONF_ID, CONF_NAME, CONF_UNIQUE_ID
29 from homeassistant.core import HomeAssistant
30 from homeassistant.helpers import device_registry as dr
31 
32 from .const import CONF_ALARM_MASTER_CODE
33 from .helpers import (
34  ZHADeviceProxy,
35  async_get_zha_device_proxy,
36  get_zha_data,
37  get_zha_gateway,
38 )
39 
40 KEYS_TO_REDACT = {
41  ATTR_IEEE,
42  CONF_UNIQUE_ID,
43  CONF_ALARM_MASTER_CODE,
44  "network_key",
45  CONF_NWK_EXTENDED_PAN_ID,
46  "partner_ieee",
47 }
48 
49 ATTRIBUTES = "attributes"
50 CLUSTER_DETAILS = "cluster_details"
51 UNSUPPORTED_ATTRIBUTES = "unsupported_attributes"
52 
53 BELLOWS_VERSION = version("bellows")
54 ZIGPY_VERSION = version("zigpy")
55 ZIGPY_DECONZ_VERSION = version("zigpy-deconz")
56 ZIGPY_XBEE_VERSION = version("zigpy-xbee")
57 ZIGPY_ZNP_VERSION = version("zigpy-znp")
58 ZIGPY_ZIGATE_VERSION = version("zigpy-zigate")
59 ZHA_QUIRKS_VERSION = version("zha-quirks")
60 ZHA_VERSION = version("zha")
61 
62 
63 def shallow_asdict(obj: Any) -> dict:
64  """Return a shallow copy of a dataclass as a dict."""
65  if hasattr(obj, "__dataclass_fields__"):
66  result = {}
67 
68  for field in dataclasses.fields(obj):
69  result[field.name] = shallow_asdict(getattr(obj, field.name))
70 
71  return result
72  if hasattr(obj, "as_dict"):
73  return obj.as_dict()
74  return obj
75 
76 
78  hass: HomeAssistant, config_entry: ConfigEntry
79 ) -> dict[str, Any]:
80  """Return diagnostics for a config entry."""
81  zha_data = get_zha_data(hass)
82  gateway: Gateway = get_zha_gateway(hass)
83  app = gateway.application_controller
84 
85  energy_scan = await app.energy_scan(
86  channels=Channels.ALL_CHANNELS, duration_exp=4, count=1
87  )
88 
89  return async_redact_data(
90  {
91  "config": zha_data.yaml_config,
92  "config_entry": config_entry.as_dict(),
93  "application_state": shallow_asdict(app.state),
94  "energy_scan": {
95  channel: 100 * energy / 255 for channel, energy in energy_scan.items()
96  },
97  "versions": {
98  "bellows": BELLOWS_VERSION,
99  "zigpy": ZIGPY_VERSION,
100  "zigpy_deconz": ZIGPY_DECONZ_VERSION,
101  "zigpy_xbee": ZIGPY_XBEE_VERSION,
102  "zigpy_znp": ZIGPY_ZNP_VERSION,
103  "zigpy_zigate": ZIGPY_ZIGATE_VERSION,
104  "zhaquirks": ZHA_QUIRKS_VERSION,
105  "zha": ZHA_VERSION,
106  },
107  "devices": [
108  {
109  "manufacturer": device.manufacturer,
110  "model": device.model,
111  "logical_type": device.device_type,
112  }
113  for device in gateway.devices.values()
114  ],
115  },
116  KEYS_TO_REDACT,
117  )
118 
119 
121  hass: HomeAssistant, config_entry: ConfigEntry, device: dr.DeviceEntry
122 ) -> dict[str, Any]:
123  """Return diagnostics for a device."""
124  zha_device_proxy: ZHADeviceProxy = async_get_zha_device_proxy(hass, device.id)
125  device_info: dict[str, Any] = zha_device_proxy.zha_device_info
126  device_info[CLUSTER_DETAILS] = get_endpoint_cluster_attr_data(
127  zha_device_proxy.device
128  )
129  return async_redact_data(device_info, KEYS_TO_REDACT)
130 
131 
132 def get_endpoint_cluster_attr_data(zha_device: Device) -> dict:
133  """Return endpoint cluster attribute data."""
134  cluster_details = {}
135  for ep_id, endpoint in zha_device.device.endpoints.items():
136  if ep_id == 0:
137  continue
138  endpoint_key = (
139  f"{PROFILES.get(endpoint.profile_id).DeviceType(endpoint.device_type).name}"
140  if PROFILES.get(endpoint.profile_id) is not None
141  and endpoint.device_type is not None
142  else UNKNOWN
143  )
144  cluster_details[ep_id] = {
145  ATTR_DEVICE_TYPE: {
146  CONF_NAME: endpoint_key,
147  CONF_ID: endpoint.device_type,
148  },
149  ATTR_PROFILE_ID: endpoint.profile_id,
150  ATTR_IN_CLUSTERS: {
151  f"0x{cluster_id:04x}": {
152  "endpoint_attribute": cluster.ep_attribute,
153  **get_cluster_attr_data(cluster),
154  }
155  for cluster_id, cluster in endpoint.in_clusters.items()
156  },
157  ATTR_OUT_CLUSTERS: {
158  f"0x{cluster_id:04x}": {
159  "endpoint_attribute": cluster.ep_attribute,
160  **get_cluster_attr_data(cluster),
161  }
162  for cluster_id, cluster in endpoint.out_clusters.items()
163  },
164  }
165  return cluster_details
166 
167 
168 def get_cluster_attr_data(cluster: Cluster) -> dict:
169  """Return cluster attribute data."""
170  return {
171  ATTRIBUTES: {
172  f"0x{attr_id:04x}": {
173  ATTR_ATTRIBUTE: repr(attr_def),
174  ATTR_VALUE: cluster.get(attr_def.name),
175  }
176  for attr_id, attr_def in cluster.attributes.items()
177  },
178  UNSUPPORTED_ATTRIBUTES: sorted(
179  cluster.unsupported_attributes, key=lambda v: (isinstance(v, str), v)
180  ),
181  }
dict async_redact_data(Mapping data, Iterable[Any] to_redact)
Definition: util.py:14
dict get_cluster_attr_data(Cluster cluster)
Definition: diagnostics.py:168
dict[str, Any] async_get_device_diagnostics(HomeAssistant hass, ConfigEntry config_entry, dr.DeviceEntry device)
Definition: diagnostics.py:122
dict get_endpoint_cluster_attr_data(Device zha_device)
Definition: diagnostics.py:132
dict[str, Any] async_get_config_entry_diagnostics(HomeAssistant hass, ConfigEntry config_entry)
Definition: diagnostics.py:79
Gateway get_zha_gateway(HomeAssistant hass)
Definition: helpers.py:1028
ZHADeviceProxy async_get_zha_device_proxy(HomeAssistant hass, str device_id)
Definition: helpers.py:1053
HAZHAData get_zha_data(HomeAssistant hass)
Definition: helpers.py:1020