1 """Handle intents with scripts."""
3 from __future__
import annotations
6 from typing
import Any, TypedDict
8 import voluptuous
as vol
14 config_validation
as cv,
23 _LOGGER = logging.getLogger(__name__)
25 DOMAIN =
"intent_script"
27 CONF_PLATFORMS =
"platforms"
28 CONF_INTENTS =
"intents"
29 CONF_SPEECH =
"speech"
30 CONF_REPROMPT =
"reprompt"
32 CONF_ACTION =
"action"
35 CONF_CONTENT =
"content"
37 CONF_ASYNC_ACTION =
"async_action"
39 DEFAULT_CONF_ASYNC_ACTION =
False
41 CONFIG_SCHEMA = vol.Schema(
45 vol.Optional(CONF_DESCRIPTION): cv.string,
46 vol.Optional(CONF_PLATFORMS): vol.All([cv.string], vol.Coerce(set)),
47 vol.Optional(CONF_ACTION): cv.SCRIPT_SCHEMA,
49 CONF_ASYNC_ACTION, default=DEFAULT_CONF_ASYNC_ACTION
51 vol.Optional(CONF_MODE, default=script.DEFAULT_SCRIPT_MODE): vol.In(
52 script.SCRIPT_MODE_CHOICES
54 vol.Optional(CONF_CARD): {
55 vol.Optional(CONF_TYPE, default=
"simple"): cv.string,
56 vol.Required(CONF_TITLE): cv.template,
57 vol.Required(CONF_CONTENT): cv.template,
59 vol.Optional(CONF_SPEECH): {
60 vol.Optional(CONF_TYPE, default=
"plain"): cv.string,
61 vol.Required(CONF_TEXT): cv.template,
63 vol.Optional(CONF_REPROMPT): {
64 vol.Optional(CONF_TYPE, default=
"plain"): cv.string,
65 vol.Required(CONF_TEXT): cv.template,
70 extra=vol.ALLOW_EXTRA,
74 async
def async_reload(hass: HomeAssistant, service_call: ServiceCall) ->
None:
75 """Handle reload Intent Script service call."""
77 existing_intents = hass.data[DOMAIN]
79 for intent_type
in existing_intents:
80 intent.async_remove(hass, intent_type)
82 if not new_config
or DOMAIN
not in new_config:
83 hass.data[DOMAIN] = {}
86 new_intents = new_config[DOMAIN]
92 """Load YAML intents into the intent system."""
93 hass.data[DOMAIN] = intents
95 for intent_type, conf
in intents.items():
96 if CONF_ACTION
in conf:
97 script_mode: str = conf.get(CONF_MODE, script.DEFAULT_SCRIPT_MODE)
101 f
"Intent Script {intent_type}",
103 script_mode=script_mode,
108 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
109 """Set up the intent script component."""
110 intents = config[DOMAIN]
114 async
def _handle_reload(service_call: ServiceCall) ->
None:
117 service.async_register_admin_service(
128 """Intent config data type for speech or reprompt info."""
130 content: template.Template
131 title: template.Template
132 text: template.Template
137 """Intent config data type for card info."""
140 title: template.Template
141 content: template.Template
145 """Respond to an intent with a script."""
148 vol.Any(
"name",
"area",
"floor"): cv.string,
149 vol.Optional(
"domain"): vol.All(cv.ensure_list, [cv.string]),
150 vol.Optional(
"device_class"): vol.All(cv.ensure_list, [cv.string]),
153 def __init__(self, intent_type: str, config: ConfigType) ->
None:
154 """Initialize the script intent handler."""
160 async
def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
161 """Handle the intent."""
162 speech: _IntentSpeechRepromptData |
None = self.
configconfig.
get(CONF_SPEECH)
163 reprompt: _IntentSpeechRepromptData |
None = self.
configconfig.
get(CONF_REPROMPT)
164 card: _IntentCardData |
None = self.
configconfig.
get(CONF_CARD)
166 is_async_action: bool = self.
configconfig[CONF_ASYNC_ACTION]
167 hass: HomeAssistant = intent_obj.hass
168 intent_slots = self.async_validate_slots(intent_obj.slots)
169 slots: dict[str, Any] = {
170 key: value[
"value"]
for key, value
in intent_slots.items()
174 "Intent named %s received with slots: %s",
175 intent_obj.intent_type,
178 for key, value
in slots.items()
179 if not key.startswith(
"_")
and not key.endswith(
"_raw_value")
183 entity_name = slots.get(
"name")
184 area_name = slots.get(
"area")
185 floor_name = slots.get(
"floor")
189 domains: set[str] |
None =
None
190 device_classes: set[str] |
None =
None
192 if "domain" in slots:
193 domains = set(slots[
"domain"])
195 if "device_class" in slots:
196 device_classes = set(slots[
"device_class"])
198 match_constraints = intent.MatchTargetsConstraints(
201 floor_name=floor_name,
203 device_classes=device_classes,
204 assistant=intent_obj.assistant,
207 if match_constraints.has_constraints:
208 match_result = intent.async_match_targets(hass, match_constraints)
209 if match_result.is_match:
212 if match_result.states:
213 targets[
"entities"] = [
214 state.entity_id
for state
in match_result.states
217 if match_result.areas:
218 targets[
"areas"] = [area.id
for area
in match_result.areas]
220 if match_result.floors:
221 targets[
"floors"] = [
222 floor.floor_id
for floor
in match_result.floors
226 slots[
"targets"] = targets
228 if action
is not None:
230 intent_obj.hass.async_create_task(
231 action.async_run(slots, intent_obj.context)
234 action_res = await action.async_run(slots, intent_obj.context)
237 if action_res
and action_res.service_response
is not None:
238 slots[
"action_response"] = action_res.service_response
240 response = intent_obj.create_response()
242 if speech
is not None:
243 response.async_set_speech(
244 speech[
"text"].async_render(slots, parse_result=
False),
248 if reprompt
is not None:
249 text_reprompt = reprompt[
"text"].async_render(slots, parse_result=
False)
251 response.async_set_reprompt(
257 response.async_set_card(
258 card[
"title"].async_render(slots, parse_result=
False),
259 card[
"content"].async_render(slots, parse_result=
False),
None __init__(self, str intent_type, ConfigType config)
intent.IntentResponse async_handle(self, intent.Intent intent_obj)
web.Response get(self, web.Request request, str config_key)
bool async_setup(HomeAssistant hass, ConfigType config)
None async_reload(HomeAssistant hass, ServiceCall service_call)
None async_load_intents(HomeAssistant hass, dict[str, ConfigType] intents)
ConfigType|None async_integration_yaml_config(HomeAssistant hass, str integration_name)