core/homeassistant/components/hue/services.py

164 lines
5.0 KiB
Python

"""Handle Hue Service calls."""
from __future__ import annotations
import asyncio
import logging
from aiohue import HueBridgeV1, HueBridgeV2
import voluptuous as vol
from homeassistant.core import HomeAssistant, ServiceCall
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.service import verify_domain_control
from .bridge import HueBridge
from .const import (
ATTR_DYNAMIC,
ATTR_GROUP_NAME,
ATTR_SCENE_NAME,
ATTR_TRANSITION,
DOMAIN,
SERVICE_HUE_ACTIVATE_SCENE,
)
LOGGER = logging.getLogger(__name__)
def async_register_services(hass: HomeAssistant) -> None:
"""Register services for Hue integration."""
async def hue_activate_scene(call: ServiceCall, skip_reload=True) -> None:
"""Handle activation of Hue scene."""
# Get parameters
group_name = call.data[ATTR_GROUP_NAME]
scene_name = call.data[ATTR_SCENE_NAME]
transition = call.data.get(ATTR_TRANSITION)
dynamic = call.data.get(ATTR_DYNAMIC, False)
# Call the set scene function on each bridge
tasks = [
hue_activate_scene_v1(bridge, group_name, scene_name, transition)
if bridge.api_version == 1
else hue_activate_scene_v2(
bridge, group_name, scene_name, transition, dynamic
)
for bridge in hass.data[DOMAIN].values()
if isinstance(bridge, HueBridge)
]
results = await asyncio.gather(*tasks)
# Did *any* bridge succeed?
# Note that we'll get a "True" value for a successful call
if True not in results:
LOGGER.warning(
"No bridge was able to activate scene %s in group %s",
scene_name,
group_name,
)
if not hass.services.has_service(DOMAIN, SERVICE_HUE_ACTIVATE_SCENE):
# Register a local handler for scene activation
hass.services.async_register(
DOMAIN,
SERVICE_HUE_ACTIVATE_SCENE,
verify_domain_control(hass, DOMAIN)(hue_activate_scene),
schema=vol.Schema(
{
vol.Required(ATTR_GROUP_NAME): cv.string,
vol.Required(ATTR_SCENE_NAME): cv.string,
vol.Optional(ATTR_TRANSITION): cv.positive_int,
vol.Optional(ATTR_DYNAMIC): cv.boolean,
}
),
)
async def hue_activate_scene_v1(
bridge: HueBridge,
group_name: str,
scene_name: str,
transition: int | None = None,
is_retry: bool = False,
) -> bool:
"""Service for V1 bridge to call directly into bridge to set scenes."""
api: HueBridgeV1 = bridge.api
if api.scenes is None:
LOGGER.warning("Hub %s does not support scenes", api.host)
return False
group = next(
(group for group in api.groups.values() if group.name == group_name),
None,
)
# Additional scene logic to handle duplicate scene names across groups
scene = next(
(
scene
for scene in api.scenes.values()
if scene.name == scene_name
and group is not None
and sorted(scene.lights) == sorted(group.lights)
),
None,
)
# If we can't find it, fetch latest info and try again
if not is_retry and (group is None or scene is None):
await bridge.async_request_call(api.groups.update)
await bridge.async_request_call(api.scenes.update)
return await hue_activate_scene_v1(
bridge, group_name, scene_name, transition, is_retry=True
)
if group is None or scene is None:
LOGGER.debug(
"Unable to find scene %s for group %s on bridge %s",
scene_name,
group_name,
bridge.host,
)
return False
await bridge.async_request_call(
group.set_action, scene=scene.id, transitiontime=transition
)
return True
async def hue_activate_scene_v2(
bridge: HueBridge,
group_name: str,
scene_name: str,
transition: int | None = None,
dynamic: bool = True,
) -> bool:
"""Service for V2 bridge to call scene by name."""
LOGGER.warning(
(
"Use of service_call '%s' is deprecated and will be removed "
"in a future release. Please use scene entities instead"
),
SERVICE_HUE_ACTIVATE_SCENE,
)
api: HueBridgeV2 = bridge.api
for scene in api.scenes:
if scene.metadata.name.lower() != scene_name.lower():
continue
group = api.scenes.get_group(scene.id)
if group.metadata.name.lower() != group_name.lower():
continue
# found match!
if transition:
transition = transition * 1000 # transition is in ms
await bridge.async_request_call(
api.scenes.recall, scene.id, dynamic=dynamic, duration=transition
)
return True
LOGGER.debug(
"Unable to find scene %s for group %s on bridge %s",
scene_name,
group_name,
bridge.host,
)
return False