mirror of
https://github.com/home-assistant/core.git
synced 2026-10-07 14:58:11 -04:00
Extract light param processing into helper module (#183435)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,10 @@
|
||||
"""Provides functionality to interact with lights."""
|
||||
|
||||
from collections.abc import Iterable
|
||||
import csv
|
||||
import dataclasses
|
||||
import logging
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any, Self, cast, final, override
|
||||
from typing import Any, Self, cast, final, override
|
||||
|
||||
import probatio
|
||||
from propcache.api import cached_property
|
||||
@@ -19,7 +18,7 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity import ToggleEntity, ToggleEntityDescription
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.frame import ReportBehavior, report_usage
|
||||
@@ -27,6 +26,28 @@ from homeassistant.helpers.typing import ConfigType, VolDictType
|
||||
from homeassistant.util import color as color_util
|
||||
|
||||
from .const import ( # noqa: F401
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_BRIGHTNESS_PCT,
|
||||
ATTR_BRIGHTNESS_STEP,
|
||||
ATTR_BRIGHTNESS_STEP_PCT,
|
||||
ATTR_COLOR_MODE,
|
||||
ATTR_COLOR_NAME,
|
||||
ATTR_COLOR_TEMP_KELVIN,
|
||||
ATTR_EFFECT,
|
||||
ATTR_EFFECT_LIST,
|
||||
ATTR_FLASH,
|
||||
ATTR_HS_COLOR,
|
||||
ATTR_MAX_COLOR_TEMP_KELVIN,
|
||||
ATTR_MIN_COLOR_TEMP_KELVIN,
|
||||
ATTR_PROFILE,
|
||||
ATTR_RGB_COLOR,
|
||||
ATTR_RGBW_COLOR,
|
||||
ATTR_RGBWW_COLOR,
|
||||
ATTR_SUPPORTED_COLOR_MODES,
|
||||
ATTR_TRANSITION,
|
||||
ATTR_WHITE,
|
||||
ATTR_XY_COLOR,
|
||||
COLOR_GROUP,
|
||||
COLOR_MODES_BRIGHTNESS,
|
||||
COLOR_MODES_COLOR,
|
||||
DATA_COMPONENT,
|
||||
@@ -34,201 +55,50 @@ from .const import ( # noqa: F401
|
||||
DEFAULT_MAX_KELVIN,
|
||||
DEFAULT_MIN_KELVIN,
|
||||
DOMAIN,
|
||||
EFFECT_COLORLOOP,
|
||||
EFFECT_OFF,
|
||||
EFFECT_RANDOM,
|
||||
EFFECT_WHITE,
|
||||
FLASH_LONG,
|
||||
FLASH_SHORT,
|
||||
LIGHT_PROFILES_FILE,
|
||||
LIGHT_TURN_OFF_SCHEMA,
|
||||
LIGHT_TURN_ON_SCHEMA,
|
||||
SCAN_INTERVAL,
|
||||
VALID_BRIGHTNESS,
|
||||
VALID_BRIGHTNESS_PCT,
|
||||
VALID_BRIGHTNESS_STEP,
|
||||
VALID_BRIGHTNESS_STEP_PCT,
|
||||
VALID_COLOR_MODES,
|
||||
VALID_FLASH,
|
||||
VALID_TRANSITION,
|
||||
ColorMode,
|
||||
LightEntityCapabilityAttribute,
|
||||
LightEntityFeature,
|
||||
LightEntityStateAttribute,
|
||||
)
|
||||
from .helper import ( # noqa: F401
|
||||
brightness_supported,
|
||||
color_supported,
|
||||
color_temp_supported,
|
||||
filter_supported_color_modes,
|
||||
filter_turn_off_params,
|
||||
filter_turn_on_params,
|
||||
get_supported_color_modes,
|
||||
preprocess_turn_on_alternatives,
|
||||
process_turn_off_params,
|
||||
process_turn_on_params,
|
||||
valid_supported_color_modes,
|
||||
)
|
||||
|
||||
ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
||||
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA
|
||||
PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE
|
||||
|
||||
|
||||
# Color mode of the light
|
||||
ATTR_COLOR_MODE = "color_mode"
|
||||
# List of color modes supported by the light
|
||||
ATTR_SUPPORTED_COLOR_MODES = "supported_color_modes"
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
|
||||
def filter_supported_color_modes(color_modes: Iterable[ColorMode]) -> set[ColorMode]:
|
||||
"""Filter the given color modes."""
|
||||
color_modes = set(color_modes)
|
||||
if (
|
||||
not color_modes
|
||||
or ColorMode.UNKNOWN in color_modes
|
||||
or (ColorMode.WHITE in color_modes and not color_supported(color_modes))
|
||||
):
|
||||
raise HomeAssistantError
|
||||
|
||||
if ColorMode.ONOFF in color_modes and len(color_modes) > 1:
|
||||
color_modes.remove(ColorMode.ONOFF)
|
||||
if ColorMode.BRIGHTNESS in color_modes and len(color_modes) > 1:
|
||||
color_modes.remove(ColorMode.BRIGHTNESS)
|
||||
return color_modes
|
||||
|
||||
|
||||
def valid_supported_color_modes(
|
||||
color_modes: Iterable[ColorMode],
|
||||
) -> set[ColorMode]:
|
||||
"""Validate the given color modes."""
|
||||
color_modes = set(color_modes)
|
||||
if (
|
||||
not color_modes
|
||||
or ColorMode.UNKNOWN in color_modes
|
||||
or (ColorMode.BRIGHTNESS in color_modes and len(color_modes) > 1)
|
||||
or (ColorMode.ONOFF in color_modes and len(color_modes) > 1)
|
||||
or (ColorMode.WHITE in color_modes and not color_supported(color_modes))
|
||||
):
|
||||
raise probatio.Error(f"Invalid supported_color_modes {sorted(color_modes)}")
|
||||
return color_modes
|
||||
|
||||
|
||||
def brightness_supported(color_modes: Iterable[ColorMode | str] | None) -> bool:
|
||||
"""Test if brightness is supported."""
|
||||
if not color_modes:
|
||||
return False
|
||||
return not COLOR_MODES_BRIGHTNESS.isdisjoint(color_modes)
|
||||
|
||||
|
||||
def color_supported(color_modes: Iterable[ColorMode | str] | None) -> bool:
|
||||
"""Test if color is supported."""
|
||||
if not color_modes:
|
||||
return False
|
||||
return not COLOR_MODES_COLOR.isdisjoint(color_modes)
|
||||
|
||||
|
||||
def color_temp_supported(color_modes: Iterable[ColorMode | str] | None) -> bool:
|
||||
"""Test if color temperature is supported."""
|
||||
if not color_modes:
|
||||
return False
|
||||
return ColorMode.COLOR_TEMP in color_modes
|
||||
|
||||
|
||||
def get_supported_color_modes(hass: HomeAssistant, entity_id: str) -> set[str] | None:
|
||||
"""Get supported color modes for a light entity.
|
||||
|
||||
First try the statemachine, then entity registry.
|
||||
This is the equivalent of entity helper get_supported_features.
|
||||
"""
|
||||
if state := hass.states.get(entity_id):
|
||||
return state.attributes.get(
|
||||
LightEntityCapabilityAttribute.SUPPORTED_COLOR_MODES
|
||||
)
|
||||
|
||||
entity_registry = er.async_get(hass)
|
||||
if not (entry := entity_registry.async_get(entity_id)):
|
||||
raise HomeAssistantError(f"Unknown entity {entity_id}")
|
||||
if not entry.capabilities:
|
||||
return None
|
||||
|
||||
return entry.capabilities.get(LightEntityCapabilityAttribute.SUPPORTED_COLOR_MODES)
|
||||
|
||||
|
||||
# Float that represents transition time in seconds to make change.
|
||||
ATTR_TRANSITION = "transition"
|
||||
|
||||
# Lists holding color values
|
||||
ATTR_RGB_COLOR = "rgb_color"
|
||||
ATTR_RGBW_COLOR = "rgbw_color"
|
||||
ATTR_RGBWW_COLOR = "rgbww_color"
|
||||
ATTR_XY_COLOR = "xy_color"
|
||||
ATTR_HS_COLOR = "hs_color"
|
||||
ATTR_COLOR_TEMP_KELVIN = "color_temp_kelvin"
|
||||
ATTR_MIN_COLOR_TEMP_KELVIN = "min_color_temp_kelvin"
|
||||
ATTR_MAX_COLOR_TEMP_KELVIN = "max_color_temp_kelvin"
|
||||
ATTR_COLOR_NAME = "color_name"
|
||||
ATTR_WHITE = "white"
|
||||
|
||||
# Brightness of the light, 0..255 or percentage
|
||||
ATTR_BRIGHTNESS = "brightness"
|
||||
ATTR_BRIGHTNESS_PCT = "brightness_pct"
|
||||
ATTR_BRIGHTNESS_STEP = "brightness_step"
|
||||
ATTR_BRIGHTNESS_STEP_PCT = "brightness_step_pct"
|
||||
|
||||
# String representing a profile (built-in ones or external defined).
|
||||
ATTR_PROFILE = "profile"
|
||||
|
||||
# If the light should flash, can be FLASH_SHORT or FLASH_LONG.
|
||||
ATTR_FLASH = "flash"
|
||||
FLASH_SHORT = "short"
|
||||
FLASH_LONG = "long"
|
||||
|
||||
# List of possible effects
|
||||
ATTR_EFFECT_LIST = "effect_list"
|
||||
|
||||
# Apply an effect to the light, can be EFFECT_COLORLOOP.
|
||||
ATTR_EFFECT = "effect"
|
||||
EFFECT_COLORLOOP = "colorloop"
|
||||
EFFECT_OFF = "off"
|
||||
EFFECT_RANDOM = "random"
|
||||
EFFECT_WHITE = "white"
|
||||
|
||||
COLOR_GROUP = "Color descriptors"
|
||||
|
||||
LIGHT_PROFILES_FILE = "light_profiles.csv"
|
||||
|
||||
# Service call validation schemas
|
||||
VALID_TRANSITION = probatio.All(probatio.Coerce(float), probatio.Clamp(min=0, max=6553))
|
||||
VALID_BRIGHTNESS = probatio.All(probatio.Coerce(int), probatio.Clamp(min=0, max=255))
|
||||
VALID_BRIGHTNESS_PCT = probatio.All(
|
||||
probatio.Coerce(float), probatio.Range(min=0, max=100)
|
||||
)
|
||||
VALID_BRIGHTNESS_STEP = probatio.All(
|
||||
probatio.Coerce(int), probatio.Clamp(min=-255, max=255)
|
||||
)
|
||||
VALID_BRIGHTNESS_STEP_PCT = probatio.All(
|
||||
probatio.Coerce(float), probatio.Clamp(min=-100, max=100)
|
||||
)
|
||||
VALID_FLASH = probatio.In([FLASH_SHORT, FLASH_LONG])
|
||||
|
||||
LIGHT_TURN_ON_SCHEMA: VolDictType = {
|
||||
probatio.Exclusive(ATTR_PROFILE, COLOR_GROUP): cv.string,
|
||||
ATTR_TRANSITION: VALID_TRANSITION,
|
||||
probatio.Exclusive(ATTR_BRIGHTNESS, ATTR_BRIGHTNESS): VALID_BRIGHTNESS,
|
||||
probatio.Exclusive(ATTR_BRIGHTNESS_PCT, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_PCT,
|
||||
probatio.Exclusive(ATTR_BRIGHTNESS_STEP, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_STEP,
|
||||
probatio.Exclusive(
|
||||
ATTR_BRIGHTNESS_STEP_PCT, ATTR_BRIGHTNESS
|
||||
): VALID_BRIGHTNESS_STEP_PCT,
|
||||
probatio.Exclusive(ATTR_COLOR_NAME, COLOR_GROUP): cv.string,
|
||||
probatio.Exclusive(ATTR_COLOR_TEMP_KELVIN, COLOR_GROUP): cv.positive_int,
|
||||
probatio.Exclusive(ATTR_HS_COLOR, COLOR_GROUP): probatio.All(
|
||||
probatio.Coerce(tuple),
|
||||
probatio.ExactSequence(
|
||||
(
|
||||
probatio.All(probatio.Coerce(float), probatio.Range(min=0, max=360)),
|
||||
probatio.All(probatio.Coerce(float), probatio.Range(min=0, max=100)),
|
||||
)
|
||||
),
|
||||
),
|
||||
probatio.Exclusive(ATTR_RGB_COLOR, COLOR_GROUP): probatio.All(
|
||||
probatio.Coerce(tuple), probatio.ExactSequence((cv.byte,) * 3)
|
||||
),
|
||||
probatio.Exclusive(ATTR_RGBW_COLOR, COLOR_GROUP): probatio.All(
|
||||
probatio.Coerce(tuple), probatio.ExactSequence((cv.byte,) * 4)
|
||||
),
|
||||
probatio.Exclusive(ATTR_RGBWW_COLOR, COLOR_GROUP): probatio.All(
|
||||
probatio.Coerce(tuple), probatio.ExactSequence((cv.byte,) * 5)
|
||||
),
|
||||
probatio.Exclusive(ATTR_XY_COLOR, COLOR_GROUP): probatio.All(
|
||||
probatio.Coerce(tuple), probatio.ExactSequence((cv.small_float, cv.small_float))
|
||||
),
|
||||
probatio.Exclusive(ATTR_WHITE, COLOR_GROUP): probatio.Any(True, VALID_BRIGHTNESS),
|
||||
ATTR_FLASH: VALID_FLASH,
|
||||
ATTR_EFFECT: cv.string,
|
||||
}
|
||||
|
||||
LIGHT_TURN_OFF_SCHEMA: VolDictType = {
|
||||
ATTR_TRANSITION: VALID_TRANSITION,
|
||||
ATTR_FLASH: VALID_FLASH,
|
||||
}
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -237,259 +107,6 @@ def is_on(hass: HomeAssistant, entity_id: str) -> bool:
|
||||
return hass.states.is_state(entity_id, STATE_ON)
|
||||
|
||||
|
||||
def preprocess_turn_on_alternatives(
|
||||
hass: HomeAssistant, params: dict[str, Any]
|
||||
) -> None:
|
||||
"""Process extra data for turn light on request.
|
||||
|
||||
Async friendly.
|
||||
"""
|
||||
# Bail out, we process this later.
|
||||
if ATTR_BRIGHTNESS_STEP in params or ATTR_BRIGHTNESS_STEP_PCT in params:
|
||||
return
|
||||
|
||||
if ATTR_PROFILE in params:
|
||||
hass.data[DATA_PROFILES].apply_profile(params.pop(ATTR_PROFILE), params)
|
||||
|
||||
if (color_name := params.pop(ATTR_COLOR_NAME, None)) is not None:
|
||||
try:
|
||||
params[ATTR_RGB_COLOR] = tuple(color_util.color_name_to_rgb(color_name))
|
||||
except ValueError:
|
||||
_LOGGER.warning("Got unknown color %s, falling back to white", color_name)
|
||||
params[ATTR_RGB_COLOR] = (255, 255, 255)
|
||||
|
||||
brightness_pct = params.pop(ATTR_BRIGHTNESS_PCT, None)
|
||||
if brightness_pct is not None:
|
||||
params[ATTR_BRIGHTNESS] = round(255 * brightness_pct / 100)
|
||||
|
||||
|
||||
def filter_turn_off_params(
|
||||
light: LightEntity, params: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Filter out params not used in turn off or not supported by the light."""
|
||||
if not params:
|
||||
return params
|
||||
|
||||
supported_features = light.supported_features
|
||||
|
||||
if LightEntityFeature.FLASH not in supported_features:
|
||||
params.pop(ATTR_FLASH, None)
|
||||
if LightEntityFeature.TRANSITION not in supported_features:
|
||||
params.pop(ATTR_TRANSITION, None)
|
||||
|
||||
return {k: v for k, v in params.items() if k in (ATTR_TRANSITION, ATTR_FLASH)}
|
||||
|
||||
|
||||
def process_turn_off_params(
|
||||
hass: HomeAssistant, light: LightEntity, params: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Process light turn off params."""
|
||||
params = dict(params)
|
||||
|
||||
if ATTR_TRANSITION not in params:
|
||||
hass.data[DATA_PROFILES].apply_default(light.entity_id, True, params)
|
||||
|
||||
return params
|
||||
|
||||
|
||||
def filter_turn_on_params(light: LightEntity, params: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Filter out params not supported by the light."""
|
||||
supported_features = light.supported_features
|
||||
|
||||
if LightEntityFeature.EFFECT not in supported_features:
|
||||
params.pop(ATTR_EFFECT, None)
|
||||
if LightEntityFeature.FLASH not in supported_features:
|
||||
params.pop(ATTR_FLASH, None)
|
||||
if LightEntityFeature.TRANSITION not in supported_features:
|
||||
params.pop(ATTR_TRANSITION, None)
|
||||
|
||||
supported_color_modes = (
|
||||
light._light_internal_supported_color_modes # noqa: SLF001
|
||||
)
|
||||
if not brightness_supported(supported_color_modes):
|
||||
params.pop(ATTR_BRIGHTNESS, None)
|
||||
if ColorMode.COLOR_TEMP not in supported_color_modes:
|
||||
params.pop(ATTR_COLOR_TEMP_KELVIN, None)
|
||||
if ColorMode.HS not in supported_color_modes:
|
||||
params.pop(ATTR_HS_COLOR, None)
|
||||
if ColorMode.RGB not in supported_color_modes:
|
||||
params.pop(ATTR_RGB_COLOR, None)
|
||||
if ColorMode.RGBW not in supported_color_modes:
|
||||
params.pop(ATTR_RGBW_COLOR, None)
|
||||
if ColorMode.RGBWW not in supported_color_modes:
|
||||
params.pop(ATTR_RGBWW_COLOR, None)
|
||||
if ColorMode.WHITE not in supported_color_modes:
|
||||
params.pop(ATTR_WHITE, None)
|
||||
if ColorMode.XY not in supported_color_modes:
|
||||
params.pop(ATTR_XY_COLOR, None)
|
||||
|
||||
return params
|
||||
|
||||
|
||||
def process_turn_on_params( # noqa: C901
|
||||
hass: HomeAssistant, light: LightEntity, params: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Process light turn on params."""
|
||||
params = dict(params)
|
||||
|
||||
# Only process params once we processed brightness step
|
||||
if params and (
|
||||
ATTR_BRIGHTNESS_STEP in params or ATTR_BRIGHTNESS_STEP_PCT in params
|
||||
):
|
||||
brightness = light.brightness if light.is_on and light.brightness else 0
|
||||
|
||||
if ATTR_BRIGHTNESS_STEP in params:
|
||||
brightness += params.pop(ATTR_BRIGHTNESS_STEP)
|
||||
|
||||
else:
|
||||
brightness_pct = round(brightness / 255 * 100)
|
||||
brightness = round(
|
||||
(brightness_pct + params.pop(ATTR_BRIGHTNESS_STEP_PCT)) / 100 * 255
|
||||
)
|
||||
|
||||
params[ATTR_BRIGHTNESS] = max(0, min(255, brightness))
|
||||
|
||||
preprocess_turn_on_alternatives(hass, params)
|
||||
|
||||
if (not params or not light.is_on) or (params and ATTR_TRANSITION not in params):
|
||||
hass.data[DATA_PROFILES].apply_default(light.entity_id, light.is_on, params)
|
||||
|
||||
supported_color_modes = light._light_internal_supported_color_modes # noqa: SLF001
|
||||
|
||||
# If a color temperature is specified, emulate it if not supported by the light
|
||||
if ATTR_COLOR_TEMP_KELVIN in params:
|
||||
if (
|
||||
ColorMode.COLOR_TEMP not in supported_color_modes
|
||||
and ColorMode.RGBWW in supported_color_modes
|
||||
):
|
||||
color_temp = params.pop(ATTR_COLOR_TEMP_KELVIN)
|
||||
brightness = cast(int, params.get(ATTR_BRIGHTNESS, light.brightness))
|
||||
params[ATTR_RGBWW_COLOR] = color_util.color_temperature_to_rgbww(
|
||||
color_temp,
|
||||
brightness,
|
||||
light.min_color_temp_kelvin,
|
||||
light.max_color_temp_kelvin,
|
||||
)
|
||||
elif ColorMode.COLOR_TEMP not in supported_color_modes:
|
||||
color_temp = params.pop(ATTR_COLOR_TEMP_KELVIN)
|
||||
if color_supported(supported_color_modes):
|
||||
params[ATTR_HS_COLOR] = color_util.color_temperature_to_hs(color_temp)
|
||||
|
||||
# If a color is specified, convert to the color space supported by the light
|
||||
rgb_color: tuple[int, int, int] | None
|
||||
rgbww_color: tuple[int, int, int, int, int] | None
|
||||
if ATTR_HS_COLOR in params and ColorMode.HS not in supported_color_modes:
|
||||
hs_color = params.pop(ATTR_HS_COLOR)
|
||||
if ColorMode.RGB in supported_color_modes:
|
||||
params[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color)
|
||||
elif ColorMode.RGBW in supported_color_modes:
|
||||
rgb_color = color_util.color_hs_to_RGB(*hs_color)
|
||||
params[ATTR_RGBW_COLOR] = color_util.color_rgb_to_rgbw(*rgb_color)
|
||||
elif ColorMode.RGBWW in supported_color_modes:
|
||||
rgb_color = color_util.color_hs_to_RGB(*hs_color)
|
||||
params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww(
|
||||
*rgb_color, light.min_color_temp_kelvin, light.max_color_temp_kelvin
|
||||
)
|
||||
elif ColorMode.XY in supported_color_modes:
|
||||
params[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color)
|
||||
elif ColorMode.COLOR_TEMP in supported_color_modes:
|
||||
xy_color = color_util.color_hs_to_xy(*hs_color)
|
||||
params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature(
|
||||
*xy_color
|
||||
)
|
||||
elif ATTR_RGB_COLOR in params and ColorMode.RGB not in supported_color_modes:
|
||||
rgb_color = params.pop(ATTR_RGB_COLOR)
|
||||
assert rgb_color is not None
|
||||
if TYPE_CHECKING:
|
||||
rgb_color = cast(tuple[int, int, int], rgb_color)
|
||||
if ColorMode.RGBW in supported_color_modes:
|
||||
params[ATTR_RGBW_COLOR] = color_util.color_rgb_to_rgbw(*rgb_color)
|
||||
elif ColorMode.RGBWW in supported_color_modes:
|
||||
params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww(
|
||||
*rgb_color,
|
||||
light.min_color_temp_kelvin,
|
||||
light.max_color_temp_kelvin,
|
||||
)
|
||||
elif ColorMode.HS in supported_color_modes:
|
||||
params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
|
||||
elif ColorMode.XY in supported_color_modes:
|
||||
params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color)
|
||||
elif ColorMode.COLOR_TEMP in supported_color_modes:
|
||||
xy_color = color_util.color_RGB_to_xy(*rgb_color)
|
||||
params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature(
|
||||
*xy_color
|
||||
)
|
||||
elif ATTR_XY_COLOR in params and ColorMode.XY not in supported_color_modes:
|
||||
xy_color = params.pop(ATTR_XY_COLOR)
|
||||
if ColorMode.HS in supported_color_modes:
|
||||
params[ATTR_HS_COLOR] = color_util.color_xy_to_hs(*xy_color)
|
||||
elif ColorMode.RGB in supported_color_modes:
|
||||
params[ATTR_RGB_COLOR] = color_util.color_xy_to_RGB(*xy_color)
|
||||
elif ColorMode.RGBW in supported_color_modes:
|
||||
rgb_color = color_util.color_xy_to_RGB(*xy_color)
|
||||
params[ATTR_RGBW_COLOR] = color_util.color_rgb_to_rgbw(*rgb_color)
|
||||
elif ColorMode.RGBWW in supported_color_modes:
|
||||
rgb_color = color_util.color_xy_to_RGB(*xy_color)
|
||||
params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww(
|
||||
*rgb_color, light.min_color_temp_kelvin, light.max_color_temp_kelvin
|
||||
)
|
||||
elif ColorMode.COLOR_TEMP in supported_color_modes:
|
||||
params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature(
|
||||
*xy_color
|
||||
)
|
||||
elif ATTR_RGBW_COLOR in params and ColorMode.RGBW not in supported_color_modes:
|
||||
rgbw_color = params.pop(ATTR_RGBW_COLOR)
|
||||
rgb_color = color_util.color_rgbw_to_rgb(*rgbw_color)
|
||||
if ColorMode.RGB in supported_color_modes:
|
||||
params[ATTR_RGB_COLOR] = rgb_color
|
||||
elif ColorMode.RGBWW in supported_color_modes:
|
||||
params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww(
|
||||
*rgb_color, light.min_color_temp_kelvin, light.max_color_temp_kelvin
|
||||
)
|
||||
elif ColorMode.HS in supported_color_modes:
|
||||
params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
|
||||
elif ColorMode.XY in supported_color_modes:
|
||||
params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color)
|
||||
elif ColorMode.COLOR_TEMP in supported_color_modes:
|
||||
xy_color = color_util.color_RGB_to_xy(*rgb_color)
|
||||
params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature(
|
||||
*xy_color
|
||||
)
|
||||
elif ATTR_RGBWW_COLOR in params and ColorMode.RGBWW not in supported_color_modes:
|
||||
rgbww_color = params.pop(ATTR_RGBWW_COLOR)
|
||||
assert rgbww_color is not None
|
||||
if TYPE_CHECKING:
|
||||
rgbww_color = cast(tuple[int, int, int, int, int], rgbww_color)
|
||||
rgb_color = color_util.color_rgbww_to_rgb(
|
||||
*rgbww_color, light.min_color_temp_kelvin, light.max_color_temp_kelvin
|
||||
)
|
||||
if ColorMode.RGB in supported_color_modes:
|
||||
params[ATTR_RGB_COLOR] = rgb_color
|
||||
elif ColorMode.RGBW in supported_color_modes:
|
||||
params[ATTR_RGBW_COLOR] = color_util.color_rgb_to_rgbw(*rgb_color)
|
||||
elif ColorMode.HS in supported_color_modes:
|
||||
params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
|
||||
elif ColorMode.XY in supported_color_modes:
|
||||
params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color)
|
||||
elif ColorMode.COLOR_TEMP in supported_color_modes:
|
||||
xy_color = color_util.color_RGB_to_xy(*rgb_color)
|
||||
params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature(
|
||||
*xy_color
|
||||
)
|
||||
|
||||
# If white is set to True, set it to the light's brightness
|
||||
# Add a warning in Home Assistant Core 2024.3 if the brightness is set to an
|
||||
# integer.
|
||||
if params.get(ATTR_WHITE) is True:
|
||||
params[ATTR_WHITE] = light.brightness
|
||||
|
||||
# If both white and brightness are specified, override white
|
||||
if ATTR_WHITE in params and ColorMode.WHITE in supported_color_modes:
|
||||
params[ATTR_WHITE] = params.pop(ATTR_BRIGHTNESS, params[ATTR_WHITE])
|
||||
|
||||
return params
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Expose light control via state machine and services."""
|
||||
component = hass.data[DATA_COMPONENT] = EntityComponent[LightEntity](
|
||||
|
||||
@@ -4,6 +4,10 @@ from datetime import timedelta
|
||||
from enum import IntFlag, StrEnum
|
||||
from typing import TYPE_CHECKING, Final
|
||||
|
||||
import probatio
|
||||
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.typing import VolDictType
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -92,3 +96,108 @@ COLOR_MODES_COLOR = {
|
||||
# https://developers.meethue.com/documentation/core-concepts
|
||||
DEFAULT_MIN_KELVIN = 2000 # 500 mireds
|
||||
DEFAULT_MAX_KELVIN = 6535 # 153 mireds
|
||||
|
||||
|
||||
# Color mode of the light
|
||||
ATTR_COLOR_MODE = "color_mode"
|
||||
# List of color modes supported by the light
|
||||
ATTR_SUPPORTED_COLOR_MODES = "supported_color_modes"
|
||||
|
||||
# Float that represents transition time in seconds to make change.
|
||||
ATTR_TRANSITION = "transition"
|
||||
|
||||
# Lists holding color values
|
||||
ATTR_RGB_COLOR = "rgb_color"
|
||||
ATTR_RGBW_COLOR = "rgbw_color"
|
||||
ATTR_RGBWW_COLOR = "rgbww_color"
|
||||
ATTR_XY_COLOR = "xy_color"
|
||||
ATTR_HS_COLOR = "hs_color"
|
||||
ATTR_COLOR_TEMP_KELVIN = "color_temp_kelvin"
|
||||
ATTR_MIN_COLOR_TEMP_KELVIN = "min_color_temp_kelvin"
|
||||
ATTR_MAX_COLOR_TEMP_KELVIN = "max_color_temp_kelvin"
|
||||
ATTR_COLOR_NAME = "color_name"
|
||||
ATTR_WHITE = "white"
|
||||
|
||||
# Brightness of the light, 0..255 or percentage
|
||||
ATTR_BRIGHTNESS = "brightness"
|
||||
ATTR_BRIGHTNESS_PCT = "brightness_pct"
|
||||
ATTR_BRIGHTNESS_STEP = "brightness_step"
|
||||
ATTR_BRIGHTNESS_STEP_PCT = "brightness_step_pct"
|
||||
|
||||
# String representing a profile (built-in ones or external defined).
|
||||
ATTR_PROFILE = "profile"
|
||||
|
||||
# If the light should flash, can be FLASH_SHORT or FLASH_LONG.
|
||||
ATTR_FLASH = "flash"
|
||||
FLASH_SHORT = "short"
|
||||
FLASH_LONG = "long"
|
||||
|
||||
# List of possible effects
|
||||
ATTR_EFFECT_LIST = "effect_list"
|
||||
|
||||
# Apply an effect to the light, can be EFFECT_COLORLOOP.
|
||||
ATTR_EFFECT = "effect"
|
||||
EFFECT_COLORLOOP = "colorloop"
|
||||
EFFECT_OFF = "off"
|
||||
EFFECT_RANDOM = "random"
|
||||
EFFECT_WHITE = "white"
|
||||
|
||||
COLOR_GROUP = "Color descriptors"
|
||||
|
||||
LIGHT_PROFILES_FILE = "light_profiles.csv"
|
||||
|
||||
# Service call validation schemas
|
||||
VALID_TRANSITION = probatio.All(probatio.Coerce(float), probatio.Clamp(min=0, max=6553))
|
||||
VALID_BRIGHTNESS = probatio.All(probatio.Coerce(int), probatio.Clamp(min=0, max=255))
|
||||
VALID_BRIGHTNESS_PCT = probatio.All(
|
||||
probatio.Coerce(float), probatio.Range(min=0, max=100)
|
||||
)
|
||||
VALID_BRIGHTNESS_STEP = probatio.All(
|
||||
probatio.Coerce(int), probatio.Clamp(min=-255, max=255)
|
||||
)
|
||||
VALID_BRIGHTNESS_STEP_PCT = probatio.All(
|
||||
probatio.Coerce(float), probatio.Clamp(min=-100, max=100)
|
||||
)
|
||||
VALID_FLASH = probatio.In([FLASH_SHORT, FLASH_LONG])
|
||||
|
||||
LIGHT_TURN_ON_SCHEMA: VolDictType = {
|
||||
probatio.Exclusive(ATTR_PROFILE, COLOR_GROUP): cv.string,
|
||||
ATTR_TRANSITION: VALID_TRANSITION,
|
||||
probatio.Exclusive(ATTR_BRIGHTNESS, ATTR_BRIGHTNESS): VALID_BRIGHTNESS,
|
||||
probatio.Exclusive(ATTR_BRIGHTNESS_PCT, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_PCT,
|
||||
probatio.Exclusive(ATTR_BRIGHTNESS_STEP, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_STEP,
|
||||
probatio.Exclusive(
|
||||
ATTR_BRIGHTNESS_STEP_PCT, ATTR_BRIGHTNESS
|
||||
): VALID_BRIGHTNESS_STEP_PCT,
|
||||
probatio.Exclusive(ATTR_COLOR_NAME, COLOR_GROUP): cv.string,
|
||||
probatio.Exclusive(ATTR_COLOR_TEMP_KELVIN, COLOR_GROUP): cv.positive_int,
|
||||
probatio.Exclusive(ATTR_HS_COLOR, COLOR_GROUP): probatio.All(
|
||||
probatio.Coerce(tuple),
|
||||
probatio.ExactSequence(
|
||||
(
|
||||
probatio.All(probatio.Coerce(float), probatio.Range(min=0, max=360)),
|
||||
probatio.All(probatio.Coerce(float), probatio.Range(min=0, max=100)),
|
||||
)
|
||||
),
|
||||
),
|
||||
probatio.Exclusive(ATTR_RGB_COLOR, COLOR_GROUP): probatio.All(
|
||||
probatio.Coerce(tuple), probatio.ExactSequence((cv.byte,) * 3)
|
||||
),
|
||||
probatio.Exclusive(ATTR_RGBW_COLOR, COLOR_GROUP): probatio.All(
|
||||
probatio.Coerce(tuple), probatio.ExactSequence((cv.byte,) * 4)
|
||||
),
|
||||
probatio.Exclusive(ATTR_RGBWW_COLOR, COLOR_GROUP): probatio.All(
|
||||
probatio.Coerce(tuple), probatio.ExactSequence((cv.byte,) * 5)
|
||||
),
|
||||
probatio.Exclusive(ATTR_XY_COLOR, COLOR_GROUP): probatio.All(
|
||||
probatio.Coerce(tuple), probatio.ExactSequence((cv.small_float, cv.small_float))
|
||||
),
|
||||
probatio.Exclusive(ATTR_WHITE, COLOR_GROUP): probatio.Any(True, VALID_BRIGHTNESS),
|
||||
ATTR_FLASH: VALID_FLASH,
|
||||
ATTR_EFFECT: cv.string,
|
||||
}
|
||||
|
||||
LIGHT_TURN_OFF_SCHEMA: VolDictType = {
|
||||
ATTR_TRANSITION: VALID_TRANSITION,
|
||||
ATTR_FLASH: VALID_FLASH,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
"""Helper functions for the light integration."""
|
||||
|
||||
from collections.abc import Iterable
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
import probatio
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.util import color as color_util
|
||||
|
||||
from .const import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_BRIGHTNESS_PCT,
|
||||
ATTR_BRIGHTNESS_STEP,
|
||||
ATTR_BRIGHTNESS_STEP_PCT,
|
||||
ATTR_COLOR_NAME,
|
||||
ATTR_COLOR_TEMP_KELVIN,
|
||||
ATTR_EFFECT,
|
||||
ATTR_FLASH,
|
||||
ATTR_HS_COLOR,
|
||||
ATTR_PROFILE,
|
||||
ATTR_RGB_COLOR,
|
||||
ATTR_RGBW_COLOR,
|
||||
ATTR_RGBWW_COLOR,
|
||||
ATTR_TRANSITION,
|
||||
ATTR_WHITE,
|
||||
ATTR_XY_COLOR,
|
||||
COLOR_MODES_BRIGHTNESS,
|
||||
COLOR_MODES_COLOR,
|
||||
DATA_PROFILES,
|
||||
ColorMode,
|
||||
LightEntityCapabilityAttribute,
|
||||
LightEntityFeature,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import LightEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def filter_supported_color_modes(color_modes: Iterable[ColorMode]) -> set[ColorMode]:
|
||||
"""Filter the given color modes."""
|
||||
color_modes = set(color_modes)
|
||||
if (
|
||||
not color_modes
|
||||
or ColorMode.UNKNOWN in color_modes
|
||||
or (ColorMode.WHITE in color_modes and not color_supported(color_modes))
|
||||
):
|
||||
raise HomeAssistantError
|
||||
|
||||
if ColorMode.ONOFF in color_modes and len(color_modes) > 1:
|
||||
color_modes.remove(ColorMode.ONOFF)
|
||||
if ColorMode.BRIGHTNESS in color_modes and len(color_modes) > 1:
|
||||
color_modes.remove(ColorMode.BRIGHTNESS)
|
||||
return color_modes
|
||||
|
||||
|
||||
def valid_supported_color_modes(
|
||||
color_modes: Iterable[ColorMode],
|
||||
) -> set[ColorMode]:
|
||||
"""Validate the given color modes."""
|
||||
color_modes = set(color_modes)
|
||||
if (
|
||||
not color_modes
|
||||
or ColorMode.UNKNOWN in color_modes
|
||||
or (ColorMode.BRIGHTNESS in color_modes and len(color_modes) > 1)
|
||||
or (ColorMode.ONOFF in color_modes and len(color_modes) > 1)
|
||||
or (ColorMode.WHITE in color_modes and not color_supported(color_modes))
|
||||
):
|
||||
raise probatio.Error(f"Invalid supported_color_modes {sorted(color_modes)}")
|
||||
return color_modes
|
||||
|
||||
|
||||
def brightness_supported(color_modes: Iterable[ColorMode | str] | None) -> bool:
|
||||
"""Test if brightness is supported."""
|
||||
if not color_modes:
|
||||
return False
|
||||
return not COLOR_MODES_BRIGHTNESS.isdisjoint(color_modes)
|
||||
|
||||
|
||||
def color_supported(color_modes: Iterable[ColorMode | str] | None) -> bool:
|
||||
"""Test if color is supported."""
|
||||
if not color_modes:
|
||||
return False
|
||||
return not COLOR_MODES_COLOR.isdisjoint(color_modes)
|
||||
|
||||
|
||||
def color_temp_supported(color_modes: Iterable[ColorMode | str] | None) -> bool:
|
||||
"""Test if color temperature is supported."""
|
||||
if not color_modes:
|
||||
return False
|
||||
return ColorMode.COLOR_TEMP in color_modes
|
||||
|
||||
|
||||
def get_supported_color_modes(hass: HomeAssistant, entity_id: str) -> set[str] | None:
|
||||
"""Get supported color modes for a light entity.
|
||||
|
||||
First try the statemachine, then entity registry.
|
||||
This is the equivalent of entity helper get_supported_features.
|
||||
"""
|
||||
if state := hass.states.get(entity_id):
|
||||
return state.attributes.get(
|
||||
LightEntityCapabilityAttribute.SUPPORTED_COLOR_MODES
|
||||
)
|
||||
|
||||
entity_registry = er.async_get(hass)
|
||||
if not (entry := entity_registry.async_get(entity_id)):
|
||||
raise HomeAssistantError(f"Unknown entity {entity_id}")
|
||||
if not entry.capabilities:
|
||||
return None
|
||||
|
||||
return entry.capabilities.get(LightEntityCapabilityAttribute.SUPPORTED_COLOR_MODES)
|
||||
|
||||
|
||||
def preprocess_turn_on_alternatives(
|
||||
hass: HomeAssistant, params: dict[str, Any]
|
||||
) -> None:
|
||||
"""Process extra data for turn light on request.
|
||||
|
||||
Async friendly.
|
||||
"""
|
||||
# Bail out, we process this later.
|
||||
if ATTR_BRIGHTNESS_STEP in params or ATTR_BRIGHTNESS_STEP_PCT in params:
|
||||
return
|
||||
|
||||
if ATTR_PROFILE in params:
|
||||
hass.data[DATA_PROFILES].apply_profile(params.pop(ATTR_PROFILE), params)
|
||||
|
||||
if (color_name := params.pop(ATTR_COLOR_NAME, None)) is not None:
|
||||
try:
|
||||
params[ATTR_RGB_COLOR] = tuple(color_util.color_name_to_rgb(color_name))
|
||||
except ValueError:
|
||||
_LOGGER.warning("Got unknown color %s, falling back to white", color_name)
|
||||
params[ATTR_RGB_COLOR] = (255, 255, 255)
|
||||
|
||||
brightness_pct = params.pop(ATTR_BRIGHTNESS_PCT, None)
|
||||
if brightness_pct is not None:
|
||||
params[ATTR_BRIGHTNESS] = round(255 * brightness_pct / 100)
|
||||
|
||||
|
||||
def filter_turn_off_params(
|
||||
light: LightEntity, params: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Filter out params not used in turn off or not supported by the light."""
|
||||
if not params:
|
||||
return params
|
||||
|
||||
supported_features = light.supported_features
|
||||
|
||||
if LightEntityFeature.FLASH not in supported_features:
|
||||
params.pop(ATTR_FLASH, None)
|
||||
if LightEntityFeature.TRANSITION not in supported_features:
|
||||
params.pop(ATTR_TRANSITION, None)
|
||||
|
||||
return {k: v for k, v in params.items() if k in (ATTR_TRANSITION, ATTR_FLASH)}
|
||||
|
||||
|
||||
def process_turn_off_params(
|
||||
hass: HomeAssistant, light: LightEntity, params: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Process light turn off params."""
|
||||
params = dict(params)
|
||||
|
||||
if ATTR_TRANSITION not in params:
|
||||
hass.data[DATA_PROFILES].apply_default(light.entity_id, True, params)
|
||||
|
||||
return params
|
||||
|
||||
|
||||
def filter_turn_on_params(light: LightEntity, params: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Filter out params not supported by the light."""
|
||||
supported_features = light.supported_features
|
||||
|
||||
if LightEntityFeature.EFFECT not in supported_features:
|
||||
params.pop(ATTR_EFFECT, None)
|
||||
if LightEntityFeature.FLASH not in supported_features:
|
||||
params.pop(ATTR_FLASH, None)
|
||||
if LightEntityFeature.TRANSITION not in supported_features:
|
||||
params.pop(ATTR_TRANSITION, None)
|
||||
|
||||
supported_color_modes = (
|
||||
light._light_internal_supported_color_modes # noqa: SLF001
|
||||
)
|
||||
if not brightness_supported(supported_color_modes):
|
||||
params.pop(ATTR_BRIGHTNESS, None)
|
||||
if ColorMode.COLOR_TEMP not in supported_color_modes:
|
||||
params.pop(ATTR_COLOR_TEMP_KELVIN, None)
|
||||
if ColorMode.HS not in supported_color_modes:
|
||||
params.pop(ATTR_HS_COLOR, None)
|
||||
if ColorMode.RGB not in supported_color_modes:
|
||||
params.pop(ATTR_RGB_COLOR, None)
|
||||
if ColorMode.RGBW not in supported_color_modes:
|
||||
params.pop(ATTR_RGBW_COLOR, None)
|
||||
if ColorMode.RGBWW not in supported_color_modes:
|
||||
params.pop(ATTR_RGBWW_COLOR, None)
|
||||
if ColorMode.WHITE not in supported_color_modes:
|
||||
params.pop(ATTR_WHITE, None)
|
||||
if ColorMode.XY not in supported_color_modes:
|
||||
params.pop(ATTR_XY_COLOR, None)
|
||||
|
||||
return params
|
||||
|
||||
|
||||
def process_turn_on_params( # noqa: C901
|
||||
hass: HomeAssistant, light: LightEntity, params: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Process light turn on params."""
|
||||
params = dict(params)
|
||||
|
||||
# Only process params once we processed brightness step
|
||||
if params and (
|
||||
ATTR_BRIGHTNESS_STEP in params or ATTR_BRIGHTNESS_STEP_PCT in params
|
||||
):
|
||||
brightness = light.brightness if light.is_on and light.brightness else 0
|
||||
|
||||
if ATTR_BRIGHTNESS_STEP in params:
|
||||
brightness += params.pop(ATTR_BRIGHTNESS_STEP)
|
||||
|
||||
else:
|
||||
brightness_pct = round(brightness / 255 * 100)
|
||||
brightness = round(
|
||||
(brightness_pct + params.pop(ATTR_BRIGHTNESS_STEP_PCT)) / 100 * 255
|
||||
)
|
||||
|
||||
params[ATTR_BRIGHTNESS] = max(0, min(255, brightness))
|
||||
|
||||
preprocess_turn_on_alternatives(hass, params)
|
||||
|
||||
if (not params or not light.is_on) or (params and ATTR_TRANSITION not in params):
|
||||
hass.data[DATA_PROFILES].apply_default(light.entity_id, light.is_on, params)
|
||||
|
||||
supported_color_modes = light._light_internal_supported_color_modes # noqa: SLF001
|
||||
|
||||
# If a color temperature is specified, emulate it if not supported by the light
|
||||
if ATTR_COLOR_TEMP_KELVIN in params:
|
||||
if (
|
||||
ColorMode.COLOR_TEMP not in supported_color_modes
|
||||
and ColorMode.RGBWW in supported_color_modes
|
||||
):
|
||||
color_temp = params.pop(ATTR_COLOR_TEMP_KELVIN)
|
||||
brightness = cast(int, params.get(ATTR_BRIGHTNESS, light.brightness))
|
||||
params[ATTR_RGBWW_COLOR] = color_util.color_temperature_to_rgbww(
|
||||
color_temp,
|
||||
brightness,
|
||||
light.min_color_temp_kelvin,
|
||||
light.max_color_temp_kelvin,
|
||||
)
|
||||
elif ColorMode.COLOR_TEMP not in supported_color_modes:
|
||||
color_temp = params.pop(ATTR_COLOR_TEMP_KELVIN)
|
||||
if color_supported(supported_color_modes):
|
||||
params[ATTR_HS_COLOR] = color_util.color_temperature_to_hs(color_temp)
|
||||
|
||||
# If a color is specified, convert to the color space supported by the light
|
||||
rgb_color: tuple[int, int, int] | None
|
||||
rgbww_color: tuple[int, int, int, int, int] | None
|
||||
if ATTR_HS_COLOR in params and ColorMode.HS not in supported_color_modes:
|
||||
hs_color = params.pop(ATTR_HS_COLOR)
|
||||
if ColorMode.RGB in supported_color_modes:
|
||||
params[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color)
|
||||
elif ColorMode.RGBW in supported_color_modes:
|
||||
rgb_color = color_util.color_hs_to_RGB(*hs_color)
|
||||
params[ATTR_RGBW_COLOR] = color_util.color_rgb_to_rgbw(*rgb_color)
|
||||
elif ColorMode.RGBWW in supported_color_modes:
|
||||
rgb_color = color_util.color_hs_to_RGB(*hs_color)
|
||||
params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww(
|
||||
*rgb_color, light.min_color_temp_kelvin, light.max_color_temp_kelvin
|
||||
)
|
||||
elif ColorMode.XY in supported_color_modes:
|
||||
params[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color)
|
||||
elif ColorMode.COLOR_TEMP in supported_color_modes:
|
||||
xy_color = color_util.color_hs_to_xy(*hs_color)
|
||||
params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature(
|
||||
*xy_color
|
||||
)
|
||||
elif ATTR_RGB_COLOR in params and ColorMode.RGB not in supported_color_modes:
|
||||
rgb_color = params.pop(ATTR_RGB_COLOR)
|
||||
assert rgb_color is not None
|
||||
if TYPE_CHECKING:
|
||||
rgb_color = cast(tuple[int, int, int], rgb_color)
|
||||
if ColorMode.RGBW in supported_color_modes:
|
||||
params[ATTR_RGBW_COLOR] = color_util.color_rgb_to_rgbw(*rgb_color)
|
||||
elif ColorMode.RGBWW in supported_color_modes:
|
||||
params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww(
|
||||
*rgb_color,
|
||||
light.min_color_temp_kelvin,
|
||||
light.max_color_temp_kelvin,
|
||||
)
|
||||
elif ColorMode.HS in supported_color_modes:
|
||||
params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
|
||||
elif ColorMode.XY in supported_color_modes:
|
||||
params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color)
|
||||
elif ColorMode.COLOR_TEMP in supported_color_modes:
|
||||
xy_color = color_util.color_RGB_to_xy(*rgb_color)
|
||||
params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature(
|
||||
*xy_color
|
||||
)
|
||||
elif ATTR_XY_COLOR in params and ColorMode.XY not in supported_color_modes:
|
||||
xy_color = params.pop(ATTR_XY_COLOR)
|
||||
if ColorMode.HS in supported_color_modes:
|
||||
params[ATTR_HS_COLOR] = color_util.color_xy_to_hs(*xy_color)
|
||||
elif ColorMode.RGB in supported_color_modes:
|
||||
params[ATTR_RGB_COLOR] = color_util.color_xy_to_RGB(*xy_color)
|
||||
elif ColorMode.RGBW in supported_color_modes:
|
||||
rgb_color = color_util.color_xy_to_RGB(*xy_color)
|
||||
params[ATTR_RGBW_COLOR] = color_util.color_rgb_to_rgbw(*rgb_color)
|
||||
elif ColorMode.RGBWW in supported_color_modes:
|
||||
rgb_color = color_util.color_xy_to_RGB(*xy_color)
|
||||
params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww(
|
||||
*rgb_color, light.min_color_temp_kelvin, light.max_color_temp_kelvin
|
||||
)
|
||||
elif ColorMode.COLOR_TEMP in supported_color_modes:
|
||||
params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature(
|
||||
*xy_color
|
||||
)
|
||||
elif ATTR_RGBW_COLOR in params and ColorMode.RGBW not in supported_color_modes:
|
||||
rgbw_color = params.pop(ATTR_RGBW_COLOR)
|
||||
rgb_color = color_util.color_rgbw_to_rgb(*rgbw_color)
|
||||
if ColorMode.RGB in supported_color_modes:
|
||||
params[ATTR_RGB_COLOR] = rgb_color
|
||||
elif ColorMode.RGBWW in supported_color_modes:
|
||||
params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww(
|
||||
*rgb_color, light.min_color_temp_kelvin, light.max_color_temp_kelvin
|
||||
)
|
||||
elif ColorMode.HS in supported_color_modes:
|
||||
params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
|
||||
elif ColorMode.XY in supported_color_modes:
|
||||
params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color)
|
||||
elif ColorMode.COLOR_TEMP in supported_color_modes:
|
||||
xy_color = color_util.color_RGB_to_xy(*rgb_color)
|
||||
params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature(
|
||||
*xy_color
|
||||
)
|
||||
elif ATTR_RGBWW_COLOR in params and ColorMode.RGBWW not in supported_color_modes:
|
||||
rgbww_color = params.pop(ATTR_RGBWW_COLOR)
|
||||
assert rgbww_color is not None
|
||||
if TYPE_CHECKING:
|
||||
rgbww_color = cast(tuple[int, int, int, int, int], rgbww_color)
|
||||
rgb_color = color_util.color_rgbww_to_rgb(
|
||||
*rgbww_color, light.min_color_temp_kelvin, light.max_color_temp_kelvin
|
||||
)
|
||||
if ColorMode.RGB in supported_color_modes:
|
||||
params[ATTR_RGB_COLOR] = rgb_color
|
||||
elif ColorMode.RGBW in supported_color_modes:
|
||||
params[ATTR_RGBW_COLOR] = color_util.color_rgb_to_rgbw(*rgb_color)
|
||||
elif ColorMode.HS in supported_color_modes:
|
||||
params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
|
||||
elif ColorMode.XY in supported_color_modes:
|
||||
params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color)
|
||||
elif ColorMode.COLOR_TEMP in supported_color_modes:
|
||||
xy_color = color_util.color_RGB_to_xy(*rgb_color)
|
||||
params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature(
|
||||
*xy_color
|
||||
)
|
||||
|
||||
# If white is set to True, set it to the light's brightness
|
||||
# Add a warning in Home Assistant Core 2024.3 if the brightness is set to an
|
||||
# integer.
|
||||
if params.get(ATTR_WHITE) is True:
|
||||
params[ATTR_WHITE] = light.brightness
|
||||
|
||||
# If both white and brightness are specified, override white
|
||||
if ATTR_WHITE in params and ColorMode.WHITE in supported_color_modes:
|
||||
params[ATTR_WHITE] = params.pop(ATTR_BRIGHTNESS, params[ATTR_WHITE])
|
||||
|
||||
return params
|
||||
Reference in New Issue
Block a user