Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions homeassistant/components/light/reproduce_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
STATE_ON,
)
from homeassistant.core import Context, HomeAssistant, State
from homeassistant.util import color as color_util

from . import (
ATTR_BRIGHTNESS,
ATTR_COLOR_MODE,
ATTR_COLOR_TEMP,
ATTR_COLOR_TEMP_KELVIN,
ATTR_EFFECT,
ATTR_HS_COLOR,
ATTR_RGB_COLOR,
Expand All @@ -40,6 +42,7 @@
COLOR_GROUP = [
ATTR_HS_COLOR,
ATTR_COLOR_TEMP,
ATTR_COLOR_TEMP_KELVIN,
ATTR_RGB_COLOR,
ATTR_RGBW_COLOR,
ATTR_RGBWW_COLOR,
Expand All @@ -55,7 +58,7 @@ class ColorModeAttr(NamedTuple):


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.RGB: ColorModeAttr(ATTR_RGB_COLOR, ATTR_RGB_COLOR),
ColorMode.RGBW: ColorModeAttr(ATTR_RGBW_COLOR, ATTR_RGBW_COLOR),
Expand Down Expand Up @@ -124,13 +127,25 @@ async def _async_reproduce_state(
color_mode = state.attributes[ATTR_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 (
color_mode != ColorMode.COLOR_TEMP
or (mireds := state.attributes.get(ATTR_COLOR_TEMP)) is None
):
_LOGGER.warning(
"Color mode %s specified but attribute %s missing for: %s",
color_mode,
cm_attr.state_attr,
state.entity_id,
)
return
_LOGGER.warning(
"Color mode %s specified but attribute %s missing for: %s",
"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,
)
return
cm_attr_state = color_util.color_temperature_mired_to_kelvin(mireds)
service_data[cm_attr.parameter] = cm_attr_state
else:
# Fall back to Choosing the first color that is specified
Expand Down
48 changes: 32 additions & 16 deletions tests/components/light/test_reproduce_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

VALID_BRIGHTNESS = {"brightness": 180}
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_RGB_COLOR = {"rgb_color": (255, 63, 111)}
VALID_RGBW_COLOR = {"rgbw_color": (255, 63, 111, 10)}
Expand All @@ -19,7 +19,7 @@

NONE_BRIGHTNESS = {"brightness": 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_RGB_COLOR = {"rgb_color": None}
NONE_RGBW_COLOR = {"rgbw_color": None}
Expand All @@ -34,7 +34,7 @@ async def test_reproducing_states(
hass.states.async_set("light.entity_off", "off", {})
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_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_rgb", "on", VALID_RGB_COLOR)
hass.states.async_set("light.entity_xy", "on", VALID_XY_COLOR)
Expand All @@ -49,7 +49,7 @@ async def test_reproducing_states(
State("light.entity_off", "off"),
State("light.entity_bright", "on", VALID_BRIGHTNESS),
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_rgb", "on", VALID_RGB_COLOR),
State("light.entity_xy", "on", VALID_XY_COLOR),
Expand All @@ -73,7 +73,7 @@ async def test_reproducing_states(
State("light.entity_xy", "off"),
State("light.entity_off", "on", VALID_BRIGHTNESS),
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_hs", "on", VALID_RGB_COLOR),
State("light.entity_rgb", "on", VALID_XY_COLOR),
Expand All @@ -92,7 +92,7 @@ async def test_reproducing_states(
expected_bright["entity_id"] = "light.entity_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_calls.append(expected_effect)

Expand Down Expand Up @@ -146,7 +146,7 @@ async def test_filter_color_modes(
"""Test filtering of parameters according to color mode."""
hass.states.async_set("light.entity", "off", {})
all_colors = {
**VALID_COLOR_TEMP,
**VALID_COLOR_TEMP_KELVIN,
**VALID_HS_COLOR,
**VALID_RGB_COLOR,
**VALID_RGBW_COLOR,
Expand All @@ -162,7 +162,7 @@ async def test_filter_color_modes(
)

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.HS: {**VALID_BRIGHTNESS, **VALID_HS_COLOR},
light.ColorMode.ONOFF: {**VALID_BRIGHTNESS},
Expand Down Expand Up @@ -201,13 +201,14 @@ async def test_filter_color_modes_missing_attributes(
hass.states.async_set("light.entity", "off", {})
expected_log = (
"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")

all_colors = {
**VALID_COLOR_TEMP,
**VALID_COLOR_TEMP_KELVIN,
**VALID_HS_COLOR,
**VALID_RGB_COLOR,
**VALID_RGBW_COLOR,
Expand All @@ -216,21 +217,35 @@ async def test_filter_color_modes_missing_attributes(
**VALID_BRIGHTNESS,
}

# Test missing `color_temp` attribute
# Test missing `color_temp_kelvin` attribute
stored_attributes = {**all_colors}
stored_attributes.pop("color_temp")
stored_attributes.pop("color_temp_kelvin")
caplog.clear()
await async_reproduce_state(
hass,
[State("light.entity", "on", {**stored_attributes, "color_mode": color_mode})],
)
assert len(turn_on_calls) == 0
assert expected_log in caplog.text
assert expected_fallback_log not in caplog.text

# Test with correct `color_temp` attribute
stored_attributes["color_temp"] = 240
expected = {"brightness": 180, "color_temp": 240}
# Test with deprecated `color_temp` attribute
stored_attributes["color_temp"] = 250
expected = {"brightness": 180, "color_temp_kelvin": 4000}
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(
hass,
[State("light.entity", "on", {**all_colors, "color_mode": color_mode})],
Expand All @@ -239,14 +254,15 @@ async def test_filter_color_modes_missing_attributes(
assert turn_on_calls[0].domain == "light"
assert dict(turn_on_calls[0].data) == {"entity_id": "light.entity", **expected}
assert expected_log not in caplog.text
assert expected_fallback_log not in caplog.text


@pytest.mark.parametrize(
"saved_state",
[
NONE_BRIGHTNESS,
NONE_EFFECT,
NONE_COLOR_TEMP,
NONE_COLOR_TEMP_KELVIN,
NONE_HS_COLOR,
NONE_RGB_COLOR,
NONE_RGBW_COLOR,
Expand Down