mirror of
https://github.com/home-assistant/core.git
synced 2026-09-18 06:28:02 +01:00
228 lines
7.6 KiB
Python
228 lines
7.6 KiB
Python
"""Intents for the climate integration."""
|
|
|
|
from typing import override
|
|
|
|
import probatio
|
|
|
|
from homeassistant.const import ATTR_ENTITY_ID
|
|
from homeassistant.core import HomeAssistant, State
|
|
from homeassistant.helpers import config_validation as cv, intent, translation
|
|
|
|
from . import (
|
|
ATTR_FAN_MODE,
|
|
ATTR_FAN_MODES,
|
|
ATTR_TEMPERATURE,
|
|
DOMAIN,
|
|
INTENT_SET_FAN_MODE,
|
|
INTENT_SET_TEMPERATURE,
|
|
SERVICE_SET_FAN_MODE,
|
|
SERVICE_SET_TEMPERATURE,
|
|
ClimateEntityFeature,
|
|
)
|
|
|
|
FAN_MODE_TRANSLATION_PREFIX = (
|
|
f"component.{DOMAIN}.entity_component._.state_attributes.{ATTR_FAN_MODE}.state."
|
|
)
|
|
|
|
|
|
async def async_setup_intents(hass: HomeAssistant) -> None:
|
|
"""Set up the climate intents."""
|
|
intent.async_register(hass, SetTemperatureIntent())
|
|
intent.async_register(hass, SetFanModeIntent())
|
|
|
|
|
|
class SetTemperatureIntent(intent.IntentHandler):
|
|
"""Handle SetTemperature intents."""
|
|
|
|
intent_type = INTENT_SET_TEMPERATURE
|
|
description = "Sets the target temperature of a climate device or entity"
|
|
slot_schema = {
|
|
probatio.Required("temperature"): probatio.Coerce(float),
|
|
probatio.Optional("area"): intent.non_empty_string,
|
|
probatio.Optional("name"): intent.non_empty_string,
|
|
probatio.Optional("floor"): intent.non_empty_string,
|
|
probatio.Optional("preferred_area_id"): cv.string,
|
|
probatio.Optional("preferred_floor_id"): cv.string,
|
|
}
|
|
platforms = {DOMAIN}
|
|
|
|
@override
|
|
async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
|
|
"""Handle the intent."""
|
|
hass = intent_obj.hass
|
|
slots = self.async_validate_slots(intent_obj.slots)
|
|
|
|
temperature: float = slots["temperature"]["value"]
|
|
|
|
name: str | None = None
|
|
if "name" in slots:
|
|
name = slots["name"]["value"]
|
|
|
|
area_name: str | None = None
|
|
if "area" in slots:
|
|
area_name = slots["area"]["value"]
|
|
|
|
floor_name: str | None = None
|
|
if "floor" in slots:
|
|
floor_name = slots["floor"]["value"]
|
|
|
|
match_constraints = intent.MatchTargetsConstraints(
|
|
name=name,
|
|
area_name=area_name,
|
|
floor_name=floor_name,
|
|
domains=[DOMAIN],
|
|
assistant=intent_obj.assistant,
|
|
features=ClimateEntityFeature.TARGET_TEMPERATURE,
|
|
single_target=True,
|
|
)
|
|
match_preferences = intent.MatchTargetsPreferences(
|
|
area_id=slots.get("preferred_area_id", {}).get("value"),
|
|
floor_id=slots.get("preferred_floor_id", {}).get("value"),
|
|
)
|
|
match_result = intent.async_match_targets(
|
|
hass, match_constraints, match_preferences
|
|
)
|
|
if not match_result.is_match:
|
|
raise intent.MatchFailedError(
|
|
result=match_result, constraints=match_constraints
|
|
)
|
|
|
|
assert match_result.states
|
|
climate_state = match_result.states[0]
|
|
|
|
await hass.services.async_call(
|
|
DOMAIN,
|
|
SERVICE_SET_TEMPERATURE,
|
|
service_data={ATTR_TEMPERATURE: temperature},
|
|
target={ATTR_ENTITY_ID: climate_state.entity_id},
|
|
blocking=True,
|
|
)
|
|
|
|
response = intent_obj.create_response()
|
|
response.async_set_results(
|
|
success_results=[
|
|
intent.IntentResponseTarget(
|
|
type=intent.IntentResponseTargetType.ENTITY,
|
|
name=climate_state.name,
|
|
id=climate_state.entity_id,
|
|
)
|
|
]
|
|
)
|
|
response.async_set_states(matched_states=[climate_state])
|
|
return response
|
|
|
|
|
|
class SetFanModeIntent(intent.IntentHandler):
|
|
"""Handle SetFanMode intents."""
|
|
|
|
intent_type = INTENT_SET_FAN_MODE
|
|
description = "Sets the fan mode of a climate device or entity"
|
|
slot_schema = {
|
|
probatio.Required("fan_mode"): intent.non_empty_string,
|
|
probatio.Optional("area"): intent.non_empty_string,
|
|
probatio.Optional("name"): intent.non_empty_string,
|
|
probatio.Optional("floor"): intent.non_empty_string,
|
|
probatio.Optional("preferred_area_id"): cv.string,
|
|
probatio.Optional("preferred_floor_id"): cv.string,
|
|
}
|
|
platforms = {DOMAIN}
|
|
|
|
@override
|
|
async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
|
|
"""Handle the intent."""
|
|
hass = intent_obj.hass
|
|
slots = self.async_validate_slots(intent_obj.slots)
|
|
|
|
requested_fan_mode: str = slots["fan_mode"]["value"]
|
|
|
|
name: str | None = None
|
|
if "name" in slots:
|
|
name = slots["name"]["value"]
|
|
|
|
area_name: str | None = None
|
|
if "area" in slots:
|
|
area_name = slots["area"]["value"]
|
|
|
|
floor_name: str | None = None
|
|
if "floor" in slots:
|
|
floor_name = slots["floor"]["value"]
|
|
|
|
match_constraints = intent.MatchTargetsConstraints(
|
|
name=name,
|
|
area_name=area_name,
|
|
floor_name=floor_name,
|
|
domains=[DOMAIN],
|
|
assistant=intent_obj.assistant,
|
|
features=ClimateEntityFeature.FAN_MODE,
|
|
single_target=True,
|
|
)
|
|
match_preferences = intent.MatchTargetsPreferences(
|
|
area_id=slots.get("preferred_area_id", {}).get("value"),
|
|
floor_id=slots.get("preferred_floor_id", {}).get("value"),
|
|
)
|
|
match_result = intent.async_match_targets(
|
|
hass, match_constraints, match_preferences
|
|
)
|
|
if not match_result.is_match:
|
|
raise intent.MatchFailedError(
|
|
result=match_result, constraints=match_constraints
|
|
)
|
|
|
|
assert match_result.states
|
|
climate_state = match_result.states[0]
|
|
|
|
fan_mode = await _async_resolve_fan_mode(
|
|
hass, intent_obj.language, climate_state, requested_fan_mode
|
|
)
|
|
if fan_mode is None:
|
|
raise intent.IntentHandleError(
|
|
f"Fan mode {requested_fan_mode} is not supported by "
|
|
f"{climate_state.name}"
|
|
)
|
|
|
|
await hass.services.async_call(
|
|
DOMAIN,
|
|
SERVICE_SET_FAN_MODE,
|
|
service_data={ATTR_FAN_MODE: fan_mode},
|
|
target={ATTR_ENTITY_ID: climate_state.entity_id},
|
|
blocking=True,
|
|
context=intent_obj.context,
|
|
)
|
|
|
|
response = intent_obj.create_response()
|
|
response.async_set_results(
|
|
success_results=[
|
|
intent.IntentResponseTarget(
|
|
type=intent.IntentResponseTargetType.ENTITY,
|
|
name=climate_state.name,
|
|
id=climate_state.entity_id,
|
|
)
|
|
]
|
|
)
|
|
response.async_set_states(matched_states=[climate_state])
|
|
return response
|
|
|
|
|
|
async def _async_resolve_fan_mode(
|
|
hass: HomeAssistant, language: str, climate_state: State, requested: str
|
|
) -> str | None:
|
|
"""Return a matching fan mode using translations if necessary."""
|
|
available: list[str] = climate_state.attributes.get(ATTR_FAN_MODES) or []
|
|
if requested in available:
|
|
return requested
|
|
|
|
by_casefold = {mode.casefold(): mode for mode in available}
|
|
if (fan_mode := by_casefold.get(requested.casefold())) is not None:
|
|
return fan_mode
|
|
|
|
translations = await translation.async_get_translations(
|
|
hass, language, "entity_component", {DOMAIN}
|
|
)
|
|
for key, localized in translations.items():
|
|
if key.startswith(FAN_MODE_TRANSLATION_PREFIX) and (
|
|
localized.casefold() == requested.casefold()
|
|
):
|
|
return by_casefold.get(key.removeprefix(FAN_MODE_TRANSLATION_PREFIX))
|
|
|
|
return None
|