mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-10-29 06:37:13 +01:00
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>
127 lines
3.8 KiB
Python
127 lines
3.8 KiB
Python
from typing import Any
|
|
|
|
import homeassistant.helpers.config_validation as cv
|
|
import pandas as pd
|
|
import voluptuous as vol
|
|
from homeassistant.helpers import selector
|
|
|
|
from .const import (
|
|
DOCS,
|
|
DOCS_APPLY,
|
|
DOCS_GET_COMMANDS,
|
|
DOCS_MANUAL_CONTROL,
|
|
SET_MANUAL_CONTROL_SCHEMA,
|
|
VALIDATION_TUPLES,
|
|
apply_service_schema,
|
|
get_commands_service_schema,
|
|
)
|
|
|
|
|
|
def _format_voluptuous_instance(instance: vol.All) -> str:
|
|
coerce_type = None
|
|
min_val = None
|
|
max_val = None
|
|
|
|
for validator in instance.validators:
|
|
if isinstance(validator, vol.Coerce):
|
|
coerce_type = validator.type.__name__
|
|
elif isinstance(validator, vol.Clamp | vol.Range):
|
|
min_val = validator.min
|
|
max_val = validator.max
|
|
|
|
if min_val is not None and max_val is not None:
|
|
return f"`{coerce_type}` {min_val}-{max_val}"
|
|
if min_val is not None:
|
|
return f"`{coerce_type} > {min_val}`"
|
|
if max_val is not None:
|
|
return f"`{coerce_type} < {max_val}`"
|
|
return f"`{coerce_type}`"
|
|
|
|
|
|
def _type_to_str(type_: Any) -> str: # noqa: PLR0911
|
|
"""Convert a (voluptuous) type to a string."""
|
|
if type_ == cv.entity_ids:
|
|
return "list of `entity_id`s"
|
|
if type_ in (bool, int, float, str):
|
|
return f"`{type_.__name__}`"
|
|
if type_ == cv.boolean:
|
|
return "bool"
|
|
if type_ == cv.time:
|
|
return "`str` (HH:MM[:SS])"
|
|
if isinstance(type_, vol.All):
|
|
return _format_voluptuous_instance(type_)
|
|
if isinstance(type_, vol.Any):
|
|
return " or ".join(_type_to_str(t) for t in type_.validators)
|
|
if isinstance(type_, vol.In):
|
|
return f"one of `{type_.container}`"
|
|
if isinstance(type_, selector.SelectSelector):
|
|
return f"one of `{type_.config['options']}`"
|
|
if isinstance(type_, selector.ColorRGBSelector):
|
|
return "RGB color"
|
|
msg = f"Unknown type: {type_}"
|
|
raise ValueError(msg)
|
|
|
|
|
|
def generate_config_markdown_table() -> str:
|
|
rows: list[dict[str, str]] = []
|
|
for k, default, type_ in VALIDATION_TUPLES:
|
|
description = DOCS[k]
|
|
row = {
|
|
"Variable name": f"`{k}`",
|
|
"Description": description,
|
|
"Default": f"`{default}`",
|
|
"Type": _type_to_str(type_),
|
|
}
|
|
rows.append(row)
|
|
|
|
df = pd.DataFrame(rows)
|
|
return df.to_markdown(index=False)
|
|
|
|
|
|
def _schema_to_dict(schema: vol.Schema) -> dict[str, tuple[bool, Any]]:
|
|
result: dict[str, tuple[bool, Any]] = {}
|
|
for key, value in schema.schema.items():
|
|
if isinstance(key, vol.Required | vol.Optional):
|
|
required = isinstance(key, vol.Required) and key.default == vol.UNDEFINED
|
|
result[key.schema] = (required, value)
|
|
return result
|
|
|
|
|
|
def _generate_service_markdown_table(
|
|
schema: vol.Schema,
|
|
alternative_docs: dict[str, str] | None = None,
|
|
) -> str:
|
|
rows: list[dict[str, str]] = []
|
|
for k, (required, type_) in _schema_to_dict(schema).items():
|
|
if alternative_docs is not None and k in alternative_docs:
|
|
description = alternative_docs[k]
|
|
else:
|
|
description = DOCS[k]
|
|
row = {
|
|
"Service data attribute": f"`{k}`",
|
|
"Description": description,
|
|
"Required": "✅" if required else "❌",
|
|
"Type": _type_to_str(type_),
|
|
}
|
|
rows.append(row)
|
|
|
|
df = pd.DataFrame(rows)
|
|
return df.to_markdown(index=False)
|
|
|
|
|
|
def generate_apply_markdown_table() -> str:
|
|
return _generate_service_markdown_table(apply_service_schema(), DOCS_APPLY)
|
|
|
|
|
|
def generate_get_commands_markdown_table() -> str:
|
|
return _generate_service_markdown_table(
|
|
get_commands_service_schema(),
|
|
DOCS_GET_COMMANDS,
|
|
)
|
|
|
|
|
|
def generate_set_manual_control_markdown_table() -> str:
|
|
return _generate_service_markdown_table(
|
|
SET_MANUAL_CONTROL_SCHEMA,
|
|
DOCS_MANUAL_CONTROL,
|
|
)
|