1 """The Search integration."""
3 from __future__
import annotations
5 from collections
import defaultdict
6 from collections.abc
import Iterable
7 from enum
import StrEnum
11 import voluptuous
as vol
18 config_validation
as cv,
19 device_registry
as dr,
20 entity_registry
as er,
24 entity_sources
as get_entity_sources,
29 _LOGGER = logging.getLogger(__name__)
31 CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
39 AUTOMATION =
"automation"
40 AUTOMATION_BLUEPRINT =
"automation_blueprint"
41 CONFIG_ENTRY =
"config_entry"
46 INTEGRATION =
"integration"
51 SCRIPT_BLUEPRINT =
"script_blueprint"
54 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
55 """Set up the Search component."""
56 websocket_api.async_register_command(hass, websocket_search_related)
60 @websocket_api.websocket_command(
{
vol.Required("type"):
"search/related",
61 vol.Required(
"item_type"): vol.Coerce(ItemType),
62 vol.Required(
"item_id"): str,
72 searcher =
Searcher(hass, get_entity_sources(hass))
73 connection.send_result(
74 msg[
"id"], searcher.async_search(msg[
"item_type"], msg[
"item_id"])
79 """Find related things."""
81 EXIST_AS_ENTITY = {
"automation",
"group",
"person",
"scene",
"script"}
86 entity_sources: dict[str, EntityInfo],
94 self.results: defaultdict[ItemType, set[str]] = defaultdict(set)
97 def async_search(self, item_type: ItemType, item_id: str) -> dict[str, set[str]]:
99 _LOGGER.debug(
"Searching for %s/%s", item_type, item_id)
100 getattr(self, f
"_async_search_{item_type}")(item_id)
103 if item_type
in self.results
and item_id
in self.results[item_type]:
104 self.results[item_type].
remove(item_id)
107 return {key: val
for key, val
in self.results.items()
if val}
110 def _add(self, item_type: ItemType, item_id: str | Iterable[str] |
None) ->
None:
111 """Add an item (or items) to the results."""
115 if isinstance(item_id, str):
116 self.results[item_type].
add(item_id)
118 self.results[item_type].
update(item_id)
122 """Find results for an area."""
128 self.
_add_add(ItemType.LABEL, area_entry.labels)
132 ItemType.AUTOMATION, automation.automations_with_area(self.
hasshass, area_id)
136 self.
_add_add(ItemType.SCRIPT, script.scripts_with_area(self.
hasshass, area_id))
139 entity_entries = er.async_entries_for_area(self.
_entity_registry_entity_registry, area_id)
142 for device
in dr.async_entries_for_area(self.
_device_registry_device_registry, area_id):
143 self.
_add_add(ItemType.DEVICE, device.id)
147 self.
_add_add(ItemType.CONFIG_ENTRY, device_entry.config_entries)
152 automation.automations_with_device(self.
hasshass, device.id),
156 self.
_add_add(ItemType.SCRIPT, script.scripts_with_device(self.
hasshass, device.id))
159 for entity_entry
in er.async_entries_for_device(
163 if entity_entry.area_id
is not None:
165 entity_entries.append(entity_entry)
168 for entity_entry
in entity_entries:
169 self.
_add_add(ItemType.ENTITY, entity_entry.entity_id)
173 self.
_add_add(
ItemType(entity_entry.domain), entity_entry.entity_id)
178 automation.automations_with_entity(self.
hasshass, entity_entry.entity_id),
184 script.scripts_with_entity(self.
hasshass, entity_entry.entity_id),
190 group.groups_with_entity(self.
hasshass, entity_entry.entity_id),
196 person.persons_with_entity(self.
hasshass, entity_entry.entity_id),
202 scene.scenes_with_entity(self.
hasshass, entity_entry.entity_id),
206 self.
_add_add(ItemType.CONFIG_ENTRY, entity_entry.config_entry_id)
210 """Find results for an automation."""
214 self.
_add_add(ItemType.LABEL, entity_entry.labels)
218 ItemType.AUTOMATION_BLUEPRINT,
219 automation.blueprint_in_automation(self.
hasshass, automation_entity_id),
225 automation.floors_in_automation(self.
hasshass, automation_entity_id),
229 for area
in automation.areas_in_automation(self.
hasshass, automation_entity_id):
230 self.
_add_add(ItemType.AREA, area)
234 for device
in automation.devices_in_automation(self.
hasshass, automation_entity_id):
235 self.
_add_add(ItemType.DEVICE, device)
239 for entity_id
in automation.entities_in_automation(
240 self.
hasshass, automation_entity_id
242 self.
_add_add(ItemType.ENTITY, entity_id)
252 if domain ==
"group":
253 for group_entity_id
in group.get_entity_ids(self.
hasshass, entity_id):
254 self.
_add_add(ItemType.ENTITY, group_entity_id)
259 if domain ==
"scene":
260 for scene_entity_id
in scene.entities_in_scene(self.
hasshass, entity_id):
261 self.
_add_add(ItemType.ENTITY, scene_entity_id)
266 if domain ==
"script":
271 """Find results for an automation blueprint."""
274 automation.automations_with_blueprint(self.
hasshass, blueprint_path),
279 """Find results for a config entry."""
280 for device_entry
in dr.async_entries_for_config_entry(
283 self.
_add_add(ItemType.DEVICE, device_entry.id)
286 for entity_entry
in er.async_entries_for_config_entry(
289 self.
_add_add(ItemType.ENTITY, entity_entry.entity_id)
294 """Find results for a device."""
300 self.
_add_add(ItemType.LABEL, device_entry.labels)
305 automation.automations_with_device(self.
hasshass, device_id),
309 self.
_add_add(ItemType.SCRIPT, script.scripts_with_device(self.
hasshass, device_id))
312 for entity_entry
in er.async_entries_for_device(
315 self.
_add_add(ItemType.ENTITY, entity_entry.entity_id)
321 """Find results for an entity."""
325 if entity_entry
and entry_point:
327 self.
_add_add(ItemType.LABEL, entity_entry.labels)
332 automation.automations_with_entity(self.
hasshass, entity_id),
336 self.
_add_add(ItemType.SCRIPT, script.scripts_with_entity(self.
hasshass, entity_id))
339 self.
_add_add(ItemType.GROUP, group.groups_with_entity(self.
hasshass, entity_id))
342 self.
_add_add(ItemType.PERSON, person.persons_with_entity(self.
hasshass, entity_id))
345 self.
_add_add(ItemType.SCENE, scene.scenes_with_entity(self.
hasshass, entity_id))
349 """Find results for a floor."""
353 automation.automations_with_floor(self.
hasshass, floor_id),
357 self.
_add_add(ItemType.SCRIPT, script.scripts_with_floor(self.
hasshass, floor_id))
359 for area_entry
in ar.async_entries_for_floor(self.
_area_registry_area_registry, floor_id):
360 self.
_add_add(ItemType.AREA, area_entry.id)
365 """Find results for a group.
367 Note: We currently only support the classic groups, thus
368 we don't look up the area/floor for a group entity.
373 automation.automations_with_entity(self.
hasshass, group_entity_id),
378 ItemType.SCRIPT, script.scripts_with_entity(self.
hasshass, group_entity_id)
382 self.
_add_add(ItemType.SCENE, scene.scenes_with_entity(self.
hasshass, group_entity_id))
385 for entity_id
in group.get_entity_ids(self.
hasshass, group_entity_id):
386 self.
_add_add(ItemType.ENTITY, entity_id)
391 """Find results for a label."""
394 for area_entry
in ar.async_entries_for_label(self.
_area_registry_area_registry, label_id):
395 self.
_add_add(ItemType.AREA, area_entry.id)
398 for device
in dr.async_entries_for_label(self.
_device_registry_device_registry, label_id):
399 self.
_add_add(ItemType.DEVICE, device.id)
402 for entity_entry
in er.async_entries_for_label(self.
_entity_registry_entity_registry, label_id):
403 self.
_add_add(ItemType.ENTITY, entity_entry.entity_id)
413 automation.automations_with_label(self.
hasshass, label_id),
417 self.
_add_add(ItemType.SCRIPT, script.scripts_with_label(self.
hasshass, label_id))
421 """Find results for a person."""
425 self.
_add_add(ItemType.LABEL, entity_entry.labels)
430 automation.automations_with_entity(self.
hasshass, person_entity_id),
435 ItemType.SCRIPT, script.scripts_with_entity(self.
hasshass, person_entity_id)
440 ItemType.ENTITY, person.entities_in_person(self.
hasshass, person_entity_id)
445 """Find results for a scene."""
449 self.
_add_add(ItemType.LABEL, entity_entry.labels)
454 automation.automations_with_entity(self.
hasshass, scene_entity_id),
459 ItemType.SCRIPT, script.scripts_with_entity(self.
hasshass, scene_entity_id)
463 for entity
in scene.entities_in_scene(self.
hasshass, scene_entity_id):
464 self.
_add_add(ItemType.ENTITY, entity)
469 self, script_entity_id: str, *, entry_point: bool =
True
471 """Find results for a script."""
475 if entity_entry
and entry_point:
477 self.
_add_add(ItemType.LABEL, entity_entry.labels)
481 ItemType.SCRIPT_BLUEPRINT,
482 script.blueprint_in_script(self.
hasshass, script_entity_id),
486 self.
_add_add(ItemType.FLOOR, script.floors_in_script(self.
hasshass, script_entity_id))
489 for area
in script.areas_in_script(self.
hasshass, script_entity_id):
490 self.
_add_add(ItemType.AREA, area)
494 for device
in script.devices_in_script(self.
hasshass, script_entity_id):
495 self.
_add_add(ItemType.DEVICE, device)
499 for entity_id
in script.entities_in_script(self.
hasshass, script_entity_id):
500 self.
_add_add(ItemType.ENTITY, entity_id)
510 if domain ==
"group":
511 for group_entity_id
in group.get_entity_ids(self.
hasshass, entity_id):
512 self.
_add_add(ItemType.ENTITY, group_entity_id)
517 if domain ==
"scene":
518 for scene_entity_id
in scene.entities_in_scene(self.
hasshass, entity_id):
519 self.
_add_add(ItemType.ENTITY, scene_entity_id)
524 if domain ==
"script":
529 """Find results for a script blueprint."""
531 ItemType.SCRIPT, script.scripts_with_blueprint(self.
hasshass, blueprint_path)
536 """Resolve up from a device.
538 Above a device is an area or floor.
539 Above a device is also the config entry.
542 if device_entry.area_id:
543 self.
_add_add(ItemType.AREA, device_entry.area_id)
546 self.
_add_add(ItemType.CONFIG_ENTRY, device_entry.config_entries)
547 for config_entry_id
in device_entry.config_entries:
548 if entry := self.
hasshass.config_entries.async_get_entry(config_entry_id):
549 self.
_add_add(ItemType.INTEGRATION, entry.domain)
555 """Resolve up from an entity.
557 Above an entity is a device, area or floor.
558 Above an entity is also the config entry.
562 if entity_entry.area_id:
563 self.
_add_add(ItemType.AREA, entity_entry.area_id)
567 elif entity_entry.device_id
and (
570 if device_entry.area_id:
571 self.
_add_add(ItemType.AREA, device_entry.area_id)
575 self.
_add_add(ItemType.DEVICE, entity_entry.device_id)
578 if entity_entry.config_entry_id:
579 self.
_add_add(ItemType.CONFIG_ENTRY, entity_entry.config_entry_id)
581 if entry := self.
hasshass.config_entries.async_get_entry(
582 entity_entry.config_entry_id
585 self.
_add_add(ItemType.INTEGRATION, entry.domain)
589 self.
_add_add(ItemType.CONFIG_ENTRY, source.get(
"config_entry"))
590 self.
_add_add(ItemType.INTEGRATION, source[
"domain"])
596 """Resolve up from an area.
598 Above an area can be a floor.
600 if area_entry := self.
_area_registry_area_registry.async_get_area(area_id):
601 self.
_add_add(ItemType.FLOOR, area_entry.floor_id)
604
None _async_search_script(self, str script_entity_id, *bool entry_point=True)
None _async_search_automation(self, str automation_entity_id)
None __init__(self, HomeAssistant hass, dict[str, EntityInfo] entity_sources)
None _async_search_automation_blueprint(self, str blueprint_path)
None _async_search_config_entry(self, str config_entry_id)
dict[str, set[str]] async_search(self, ItemType item_type, str item_id)
None _async_search_scene(self, str scene_entity_id)
None _async_search_device(self, str device_id, *bool entry_point=True)
dictionary EXIST_AS_ENTITY
None _async_search_entity(self, str entity_id, *bool entry_point=True)
dr.DeviceEntry|None _async_resolve_up_device(self, str device_id)
None _async_search_person(self, str person_entity_id)
None _async_search_area(self, str area_id, *bool entry_point=True)
er.RegistryEntry|None _async_resolve_up_entity(self, str entity_id)
None _async_search_group(self, str group_entity_id)
None _async_search_script_blueprint(self, str blueprint_path)
None _async_search_floor(self, str floor_id)
ar.AreaEntry|None _async_resolve_up_area(self, str area_id)
None _add(self, ItemType item_type, str|Iterable[str]|None item_id)
None _async_search_label(self, str label_id)
bool add(self, _T matcher)
bool remove(self, _T matcher)
web.Response get(self, web.Request request, str config_key)
IssData update(pyiss.ISS iss)
None websocket_search_related(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
bool async_setup(HomeAssistant hass, ConfigType config)
tuple[str, str] split_entity_id(str entity_id)
AreaRegistry async_get(HomeAssistant hass)