1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-28 19:26:11 +01:00
Files
2026-04-30 21:14:48 +02:00

157 lines
4.9 KiB
Python

"""Allow users to set and activate scenes."""
import functools as ft
import importlib
import logging
from typing import Any, Final, final
import voluptuous as vol
from homeassistant.components.light import ATTR_TRANSITION
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PLATFORM, SERVICE_TURN_ON, STATE_UNAVAILABLE
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import dt as dt_util
from homeassistant.util.async_ import run_callback_threadsafe
from homeassistant.util.hass_dict import HassKey
DOMAIN: Final = "scene"
DATA_COMPONENT: HassKey[EntityComponent[BaseScene]] = HassKey(DOMAIN)
STATES: Final = "states"
def _hass_domain_validator(config: dict[str, Any]) -> dict[str, Any]:
"""Validate platform in config for homeassistant domain."""
if CONF_PLATFORM not in config:
config = {CONF_PLATFORM: HOMEASSISTANT_DOMAIN, STATES: config}
return config
def _platform_validator(config: dict[str, Any]) -> dict[str, Any]:
"""Validate it is a valid platform."""
platform_name = config[CONF_PLATFORM]
try:
platform = importlib.import_module(
f"homeassistant.components.{platform_name}.scene"
)
except ImportError:
raise vol.Invalid("Invalid platform specified") from None
if not hasattr(platform, "PLATFORM_SCHEMA"):
return config
return platform.PLATFORM_SCHEMA(config) # type: ignore[no-any-return]
PLATFORM_SCHEMA = vol.Schema(
vol.All(
_hass_domain_validator,
vol.Schema({vol.Required(CONF_PLATFORM): str}, extra=vol.ALLOW_EXTRA),
_platform_validator,
),
extra=vol.ALLOW_EXTRA,
)
# mypy: disallow-any-generics
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the scenes."""
component = hass.data[DATA_COMPONENT] = EntityComponent[BaseScene](
logging.getLogger(__name__), DOMAIN, hass
)
await component.async_setup(config)
# Ensure Home Assistant platform always loaded.
hass.async_create_task(
component.async_setup_platform(
HOMEASSISTANT_DOMAIN, {"platform": HOMEASSISTANT_DOMAIN, STATES: []}
),
eager_start=True,
)
component.async_register_entity_service(
SERVICE_TURN_ON,
{ATTR_TRANSITION: vol.All(vol.Coerce(float), vol.Clamp(min=0, max=6553))},
"_async_activate",
)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a config entry."""
return await hass.data[DATA_COMPONENT].async_setup_entry(entry)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.data[DATA_COMPONENT].async_unload_entry(entry)
class BaseScene(RestoreEntity):
"""Base type for scenes."""
_attr_should_poll = False
__last_activated: str | None = None
@property
@final
def state(self) -> str | None:
"""Return the state of the scene."""
if self.__last_activated is None:
return None
return self.__last_activated
@final
def _record_activation(self) -> None:
run_callback_threadsafe(self.hass.loop, self._async_record_activation).result()
@final
@callback
def _async_record_activation(self) -> None:
"""Update the activation timestamp."""
self.__last_activated = dt_util.utcnow().isoformat()
async def async_internal_added_to_hass(self) -> None:
"""Call when the scene is added to hass."""
await super().async_internal_added_to_hass()
state = await self.async_get_last_state()
if (
state is not None
and state.state is not None
and state.state != STATE_UNAVAILABLE
):
self.__last_activated = state.state
async def _async_activate(self, **kwargs: Any) -> None:
"""Activate scene."""
raise NotImplementedError
def activate(self, **kwargs: Any) -> None:
"""Activate scene. Try to get entities into requested state."""
raise NotImplementedError
async def async_activate(self, **kwargs: Any) -> None:
"""Activate scene. Try to get entities into requested state."""
task = self.hass.async_add_executor_job(ft.partial(self.activate, **kwargs))
if task:
await task
class Scene(BaseScene):
"""A scene is a group of entities and the states we want them to be."""
@final
async def _async_activate(self, **kwargs: Any) -> None:
"""Activate scene.
Should not be overridden, handle setting last press timestamp.
"""
self._async_record_activation()
self.async_write_ha_state()
await self.async_activate(**kwargs)