adaptive-lighting/custom_components/adaptive_lighting/__init__.py
Mathias Berglund d26fadc5bb ✨ Add adaptive_lighting.get_commands service
A service that only returns data: for each light, the `light.turn_on`
calls Adaptive Lighting would make to adapt it, now or for a given time
of day (today), without making them. Each call has its service data
and the time to wait before it (between split calls, with
`separate_turn_on_commands`).

This lets automations use Adaptive Lighting's curves for other times,
e.g. to step lights to later or earlier settings from a button: when
the automation makes the calls, Adaptive Lighting treats them like any
other change (with `take_over_control`, it stops adapting those lights
until manual control is reset).

- Only the lights and light groups in the switches' `lights` are
  allowed. Without `entity_id`, each gets the calls of the switch it's
  in (of several, the one that's on). A light group is replaced by its
  members when the switch expands light groups, as Adaptive Lighting
  does.
- Omitted options follow the switch (its adapt switches,
  `prefer_rgb_color` and `initial_transition`), and settings are for
  when the transition ends, as for the regular adaptation. Manual
  control and whether the lights are on don't change the calls.
- The calls are built by the same code as Adaptive Lighting's own,
  factored out of `prepare_adaptation_data` (`_build_service_data`) and
  `adaptation_utils.prepare_adaptation_data` (`plan_service_calls`),
  without changing their behaviour. `SunLightSettings.get_settings`
  takes an optional time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 12:41:40 +03:00

153 lines
4.7 KiB
Python

"""Adaptive Lighting integration in Home-Assistant."""
import logging
from functools import partial
from typing import Any
import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_SOURCE, Platform
from homeassistant.core import Event, HomeAssistant, SupportsResponse
from homeassistant.helpers import service
from .const import (
_DOMAIN_SCHEMA, # pyright: ignore[reportPrivateUsage]
ATTR_ADAPTIVE_LIGHTING_MANAGER,
CONF_NAME,
DOMAIN,
SERVICE_APPLY,
SERVICE_CHANGE_SWITCH_SETTINGS,
SERVICE_GET_COMMANDS,
SERVICE_SET_MANUAL_CONTROL,
SET_MANUAL_CONTROL_SCHEMA,
UNDO_UPDATE_LISTENER,
apply_service_schema,
change_switch_settings_schema,
get_commands_service_schema,
)
from .switch import (
handle_apply_service,
handle_change_switch_settings,
handle_get_commands_service,
handle_set_manual_control_service,
)
_LOGGER = logging.getLogger(__name__)
PLATFORMS = ["switch"]
def _all_unique_names(value: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""Validate that all entities have a unique profile name."""
hosts = [device[CONF_NAME] for device in value]
schema = vol.Schema(vol.Unique())
schema(hosts)
return value
CONFIG_SCHEMA = vol.Schema(
{DOMAIN: vol.All(cv.ensure_list, [_DOMAIN_SCHEMA], _all_unique_names)},
extra=vol.ALLOW_EXTRA,
)
async def reload_configuration_yaml(event: Event) -> None:
"""Reload configuration.yaml."""
hass: HomeAssistant | None = event.data.get("hass")
if hass is not None:
await hass.services.async_call("homeassistant", "check_config", {})
else:
_LOGGER.error("HomeAssistant instance not found in event data.")
async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool:
"""Import integration from config."""
hass.services.async_register(
domain=DOMAIN,
service=SERVICE_APPLY,
service_func=partial(handle_apply_service, hass),
schema=apply_service_schema(),
)
hass.services.async_register(
domain=DOMAIN,
service=SERVICE_GET_COMMANDS,
service_func=partial(handle_get_commands_service, hass),
schema=get_commands_service_schema(),
supports_response=SupportsResponse.ONLY,
)
hass.services.async_register(
domain=DOMAIN,
service=SERVICE_SET_MANUAL_CONTROL,
service_func=partial(handle_set_manual_control_service, hass),
schema=SET_MANUAL_CONTROL_SCHEMA,
)
if register_platform_service := getattr(
service,
"async_register_platform_entity_service",
None,
):
register_platform_service(
hass,
DOMAIN,
SERVICE_CHANGE_SWITCH_SETTINGS,
entity_domain=Platform.SWITCH,
func=handle_change_switch_settings,
schema=change_switch_settings_schema(),
)
if DOMAIN in config:
for entry in config[DOMAIN]:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_IMPORT},
data=entry,
),
)
return True
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up the component."""
data = hass.data.setdefault(DOMAIN, {})
# This will reload any changes the user made to any YAML configurations.
# Called during 'quick reload' or hass.reload_config_entry
hass.bus.async_listen("hass.config.entry_updated", reload_configuration_yaml)
undo_listener = config_entry.add_update_listener(async_update_options)
data[config_entry.entry_id] = {UNDO_UPDATE_LISTENER: undo_listener}
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
return True
async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
"""Update options."""
await hass.config_entries.async_reload(config_entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_forward_entry_unload(
config_entry,
"switch",
)
data = hass.data[DOMAIN]
data[config_entry.entry_id][UNDO_UPDATE_LISTENER]()
if unload_ok:
data.pop(config_entry.entry_id)
if len(data) == 1 and ATTR_ADAPTIVE_LIGHTING_MANAGER in data:
# no more config_entries
manager = data.pop(ATTR_ADAPTIVE_LIGHTING_MANAGER)
manager.disable()
if not data:
hass.data.pop(DOMAIN)
return unload_ok