mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
Skip unexposed entities in intent handlers (#92415)
* Filter intent handler entities by exposure * Add test for skipping unexposed entities
This commit is contained in:
parent
2ae3e90238
commit
74560ab139
@ -201,6 +201,7 @@ class DefaultAgent(AbstractConversationAgent):
|
|||||||
user_input.text,
|
user_input.text,
|
||||||
user_input.context,
|
user_input.context,
|
||||||
language,
|
language,
|
||||||
|
assistant=DOMAIN,
|
||||||
)
|
)
|
||||||
except intent.IntentHandleError:
|
except intent.IntentHandleError:
|
||||||
_LOGGER.exception("Intent handling error")
|
_LOGGER.exception("Intent handling error")
|
||||||
|
@ -140,16 +140,18 @@ class GetStateIntentHandler(intent.IntentHandler):
|
|||||||
area=area,
|
area=area,
|
||||||
domains=domains,
|
domains=domains,
|
||||||
device_classes=device_classes,
|
device_classes=device_classes,
|
||||||
|
assistant=intent_obj.assistant,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Found %s state(s) that matched: name=%s, area=%s, domains=%s, device_classes=%s",
|
"Found %s state(s) that matched: name=%s, area=%s, domains=%s, device_classes=%s, assistant=%s",
|
||||||
len(states),
|
len(states),
|
||||||
name,
|
name,
|
||||||
area,
|
area,
|
||||||
domains,
|
domains,
|
||||||
device_classes,
|
device_classes,
|
||||||
|
intent_obj.assistant,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create response
|
# Create response
|
||||||
|
@ -11,6 +11,7 @@ from typing import Any, TypeVar
|
|||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.components.homeassistant.exposed_entities import async_should_expose
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_DEVICE_CLASS,
|
ATTR_DEVICE_CLASS,
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
@ -65,6 +66,7 @@ async def async_handle(
|
|||||||
text_input: str | None = None,
|
text_input: str | None = None,
|
||||||
context: Context | None = None,
|
context: Context | None = None,
|
||||||
language: str | None = None,
|
language: str | None = None,
|
||||||
|
assistant: str | None = None,
|
||||||
) -> IntentResponse:
|
) -> IntentResponse:
|
||||||
"""Handle an intent."""
|
"""Handle an intent."""
|
||||||
handler: IntentHandler = hass.data.get(DATA_KEY, {}).get(intent_type)
|
handler: IntentHandler = hass.data.get(DATA_KEY, {}).get(intent_type)
|
||||||
@ -79,7 +81,14 @@ async def async_handle(
|
|||||||
language = hass.config.language
|
language = hass.config.language
|
||||||
|
|
||||||
intent = Intent(
|
intent = Intent(
|
||||||
hass, platform, intent_type, slots or {}, text_input, context, language
|
hass,
|
||||||
|
platform=platform,
|
||||||
|
intent_type=intent_type,
|
||||||
|
slots=slots or {},
|
||||||
|
text_input=text_input,
|
||||||
|
context=context,
|
||||||
|
language=language,
|
||||||
|
assistant=assistant,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -208,6 +217,7 @@ def async_match_states(
|
|||||||
entities: entity_registry.EntityRegistry | None = None,
|
entities: entity_registry.EntityRegistry | None = None,
|
||||||
areas: area_registry.AreaRegistry | None = None,
|
areas: area_registry.AreaRegistry | None = None,
|
||||||
devices: device_registry.DeviceRegistry | None = None,
|
devices: device_registry.DeviceRegistry | None = None,
|
||||||
|
assistant: str | None = None,
|
||||||
) -> Iterable[State]:
|
) -> Iterable[State]:
|
||||||
"""Find states that match the constraints."""
|
"""Find states that match the constraints."""
|
||||||
if states is None:
|
if states is None:
|
||||||
@ -258,6 +268,14 @@ def async_match_states(
|
|||||||
|
|
||||||
states_and_entities = list(_filter_by_area(states_and_entities, area, devices))
|
states_and_entities = list(_filter_by_area(states_and_entities, area, devices))
|
||||||
|
|
||||||
|
if assistant is not None:
|
||||||
|
# Filter by exposure
|
||||||
|
states_and_entities = [
|
||||||
|
(state, entity)
|
||||||
|
for state, entity in states_and_entities
|
||||||
|
if async_should_expose(hass, assistant, state.entity_id)
|
||||||
|
]
|
||||||
|
|
||||||
if name is not None:
|
if name is not None:
|
||||||
if devices is None:
|
if devices is None:
|
||||||
devices = device_registry.async_get(hass)
|
devices = device_registry.async_get(hass)
|
||||||
@ -387,6 +405,7 @@ class ServiceIntentHandler(IntentHandler):
|
|||||||
area=area,
|
area=area,
|
||||||
domains=domains,
|
domains=domains,
|
||||||
device_classes=device_classes,
|
device_classes=device_classes,
|
||||||
|
assistant=intent_obj.assistant,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -496,6 +515,7 @@ class Intent:
|
|||||||
"context",
|
"context",
|
||||||
"language",
|
"language",
|
||||||
"category",
|
"category",
|
||||||
|
"assistant",
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -508,6 +528,7 @@ class Intent:
|
|||||||
context: Context,
|
context: Context,
|
||||||
language: str,
|
language: str,
|
||||||
category: IntentCategory | None = None,
|
category: IntentCategory | None = None,
|
||||||
|
assistant: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize an intent."""
|
"""Initialize an intent."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
@ -518,6 +539,7 @@ class Intent:
|
|||||||
self.context = context
|
self.context = context
|
||||||
self.language = language
|
self.language = language
|
||||||
self.category = category
|
self.category = category
|
||||||
|
self.assistant = assistant
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def create_response(self) -> IntentResponse:
|
def create_response(self) -> IntentResponse:
|
||||||
|
@ -174,3 +174,52 @@ async def test_expose_flag_automatically_set(
|
|||||||
new_light: {"should_expose": True},
|
new_light: {"should_expose": True},
|
||||||
test.entity_id: {"should_expose": False},
|
test.entity_id: {"should_expose": False},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_unexposed_entities_skipped(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
init_components,
|
||||||
|
area_registry: ar.AreaRegistry,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
) -> None:
|
||||||
|
"""Test that unexposed entities are skipped in exposed areas."""
|
||||||
|
area_kitchen = area_registry.async_get_or_create("kitchen")
|
||||||
|
|
||||||
|
# Both lights are in the kitchen
|
||||||
|
exposed_light = entity_registry.async_get_or_create("light", "demo", "1234")
|
||||||
|
entity_registry.async_update_entity(
|
||||||
|
exposed_light.entity_id,
|
||||||
|
area_id=area_kitchen.id,
|
||||||
|
)
|
||||||
|
hass.states.async_set(exposed_light.entity_id, "off")
|
||||||
|
|
||||||
|
unexposed_light = entity_registry.async_get_or_create("light", "demo", "5678")
|
||||||
|
entity_registry.async_update_entity(
|
||||||
|
unexposed_light.entity_id,
|
||||||
|
area_id=area_kitchen.id,
|
||||||
|
)
|
||||||
|
hass.states.async_set(unexposed_light.entity_id, "off")
|
||||||
|
|
||||||
|
# On light is exposed, the other is not
|
||||||
|
expose_entity(hass, exposed_light.entity_id, True)
|
||||||
|
expose_entity(hass, unexposed_light.entity_id, False)
|
||||||
|
|
||||||
|
# Only one light should be turned on
|
||||||
|
calls = async_mock_service(hass, "light", "turn_on")
|
||||||
|
result = await conversation.async_converse(
|
||||||
|
hass, "turn on kitchen lights", None, Context(), None
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(calls) == 1
|
||||||
|
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||||
|
|
||||||
|
# Only one light should be returned
|
||||||
|
hass.states.async_set(exposed_light.entity_id, "on")
|
||||||
|
hass.states.async_set(unexposed_light.entity_id, "on")
|
||||||
|
result = await conversation.async_converse(
|
||||||
|
hass, "how many lights are on in the kitchen", None, Context(), None
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.response.response_type == intent.IntentResponseType.QUERY_ANSWER
|
||||||
|
assert len(result.response.matched_states) == 1
|
||||||
|
assert result.response.matched_states[0].entity_id == exposed_light.entity_id
|
||||||
|
Loading…
x
Reference in New Issue
Block a user