Add color_mode support to MQTT JSON light (#47993)

pull/48542/head
Erik Montnemery 2021-03-31 11:28:45 +02:00 committed by GitHub
parent 64d5dd1f6b
commit c7584a1f92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 705 additions and 58 deletions

View File

@ -24,6 +24,7 @@ ABBREVIATIONS = {
"bat_lev_tpl": "battery_level_template",
"chrg_t": "charging_topic",
"chrg_tpl": "charging_template",
"clrm": "color_mode",
"clr_temp_cmd_t": "color_temp_command_topic",
"clr_temp_stat_t": "color_temp_state_topic",
"clr_temp_tpl": "color_temp_template",
@ -169,6 +170,7 @@ ABBREVIATIONS = {
"stat_val_tpl": "state_value_template",
"stype": "subtype",
"sup_feat": "supported_features",
"sup_clrm": "supported_color_modes",
"swing_mode_cmd_tpl": "swing_mode_command_template",
"swing_mode_cmd_t": "swing_mode_command_topic",
"swing_mode_stat_tpl": "swing_mode_state_template",

View File

@ -7,12 +7,23 @@ import voluptuous as vol
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_MODE,
ATTR_COLOR_TEMP,
ATTR_EFFECT,
ATTR_FLASH,
ATTR_HS_COLOR,
ATTR_RGB_COLOR,
ATTR_RGBW_COLOR,
ATTR_RGBWW_COLOR,
ATTR_TRANSITION,
ATTR_WHITE_VALUE,
ATTR_XY_COLOR,
COLOR_MODE_COLOR_TEMP,
COLOR_MODE_HS,
COLOR_MODE_RGB,
COLOR_MODE_RGBW,
COLOR_MODE_RGBWW,
COLOR_MODE_XY,
FLASH_LONG,
FLASH_SHORT,
SUPPORT_BRIGHTNESS,
@ -22,6 +33,7 @@ from homeassistant.components.light import (
SUPPORT_FLASH,
SUPPORT_TRANSITION,
SUPPORT_WHITE_VALUE,
VALID_COLOR_MODES,
LightEntity,
)
from homeassistant.const import (
@ -54,6 +66,7 @@ _LOGGER = logging.getLogger(__name__)
DOMAIN = "mqtt_json"
DEFAULT_BRIGHTNESS = False
DEFAULT_COLOR_MODE = False
DEFAULT_COLOR_TEMP = False
DEFAULT_EFFECT = False
DEFAULT_FLASH_TIME_LONG = 10
@ -66,6 +79,9 @@ DEFAULT_XY = False
DEFAULT_HS = False
DEFAULT_BRIGHTNESS_SCALE = 255
CONF_COLOR_MODE = "color_mode"
CONF_SUPPORTED_COLOR_MODES = "supported_color_modes"
CONF_EFFECT_LIST = "effect_list"
CONF_FLASH_TIME_LONG = "flash_time_long"
@ -74,14 +90,25 @@ CONF_FLASH_TIME_SHORT = "flash_time_short"
CONF_MAX_MIREDS = "max_mireds"
CONF_MIN_MIREDS = "min_mireds"
# Stealing some of these from the base MQTT configs.
PLATFORM_SCHEMA_JSON = (
def valid_color_configuration(config):
"""Test color_mode is not combined with deprecated config."""
deprecated = {CONF_COLOR_TEMP, CONF_HS, CONF_RGB, CONF_WHITE_VALUE, CONF_XY}
if config[CONF_COLOR_MODE] and any(config.get(key) for key in deprecated):
raise vol.Invalid(f"color_mode must not be combined with any of {deprecated}")
return config
PLATFORM_SCHEMA_JSON = vol.All(
mqtt.MQTT_RW_PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_BRIGHTNESS, default=DEFAULT_BRIGHTNESS): cv.boolean,
vol.Optional(
CONF_BRIGHTNESS_SCALE, default=DEFAULT_BRIGHTNESS_SCALE
): vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Inclusive(
CONF_COLOR_MODE, "color_mode", default=DEFAULT_COLOR_MODE
): cv.boolean,
vol.Optional(CONF_COLOR_TEMP, default=DEFAULT_COLOR_TEMP): cv.boolean,
vol.Optional(CONF_EFFECT, default=DEFAULT_EFFECT): cv.boolean,
vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]),
@ -102,12 +129,16 @@ PLATFORM_SCHEMA_JSON = (
vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean,
vol.Optional(CONF_RGB, default=DEFAULT_RGB): cv.boolean,
vol.Optional(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Inclusive(CONF_SUPPORTED_COLOR_MODES, "color_mode"): vol.All(
cv.ensure_list, [vol.In(VALID_COLOR_MODES)], vol.Unique()
),
vol.Optional(CONF_WHITE_VALUE, default=DEFAULT_WHITE_VALUE): cv.boolean,
vol.Optional(CONF_XY, default=DEFAULT_XY): cv.boolean,
}
},
)
.extend(MQTT_ENTITY_COMMON_SCHEMA.schema)
.extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema)
.extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema),
valid_color_configuration,
)
@ -129,11 +160,16 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
self._topic = None
self._optimistic = False
self._brightness = None
self._color_mode = None
self._color_temp = None
self._effect = None
self._hs = None
self._white_value = None
self._flash_times = None
self._hs = None
self._rgb = None
self._rgbw = None
self._rgbww = None
self._white_value = None
self._xy = None
MqttEntity.__init__(self, None, config, config_entry, discovery_data)
@ -156,50 +192,90 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
}
self._supported_features = SUPPORT_TRANSITION | SUPPORT_FLASH
self._supported_features |= config[CONF_RGB] and SUPPORT_COLOR
self._supported_features |= config[CONF_BRIGHTNESS] and SUPPORT_BRIGHTNESS
self._supported_features |= config[CONF_COLOR_TEMP] and SUPPORT_COLOR_TEMP
self._supported_features |= config[CONF_EFFECT] and SUPPORT_EFFECT
self._supported_features |= config[CONF_WHITE_VALUE] and SUPPORT_WHITE_VALUE
self._supported_features |= config[CONF_XY] and SUPPORT_COLOR
self._supported_features |= config[CONF_HS] and SUPPORT_COLOR
if not self._config[CONF_COLOR_MODE]:
self._supported_features |= config[CONF_BRIGHTNESS] and SUPPORT_BRIGHTNESS
self._supported_features |= config[CONF_COLOR_TEMP] and SUPPORT_COLOR_TEMP
self._supported_features |= config[CONF_HS] and SUPPORT_COLOR
self._supported_features |= config[CONF_RGB] and SUPPORT_COLOR
self._supported_features |= config[CONF_WHITE_VALUE] and SUPPORT_WHITE_VALUE
self._supported_features |= config[CONF_XY] and SUPPORT_COLOR
def _parse_color(self, values):
try:
red = int(values["color"]["r"])
green = int(values["color"]["g"])
blue = int(values["color"]["b"])
def _update_color(self, values):
if not self._config[CONF_COLOR_MODE]:
# Deprecated color handling
try:
red = int(values["color"]["r"])
green = int(values["color"]["g"])
blue = int(values["color"]["b"])
self._hs = color_util.color_RGB_to_hs(red, green, blue)
except KeyError:
pass
except ValueError:
_LOGGER.warning("Invalid RGB color value received")
return
return color_util.color_RGB_to_hs(red, green, blue)
except KeyError:
pass
except ValueError:
_LOGGER.warning("Invalid RGB color value received")
return self._hs
try:
x_color = float(values["color"]["x"])
y_color = float(values["color"]["y"])
self._hs = color_util.color_xy_to_hs(x_color, y_color)
except KeyError:
pass
except ValueError:
_LOGGER.warning("Invalid XY color value received")
return
try:
x_color = float(values["color"]["x"])
y_color = float(values["color"]["y"])
return color_util.color_xy_to_hs(x_color, y_color)
except KeyError:
pass
except ValueError:
_LOGGER.warning("Invalid XY color value received")
return self._hs
try:
hue = float(values["color"]["h"])
saturation = float(values["color"]["s"])
return (hue, saturation)
except KeyError:
pass
except ValueError:
_LOGGER.warning("Invalid HS color value received")
return self._hs
return self._hs
try:
hue = float(values["color"]["h"])
saturation = float(values["color"]["s"])
self._hs = (hue, saturation)
except KeyError:
pass
except ValueError:
_LOGGER.warning("Invalid HS color value received")
return
else:
color_mode = values["color_mode"]
if not self._supports_color_mode(color_mode):
_LOGGER.warning("Invalid color mode received")
return
try:
if color_mode == COLOR_MODE_COLOR_TEMP:
self._color_temp = int(values["color_temp"])
self._color_mode = COLOR_MODE_COLOR_TEMP
elif color_mode == COLOR_MODE_HS:
hue = float(values["color"]["h"])
saturation = float(values["color"]["s"])
self._color_mode = COLOR_MODE_HS
self._hs = (hue, saturation)
elif color_mode == COLOR_MODE_RGB:
r = int(values["color"]["r"]) # pylint: disable=invalid-name
g = int(values["color"]["g"]) # pylint: disable=invalid-name
b = int(values["color"]["b"]) # pylint: disable=invalid-name
self._color_mode = COLOR_MODE_RGB
self._rgb = (r, g, b)
elif color_mode == COLOR_MODE_RGBW:
r = int(values["color"]["r"]) # pylint: disable=invalid-name
g = int(values["color"]["g"]) # pylint: disable=invalid-name
b = int(values["color"]["b"]) # pylint: disable=invalid-name
w = int(values["color"]["w"]) # pylint: disable=invalid-name
self._color_mode = COLOR_MODE_RGBW
self._rgbw = (r, g, b, w)
elif color_mode == COLOR_MODE_RGBWW:
r = int(values["color"]["r"]) # pylint: disable=invalid-name
g = int(values["color"]["g"]) # pylint: disable=invalid-name
b = int(values["color"]["b"]) # pylint: disable=invalid-name
c = int(values["color"]["c"]) # pylint: disable=invalid-name
w = int(values["color"]["w"]) # pylint: disable=invalid-name
self._color_mode = COLOR_MODE_RGBWW
self._rgbww = (r, g, b, c, w)
elif color_mode == COLOR_MODE_XY:
x = float(values["color"]["x"]) # pylint: disable=invalid-name
y = float(values["color"]["y"]) # pylint: disable=invalid-name
self._color_mode = COLOR_MODE_XY
self._xy = (x, y)
except (KeyError, ValueError):
_LOGGER.warning("Invalid or incomplete color value received")
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""
@ -220,7 +296,10 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
if values["color"] is None:
self._hs = None
else:
self._hs = self._parse_color(values)
self._update_color(values)
if self._config[CONF_COLOR_MODE] and "color_mode" in values:
self._update_color(values)
if self._supported_features and SUPPORT_BRIGHTNESS:
try:
@ -234,7 +313,11 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
except (TypeError, ValueError):
_LOGGER.warning("Invalid brightness value received")
if self._supported_features and SUPPORT_COLOR_TEMP:
if (
self._supported_features
and SUPPORT_COLOR_TEMP
and not self._config[CONF_COLOR_MODE]
):
try:
if values["color_temp"] is None:
self._color_temp = None
@ -274,16 +357,17 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
if self._optimistic and last_state:
self._state = last_state.state == STATE_ON
if last_state.attributes.get(ATTR_BRIGHTNESS):
self._brightness = last_state.attributes.get(ATTR_BRIGHTNESS)
if last_state.attributes.get(ATTR_HS_COLOR):
self._hs = last_state.attributes.get(ATTR_HS_COLOR)
if last_state.attributes.get(ATTR_COLOR_TEMP):
self._color_temp = last_state.attributes.get(ATTR_COLOR_TEMP)
if last_state.attributes.get(ATTR_EFFECT):
self._effect = last_state.attributes.get(ATTR_EFFECT)
if last_state.attributes.get(ATTR_WHITE_VALUE):
self._white_value = last_state.attributes.get(ATTR_WHITE_VALUE)
last_attributes = last_state.attributes
self._brightness = last_attributes.get(ATTR_BRIGHTNESS, self._brightness)
self._color_mode = last_attributes.get(ATTR_COLOR_MODE, self._color_mode)
self._color_temp = last_attributes.get(ATTR_COLOR_TEMP, self._color_temp)
self._effect = last_attributes.get(ATTR_EFFECT, self._effect)
self._hs = last_attributes.get(ATTR_HS_COLOR, self._hs)
self._rgb = last_attributes.get(ATTR_RGB_COLOR, self._rgb)
self._rgbw = last_attributes.get(ATTR_RGBW_COLOR, self._rgbw)
self._rgbww = last_attributes.get(ATTR_RGBWW_COLOR, self._rgbww)
self._white_value = last_attributes.get(ATTR_WHITE_VALUE, self._white_value)
self._xy = last_attributes.get(ATTR_XY_COLOR, self._xy)
@property
def brightness(self):
@ -320,6 +404,26 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
"""Return the hs color value."""
return self._hs
@property
def rgb_color(self):
"""Return the hs color value."""
return self._rgb
@property
def rgbw_color(self):
"""Return the hs color value."""
return self._rgbw
@property
def rgbww_color(self):
"""Return the hs color value."""
return self._rgbww
@property
def xy_color(self):
"""Return the hs color value."""
return self._xy
@property
def white_value(self):
"""Return the white property."""
@ -335,6 +439,16 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
"""Return true if we do optimistic updates."""
return self._optimistic
@property
def color_mode(self):
"""Return current color mode."""
return self._color_mode
@property
def supported_color_modes(self):
"""Flag supported color modes."""
return self._config.get(CONF_SUPPORTED_COLOR_MODES)
@property
def supported_features(self):
"""Flag supported features."""
@ -352,6 +466,18 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
elif flash == FLASH_SHORT:
message["flash"] = self._flash_times[CONF_FLASH_TIME_SHORT]
def _scale_rgbxx(self, rgbxx, kwargs):
# If there's a brightness topic set, we don't want to scale the
# RGBxx values given using the brightness.
if self._config[CONF_BRIGHTNESS]:
brightness = 255
else:
brightness = kwargs.get(ATTR_BRIGHTNESS, 255)
return tuple(round(i / 255 * brightness) for i in rgbxx)
def _supports_color_mode(self, color_mode):
return self.supported_color_modes and color_mode in self.supported_color_modes
async def async_turn_on(self, **kwargs):
"""Turn the device on.
@ -391,6 +517,52 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
self._hs = kwargs[ATTR_HS_COLOR]
should_update = True
if ATTR_HS_COLOR in kwargs and self._supports_color_mode(COLOR_MODE_HS):
hs_color = kwargs[ATTR_HS_COLOR]
message["color"] = {"h": hs_color[0], "s": hs_color[1]}
if self._optimistic:
self._color_mode = COLOR_MODE_HS
self._hs = hs_color
should_update = True
if ATTR_RGB_COLOR in kwargs and self._supports_color_mode(COLOR_MODE_RGB):
rgb = self._scale_rgbxx(kwargs[ATTR_RGB_COLOR], kwargs)
message["color"] = {"r": rgb[0], "g": rgb[1], "b": rgb[2]}
if self._optimistic:
self._color_mode = COLOR_MODE_RGB
self._rgb = rgb
should_update = True
if ATTR_RGBW_COLOR in kwargs and self._supports_color_mode(COLOR_MODE_RGBW):
rgb = self._scale_rgbxx(kwargs[ATTR_RGBW_COLOR], kwargs)
message["color"] = {"r": rgb[0], "g": rgb[1], "b": rgb[2], "w": rgb[3]}
if self._optimistic:
self._color_mode = COLOR_MODE_RGBW
self._rgbw = rgb
should_update = True
if ATTR_RGBWW_COLOR in kwargs and self._supports_color_mode(COLOR_MODE_RGBWW):
rgb = self._scale_rgbxx(kwargs[ATTR_RGBWW_COLOR], kwargs)
message["color"] = {
"r": rgb[0],
"g": rgb[1],
"b": rgb[2],
"c": rgb[3],
"w": rgb[4],
}
if self._optimistic:
self._color_mode = COLOR_MODE_RGBWW
self._rgbww = rgb
should_update = True
if ATTR_XY_COLOR in kwargs and self._supports_color_mode(COLOR_MODE_XY):
xy = kwargs[ATTR_XY_COLOR] # pylint: disable=invalid-name
message["color"] = {"x": xy[0], "y": xy[1]}
if self._optimistic:
self._color_mode = COLOR_MODE_XY
self._xy = xy
should_update = True
self._set_flash_and_transition(message, **kwargs)
if ATTR_BRIGHTNESS in kwargs and self._config[CONF_BRIGHTNESS]:

View File

@ -14,6 +14,8 @@ from homeassistant.components.light import (
ATTR_KELVIN,
ATTR_PROFILE,
ATTR_RGB_COLOR,
ATTR_RGBW_COLOR,
ATTR_RGBWW_COLOR,
ATTR_TRANSITION,
ATTR_WHITE_VALUE,
ATTR_XY_COLOR,
@ -37,6 +39,8 @@ def turn_on(
brightness=None,
brightness_pct=None,
rgb_color=None,
rgbw_color=None,
rgbww_color=None,
xy_color=None,
hs_color=None,
color_temp=None,
@ -56,6 +60,8 @@ def turn_on(
brightness,
brightness_pct,
rgb_color,
rgbw_color,
rgbww_color,
xy_color,
hs_color,
color_temp,
@ -75,6 +81,8 @@ async def async_turn_on(
brightness=None,
brightness_pct=None,
rgb_color=None,
rgbw_color=None,
rgbww_color=None,
xy_color=None,
hs_color=None,
color_temp=None,
@ -95,6 +103,8 @@ async def async_turn_on(
(ATTR_BRIGHTNESS, brightness),
(ATTR_BRIGHTNESS_PCT, brightness_pct),
(ATTR_RGB_COLOR, rgb_color),
(ATTR_RGBW_COLOR, rgbw_color),
(ATTR_RGBWW_COLOR, rgbww_color),
(ATTR_XY_COLOR, xy_color),
(ATTR_HS_COLOR, hs_color),
(ATTR_COLOR_TEMP, color_temp),

View File

@ -162,6 +162,32 @@ async def test_fail_setup_if_no_command_topic(hass, mqtt_mock):
assert hass.states.get("light.test") is None
@pytest.mark.parametrize("deprecated", ("color_temp", "hs", "rgb", "white_value", "xy"))
async def test_fail_setup_if_color_mode_deprecated(hass, mqtt_mock, deprecated):
"""Test if setup fails if color mode is combined with deprecated config keys."""
supported_color_modes = ["color_temp", "hs", "rgb", "rgbw", "rgbww", "xy"]
config = {
light.DOMAIN: {
"brightness": True,
"color_mode": True,
"command_topic": "test_light_rgb/set",
"name": "test",
"platform": "mqtt",
"schema": "json",
"supported_color_modes": supported_color_modes,
}
}
config[light.DOMAIN][deprecated] = True
assert await async_setup_component(
hass,
light.DOMAIN,
config,
)
await hass.async_block_till_done()
assert hass.states.get("light.test") is None
async def test_no_color_brightness_color_temp_white_val_if_no_topics(hass, mqtt_mock):
"""Test for no RGB, brightness, color temp, effect, white val or XY."""
assert await async_setup_component(
@ -323,6 +349,166 @@ async def test_controlling_state_via_topic(hass, mqtt_mock):
assert light_state.attributes.get("white_value") == 155
async def test_controlling_state_via_topic2(hass, mqtt_mock, caplog):
"""Test the controlling of the state via topic for a light supporting color mode."""
supported_color_modes = ["color_temp", "hs", "rgb", "rgbw", "rgbww", "xy"]
assert await async_setup_component(
hass,
light.DOMAIN,
{
light.DOMAIN: {
"brightness": True,
"color_mode": True,
"command_topic": "test_light_rgb/set",
"effect": True,
"name": "test",
"platform": "mqtt",
"qos": "0",
"schema": "json",
"state_topic": "test_light_rgb",
"supported_color_modes": supported_color_modes,
}
},
)
await hass.async_block_till_done()
state = hass.states.get("light.test")
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 44
assert state.attributes.get("brightness") is None
assert state.attributes.get("color_mode") is None
assert state.attributes.get("color_temp") is None
assert state.attributes.get("effect") is None
assert state.attributes.get("hs_color") is None
assert state.attributes.get("rgb_color") is None
assert state.attributes.get("rgbw_color") is None
assert state.attributes.get("rgbww_color") is None
assert state.attributes.get("supported_color_modes") == supported_color_modes
assert state.attributes.get("white_value") is None
assert state.attributes.get("xy_color") is None
assert not state.attributes.get(ATTR_ASSUMED_STATE)
# Turn on the light, rgbww mode, additional values in the update
async_fire_mqtt_message(
hass,
"test_light_rgb",
'{"state":"ON",'
'"color_mode":"rgbww",'
'"color":{"r":255,"g":128,"b":64, "c": 32, "w": 16, "x": 1, "y": 1},'
'"brightness":255,'
'"color_temp":155,'
'"effect":"colorloop",'
'"white_value":150}',
)
state = hass.states.get("light.test")
assert state.state == STATE_ON
assert state.attributes.get("brightness") == 255
assert state.attributes.get("color_mode") == "rgbww"
assert state.attributes.get("color_temp") is None
assert state.attributes.get("effect") == "colorloop"
assert state.attributes.get("hs_color") is None
assert state.attributes.get("rgb_color") is None
assert state.attributes.get("rgbw_color") is None
assert state.attributes.get("rgbww_color") == (255, 128, 64, 32, 16)
assert state.attributes.get("white_value") is None
assert state.attributes.get("xy_color") is None
# Light turned off
async_fire_mqtt_message(hass, "test_light_rgb", '{"state":"OFF"}')
state = hass.states.get("light.test")
assert state.state == STATE_OFF
# Light turned on, brightness 100
async_fire_mqtt_message(hass, "test_light_rgb", '{"state":"ON", "brightness":100}')
state = hass.states.get("light.test")
assert state.attributes["brightness"] == 100
# RGB color
async_fire_mqtt_message(
hass,
"test_light_rgb",
'{"state":"ON", "color_mode":"rgb", "color":{"r":64,"g":128,"b":255}}',
)
state = hass.states.get("light.test")
assert state.attributes.get("color_mode") == "rgb"
assert state.attributes.get("rgb_color") == (64, 128, 255)
# RGBW color
async_fire_mqtt_message(
hass,
"test_light_rgb",
'{"state":"ON", "color_mode":"rgbw", "color":{"r":64,"g":128,"b":255,"w":32}}',
)
state = hass.states.get("light.test")
assert state.attributes.get("color_mode") == "rgbw"
assert state.attributes.get("rgbw_color") == (64, 128, 255, 32)
# XY color
async_fire_mqtt_message(
hass,
"test_light_rgb",
'{"state":"ON", "color_mode":"xy", "color":{"x":0.135,"y":0.235}}',
)
state = hass.states.get("light.test")
assert state.attributes.get("color_mode") == "xy"
assert state.attributes.get("xy_color") == (0.135, 0.235)
# HS color
async_fire_mqtt_message(
hass,
"test_light_rgb",
'{"state":"ON", "color_mode":"hs", "color":{"h":180,"s":50}}',
)
state = hass.states.get("light.test")
assert state.attributes.get("color_mode") == "hs"
assert state.attributes.get("hs_color") == (180.0, 50.0)
# Color temp
async_fire_mqtt_message(
hass,
"test_light_rgb",
'{"state":"ON", "color_mode":"color_temp", "color_temp":155}',
)
state = hass.states.get("light.test")
assert state.attributes.get("color_mode") == "color_temp"
assert state.attributes.get("color_temp") == 155
async_fire_mqtt_message(
hass, "test_light_rgb", '{"state":"ON", "effect":"other_effect"}'
)
state = hass.states.get("light.test")
assert state.attributes.get("effect") == "other_effect"
# White value should be ignored
async_fire_mqtt_message(hass, "test_light_rgb", '{"state":"ON", "white_value":155}')
state = hass.states.get("light.test")
assert state.attributes.get("white_value") is None
# Invalid color mode
async_fire_mqtt_message(
hass, "test_light_rgb", '{"state":"ON", "color_mode":"col_temp"}'
)
assert "Invalid color mode received" in caplog.text
caplog.clear()
# Incomplete color
async_fire_mqtt_message(
hass, "test_light_rgb", '{"state":"ON", "color_mode":"rgb"}'
)
assert "Invalid or incomplete color value received" in caplog.text
caplog.clear()
# Invalid color
async_fire_mqtt_message(
hass,
"test_light_rgb",
'{"state":"ON", "color_mode":"rgb", "color":{"r":64,"g":128,"b":"cow"}}',
)
assert "Invalid or incomplete color value received" in caplog.text
async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock):
"""Test the sending of command in optimistic mode."""
fake_state = ha.State(
@ -457,6 +643,206 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock):
assert state.attributes["xy_color"] == (0.611, 0.375)
async def test_sending_mqtt_commands_and_optimistic2(hass, mqtt_mock):
"""Test the sending of command in optimistic mode for a light supporting color mode."""
supported_color_modes = ["color_temp", "hs", "rgb", "rgbw", "rgbww", "xy"]
fake_state = ha.State(
"light.test",
"on",
{
"brightness": 95,
"color_temp": 100,
"color_mode": "rgb",
"effect": "random",
"hs_color": [100, 100],
"white_value": 50,
},
)
with patch(
"homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state",
return_value=fake_state,
):
assert await async_setup_component(
hass,
light.DOMAIN,
{
light.DOMAIN: {
"brightness": True,
"color_mode": True,
"command_topic": "test_light_rgb/set",
"effect": True,
"name": "test",
"platform": "mqtt",
"qos": 2,
"schema": "json",
"supported_color_modes": supported_color_modes,
}
},
)
await hass.async_block_till_done()
state = hass.states.get("light.test")
assert state.state == STATE_ON
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 44
assert state.attributes.get("brightness") == 95
assert state.attributes.get("color_mode") == "rgb"
assert state.attributes.get("color_temp") is None
assert state.attributes.get("effect") == "random"
assert state.attributes.get("hs_color") is None
assert state.attributes.get("rgb_color") is None
assert state.attributes.get("rgbw_color") is None
assert state.attributes.get("rgbww_color") is None
assert state.attributes.get("supported_color_modes") == supported_color_modes
assert state.attributes.get("white_value") is None
assert state.attributes.get("xy_color") is None
assert state.attributes.get(ATTR_ASSUMED_STATE)
# Turn the light on
await common.async_turn_on(hass, "light.test")
mqtt_mock.async_publish.assert_called_once_with(
"test_light_rgb/set", '{"state": "ON"}', 2, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("light.test")
assert state.state == STATE_ON
# Turn the light on with color temperature
await common.async_turn_on(hass, "light.test", color_temp=90)
mqtt_mock.async_publish.assert_called_once_with(
"test_light_rgb/set",
JsonValidator('{"state": "ON", "color_temp": 90}'),
2,
False,
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("light.test")
assert state.state == STATE_ON
# Turn the light off
await common.async_turn_off(hass, "light.test")
mqtt_mock.async_publish.assert_called_once_with(
"test_light_rgb/set", '{"state": "OFF"}', 2, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("light.test")
assert state.state == STATE_OFF
# Set hs color
await common.async_turn_on(hass, "light.test", brightness=75, hs_color=[359, 78])
state = hass.states.get("light.test")
assert state.state == STATE_ON
assert state.attributes["brightness"] == 75
assert state.attributes["color_mode"] == "hs"
assert state.attributes["hs_color"] == (359, 78)
assert state.attributes["rgb_color"] == (255, 56, 59)
assert state.attributes["xy_color"] == (0.654, 0.301)
assert "rgbw_color" not in state.attributes
assert "rgbww_color" not in state.attributes
assert "white_value" not in state.attributes
mqtt_mock.async_publish.assert_called_once_with(
"test_light_rgb/set",
JsonValidator(
'{"state": "ON", "color": {"h": 359.0, "s": 78.0},' ' "brightness": 75}'
),
2,
False,
)
mqtt_mock.async_publish.reset_mock()
# Set rgb color, white value should be discarded
await common.async_turn_on(
hass, "light.test", rgb_color=[255, 128, 0], white_value=80
)
state = hass.states.get("light.test")
assert state.state == STATE_ON
assert state.attributes["brightness"] == 75
assert state.attributes["color_mode"] == "rgb"
assert state.attributes["hs_color"] == (30.118, 100.0)
assert state.attributes["rgb_color"] == (255, 128, 0)
assert state.attributes["xy_color"] == (0.611, 0.375)
assert "rgbw_color" not in state.attributes
assert "rgbww_color" not in state.attributes
assert "white_value" not in state.attributes
mqtt_mock.async_publish.assert_called_once_with(
"test_light_rgb/set",
JsonValidator('{"state": "ON", "color": {"r": 255, "g": 128, "b": 0} }'),
2,
False,
)
mqtt_mock.async_publish.reset_mock()
# Set rgbw color
await common.async_turn_on(
hass, "light.test", rgbw_color=[255, 128, 0, 123], white_value=80
)
state = hass.states.get("light.test")
assert state.state == STATE_ON
assert state.attributes["brightness"] == 75
assert state.attributes["color_mode"] == "rgbw"
assert state.attributes["rgbw_color"] == (255, 128, 0, 123)
assert "hs_color" not in state.attributes
assert "rgb_color" not in state.attributes
assert "rgbww_color" not in state.attributes
assert "white_value" not in state.attributes
assert "xy_color" not in state.attributes
mqtt_mock.async_publish.assert_called_once_with(
"test_light_rgb/set",
JsonValidator(
'{"state": "ON", "color": {"r": 255, "g": 128, "b": 0, "w": 123} }'
),
2,
False,
)
mqtt_mock.async_publish.reset_mock()
# Set rgbww color
await common.async_turn_on(hass, "light.test", rgbww_color=[255, 128, 0, 45, 32])
state = hass.states.get("light.test")
assert state.state == STATE_ON
assert state.attributes["brightness"] == 75
assert state.attributes["color_mode"] == "rgbww"
assert state.attributes["rgbww_color"] == (255, 128, 0, 45, 32)
assert "hs_color" not in state.attributes
assert "rgb_color" not in state.attributes
assert "rgbw_color" not in state.attributes
assert "white_value" not in state.attributes
assert "xy_color" not in state.attributes
mqtt_mock.async_publish.assert_called_once_with(
"test_light_rgb/set",
JsonValidator(
'{"state": "ON", "color": {"r": 255, "g": 128, "b": 0, "c": 45, "w": 32} }'
),
2,
False,
)
mqtt_mock.async_publish.reset_mock()
# Set xy color
await common.async_turn_on(
hass, "light.test", brightness=50, xy_color=[0.123, 0.223]
)
state = hass.states.get("light.test")
assert state.state == STATE_ON
assert state.attributes["brightness"] == 50
assert state.attributes["color_mode"] == "xy"
assert state.attributes["hs_color"] == (196.471, 100.0)
assert state.attributes["rgb_color"] == (0, 185, 255)
assert state.attributes["xy_color"] == (0.123, 0.223)
assert "rgbw_color" not in state.attributes
assert "rgbww_color" not in state.attributes
assert "white_value" not in state.attributes
mqtt_mock.async_publish.assert_called_once_with(
"test_light_rgb/set",
JsonValidator(
'{"state": "ON", "color": {"x": 0.123, "y": 0.223},' ' "brightness": 50}'
),
2,
False,
)
mqtt_mock.async_publish.reset_mock()
async def test_sending_hs_color(hass, mqtt_mock):
"""Test light.turn_on with hs color sends hs color parameters."""
assert await async_setup_component(
@ -574,6 +960,83 @@ async def test_sending_rgb_color_no_brightness(hass, mqtt_mock):
)
async def test_sending_rgb_color_no_brightness2(hass, mqtt_mock):
"""Test light.turn_on with hs color sends rgb color parameters."""
supported_color_modes = ["rgb", "rgbw", "rgbww"]
assert await async_setup_component(
hass,
light.DOMAIN,
{
light.DOMAIN: {
"color_mode": True,
"command_topic": "test_light_rgb/set",
"name": "test",
"platform": "mqtt",
"schema": "json",
"supported_color_modes": supported_color_modes,
}
},
)
await hass.async_block_till_done()
state = hass.states.get("light.test")
assert state.state == STATE_OFF
await common.async_turn_on(
hass, "light.test", brightness=50, xy_color=[0.123, 0.123]
)
await common.async_turn_on(hass, "light.test", brightness=50, hs_color=[359, 78])
await common.async_turn_on(
hass, "light.test", rgb_color=[255, 128, 0], brightness=255
)
await common.async_turn_on(
hass, "light.test", rgbw_color=[128, 64, 32, 16], brightness=128
)
await common.async_turn_on(
hass, "light.test", rgbww_color=[128, 64, 32, 16, 8], brightness=64
)
mqtt_mock.async_publish.assert_has_calls(
[
call(
"test_light_rgb/set",
JsonValidator('{"state": "ON", "color": {"r": 0, "g": 24, "b": 50}}'),
0,
False,
),
call(
"test_light_rgb/set",
JsonValidator('{"state": "ON", "color": {"r": 50, "g": 11, "b": 12}}'),
0,
False,
),
call(
"test_light_rgb/set",
JsonValidator('{"state": "ON", "color": {"r": 255, "g": 128, "b": 0}}'),
0,
False,
),
call(
"test_light_rgb/set",
JsonValidator(
'{"state": "ON", "color": {"r": 64, "g": 32, "b": 16, "w": 8}}'
),
0,
False,
),
call(
"test_light_rgb/set",
JsonValidator(
'{"state": "ON", "color": {"r": 32, "g": 16, "b": 8, "c": 4, "w": 2}}'
),
0,
False,
),
],
any_order=True,
)
async def test_sending_rgb_color_with_brightness(hass, mqtt_mock):
"""Test light.turn_on with hs color sends rgb color parameters."""
assert await async_setup_component(