Skip to content

Commit 3658953

Browse files
authored
Migrate Tuya light (brightness) to use wrapper class (home-assistant#156735)
1 parent 0be5893 commit 3658953

File tree

1 file changed

+100
-84
lines changed

1 file changed

+100
-84
lines changed

homeassistant/components/tuya/light.py

Lines changed: 100 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,82 @@
3232
from .models import (
3333
DPCodeBooleanWrapper,
3434
DPCodeEnumWrapper,
35+
DPCodeIntegerWrapper,
3536
IntegerTypeData,
3637
find_dpcode,
3738
)
3839
from .util import get_dpcode, get_dptype, remap_value
3940

4041

42+
class _BrightnessWrapper(DPCodeIntegerWrapper):
43+
"""Wrapper for brightness DP code.
44+
45+
Handles brightness value conversion between device scale and Home Assistant's
46+
0-255 scale. Supports optional dynamic brightness_min and brightness_max
47+
wrappers that allow the device to specify runtime brightness range limits.
48+
"""
49+
50+
brightness_min: DPCodeIntegerWrapper | None = None
51+
brightness_max: DPCodeIntegerWrapper | None = None
52+
53+
def read_device_status(self, device: CustomerDevice) -> Any | None:
54+
"""Return the brightness of this light between 0..255."""
55+
if (brightness := self._read_device_status_raw(device)) is None:
56+
return None
57+
58+
# Remap value to our scale
59+
brightness = self.type_information.remap_value_to(brightness)
60+
61+
# If there is a min/max value, the brightness is actually limited.
62+
# Meaning it is actually not on a 0-255 scale.
63+
if (
64+
self.brightness_max is not None
65+
and self.brightness_min is not None
66+
and (brightness_max := device.status.get(self.brightness_max.dpcode))
67+
is not None
68+
and (brightness_min := device.status.get(self.brightness_min.dpcode))
69+
is not None
70+
):
71+
# Remap values onto our scale
72+
brightness_max = self.brightness_max.type_information.remap_value_to(
73+
brightness_max
74+
)
75+
brightness_min = self.brightness_min.type_information.remap_value_to(
76+
brightness_min
77+
)
78+
79+
# Remap the brightness value from their min-max to our 0-255 scale
80+
brightness = remap_value(
81+
brightness, from_min=brightness_min, from_max=brightness_max
82+
)
83+
84+
return round(brightness)
85+
86+
def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any:
87+
"""Convert a Home Assistant value (0..255) back to a raw device value."""
88+
# If there is a min/max value, the brightness is actually limited.
89+
# Meaning it is actually not on a 0-255 scale.
90+
if (
91+
self.brightness_max is not None
92+
and self.brightness_min is not None
93+
and (brightness_max := device.status.get(self.brightness_max.dpcode))
94+
is not None
95+
and (brightness_min := device.status.get(self.brightness_min.dpcode))
96+
is not None
97+
):
98+
# Remap values onto our scale
99+
brightness_max = self.brightness_max.type_information.remap_value_to(
100+
brightness_max
101+
)
102+
brightness_min = self.brightness_min.type_information.remap_value_to(
103+
brightness_min
104+
)
105+
106+
# Remap the brightness value from our 0-255 scale to their min-max
107+
value = remap_value(value, to_min=brightness_min, to_max=brightness_max)
108+
return round(self.type_information.remap_value_from(value))
109+
110+
41111
@dataclass
42112
class ColorTypeData:
43113
"""Color Type Data."""
@@ -417,6 +487,24 @@ def brightness(self) -> int:
417487
return round(self.type_data.v_type.remap_value_to(self.v_value, 0, 255))
418488

419489

490+
def _get_brightness_wrapper(
491+
device: CustomerDevice, description: TuyaLightEntityDescription
492+
) -> _BrightnessWrapper | None:
493+
if (
494+
brightness_wrapper := _BrightnessWrapper.find_dpcode(
495+
device, description.brightness, prefer_function=True
496+
)
497+
) is None:
498+
return None
499+
brightness_wrapper.brightness_max = DPCodeIntegerWrapper.find_dpcode(
500+
device, description.brightness_max, prefer_function=True
501+
)
502+
brightness_wrapper.brightness_min = DPCodeIntegerWrapper.find_dpcode(
503+
device, description.brightness_min, prefer_function=True
504+
)
505+
return brightness_wrapper
506+
507+
420508
async def async_setup_entry(
421509
hass: HomeAssistant,
422510
entry: TuyaConfigEntry,
@@ -437,6 +525,7 @@ def async_discover_device(device_ids: list[str]):
437525
device,
438526
manager,
439527
description,
528+
brightness_wrapper=_get_brightness_wrapper(device, description),
440529
color_mode_wrapper=DPCodeEnumWrapper.find_dpcode(
441530
device, description.color_mode, prefer_function=True
442531
),
@@ -464,9 +553,6 @@ class TuyaLightEntity(TuyaEntity, LightEntity):
464553

465554
entity_description: TuyaLightEntityDescription
466555

467-
_brightness_max: IntegerTypeData | None = None
468-
_brightness_min: IntegerTypeData | None = None
469-
_brightness: IntegerTypeData | None = None
470556
_color_data_dpcode: DPCode | None = None
471557
_color_data_type: ColorTypeData | None = None
472558
_color_temp: IntegerTypeData | None = None
@@ -481,32 +567,22 @@ def __init__(
481567
device_manager: Manager,
482568
description: TuyaLightEntityDescription,
483569
*,
570+
brightness_wrapper: DPCodeIntegerWrapper | None,
484571
color_mode_wrapper: DPCodeEnumWrapper | None,
485572
switch_wrapper: DPCodeBooleanWrapper,
486573
) -> None:
487574
"""Init TuyaHaLight."""
488575
super().__init__(device, device_manager)
489576
self.entity_description = description
490577
self._attr_unique_id = f"{super().unique_id}{description.key}"
578+
self._brightness_wrapper = brightness_wrapper
491579
self._color_mode_wrapper = color_mode_wrapper
492580
self._switch_wrapper = switch_wrapper
493581

494582
color_modes: set[ColorMode] = {ColorMode.ONOFF}
495583

496-
if int_type := find_dpcode(
497-
self.device,
498-
description.brightness,
499-
dptype=DPType.INTEGER,
500-
prefer_function=True,
501-
):
502-
self._brightness = int_type
584+
if brightness_wrapper:
503585
color_modes.add(ColorMode.BRIGHTNESS)
504-
self._brightness_max = find_dpcode(
505-
self.device, description.brightness_max, dptype=DPType.INTEGER
506-
)
507-
self._brightness_min = find_dpcode(
508-
self.device, description.brightness_min, dptype=DPType.INTEGER
509-
)
510586

511587
if (dpcode := get_dpcode(self.device, description.color_data)) and (
512588
get_dptype(self.device, dpcode, prefer_function=True) == DPType.JSON
@@ -529,7 +605,8 @@ def __init__(
529605
# If no type is found, use a default one
530606
self._color_data_type = self.entity_description.default_color_type
531607
if self._color_data_dpcode == DPCode.COLOUR_DATA_V2 or (
532-
self._brightness and self._brightness.max > 255
608+
self._brightness_wrapper
609+
and self._brightness_wrapper.type_information.max > 255
533610
):
534611
self._color_data_type = DEFAULT_COLOR_TYPE_DATA_V2
535612

@@ -641,46 +718,16 @@ def turn_on(self, **kwargs: Any) -> None:
641718
},
642719
]
643720

644-
elif self._brightness and (ATTR_BRIGHTNESS in kwargs or ATTR_WHITE in kwargs):
721+
elif self._brightness_wrapper and (
722+
ATTR_BRIGHTNESS in kwargs or ATTR_WHITE in kwargs
723+
):
645724
if ATTR_BRIGHTNESS in kwargs:
646725
brightness = kwargs[ATTR_BRIGHTNESS]
647726
else:
648727
brightness = kwargs[ATTR_WHITE]
649728

650-
# If there is a min/max value, the brightness is actually limited.
651-
# Meaning it is actually not on a 0-255 scale.
652-
if (
653-
self._brightness_max is not None
654-
and self._brightness_min is not None
655-
and (
656-
brightness_max := self.device.status.get(
657-
self._brightness_max.dpcode
658-
)
659-
)
660-
is not None
661-
and (
662-
brightness_min := self.device.status.get(
663-
self._brightness_min.dpcode
664-
)
665-
)
666-
is not None
667-
):
668-
# Remap values onto our scale
669-
brightness_max = self._brightness_max.remap_value_to(brightness_max)
670-
brightness_min = self._brightness_min.remap_value_to(brightness_min)
671-
672-
# Remap the brightness value from their min-max to our 0-255 scale
673-
brightness = remap_value(
674-
brightness,
675-
to_min=brightness_min,
676-
to_max=brightness_max,
677-
)
678-
679729
commands += [
680-
{
681-
"code": self._brightness.dpcode,
682-
"value": round(self._brightness.remap_value_from(brightness)),
683-
},
730+
self._brightness_wrapper.get_update_command(self.device, brightness),
684731
]
685732

686733
self._send_command(commands)
@@ -691,43 +738,12 @@ async def async_turn_off(self, **kwargs: Any) -> None:
691738

692739
@property
693740
def brightness(self) -> int | None:
694-
"""Return the brightness of the light."""
741+
"""Return the brightness of this light between 0..255."""
695742
# If the light is currently in color mode, extract the brightness from the color data
696743
if self.color_mode == ColorMode.HS and (color_data := self._get_color_data()):
697744
return color_data.brightness
698745

699-
if not self._brightness:
700-
return None
701-
702-
brightness = self.device.status.get(self._brightness.dpcode)
703-
if brightness is None:
704-
return None
705-
706-
# Remap value to our scale
707-
brightness = self._brightness.remap_value_to(brightness)
708-
709-
# If there is a min/max value, the brightness is actually limited.
710-
# Meaning it is actually not on a 0-255 scale.
711-
if (
712-
self._brightness_max is not None
713-
and self._brightness_min is not None
714-
and (brightness_max := self.device.status.get(self._brightness_max.dpcode))
715-
is not None
716-
and (brightness_min := self.device.status.get(self._brightness_min.dpcode))
717-
is not None
718-
):
719-
# Remap values onto our scale
720-
brightness_max = self._brightness_max.remap_value_to(brightness_max)
721-
brightness_min = self._brightness_min.remap_value_to(brightness_min)
722-
723-
# Remap the brightness value from their min-max to our 0-255 scale
724-
brightness = remap_value(
725-
brightness,
726-
from_min=brightness_min,
727-
from_max=brightness_max,
728-
)
729-
730-
return round(brightness)
746+
return self._read_wrapper(self._brightness_wrapper)
731747

732748
@property
733749
def color_temp_kelvin(self) -> int | None:

0 commit comments

Comments
 (0)