Allow searching for person (#77339)

This commit is contained in:
Paulus Schoutsen 2022-08-29 13:59:00 -04:00 committed by GitHub
parent 4ba8fb6457
commit 7d9ae0784e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 140 additions and 4 deletions

View file

@ -125,6 +125,38 @@ async def async_add_user_device_tracker(
break
@callback
def persons_with_entity(hass: HomeAssistant, entity_id: str) -> list[str]:
"""Return all persons that reference the entity."""
if (
DOMAIN not in hass.data
or split_entity_id(entity_id)[0] != DEVICE_TRACKER_DOMAIN
):
return []
component: EntityComponent = hass.data[DOMAIN][2]
return [
person_entity.entity_id
for person_entity in component.entities
if entity_id in cast(Person, person_entity).device_trackers
]
@callback
def entities_in_person(hass: HomeAssistant, entity_id: str) -> list[str]:
"""Return all entities belonging to a person."""
if DOMAIN not in hass.data:
return []
component: EntityComponent = hass.data[DOMAIN][2]
if (person_entity := component.get_entity(entity_id)) is None:
return []
return cast(Person, person_entity).device_trackers
CREATE_FIELDS = {
vol.Required(CONF_NAME): vol.All(str, vol.Length(min=1)),
vol.Optional(CONF_USER_ID): vol.Any(str, None),
@ -318,7 +350,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
)
await storage_collection.async_load()
hass.data[DOMAIN] = (yaml_collection, storage_collection)
hass.data[DOMAIN] = (yaml_collection, storage_collection, entity_component)
collection.StorageCollectionWebsocket(
storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS
@ -412,6 +444,11 @@ class Person(RestoreEntity):
"""Return a unique ID for the person."""
return self._config[CONF_ID]
@property
def device_trackers(self):
"""Return the device trackers for the person."""
return self._config[CONF_DEVICE_TRACKERS]
async def async_added_to_hass(self):
"""Register device trackers."""
await super().async_added_to_hass()
@ -506,7 +543,7 @@ def ws_list_person(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg
):
"""List persons."""
yaml, storage = hass.data[DOMAIN]
yaml, storage, _ = hass.data[DOMAIN]
connection.send_result(
msg[ATTR_ID], {"storage": storage.async_items(), "config": yaml.async_items()}
)

View file

@ -6,7 +6,7 @@ import logging
import voluptuous as vol
from homeassistant.components import automation, group, script, websocket_api
from homeassistant.components import automation, group, person, script, websocket_api
from homeassistant.components.homeassistant import scene
from homeassistant.core import HomeAssistant, callback, split_entity_id
from homeassistant.helpers import device_registry, entity_registry
@ -36,6 +36,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"group",
"scene",
"script",
"person",
)
),
vol.Required("item_id"): str,
@ -67,7 +68,7 @@ class Searcher:
# These types won't be further explored. Config entries + Output types.
DONT_RESOLVE = {"scene", "automation", "script", "group", "config_entry", "area"}
# These types exist as an entity and so need cleanup in results
EXIST_AS_ENTITY = {"script", "scene", "automation", "group"}
EXIST_AS_ENTITY = {"script", "scene", "automation", "group", "person"}
def __init__(
self,
@ -183,6 +184,9 @@ class Searcher:
for entity in script.scripts_with_entity(self.hass, entity_id):
self._add_or_resolve("entity", entity)
for entity in person.persons_with_entity(self.hass, entity_id):
self._add_or_resolve("entity", entity)
# Find devices
entity_entry = self._entity_reg.async_get(entity_id)
if entity_entry is not None:
@ -251,6 +255,15 @@ class Searcher:
for entity in scene.entities_in_scene(self.hass, scene_entity_id):
self._add_or_resolve("entity", entity)
@callback
def _resolve_person(self, person_entity_id) -> None:
"""Resolve a person.
Will only be called if person is an entry point.
"""
for entity in person.entities_in_person(self.hass, person_entity_id):
self._add_or_resolve("entity", entity)
@callback
def _resolve_config_entry(self, config_entry_id) -> None:
"""Resolve a config entry.

View file

@ -783,3 +783,59 @@ async def test_person_storage_fixing_device_trackers(storage_collection):
await storage_collection.async_load()
assert storage_collection.data["bla"]["device_trackers"] == []
async def test_persons_with_entity(hass):
"""Test finding persons with an entity."""
assert await async_setup_component(
hass,
"person",
{
"person": [
{
"id": "abcd",
"name": "Paulus",
"device_trackers": [
"device_tracker.paulus_iphone",
"device_tracker.paulus_ipad",
],
},
{
"id": "efgh",
"name": "Anne Therese",
"device_trackers": [
"device_tracker.at_pixel",
],
},
]
},
)
assert person.persons_with_entity(hass, "device_tracker.paulus_iphone") == [
"person.paulus"
]
async def test_entities_in_person(hass):
"""Test finding entities tracked by person."""
assert await async_setup_component(
hass,
"person",
{
"person": [
{
"id": "abcd",
"name": "Paulus",
"device_trackers": [
"device_tracker.paulus_iphone",
"device_tracker.paulus_ipad",
],
}
]
},
)
assert person.entities_in_person(hass, "person.paulus") == [
"device_tracker.paulus_iphone",
"device_tracker.paulus_ipad",
]

View file

@ -368,6 +368,36 @@ async def test_area_lookup(hass):
}
async def test_person_lookup(hass):
"""Test searching persons."""
assert await async_setup_component(
hass,
"person",
{
"person": [
{
"id": "abcd",
"name": "Paulus",
"device_trackers": ["device_tracker.paulus_iphone"],
}
]
},
)
device_reg = dr.async_get(hass)
entity_reg = er.async_get(hass)
searcher = search.Searcher(hass, device_reg, entity_reg, MOCK_ENTITY_SOURCES)
assert searcher.async_search("entity", "device_tracker.paulus_iphone") == {
"person": {"person.paulus"},
}
searcher = search.Searcher(hass, device_reg, entity_reg, MOCK_ENTITY_SOURCES)
assert searcher.async_search("entity", "person.paulus") == {
"entity": {"device_tracker.paulus_iphone"},
}
async def test_ws_api(hass, hass_ws_client):
"""Test WS API."""
assert await async_setup_component(hass, "search", {})