148 lines
4.7 KiB
Python
148 lines
4.7 KiB
Python
"""Intents for the light integration."""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
from typing import Any
|
|
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers import area_registry as ar, config_validation as cv, intent
|
|
import homeassistant.util.color as color_util
|
|
|
|
from . import (
|
|
ATTR_BRIGHTNESS_PCT,
|
|
ATTR_RGB_COLOR,
|
|
ATTR_SUPPORTED_COLOR_MODES,
|
|
DOMAIN,
|
|
brightness_supported,
|
|
color_supported,
|
|
)
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
INTENT_SET = "HassLightSet"
|
|
|
|
|
|
async def async_setup_intents(hass: HomeAssistant) -> None:
|
|
"""Set up the light intents."""
|
|
intent.async_register(hass, SetIntentHandler())
|
|
|
|
|
|
class SetIntentHandler(intent.IntentHandler):
|
|
"""Handle set color intents."""
|
|
|
|
intent_type = INTENT_SET
|
|
slot_schema = {
|
|
vol.Any("name", "area"): cv.string,
|
|
vol.Optional("domain"): vol.All(cv.ensure_list, [cv.string]),
|
|
vol.Optional("device_class"): vol.All(cv.ensure_list, [cv.string]),
|
|
vol.Optional("color"): color_util.color_name_to_rgb,
|
|
vol.Optional("brightness"): vol.All(vol.Coerce(int), vol.Range(0, 100)),
|
|
}
|
|
|
|
async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
|
|
"""Handle the hass intent."""
|
|
hass = intent_obj.hass
|
|
service_data: dict[str, Any] = {}
|
|
slots = self.async_validate_slots(intent_obj.slots)
|
|
|
|
name: str | None = slots.get("name", {}).get("value")
|
|
if name == "all":
|
|
# Don't match on name if targeting all entities
|
|
name = None
|
|
|
|
# Look up area first to fail early
|
|
area_name = slots.get("area", {}).get("value")
|
|
area: ar.AreaEntry | None = None
|
|
if area_name is not None:
|
|
areas = ar.async_get(hass)
|
|
area = areas.async_get_area(area_name) or areas.async_get_area_by_name(
|
|
area_name
|
|
)
|
|
if area is None:
|
|
raise intent.IntentHandleError(f"No area named {area_name}")
|
|
|
|
# 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"]["value"])
|
|
|
|
if "device_class" in slots:
|
|
device_classes = set(slots["device_class"]["value"])
|
|
|
|
states = list(
|
|
intent.async_match_states(
|
|
hass,
|
|
name=name,
|
|
area=area,
|
|
domains=domains,
|
|
device_classes=device_classes,
|
|
)
|
|
)
|
|
|
|
if not states:
|
|
raise intent.IntentHandleError("No entities matched")
|
|
|
|
if "color" in slots:
|
|
service_data[ATTR_RGB_COLOR] = slots["color"]["value"]
|
|
|
|
if "brightness" in slots:
|
|
service_data[ATTR_BRIGHTNESS_PCT] = slots["brightness"]["value"]
|
|
|
|
response = intent_obj.create_response()
|
|
needs_brightness = ATTR_BRIGHTNESS_PCT in service_data
|
|
needs_color = ATTR_RGB_COLOR in service_data
|
|
|
|
success_results: list[intent.IntentResponseTarget] = []
|
|
failed_results: list[intent.IntentResponseTarget] = []
|
|
service_coros = []
|
|
|
|
if area is not None:
|
|
success_results.append(
|
|
intent.IntentResponseTarget(
|
|
type=intent.IntentResponseTargetType.AREA,
|
|
name=area.name,
|
|
id=area.id,
|
|
)
|
|
)
|
|
|
|
for state in states:
|
|
target = intent.IntentResponseTarget(
|
|
type=intent.IntentResponseTargetType.ENTITY,
|
|
name=state.name,
|
|
id=state.entity_id,
|
|
)
|
|
|
|
# Test brightness/color
|
|
supported_color_modes = state.attributes.get(ATTR_SUPPORTED_COLOR_MODES)
|
|
if (needs_color and not color_supported(supported_color_modes)) or (
|
|
needs_brightness and not brightness_supported(supported_color_modes)
|
|
):
|
|
failed_results.append(target)
|
|
continue
|
|
|
|
service_coros.append(
|
|
hass.services.async_call(
|
|
DOMAIN,
|
|
SERVICE_TURN_ON,
|
|
{**service_data, ATTR_ENTITY_ID: state.entity_id},
|
|
context=intent_obj.context,
|
|
)
|
|
)
|
|
success_results.append(target)
|
|
|
|
# Handle service calls in parallel.
|
|
await asyncio.gather(*service_coros)
|
|
|
|
response.async_set_results(
|
|
success_results=success_results, failed_results=failed_results
|
|
)
|
|
|
|
return response
|