mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 17:27:52 +00:00
Add temperature to the light color mode parameter fallbacks (#86026)
* Add color temperature to the color mode fallbacks * Manually add ATTR_COLOR_TEMP since ATTR_COLOR_TEMP_KELVIN is pre-parsed * Include the legacy ATTR_COLOR_TEMP attribute in the tests * Apply suggestions from code review Co-authored-by: Erik Montnemery <erik@montnemery.com> * Add citation for McCamy's approximation formula If still existing, also see page 3 of https://www.st.com/resource/en/application_note/an5638-how-correlated-color-temperature-is-calculated-by-vd6283-stmicroelectronics.pdf * Update homeassistant/util/color.py --------- Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
parent
7ec2980e52
commit
0232c8dcb0
@ -500,6 +500,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
|
||||
)
|
||||
elif ColorMode.XY in supported_color_modes:
|
||||
params[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color)
|
||||
elif ColorMode.COLOR_TEMP in supported_color_modes:
|
||||
xy_color = color_util.color_hs_to_xy(*hs_color)
|
||||
params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature(
|
||||
*xy_color
|
||||
)
|
||||
params[ATTR_COLOR_TEMP] = color_util.color_temperature_kelvin_to_mired(
|
||||
params[ATTR_COLOR_TEMP_KELVIN]
|
||||
)
|
||||
elif ATTR_RGB_COLOR in params and ColorMode.RGB not in supported_color_modes:
|
||||
assert (rgb_color := params.pop(ATTR_RGB_COLOR)) is not None
|
||||
if ColorMode.RGBW in supported_color_modes:
|
||||
@ -515,6 +523,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
|
||||
params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
|
||||
elif ColorMode.XY in supported_color_modes:
|
||||
params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color)
|
||||
elif ColorMode.COLOR_TEMP in supported_color_modes:
|
||||
xy_color = color_util.color_RGB_to_xy(*rgb_color)
|
||||
params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature(
|
||||
*xy_color
|
||||
)
|
||||
params[ATTR_COLOR_TEMP] = color_util.color_temperature_kelvin_to_mired(
|
||||
params[ATTR_COLOR_TEMP_KELVIN]
|
||||
)
|
||||
elif ATTR_XY_COLOR in params and ColorMode.XY not in supported_color_modes:
|
||||
xy_color = params.pop(ATTR_XY_COLOR)
|
||||
if ColorMode.HS in supported_color_modes:
|
||||
@ -529,6 +545,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
|
||||
params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww(
|
||||
*rgb_color, light.min_color_temp_kelvin, light.max_color_temp_kelvin
|
||||
)
|
||||
elif ColorMode.COLOR_TEMP in supported_color_modes:
|
||||
params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature(
|
||||
*xy_color
|
||||
)
|
||||
params[ATTR_COLOR_TEMP] = color_util.color_temperature_kelvin_to_mired(
|
||||
params[ATTR_COLOR_TEMP_KELVIN]
|
||||
)
|
||||
elif ATTR_RGBW_COLOR in params and ColorMode.RGBW not in supported_color_modes:
|
||||
rgbw_color = params.pop(ATTR_RGBW_COLOR)
|
||||
rgb_color = color_util.color_rgbw_to_rgb(*rgbw_color)
|
||||
@ -542,6 +565,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
|
||||
params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
|
||||
elif ColorMode.XY in supported_color_modes:
|
||||
params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color)
|
||||
elif ColorMode.COLOR_TEMP in supported_color_modes:
|
||||
xy_color = color_util.color_RGB_to_xy(*rgb_color)
|
||||
params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature(
|
||||
*xy_color
|
||||
)
|
||||
params[ATTR_COLOR_TEMP] = color_util.color_temperature_kelvin_to_mired(
|
||||
params[ATTR_COLOR_TEMP_KELVIN]
|
||||
)
|
||||
elif (
|
||||
ATTR_RGBWW_COLOR in params and ColorMode.RGBWW not in supported_color_modes
|
||||
):
|
||||
@ -558,6 +589,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
|
||||
params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
|
||||
elif ColorMode.XY in supported_color_modes:
|
||||
params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color)
|
||||
elif ColorMode.COLOR_TEMP in supported_color_modes:
|
||||
xy_color = color_util.color_RGB_to_xy(*rgb_color)
|
||||
params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature(
|
||||
*xy_color
|
||||
)
|
||||
params[ATTR_COLOR_TEMP] = color_util.color_temperature_kelvin_to_mired(
|
||||
params[ATTR_COLOR_TEMP_KELVIN]
|
||||
)
|
||||
|
||||
# If white is set to True, set it to the light's brightness
|
||||
# Add a warning in Home Assistant Core 2023.5 if the brightness is set to an
|
||||
|
@ -576,6 +576,18 @@ def _white_levels_to_color_temperature(
|
||||
), min(255, round(brightness * 255))
|
||||
|
||||
|
||||
def color_xy_to_temperature(x: float, y: float) -> int:
|
||||
"""Convert an xy color to a color temperature in Kelvin.
|
||||
|
||||
Uses McCamy's approximation (https://doi.org/10.1002/col.5080170211),
|
||||
close enough for uses between 2000 K and 10000 K.
|
||||
"""
|
||||
n = (x - 0.3320) / (0.1858 - y)
|
||||
CCT = 437 * (n**3) + 3601 * (n**2) + 6861 * n + 5517
|
||||
|
||||
return int(CCT)
|
||||
|
||||
|
||||
def _clamp(color_component: float, minimum: float = 0, maximum: float = 255) -> float:
|
||||
"""Clamp the given color component value between the given min and max values.
|
||||
|
||||
|
@ -1444,6 +1444,7 @@ async def test_light_service_call_color_conversion(
|
||||
platform.ENTITIES.append(platform.MockLight("Test_legacy", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_rgbw", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_rgbww", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_temperature", STATE_ON))
|
||||
|
||||
entity0 = platform.ENTITIES[0]
|
||||
entity0.supported_color_modes = {light.ColorMode.HS}
|
||||
@ -1470,6 +1471,9 @@ async def test_light_service_call_color_conversion(
|
||||
entity6 = platform.ENTITIES[6]
|
||||
entity6.supported_color_modes = {light.ColorMode.RGBWW}
|
||||
|
||||
entity7 = platform.ENTITIES[7]
|
||||
entity7.supported_color_modes = {light.ColorMode.COLOR_TEMP}
|
||||
|
||||
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@ -1498,6 +1502,9 @@ async def test_light_service_call_color_conversion(
|
||||
state = hass.states.get(entity6.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.ColorMode.RGBWW]
|
||||
|
||||
state = hass.states.get(entity7.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.ColorMode.COLOR_TEMP]
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
@ -1510,6 +1517,7 @@ async def test_light_service_call_color_conversion(
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
entity7.entity_id,
|
||||
],
|
||||
"brightness_pct": 100,
|
||||
"hs_color": (240, 100),
|
||||
@ -1530,6 +1538,8 @@ async def test_light_service_call_color_conversion(
|
||||
assert data == {"brightness": 255, "rgbw_color": (0, 0, 255, 0)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
assert data == {"brightness": 255, "rgbww_color": (0, 0, 255, 0, 0)}
|
||||
_, data = entity7.last_call("turn_on")
|
||||
assert data == {"brightness": 255, "color_temp_kelvin": 1739, "color_temp": 575}
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
@ -1543,6 +1553,7 @@ async def test_light_service_call_color_conversion(
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
entity7.entity_id,
|
||||
],
|
||||
"brightness_pct": 100,
|
||||
"hs_color": (240, 0),
|
||||
@ -1564,6 +1575,8 @@ async def test_light_service_call_color_conversion(
|
||||
_, data = entity6.last_call("turn_on")
|
||||
# The midpoint of the the white channels is warm, compensated by adding green + blue
|
||||
assert data == {"brightness": 255, "rgbww_color": (0, 76, 141, 255, 255)}
|
||||
_, data = entity7.last_call("turn_on")
|
||||
assert data == {"brightness": 255, "color_temp_kelvin": 5962, "color_temp": 167}
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
@ -1577,6 +1590,7 @@ async def test_light_service_call_color_conversion(
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
entity7.entity_id,
|
||||
],
|
||||
"brightness_pct": 50,
|
||||
"rgb_color": (128, 0, 0),
|
||||
@ -1597,6 +1611,8 @@ async def test_light_service_call_color_conversion(
|
||||
assert data == {"brightness": 128, "rgbw_color": (128, 0, 0, 0)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbww_color": (128, 0, 0, 0, 0)}
|
||||
_, data = entity7.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "color_temp_kelvin": 6279, "color_temp": 159}
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
@ -1610,6 +1626,7 @@ async def test_light_service_call_color_conversion(
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
entity7.entity_id,
|
||||
],
|
||||
"brightness_pct": 50,
|
||||
"rgb_color": (255, 255, 255),
|
||||
@ -1631,6 +1648,8 @@ async def test_light_service_call_color_conversion(
|
||||
_, data = entity6.last_call("turn_on")
|
||||
# The midpoint the the white channels is warm, compensated by adding green + blue
|
||||
assert data == {"brightness": 128, "rgbww_color": (0, 76, 141, 255, 255)}
|
||||
_, data = entity7.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "color_temp_kelvin": 5962, "color_temp": 167}
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
@ -1644,6 +1663,7 @@ async def test_light_service_call_color_conversion(
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
entity7.entity_id,
|
||||
],
|
||||
"brightness_pct": 50,
|
||||
"xy_color": (0.1, 0.8),
|
||||
@ -1664,6 +1684,8 @@ async def test_light_service_call_color_conversion(
|
||||
assert data == {"brightness": 128, "rgbw_color": (0, 255, 22, 0)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbww_color": (0, 255, 22, 0, 0)}
|
||||
_, data = entity7.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "color_temp_kelvin": 8645, "color_temp": 115}
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
@ -1677,6 +1699,7 @@ async def test_light_service_call_color_conversion(
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
entity7.entity_id,
|
||||
],
|
||||
"brightness_pct": 50,
|
||||
"xy_color": (0.323, 0.329),
|
||||
@ -1698,6 +1721,8 @@ async def test_light_service_call_color_conversion(
|
||||
_, data = entity6.last_call("turn_on")
|
||||
# The midpoint the the white channels is warm, compensated by adding green + blue
|
||||
assert data == {"brightness": 128, "rgbww_color": (0, 75, 140, 255, 255)}
|
||||
_, data = entity7.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "color_temp_kelvin": 5962, "color_temp": 167}
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
@ -1711,6 +1736,7 @@ async def test_light_service_call_color_conversion(
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
entity7.entity_id,
|
||||
],
|
||||
"brightness_pct": 50,
|
||||
"rgbw_color": (128, 0, 0, 64),
|
||||
@ -1732,6 +1758,8 @@ async def test_light_service_call_color_conversion(
|
||||
_, data = entity6.last_call("turn_on")
|
||||
# The midpoint the the white channels is warm, compensated by adding green + blue
|
||||
assert data == {"brightness": 128, "rgbww_color": (128, 0, 30, 117, 117)}
|
||||
_, data = entity7.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "color_temp_kelvin": 3011, "color_temp": 332}
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
@ -1745,6 +1773,7 @@ async def test_light_service_call_color_conversion(
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
entity7.entity_id,
|
||||
],
|
||||
"brightness_pct": 50,
|
||||
"rgbw_color": (255, 255, 255, 255),
|
||||
@ -1766,6 +1795,8 @@ async def test_light_service_call_color_conversion(
|
||||
_, data = entity6.last_call("turn_on")
|
||||
# The midpoint the the white channels is warm, compensated by adding green + blue
|
||||
assert data == {"brightness": 128, "rgbww_color": (0, 76, 141, 255, 255)}
|
||||
_, data = entity7.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "color_temp_kelvin": 5962, "color_temp": 167}
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
@ -1779,6 +1810,7 @@ async def test_light_service_call_color_conversion(
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
entity7.entity_id,
|
||||
],
|
||||
"brightness_pct": 50,
|
||||
"rgbww_color": (128, 0, 0, 64, 32),
|
||||
@ -1799,6 +1831,8 @@ async def test_light_service_call_color_conversion(
|
||||
assert data == {"brightness": 128, "rgbw_color": (128, 9, 0, 33)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbww_color": (128, 0, 0, 64, 32)}
|
||||
_, data = entity7.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "color_temp_kelvin": 3845, "color_temp": 260}
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
@ -1812,6 +1846,7 @@ async def test_light_service_call_color_conversion(
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
entity7.entity_id,
|
||||
],
|
||||
"brightness_pct": 50,
|
||||
"rgbww_color": (255, 255, 255, 255, 255),
|
||||
@ -1833,6 +1868,8 @@ async def test_light_service_call_color_conversion(
|
||||
assert data == {"brightness": 128, "rgbw_color": (96, 44, 0, 255)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbww_color": (255, 255, 255, 255, 255)}
|
||||
_, data = entity7.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "color_temp_kelvin": 3451, "color_temp": 289}
|
||||
|
||||
|
||||
async def test_light_service_call_color_conversion_named_tuple(
|
||||
|
@ -270,6 +270,15 @@ def test_color_rgbw_to_rgb() -> None:
|
||||
assert color_util.color_rgbw_to_rgb(0, 0, 0, 127) == (127, 127, 127)
|
||||
|
||||
|
||||
def test_color_xy_to_temperature() -> None:
|
||||
"""Test color_xy_to_temperature."""
|
||||
assert color_util.color_xy_to_temperature(0.5119, 0.4147) == 2136
|
||||
assert color_util.color_xy_to_temperature(0.368, 0.3686) == 4302
|
||||
assert color_util.color_xy_to_temperature(0.4448, 0.4066) == 2893
|
||||
assert color_util.color_xy_to_temperature(0.1, 0.8) == 8645
|
||||
assert color_util.color_xy_to_temperature(0.5, 0.4) == 2140
|
||||
|
||||
|
||||
def test_color_rgb_to_hex() -> None:
|
||||
"""Test color_rgb_to_hex."""
|
||||
assert color_util.color_rgb_to_hex(255, 255, 255) == "ffffff"
|
||||
|
Loading…
x
Reference in New Issue
Block a user