Home Assistant Unofficial Reference 2024.12.1
websocket_api.py
Go to the documentation of this file.
1 """Websocket API for automation."""
2 
3 import json
4 from typing import Any
5 
6 import voluptuous as vol
7 
8 from homeassistant.components import websocket_api
9 from homeassistant.core import HomeAssistant, callback
10 from homeassistant.exceptions import HomeAssistantError
12  DATA_DISPATCHER,
13  async_dispatcher_connect,
14  async_dispatcher_send,
15 )
16 from homeassistant.helpers.json import ExtendedJSONEncoder
17 from homeassistant.helpers.script import (
18  SCRIPT_BREAKPOINT_HIT,
19  SCRIPT_DEBUG_CONTINUE_ALL,
20  breakpoint_clear,
21  breakpoint_clear_all,
22  breakpoint_list,
23  breakpoint_set,
24  debug_continue,
25  debug_step,
26  debug_stop,
27 )
28 
29 from .util import async_get_trace, async_list_contexts, async_list_traces
30 
31 TRACE_DOMAINS = ("automation", "script")
32 
33 
34 @callback
35 def async_setup(hass: HomeAssistant) -> None:
36  """Set up the websocket API."""
37  websocket_api.async_register_command(hass, websocket_trace_get)
38  websocket_api.async_register_command(hass, websocket_trace_list)
39  websocket_api.async_register_command(hass, websocket_trace_contexts)
40  websocket_api.async_register_command(hass, websocket_breakpoint_clear)
41  websocket_api.async_register_command(hass, websocket_breakpoint_list)
42  websocket_api.async_register_command(hass, websocket_breakpoint_set)
43  websocket_api.async_register_command(hass, websocket_debug_continue)
44  websocket_api.async_register_command(hass, websocket_debug_step)
45  websocket_api.async_register_command(hass, websocket_debug_stop)
46  websocket_api.async_register_command(hass, websocket_subscribe_breakpoint_events)
47 
48 
49 @websocket_api.require_admin
50 @websocket_api.websocket_command( { vol.Required("type"): "trace/get",
51  vol.Required("domain"): vol.In(TRACE_DOMAINS),
52  vol.Required("item_id"): str,
53  vol.Required("run_id"): str,
54  }
55 )
56 @websocket_api.async_response
57 async def websocket_trace_get(
58  hass: HomeAssistant,
60  msg: dict[str, Any],
61 ) -> None:
62  """Get a script or automation trace."""
63  key = f"{msg['domain']}.{msg['item_id']}"
64  run_id = msg["run_id"]
65 
66  try:
67  requested_trace = await async_get_trace(hass, key, run_id)
68  except KeyError:
69  connection.send_error(
70  msg["id"], websocket_api.ERR_NOT_FOUND, "The trace could not be found"
71  )
72  return
73 
74  message = websocket_api.messages.result_message(msg["id"], requested_trace)
75 
76  connection.send_message(
77  json.dumps(message, cls=ExtendedJSONEncoder, allow_nan=False)
78  )
79 
80 
81 @websocket_api.require_admin
82 @websocket_api.websocket_command( { vol.Required("type"): "trace/list",
83  vol.Required("domain", "id"): vol.In(TRACE_DOMAINS),
84  vol.Optional("item_id", "id"): str,
85  }
86 )
87 @websocket_api.async_response
88 async def websocket_trace_list(
89  hass: HomeAssistant,
91  msg: dict[str, Any],
92 ) -> None:
93  """Summarize script and automation traces."""
94  wanted_domain = msg["domain"]
95  key = f"{msg['domain']}.{msg['item_id']}" if "item_id" in msg else None
96 
97  traces = await async_list_traces(hass, wanted_domain, key)
98 
99  connection.send_result(msg["id"], traces)
100 
101 
102 @websocket_api.require_admin
103 @websocket_api.websocket_command( { vol.Required("type"): "trace/contexts",
104  vol.Inclusive("domain", "id"): vol.In(TRACE_DOMAINS),
105  vol.Inclusive("item_id", "id"): str,
106  }
107 )
108 @websocket_api.async_response
109 async def websocket_trace_contexts(
110  hass: HomeAssistant,
111  connection: websocket_api.ActiveConnection,
112  msg: dict[str, Any],
113 ) -> None:
114  """Retrieve contexts we have traces for."""
115  key = f"{msg['domain']}.{msg['item_id']}" if "item_id" in msg else None
116 
117  contexts = await async_list_contexts(hass, key)
118 
119  connection.send_result(msg["id"], contexts)
120 
121 
122 @callback
123 @websocket_api.require_admin
124 @websocket_api.websocket_command( { vol.Required("type"): "trace/debug/breakpoint/set",
125  vol.Required("domain"): vol.In(TRACE_DOMAINS),
126  vol.Required("item_id"): str,
127  vol.Required("node"): str,
128  vol.Optional("run_id"): str,
129  }
130 )
132  hass: HomeAssistant,
133  connection: websocket_api.ActiveConnection,
134  msg: dict[str, Any],
135 ) -> None:
136  """Set breakpoint."""
137  key = f"{msg['domain']}.{msg['item_id']}"
138  node: str = msg["node"]
139  run_id: str | None = msg.get("run_id")
140 
141  if (
142  SCRIPT_BREAKPOINT_HIT not in hass.data.get(DATA_DISPATCHER, {})
143  or not hass.data[DATA_DISPATCHER][SCRIPT_BREAKPOINT_HIT]
144  ):
145  raise HomeAssistantError("No breakpoint subscription")
146 
147  result = breakpoint_set(hass, key, run_id, node)
148  connection.send_result(msg["id"], result)
149 
150 
151 @callback
152 @websocket_api.require_admin
153 @websocket_api.websocket_command( { vol.Required("type"): "trace/debug/breakpoint/clear",
154  vol.Required("domain"): vol.In(TRACE_DOMAINS),
155  vol.Required("item_id"): str,
156  vol.Required("node"): str,
157  vol.Optional("run_id"): str,
158  }
159 )
161  hass: HomeAssistant,
162  connection: websocket_api.ActiveConnection,
163  msg: dict[str, Any],
164 ) -> None:
165  """Clear breakpoint."""
166  key = f"{msg['domain']}.{msg['item_id']}"
167  node: str = msg["node"]
168  run_id: str | None = msg.get("run_id")
169 
170  result = breakpoint_clear(hass, key, run_id, node)
171 
172  connection.send_result(msg["id"], result)
173 
174 
175 @callback
176 @websocket_api.require_admin
177 @websocket_api.websocket_command({vol.Required("type"): "trace/debug/breakpoint/list"})
179  hass: HomeAssistant,
180  connection: websocket_api.ActiveConnection,
181  msg: dict[str, Any],
182 ) -> None:
183  """List breakpoints."""
184  breakpoints = breakpoint_list(hass)
185  for _breakpoint in breakpoints:
186  key = _breakpoint.pop("key")
187  _breakpoint["domain"], _breakpoint["item_id"] = key.split(".", 1)
188 
189  connection.send_result(msg["id"], breakpoints)
190 
191 
192 @callback
193 @websocket_api.require_admin
194 @websocket_api.websocket_command( {vol.Required("type"): "trace/debug/breakpoint/subscribe"}
195 )
197  hass: HomeAssistant,
198  connection: websocket_api.ActiveConnection,
199  msg: dict[str, Any],
200 ) -> None:
201  """Subscribe to breakpoint events."""
202 
203  @callback
204  def breakpoint_hit(key: str, run_id: str, node: str) -> None:
205  """Forward events to websocket."""
206  domain, item_id = key.split(".", 1)
207  connection.send_message(
208  websocket_api.event_message(
209  msg["id"],
210  {
211  "domain": domain,
212  "item_id": item_id,
213  "run_id": run_id,
214  "node": node,
215  },
216  )
217  )
218 
219  remove_signal = async_dispatcher_connect(
220  hass, SCRIPT_BREAKPOINT_HIT, breakpoint_hit
221  )
222 
223  @callback
224  def unsub() -> None:
225  """Unsubscribe from breakpoint events."""
226  remove_signal()
227  if (
228  SCRIPT_BREAKPOINT_HIT not in hass.data.get(DATA_DISPATCHER, {})
229  or not hass.data[DATA_DISPATCHER][SCRIPT_BREAKPOINT_HIT]
230  ):
232  async_dispatcher_send(hass, SCRIPT_DEBUG_CONTINUE_ALL)
233 
234  connection.subscriptions[msg["id"]] = unsub
235 
236  connection.send_message(websocket_api.result_message(msg["id"]))
237 
238 
239 @callback
240 @websocket_api.require_admin
241 @websocket_api.websocket_command( { vol.Required("type"): "trace/debug/continue",
242  vol.Required("domain"): vol.In(TRACE_DOMAINS),
243  vol.Required("item_id"): str,
244  vol.Required("run_id"): str,
245  }
246 )
248  hass: HomeAssistant,
249  connection: websocket_api.ActiveConnection,
250  msg: dict[str, Any],
251 ) -> None:
252  """Resume execution of halted script or automation."""
253  key = f"{msg['domain']}.{msg['item_id']}"
254  run_id: str = msg["run_id"]
255 
256  result = debug_continue(hass, key, run_id)
257 
258  connection.send_result(msg["id"], result)
259 
260 
261 @callback
262 @websocket_api.require_admin
263 @websocket_api.websocket_command( { vol.Required("type"): "trace/debug/step",
264  vol.Required("domain"): vol.In(TRACE_DOMAINS),
265  vol.Required("item_id"): str,
266  vol.Required("run_id"): str,
267  }
268 )
270  hass: HomeAssistant,
271  connection: websocket_api.ActiveConnection,
272  msg: dict[str, Any],
273 ) -> None:
274  """Single step a halted script or automation."""
275  key = f"{msg['domain']}.{msg['item_id']}"
276  run_id: str = msg["run_id"]
277 
278  result = debug_step(hass, key, run_id)
279 
280  connection.send_result(msg["id"], result)
281 
282 
283 @callback
284 @websocket_api.require_admin
285 @websocket_api.websocket_command( { vol.Required("type"): "trace/debug/stop",
286  vol.Required("domain"): vol.In(TRACE_DOMAINS),
287  vol.Required("item_id"): str,
288  vol.Required("run_id"): str,
289  }
290 )
292  hass: HomeAssistant,
293  connection: websocket_api.ActiveConnection,
294  msg: dict[str, Any],
295 ) -> None:
296  """Stop a halted script or automation."""
297  key = f"{msg['domain']}.{msg['item_id']}"
298  run_id: str = msg["run_id"]
299 
300  result = debug_stop(hass, key, run_id)
301 
302  connection.send_result(msg["id"], result)
303 
dict[str, BaseTrace] async_get_trace(HomeAssistant hass, str key, str run_id)
Definition: util.py:21
dict[str, dict[str, str]] async_list_contexts(HomeAssistant hass, str|None key)
Definition: util.py:31
list[dict[str, Any]] async_list_traces(HomeAssistant hass, str wanted_domain, str|None wanted_key)
Definition: util.py:64
None websocket_debug_step(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None websocket_subscribe_breakpoint_events(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None websocket_breakpoint_clear(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None websocket_trace_contexts(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None websocket_breakpoint_list(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None websocket_breakpoint_set(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None websocket_trace_list(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None websocket_debug_stop(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None websocket_debug_continue(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None websocket_trace_get(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193
None debug_continue(HomeAssistant hass, str key, str run_id)
Definition: script.py:2081
None breakpoint_set(HomeAssistant hass, str key, str|None run_id, str node)
Definition: script.py:2056
list[dict[str, Any]] breakpoint_list(HomeAssistant hass)
Definition: script.py:2068
None breakpoint_clear_all(HomeAssistant hass)
Definition: script.py:2048
None breakpoint_clear(HomeAssistant hass, str key, str|None run_id, str node)
Definition: script.py:2038
None debug_step(HomeAssistant hass, str key, str run_id)
Definition: script.py:2091
None debug_stop(HomeAssistant hass, str key, str run_id)
Definition: script.py:2101