From 487dd3f95685175a0ffd8d170e7a384421622881 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Wed, 28 Jun 2023 17:34:43 -0500 Subject: [PATCH] Add targeted entities to sentence debug API (#95480) * Return targets with debug sentence API * Update test * Update homeassistant/components/conversation/__init__.py * Include area/domain in test sentences --------- Co-authored-by: Paulus Schoutsen --- .../components/conversation/__init__.py | 48 +++++++++++++++ .../conversation/snapshots/test_init.ambr | 59 +++++++++++++++++++ tests/components/conversation/test_init.py | 10 +++- 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/conversation/__init__.py b/homeassistant/components/conversation/__init__.py index f704a8baa33..5b82b5dae72 100644 --- a/homeassistant/components/conversation/__init__.py +++ b/homeassistant/components/conversation/__init__.py @@ -8,6 +8,7 @@ import logging import re from typing import Any, Literal +from hassil.recognize import RecognizeResult import voluptuous as vol from homeassistant import core @@ -353,6 +354,10 @@ async def websocket_hass_agent_debug( } for entity_key, entity in result.entities.items() }, + "targets": { + state.entity_id: {"matched": is_matched} + for state, is_matched in _get_debug_targets(hass, result) + }, } if result is not None else None @@ -362,6 +367,49 @@ async def websocket_hass_agent_debug( ) +def _get_debug_targets( + hass: HomeAssistant, + result: RecognizeResult, +) -> Iterable[tuple[core.State, bool]]: + """Yield state/is_matched pairs for a hassil recognition.""" + entities = result.entities + + name: str | None = None + area_name: str | None = None + domains: set[str] | None = None + device_classes: set[str] | None = None + state_names: set[str] | None = None + + if "name" in entities: + name = str(entities["name"].value) + + if "area" in entities: + area_name = str(entities["area"].value) + + if "domain" in entities: + domains = set(cv.ensure_list(entities["domain"].value)) + + if "device_class" in entities: + device_classes = set(cv.ensure_list(entities["device_class"].value)) + + if "state" in entities: + # HassGetState only + state_names = set(cv.ensure_list(entities["state"].value)) + + states = intent.async_match_states( + hass, + name=name, + area_name=area_name, + domains=domains, + device_classes=device_classes, + ) + + for state in states: + # For queries, a target is "matched" based on its state + is_matched = (state_names is None) or (state.state in state_names) + yield state, is_matched + + class ConversationProcessView(http.HomeAssistantView): """View to process text.""" diff --git a/tests/components/conversation/snapshots/test_init.ambr b/tests/components/conversation/snapshots/test_init.ambr index 23afe9ce3f7..8ef0cef52f9 100644 --- a/tests/components/conversation/snapshots/test_init.ambr +++ b/tests/components/conversation/snapshots/test_init.ambr @@ -382,6 +382,11 @@ 'intent': dict({ 'name': 'HassTurnOn', }), + 'targets': dict({ + 'light.kitchen': dict({ + 'matched': True, + }), + }), }), dict({ 'entities': dict({ @@ -394,6 +399,60 @@ 'intent': dict({ 'name': 'HassTurnOff', }), + 'targets': dict({ + 'light.kitchen': dict({ + 'matched': True, + }), + }), + }), + dict({ + 'entities': dict({ + 'area': dict({ + 'name': 'area', + 'text': 'kitchen', + 'value': 'kitchen', + }), + 'domain': dict({ + 'name': 'domain', + 'text': '', + 'value': 'light', + }), + }), + 'intent': dict({ + 'name': 'HassTurnOn', + }), + 'targets': dict({ + 'light.kitchen': dict({ + 'matched': True, + }), + }), + }), + dict({ + 'entities': dict({ + 'area': dict({ + 'name': 'area', + 'text': 'kitchen', + 'value': 'kitchen', + }), + 'domain': dict({ + 'name': 'domain', + 'text': 'lights', + 'value': 'light', + }), + 'state': dict({ + 'name': 'state', + 'text': 'on', + 'value': 'on', + }), + }), + 'intent': dict({ + 'name': 'HassGetState', + }), + 'targets': dict({ + 'light.kitchen': dict({ + 'matched': False, + }), + }), }), None, ]), diff --git a/tests/components/conversation/test_init.py b/tests/components/conversation/test_init.py index ec2128e3bd7..6ad9beb3362 100644 --- a/tests/components/conversation/test_init.py +++ b/tests/components/conversation/test_init.py @@ -1652,16 +1652,22 @@ async def test_ws_hass_agent_debug( hass: HomeAssistant, init_components, hass_ws_client: WebSocketGenerator, + area_registry: ar.AreaRegistry, entity_registry: er.EntityRegistry, snapshot: SnapshotAssertion, ) -> None: """Test homeassistant agent debug websocket command.""" client = await hass_ws_client(hass) + kitchen_area = area_registry.async_create("kitchen") entity_registry.async_get_or_create( "light", "demo", "1234", suggested_object_id="kitchen" ) - entity_registry.async_update_entity("light.kitchen", aliases={"my cool light"}) + entity_registry.async_update_entity( + "light.kitchen", + aliases={"my cool light"}, + area_id=kitchen_area.id, + ) hass.states.async_set("light.kitchen", "off") on_calls = async_mock_service(hass, LIGHT_DOMAIN, "turn_on") @@ -1673,6 +1679,8 @@ async def test_ws_hass_agent_debug( "sentences": [ "turn on my cool light", "turn my cool light off", + "turn on all lights in the kitchen", + "how many lights are on in the kitchen?", "this will not match anything", # null in results ], }