1 """Module to coordinate user intentions."""
3 from __future__
import annotations
5 from abc
import abstractmethod
7 from collections.abc
import Callable, Collection, Coroutine, Iterable
9 from dataclasses
import dataclass, field
10 from enum
import Enum, StrEnum, auto
11 from itertools
import groupby
13 from typing
import Any
15 from propcache
import cached_property
16 import voluptuous
as vol
22 ATTR_SUPPORTED_FEATURES,
31 config_validation
as cv,
36 from .typing
import VolSchemaType
38 _LOGGER = logging.getLogger(__name__)
39 type _SlotsType = dict[str, Any]
40 type _IntentSlotsType = dict[
41 str | tuple[str, str], VolSchemaType | Callable[[Any], Any]
44 INTENT_TURN_OFF =
"HassTurnOff"
45 INTENT_TURN_ON =
"HassTurnOn"
46 INTENT_TOGGLE =
"HassToggle"
47 INTENT_GET_STATE =
"HassGetState"
48 INTENT_NEVERMIND =
"HassNevermind"
49 INTENT_SET_POSITION =
"HassSetPosition"
50 INTENT_START_TIMER =
"HassStartTimer"
51 INTENT_CANCEL_TIMER =
"HassCancelTimer"
52 INTENT_CANCEL_ALL_TIMERS =
"HassCancelAllTimers"
53 INTENT_INCREASE_TIMER =
"HassIncreaseTimer"
54 INTENT_DECREASE_TIMER =
"HassDecreaseTimer"
55 INTENT_PAUSE_TIMER =
"HassPauseTimer"
56 INTENT_UNPAUSE_TIMER =
"HassUnpauseTimer"
57 INTENT_TIMER_STATUS =
"HassTimerStatus"
58 INTENT_GET_CURRENT_DATE =
"HassGetCurrentDate"
59 INTENT_GET_CURRENT_TIME =
"HassGetCurrentTime"
60 INTENT_RESPOND =
"HassRespond"
62 SLOT_SCHEMA = vol.Schema({}, extra=vol.ALLOW_EXTRA)
64 DATA_KEY: HassKey[dict[str, IntentHandler]] =
HassKey(
"intent")
66 SPEECH_TYPE_PLAIN =
"plain"
67 SPEECH_TYPE_SSML =
"ssml"
73 """Register an intent with Home Assistant."""
74 if (intents := hass.data.get(DATA_KEY))
is None:
76 hass.data[DATA_KEY] = intents
78 assert getattr(handler,
"intent_type",
None),
"intent_type should be set"
80 if handler.intent_type
in intents:
82 "Intent %s is being overwritten by %s", handler.intent_type, handler
85 intents[handler.intent_type] = handler
91 """Remove an intent from Home Assistant."""
92 if (intents := hass.data.get(DATA_KEY))
is None:
95 intents.pop(intent_type,
None)
99 def async_get(hass: HomeAssistant) -> Iterable[IntentHandler]:
100 """Return registered intents."""
101 return hass.data.get(DATA_KEY, {}).values()
109 slots: _SlotsType |
None =
None,
110 text_input: str |
None =
None,
111 context: Context |
None =
None,
112 language: str |
None =
None,
113 assistant: str |
None =
None,
114 device_id: str |
None =
None,
115 conversation_agent_id: str |
None =
None,
117 """Handle an intent."""
118 handler = hass.data.get(DATA_KEY, {}).
get(intent_type)
127 language = hass.config.language
132 intent_type=intent_type,
134 text_input=text_input,
139 conversation_agent_id=conversation_agent_id,
143 _LOGGER.info(
"Triggering intent handler %s", handler)
144 result = await handler.async_handle(intent)
145 except vol.Invalid
as err:
146 _LOGGER.warning(
"Received invalid slot info for %s: %s", intent_type, err)
147 raise InvalidSlotInfo(f
"Received invalid slot info for {intent_type}")
from err
150 except Exception
as err:
151 _LOGGER.exception(
"Error handling %s", intent_type)
157 """Base class for intent related errors."""
161 """When the intent is not registered."""
165 """When the slot data is invalid."""
169 """Error while handling intent."""
171 def __init__(self, message: str =
"", response_key: str |
None =
None) ->
None:
172 """Initialize error."""
178 """Unexpected error while handling intent."""
181 class MatchFailedReason(Enum):
182 """Possible reasons for match failure in async_match_targets."""
185 """No entities matched name constraint."""
188 """No entities matched area constraint."""
191 """No entities matched floor constraint."""
194 """No entities matched domain constraint."""
196 DEVICE_CLASS = auto()
197 """No entities matched device class constraint."""
200 """No entities matched supported features constraint."""
203 """No entities matched required states constraint."""
206 """No entities matched exposed to assistant constraint."""
208 INVALID_AREA = auto()
209 """Area name from constraint does not exist."""
211 INVALID_FLOOR = auto()
212 """Floor name from constraint does not exist."""
214 DUPLICATE_NAME = auto()
215 """Two or more entities matched the same name constraint and could not be disambiguated."""
218 """Return True if the match failed because no entities matched."""
220 MatchFailedReason.INVALID_AREA,
221 MatchFailedReason.INVALID_FLOOR,
222 MatchFailedReason.DUPLICATE_NAME,
228 """Constraints for async_match_targets."""
230 name: str |
None =
None
231 """Entity name or alias."""
233 area_name: str |
None =
None
234 """Area name, id, or alias."""
236 floor_name: str |
None =
None
237 """Floor name, id, or alias."""
239 domains: Collection[str] |
None =
None
242 device_classes: Collection[str] |
None =
None
243 """Device class names."""
245 features: int |
None =
None
246 """Required supported features."""
248 states: Collection[str] |
None =
None
249 """Required states for entities."""
251 assistant: str |
None =
None
252 """Name of assistant that entities should be exposed to."""
254 allow_duplicate_names: bool =
False
255 """True if entities with duplicate names are allowed in result."""
259 """Returns True if at least one constraint is set (ignores assistant)."""
265 or self.device_classes
273 """Preferences used to disambiguate duplicate name matches in async_match_targets."""
275 area_id: str |
None =
None
276 """Id of area to use when deduplicating names."""
278 floor_id: str |
None =
None
279 """Id of floor to use when deduplicating names."""
284 """Result from async_match_targets."""
287 """True if one or more entities matched."""
289 no_match_reason: MatchFailedReason |
None =
None
290 """Reason for failed match when is_match = False."""
292 states: list[State] = field(default_factory=list)
293 """List of matched entity states when is_match = True."""
295 no_match_name: str |
None =
None
296 """Name of invalid area/floor or duplicate name when match fails for those reasons."""
299 """Areas that were targeted."""
302 """Floors that were targeted."""
306 """Error when target matching fails."""
310 result: MatchTargetsResult,
311 constraints: MatchTargetsConstraints,
312 preferences: MatchTargetsPreferences |
None =
None,
314 """Initialize error."""
322 """Return string representation."""
323 return f
"<MatchFailedError result={self.result}, constraints={self.constraints}, preferences={self.preferences}>"
327 """Error when no states match the intent's constraints."""
331 reason: MatchFailedReason,
332 name: str |
None =
None,
333 area: str |
None =
None,
334 floor: str |
None =
None,
335 domains: set[str] |
None =
None,
336 device_classes: set[str] |
None =
None,
338 """Initialize error."""
346 device_classes=device_classes,
353 """Candidate for async_match_targets."""
361 matched_name: str |
None =
None
367 """Find all areas matching a name (including aliases)."""
369 for area
in areas.async_list_areas():
378 for alias
in area.aliases:
387 """Find all floors matching a name (including aliases)."""
389 for floor
in floors.async_list_floors():
391 if (floor.floor_id == name)
or (
_normalize_name(floor.name) == name_norm):
395 if not floor.aliases:
398 for alias
in floor.aliases:
405 """Normalize name for comparison."""
406 return name.strip().casefold()
411 candidates: Iterable[MatchTargetsCandidate],
412 ) -> Iterable[MatchTargetsCandidate]:
413 """Filter candidates by name."""
416 for candidate
in candidates:
421 candidate.matched_name = name
425 if candidate.entity
is None:
428 if candidate.entity.name
and (
431 candidate.matched_name = name
436 if candidate.entity.aliases:
437 for alias
in candidate.entity.aliases:
439 candidate.matched_name = name
446 candidates: Iterable[MatchTargetsCandidate],
447 ) -> Iterable[MatchTargetsCandidate]:
448 """Filter candidates by supported features."""
449 for candidate
in candidates:
450 if (candidate.entity
is not None)
and (
451 (candidate.entity.supported_features & features) == features
456 supported_features = candidate.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
457 if (supported_features & features) == features:
462 device_classes: Iterable[str],
463 candidates: Iterable[MatchTargetsCandidate],
464 ) -> Iterable[MatchTargetsCandidate]:
465 """Filter candidates by device classes."""
466 for candidate
in candidates:
468 (candidate.entity
is not None)
469 and candidate.entity.device_class
470 and (candidate.entity.device_class
in device_classes)
475 device_class = candidate.state.attributes.get(ATTR_DEVICE_CLASS)
476 if device_class
and (device_class
in device_classes):
481 areas: area_registry.AreaRegistry,
483 candidates: Iterable[MatchTargetsCandidate],
485 """Add area and device entries to match candidates."""
486 for candidate
in candidates:
487 if candidate.entity
is None:
490 if candidate.entity.device_id:
491 candidate.device = devices.async_get(candidate.entity.device_id)
493 if candidate.entity.area_id:
495 candidate.area = areas.async_get_area(candidate.entity.area_id)
496 assert candidate.area
is not None
497 elif (candidate.device
is not None)
and candidate.device.area_id:
499 candidate.area = areas.async_get_area(candidate.device.area_id)
505 constraints: MatchTargetsConstraints,
506 preferences: MatchTargetsPreferences |
None =
None,
507 states: list[State] |
None =
None,
508 ) -> MatchTargetsResult:
509 """Match entities based on constraints in order to handle an intent."""
511 filtered_by_domain =
False
515 states = hass.states.async_all(constraints.domains)
516 filtered_by_domain =
True
525 if constraints.assistant
532 if constraints.domains
and (
not filtered_by_domain):
534 candidates = [c
for c
in candidates
if c.state.domain
in constraints.domains]
538 if constraints.states:
540 candidates = [c
for c
in candidates
if c.state.state
in constraints.states]
547 or constraints.features
548 or constraints.device_classes
549 or constraints.area_name
550 or constraints.floor_name
552 if constraints.assistant:
554 candidates = [c
for c
in candidates
if c.is_exposed]
561 er = entity_registry.async_get(hass)
562 for candidate
in candidates:
563 candidate.entity = er.async_get(candidate.state.entity_id)
571 if constraints.features:
577 if constraints.device_classes:
592 if constraints.floor_name
or constraints.area_name:
593 ar = area_registry.async_get(hass)
594 dr = device_registry.async_get(hass)
598 if constraints.floor_name:
600 fr = floor_registry.async_get(hass)
602 if not targeted_floors:
605 MatchFailedReason.INVALID_FLOOR,
606 no_match_name=constraints.floor_name,
609 possible_floor_ids = {floor.floor_id
for floor
in targeted_floors}
610 possible_area_ids = {
612 for area
in ar.async_list_areas()
613 if area.floor_id
in possible_floor_ids
619 if (c.area
is not None)
and (c.area.id
in possible_area_ids)
623 False, MatchFailedReason.FLOOR, floors=targeted_floors
627 possible_area_ids = {area.id
for area
in ar.async_list_areas()}
629 if constraints.area_name:
631 if not targeted_areas:
634 MatchFailedReason.INVALID_AREA,
635 no_match_name=constraints.area_name,
638 matching_area_ids = {area.id
for area
in targeted_areas}
641 possible_area_ids.intersection_update(matching_area_ids)
645 if (c.area
is not None)
and (c.area.id
in possible_area_ids)
649 False, MatchFailedReason.AREA, areas=targeted_areas
652 if constraints.assistant:
654 candidates = [c
for c
in candidates
if c.is_exposed]
658 if constraints.name
and (
not constraints.allow_duplicate_names):
661 ar = area_registry.async_get(hass)
662 dr = device_registry.async_get(hass)
666 sorted_candidates = sorted(
667 [c
for c
in candidates
if c.matched_name],
668 key=
lambda c: c.matched_name
or "",
670 final_candidates: list[MatchTargetsCandidate] = []
671 for name, group
in groupby(sorted_candidates, key=
lambda c: c.matched_name):
672 group_candidates =
list(group)
673 if len(group_candidates) < 2:
675 final_candidates.extend(group_candidates)
679 if preferences.floor_id:
682 for c
in group_candidates
683 if (c.area
is not None)
684 and (c.area.floor_id == preferences.floor_id)
686 if len(group_candidates) < 2:
688 final_candidates.extend(group_candidates)
691 if preferences.area_id:
694 for c
in group_candidates
695 if (c.area
is not None)
and (c.area.id == preferences.area_id)
697 if len(group_candidates) < 2:
699 final_candidates.extend(group_candidates)
705 MatchFailedReason.DUPLICATE_NAME,
707 areas=targeted_areas
or [],
708 floors=targeted_floors
or [],
711 if not final_candidates:
714 MatchFailedReason.NAME,
715 areas=targeted_areas
or [],
716 floors=targeted_floors
or [],
719 candidates = final_candidates
724 states=[c.state
for c
in candidates],
725 areas=targeted_areas
or [],
726 floors=targeted_floors
or [],
734 name: str |
None =
None,
735 area_name: str |
None =
None,
736 floor_name: str |
None =
None,
737 domains: Collection[str] |
None =
None,
738 device_classes: Collection[str] |
None =
None,
739 states: list[State] |
None =
None,
740 assistant: str |
None =
None,
741 ) -> Iterable[State]:
742 """Simplified interface to async_match_targets that returns states matching the constraints."""
748 floor_name=floor_name,
750 device_classes=device_classes,
760 """Test if state supports a feature."""
761 if state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) & feature == 0:
766 """Intent handler registration."""
769 platforms: set[str] |
None =
None
770 description: str |
None =
None
774 """Return a slot schema."""
779 """Test if an intent can be handled."""
780 return self.platforms
is None or intent_obj.platform
in self.platforms
784 """Validate slot information."""
792 """Create validation schema for slots."""
796 key: SLOT_SCHEMA.extend({
"value": validator})
797 for key, validator
in self.
slot_schemaslot_schema.items()
799 extra=vol.ALLOW_EXTRA,
803 """Handle the intent."""
804 raise NotImplementedError
807 """Represent a string of an intent handler."""
808 return f
"<{self.__class__.__name__} - {self.intent_type}>"
812 """Coerce value to string and fail if string is empty or whitespace."""
813 value_str = cv.string(value)
814 if not value_str.strip():
815 raise vol.Invalid(
"string value is empty")
821 """Service Intent handler registration (dynamic).
823 Service specific intent handler that calls a service by name/entity_id.
828 service_timeout: float = 0.2
833 speech: str |
None =
None,
834 required_slots: _IntentSlotsType |
None =
None,
835 optional_slots: _IntentSlotsType |
None =
None,
836 required_domains: set[str] |
None =
None,
837 required_features: int |
None =
None,
838 required_states: set[str] |
None =
None,
839 description: str |
None =
None,
840 platforms: set[str] |
None =
None,
841 device_classes: set[type[StrEnum]] |
None =
None,
843 """Create Service Intent Handler."""
853 self.required_slots: _IntentSlotsType = {}
855 for key, value_schema
in required_slots.items():
856 if isinstance(key, str):
860 self.required_slots[key] = value_schema
862 self.optional_slots: _IntentSlotsType = {}
864 for key, value_schema
in optional_slots.items():
865 if isinstance(key, str):
869 self.optional_slots[key] = value_schema
873 """Return a slot schema."""
878 vol.Any(
"name",
"area",
"floor"): non_empty_string,
879 vol.Optional(
"domain"): vol.All(cv.ensure_list, [domain_validator]),
884 flattened_device_classes = vol.In(
888 for device_class
in device_class_enum
893 vol.Optional(
"device_class"): vol.All(
895 [flattened_device_classes],
902 vol.Optional(
"preferred_area_id"): cv.string,
903 vol.Optional(
"preferred_floor_id"): cv.string,
907 if self.required_slots:
910 vol.Required(key[0]): validator
911 for key, validator
in self.required_slots.items()
915 if self.optional_slots:
918 vol.Optional(key[0]): validator
919 for key, validator
in self.optional_slots.items()
927 self, intent_obj: Intent, state: State
928 ) -> tuple[str, str]:
929 """Get the domain and service name to call."""
930 raise NotImplementedError
933 """Handle the hass intent."""
934 hass = intent_obj.hass
937 name_slot = slots.get(
"name", {})
938 entity_name: str |
None = name_slot.get(
"value")
939 entity_text: str |
None = name_slot.get(
"text")
940 if entity_name ==
"all":
945 area_slot = slots.get(
"area", {})
946 area_id = area_slot.get(
"value")
948 floor_slot = slots.get(
"floor", {})
949 floor_id = floor_slot.get(
"value")
954 device_classes: set[str] |
None =
None
956 if "domain" in slots:
957 domains = set(slots[
"domain"][
"value"])
959 if "device_class" in slots:
960 device_classes = set(slots[
"device_class"][
"value"])
967 device_classes=device_classes,
968 assistant=intent_obj.assistant,
972 if not match_constraints.has_constraints:
977 area_id=slots.get(
"preferred_area_id", {}).
get(
"value"),
978 floor_id=slots.get(
"preferred_floor_id", {}).
get(
"value"),
982 if not match_result.is_match:
985 constraints=match_constraints,
986 preferences=match_preferences,
990 if (
"name" in slots)
and entity_text:
991 slots[
"name"][
"value"] = entity_text
994 if (
"area" in slots)
and match_result.areas:
995 slots[
"area"][
"value"] = match_result.areas[0].id
997 if (
"floor" in slots)
and match_result.floors:
998 slots[
"floor"][
"value"] = match_result.floors[0].floor_id
1001 intent_obj.slots = slots
1004 intent_obj, match_result, match_constraints, match_preferences
1008 response.async_set_states(
1009 matched_states=match_result.states, unmatched_states=[]
1017 match_result: MatchTargetsResult,
1018 match_constraints: MatchTargetsConstraints,
1019 match_preferences: MatchTargetsPreferences |
None =
None,
1020 ) -> IntentResponse:
1021 """Complete action on matched entity states."""
1022 states = match_result.states
1023 response = intent_obj.create_response()
1025 hass = intent_obj.hass
1026 success_results: list[IntentResponseTarget] = []
1028 if match_result.floors:
1029 success_results.extend(
1031 type=IntentResponseTargetType.FLOOR,
1035 for floor
in match_result.floors
1037 speech_name = match_result.floors[0].name
1038 elif match_result.areas:
1039 success_results.extend(
1041 type=IntentResponseTargetType.AREA, name=area.name, id=area.id
1043 for area
in match_result.areas
1045 speech_name = match_result.areas[0].name
1047 speech_name = states[0].name
1049 service_coros: list[Coroutine[Any, Any,
None]] = []
1050 for state
in states:
1052 service_coros.append(
1057 failed_results: list[IntentResponseTarget] = []
1058 for state, service_coro
in zip(
1059 states, asyncio.as_completed(service_coros), strict=
False
1062 type=IntentResponseTargetType.ENTITY,
1069 success_results.append(target)
1071 failed_results.append(target)
1072 _LOGGER.exception(
"Service call failed for %s", state.entity_id)
1074 if not success_results:
1076 failed_entity_ids = [target.id
for target
in failed_results]
1078 f
"Failed to call {service} for: {failed_entity_ids}"
1081 response.async_set_results(
1082 success_results=success_results, failed_results=failed_results
1086 states = [hass.states.get(state.entity_id)
or state
for state
in states]
1087 response.async_set_states(states)
1089 if self.
speechspeech
is not None:
1090 response.async_set_speech(self.
speechspeech.format(speech_name))
1095 self, domain: str, service: str, intent_obj: Intent, state: State
1097 """Call service on entity."""
1098 hass = intent_obj.hass
1100 service_data: dict[str, Any] = {ATTR_ENTITY_ID: state.entity_id}
1101 if self.required_slots:
1102 service_data.update(
1104 key[1]: intent_obj.slots[key[0]][
"value"]
1105 for key
in self.required_slots
1109 if self.optional_slots:
1110 for key
in self.optional_slots:
1111 value = intent_obj.slots.get(key[0])
1113 service_data[key[1]] = value[
"value"]
1116 hass.async_create_task_internal(
1117 hass.services.async_call(
1121 context=intent_obj.context,
1124 f
"intent_call_service_{domain}_{service}",
1129 """Run task with timeout to (hopefully) catch validation errors.
1131 After the timeout the task will continue to run in the background.
1134 await asyncio.wait({task}, timeout=self.service_timeout)
1135 except TimeoutError:
1137 except asyncio.CancelledError:
1140 _LOGGER.debug(
"Service call was cancelled: %s", task.get_name())
1142 await asyncio.wait({task}, timeout=5)
1147 """Service Intent handler registration.
1149 Service specific intent handler that calls a service by name/entity_id.
1157 speech: str |
None =
None,
1158 required_slots: _IntentSlotsType |
None =
None,
1159 optional_slots: _IntentSlotsType |
None =
None,
1160 required_domains: set[str] |
None =
None,
1161 required_features: int |
None =
None,
1162 required_states: set[str] |
None =
None,
1163 description: str |
None =
None,
1164 platforms: set[str] |
None =
None,
1165 device_classes: set[type[StrEnum]] |
None =
None,
1167 """Create service handler."""
1171 required_slots=required_slots,
1172 optional_slots=optional_slots,
1173 required_domains=required_domains,
1174 required_features=required_features,
1175 required_states=required_states,
1176 description=description,
1177 platforms=platforms,
1178 device_classes=device_classes,
1184 self, intent_obj: Intent, state: State
1185 ) -> tuple[str, str]:
1186 """Get the domain and service name to call."""
1191 """Category of an intent."""
1194 """Trigger an action like turning an entity on or off"""
1197 """Get information about the state of an entity"""
1201 """Hold the intent."""
1214 "conversation_agent_id",
1219 hass: HomeAssistant,
1223 text_input: str |
None,
1226 category: IntentCategory |
None =
None,
1227 assistant: str |
None =
None,
1228 device_id: str |
None =
None,
1229 conversation_agent_id: str |
None =
None,
1231 """Initialize an intent."""
1246 """Create a response."""
1251 """Type of the intent response."""
1253 ACTION_DONE =
"action_done"
1254 """Intent caused an action to occur"""
1256 PARTIAL_ACTION_DONE =
"partial_action_done"
1257 """Intent caused an action, but it could only be partially done"""
1259 QUERY_ANSWER =
"query_answer"
1260 """Response is an answer to a query"""
1263 """Response is an error"""
1267 """Reason for an intent response error."""
1269 NO_INTENT_MATCH =
"no_intent_match"
1270 """Text could not be matched to an intent"""
1272 NO_VALID_TARGETS =
"no_valid_targets"
1273 """Intent was matched, but no valid areas/devices/entities were targeted"""
1275 FAILED_TO_HANDLE =
"failed_to_handle"
1276 """Unexpected error occurred while handling intent"""
1279 """Error outside the scope of intent processing"""
1283 """Type of target for an intent response."""
1290 DEVICE_CLASS =
"device_class"
1294 @dataclass(slots=True)
1296 """Target of the intent response."""
1299 type: IntentResponseTargetType
1300 id: str |
None =
None
1304 """Response to an intent."""
1309 intent: Intent |
None =
None,
1311 """Initialize an IntentResponse."""
1314 self.speech: dict[str, dict[str, Any]] = {}
1315 self.reprompt: dict[str, dict[str, Any]] = {}
1316 self.card: dict[str, dict[str, str]] = {}
1317 self.
error_codeerror_code: IntentResponseErrorCode |
None =
None
1318 self.
intent_targetsintent_targets: list[IntentResponseTarget] = []
1320 self.
failed_resultsfailed_results: list[IntentResponseTarget] = []
1325 if (self.
intentintent
is not None)
and (self.
intentintent.category == IntentCategory.QUERY):
1329 self.
response_typeresponse_type = IntentResponseType.ACTION_DONE
1335 speech_type: str =
"plain",
1336 extra_data: Any |
None =
None,
1338 """Set speech response."""
1339 self.speech[speech_type] = {
1341 "extra_data": extra_data,
1348 speech_type: str =
"plain",
1349 extra_data: Any |
None =
None,
1351 """Set reprompt response."""
1352 self.reprompt[speech_type] = {
1354 "extra_data": extra_data,
1359 self, title: str, content: str, card_type: str =
"simple"
1361 """Set card response."""
1362 self.card[card_type] = {
"title": title,
"content": content}
1366 """Set response error."""
1376 intent_targets: list[IntentResponseTarget],
1378 """Set response targets."""
1384 success_results: list[IntentResponseTarget],
1385 failed_results: list[IntentResponseTarget] |
None =
None,
1387 """Set response results."""
1389 self.
failed_resultsfailed_results = failed_results
if failed_results
is not None else []
1393 self, matched_states: list[State], unmatched_states: list[State] |
None =
None
1395 """Set entity states that were matched or not matched during intent handling (query)."""
1401 """Set slots that will be used in the response template of the default agent."""
1406 """Return a dictionary representation of an intent response."""
1407 response_dict: dict[str, Any] = {
1408 "speech": self.speech,
1415 response_dict[
"reprompt"] = self.reprompt
1417 response_dict[
"speech_slots"] = self.
speech_slotsspeech_slots
1419 response_data: dict[str, Any] = {}
1421 if self.
response_typeresponse_type == IntentResponseType.ERROR:
1422 assert self.
error_codeerror_code
is not None,
"error code is required"
1423 response_data[
"code"] = self.
error_codeerror_code.value
1426 response_data[
"targets"] = [
1427 dataclasses.asdict(target)
for target
in self.
intent_targetsintent_targets
1431 response_data[
"success"] = [
1432 dataclasses.asdict(target)
for target
in self.
success_resultssuccess_results
1435 response_data[
"failed"] = [
1436 dataclasses.asdict(target)
for target
in self.
failed_resultsfailed_results
1439 response_dict[
"data"] = response_data
1441 return response_dict
tuple[str, str] get_domain_and_service(self, Intent intent_obj, State state)
None __init__(self, str intent_type, str|None speech=None, _IntentSlotsType|None required_slots=None, _IntentSlotsType|None optional_slots=None, set[str]|None required_domains=None, int|None required_features=None, set[str]|None required_states=None, str|None description=None, set[str]|None platforms=None, set[type[StrEnum]]|None device_classes=None)
None _run_then_background(self, asyncio.Task[Any] task)
IntentResponse async_handle_states(self, Intent intent_obj, MatchTargetsResult match_result, MatchTargetsConstraints match_constraints, MatchTargetsPreferences|None match_preferences=None)
None async_call_service(self, str domain, str service, Intent intent_obj, State state)
IntentResponse async_handle(self, Intent intent_obj)
None __init__(self, str message="", str|None response_key=None)
vol.Schema _slot_schema(self)
dict|None slot_schema(self)
IntentResponse async_handle(self, Intent intent_obj)
_SlotsType async_validate_slots(self, _SlotsType slots)
bool async_can_handle(self, Intent intent_obj)
None async_set_card(self, str title, str content, str card_type="simple")
dict[str, Any] as_dict(self)
None async_set_states(self, list[State] matched_states, list[State]|None unmatched_states=None)
None async_set_reprompt(self, str speech, str speech_type="plain", Any|None extra_data=None)
None async_set_speech(self, str speech, str speech_type="plain", Any|None extra_data=None)
None async_set_results(self, list[IntentResponseTarget] success_results, list[IntentResponseTarget]|None failed_results=None)
None async_set_error(self, IntentResponseErrorCode code, str message)
None async_set_targets(self, list[IntentResponseTarget] intent_targets)
None async_set_speech_slots(self, dict[str, Any] speech_slots)
None __init__(self, str language, Intent|None intent=None)
IntentResponse create_response(self)
None __init__(self, HomeAssistant hass, str platform, str intent_type, _SlotsType slots, str|None text_input, Context context, str language, IntentCategory|None category=None, str|None assistant=None, str|None device_id=None, str|None conversation_agent_id=None)
None __init__(self, MatchTargetsResult result, MatchTargetsConstraints constraints, MatchTargetsPreferences|None preferences=None)
bool is_no_entities_reason(self)
bool has_constraints(self)
None __init__(self, MatchFailedReason reason, str|None name=None, str|None area=None, str|None floor=None, set[str]|None domains=None, set[str]|None device_classes=None)
tuple[str, str] get_domain_and_service(self, Intent intent_obj, State state)
None __init__(self, str intent_type, str domain, str service, str|None speech=None, _IntentSlotsType|None required_slots=None, _IntentSlotsType|None optional_slots=None, set[str]|None required_domains=None, int|None required_features=None, set[str]|None required_states=None, str|None description=None, set[str]|None platforms=None, set[type[StrEnum]]|None device_classes=None)
web.Response get(self, web.Request request, str config_key)
bool async_should_expose(HomeAssistant hass, str assistant, str entity_id)
Iterable[MatchTargetsCandidate] _filter_by_name(str name, Iterable[MatchTargetsCandidate] candidates)
Iterable[MatchTargetsCandidate] _filter_by_device_classes(Iterable[str] device_classes, Iterable[MatchTargetsCandidate] candidates)
None _add_areas(area_registry.AreaRegistry areas, device_registry.DeviceRegistry devices, Iterable[MatchTargetsCandidate] candidates)
Iterable[floor_registry.FloorEntry] find_floors(str name, floor_registry.FloorRegistry floors)
Iterable[State] async_match_states(HomeAssistant hass, str|None name=None, str|None area_name=None, str|None floor_name=None, Collection[str]|None domains=None, Collection[str]|None device_classes=None, list[State]|None states=None, str|None assistant=None)
None async_test_feature(State state, int feature, str feature_name)
MatchTargetsResult async_match_targets(HomeAssistant hass, MatchTargetsConstraints constraints, MatchTargetsPreferences|None preferences=None, list[State]|None states=None)
None async_register(HomeAssistant hass, IntentHandler handler)
Iterable[area_registry.AreaEntry] find_areas(str name, area_registry.AreaRegistry areas)
str non_empty_string(Any value)
IntentResponse async_handle(HomeAssistant hass, str platform, str intent_type, _SlotsType|None slots=None, str|None text_input=None, Context|None context=None, str|None language=None, str|None assistant=None, str|None device_id=None, str|None conversation_agent_id=None)
None async_remove(HomeAssistant hass, str intent_type)
Iterable[IntentHandler] async_get(HomeAssistant hass)
str _normalize_name(str name)
Iterable[MatchTargetsCandidate] _filter_by_features(int features, Iterable[MatchTargetsCandidate] candidates)