mirror of
https://github.com/home-assistant/core.git
synced 2026-06-02 05:34:15 +01:00
6fbdcac323
* Check if changing TO a color mode Changing brightness alone does not change work mode, but changing brightness with a color value will keep the light in white mode. By verifying the new state has color or not, rather than the existing state being in color work mode, the light will change to color correctly. Tuya interprets HSV as including the brightness in the (v) value (which is generally what that's used for when setting HSV values). The brightness value given by Home Assistant is still used in this case. * Fix brightness-only turning colour mode to white This will take into account the case where brightness is the only parameter for both the case of colour mode and white mode. Tests passed after this change: * Brightness only (colour mode) ✅ * Brightness only (white mode) ✅ * Colour only (colour mode) ✅ * Colour only (white mode) ✅ * Colour temp only (colour mode) ✅ * Colour temp only (white mode) ✅ * Colour + brightness (colour mode) ✅ * Colour + brightness (white mode) ✅ * Colour temp + brightness (colour mode) ✅ * Colour temp + brightness (white mode) ✅ * Fix code formatting
697 lines
24 KiB
Python
697 lines
24 KiB
Python
"""Support for the Tuya lights."""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
import json
|
|
from typing import Any, cast
|
|
|
|
from tuya_iot import TuyaDevice, TuyaDeviceManager
|
|
|
|
from homeassistant.components.light import (
|
|
ATTR_BRIGHTNESS,
|
|
ATTR_COLOR_TEMP,
|
|
ATTR_HS_COLOR,
|
|
ColorMode,
|
|
LightEntity,
|
|
LightEntityDescription,
|
|
)
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.const import EntityCategory
|
|
from homeassistant.core import HomeAssistant, callback
|
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
|
|
from . import HomeAssistantTuyaData
|
|
from .base import IntegerTypeData, TuyaEntity
|
|
from .const import DOMAIN, TUYA_DISCOVERY_NEW, DPCode, DPType, WorkMode
|
|
from .util import remap_value
|
|
|
|
|
|
@dataclass
|
|
class ColorTypeData:
|
|
"""Color Type Data."""
|
|
|
|
h_type: IntegerTypeData
|
|
s_type: IntegerTypeData
|
|
v_type: IntegerTypeData
|
|
|
|
|
|
DEFAULT_COLOR_TYPE_DATA = ColorTypeData(
|
|
h_type=IntegerTypeData(DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=360, step=1),
|
|
s_type=IntegerTypeData(DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=255, step=1),
|
|
v_type=IntegerTypeData(DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=255, step=1),
|
|
)
|
|
|
|
DEFAULT_COLOR_TYPE_DATA_V2 = ColorTypeData(
|
|
h_type=IntegerTypeData(DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=360, step=1),
|
|
s_type=IntegerTypeData(DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=1000, step=1),
|
|
v_type=IntegerTypeData(DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=1000, step=1),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class TuyaLightEntityDescription(LightEntityDescription):
|
|
"""Describe an Tuya light entity."""
|
|
|
|
brightness_max: DPCode | None = None
|
|
brightness_min: DPCode | None = None
|
|
brightness: DPCode | tuple[DPCode, ...] | None = None
|
|
color_data: DPCode | tuple[DPCode, ...] | None = None
|
|
color_mode: DPCode | None = None
|
|
color_temp: DPCode | tuple[DPCode, ...] | None = None
|
|
default_color_type: ColorTypeData = field(
|
|
default_factory=lambda: DEFAULT_COLOR_TYPE_DATA
|
|
)
|
|
|
|
|
|
LIGHTS: dict[str, tuple[TuyaLightEntityDescription, ...]] = {
|
|
# Curtain Switch
|
|
# https://developer.tuya.com/en/docs/iot/category-clkg?id=Kaiuz0gitil39
|
|
"clkg": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_BACKLIGHT,
|
|
name="Backlight",
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
),
|
|
# String Lights
|
|
# https://developer.tuya.com/en/docs/iot/dc?id=Kaof7taxmvadu
|
|
"dc": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_LED,
|
|
color_mode=DPCode.WORK_MODE,
|
|
brightness=DPCode.BRIGHT_VALUE,
|
|
color_temp=DPCode.TEMP_VALUE,
|
|
color_data=DPCode.COLOUR_DATA,
|
|
),
|
|
),
|
|
# Strip Lights
|
|
# https://developer.tuya.com/en/docs/iot/dd?id=Kaof804aibg2l
|
|
"dd": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_LED,
|
|
color_mode=DPCode.WORK_MODE,
|
|
brightness=DPCode.BRIGHT_VALUE,
|
|
color_temp=DPCode.TEMP_VALUE,
|
|
color_data=DPCode.COLOUR_DATA,
|
|
default_color_type=DEFAULT_COLOR_TYPE_DATA_V2,
|
|
),
|
|
),
|
|
# Light
|
|
# https://developer.tuya.com/en/docs/iot/categorydj?id=Kaiuyzy3eheyy
|
|
"dj": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_LED,
|
|
color_mode=DPCode.WORK_MODE,
|
|
brightness=(DPCode.BRIGHT_VALUE_V2, DPCode.BRIGHT_VALUE),
|
|
color_temp=(DPCode.TEMP_VALUE_V2, DPCode.TEMP_VALUE),
|
|
color_data=(DPCode.COLOUR_DATA_V2, DPCode.COLOUR_DATA),
|
|
),
|
|
# Not documented
|
|
# Based on multiple reports: manufacturer customized Dimmer 2 switches
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_1,
|
|
name="Light",
|
|
brightness=DPCode.BRIGHT_VALUE_1,
|
|
),
|
|
),
|
|
# Ceiling Fan Light
|
|
# https://developer.tuya.com/en/docs/iot/fsd?id=Kaof8eiei4c2v
|
|
"fsd": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_LED,
|
|
color_mode=DPCode.WORK_MODE,
|
|
brightness=DPCode.BRIGHT_VALUE,
|
|
color_temp=DPCode.TEMP_VALUE,
|
|
color_data=DPCode.COLOUR_DATA,
|
|
),
|
|
# Some ceiling fan lights use LIGHT for DPCode instead of SWITCH_LED
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.LIGHT,
|
|
),
|
|
),
|
|
# Ambient Light
|
|
# https://developer.tuya.com/en/docs/iot/ambient-light?id=Kaiuz06amhe6g
|
|
"fwd": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_LED,
|
|
color_mode=DPCode.WORK_MODE,
|
|
brightness=DPCode.BRIGHT_VALUE,
|
|
color_temp=DPCode.TEMP_VALUE,
|
|
color_data=DPCode.COLOUR_DATA,
|
|
),
|
|
),
|
|
# Motion Sensor Light
|
|
# https://developer.tuya.com/en/docs/iot/gyd?id=Kaof8a8hycfmy
|
|
"gyd": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_LED,
|
|
color_mode=DPCode.WORK_MODE,
|
|
brightness=DPCode.BRIGHT_VALUE,
|
|
color_temp=DPCode.TEMP_VALUE,
|
|
color_data=DPCode.COLOUR_DATA,
|
|
),
|
|
),
|
|
# Humidifier Light
|
|
# https://developer.tuya.com/en/docs/iot/categoryjsq?id=Kaiuz1smr440b
|
|
"jsq": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_LED,
|
|
color_mode=DPCode.WORK_MODE,
|
|
brightness=DPCode.BRIGHT_VALUE,
|
|
color_data=DPCode.COLOUR_DATA_HSV,
|
|
),
|
|
),
|
|
# Switch
|
|
# https://developer.tuya.com/en/docs/iot/s?id=K9gf7o5prgf7s
|
|
"kg": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_BACKLIGHT,
|
|
name="Backlight",
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
),
|
|
# Air conditioner
|
|
# https://developer.tuya.com/en/docs/iot/categorykt?id=Kaiuz0z71ov2n
|
|
"kt": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.LIGHT,
|
|
name="Backlight",
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
),
|
|
# Unknown light product
|
|
# Found as VECINO RGBW as provided by diagnostics
|
|
# Not documented
|
|
"mbd": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_LED,
|
|
color_mode=DPCode.WORK_MODE,
|
|
brightness=DPCode.BRIGHT_VALUE,
|
|
color_data=DPCode.COLOUR_DATA,
|
|
),
|
|
),
|
|
# Unknown product with light capabilities
|
|
# Fond in some diffusers, plugs and PIR flood lights
|
|
# Not documented
|
|
"qjdcz": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_LED,
|
|
color_mode=DPCode.WORK_MODE,
|
|
brightness=DPCode.BRIGHT_VALUE,
|
|
color_data=DPCode.COLOUR_DATA,
|
|
),
|
|
),
|
|
# Heater
|
|
# https://developer.tuya.com/en/docs/iot/categoryqn?id=Kaiuz18kih0sm
|
|
"qn": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.LIGHT,
|
|
name="Backlight",
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
),
|
|
# Smart Camera
|
|
# https://developer.tuya.com/en/docs/iot/categorysp?id=Kaiuz35leyo12
|
|
"sp": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.FLOODLIGHT_SWITCH,
|
|
brightness=DPCode.FLOODLIGHT_LIGHTNESS,
|
|
name="Floodlight",
|
|
),
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.BASIC_INDICATOR,
|
|
name="Indicator light",
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
),
|
|
# Dimmer Switch
|
|
# https://developer.tuya.com/en/docs/iot/categorytgkg?id=Kaiuz0ktx7m0o
|
|
"tgkg": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_LED_1,
|
|
name="Light",
|
|
brightness=DPCode.BRIGHT_VALUE_1,
|
|
brightness_max=DPCode.BRIGHTNESS_MAX_1,
|
|
brightness_min=DPCode.BRIGHTNESS_MIN_1,
|
|
),
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_LED_2,
|
|
name="Light 2",
|
|
brightness=DPCode.BRIGHT_VALUE_2,
|
|
brightness_max=DPCode.BRIGHTNESS_MAX_2,
|
|
brightness_min=DPCode.BRIGHTNESS_MIN_2,
|
|
),
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_LED_3,
|
|
name="Light 3",
|
|
brightness=DPCode.BRIGHT_VALUE_3,
|
|
brightness_max=DPCode.BRIGHTNESS_MAX_3,
|
|
brightness_min=DPCode.BRIGHTNESS_MIN_3,
|
|
),
|
|
),
|
|
# Dimmer
|
|
# https://developer.tuya.com/en/docs/iot/tgq?id=Kaof8ke9il4k4
|
|
"tgq": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_LED,
|
|
name="Light",
|
|
brightness=(DPCode.BRIGHT_VALUE_V2, DPCode.BRIGHT_VALUE),
|
|
brightness_max=DPCode.BRIGHTNESS_MAX_1,
|
|
brightness_min=DPCode.BRIGHTNESS_MIN_1,
|
|
),
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_LED_1,
|
|
name="Light",
|
|
brightness=DPCode.BRIGHT_VALUE_1,
|
|
),
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_LED_2,
|
|
name="Light 2",
|
|
brightness=DPCode.BRIGHT_VALUE_2,
|
|
),
|
|
),
|
|
# Wake Up Light II
|
|
# Not documented
|
|
"hxd": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_LED,
|
|
name="Light",
|
|
brightness=(DPCode.BRIGHT_VALUE_V2, DPCode.BRIGHT_VALUE),
|
|
brightness_max=DPCode.BRIGHTNESS_MAX_1,
|
|
brightness_min=DPCode.BRIGHTNESS_MIN_1,
|
|
),
|
|
),
|
|
# Solar Light
|
|
# https://developer.tuya.com/en/docs/iot/tynd?id=Kaof8j02e1t98
|
|
"tyndj": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_LED,
|
|
color_mode=DPCode.WORK_MODE,
|
|
brightness=DPCode.BRIGHT_VALUE,
|
|
color_temp=DPCode.TEMP_VALUE,
|
|
color_data=DPCode.COLOUR_DATA,
|
|
),
|
|
),
|
|
# Ceiling Light
|
|
# https://developer.tuya.com/en/docs/iot/ceiling-light?id=Kaiuz03xxfc4r
|
|
"xdd": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_LED,
|
|
color_mode=DPCode.WORK_MODE,
|
|
brightness=DPCode.BRIGHT_VALUE,
|
|
color_temp=DPCode.TEMP_VALUE,
|
|
color_data=DPCode.COLOUR_DATA,
|
|
),
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_NIGHT_LIGHT,
|
|
name="Night light",
|
|
),
|
|
),
|
|
# Remote Control
|
|
# https://developer.tuya.com/en/docs/iot/ykq?id=Kaof8ljn81aov
|
|
"ykq": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.SWITCH_CONTROLLER,
|
|
color_mode=DPCode.WORK_MODE,
|
|
brightness=DPCode.BRIGHT_CONTROLLER,
|
|
color_temp=DPCode.TEMP_CONTROLLER,
|
|
),
|
|
),
|
|
# Fan
|
|
# https://developer.tuya.com/en/docs/iot/categoryfs?id=Kaiuz1xweel1c
|
|
"fs": (
|
|
TuyaLightEntityDescription(
|
|
key=DPCode.LIGHT,
|
|
color_mode=DPCode.WORK_MODE,
|
|
brightness=DPCode.BRIGHT_VALUE,
|
|
color_temp=DPCode.TEMP_VALUE,
|
|
),
|
|
),
|
|
}
|
|
|
|
# Socket (duplicate of `kg`)
|
|
# https://developer.tuya.com/en/docs/iot/s?id=K9gf7o5prgf7s
|
|
LIGHTS["cz"] = LIGHTS["kg"]
|
|
|
|
# Power Socket (duplicate of `kg`)
|
|
# https://developer.tuya.com/en/docs/iot/s?id=K9gf7o5prgf7s
|
|
LIGHTS["pc"] = LIGHTS["kg"]
|
|
|
|
|
|
@dataclass
|
|
class ColorData:
|
|
"""Color Data."""
|
|
|
|
type_data: ColorTypeData
|
|
h_value: int
|
|
s_value: int
|
|
v_value: int
|
|
|
|
@property
|
|
def hs_color(self) -> tuple[float, float]:
|
|
"""Get the HS value from this color data."""
|
|
return (
|
|
self.type_data.h_type.remap_value_to(self.h_value, 0, 360),
|
|
self.type_data.s_type.remap_value_to(self.s_value, 0, 100),
|
|
)
|
|
|
|
@property
|
|
def brightness(self) -> int:
|
|
"""Get the brightness value from this color data."""
|
|
return round(self.type_data.v_type.remap_value_to(self.v_value, 0, 255))
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
|
) -> None:
|
|
"""Set up tuya light dynamically through tuya discovery."""
|
|
hass_data: HomeAssistantTuyaData = hass.data[DOMAIN][entry.entry_id]
|
|
|
|
@callback
|
|
def async_discover_device(device_ids: list[str]):
|
|
"""Discover and add a discovered tuya light."""
|
|
entities: list[TuyaLightEntity] = []
|
|
for device_id in device_ids:
|
|
device = hass_data.device_manager.device_map[device_id]
|
|
if descriptions := LIGHTS.get(device.category):
|
|
for description in descriptions:
|
|
if description.key in device.status:
|
|
entities.append(
|
|
TuyaLightEntity(
|
|
device, hass_data.device_manager, description
|
|
)
|
|
)
|
|
|
|
async_add_entities(entities)
|
|
|
|
async_discover_device([*hass_data.device_manager.device_map])
|
|
|
|
entry.async_on_unload(
|
|
async_dispatcher_connect(hass, TUYA_DISCOVERY_NEW, async_discover_device)
|
|
)
|
|
|
|
|
|
class TuyaLightEntity(TuyaEntity, LightEntity):
|
|
"""Tuya light device."""
|
|
|
|
entity_description: TuyaLightEntityDescription
|
|
|
|
_brightness_max: IntegerTypeData | None = None
|
|
_brightness_min: IntegerTypeData | None = None
|
|
_brightness: IntegerTypeData | None = None
|
|
_color_data_dpcode: DPCode | None = None
|
|
_color_data_type: ColorTypeData | None = None
|
|
_color_mode: DPCode | None = None
|
|
_color_temp: IntegerTypeData | None = None
|
|
|
|
def __init__(
|
|
self,
|
|
device: TuyaDevice,
|
|
device_manager: TuyaDeviceManager,
|
|
description: TuyaLightEntityDescription,
|
|
) -> None:
|
|
"""Init TuyaHaLight."""
|
|
super().__init__(device, device_manager)
|
|
self.entity_description = description
|
|
self._attr_unique_id = f"{super().unique_id}{description.key}"
|
|
self._attr_supported_color_modes: set[ColorMode] = set()
|
|
|
|
# Determine DPCodes
|
|
self._color_mode_dpcode = self.find_dpcode(
|
|
description.color_mode, prefer_function=True
|
|
)
|
|
|
|
if int_type := self.find_dpcode(
|
|
description.brightness, dptype=DPType.INTEGER, prefer_function=True
|
|
):
|
|
self._brightness = int_type
|
|
self._attr_supported_color_modes.add(ColorMode.BRIGHTNESS)
|
|
self._brightness_max = self.find_dpcode(
|
|
description.brightness_max, dptype=DPType.INTEGER
|
|
)
|
|
self._brightness_min = self.find_dpcode(
|
|
description.brightness_min, dptype=DPType.INTEGER
|
|
)
|
|
|
|
if int_type := self.find_dpcode(
|
|
description.color_temp, dptype=DPType.INTEGER, prefer_function=True
|
|
):
|
|
self._color_temp = int_type
|
|
self._attr_supported_color_modes.add(ColorMode.COLOR_TEMP)
|
|
|
|
if (
|
|
dpcode := self.find_dpcode(description.color_data, prefer_function=True)
|
|
) and self.get_dptype(dpcode) == DPType.JSON:
|
|
self._color_data_dpcode = dpcode
|
|
self._attr_supported_color_modes.add(ColorMode.HS)
|
|
if dpcode in self.device.function:
|
|
values = cast(str, self.device.function[dpcode].values)
|
|
else:
|
|
values = self.device.status_range[dpcode].values
|
|
|
|
# Fetch color data type information
|
|
if function_data := json.loads(values):
|
|
self._color_data_type = ColorTypeData(
|
|
h_type=IntegerTypeData(dpcode, **function_data["h"]),
|
|
s_type=IntegerTypeData(dpcode, **function_data["s"]),
|
|
v_type=IntegerTypeData(dpcode, **function_data["v"]),
|
|
)
|
|
else:
|
|
# If no type is found, use a default one
|
|
self._color_data_type = self.entity_description.default_color_type
|
|
if self._color_data_dpcode == DPCode.COLOUR_DATA_V2 or (
|
|
self._brightness and self._brightness.max > 255
|
|
):
|
|
self._color_data_type = DEFAULT_COLOR_TYPE_DATA_V2
|
|
|
|
if not self._attr_supported_color_modes:
|
|
self._attr_supported_color_modes = {ColorMode.ONOFF}
|
|
|
|
@property
|
|
def is_on(self) -> bool:
|
|
"""Return true if light is on."""
|
|
return self.device.status.get(self.entity_description.key, False)
|
|
|
|
def turn_on(self, **kwargs: Any) -> None:
|
|
"""Turn on or control the light."""
|
|
commands = [{"code": self.entity_description.key, "value": True}]
|
|
|
|
if self._color_temp and ATTR_COLOR_TEMP in kwargs:
|
|
if self._color_mode_dpcode:
|
|
commands += [
|
|
{
|
|
"code": self._color_mode_dpcode,
|
|
"value": WorkMode.WHITE,
|
|
},
|
|
]
|
|
|
|
commands += [
|
|
{
|
|
"code": self._color_temp.dpcode,
|
|
"value": round(
|
|
self._color_temp.remap_value_from(
|
|
kwargs[ATTR_COLOR_TEMP],
|
|
self.min_mireds,
|
|
self.max_mireds,
|
|
reverse=True,
|
|
)
|
|
),
|
|
},
|
|
]
|
|
|
|
if self._color_data_type and (
|
|
ATTR_HS_COLOR in kwargs
|
|
or (
|
|
ATTR_BRIGHTNESS in kwargs
|
|
and self.color_mode == ColorMode.HS
|
|
and ATTR_COLOR_TEMP not in kwargs
|
|
)
|
|
):
|
|
if self._color_mode_dpcode:
|
|
commands += [
|
|
{
|
|
"code": self._color_mode_dpcode,
|
|
"value": WorkMode.COLOUR,
|
|
},
|
|
]
|
|
|
|
if not (brightness := kwargs.get(ATTR_BRIGHTNESS)):
|
|
brightness = self.brightness or 0
|
|
|
|
if not (color := kwargs.get(ATTR_HS_COLOR)):
|
|
color = self.hs_color or (0, 0)
|
|
|
|
commands += [
|
|
{
|
|
"code": self._color_data_dpcode,
|
|
"value": json.dumps(
|
|
{
|
|
"h": round(
|
|
self._color_data_type.h_type.remap_value_from(
|
|
color[0], 0, 360
|
|
)
|
|
),
|
|
"s": round(
|
|
self._color_data_type.s_type.remap_value_from(
|
|
color[1], 0, 100
|
|
)
|
|
),
|
|
"v": round(
|
|
self._color_data_type.v_type.remap_value_from(
|
|
brightness
|
|
)
|
|
),
|
|
}
|
|
),
|
|
},
|
|
]
|
|
|
|
elif ATTR_BRIGHTNESS in kwargs and self._brightness:
|
|
brightness = kwargs[ATTR_BRIGHTNESS]
|
|
|
|
# If there is a min/max value, the brightness is actually limited.
|
|
# Meaning it is actually not on a 0-255 scale.
|
|
if (
|
|
self._brightness_max is not None
|
|
and self._brightness_min is not None
|
|
and (
|
|
brightness_max := self.device.status.get(
|
|
self._brightness_max.dpcode
|
|
)
|
|
)
|
|
is not None
|
|
and (
|
|
brightness_min := self.device.status.get(
|
|
self._brightness_min.dpcode
|
|
)
|
|
)
|
|
is not None
|
|
):
|
|
# Remap values onto our scale
|
|
brightness_max = self._brightness_max.remap_value_to(brightness_max)
|
|
brightness_min = self._brightness_min.remap_value_to(brightness_min)
|
|
|
|
# Remap the brightness value from their min-max to our 0-255 scale
|
|
brightness = remap_value(
|
|
brightness,
|
|
to_min=brightness_min,
|
|
to_max=brightness_max,
|
|
)
|
|
|
|
commands += [
|
|
{
|
|
"code": self._brightness.dpcode,
|
|
"value": round(self._brightness.remap_value_from(brightness)),
|
|
},
|
|
]
|
|
|
|
self._send_command(commands)
|
|
|
|
def turn_off(self, **kwargs: Any) -> None:
|
|
"""Instruct the light to turn off."""
|
|
self._send_command([{"code": self.entity_description.key, "value": False}])
|
|
|
|
@property
|
|
def brightness(self) -> int | None:
|
|
"""Return the brightness of the light."""
|
|
# If the light is currently in color mode, extract the brightness from the color data
|
|
if self.color_mode == ColorMode.HS and (color_data := self._get_color_data()):
|
|
return color_data.brightness
|
|
|
|
if not self._brightness:
|
|
return None
|
|
|
|
brightness = self.device.status.get(self._brightness.dpcode)
|
|
if brightness is None:
|
|
return None
|
|
|
|
# Remap value to our scale
|
|
brightness = self._brightness.remap_value_to(brightness)
|
|
|
|
# If there is a min/max value, the brightness is actually limited.
|
|
# Meaning it is actually not on a 0-255 scale.
|
|
if (
|
|
self._brightness_max is not None
|
|
and self._brightness_min is not None
|
|
and (brightness_max := self.device.status.get(self._brightness_max.dpcode))
|
|
is not None
|
|
and (brightness_min := self.device.status.get(self._brightness_min.dpcode))
|
|
is not None
|
|
):
|
|
# Remap values onto our scale
|
|
brightness_max = self._brightness_max.remap_value_to(brightness_max)
|
|
brightness_min = self._brightness_min.remap_value_to(brightness_min)
|
|
|
|
# Remap the brightness value from their min-max to our 0-255 scale
|
|
brightness = remap_value(
|
|
brightness,
|
|
from_min=brightness_min,
|
|
from_max=brightness_max,
|
|
)
|
|
|
|
return round(brightness)
|
|
|
|
@property
|
|
def color_temp(self) -> int | None:
|
|
"""Return the color_temp of the light."""
|
|
if not self._color_temp:
|
|
return None
|
|
|
|
temperature = self.device.status.get(self._color_temp.dpcode)
|
|
if temperature is None:
|
|
return None
|
|
|
|
return round(
|
|
self._color_temp.remap_value_to(
|
|
temperature, self.min_mireds, self.max_mireds, reverse=True
|
|
)
|
|
)
|
|
|
|
@property
|
|
def hs_color(self) -> tuple[float, float] | None:
|
|
"""Return the hs_color of the light."""
|
|
if self._color_data_dpcode is None or not (
|
|
color_data := self._get_color_data()
|
|
):
|
|
return None
|
|
return color_data.hs_color
|
|
|
|
@property
|
|
def color_mode(self) -> ColorMode:
|
|
"""Return the color_mode of the light."""
|
|
# We consider it to be in HS color mode, when work mode is anything
|
|
# else than "white".
|
|
if (
|
|
self._color_mode_dpcode
|
|
and self.device.status.get(self._color_mode_dpcode) != WorkMode.WHITE
|
|
):
|
|
return ColorMode.HS
|
|
if self._color_temp:
|
|
return ColorMode.COLOR_TEMP
|
|
if self._brightness:
|
|
return ColorMode.BRIGHTNESS
|
|
return ColorMode.ONOFF
|
|
|
|
def _get_color_data(self) -> ColorData | None:
|
|
"""Get current color data from device."""
|
|
if (
|
|
self._color_data_type is None
|
|
or self._color_data_dpcode is None
|
|
or self._color_data_dpcode not in self.device.status
|
|
):
|
|
return None
|
|
|
|
if not (status_data := self.device.status[self._color_data_dpcode]):
|
|
return None
|
|
|
|
if not (status := json.loads(status_data)):
|
|
return None
|
|
|
|
return ColorData(
|
|
type_data=self._color_data_type,
|
|
h_value=status["h"],
|
|
s_value=status["s"],
|
|
v_value=status["v"],
|
|
)
|