core/homeassistant/components/mqtt/light/schema_json.py

751 lines
28 KiB
Python

"""Support for MQTT JSON lights."""
from __future__ import annotations
from collections.abc import Callable
from contextlib import suppress
import logging
from typing import TYPE_CHECKING, Any, cast
import voluptuous as vol
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_MODE,
ATTR_COLOR_TEMP,
ATTR_EFFECT,
ATTR_FLASH,
ATTR_HS_COLOR,
ATTR_RGB_COLOR,
ATTR_RGBW_COLOR,
ATTR_RGBWW_COLOR,
ATTR_TRANSITION,
ATTR_WHITE,
ATTR_XY_COLOR,
DOMAIN as LIGHT_DOMAIN,
ENTITY_ID_FORMAT,
FLASH_LONG,
FLASH_SHORT,
VALID_COLOR_MODES,
ColorMode,
LightEntity,
LightEntityFeature,
brightness_supported,
color_supported,
filter_supported_color_modes,
valid_supported_color_modes,
)
from homeassistant.const import (
CONF_BRIGHTNESS,
CONF_COLOR_TEMP,
CONF_EFFECT,
CONF_HS,
CONF_NAME,
CONF_OPTIMISTIC,
CONF_RGB,
CONF_XY,
STATE_ON,
)
from homeassistant.core import async_get_hass, callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.json import json_dumps
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType, VolSchemaType
import homeassistant.util.color as color_util
from homeassistant.util.json import json_loads_object
from homeassistant.util.yaml import dump as yaml_dump
from .. import subscription
from ..config import DEFAULT_QOS, DEFAULT_RETAIN, MQTT_RW_SCHEMA
from ..const import (
CONF_COMMAND_TOPIC,
CONF_QOS,
CONF_RETAIN,
CONF_STATE_TOPIC,
DOMAIN as MQTT_DOMAIN,
)
from ..entity import MqttEntity
from ..models import ReceiveMessage
from ..schemas import MQTT_ENTITY_COMMON_SCHEMA
from ..util import valid_subscribe_topic
from .schema import MQTT_LIGHT_SCHEMA_SCHEMA
from .schema_basic import (
CONF_BRIGHTNESS_SCALE,
CONF_WHITE_SCALE,
MQTT_LIGHT_ATTRIBUTES_BLOCKED,
)
_LOGGER = logging.getLogger(__name__)
DOMAIN = "mqtt_json"
DEFAULT_BRIGHTNESS = False
DEFAULT_COLOR_MODE = False
DEFAULT_COLOR_TEMP = False
DEFAULT_EFFECT = False
DEFAULT_FLASH_TIME_LONG = 10
DEFAULT_FLASH_TIME_SHORT = 2
DEFAULT_NAME = "MQTT JSON Light"
DEFAULT_RGB = False
DEFAULT_XY = False
DEFAULT_HS = False
DEFAULT_BRIGHTNESS_SCALE = 255
DEFAULT_WHITE_SCALE = 255
CONF_COLOR_MODE = "color_mode"
CONF_SUPPORTED_COLOR_MODES = "supported_color_modes"
CONF_EFFECT_LIST = "effect_list"
CONF_FLASH_TIME_LONG = "flash_time_long"
CONF_FLASH_TIME_SHORT = "flash_time_short"
CONF_MAX_MIREDS = "max_mireds"
CONF_MIN_MIREDS = "min_mireds"
def valid_color_configuration(
setup_from_yaml: bool,
) -> Callable[[dict[str, Any]], dict[str, Any]]:
"""Test color_mode is not combined with deprecated config."""
def _valid_color_configuration(config: ConfigType) -> ConfigType:
deprecated = {CONF_COLOR_TEMP, CONF_HS, CONF_RGB, CONF_XY}
deprecated_flags_used = any(config.get(key) for key in deprecated)
if config.get(CONF_SUPPORTED_COLOR_MODES):
if deprecated_flags_used:
raise vol.Invalid(
"supported_color_modes must not "
f"be combined with any of {deprecated}"
)
elif deprecated_flags_used:
deprecated_flags = ", ".join(key for key in deprecated if key in config)
_LOGGER.warning(
"Deprecated flags [%s] used in MQTT JSON light config "
"for handling color mode, please use `supported_color_modes` instead. "
"Got: %s. This will stop working in Home Assistant Core 2025.3",
deprecated_flags,
config,
)
if not setup_from_yaml:
return config
issue_id = hex(hash(frozenset(config)))
yaml_config_str = yaml_dump(config)
learn_more_url = (
"https://www.home-assistant.io/integrations/"
f"{LIGHT_DOMAIN}.mqtt/#json-schema"
)
hass = async_get_hass()
async_create_issue(
hass,
MQTT_DOMAIN,
issue_id,
issue_domain=LIGHT_DOMAIN,
is_fixable=False,
severity=IssueSeverity.WARNING,
learn_more_url=learn_more_url,
translation_placeholders={
"deprecated_flags": deprecated_flags,
"config": yaml_config_str,
},
translation_key="deprecated_color_handling",
)
if CONF_COLOR_MODE in config:
_LOGGER.warning(
"Deprecated flag `color_mode` used in MQTT JSON light config "
", the `color_mode` flag is not used anymore and should be removed. "
"Got: %s. This will stop working in Home Assistant Core 2025.3",
config,
)
if not setup_from_yaml:
return config
issue_id = hex(hash(frozenset(config)))
yaml_config_str = yaml_dump(config)
learn_more_url = (
"https://www.home-assistant.io/integrations/"
f"{LIGHT_DOMAIN}.mqtt/#json-schema"
)
hass = async_get_hass()
async_create_issue(
hass,
MQTT_DOMAIN,
issue_id,
breaks_in_ha_version="2025.3.0",
issue_domain=LIGHT_DOMAIN,
is_fixable=False,
severity=IssueSeverity.WARNING,
learn_more_url=learn_more_url,
translation_placeholders={
"config": yaml_config_str,
},
translation_key="deprecated_color_mode_flag",
)
return config
return _valid_color_configuration
_PLATFORM_SCHEMA_BASE = (
MQTT_RW_SCHEMA.extend(
{
vol.Optional(CONF_BRIGHTNESS, default=DEFAULT_BRIGHTNESS): cv.boolean,
vol.Optional(
CONF_BRIGHTNESS_SCALE, default=DEFAULT_BRIGHTNESS_SCALE
): vol.All(vol.Coerce(int), vol.Range(min=1)),
# CONF_COLOR_MODE was deprecated with HA Core 2024.4 and will be
# removed with HA Core 2025.3
vol.Optional(CONF_COLOR_MODE): cv.boolean,
# CONF_COLOR_TEMP was deprecated with HA Core 2024.4 and will be
# removed with HA Core 2025.3
vol.Optional(CONF_COLOR_TEMP, default=DEFAULT_COLOR_TEMP): cv.boolean,
vol.Optional(CONF_EFFECT, default=DEFAULT_EFFECT): cv.boolean,
vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(
CONF_FLASH_TIME_LONG, default=DEFAULT_FLASH_TIME_LONG
): cv.positive_int,
vol.Optional(
CONF_FLASH_TIME_SHORT, default=DEFAULT_FLASH_TIME_SHORT
): cv.positive_int,
# CONF_HS was deprecated with HA Core 2024.4 and will be
# removed with HA Core 2025.3
vol.Optional(CONF_HS, default=DEFAULT_HS): cv.boolean,
vol.Optional(CONF_MAX_MIREDS): cv.positive_int,
vol.Optional(CONF_MIN_MIREDS): cv.positive_int,
vol.Optional(CONF_NAME): vol.Any(cv.string, None),
vol.Optional(CONF_QOS, default=DEFAULT_QOS): vol.All(
vol.Coerce(int), vol.In([0, 1, 2])
),
vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
# CONF_RGB was deprecated with HA Core 2024.4 and will be
# removed with HA Core 2025.3
vol.Optional(CONF_RGB, default=DEFAULT_RGB): cv.boolean,
vol.Optional(CONF_STATE_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_SUPPORTED_COLOR_MODES): vol.All(
cv.ensure_list,
[vol.In(VALID_COLOR_MODES)],
vol.Unique(),
valid_supported_color_modes,
),
vol.Optional(CONF_WHITE_SCALE, default=DEFAULT_WHITE_SCALE): vol.All(
vol.Coerce(int), vol.Range(min=1)
),
# CONF_XY was deprecated with HA Core 2024.4 and will be
# removed with HA Core 2025.3
vol.Optional(CONF_XY, default=DEFAULT_XY): cv.boolean,
},
)
.extend(MQTT_ENTITY_COMMON_SCHEMA.schema)
.extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema)
)
DISCOVERY_SCHEMA_JSON = vol.All(
valid_color_configuration(False),
_PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA),
)
PLATFORM_SCHEMA_MODERN_JSON = vol.All(
valid_color_configuration(True),
_PLATFORM_SCHEMA_BASE,
)
class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
"""Representation of a MQTT JSON light."""
_default_name = DEFAULT_NAME
_entity_id_format = ENTITY_ID_FORMAT
_attributes_extra_blocked = MQTT_LIGHT_ATTRIBUTES_BLOCKED
_fixed_color_mode: ColorMode | str | None = None
_flash_times: dict[str, int | None]
_topic: dict[str, str | None]
_optimistic: bool
_deprecated_color_handling: bool = False
@staticmethod
def config_schema() -> VolSchemaType:
"""Return the config schema."""
return DISCOVERY_SCHEMA_JSON
def _setup_from_config(self, config: ConfigType) -> None:
"""(Re)Setup the entity."""
self._attr_max_mireds = config.get(CONF_MAX_MIREDS, super().max_mireds)
self._attr_min_mireds = config.get(CONF_MIN_MIREDS, super().min_mireds)
self._attr_effect_list = config.get(CONF_EFFECT_LIST)
self._topic = {
key: config.get(key) for key in (CONF_STATE_TOPIC, CONF_COMMAND_TOPIC)
}
optimistic: bool = config[CONF_OPTIMISTIC]
self._optimistic = optimistic or self._topic[CONF_STATE_TOPIC] is None
self._attr_assumed_state = bool(self._optimistic)
self._flash_times = {
key: config.get(key)
for key in (CONF_FLASH_TIME_SHORT, CONF_FLASH_TIME_LONG)
}
self._attr_supported_features = (
LightEntityFeature.TRANSITION | LightEntityFeature.FLASH
)
self._attr_supported_features |= (
config[CONF_EFFECT] and LightEntityFeature.EFFECT
)
if supported_color_modes := self._config.get(CONF_SUPPORTED_COLOR_MODES):
self._attr_supported_color_modes = supported_color_modes
if self.supported_color_modes and len(self.supported_color_modes) == 1:
self._attr_color_mode = next(iter(self.supported_color_modes))
else:
self._attr_color_mode = ColorMode.UNKNOWN
else:
self._deprecated_color_handling = True
color_modes = {ColorMode.ONOFF}
if config[CONF_BRIGHTNESS]:
color_modes.add(ColorMode.BRIGHTNESS)
if config[CONF_COLOR_TEMP]:
color_modes.add(ColorMode.COLOR_TEMP)
if config[CONF_HS] or config[CONF_RGB] or config[CONF_XY]:
color_modes.add(ColorMode.HS)
self._attr_supported_color_modes = filter_supported_color_modes(color_modes)
if self.supported_color_modes and len(self.supported_color_modes) == 1:
self._fixed_color_mode = next(iter(self.supported_color_modes))
def _update_color(self, values: dict[str, Any]) -> None:
if self._deprecated_color_handling:
# Deprecated color handling
try:
red = int(values["color"]["r"])
green = int(values["color"]["g"])
blue = int(values["color"]["b"])
self._attr_hs_color = color_util.color_RGB_to_hs(red, green, blue)
except KeyError:
pass
except ValueError:
_LOGGER.warning(
"Invalid RGB color value '%s' received for entity %s",
values,
self.entity_id,
)
return
try:
x_color = float(values["color"]["x"])
y_color = float(values["color"]["y"])
self._attr_hs_color = color_util.color_xy_to_hs(x_color, y_color)
except KeyError:
pass
except ValueError:
_LOGGER.warning(
"Invalid XY color value '%s' received for entity %s",
values,
self.entity_id,
)
return
try:
hue = float(values["color"]["h"])
saturation = float(values["color"]["s"])
self._attr_hs_color = (hue, saturation)
except KeyError:
pass
except ValueError:
_LOGGER.warning(
"Invalid HS color value '%s' received for entity %s",
values,
self.entity_id,
)
return
else:
color_mode: str = values["color_mode"]
if not self._supports_color_mode(color_mode):
_LOGGER.warning(
"Invalid color mode '%s' received for entity %s",
color_mode,
self.entity_id,
)
return
try:
if color_mode == ColorMode.COLOR_TEMP:
self._attr_color_temp = int(values["color_temp"])
self._attr_color_mode = ColorMode.COLOR_TEMP
elif color_mode == ColorMode.HS:
hue = float(values["color"]["h"])
saturation = float(values["color"]["s"])
self._attr_color_mode = ColorMode.HS
self._attr_hs_color = (hue, saturation)
elif color_mode == ColorMode.RGB:
r = int(values["color"]["r"])
g = int(values["color"]["g"])
b = int(values["color"]["b"])
self._attr_color_mode = ColorMode.RGB
self._attr_rgb_color = (r, g, b)
elif color_mode == ColorMode.RGBW:
r = int(values["color"]["r"])
g = int(values["color"]["g"])
b = int(values["color"]["b"])
w = int(values["color"]["w"])
self._attr_color_mode = ColorMode.RGBW
self._attr_rgbw_color = (r, g, b, w)
elif color_mode == ColorMode.RGBWW:
r = int(values["color"]["r"])
g = int(values["color"]["g"])
b = int(values["color"]["b"])
c = int(values["color"]["c"])
w = int(values["color"]["w"])
self._attr_color_mode = ColorMode.RGBWW
self._attr_rgbww_color = (r, g, b, c, w)
elif color_mode == ColorMode.WHITE:
self._attr_color_mode = ColorMode.WHITE
elif color_mode == ColorMode.XY:
x = float(values["color"]["x"])
y = float(values["color"]["y"])
self._attr_color_mode = ColorMode.XY
self._attr_xy_color = (x, y)
except (KeyError, ValueError):
_LOGGER.warning(
"Invalid or incomplete color value '%s' received for entity %s",
values,
self.entity_id,
)
@callback
def _state_received(self, msg: ReceiveMessage) -> None:
"""Handle new MQTT messages."""
values = json_loads_object(msg.payload)
if values["state"] == "ON":
self._attr_is_on = True
elif values["state"] == "OFF":
self._attr_is_on = False
elif values["state"] is None:
self._attr_is_on = None
if (
self._deprecated_color_handling
and color_supported(self.supported_color_modes)
and "color" in values
):
# Deprecated color handling
if values["color"] is None:
self._attr_hs_color = None
else:
self._update_color(values)
if not self._deprecated_color_handling and "color_mode" in values:
self._update_color(values)
if brightness_supported(self.supported_color_modes):
try:
if brightness := values["brightness"]:
if TYPE_CHECKING:
assert isinstance(brightness, float)
self._attr_brightness = color_util.value_to_brightness(
(1, self._config[CONF_BRIGHTNESS_SCALE]), brightness
)
else:
_LOGGER.debug(
"Ignoring zero brightness value for entity %s",
self.entity_id,
)
except KeyError:
pass
except (TypeError, ValueError):
_LOGGER.warning(
"Invalid brightness value '%s' received for entity %s",
values["brightness"],
self.entity_id,
)
if (
self._deprecated_color_handling
and self.supported_color_modes
and ColorMode.COLOR_TEMP in self.supported_color_modes
):
# Deprecated color handling
try:
if values["color_temp"] is None:
self._attr_color_temp = None
else:
self._attr_color_temp = int(values["color_temp"]) # type: ignore[arg-type]
except KeyError:
pass
except ValueError:
_LOGGER.warning(
"Invalid color temp value '%s' received for entity %s",
values["color_temp"],
self.entity_id,
)
# Allow to switch back to color_temp
if "color" not in values:
self._attr_hs_color = None
if self.supported_features and LightEntityFeature.EFFECT:
with suppress(KeyError):
self._attr_effect = cast(str, values["effect"])
@callback
def _prepare_subscribe_topics(self) -> None:
"""(Re)Subscribe to topics."""
self.add_subscription(
CONF_STATE_TOPIC,
self._state_received,
{
"_attr_brightness",
"_attr_color_temp",
"_attr_effect",
"_attr_hs_color",
"_attr_is_on",
"_attr_rgb_color",
"_attr_rgbw_color",
"_attr_rgbww_color",
"_attr_xy_color",
"color_mode",
},
)
async def _subscribe_topics(self) -> None:
"""(Re)Subscribe to topics."""
subscription.async_subscribe_topics_internal(self.hass, self._sub_state)
last_state = await self.async_get_last_state()
if self._optimistic and last_state:
self._attr_is_on = last_state.state == STATE_ON
last_attributes = last_state.attributes
self._attr_brightness = last_attributes.get(
ATTR_BRIGHTNESS, self.brightness
)
self._attr_color_mode = last_attributes.get(
ATTR_COLOR_MODE, self.color_mode
)
self._attr_color_temp = last_attributes.get(
ATTR_COLOR_TEMP, self.color_temp
)
self._attr_effect = last_attributes.get(ATTR_EFFECT, self.effect)
self._attr_hs_color = last_attributes.get(ATTR_HS_COLOR, self.hs_color)
self._attr_rgb_color = last_attributes.get(ATTR_RGB_COLOR, self.rgb_color)
self._attr_rgbw_color = last_attributes.get(
ATTR_RGBW_COLOR, self.rgbw_color
)
self._attr_rgbww_color = last_attributes.get(
ATTR_RGBWW_COLOR, self.rgbww_color
)
self._attr_xy_color = last_attributes.get(ATTR_XY_COLOR, self.xy_color)
@property
def color_mode(self) -> ColorMode | str | None:
"""Return current color mode."""
if not self._deprecated_color_handling:
return self._attr_color_mode
if self._fixed_color_mode:
# Legacy light with support for a single color mode
return self._fixed_color_mode
# Legacy light with support for ct + hs, prioritize hs
if self.hs_color is not None:
return ColorMode.HS
return ColorMode.COLOR_TEMP
def _set_flash_and_transition(self, message: dict[str, Any], **kwargs: Any) -> None:
if ATTR_TRANSITION in kwargs:
message["transition"] = kwargs[ATTR_TRANSITION]
if ATTR_FLASH in kwargs:
flash: str = kwargs[ATTR_FLASH]
if flash == FLASH_LONG:
message["flash"] = self._flash_times[CONF_FLASH_TIME_LONG]
elif flash == FLASH_SHORT:
message["flash"] = self._flash_times[CONF_FLASH_TIME_SHORT]
def _scale_rgbxx(self, rgbxx: tuple[int, ...], kwargs: Any) -> tuple[int, ...]:
# If brightness is supported, we don't want to scale the
# RGBxx values given using the brightness and
# we pop the brightness, to omit it from the payload
brightness: int
if self._config[CONF_BRIGHTNESS]:
brightness = 255
else:
brightness = kwargs.pop(ATTR_BRIGHTNESS, 255)
return tuple(round(i / 255 * brightness) for i in rgbxx)
def _supports_color_mode(self, color_mode: ColorMode | str) -> bool:
"""Return True if the light natively supports a color mode."""
return (
not self._deprecated_color_handling
and self.supported_color_modes is not None
and color_mode in self.supported_color_modes
)
async def async_turn_on(self, **kwargs: Any) -> None: # noqa: C901
"""Turn the device on.
This method is a coroutine.
"""
brightness: int
should_update = False
hs_color: tuple[float, float]
message: dict[str, Any] = {"state": "ON"}
rgb: tuple[int, ...]
rgbw: tuple[int, ...]
rgbcw: tuple[int, ...]
xy_color: tuple[float, float]
if ATTR_HS_COLOR in kwargs and (
self._config[CONF_HS] or self._config[CONF_RGB] or self._config[CONF_XY]
):
# Legacy color handling
hs_color = kwargs[ATTR_HS_COLOR]
message["color"] = {}
if self._config[CONF_RGB]:
# If brightness is supported, we don't want to scale the
# RGB values given using the brightness.
if self._config[CONF_BRIGHTNESS]:
brightness = 255
else:
# We pop the brightness, to omit it from the payload
brightness = kwargs.pop(ATTR_BRIGHTNESS, 255)
rgb = color_util.color_hsv_to_RGB(
hs_color[0], hs_color[1], brightness / 255 * 100
)
message["color"]["r"] = rgb[0]
message["color"]["g"] = rgb[1]
message["color"]["b"] = rgb[2]
if self._config[CONF_XY]:
xy_color = color_util.color_hs_to_xy(*kwargs[ATTR_HS_COLOR])
message["color"]["x"] = xy_color[0]
message["color"]["y"] = xy_color[1]
if self._config[CONF_HS]:
message["color"]["h"] = hs_color[0]
message["color"]["s"] = hs_color[1]
if self._optimistic:
self._attr_color_temp = None
self._attr_hs_color = kwargs[ATTR_HS_COLOR]
should_update = True
if ATTR_HS_COLOR in kwargs and self._supports_color_mode(ColorMode.HS):
hs_color = kwargs[ATTR_HS_COLOR]
message["color"] = {"h": hs_color[0], "s": hs_color[1]}
if self._optimistic:
self._attr_color_mode = ColorMode.HS
self._attr_hs_color = hs_color
should_update = True
if ATTR_RGB_COLOR in kwargs and self._supports_color_mode(ColorMode.RGB):
rgb = self._scale_rgbxx(kwargs[ATTR_RGB_COLOR], kwargs)
message["color"] = {"r": rgb[0], "g": rgb[1], "b": rgb[2]}
if self._optimistic:
self._attr_color_mode = ColorMode.RGB
self._attr_rgb_color = cast(tuple[int, int, int], rgb)
should_update = True
if ATTR_RGBW_COLOR in kwargs and self._supports_color_mode(ColorMode.RGBW):
rgbw = self._scale_rgbxx(kwargs[ATTR_RGBW_COLOR], kwargs)
message["color"] = {"r": rgbw[0], "g": rgbw[1], "b": rgbw[2], "w": rgbw[3]}
if self._optimistic:
self._attr_color_mode = ColorMode.RGBW
self._attr_rgbw_color = cast(tuple[int, int, int, int], rgbw)
should_update = True
if ATTR_RGBWW_COLOR in kwargs and self._supports_color_mode(ColorMode.RGBWW):
rgbcw = self._scale_rgbxx(kwargs[ATTR_RGBWW_COLOR], kwargs)
message["color"] = {
"r": rgbcw[0],
"g": rgbcw[1],
"b": rgbcw[2],
"c": rgbcw[3],
"w": rgbcw[4],
}
if self._optimistic:
self._attr_color_mode = ColorMode.RGBWW
self._attr_rgbww_color = cast(tuple[int, int, int, int, int], rgbcw)
should_update = True
if ATTR_XY_COLOR in kwargs and self._supports_color_mode(ColorMode.XY):
xy_color = kwargs[ATTR_XY_COLOR]
message["color"] = {"x": xy_color[0], "y": xy_color[1]}
if self._optimistic:
self._attr_color_mode = ColorMode.XY
self._attr_xy_color = xy_color
should_update = True
self._set_flash_and_transition(message, **kwargs)
if ATTR_BRIGHTNESS in kwargs and brightness_supported(
self.supported_color_modes
):
device_brightness = color_util.brightness_to_value(
(1, self._config[CONF_BRIGHTNESS_SCALE]),
kwargs[ATTR_BRIGHTNESS],
)
# Make sure the brightness is not rounded down to 0
device_brightness = max(round(device_brightness), 1)
message["brightness"] = device_brightness
if self._optimistic:
self._attr_brightness = kwargs[ATTR_BRIGHTNESS]
should_update = True
if ATTR_COLOR_TEMP in kwargs:
message["color_temp"] = int(kwargs[ATTR_COLOR_TEMP])
if self._optimistic:
self._attr_color_mode = ColorMode.COLOR_TEMP
self._attr_color_temp = kwargs[ATTR_COLOR_TEMP]
self._attr_hs_color = None
should_update = True
if ATTR_EFFECT in kwargs:
message["effect"] = kwargs[ATTR_EFFECT]
if self._optimistic:
self._attr_effect = kwargs[ATTR_EFFECT]
should_update = True
if ATTR_WHITE in kwargs and self._supports_color_mode(ColorMode.WHITE):
white_normalized = kwargs[ATTR_WHITE] / DEFAULT_WHITE_SCALE
white_scale = self._config[CONF_WHITE_SCALE]
device_white_level = min(round(white_normalized * white_scale), white_scale)
# Make sure the brightness is not rounded down to 0
device_white_level = max(device_white_level, 1)
message["white"] = device_white_level
if self._optimistic:
self._attr_color_mode = ColorMode.WHITE
self._attr_brightness = kwargs[ATTR_WHITE]
should_update = True
await self.async_publish_with_config(
str(self._topic[CONF_COMMAND_TOPIC]), json_dumps(message)
)
if self._optimistic:
# Optimistically assume that the light has changed state.
self._attr_is_on = True
should_update = True
if should_update:
self.async_write_ha_state()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the device off.
This method is a coroutine.
"""
message: dict[str, Any] = {"state": "OFF"}
self._set_flash_and_transition(message, **kwargs)
await self.async_publish_with_config(
str(self._topic[CONF_COMMAND_TOPIC]), json_dumps(message)
)
if self._optimistic:
# Optimistically assume that the light has changed state.
self._attr_is_on = False
self.async_write_ha_state()