Home Assistant Unofficial Reference 2024.12.1
debug_info.py
Go to the documentation of this file.
1 """Helper to handle a set of topics to subscribe to."""
2 
3 from __future__ import annotations
4 
5 from collections import deque
6 from dataclasses import dataclass
7 import datetime as dt
8 import time
9 from typing import TYPE_CHECKING, Any
10 
11 from homeassistant.core import HomeAssistant
12 from homeassistant.helpers import entity_registry as er
13 from homeassistant.helpers.typing import DiscoveryInfoType
14 from homeassistant.util import dt as dt_util
15 
16 from .const import ATTR_DISCOVERY_PAYLOAD, ATTR_DISCOVERY_TOPIC
17 from .models import DATA_MQTT, PublishPayloadType
18 
19 STORED_MESSAGES = 10
20 
21 
22 @dataclass
24  """MQTT Message."""
25 
26  topic: str
27  payload: PublishPayloadType
28  qos: int
29  retain: bool
30  timestamp: float
31 
32 
34  hass: HomeAssistant,
35  entity_id: str,
36  topic: str,
37  payload: PublishPayloadType,
38  qos: int,
39  retain: bool,
40 ) -> None:
41  """Log an outgoing MQTT message."""
42  entity_info = hass.data[DATA_MQTT].debug_info_entities.setdefault(
43  entity_id, {"subscriptions": {}, "discovery_data": {}, "transmitted": {}}
44  )
45  if topic not in entity_info["transmitted"]:
46  entity_info["transmitted"][topic] = {
47  "messages": deque([], STORED_MESSAGES),
48  }
50  topic, payload, qos, retain, timestamp=time.monotonic()
51  )
52  entity_info["transmitted"][topic]["messages"].append(msg)
53 
54 
56  hass: HomeAssistant, subscription: str, entity_id: str | None
57 ) -> None:
58  """Prepare debug data for subscription."""
59  if entity_id:
60  entity_info = hass.data[DATA_MQTT].debug_info_entities.setdefault(
61  entity_id, {"subscriptions": {}, "discovery_data": {}, "transmitted": {}}
62  )
63  if subscription not in entity_info["subscriptions"]:
64  entity_info["subscriptions"][subscription] = {
65  "count": 1,
66  "messages": deque([], STORED_MESSAGES),
67  }
68  else:
69  entity_info["subscriptions"][subscription]["count"] += 1
70 
71 
73  hass: HomeAssistant, subscription: str, entity_id: str | None
74 ) -> None:
75  """Remove debug data for subscription if it exists."""
76  if entity_id and entity_id in (
77  debug_info_entities := hass.data[DATA_MQTT].debug_info_entities
78  ):
79  subscriptions = debug_info_entities[entity_id]["subscriptions"]
80  subscriptions[subscription]["count"] -= 1
81  if not subscriptions[subscription]["count"]:
82  del subscriptions[subscription]
83 
84 
86  hass: HomeAssistant, discovery_data: DiscoveryInfoType, entity_id: str
87 ) -> None:
88  """Add discovery data."""
89  entity_info = hass.data[DATA_MQTT].debug_info_entities.setdefault(
90  entity_id, {"subscriptions": {}, "discovery_data": {}, "transmitted": {}}
91  )
92  entity_info["discovery_data"] = discovery_data
93 
94 
96  hass: HomeAssistant, discovery_payload: DiscoveryInfoType, entity_id: str
97 ) -> None:
98  """Update discovery data."""
99  discovery_data = hass.data[DATA_MQTT].debug_info_entities[entity_id][
100  "discovery_data"
101  ]
102  if TYPE_CHECKING:
103  assert discovery_data is not None
104  discovery_data[ATTR_DISCOVERY_PAYLOAD] = discovery_payload
105 
106 
107 def remove_entity_data(hass: HomeAssistant, entity_id: str) -> None:
108  """Remove discovery data."""
109  if entity_id in (debug_info_entities := hass.data[DATA_MQTT].debug_info_entities):
110  del debug_info_entities[entity_id]
111 
112 
114  hass: HomeAssistant,
115  discovery_hash: tuple[str, str],
116  discovery_data: DiscoveryInfoType,
117  device_id: str,
118 ) -> None:
119  """Add discovery data."""
120  hass.data[DATA_MQTT].debug_info_triggers[discovery_hash] = {
121  "device_id": device_id,
122  "discovery_data": discovery_data,
123  }
124 
125 
127  hass: HomeAssistant,
128  discovery_hash: tuple[str, str],
129  discovery_payload: DiscoveryInfoType,
130 ) -> None:
131  """Update discovery data."""
132  hass.data[DATA_MQTT].debug_info_triggers[discovery_hash]["discovery_data"][
133  ATTR_DISCOVERY_PAYLOAD
134  ] = discovery_payload
135 
136 
138  hass: HomeAssistant, discovery_hash: tuple[str, str]
139 ) -> None:
140  """Remove discovery data."""
141  hass.data[DATA_MQTT].debug_info_triggers.pop(discovery_hash, None)
142 
143 
144 def _info_for_entity(hass: HomeAssistant, entity_id: str) -> dict[str, Any]:
145  entity_info = hass.data[DATA_MQTT].debug_info_entities[entity_id]
146  monotonic_time_diff = time.time() - time.monotonic()
147  subscriptions = [
148  {
149  "topic": topic,
150  "messages": [
151  {
152  "payload": str(msg.payload),
153  "qos": msg.qos,
154  "retain": msg.retain,
155  "time": dt_util.utc_from_timestamp(
156  msg.timestamp + monotonic_time_diff,
157  tz=dt.UTC,
158  ),
159  "topic": msg.topic,
160  }
161  for msg in subscription["messages"]
162  ],
163  }
164  for topic, subscription in entity_info["subscriptions"].items()
165  ]
166  transmitted = [
167  {
168  "topic": topic,
169  "messages": [
170  {
171  "payload": str(msg.payload),
172  "qos": msg.qos,
173  "retain": msg.retain,
174  "time": dt_util.utc_from_timestamp(
175  msg.timestamp + monotonic_time_diff,
176  tz=dt.UTC,
177  ),
178  "topic": msg.topic,
179  }
180  for msg in subscription["messages"]
181  ],
182  }
183  for topic, subscription in entity_info["transmitted"].items()
184  ]
185  discovery_data = {
186  "topic": entity_info["discovery_data"].get(ATTR_DISCOVERY_TOPIC, ""),
187  "payload": entity_info["discovery_data"].get(ATTR_DISCOVERY_PAYLOAD, ""),
188  }
189 
190  return {
191  "entity_id": entity_id,
192  "subscriptions": subscriptions,
193  "discovery_data": discovery_data,
194  "transmitted": transmitted,
195  }
196 
197 
199  hass: HomeAssistant, trigger_key: tuple[str, str]
200 ) -> dict[str, Any]:
201  trigger = hass.data[DATA_MQTT].debug_info_triggers[trigger_key]
202  discovery_data = None
203  if trigger["discovery_data"] is not None:
204  discovery_data = {
205  "topic": trigger["discovery_data"][ATTR_DISCOVERY_TOPIC],
206  "payload": trigger["discovery_data"][ATTR_DISCOVERY_PAYLOAD],
207  }
208  return {"discovery_data": discovery_data, "trigger_key": trigger_key}
209 
210 
211 def info_for_config_entry(hass: HomeAssistant) -> dict[str, list[Any]]:
212  """Get debug info for all entities and triggers."""
213 
214  mqtt_data = hass.data[DATA_MQTT]
215  mqtt_info: dict[str, list[Any]] = {"entities": [], "triggers": []}
216 
217  mqtt_info["entities"].extend(
218  _info_for_entity(hass, entity_id) for entity_id in mqtt_data.debug_info_entities
219  )
220 
221  mqtt_info["triggers"].extend(
222  _info_for_trigger(hass, trigger_key)
223  for trigger_key in mqtt_data.debug_info_triggers
224  )
225 
226  return mqtt_info
227 
228 
229 def info_for_device(hass: HomeAssistant, device_id: str) -> dict[str, list[Any]]:
230  """Get debug info for a device."""
231 
232  mqtt_data = hass.data[DATA_MQTT]
233 
234  mqtt_info: dict[str, list[Any]] = {"entities": [], "triggers": []}
235  entity_registry = er.async_get(hass)
236 
237  entries = er.async_entries_for_device(
238  entity_registry, device_id, include_disabled_entities=True
239  )
240  mqtt_info["entities"].extend(
241  _info_for_entity(hass, entry.entity_id)
242  for entry in entries
243  if entry.entity_id in mqtt_data.debug_info_entities
244  )
245 
246  mqtt_info["triggers"].extend(
247  _info_for_trigger(hass, trigger_key)
248  for trigger_key, trigger in mqtt_data.debug_info_triggers.items()
249  if trigger["device_id"] == device_id
250  )
251 
252  return mqtt_info
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
None remove_trigger_discovery_data(HomeAssistant hass, tuple[str, str] discovery_hash)
Definition: debug_info.py:139
None add_entity_discovery_data(HomeAssistant hass, DiscoveryInfoType discovery_data, str entity_id)
Definition: debug_info.py:87
dict[str, list[Any]] info_for_device(HomeAssistant hass, str device_id)
Definition: debug_info.py:229
None remove_entity_data(HomeAssistant hass, str entity_id)
Definition: debug_info.py:107
dict[str, list[Any]] info_for_config_entry(HomeAssistant hass)
Definition: debug_info.py:211
None update_trigger_discovery_data(HomeAssistant hass, tuple[str, str] discovery_hash, DiscoveryInfoType discovery_payload)
Definition: debug_info.py:130
None add_subscription(HomeAssistant hass, str subscription, str|None entity_id)
Definition: debug_info.py:57
dict[str, Any] _info_for_entity(HomeAssistant hass, str entity_id)
Definition: debug_info.py:144
dict[str, Any] _info_for_trigger(HomeAssistant hass, tuple[str, str] trigger_key)
Definition: debug_info.py:200
None add_trigger_discovery_data(HomeAssistant hass, tuple[str, str] discovery_hash, DiscoveryInfoType discovery_data, str device_id)
Definition: debug_info.py:118
None update_entity_discovery_data(HomeAssistant hass, DiscoveryInfoType discovery_payload, str entity_id)
Definition: debug_info.py:97
None remove_subscription(HomeAssistant hass, str subscription, str|None entity_id)
Definition: debug_info.py:74
None log_message(HomeAssistant hass, str entity_id, str topic, PublishPayloadType payload, int qos, bool retain)
Definition: debug_info.py:40