Home Assistant Unofficial Reference 2024.12.1
light.py
Go to the documentation of this file.
1 """Support for the Tuya lights."""
2 
3 from __future__ import annotations
4 
5 from dataclasses import dataclass, field
6 import json
7 from typing import Any, cast
8 
9 from tuya_sharing import CustomerDevice, Manager
10 
12  ATTR_BRIGHTNESS,
13  ATTR_COLOR_TEMP,
14  ATTR_HS_COLOR,
15  ColorMode,
16  LightEntity,
17  LightEntityDescription,
18  filter_supported_color_modes,
19 )
20 from homeassistant.const import EntityCategory
21 from homeassistant.core import HomeAssistant, callback
22 from homeassistant.helpers.dispatcher import async_dispatcher_connect
23 from homeassistant.helpers.entity_platform import AddEntitiesCallback
24 
25 from . import TuyaConfigEntry
26 from .const import TUYA_DISCOVERY_NEW, DPCode, DPType, WorkMode
27 from .entity import IntegerTypeData, TuyaEntity
28 from .util import remap_value
29 
30 
31 @dataclass
33  """Color Type Data."""
34 
35  h_type: IntegerTypeData
36  s_type: IntegerTypeData
37  v_type: IntegerTypeData
38 
39 
40 DEFAULT_COLOR_TYPE_DATA = ColorTypeData(
41  h_type=IntegerTypeData(DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=360, step=1),
42  s_type=IntegerTypeData(DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=255, step=1),
43  v_type=IntegerTypeData(DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=255, step=1),
44 )
45 
46 DEFAULT_COLOR_TYPE_DATA_V2 = ColorTypeData(
47  h_type=IntegerTypeData(DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=360, step=1),
48  s_type=IntegerTypeData(DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=1000, step=1),
49  v_type=IntegerTypeData(DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=1000, step=1),
50 )
51 
52 
53 @dataclass(frozen=True)
55  """Describe an Tuya light entity."""
56 
57  brightness_max: DPCode | None = None
58  brightness_min: DPCode | None = None
59  brightness: DPCode | tuple[DPCode, ...] | None = None
60  color_data: DPCode | tuple[DPCode, ...] | None = None
61  color_mode: DPCode | None = None
62  color_temp: DPCode | tuple[DPCode, ...] | None = None
63  default_color_type: ColorTypeData = field(
64  default_factory=lambda: DEFAULT_COLOR_TYPE_DATA
65  )
66 
67 
68 LIGHTS: dict[str, tuple[TuyaLightEntityDescription, ...]] = {
69  # Curtain Switch
70  # https://developer.tuya.com/en/docs/iot/category-clkg?id=Kaiuz0gitil39
71  "clkg": (
73  key=DPCode.SWITCH_BACKLIGHT,
74  translation_key="backlight",
75  entity_category=EntityCategory.CONFIG,
76  ),
77  ),
78  # String Lights
79  # https://developer.tuya.com/en/docs/iot/dc?id=Kaof7taxmvadu
80  "dc": (
82  key=DPCode.SWITCH_LED,
83  name=None,
84  color_mode=DPCode.WORK_MODE,
85  brightness=DPCode.BRIGHT_VALUE,
86  color_temp=DPCode.TEMP_VALUE,
87  color_data=DPCode.COLOUR_DATA,
88  ),
89  ),
90  # Strip Lights
91  # https://developer.tuya.com/en/docs/iot/dd?id=Kaof804aibg2l
92  "dd": (
94  key=DPCode.SWITCH_LED,
95  name=None,
96  color_mode=DPCode.WORK_MODE,
97  brightness=DPCode.BRIGHT_VALUE,
98  color_temp=DPCode.TEMP_VALUE,
99  color_data=DPCode.COLOUR_DATA,
100  default_color_type=DEFAULT_COLOR_TYPE_DATA_V2,
101  ),
102  ),
103  # Light
104  # https://developer.tuya.com/en/docs/iot/categorydj?id=Kaiuyzy3eheyy
105  "dj": (
107  key=DPCode.SWITCH_LED,
108  name=None,
109  color_mode=DPCode.WORK_MODE,
110  brightness=(DPCode.BRIGHT_VALUE_V2, DPCode.BRIGHT_VALUE),
111  color_temp=(DPCode.TEMP_VALUE_V2, DPCode.TEMP_VALUE),
112  color_data=(DPCode.COLOUR_DATA_V2, DPCode.COLOUR_DATA),
113  ),
114  # Not documented
115  # Based on multiple reports: manufacturer customized Dimmer 2 switches
117  key=DPCode.SWITCH_1,
118  translation_key="light",
119  brightness=DPCode.BRIGHT_VALUE_1,
120  ),
121  ),
122  # Filament Light
123  # Based on data from https://github.com/home-assistant/core/issues/106703
124  # Product category mentioned in https://developer.tuya.com/en/docs/iot/oemapp-light?id=Kb77kja5woao6
125  # As at 30/12/23 not documented in https://developer.tuya.com/en/docs/iot/lighting?id=Kaiuyzxq30wmc
126  "dsd": (
128  key=DPCode.SWITCH_LED,
129  name=None,
130  color_mode=DPCode.WORK_MODE,
131  brightness=DPCode.BRIGHT_VALUE,
132  ),
133  ),
134  # Ceiling Fan Light
135  # https://developer.tuya.com/en/docs/iot/fsd?id=Kaof8eiei4c2v
136  "fsd": (
138  key=DPCode.SWITCH_LED,
139  name=None,
140  color_mode=DPCode.WORK_MODE,
141  brightness=DPCode.BRIGHT_VALUE,
142  color_temp=DPCode.TEMP_VALUE,
143  color_data=DPCode.COLOUR_DATA,
144  ),
145  # Some ceiling fan lights use LIGHT for DPCode instead of SWITCH_LED
147  key=DPCode.LIGHT,
148  name=None,
149  ),
150  ),
151  # Ambient Light
152  # https://developer.tuya.com/en/docs/iot/ambient-light?id=Kaiuz06amhe6g
153  "fwd": (
155  key=DPCode.SWITCH_LED,
156  name=None,
157  color_mode=DPCode.WORK_MODE,
158  brightness=DPCode.BRIGHT_VALUE,
159  color_temp=DPCode.TEMP_VALUE,
160  color_data=DPCode.COLOUR_DATA,
161  ),
162  ),
163  # Motion Sensor Light
164  # https://developer.tuya.com/en/docs/iot/gyd?id=Kaof8a8hycfmy
165  "gyd": (
167  key=DPCode.SWITCH_LED,
168  name=None,
169  color_mode=DPCode.WORK_MODE,
170  brightness=DPCode.BRIGHT_VALUE,
171  color_temp=DPCode.TEMP_VALUE,
172  color_data=DPCode.COLOUR_DATA,
173  ),
174  ),
175  # Humidifier Light
176  # https://developer.tuya.com/en/docs/iot/categoryjsq?id=Kaiuz1smr440b
177  "jsq": (
179  key=DPCode.SWITCH_LED,
180  name=None,
181  color_mode=DPCode.WORK_MODE,
182  brightness=DPCode.BRIGHT_VALUE,
183  color_data=DPCode.COLOUR_DATA_HSV,
184  ),
185  ),
186  # Switch
187  # https://developer.tuya.com/en/docs/iot/s?id=K9gf7o5prgf7s
188  "kg": (
190  key=DPCode.SWITCH_BACKLIGHT,
191  translation_key="backlight",
192  entity_category=EntityCategory.CONFIG,
193  ),
194  ),
195  # Air Purifier
196  # https://developer.tuya.com/en/docs/iot/f?id=K9gf46h2s6dzm
197  "kj": (
199  key=DPCode.LIGHT,
200  translation_key="backlight",
201  entity_category=EntityCategory.CONFIG,
202  ),
203  ),
204  # Air conditioner
205  # https://developer.tuya.com/en/docs/iot/categorykt?id=Kaiuz0z71ov2n
206  "kt": (
208  key=DPCode.LIGHT,
209  translation_key="backlight",
210  entity_category=EntityCategory.CONFIG,
211  ),
212  ),
213  # Unknown light product
214  # Found as VECINO RGBW as provided by diagnostics
215  # Not documented
216  "mbd": (
218  key=DPCode.SWITCH_LED,
219  name=None,
220  color_mode=DPCode.WORK_MODE,
221  brightness=DPCode.BRIGHT_VALUE,
222  color_data=DPCode.COLOUR_DATA,
223  ),
224  ),
225  # Unknown product with light capabilities
226  # Fond in some diffusers, plugs and PIR flood lights
227  # Not documented
228  "qjdcz": (
230  key=DPCode.SWITCH_LED,
231  name=None,
232  color_mode=DPCode.WORK_MODE,
233  brightness=DPCode.BRIGHT_VALUE,
234  color_data=DPCode.COLOUR_DATA,
235  ),
236  ),
237  # Heater
238  # https://developer.tuya.com/en/docs/iot/categoryqn?id=Kaiuz18kih0sm
239  "qn": (
241  key=DPCode.LIGHT,
242  translation_key="backlight",
243  entity_category=EntityCategory.CONFIG,
244  ),
245  ),
246  # Smart Camera
247  # https://developer.tuya.com/en/docs/iot/categorysp?id=Kaiuz35leyo12
248  "sp": (
250  key=DPCode.FLOODLIGHT_SWITCH,
251  brightness=DPCode.FLOODLIGHT_LIGHTNESS,
252  name="Floodlight",
253  ),
255  key=DPCode.BASIC_INDICATOR,
256  name="Indicator light",
257  entity_category=EntityCategory.CONFIG,
258  ),
259  ),
260  # Smart Gardening system
261  # https://developer.tuya.com/en/docs/iot/categorysz?id=Kaiuz4e6h7up0
262  "sz": (
264  key=DPCode.LIGHT,
265  brightness=DPCode.BRIGHT_VALUE,
266  translation_key="light",
267  ),
268  ),
269  # Dimmer Switch
270  # https://developer.tuya.com/en/docs/iot/categorytgkg?id=Kaiuz0ktx7m0o
271  "tgkg": (
273  key=DPCode.SWITCH_LED_1,
274  translation_key="light",
275  brightness=DPCode.BRIGHT_VALUE_1,
276  brightness_max=DPCode.BRIGHTNESS_MAX_1,
277  brightness_min=DPCode.BRIGHTNESS_MIN_1,
278  ),
280  key=DPCode.SWITCH_LED_2,
281  translation_key="light_2",
282  brightness=DPCode.BRIGHT_VALUE_2,
283  brightness_max=DPCode.BRIGHTNESS_MAX_2,
284  brightness_min=DPCode.BRIGHTNESS_MIN_2,
285  ),
287  key=DPCode.SWITCH_LED_3,
288  translation_key="light_3",
289  brightness=DPCode.BRIGHT_VALUE_3,
290  brightness_max=DPCode.BRIGHTNESS_MAX_3,
291  brightness_min=DPCode.BRIGHTNESS_MIN_3,
292  ),
293  ),
294  # Dimmer
295  # https://developer.tuya.com/en/docs/iot/tgq?id=Kaof8ke9il4k4
296  "tgq": (
298  key=DPCode.SWITCH_LED,
299  translation_key="light",
300  brightness=(DPCode.BRIGHT_VALUE_V2, DPCode.BRIGHT_VALUE),
301  brightness_max=DPCode.BRIGHTNESS_MAX_1,
302  brightness_min=DPCode.BRIGHTNESS_MIN_1,
303  ),
305  key=DPCode.SWITCH_LED_1,
306  translation_key="light",
307  brightness=DPCode.BRIGHT_VALUE_1,
308  ),
310  key=DPCode.SWITCH_LED_2,
311  translation_key="light_2",
312  brightness=DPCode.BRIGHT_VALUE_2,
313  ),
314  ),
315  # Wake Up Light II
316  # Not documented
317  "hxd": (
319  key=DPCode.SWITCH_LED,
320  translation_key="light",
321  brightness=(DPCode.BRIGHT_VALUE_V2, DPCode.BRIGHT_VALUE),
322  brightness_max=DPCode.BRIGHTNESS_MAX_1,
323  brightness_min=DPCode.BRIGHTNESS_MIN_1,
324  ),
325  ),
326  # Solar Light
327  # https://developer.tuya.com/en/docs/iot/tynd?id=Kaof8j02e1t98
328  "tyndj": (
330  key=DPCode.SWITCH_LED,
331  name=None,
332  color_mode=DPCode.WORK_MODE,
333  brightness=DPCode.BRIGHT_VALUE,
334  color_temp=DPCode.TEMP_VALUE,
335  color_data=DPCode.COLOUR_DATA,
336  ),
337  ),
338  # Ceiling Light
339  # https://developer.tuya.com/en/docs/iot/ceiling-light?id=Kaiuz03xxfc4r
340  "xdd": (
342  key=DPCode.SWITCH_LED,
343  name=None,
344  color_mode=DPCode.WORK_MODE,
345  brightness=DPCode.BRIGHT_VALUE,
346  color_temp=DPCode.TEMP_VALUE,
347  color_data=DPCode.COLOUR_DATA,
348  ),
350  key=DPCode.SWITCH_NIGHT_LIGHT,
351  translation_key="night_light",
352  ),
353  ),
354  # Remote Control
355  # https://developer.tuya.com/en/docs/iot/ykq?id=Kaof8ljn81aov
356  "ykq": (
358  key=DPCode.SWITCH_CONTROLLER,
359  name=None,
360  color_mode=DPCode.WORK_MODE,
361  brightness=DPCode.BRIGHT_CONTROLLER,
362  color_temp=DPCode.TEMP_CONTROLLER,
363  ),
364  ),
365  # Fan
366  # https://developer.tuya.com/en/docs/iot/categoryfs?id=Kaiuz1xweel1c
367  "fs": (
369  key=DPCode.LIGHT,
370  name=None,
371  color_mode=DPCode.WORK_MODE,
372  brightness=DPCode.BRIGHT_VALUE,
373  color_temp=DPCode.TEMP_VALUE,
374  ),
376  key=DPCode.SWITCH_LED,
377  translation_key="light_2",
378  brightness=DPCode.BRIGHT_VALUE_1,
379  ),
380  ),
381 }
382 
383 # Socket (duplicate of `kg`)
384 # https://developer.tuya.com/en/docs/iot/s?id=K9gf7o5prgf7s
385 LIGHTS["cz"] = LIGHTS["kg"]
386 
387 # Power Socket (duplicate of `kg`)
388 # https://developer.tuya.com/en/docs/iot/s?id=K9gf7o5prgf7s
389 LIGHTS["pc"] = LIGHTS["kg"]
390 
391 # Dimmer (duplicate of `tgq`)
392 # https://developer.tuya.com/en/docs/iot/tgq?id=Kaof8ke9il4k4
393 LIGHTS["tdq"] = LIGHTS["tgq"]
394 
395 
396 @dataclass
397 class ColorData:
398  """Color Data."""
399 
400  type_data: ColorTypeData
401  h_value: int
402  s_value: int
403  v_value: int
404 
405  @property
406  def hs_color(self) -> tuple[float, float]:
407  """Get the HS value from this color data."""
408  return (
409  self.type_data.h_type.remap_value_to(self.h_value, 0, 360),
410  self.type_data.s_type.remap_value_to(self.s_value, 0, 100),
411  )
412 
413  @property
414  def brightness(self) -> int:
415  """Get the brightness value from this color data."""
416  return round(self.type_data.v_type.remap_value_to(self.v_value, 0, 255))
417 
418 
420  hass: HomeAssistant, entry: TuyaConfigEntry, async_add_entities: AddEntitiesCallback
421 ) -> None:
422  """Set up tuya light dynamically through tuya discovery."""
423  hass_data = entry.runtime_data
424 
425  @callback
426  def async_discover_device(device_ids: list[str]):
427  """Discover and add a discovered tuya light."""
428  entities: list[TuyaLightEntity] = []
429  for device_id in device_ids:
430  device = hass_data.manager.device_map[device_id]
431  if descriptions := LIGHTS.get(device.category):
432  entities.extend(
433  TuyaLightEntity(device, hass_data.manager, description)
434  for description in descriptions
435  if description.key in device.status
436  )
437 
438  async_add_entities(entities)
439 
440  async_discover_device([*hass_data.manager.device_map])
441 
442  entry.async_on_unload(
443  async_dispatcher_connect(hass, TUYA_DISCOVERY_NEW, async_discover_device)
444  )
445 
446 
448  """Tuya light device."""
449 
450  entity_description: TuyaLightEntityDescription
451 
452  _brightness_max: IntegerTypeData | None = None
453  _brightness_min: IntegerTypeData | None = None
454  _brightness: IntegerTypeData | None = None
455  _color_data_dpcode: DPCode | None = None
456  _color_data_type: ColorTypeData | None = None
457  _color_mode: DPCode | None = None
458  _color_temp: IntegerTypeData | None = None
459  _fixed_color_mode: ColorMode | None = None
460 
461  def __init__(
462  self,
463  device: CustomerDevice,
464  device_manager: Manager,
465  description: TuyaLightEntityDescription,
466  ) -> None:
467  """Init TuyaHaLight."""
468  super().__init__(device, device_manager)
469  self.entity_descriptionentity_description = description
470  self._attr_unique_id_attr_unique_id_attr_unique_id = f"{super().unique_id}{description.key}"
471  color_modes: set[ColorMode] = {ColorMode.ONOFF}
472 
473  # Determine DPCodes
474  self._color_mode_dpcode_color_mode_dpcode = self.find_dpcodefind_dpcodefind_dpcodefind_dpcodefind_dpcode(
475  description.color_mode, prefer_function=True
476  )
477 
478  if int_type := self.find_dpcodefind_dpcodefind_dpcodefind_dpcodefind_dpcode(
479  description.brightness, dptype=DPType.INTEGER, prefer_function=True
480  ):
481  self._brightness_brightness = int_type
482  color_modes.add(ColorMode.BRIGHTNESS)
483  self._brightness_max_brightness_max = self.find_dpcodefind_dpcodefind_dpcodefind_dpcodefind_dpcode(
484  description.brightness_max, dptype=DPType.INTEGER
485  )
486  self._brightness_min_brightness_min = self.find_dpcodefind_dpcodefind_dpcodefind_dpcodefind_dpcode(
487  description.brightness_min, dptype=DPType.INTEGER
488  )
489 
490  if int_type := self.find_dpcodefind_dpcodefind_dpcodefind_dpcodefind_dpcode(
491  description.color_temp, dptype=DPType.INTEGER, prefer_function=True
492  ):
493  self._color_temp_color_temp = int_type
494  color_modes.add(ColorMode.COLOR_TEMP)
495 
496  if (
497  dpcode := self.find_dpcodefind_dpcodefind_dpcodefind_dpcodefind_dpcode(description.color_data, prefer_function=True)
498  ) and self.get_dptypeget_dptype(dpcode) == DPType.JSON:
499  self._color_data_dpcode_color_data_dpcode = dpcode
500  color_modes.add(ColorMode.HS)
501  if dpcode in self.devicedevice.function:
502  values = cast(str, self.devicedevice.function[dpcode].values)
503  else:
504  values = self.devicedevice.status_range[dpcode].values
505 
506  # Fetch color data type information
507  if function_data := json.loads(values):
508  self._color_data_type_color_data_type = ColorTypeData(
509  h_type=IntegerTypeData(dpcode, **function_data["h"]),
510  s_type=IntegerTypeData(dpcode, **function_data["s"]),
511  v_type=IntegerTypeData(dpcode, **function_data["v"]),
512  )
513  else:
514  # If no type is found, use a default one
515  self._color_data_type_color_data_type = self.entity_descriptionentity_description.default_color_type
516  if self._color_data_dpcode_color_data_dpcode == DPCode.COLOUR_DATA_V2 or (
517  self._brightness_brightness and self._brightness_brightness.max > 255
518  ):
519  self._color_data_type_color_data_type = DEFAULT_COLOR_TYPE_DATA_V2
520 
521  self._attr_supported_color_modes_attr_supported_color_modes = filter_supported_color_modes(color_modes)
522  if len(self._attr_supported_color_modes_attr_supported_color_modes) == 1:
523  # If the light supports only a single color mode, set it now
524  self._fixed_color_mode_fixed_color_mode = next(iter(self._attr_supported_color_modes_attr_supported_color_modes))
525 
526  @property
527  def is_on(self) -> bool:
528  """Return true if light is on."""
529  return self.devicedevice.status.get(self.entity_descriptionentity_description.key, False)
530 
531  def turn_on(self, **kwargs: Any) -> None:
532  """Turn on or control the light."""
533  commands = [{"code": self.entity_descriptionentity_description.key, "value": True}]
534 
535  if self._color_temp_color_temp and ATTR_COLOR_TEMP in kwargs:
536  if self._color_mode_dpcode_color_mode_dpcode:
537  commands += [
538  {
539  "code": self._color_mode_dpcode_color_mode_dpcode,
540  "value": WorkMode.WHITE,
541  },
542  ]
543 
544  commands += [
545  {
546  "code": self._color_temp_color_temp.dpcode,
547  "value": round(
548  self._color_temp_color_temp.remap_value_from(
549  kwargs[ATTR_COLOR_TEMP],
550  self.min_miredsmin_mireds,
551  self.max_miredsmax_mireds,
552  reverse=True,
553  )
554  ),
555  },
556  ]
557 
558  if self._color_data_type_color_data_type and (
559  ATTR_HS_COLOR in kwargs
560  or (
561  ATTR_BRIGHTNESS in kwargs
562  and self.color_modecolor_modecolor_modecolor_mode == ColorMode.HS
563  and ATTR_COLOR_TEMP not in kwargs
564  )
565  ):
566  if self._color_mode_dpcode_color_mode_dpcode:
567  commands += [
568  {
569  "code": self._color_mode_dpcode_color_mode_dpcode,
570  "value": WorkMode.COLOUR,
571  },
572  ]
573 
574  if not (brightness := kwargs.get(ATTR_BRIGHTNESS)):
575  brightness = self.brightnessbrightnessbrightness or 0
576 
577  if not (color := kwargs.get(ATTR_HS_COLOR)):
578  color = self.hs_colorhs_colorhs_color or (0, 0)
579 
580  commands += [
581  {
582  "code": self._color_data_dpcode_color_data_dpcode,
583  "value": json.dumps(
584  {
585  "h": round(
586  self._color_data_type_color_data_type.h_type.remap_value_from(
587  color[0], 0, 360
588  )
589  ),
590  "s": round(
591  self._color_data_type_color_data_type.s_type.remap_value_from(
592  color[1], 0, 100
593  )
594  ),
595  "v": round(
596  self._color_data_type_color_data_type.v_type.remap_value_from(
597  brightness
598  )
599  ),
600  }
601  ),
602  },
603  ]
604 
605  elif ATTR_BRIGHTNESS in kwargs and self._brightness_brightness:
606  brightness = kwargs[ATTR_BRIGHTNESS]
607 
608  # If there is a min/max value, the brightness is actually limited.
609  # Meaning it is actually not on a 0-255 scale.
610  if (
611  self._brightness_max_brightness_max is not None
612  and self._brightness_min_brightness_min is not None
613  and (
614  brightness_max := self.devicedevice.status.get(
615  self._brightness_max_brightness_max.dpcode
616  )
617  )
618  is not None
619  and (
620  brightness_min := self.devicedevice.status.get(
621  self._brightness_min_brightness_min.dpcode
622  )
623  )
624  is not None
625  ):
626  # Remap values onto our scale
627  brightness_max = self._brightness_max_brightness_max.remap_value_to(brightness_max)
628  brightness_min = self._brightness_min_brightness_min.remap_value_to(brightness_min)
629 
630  # Remap the brightness value from their min-max to our 0-255 scale
631  brightness = remap_value(
632  brightness,
633  to_min=brightness_min,
634  to_max=brightness_max,
635  )
636 
637  commands += [
638  {
639  "code": self._brightness_brightness.dpcode,
640  "value": round(self._brightness_brightness.remap_value_from(brightness)),
641  },
642  ]
643 
644  self._send_command_send_command(commands)
645 
646  def turn_off(self, **kwargs: Any) -> None:
647  """Instruct the light to turn off."""
648  self._send_command_send_command([{"code": self.entity_descriptionentity_description.key, "value": False}])
649 
650  @property
651  def brightness(self) -> int | None:
652  """Return the brightness of the light."""
653  # If the light is currently in color mode, extract the brightness from the color data
654  if self.color_modecolor_modecolor_modecolor_mode == ColorMode.HS and (color_data := self._get_color_data_get_color_data()):
655  return color_data.brightness
656 
657  if not self._brightness_brightness:
658  return None
659 
660  brightness = self.devicedevice.status.get(self._brightness_brightness.dpcode)
661  if brightness is None:
662  return None
663 
664  # Remap value to our scale
665  brightness = self._brightness_brightness.remap_value_to(brightness)
666 
667  # If there is a min/max value, the brightness is actually limited.
668  # Meaning it is actually not on a 0-255 scale.
669  if (
670  self._brightness_max_brightness_max is not None
671  and self._brightness_min_brightness_min is not None
672  and (brightness_max := self.devicedevice.status.get(self._brightness_max_brightness_max.dpcode))
673  is not None
674  and (brightness_min := self.devicedevice.status.get(self._brightness_min_brightness_min.dpcode))
675  is not None
676  ):
677  # Remap values onto our scale
678  brightness_max = self._brightness_max_brightness_max.remap_value_to(brightness_max)
679  brightness_min = self._brightness_min_brightness_min.remap_value_to(brightness_min)
680 
681  # Remap the brightness value from their min-max to our 0-255 scale
682  brightness = remap_value(
683  brightness,
684  from_min=brightness_min,
685  from_max=brightness_max,
686  )
687 
688  return round(brightness)
689 
690  @property
691  def color_temp(self) -> int | None:
692  """Return the color_temp of the light."""
693  if not self._color_temp_color_temp:
694  return None
695 
696  temperature = self.devicedevice.status.get(self._color_temp_color_temp.dpcode)
697  if temperature is None:
698  return None
699 
700  return round(
701  self._color_temp_color_temp.remap_value_to(
702  temperature, self.min_miredsmin_mireds, self.max_miredsmax_mireds, reverse=True
703  )
704  )
705 
706  @property
707  def hs_color(self) -> tuple[float, float] | None:
708  """Return the hs_color of the light."""
709  if self._color_data_dpcode_color_data_dpcode is None or not (
710  color_data := self._get_color_data_get_color_data()
711  ):
712  return None
713  return color_data.hs_color
714 
715  @property
716  def color_mode(self) -> ColorMode:
717  """Return the color_mode of the light."""
718  if self._fixed_color_mode_fixed_color_mode:
719  # The light supports only a single color mode, return it
720  return self._fixed_color_mode_fixed_color_mode
721 
722  # The light supports both color temperature and HS, determine which mode the
723  # light is in. We consider it to be in HS color mode, when work mode is anything
724  # else than "white".
725  if (
726  self._color_mode_dpcode_color_mode_dpcode
727  and self.devicedevice.status.get(self._color_mode_dpcode_color_mode_dpcode) != WorkMode.WHITE
728  ):
729  return ColorMode.HS
730  return ColorMode.COLOR_TEMP
731 
732  def _get_color_data(self) -> ColorData | None:
733  """Get current color data from device."""
734  if (
735  self._color_data_type_color_data_type is None
736  or self._color_data_dpcode_color_data_dpcode is None
737  or self._color_data_dpcode_color_data_dpcode not in self.devicedevice.status
738  ):
739  return None
740 
741  if not (status_data := self.devicedevice.status[self._color_data_dpcode_color_data_dpcode]):
742  return None
743 
744  if not (status := json.loads(status_data)):
745  return None
746 
747  return ColorData(
748  type_data=self._color_data_type_color_data_type,
749  h_value=status["h"],
750  s_value=status["s"],
751  v_value=status["v"],
752  )
tuple[float, float]|None hs_color(self)
Definition: __init__.py:947
ColorMode|str|None color_mode(self)
Definition: __init__.py:909
None _send_command(self, list[dict[str, Any]] commands)
Definition: entity.py:295
DPCode|EnumTypeData|IntegerTypeData|None find_dpcode(self, str|DPCode|tuple[DPCode,...]|None dpcodes, *bool prefer_function=False, DPType|None dptype=None)
Definition: entity.py:206
IntegerTypeData|None find_dpcode(self, str|DPCode|tuple[DPCode,...]|None dpcodes, *bool prefer_function=False, Literal[DPType.INTEGER] dptype)
Definition: entity.py:190
DPCode|None find_dpcode(self, str|DPCode|tuple[DPCode,...]|None dpcodes, *bool prefer_function=False)
Definition: entity.py:198
EnumTypeData|None find_dpcode(self, str|DPCode|tuple[DPCode,...]|None dpcodes, *bool prefer_function=False, Literal[DPType.ENUM] dptype)
Definition: entity.py:181
DPType|None get_dptype(self, DPCode|None dpcode, bool prefer_function=False)
Definition: entity.py:260
tuple[float, float] hs_color(self)
Definition: light.py:406
tuple[float, float]|None hs_color(self)
Definition: light.py:707
None __init__(self, CustomerDevice device, Manager device_manager, TuyaLightEntityDescription description)
Definition: light.py:466
ElkSystem|None async_discover_device(HomeAssistant hass, str host)
Definition: discovery.py:78
set[ColorMode] filter_supported_color_modes(Iterable[ColorMode] color_modes)
Definition: __init__.py:122
None async_setup_entry(HomeAssistant hass, TuyaConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: light.py:421
float remap_value(float value, float from_min=0, float from_max=255, float to_min=0, float to_max=255, bool reverse=False)
Definition: util.py:13
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103