Refactor light reproduce state to use kelvin attribute (#132854)

This commit is contained in:
epenet 2024-12-12 21:17:05 +01:00 committed by GitHub
parent d79dc8d22f
commit b9a7307df8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 50 additions and 19 deletions

View File

@ -15,11 +15,13 @@ from homeassistant.const import (
STATE_ON, STATE_ON,
) )
from homeassistant.core import Context, HomeAssistant, State from homeassistant.core import Context, HomeAssistant, State
from homeassistant.util import color as color_util
from . import ( from . import (
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS,
ATTR_COLOR_MODE, ATTR_COLOR_MODE,
ATTR_COLOR_TEMP, ATTR_COLOR_TEMP,
ATTR_COLOR_TEMP_KELVIN,
ATTR_EFFECT, ATTR_EFFECT,
ATTR_HS_COLOR, ATTR_HS_COLOR,
ATTR_RGB_COLOR, ATTR_RGB_COLOR,
@ -40,6 +42,7 @@ ATTR_GROUP = [ATTR_BRIGHTNESS, ATTR_EFFECT]
COLOR_GROUP = [ COLOR_GROUP = [
ATTR_HS_COLOR, ATTR_HS_COLOR,
ATTR_COLOR_TEMP, ATTR_COLOR_TEMP,
ATTR_COLOR_TEMP_KELVIN,
ATTR_RGB_COLOR, ATTR_RGB_COLOR,
ATTR_RGBW_COLOR, ATTR_RGBW_COLOR,
ATTR_RGBWW_COLOR, ATTR_RGBWW_COLOR,
@ -55,7 +58,7 @@ class ColorModeAttr(NamedTuple):
COLOR_MODE_TO_ATTRIBUTE = { COLOR_MODE_TO_ATTRIBUTE = {
ColorMode.COLOR_TEMP: ColorModeAttr(ATTR_COLOR_TEMP, ATTR_COLOR_TEMP), ColorMode.COLOR_TEMP: ColorModeAttr(ATTR_COLOR_TEMP_KELVIN, ATTR_COLOR_TEMP_KELVIN),
ColorMode.HS: ColorModeAttr(ATTR_HS_COLOR, ATTR_HS_COLOR), ColorMode.HS: ColorModeAttr(ATTR_HS_COLOR, ATTR_HS_COLOR),
ColorMode.RGB: ColorModeAttr(ATTR_RGB_COLOR, ATTR_RGB_COLOR), ColorMode.RGB: ColorModeAttr(ATTR_RGB_COLOR, ATTR_RGB_COLOR),
ColorMode.RGBW: ColorModeAttr(ATTR_RGBW_COLOR, ATTR_RGBW_COLOR), ColorMode.RGBW: ColorModeAttr(ATTR_RGBW_COLOR, ATTR_RGBW_COLOR),
@ -124,6 +127,10 @@ async def _async_reproduce_state(
color_mode = state.attributes[ATTR_COLOR_MODE] color_mode = state.attributes[ATTR_COLOR_MODE]
if cm_attr := COLOR_MODE_TO_ATTRIBUTE.get(color_mode): if cm_attr := COLOR_MODE_TO_ATTRIBUTE.get(color_mode):
if (cm_attr_state := state.attributes.get(cm_attr.state_attr)) is None: if (cm_attr_state := state.attributes.get(cm_attr.state_attr)) is None:
if (
color_mode != ColorMode.COLOR_TEMP
or (mireds := state.attributes.get(ATTR_COLOR_TEMP)) is None
):
_LOGGER.warning( _LOGGER.warning(
"Color mode %s specified but attribute %s missing for: %s", "Color mode %s specified but attribute %s missing for: %s",
color_mode, color_mode,
@ -131,6 +138,14 @@ async def _async_reproduce_state(
state.entity_id, state.entity_id,
) )
return return
_LOGGER.warning(
"Color mode %s specified but attribute %s missing for: %s, "
"using color_temp (mireds) as fallback",
color_mode,
cm_attr.state_attr,
state.entity_id,
)
cm_attr_state = color_util.color_temperature_mired_to_kelvin(mireds)
service_data[cm_attr.parameter] = cm_attr_state service_data[cm_attr.parameter] = cm_attr_state
else: else:
# Fall back to Choosing the first color that is specified # Fall back to Choosing the first color that is specified

View File

@ -10,7 +10,7 @@ from tests.common import async_mock_service
VALID_BRIGHTNESS = {"brightness": 180} VALID_BRIGHTNESS = {"brightness": 180}
VALID_EFFECT = {"effect": "random"} VALID_EFFECT = {"effect": "random"}
VALID_COLOR_TEMP = {"color_temp": 240} VALID_COLOR_TEMP_KELVIN = {"color_temp_kelvin": 4200}
VALID_HS_COLOR = {"hs_color": (345, 75)} VALID_HS_COLOR = {"hs_color": (345, 75)}
VALID_RGB_COLOR = {"rgb_color": (255, 63, 111)} VALID_RGB_COLOR = {"rgb_color": (255, 63, 111)}
VALID_RGBW_COLOR = {"rgbw_color": (255, 63, 111, 10)} VALID_RGBW_COLOR = {"rgbw_color": (255, 63, 111, 10)}
@ -19,7 +19,7 @@ VALID_XY_COLOR = {"xy_color": (0.59, 0.274)}
NONE_BRIGHTNESS = {"brightness": None} NONE_BRIGHTNESS = {"brightness": None}
NONE_EFFECT = {"effect": None} NONE_EFFECT = {"effect": None}
NONE_COLOR_TEMP = {"color_temp": None} NONE_COLOR_TEMP_KELVIN = {"color_temp_kelvin": None}
NONE_HS_COLOR = {"hs_color": None} NONE_HS_COLOR = {"hs_color": None}
NONE_RGB_COLOR = {"rgb_color": None} NONE_RGB_COLOR = {"rgb_color": None}
NONE_RGBW_COLOR = {"rgbw_color": None} NONE_RGBW_COLOR = {"rgbw_color": None}
@ -34,7 +34,7 @@ async def test_reproducing_states(
hass.states.async_set("light.entity_off", "off", {}) hass.states.async_set("light.entity_off", "off", {})
hass.states.async_set("light.entity_bright", "on", VALID_BRIGHTNESS) hass.states.async_set("light.entity_bright", "on", VALID_BRIGHTNESS)
hass.states.async_set("light.entity_effect", "on", VALID_EFFECT) hass.states.async_set("light.entity_effect", "on", VALID_EFFECT)
hass.states.async_set("light.entity_temp", "on", VALID_COLOR_TEMP) hass.states.async_set("light.entity_temp", "on", VALID_COLOR_TEMP_KELVIN)
hass.states.async_set("light.entity_hs", "on", VALID_HS_COLOR) hass.states.async_set("light.entity_hs", "on", VALID_HS_COLOR)
hass.states.async_set("light.entity_rgb", "on", VALID_RGB_COLOR) hass.states.async_set("light.entity_rgb", "on", VALID_RGB_COLOR)
hass.states.async_set("light.entity_xy", "on", VALID_XY_COLOR) hass.states.async_set("light.entity_xy", "on", VALID_XY_COLOR)
@ -49,7 +49,7 @@ async def test_reproducing_states(
State("light.entity_off", "off"), State("light.entity_off", "off"),
State("light.entity_bright", "on", VALID_BRIGHTNESS), State("light.entity_bright", "on", VALID_BRIGHTNESS),
State("light.entity_effect", "on", VALID_EFFECT), State("light.entity_effect", "on", VALID_EFFECT),
State("light.entity_temp", "on", VALID_COLOR_TEMP), State("light.entity_temp", "on", VALID_COLOR_TEMP_KELVIN),
State("light.entity_hs", "on", VALID_HS_COLOR), State("light.entity_hs", "on", VALID_HS_COLOR),
State("light.entity_rgb", "on", VALID_RGB_COLOR), State("light.entity_rgb", "on", VALID_RGB_COLOR),
State("light.entity_xy", "on", VALID_XY_COLOR), State("light.entity_xy", "on", VALID_XY_COLOR),
@ -73,7 +73,7 @@ async def test_reproducing_states(
State("light.entity_xy", "off"), State("light.entity_xy", "off"),
State("light.entity_off", "on", VALID_BRIGHTNESS), State("light.entity_off", "on", VALID_BRIGHTNESS),
State("light.entity_bright", "on", VALID_EFFECT), State("light.entity_bright", "on", VALID_EFFECT),
State("light.entity_effect", "on", VALID_COLOR_TEMP), State("light.entity_effect", "on", VALID_COLOR_TEMP_KELVIN),
State("light.entity_temp", "on", VALID_HS_COLOR), State("light.entity_temp", "on", VALID_HS_COLOR),
State("light.entity_hs", "on", VALID_RGB_COLOR), State("light.entity_hs", "on", VALID_RGB_COLOR),
State("light.entity_rgb", "on", VALID_XY_COLOR), State("light.entity_rgb", "on", VALID_XY_COLOR),
@ -92,7 +92,7 @@ async def test_reproducing_states(
expected_bright["entity_id"] = "light.entity_bright" expected_bright["entity_id"] = "light.entity_bright"
expected_calls.append(expected_bright) expected_calls.append(expected_bright)
expected_effect = dict(VALID_COLOR_TEMP) expected_effect = dict(VALID_COLOR_TEMP_KELVIN)
expected_effect["entity_id"] = "light.entity_effect" expected_effect["entity_id"] = "light.entity_effect"
expected_calls.append(expected_effect) expected_calls.append(expected_effect)
@ -146,7 +146,7 @@ async def test_filter_color_modes(
"""Test filtering of parameters according to color mode.""" """Test filtering of parameters according to color mode."""
hass.states.async_set("light.entity", "off", {}) hass.states.async_set("light.entity", "off", {})
all_colors = { all_colors = {
**VALID_COLOR_TEMP, **VALID_COLOR_TEMP_KELVIN,
**VALID_HS_COLOR, **VALID_HS_COLOR,
**VALID_RGB_COLOR, **VALID_RGB_COLOR,
**VALID_RGBW_COLOR, **VALID_RGBW_COLOR,
@ -162,7 +162,7 @@ async def test_filter_color_modes(
) )
expected_map = { expected_map = {
light.ColorMode.COLOR_TEMP: {**VALID_BRIGHTNESS, **VALID_COLOR_TEMP}, light.ColorMode.COLOR_TEMP: {**VALID_BRIGHTNESS, **VALID_COLOR_TEMP_KELVIN},
light.ColorMode.BRIGHTNESS: VALID_BRIGHTNESS, light.ColorMode.BRIGHTNESS: VALID_BRIGHTNESS,
light.ColorMode.HS: {**VALID_BRIGHTNESS, **VALID_HS_COLOR}, light.ColorMode.HS: {**VALID_BRIGHTNESS, **VALID_HS_COLOR},
light.ColorMode.ONOFF: {**VALID_BRIGHTNESS}, light.ColorMode.ONOFF: {**VALID_BRIGHTNESS},
@ -201,13 +201,14 @@ async def test_filter_color_modes_missing_attributes(
hass.states.async_set("light.entity", "off", {}) hass.states.async_set("light.entity", "off", {})
expected_log = ( expected_log = (
"Color mode color_temp specified " "Color mode color_temp specified "
"but attribute color_temp missing for: light.entity" "but attribute color_temp_kelvin missing for: light.entity"
) )
expected_fallback_log = "using color_temp (mireds) as fallback"
turn_on_calls = async_mock_service(hass, "light", "turn_on") turn_on_calls = async_mock_service(hass, "light", "turn_on")
all_colors = { all_colors = {
**VALID_COLOR_TEMP, **VALID_COLOR_TEMP_KELVIN,
**VALID_HS_COLOR, **VALID_HS_COLOR,
**VALID_RGB_COLOR, **VALID_RGB_COLOR,
**VALID_RGBW_COLOR, **VALID_RGBW_COLOR,
@ -216,9 +217,9 @@ async def test_filter_color_modes_missing_attributes(
**VALID_BRIGHTNESS, **VALID_BRIGHTNESS,
} }
# Test missing `color_temp` attribute # Test missing `color_temp_kelvin` attribute
stored_attributes = {**all_colors} stored_attributes = {**all_colors}
stored_attributes.pop("color_temp") stored_attributes.pop("color_temp_kelvin")
caplog.clear() caplog.clear()
await async_reproduce_state( await async_reproduce_state(
hass, hass,
@ -226,11 +227,25 @@ async def test_filter_color_modes_missing_attributes(
) )
assert len(turn_on_calls) == 0 assert len(turn_on_calls) == 0
assert expected_log in caplog.text assert expected_log in caplog.text
assert expected_fallback_log not in caplog.text
# Test with correct `color_temp` attribute # Test with deprecated `color_temp` attribute
stored_attributes["color_temp"] = 240 stored_attributes["color_temp"] = 250
expected = {"brightness": 180, "color_temp": 240} expected = {"brightness": 180, "color_temp_kelvin": 4000}
caplog.clear() caplog.clear()
await async_reproduce_state(
hass,
[State("light.entity", "on", {**stored_attributes, "color_mode": color_mode})],
)
assert len(turn_on_calls) == 1
assert expected_log in caplog.text
assert expected_fallback_log in caplog.text
# Test with correct `color_temp_kelvin` attribute
expected = {"brightness": 180, "color_temp_kelvin": 4200}
caplog.clear()
turn_on_calls.clear()
await async_reproduce_state( await async_reproduce_state(
hass, hass,
[State("light.entity", "on", {**all_colors, "color_mode": color_mode})], [State("light.entity", "on", {**all_colors, "color_mode": color_mode})],
@ -239,6 +254,7 @@ async def test_filter_color_modes_missing_attributes(
assert turn_on_calls[0].domain == "light" assert turn_on_calls[0].domain == "light"
assert dict(turn_on_calls[0].data) == {"entity_id": "light.entity", **expected} assert dict(turn_on_calls[0].data) == {"entity_id": "light.entity", **expected}
assert expected_log not in caplog.text assert expected_log not in caplog.text
assert expected_fallback_log not in caplog.text
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -246,7 +262,7 @@ async def test_filter_color_modes_missing_attributes(
[ [
NONE_BRIGHTNESS, NONE_BRIGHTNESS,
NONE_EFFECT, NONE_EFFECT,
NONE_COLOR_TEMP, NONE_COLOR_TEMP_KELVIN,
NONE_HS_COLOR, NONE_HS_COLOR,
NONE_RGB_COLOR, NONE_RGB_COLOR,
NONE_RGBW_COLOR, NONE_RGBW_COLOR,