Add entity matching to intent_script (#120973)

pull/124254/head
Artur Pragacz 2024-08-19 22:01:35 +02:00 committed by GitHub
parent 254aa8c9ea
commit 407e4f6ca2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 138 additions and 2 deletions

View File

@ -144,6 +144,12 @@ class _IntentCardData(TypedDict):
class ScriptIntentHandler(intent.IntentHandler):
"""Respond to an intent with a script."""
slot_schema = {
vol.Any("name", "area", "floor"): cv.string,
vol.Optional("domain"): vol.All(cv.ensure_list, [cv.string]),
vol.Optional("device_class"): vol.All(cv.ensure_list, [cv.string]),
}
def __init__(self, intent_type: str, config: ConfigType) -> None:
"""Initialize the script intent handler."""
self.intent_type = intent_type
@ -158,8 +164,10 @@ class ScriptIntentHandler(intent.IntentHandler):
card: _IntentCardData | None = self.config.get(CONF_CARD)
action: script.Script | None = self.config.get(CONF_ACTION)
is_async_action: bool = self.config[CONF_ASYNC_ACTION]
hass: HomeAssistant = intent_obj.hass
intent_slots = self.async_validate_slots(intent_obj.slots)
slots: dict[str, Any] = {
key: value["value"] for key, value in intent_obj.slots.items()
key: value["value"] for key, value in intent_slots.items()
}
_LOGGER.debug(
@ -172,6 +180,51 @@ class ScriptIntentHandler(intent.IntentHandler):
},
)
entity_name = slots.get("name")
area_name = slots.get("area")
floor_name = slots.get("floor")
# Optional domain/device class filters.
# Convert to sets for speed.
domains: set[str] | None = None
device_classes: set[str] | None = None
if "domain" in slots:
domains = set(slots["domain"])
if "device_class" in slots:
device_classes = set(slots["device_class"])
match_constraints = intent.MatchTargetsConstraints(
name=entity_name,
area_name=area_name,
floor_name=floor_name,
domains=domains,
device_classes=device_classes,
assistant=intent_obj.assistant,
)
if match_constraints.has_constraints:
match_result = intent.async_match_targets(hass, match_constraints)
if match_result.is_match:
targets = {}
if match_result.states:
targets["entities"] = [
state.entity_id for state in match_result.states
]
if match_result.areas:
targets["areas"] = [area.id for area in match_result.areas]
if match_result.floors:
targets["floors"] = [
floor.floor_id for floor in match_result.floors
]
if targets:
slots["targets"] = targets
if action is not None:
if is_async_action:
intent_obj.hass.async_create_task(

View File

@ -6,7 +6,12 @@ from homeassistant import config as hass_config
from homeassistant.components.intent_script import DOMAIN
from homeassistant.const import SERVICE_RELOAD
from homeassistant.core import HomeAssistant
from homeassistant.helpers import intent
from homeassistant.helpers import (
area_registry as ar,
entity_registry as er,
floor_registry as fr,
intent,
)
from homeassistant.setup import async_setup_component
from tests.common import async_mock_service, get_fixture_path
@ -197,6 +202,84 @@ async def test_intent_script_falsy_reprompt(hass: HomeAssistant) -> None:
assert response.card["simple"]["content"] == "Content for Paulus"
async def test_intent_script_targets(
hass: HomeAssistant,
area_registry: ar.AreaRegistry,
entity_registry: er.EntityRegistry,
floor_registry: fr.FloorRegistry,
) -> None:
"""Test intent scripts work."""
calls = async_mock_service(hass, "test", "service")
await async_setup_component(
hass,
"intent_script",
{
"intent_script": {
"Targets": {
"description": "Intent to control a test service.",
"action": {
"service": "test.service",
"data_template": {
"targets": "{{ targets if targets is defined }}",
},
},
"speech": {
"text": "{{ targets.entities[0] if targets is defined }}"
},
}
}
},
)
floor_1 = floor_registry.async_create("first floor")
kitchen = area_registry.async_get_or_create("kitchen")
area_registry.async_update(kitchen.id, floor_id=floor_1.floor_id)
entity_registry.async_get_or_create(
"light", "demo", "1234", suggested_object_id="kitchen"
)
entity_registry.async_update_entity("light.kitchen", area_id=kitchen.id)
hass.states.async_set("light.kitchen", "off")
response = await intent.async_handle(
hass,
"test",
"Targets",
{"name": {"value": "kitchen"}, "domain": {"value": "light"}},
)
assert len(calls) == 1
assert calls[0].data["targets"] == {"entities": ["light.kitchen"]}
assert response.speech["plain"]["speech"] == "light.kitchen"
calls.clear()
response = await intent.async_handle(
hass,
"test",
"Targets",
{
"area": {"value": "kitchen"},
"floor": {"value": "first floor"},
},
)
assert len(calls) == 1
assert calls[0].data["targets"] == {
"entities": ["light.kitchen"],
"areas": ["kitchen"],
"floors": ["first_floor"],
}
calls.clear()
response = await intent.async_handle(
hass,
"test",
"Targets",
{"device_class": {"value": "door"}},
)
assert len(calls) == 1
assert calls[0].data["targets"] == ""
calls.clear()
async def test_reload(hass: HomeAssistant) -> None:
"""Verify we can reload intent config."""