Home Assistant Unofficial Reference 2024.12.1
commands.py
Go to the documentation of this file.
1 """Commands part of Websocket API."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from functools import lru_cache, partial
7 import json
8 import logging
9 from typing import Any, cast
10 
11 import voluptuous as vol
12 
13 from homeassistant.auth.models import User
14 from homeassistant.auth.permissions.const import POLICY_READ
15 from homeassistant.auth.permissions.events import SUBSCRIBE_ALLOWLIST
16 from homeassistant.const import (
17  EVENT_STATE_CHANGED,
18  MATCH_ALL,
19  SIGNAL_BOOTSTRAP_INTEGRATIONS,
20 )
21 from homeassistant.core import (
22  Context,
23  Event,
24  EventStateChangedData,
25  HomeAssistant,
26  ServiceResponse,
27  State,
28  callback,
29 )
30 from homeassistant.exceptions import (
31  HomeAssistantError,
32  ServiceNotFound,
33  ServiceValidationError,
34  TemplateError,
35  Unauthorized,
36 )
37 from homeassistant.helpers import config_validation as cv, entity, template
38 from homeassistant.helpers.dispatcher import async_dispatcher_connect
40  INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA,
41  convert_include_exclude_filter,
42 )
43 from homeassistant.helpers.event import (
44  TrackTemplate,
45  TrackTemplateResult,
46  async_track_template_result,
47 )
48 from homeassistant.helpers.json import (
49  JSON_DUMP,
50  ExtendedJSONEncoder,
51  find_paths_unserializable_data,
52  json_bytes,
53  json_fragment,
54 )
55 from homeassistant.helpers.service import async_get_all_descriptions
56 from homeassistant.loader import (
57  IntegrationNotFound,
58  async_get_integration,
59  async_get_integration_descriptions,
60  async_get_integrations,
61 )
62 from homeassistant.setup import async_get_loaded_integrations, async_get_setup_timings
63 from homeassistant.util.json import format_unserializable_data
64 
65 from . import const, decorators, messages
66 from .connection import ActiveConnection
67 from .messages import construct_result_message
68 
69 ALL_SERVICE_DESCRIPTIONS_JSON_CACHE = "websocket_api_all_service_descriptions_json"
70 
71 _LOGGER = logging.getLogger(__name__)
72 
73 
74 @callback
76  hass: HomeAssistant,
77  async_reg: Callable[[HomeAssistant, const.WebSocketCommandHandler], None],
78 ) -> None:
79  """Register commands."""
80  async_reg(hass, handle_call_service)
81  async_reg(hass, handle_entity_source)
82  async_reg(hass, handle_execute_script)
83  async_reg(hass, handle_fire_event)
84  async_reg(hass, handle_get_config)
85  async_reg(hass, handle_get_services)
86  async_reg(hass, handle_get_states)
87  async_reg(hass, handle_manifest_get)
88  async_reg(hass, handle_integration_setup_info)
89  async_reg(hass, handle_manifest_list)
90  async_reg(hass, handle_ping)
91  async_reg(hass, handle_render_template)
92  async_reg(hass, handle_subscribe_bootstrap_integrations)
93  async_reg(hass, handle_subscribe_events)
94  async_reg(hass, handle_subscribe_trigger)
95  async_reg(hass, handle_test_condition)
96  async_reg(hass, handle_unsubscribe_events)
97  async_reg(hass, handle_validate_config)
98  async_reg(hass, handle_subscribe_entities)
99  async_reg(hass, handle_supported_features)
100  async_reg(hass, handle_integration_descriptions)
101 
102 
103 def pong_message(iden: int) -> dict[str, Any]:
104  """Return a pong message."""
105  return {"id": iden, "type": "pong"}
106 
107 
108 @callback
110  send_message: Callable[[bytes | str | dict[str, Any]], None],
111  user: User,
112  message_id_as_bytes: bytes,
113  event: Event,
114 ) -> None:
115  """Forward state changed events to websocket."""
116  # We have to lookup the permissions again because the user might have
117  # changed since the subscription was created.
118  permissions = user.permissions
119  if (
120  not user.is_admin
121  and not permissions.access_all_entities(POLICY_READ)
122  and not permissions.check_entity(event.data["entity_id"], POLICY_READ)
123  ):
124  return
125  send_message(messages.cached_event_message(message_id_as_bytes, event))
126 
127 
128 @callback
130  send_message: Callable[[bytes | str | dict[str, Any]], None],
131  message_id_as_bytes: bytes,
132  event: Event,
133 ) -> None:
134  """Forward events to websocket."""
135  send_message(messages.cached_event_message(message_id_as_bytes, event))
136 
137 
138 @callback
139 @decorators.websocket_command( { vol.Required("type"): "subscribe_events",
140  vol.Optional("event_type", default=MATCH_ALL): str,
141  }
142 )
144  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
145 ) -> None:
146  """Handle subscribe events command."""
147  event_type = msg["event_type"]
148 
149  if event_type not in SUBSCRIBE_ALLOWLIST and not connection.user.is_admin:
150  _LOGGER.error(
151  "Refusing to allow %s to subscribe to event %s",
152  connection.user.name,
153  event_type,
154  )
155  raise Unauthorized(user_id=connection.user.id)
156 
157  message_id_as_bytes = str(msg["id"]).encode()
158 
159  if event_type == EVENT_STATE_CHANGED:
160  forward_events = partial(
161  _forward_events_check_permissions,
162  connection.send_message,
163  connection.user,
164  message_id_as_bytes,
165  )
166  else:
167  forward_events = partial(
168  _forward_events_unconditional, connection.send_message, message_id_as_bytes
169  )
170 
171  connection.subscriptions[msg["id"]] = hass.bus.async_listen(
172  event_type, forward_events
173  )
174 
175  connection.send_result(msg["id"])
176 
177 
178 @callback
179 @decorators.websocket_command( { vol.Required("type"): "subscribe_bootstrap_integrations",
180  }
181 )
183  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
184 ) -> None:
185  """Handle subscribe bootstrap integrations command."""
186 
187  @callback
188  def forward_bootstrap_integrations(message: dict[str, Any]) -> None:
189  """Forward bootstrap integrations to websocket."""
190  connection.send_message(messages.event_message(msg["id"], message))
191 
192  connection.subscriptions[msg["id"]] = async_dispatcher_connect(
193  hass, SIGNAL_BOOTSTRAP_INTEGRATIONS, forward_bootstrap_integrations
194  )
195 
196  connection.send_result(msg["id"])
197 
198 
199 @callback
200 @decorators.websocket_command( { vol.Required("type"): "unsubscribe_events",
201  vol.Required("subscription"): cv.positive_int,
202  }
203 )
205  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
206 ) -> None:
207  """Handle unsubscribe events command."""
208  subscription = msg["subscription"]
209 
210  if subscription in connection.subscriptions:
211  connection.subscriptions.pop(subscription)()
212  connection.send_result(msg["id"])
213  else:
214  connection.send_error(msg["id"], const.ERR_NOT_FOUND, "Subscription not found.")
215 
216 
217 @decorators.websocket_command( { vol.Required("type"): "call_service",
218  vol.Required("domain"): str,
219  vol.Required("service"): str,
220  vol.Optional("target"): cv.ENTITY_SERVICE_FIELDS,
221  vol.Optional("service_data"): dict,
222  vol.Optional("return_response", default=False): bool,
223  }
224 )
225 @decorators.async_response
226 async def handle_call_service(
227  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
228 ) -> None:
229  """Handle call service command."""
230  # We do not support templates.
231  target = msg.get("target")
232  if template.is_complex(target):
233  raise vol.Invalid("Templates are not supported here")
234 
235  try:
236  context = connection.context(msg)
237  response = await hass.services.async_call(
238  domain=msg["domain"],
239  service=msg["service"],
240  service_data=msg.get("service_data"),
241  blocking=True,
242  context=context,
243  target=target,
244  return_response=msg["return_response"],
245  )
246  result: dict[str, Context | ServiceResponse] = {"context": context}
247  if msg["return_response"]:
248  result["response"] = response
249  connection.send_result(msg["id"], result)
250  except ServiceNotFound as err:
251  if err.domain == msg["domain"] and err.service == msg["service"]:
252  connection.send_error(
253  msg["id"],
254  const.ERR_NOT_FOUND,
255  f"Service {err.domain}.{err.service} not found.",
256  translation_domain=err.translation_domain,
257  translation_key=err.translation_key,
258  translation_placeholders=err.translation_placeholders,
259  )
260  else:
261  # The called service called another service which does not exist
262  connection.send_error(
263  msg["id"],
264  const.ERR_HOME_ASSISTANT_ERROR,
265  f"Service {err.domain}.{err.service} called service "
266  f"{msg['domain']}.{msg['service']} which was not found.",
267  translation_domain=const.DOMAIN,
268  translation_key="child_service_not_found",
269  translation_placeholders={
270  "domain": err.domain,
271  "service": err.service,
272  "child_domain": msg["domain"],
273  "child_service": msg["service"],
274  },
275  )
276  except vol.Invalid as err:
277  connection.send_error(msg["id"], const.ERR_INVALID_FORMAT, str(err))
278  except ServiceValidationError as err:
279  connection.logger.error(err)
280  connection.logger.debug("", exc_info=err)
281  connection.send_error(
282  msg["id"],
283  const.ERR_SERVICE_VALIDATION_ERROR,
284  f"Validation error: {err}",
285  translation_domain=err.translation_domain,
286  translation_key=err.translation_key,
287  translation_placeholders=err.translation_placeholders,
288  )
289  except HomeAssistantError as err:
290  connection.logger.exception("Unexpected exception")
291  connection.send_error(
292  msg["id"],
293  const.ERR_HOME_ASSISTANT_ERROR,
294  str(err),
295  translation_domain=err.translation_domain,
296  translation_key=err.translation_key,
297  translation_placeholders=err.translation_placeholders,
298  )
299  except Exception as err:
300  connection.logger.exception("Unexpected exception")
301  connection.send_error(msg["id"], const.ERR_UNKNOWN_ERROR, str(err))
302 
303 
304 @callback
306  hass: HomeAssistant, connection: ActiveConnection
307 ) -> list[State]:
308  user = connection.user
309  if user.is_admin or user.permissions.access_all_entities(POLICY_READ):
310  return hass.states.async_all()
311  entity_perm = connection.user.permissions.check_entity
312  return [
313  state
314  for state in hass.states.async_all()
315  if entity_perm(state.entity_id, POLICY_READ)
316  ]
317 
318 
319 @callback
320 @decorators.websocket_command({vol.Required("type"): "get_states"})
322  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
323 ) -> None:
324  """Handle get states command."""
325  states = _async_get_allowed_states(hass, connection)
326 
327  try:
328  serialized_states = [state.as_dict_json for state in states]
329  except (ValueError, TypeError):
330  pass
331  else:
332  _send_handle_get_states_response(connection, msg["id"], serialized_states)
333  return
334 
335  # If we can't serialize, we'll filter out unserializable states
336  serialized_states = []
337  for state in states:
338  try:
339  serialized_states.append(state.as_dict_json)
340  except (ValueError, TypeError):
341  connection.logger.error(
342  "Unable to serialize to JSON. Bad data found at %s",
344  find_paths_unserializable_data(state, dump=JSON_DUMP)
345  ),
346  )
347 
348  _send_handle_get_states_response(connection, msg["id"], serialized_states)
349 
350 
352  connection: ActiveConnection, msg_id: int, serialized_states: list[bytes]
353 ) -> None:
354  """Send handle get states response."""
355  connection.send_message(
357  msg_id, b"".join((b"[", b",".join(serialized_states), b"]"))
358  )
359  )
360 
361 
362 @callback
364  send_message: Callable[[str | bytes | dict[str, Any]], None],
365  entity_ids: set[str] | None,
366  entity_filter: Callable[[str], bool] | None,
367  user: User,
368  message_id_as_bytes: bytes,
369  event: Event[EventStateChangedData],
370 ) -> None:
371  """Forward entity state changed events to websocket."""
372  entity_id = event.data["entity_id"]
373  if (entity_ids and entity_id not in entity_ids) or (
374  entity_filter and not entity_filter(entity_id)
375  ):
376  return
377  # We have to lookup the permissions again because the user might have
378  # changed since the subscription was created.
379  permissions = user.permissions
380  if (
381  not user.is_admin
382  and not permissions.access_all_entities(POLICY_READ)
383  and not permissions.check_entity(entity_id, POLICY_READ)
384  ):
385  return
386  send_message(messages.cached_state_diff_message(message_id_as_bytes, event))
387 
388 
389 @callback
390 @decorators.websocket_command( { vol.Required("type"): "subscribe_entities",
391  vol.Optional("entity_ids"): cv.entity_ids,
392  **INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA.schema,
393  }
394 )
396  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
397 ) -> None:
398  """Handle subscribe entities command."""
399  entity_ids = set(msg.get("entity_ids", [])) or None
400  _filter = convert_include_exclude_filter(msg)
401  entity_filter = None if _filter.empty_filter else _filter.get_filter()
402  # We must never await between sending the states and listening for
403  # state changed events or we will introduce a race condition
404  # where some states are missed
405  states = _async_get_allowed_states(hass, connection)
406  msg_id = msg["id"]
407  message_id_as_bytes = str(msg_id).encode()
408  connection.subscriptions[msg_id] = hass.bus.async_listen(
409  EVENT_STATE_CHANGED,
410  partial(
411  _forward_entity_changes,
412  connection.send_message,
413  entity_ids,
414  entity_filter,
415  connection.user,
416  message_id_as_bytes,
417  ),
418  )
419  connection.send_result(msg_id)
420 
421  # JSON serialize here so we can recover if it blows up due to the
422  # state machine containing unserializable data. This command is required
423  # to succeed for the UI to show.
424  try:
425  if entity_ids or entity_filter:
426  serialized_states = [
427  state.as_compressed_state_json
428  for state in states
429  if (not entity_ids or state.entity_id in entity_ids)
430  and (not entity_filter or entity_filter(state.entity_id))
431  ]
432  else:
433  # Fast path when not filtering
434  serialized_states = [state.as_compressed_state_json for state in states]
435  except (ValueError, TypeError):
436  pass
437  else:
439  connection, message_id_as_bytes, serialized_states
440  )
441  return
442 
443  serialized_states = []
444  for state in states:
445  try:
446  serialized_states.append(state.as_compressed_state_json)
447  except (ValueError, TypeError):
448  connection.logger.error(
449  "Unable to serialize to JSON. Bad data found at %s",
451  find_paths_unserializable_data(state, dump=JSON_DUMP)
452  ),
453  )
454 
456  connection, message_id_as_bytes, serialized_states
457  )
458 
459 
461  connection: ActiveConnection,
462  message_id_as_bytes: bytes,
463  serialized_states: list[bytes],
464 ) -> None:
465  """Send handle entities init response."""
466  connection.send_message(
467  b"".join(
468  (
469  b'{"id":',
470  message_id_as_bytes,
471  b',"type":"event","event":{"a":{',
472  b",".join(serialized_states),
473  b"}}}",
474  )
475  )
476  )
477 
478 
479 async def _async_get_all_descriptions_json(hass: HomeAssistant) -> bytes:
480  """Return JSON of descriptions (i.e. user documentation) for all service calls."""
481  descriptions = await async_get_all_descriptions(hass)
482  if ALL_SERVICE_DESCRIPTIONS_JSON_CACHE in hass.data:
483  cached_descriptions, cached_json_payload = hass.data[
484  ALL_SERVICE_DESCRIPTIONS_JSON_CACHE
485  ]
486  # If the descriptions are the same, return the cached JSON payload
487  if cached_descriptions is descriptions:
488  return cast(bytes, cached_json_payload)
489  json_payload = json_bytes(descriptions)
490  hass.data[ALL_SERVICE_DESCRIPTIONS_JSON_CACHE] = (descriptions, json_payload)
491  return json_payload
492 
493 
494 @decorators.websocket_command({vol.Required("type"): "get_services"})
495 @decorators.async_response
496 async def handle_get_services(
497  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
498 ) -> None:
499  """Handle get services command."""
500  payload = await _async_get_all_descriptions_json(hass)
501  connection.send_message(construct_result_message(msg["id"], payload))
502 
503 
504 @callback
505 @decorators.websocket_command({vol.Required("type"): "get_config"})
507  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
508 ) -> None:
509  """Handle get config command."""
510  connection.send_result(msg["id"], hass.config.as_dict())
511 
512 
513 @decorators.websocket_command( {vol.Required("type"): "manifest/list", vol.Optional("integrations"): [str]}
514 )
515 @decorators.async_response
517  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
518 ) -> None:
519  """Handle integrations command."""
520  ints_or_excs = await async_get_integrations(
521  hass, msg.get("integrations") or async_get_loaded_integrations(hass)
522  )
523  manifest_json_fragments: list[json_fragment] = []
524  for int_or_exc in ints_or_excs.values():
525  if isinstance(int_or_exc, Exception):
526  raise int_or_exc
527  manifest_json_fragments.append(int_or_exc.manifest_json_fragment)
528  connection.send_result(msg["id"], manifest_json_fragments)
529 
530 
531 @decorators.websocket_command( {vol.Required("type"): "manifest/get", vol.Required("integration"): str}
532 )
533 @decorators.async_response
534 async def handle_manifest_get(
535  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
536 ) -> None:
537  """Handle integrations command."""
538  try:
539  integration = await async_get_integration(hass, msg["integration"])
540  except IntegrationNotFound:
541  connection.send_error(msg["id"], const.ERR_NOT_FOUND, "Integration not found")
542  else:
543  connection.send_result(msg["id"], integration.manifest_json_fragment)
544 
545 
546 @callback
547 @decorators.websocket_command({vol.Required("type"): "integration/setup_info"})
549  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
550 ) -> None:
551  """Handle integrations command."""
552  connection.send_result(
553  msg["id"],
554  [
555  {"domain": integration, "seconds": seconds}
556  for integration, seconds in async_get_setup_timings(hass).items()
557  ],
558  )
559 
560 
561 @callback
562 @decorators.websocket_command({vol.Required("type"): "ping"})
563 def handle_ping(
564  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
565 ) -> None:
566  """Handle ping command."""
567  connection.send_message(pong_message(msg["id"]))
568 
569 
570 @lru_cache
571 def _cached_template(template_str: str, hass: HomeAssistant) -> template.Template:
572  """Return a cached template."""
573  return template.Template(template_str, hass)
574 
575 
576 @decorators.websocket_command( { vol.Required("type"): "render_template",
577  vol.Required("template"): str,
578  vol.Optional("entity_ids"): cv.entity_ids,
579  vol.Optional("variables"): dict,
580  vol.Optional("timeout"): vol.Coerce(float),
581  vol.Optional("strict", default=False): bool,
582  vol.Optional("report_errors", default=False): bool,
583  }
584 )
585 @decorators.async_response
586 async def handle_render_template(
587  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
588 ) -> None:
589  """Handle render_template command."""
590  template_str = msg["template"]
591  report_errors: bool = msg["report_errors"]
592  if report_errors:
593  template_obj = template.Template(template_str, hass)
594  else:
595  template_obj = _cached_template(template_str, hass)
596  variables = msg.get("variables")
597  timeout = msg.get("timeout")
598 
599  @callback
600  def _error_listener(level: int, template_error: str) -> None:
601  connection.send_message(
602  messages.event_message(
603  msg["id"],
604  {"error": template_error, "level": logging.getLevelName(level)},
605  )
606  )
607 
608  @callback
609  def _thread_safe_error_listener(level: int, template_error: str) -> None:
610  hass.loop.call_soon_threadsafe(_error_listener, level, template_error)
611 
612  if timeout:
613  try:
614  log_fn = _thread_safe_error_listener if report_errors else None
615  timed_out = await template_obj.async_render_will_timeout(
616  timeout, variables, strict=msg["strict"], log_fn=log_fn
617  )
618  except TemplateError:
619  timed_out = False
620 
621  if timed_out:
622  connection.send_error(
623  msg["id"],
624  const.ERR_TEMPLATE_ERROR,
625  f"Exceeded maximum execution time of {timeout}s",
626  )
627  return
628 
629  @callback
630  def _template_listener(
631  event: Event[EventStateChangedData] | None,
632  updates: list[TrackTemplateResult],
633  ) -> None:
634  track_template_result = updates.pop()
635  result = track_template_result.result
636  if isinstance(result, TemplateError):
637  if not report_errors:
638  return
639  connection.send_message(
640  messages.event_message(
641  msg["id"], {"error": str(result), "level": "ERROR"}
642  )
643  )
644  return
645 
646  connection.send_message(
647  messages.event_message(
648  msg["id"], {"result": result, "listeners": info.listeners}
649  )
650  )
651 
652  try:
653  log_fn = _error_listener if report_errors else None
655  hass,
656  [TrackTemplate(template_obj, variables)],
657  _template_listener,
658  strict=msg["strict"],
659  log_fn=log_fn,
660  )
661  except TemplateError as ex:
662  connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(ex))
663  return
664 
665  connection.subscriptions[msg["id"]] = info.async_remove
666 
667  connection.send_result(msg["id"])
668 
669  hass.loop.call_soon_threadsafe(info.async_refresh)
670 
671 
673  entity_infos: dict[str, entity.EntityInfo],
674 ) -> dict[str, Any]:
675  """Prepare a websocket response from a dict of entity sources."""
676  return {
677  entity_id: {"domain": entity_info["domain"]}
678  for entity_id, entity_info in entity_infos.items()
679  }
680 
681 
682 @callback
683 @decorators.websocket_command({vol.Required("type"): "entity/source"})
685  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
686 ) -> None:
687  """Handle entity source command."""
688  all_entity_sources = entity.entity_sources(hass)
689  entity_perm = connection.user.permissions.check_entity
690 
691  if connection.user.permissions.access_all_entities(POLICY_READ):
692  entity_sources = all_entity_sources
693  else:
694  entity_sources = {
695  entity_id: source
696  for entity_id, source in all_entity_sources.items()
697  if entity_perm(entity_id, POLICY_READ)
698  }
699 
700  connection.send_result(msg["id"], _serialize_entity_sources(entity_sources))
701 
702 
703 @decorators.websocket_command( { vol.Required("type"): "subscribe_trigger",
704  vol.Required("trigger"): cv.TRIGGER_SCHEMA,
705  vol.Optional("variables"): dict,
706  }
707 )
708 @decorators.require_admin
709 @decorators.async_response
710 async def handle_subscribe_trigger(
711  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
712 ) -> None:
713  """Handle subscribe trigger command."""
714  # Circular dep
715  # pylint: disable-next=import-outside-toplevel
716  from homeassistant.helpers import trigger
717 
718  trigger_config = await trigger.async_validate_trigger_config(hass, msg["trigger"])
719 
720  @callback
721  def forward_triggers(
722  variables: dict[str, Any], context: Context | None = None
723  ) -> None:
724  """Forward events to websocket."""
725  message = messages.event_message(
726  msg["id"], {"variables": variables, "context": context}
727  )
728  connection.send_message(
729  json.dumps(
730  message, cls=ExtendedJSONEncoder, allow_nan=False, separators=(",", ":")
731  )
732  )
733 
734  connection.subscriptions[msg["id"]] = (
735  await trigger.async_initialize_triggers(
736  hass,
737  trigger_config,
738  forward_triggers,
739  const.DOMAIN,
740  const.DOMAIN,
741  connection.logger.log,
742  variables=msg.get("variables"),
743  )
744  ) or (
745  # Some triggers won't return an unsub function. Since the caller expects
746  # a subscription, we're going to fake one.
747  lambda: None
748  )
749  connection.send_result(msg["id"])
750 
751 
752 @decorators.websocket_command( { vol.Required("type"): "test_condition",
753  vol.Required("condition"): cv.CONDITION_SCHEMA,
754  vol.Optional("variables"): dict,
755  }
756 )
757 @decorators.require_admin
758 @decorators.async_response
759 async def handle_test_condition(
760  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
761 ) -> None:
762  """Handle test condition command."""
763  # Circular dep
764  # pylint: disable-next=import-outside-toplevel
765  from homeassistant.helpers import condition
766 
767  # Do static + dynamic validation of the condition
768  config = await condition.async_validate_condition_config(hass, msg["condition"])
769  # Test the condition
770  check_condition = await condition.async_from_config(hass, config)
771  connection.send_result(
772  msg["id"], {"result": check_condition(hass, msg.get("variables"))}
773  )
774 
775 
776 @decorators.websocket_command( { vol.Required("type"): "execute_script",
777  vol.Required("sequence"): cv.SCRIPT_SCHEMA,
778  vol.Optional("variables"): dict,
779  }
780 )
781 @decorators.require_admin
782 @decorators.async_response
783 async def handle_execute_script(
784  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
785 ) -> None:
786  """Handle execute script command."""
787  # Circular dep
788  # pylint: disable-next=import-outside-toplevel
789  from homeassistant.helpers.script import Script, async_validate_actions_config
790 
791  script_config = await async_validate_actions_config(hass, msg["sequence"])
792 
793  context = connection.context(msg)
794  script_obj = Script(hass, script_config, f"{const.DOMAIN} script", const.DOMAIN)
795  try:
796  script_result = await script_obj.async_run(
797  msg.get("variables"), context=context
798  )
799  except ServiceValidationError as err:
800  connection.logger.error(err)
801  connection.logger.debug("", exc_info=err)
802  connection.send_error(
803  msg["id"],
804  const.ERR_SERVICE_VALIDATION_ERROR,
805  str(err),
806  translation_domain=err.translation_domain,
807  translation_key=err.translation_key,
808  translation_placeholders=err.translation_placeholders,
809  )
810  return
811  connection.send_result(
812  msg["id"],
813  {
814  "context": context,
815  "response": script_result.service_response if script_result else None,
816  },
817  )
818 
819 
820 @callback
821 @decorators.websocket_command( { vol.Required("type"): "fire_event",
822  vol.Required("event_type"): str,
823  vol.Optional("event_data"): dict,
824  }
825 )
826 @decorators.require_admin
828  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
829 ) -> None:
830  """Handle fire event command."""
831  context = connection.context(msg)
832 
833  hass.bus.async_fire(msg["event_type"], msg.get("event_data"), context=context)
834  connection.send_result(msg["id"], {"context": context})
835 
836 
837 @decorators.websocket_command( { vol.Required("type"): "validate_config",
838  vol.Optional("triggers"): cv.match_all,
839  vol.Optional("conditions"): cv.match_all,
840  vol.Optional("actions"): cv.match_all,
841  }
842 )
843 @decorators.async_response
844 async def handle_validate_config(
845  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
846 ) -> None:
847  """Handle validate config command."""
848  # Circular dep
849  # pylint: disable-next=import-outside-toplevel
850  from homeassistant.helpers import condition, script, trigger
851 
852  result = {}
853 
854  for key, schema, validator in (
855  ("triggers", cv.TRIGGER_SCHEMA, trigger.async_validate_trigger_config),
856  (
857  "conditions",
858  cv.CONDITIONS_SCHEMA,
859  condition.async_validate_conditions_config,
860  ),
861  ("actions", cv.SCRIPT_SCHEMA, script.async_validate_actions_config),
862  ):
863  if key not in msg:
864  continue
865 
866  try:
867  await validator(hass, schema(msg[key]))
868  except (
869  vol.Invalid,
870  HomeAssistantError,
871  ) as err:
872  result[key] = {"valid": False, "error": str(err)}
873  else:
874  result[key] = {"valid": True, "error": None}
875 
876  connection.send_result(msg["id"], result)
877 
878 
879 @callback
880 @decorators.websocket_command( { vol.Required("type"): "supported_features",
881  vol.Required("features"): {str: int},
882  }
883 )
885  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
886 ) -> None:
887  """Handle setting supported features."""
888  connection.set_supported_features(msg["features"])
889  connection.send_result(msg["id"])
890 
891 
892 @decorators.require_admin
893 @decorators.websocket_command({"type": "integration/descriptions"})
894 @decorators.async_response
896  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
897 ) -> None:
898  """Get metadata for all brands and integrations."""
899  connection.send_result(msg["id"], await async_get_integration_descriptions(hass))
900 
None handle_get_config(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:518
list[State] _async_get_allowed_states(HomeAssistant hass, ActiveConnection connection)
Definition: commands.py:315
None handle_manifest_list(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:529
None handle_get_services(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:508
template.Template _cached_template(str template_str, HomeAssistant hass)
Definition: commands.py:583
None _forward_entity_changes(Callable[[str|bytes|dict[str, Any]], None] send_message, set[str]|None entity_ids, Callable[[str], bool]|None entity_filter, User user, bytes message_id_as_bytes, Event[EventStateChangedData] event)
Definition: commands.py:378
None handle_render_template(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:602
None handle_subscribe_bootstrap_integrations(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:188
None handle_execute_script(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:805
None handle_integration_setup_info(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:562
None handle_ping(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:577
None handle_unsubscribe_events(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:212
None handle_fire_event(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:851
None handle_test_condition(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:779
None _send_handle_get_states_response(ActiveConnection connection, int msg_id, list[bytes] serialized_states)
Definition: commands.py:361
None handle_supported_features(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:912
None handle_subscribe_events(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:147
dict[str, Any] _serialize_entity_sources(dict[str, entity.EntityInfo] entity_infos)
Definition: commands.py:688
None handle_manifest_get(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:548
bytes _async_get_all_descriptions_json(HomeAssistant hass)
Definition: commands.py:489
None handle_call_service(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:236
None async_register_commands(HomeAssistant hass, Callable[[HomeAssistant, const .WebSocketCommandHandler], None] async_reg)
Definition: commands.py:78
None handle_entity_source(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:700
None _send_handle_entities_init_response(ActiveConnection connection, bytes message_id_as_bytes, list[bytes] serialized_states)
Definition: commands.py:474
None _forward_events_check_permissions(Callable[[bytes|str|dict[str, Any]], None] send_message, User user, bytes message_id_as_bytes, Event event)
Definition: commands.py:114
None handle_subscribe_entities(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:407
None handle_get_states(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:331
None handle_subscribe_trigger(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:728
None handle_validate_config(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:870
None _forward_events_unconditional(Callable[[bytes|str|dict[str, Any]], None] send_message, bytes message_id_as_bytes, Event event)
Definition: commands.py:133
None handle_integration_descriptions(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: commands.py:923
bytes construct_result_message(int iden, bytes payload)
Definition: messages.py:68
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103
EntityFilter convert_include_exclude_filter(dict[str, dict[str, list[str]]] config)
TrackTemplateResultInfo async_track_template_result(HomeAssistant hass, Sequence[TrackTemplate] track_templates, TrackTemplateResultListener action, bool strict=False, Callable[[int, str], None]|None log_fn=None, bool has_super_template=False)
Definition: event.py:1345
dict[str, Any] find_paths_unserializable_data(Any bad_data, *Callable[[Any], str] dump=json.dumps)
Definition: json.py:233
list[ConfigType] async_validate_actions_config(HomeAssistant hass, list[ConfigType] actions)
Definition: script.py:300
dict[str, dict[str, Any]] async_get_all_descriptions(HomeAssistant hass)
Definition: service.py:699
dict[str, Integration|Exception] async_get_integrations(HomeAssistant hass, Iterable[str] domains)
Definition: loader.py:1368
dict[str, Any] async_get_integration_descriptions(HomeAssistant hass)
Definition: loader.py:408
Integration async_get_integration(HomeAssistant hass, str domain)
Definition: loader.py:1354
set[str] async_get_loaded_integrations(core.HomeAssistant hass)
Definition: setup.py:651
dict[str, float] async_get_setup_timings(core.HomeAssistant hass)
Definition: setup.py:798
str format_unserializable_data(dict[str, Any] data)
Definition: json.py:126