Home Assistant Unofficial Reference 2024.12.1
entity_registry.py
Go to the documentation of this file.
1 """HTTP views to interact with the entity registry."""
2 
3 from __future__ import annotations
4 
5 from typing import Any
6 
7 import voluptuous as vol
8 
9 from homeassistant import config_entries
10 from homeassistant.components import websocket_api
11 from homeassistant.components.websocket_api import ERR_NOT_FOUND, require_admin
12 from homeassistant.core import HomeAssistant, callback
13 from homeassistant.helpers import (
14  config_validation as cv,
15  device_registry as dr,
16  entity_registry as er,
17 )
18 from homeassistant.helpers.json import json_dumps
19 
20 
21 @callback
22 def async_setup(hass: HomeAssistant) -> bool:
23  """Enable the Entity Registry views."""
24 
25  websocket_api.async_register_command(hass, websocket_get_entities)
26  websocket_api.async_register_command(hass, websocket_get_entity)
27  websocket_api.async_register_command(hass, websocket_list_entities_for_display)
28  websocket_api.async_register_command(hass, websocket_list_entities)
29  websocket_api.async_register_command(hass, websocket_remove_entity)
30  websocket_api.async_register_command(hass, websocket_update_entity)
31  return True
32 
33 
34 @websocket_api.websocket_command({vol.Required("type"): "config/entity_registry/list"})
35 @callback
37  hass: HomeAssistant,
39  msg: dict[str, Any],
40 ) -> None:
41  """Handle list registry entries command."""
42  registry = er.async_get(hass)
43  # Build start of response message
44  msg_json_prefix = (
45  f'{{"id":{msg["id"]},"type": "{websocket_api.TYPE_RESULT}",'
46  '"success":true,"result": ['
47  ).encode()
48  # Concatenate cached entity registry item JSON serializations
49  inner = b",".join(
50  [
51  entry.partial_json_repr
52  for entry in registry.entities.values()
53  if entry.partial_json_repr is not None
54  ]
55  )
56  msg_json = b"".join((msg_json_prefix, inner, b"]}"))
57  connection.send_message(msg_json)
58 
59 
60 _ENTITY_CATEGORIES_JSON = json_dumps(er.ENTITY_CATEGORY_INDEX_TO_VALUE)
61 
62 
63 @websocket_api.websocket_command( {vol.Required("type"): "config/entity_registry/list_for_display"}
64 )
65 @callback
67  hass: HomeAssistant,
69  msg: dict[str, Any],
70 ) -> None:
71  """Handle list registry entries command."""
72  registry = er.async_get(hass)
73  # Build start of response message
74  msg_json_prefix = (
75  f'{{"id":{msg["id"]},"type":"{websocket_api.TYPE_RESULT}","success":true,'
76  f'"result":{{"entity_categories":{_ENTITY_CATEGORIES_JSON},"entities":['
77  ).encode()
78  # Concatenate cached entity registry item JSON serializations
79  inner = b",".join(
80  [
81  entry.display_json_repr
82  for entry in registry.entities.values()
83  if entry.disabled_by is None and entry.display_json_repr is not None
84  ]
85  )
86  msg_json = b"".join((msg_json_prefix, inner, b"]}}"))
87  connection.send_message(msg_json)
88 
89 
90 @websocket_api.websocket_command( { vol.Required("type"): "config/entity_registry/get",
91  vol.Required("entity_id"): cv.entity_id,
92  }
93 )
94 @callback
96  hass: HomeAssistant,
98  msg: dict[str, Any],
99 ) -> None:
100  """Handle get entity registry entry command.
101 
102  Async friendly.
103  """
104  registry = er.async_get(hass)
105 
106  if (entry := registry.entities.get(msg["entity_id"])) is None:
107  connection.send_message(
108  websocket_api.error_message(msg["id"], ERR_NOT_FOUND, "Entity not found")
109  )
110  return
111 
112  connection.send_message(
113  websocket_api.result_message(msg["id"], entry.extended_dict)
114  )
115 
116 
117 @websocket_api.websocket_command( { vol.Required("type"): "config/entity_registry/get_entries",
118  vol.Required("entity_ids"): cv.entity_ids,
119  }
120 )
121 @callback
123  hass: HomeAssistant,
124  connection: websocket_api.ActiveConnection,
125  msg: dict[str, Any],
126 ) -> None:
127  """Handle get entity registry entries command.
128 
129  Async friendly.
130  """
131  registry = er.async_get(hass)
132 
133  entity_ids = msg["entity_ids"]
134  entries: dict[str, dict[str, Any] | None] = {}
135  for entity_id in entity_ids:
136  entry = registry.entities.get(entity_id)
137  entries[entity_id] = entry.extended_dict if entry else None
138 
139  connection.send_message(websocket_api.result_message(msg["id"], entries))
140 
141 
142 @require_admin
143 @websocket_api.websocket_command( { vol.Required("type"): "config/entity_registry/update",
144  vol.Required("entity_id"): cv.entity_id,
145  # If passed in, we update value. Passing None will remove old value.
146  vol.Optional("aliases"): list,
147  vol.Optional("area_id"): vol.Any(str, None),
148  # Categories is a mapping of key/value (scope/category_id) pairs.
149  # If passed in, we update/adjust only the provided scope(s).
150  # Other category scopes in the entity, are left as is.
151  #
152  # Categorized items such as entities
153  # can only be in 1 category ID per scope at a time.
154  # Therefore, passing in a category ID will either add or move
155  # the entity to that specific category. Passing in None will
156  # remove the entity from the category.
157  vol.Optional("categories"): cv.schema_with_slug_keys(vol.Any(str, None)),
158  vol.Optional("device_class"): vol.Any(str, None),
159  vol.Optional("icon"): vol.Any(str, None),
160  vol.Optional("labels"): [str],
161  vol.Optional("name"): vol.Any(str, None),
162  vol.Optional("new_entity_id"): str,
163  # We only allow setting disabled_by user via API.
164  vol.Optional("disabled_by"): vol.Any(
165  None,
166  vol.All(
167  vol.Coerce(er.RegistryEntryDisabler),
168  er.RegistryEntryDisabler.USER.value,
169  ),
170  ),
171  # We only allow setting hidden_by user via API.
172  vol.Optional("hidden_by"): vol.Any(
173  None,
174  vol.All(
175  vol.Coerce(er.RegistryEntryHider),
176  er.RegistryEntryHider.USER.value,
177  ),
178  ),
179  vol.Inclusive("options_domain", "entity_option"): str,
180  vol.Inclusive("options", "entity_option"): vol.Any(None, dict),
181  }
182 )
183 @callback
185  hass: HomeAssistant,
186  connection: websocket_api.ActiveConnection,
187  msg: dict[str, Any],
188 ) -> None:
189  """Handle update entity websocket command.
190 
191  Async friendly.
192  """
193  registry = er.async_get(hass)
194 
195  entity_id = msg["entity_id"]
196  if not (entity_entry := registry.async_get(entity_id)):
197  connection.send_message(
198  websocket_api.error_message(msg["id"], ERR_NOT_FOUND, "Entity not found")
199  )
200  return
201 
202  changes = {}
203 
204  for key in (
205  "area_id",
206  "device_class",
207  "disabled_by",
208  "hidden_by",
209  "icon",
210  "name",
211  "new_entity_id",
212  ):
213  if key in msg:
214  changes[key] = msg[key]
215 
216  if "aliases" in msg:
217  # Convert aliases to a set
218  changes["aliases"] = set(msg["aliases"])
219 
220  if "labels" in msg:
221  # Convert labels to a set
222  changes["labels"] = set(msg["labels"])
223 
224  if "disabled_by" in msg and msg["disabled_by"] is None:
225  # Don't allow enabling an entity of a disabled device
226  if entity_entry.device_id:
227  device_registry = dr.async_get(hass)
228  device = device_registry.async_get(entity_entry.device_id)
229  if device and device.disabled:
230  connection.send_message(
231  websocket_api.error_message(
232  msg["id"], "invalid_info", "Device is disabled"
233  )
234  )
235  return
236 
237  # Update the categories if provided
238  if "categories" in msg:
239  categories = entity_entry.categories.copy()
240  for scope, category_id in msg["categories"].items():
241  if scope in categories and category_id is None:
242  # Remove the category from the scope as it was unset
243  del categories[scope]
244  elif category_id is not None:
245  # Add or update the category for the given scope
246  categories[scope] = category_id
247  changes["categories"] = categories
248 
249  try:
250  if changes:
251  entity_entry = registry.async_update_entity(entity_id, **changes)
252  except ValueError as err:
253  connection.send_message(
254  websocket_api.error_message(msg["id"], "invalid_info", str(err))
255  )
256  return
257 
258  if "new_entity_id" in msg:
259  entity_id = msg["new_entity_id"]
260 
261  try:
262  if "options_domain" in msg:
263  entity_entry = registry.async_update_entity_options(
264  entity_id, msg["options_domain"], msg["options"]
265  )
266  except ValueError as err:
267  connection.send_message(
268  websocket_api.error_message(msg["id"], "invalid_info", str(err))
269  )
270  return
271 
272  result: dict[str, Any] = {"entity_entry": entity_entry.extended_dict}
273  if "disabled_by" in changes and changes["disabled_by"] is None:
274  # Enabling an entity requires a config entry reload, or HA restart
275  if (
276  not (config_entry_id := entity_entry.config_entry_id)
277  or (config_entry := hass.config_entries.async_get_entry(config_entry_id))
278  and not config_entry.supports_unload
279  ):
280  result["require_restart"] = True
281  else:
282  result["reload_delay"] = config_entries.RELOAD_AFTER_UPDATE_DELAY
283  connection.send_result(msg["id"], result)
284 
285 
286 @require_admin
287 @websocket_api.websocket_command( { vol.Required("type"): "config/entity_registry/remove",
288  vol.Required("entity_id"): cv.entity_id,
289  }
290 )
291 @callback
293  hass: HomeAssistant,
294  connection: websocket_api.ActiveConnection,
295  msg: dict[str, Any],
296 ) -> None:
297  """Handle remove entity websocket command.
298 
299  Async friendly.
300  """
301  registry = er.async_get(hass)
302 
303  if msg["entity_id"] not in registry.entities:
304  connection.send_message(
305  websocket_api.error_message(msg["id"], ERR_NOT_FOUND, "Entity not found")
306  )
307  return
308 
309  registry.async_remove(msg["entity_id"])
310  connection.send_message(websocket_api.result_message(msg["id"]))
311 
None websocket_update_entity(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None websocket_get_entities(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None websocket_remove_entity(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None websocket_list_entities(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None websocket_list_entities_for_display(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None websocket_get_entity(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
str json_dumps(Any data)
Definition: json.py:149