Add entity matching to intent_script (#120973)
parent
254aa8c9ea
commit
407e4f6ca2
|
@ -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(
|
||||
|
|
|
@ -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."""
|
||||
|
||||
|
|
Loading…
Reference in New Issue