Use device area id in intent matching (#86678)

* Use device area id when matching

* Normalize whitespace in response

* Add extra test entity
This commit is contained in:
Michael Hansen 2023-01-26 09:48:49 -06:00 committed by GitHub
parent 38203003d2
commit adeaf746ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 71 additions and 11 deletions

View File

@ -160,17 +160,19 @@ class DefaultAgent(AbstractConversationAgent):
).get(response_key)
if response_str:
response_template = template.Template(response_str, self.hass)
intent_response.async_set_speech(
response_template.async_render(
{
"slots": {
entity_name: entity_value.text or entity_value.value
for entity_name, entity_value in result.entities.items()
}
speech = response_template.async_render(
{
"slots": {
entity_name: entity_value.text or entity_value.value
for entity_name, entity_value in result.entities.items()
}
)
}
)
# Normalize whitespace
speech = " ".join(speech.strip().split())
intent_response.async_set_speech(speech)
return ConversationResult(
response=intent_response, conversation_id=conversation_id
)

View File

@ -20,7 +20,7 @@ from homeassistant.core import Context, HomeAssistant, State, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.loader import bind_hass
from . import area_registry, config_validation as cv, entity_registry
from . import area_registry, config_validation as cv, device_registry, entity_registry
_LOGGER = logging.getLogger(__name__)
_SlotsType = dict[str, Any]
@ -159,6 +159,7 @@ def async_match_states(
states: Iterable[State] | None = None,
entities: entity_registry.EntityRegistry | None = None,
areas: area_registry.AreaRegistry | None = None,
devices: device_registry.DeviceRegistry | None = None,
) -> Iterable[State]:
"""Find states that match the constraints."""
if states is None:
@ -206,11 +207,28 @@ def async_match_states(
assert area is not None, f"No area named {area_name}"
if area is not None:
if devices is None:
devices = device_registry.async_get(hass)
entity_area_ids: dict[str, str | None] = {}
for _state, entity in states_and_entities:
if entity is None:
continue
if entity.area_id:
# Use entity's area id first
entity_area_ids[entity.id] = entity.area_id
elif entity.device_id:
# Fall back to device area if not set on entity
device = devices.async_get(entity.device_id)
if device is not None:
entity_area_ids[entity.id] = device.area_id
# Filter by area
states_and_entities = [
(state, entity)
for state, entity in states_and_entities
if (entity is not None) and (entity.area_id == area.id)
if (entity is not None) and (entity_area_ids.get(entity.id) == area.id)
]
if name is not None:

View File

@ -9,6 +9,7 @@ from homeassistant.core import State
from homeassistant.helpers import (
area_registry,
config_validation as cv,
device_registry,
entity_registry,
intent,
)
@ -41,7 +42,7 @@ async def test_async_match_states(hass):
entities.async_update_entity(state1.entity_id, area_id=area_kitchen.id)
entities.async_get_or_create(
"switch", "demo", "1234", suggested_object_id="bedroom"
"switch", "demo", "5678", suggested_object_id="bedroom"
)
entities.async_update_entity(
state2.entity_id,
@ -92,6 +93,45 @@ async def test_async_match_states(hass):
)
async def test_match_device_area(hass):
"""Test async_match_state with a device in an area."""
areas = area_registry.async_get(hass)
area_kitchen = areas.async_get_or_create("kitchen")
area_bedroom = areas.async_get_or_create("bedroom")
devices = device_registry.async_get(hass)
kitchen_device = devices.async_get_or_create(
config_entry_id="1234", connections=set(), identifiers={("demo", "id-1234")}
)
devices.async_update_device(kitchen_device.id, area_id=area_kitchen.id)
state1 = State(
"light.kitchen", "on", attributes={ATTR_FRIENDLY_NAME: "kitchen light"}
)
state2 = State(
"light.bedroom", "on", attributes={ATTR_FRIENDLY_NAME: "bedroom light"}
)
state3 = State(
"light.living_room", "on", attributes={ATTR_FRIENDLY_NAME: "living room light"}
)
entities = entity_registry.async_get(hass)
entities.async_get_or_create("light", "demo", "1234", suggested_object_id="kitchen")
entities.async_update_entity(state1.entity_id, device_id=kitchen_device.id)
entities.async_get_or_create("light", "demo", "5678", suggested_object_id="bedroom")
entities.async_update_entity(state2.entity_id, area_id=area_bedroom.id)
# Match on area/domain
assert [state1] == list(
intent.async_match_states(
hass,
domains={"light"},
area_name="kitchen",
states=[state1, state2, state3],
)
)
def test_async_validate_slots():
"""Test async_validate_slots of IntentHandler."""
handler1 = MockIntentHandler({vol.Required("name"): cv.string})