Fix ColorMode.WHITE support in Tuya (#126242)

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
wedsa5 2025-07-22 06:07:34 -06:00 committed by GitHub
parent 3f67ba4c02
commit 5a771b501d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 98 additions and 26 deletions

View File

@ -12,6 +12,7 @@ from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP_KELVIN, ATTR_COLOR_TEMP_KELVIN,
ATTR_HS_COLOR, ATTR_HS_COLOR,
ATTR_WHITE,
ColorMode, ColorMode,
LightEntity, LightEntity,
LightEntityDescription, LightEntityDescription,
@ -488,6 +489,7 @@ class TuyaLightEntity(TuyaEntity, LightEntity):
_color_data_type: ColorTypeData | None = None _color_data_type: ColorTypeData | None = None
_color_mode: DPCode | None = None _color_mode: DPCode | None = None
_color_temp: IntegerTypeData | None = None _color_temp: IntegerTypeData | None = None
_white_color_mode = ColorMode.COLOR_TEMP
_fixed_color_mode: ColorMode | None = None _fixed_color_mode: ColorMode | None = None
_attr_min_color_temp_kelvin = 2000 # 500 Mireds _attr_min_color_temp_kelvin = 2000 # 500 Mireds
_attr_max_color_temp_kelvin = 6500 # 153 Mireds _attr_max_color_temp_kelvin = 6500 # 153 Mireds
@ -526,6 +528,13 @@ class TuyaLightEntity(TuyaEntity, LightEntity):
): ):
self._color_temp = int_type self._color_temp = int_type
color_modes.add(ColorMode.COLOR_TEMP) color_modes.add(ColorMode.COLOR_TEMP)
# If entity does not have color_temp, check if it has work_mode "white"
elif color_mode_enum := self.find_dpcode(
description.color_mode, dptype=DPType.ENUM, prefer_function=True
):
if WorkMode.WHITE.value in color_mode_enum.range:
color_modes.add(ColorMode.WHITE)
self._white_color_mode = ColorMode.WHITE
if ( if (
dpcode := self.find_dpcode(description.color_data, prefer_function=True) dpcode := self.find_dpcode(description.color_data, prefer_function=True)
@ -566,8 +575,9 @@ class TuyaLightEntity(TuyaEntity, LightEntity):
"""Turn on or control the light.""" """Turn on or control the light."""
commands = [{"code": self.entity_description.key, "value": True}] commands = [{"code": self.entity_description.key, "value": True}]
if self._color_temp and ATTR_COLOR_TEMP_KELVIN in kwargs: if self._color_mode_dpcode and (
if self._color_mode_dpcode: ATTR_WHITE in kwargs or ATTR_COLOR_TEMP_KELVIN in kwargs
):
commands += [ commands += [
{ {
"code": self._color_mode_dpcode, "code": self._color_mode_dpcode,
@ -575,6 +585,7 @@ class TuyaLightEntity(TuyaEntity, LightEntity):
}, },
] ]
if self._color_temp and ATTR_COLOR_TEMP_KELVIN in kwargs:
commands += [ commands += [
{ {
"code": self._color_temp.dpcode, "code": self._color_temp.dpcode,
@ -596,6 +607,7 @@ class TuyaLightEntity(TuyaEntity, LightEntity):
or ( or (
ATTR_BRIGHTNESS in kwargs ATTR_BRIGHTNESS in kwargs
and self.color_mode == ColorMode.HS and self.color_mode == ColorMode.HS
and ATTR_WHITE not in kwargs
and ATTR_COLOR_TEMP_KELVIN not in kwargs and ATTR_COLOR_TEMP_KELVIN not in kwargs
) )
): ):
@ -755,15 +767,15 @@ class TuyaLightEntity(TuyaEntity, LightEntity):
# The light supports only a single color mode, return it # The light supports only a single color mode, return it
return self._fixed_color_mode return self._fixed_color_mode
# The light supports both color temperature and HS, determine which mode the # The light supports both white (with or without adjustable color temperature)
# light is in. We consider it to be in HS color mode, when work mode is anything # and HS, determine which mode the light is in. We consider it to be in HS color
# else than "white". # mode, when work mode is anything else than "white".
if ( if (
self._color_mode_dpcode self._color_mode_dpcode
and self.device.status.get(self._color_mode_dpcode) != WorkMode.WHITE and self.device.status.get(self._color_mode_dpcode) != WorkMode.WHITE
): ):
return ColorMode.HS return ColorMode.HS
return ColorMode.COLOR_TEMP return self._white_color_mode
def _get_color_data(self) -> ColorData | None: def _get_color_data(self) -> ColorData | None:
"""Get current color data from device.""" """Get current color data from device."""

View File

@ -64,6 +64,7 @@
'capabilities': dict({ 'capabilities': dict({
'supported_color_modes': list([ 'supported_color_modes': list([
<ColorMode.HS: 'hs'>, <ColorMode.HS: 'hs'>,
<ColorMode.WHITE: 'white'>,
]), ]),
}), }),
'config_entry_id': <ANY>, 'config_entry_id': <ANY>,
@ -99,25 +100,16 @@
StateSnapshot({ StateSnapshot({
'attributes': ReadOnlyDict({ 'attributes': ReadOnlyDict({
'brightness': 138, 'brightness': 138,
'color_mode': <ColorMode.HS: 'hs'>, 'color_mode': <ColorMode.WHITE: 'white'>,
'friendly_name': 'Garage light', 'friendly_name': 'Garage light',
'hs_color': tuple( 'hs_color': None,
243.0, 'rgb_color': None,
86.0,
),
'rgb_color': tuple(
47,
36,
255,
),
'supported_color_modes': list([ 'supported_color_modes': list([
<ColorMode.HS: 'hs'>, <ColorMode.HS: 'hs'>,
<ColorMode.WHITE: 'white'>,
]), ]),
'supported_features': <LightEntityFeature: 0>, 'supported_features': <LightEntityFeature: 0>,
'xy_color': tuple( 'xy_color': None,
0.148,
0.055,
),
}), }),
'context': <ANY>, 'context': <ANY>,
'entity_id': 'light.garage_light', 'entity_id': 'light.garage_light',

View File

@ -8,6 +8,11 @@ import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from tuya_sharing import CustomerDevice from tuya_sharing import CustomerDevice
from homeassistant.components.light import (
DOMAIN as LIGHT_DOMAIN,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
)
from homeassistant.components.tuya import ManagerCompat from homeassistant.components.tuya import ManagerCompat
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -55,3 +60,66 @@ async def test_platform_setup_no_discovery(
assert not er.async_entries_for_config_entry( assert not er.async_entries_for_config_entry(
entity_registry, mock_config_entry.entry_id entity_registry, mock_config_entry.entry_id
) )
@pytest.mark.parametrize(
"mock_device_code",
["dj_smart_light_bulb"],
)
async def test_turn_on_white(
hass: HomeAssistant,
mock_manager: ManagerCompat,
mock_config_entry: MockConfigEntry,
mock_device: CustomerDevice,
) -> None:
"""Test turn_on service."""
entity_id = "light.garage_light"
await initialize_entry(hass, mock_manager, mock_config_entry, mock_device)
state = hass.states.get(entity_id)
assert state is not None, f"{entity_id} does not exist"
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{
"entity_id": entity_id,
"white": 150,
},
)
await hass.async_block_till_done()
mock_manager.send_commands.assert_called_once_with(
mock_device.id,
[
{"code": "switch_led", "value": True},
{"code": "work_mode", "value": "white"},
],
)
@pytest.mark.parametrize(
"mock_device_code",
["dj_smart_light_bulb"],
)
async def test_turn_off(
hass: HomeAssistant,
mock_manager: ManagerCompat,
mock_config_entry: MockConfigEntry,
mock_device: CustomerDevice,
) -> None:
"""Test turn_off service."""
entity_id = "light.garage_light"
await initialize_entry(hass, mock_manager, mock_config_entry, mock_device)
state = hass.states.get(entity_id)
assert state is not None, f"{entity_id} does not exist"
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
{
"entity_id": entity_id,
},
)
await hass.async_block_till_done()
mock_manager.send_commands.assert_called_once_with(
mock_device.id, [{"code": "switch_led", "value": False}]
)