Add support for color_mode white to MQTT light basic schema (#51484)

* Add support for color_mode white to MQTT light basic schema

* Add missing abbreviations
pull/52091/head
Erik Montnemery 2021-06-22 11:59:20 +02:00 committed by GitHub
parent 39bf304031
commit 52c142a82d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 186 additions and 12 deletions

View File

@ -220,6 +220,8 @@ ABBREVIATIONS = {
"uniq_id": "unique_id",
"unit_of_meas": "unit_of_measurement",
"val_tpl": "value_template",
"whit_cmd_t": "white_command_topic",
"whit_scl": "white_scale",
"whit_val_cmd_t": "white_value_command_topic",
"whit_val_scl": "white_value_scale",
"whit_val_stat_t": "white_value_state_topic",

View File

@ -12,6 +12,7 @@ from homeassistant.components.light import (
ATTR_RGB_COLOR,
ATTR_RGBW_COLOR,
ATTR_RGBWW_COLOR,
ATTR_WHITE,
ATTR_WHITE_VALUE,
ATTR_XY_COLOR,
COLOR_MODE_BRIGHTNESS,
@ -22,6 +23,7 @@ from homeassistant.components.light import (
COLOR_MODE_RGBW,
COLOR_MODE_RGBWW,
COLOR_MODE_UNKNOWN,
COLOR_MODE_WHITE,
COLOR_MODE_XY,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
@ -29,6 +31,7 @@ from homeassistant.components.light import (
SUPPORT_EFFECT,
SUPPORT_WHITE_VALUE,
LightEntity,
valid_supported_color_modes,
)
from homeassistant.const import (
CONF_NAME,
@ -86,6 +89,8 @@ CONF_STATE_VALUE_TEMPLATE = "state_value_template"
CONF_XY_COMMAND_TOPIC = "xy_command_topic"
CONF_XY_STATE_TOPIC = "xy_state_topic"
CONF_XY_VALUE_TEMPLATE = "xy_value_template"
CONF_WHITE_COMMAND_TOPIC = "white_command_topic"
CONF_WHITE_SCALE = "white_scale"
CONF_WHITE_VALUE_COMMAND_TOPIC = "white_value_command_topic"
CONF_WHITE_VALUE_SCALE = "white_value_scale"
CONF_WHITE_VALUE_STATE_TOPIC = "white_value_state_topic"
@ -98,6 +103,7 @@ DEFAULT_OPTIMISTIC = False
DEFAULT_PAYLOAD_OFF = "OFF"
DEFAULT_PAYLOAD_ON = "ON"
DEFAULT_WHITE_VALUE_SCALE = 255
DEFAULT_WHITE_SCALE = 255
DEFAULT_ON_COMMAND_TYPE = "last"
VALUES_ON_COMMAND_TYPE = ["first", "last", "brightness"]
@ -168,6 +174,10 @@ PLATFORM_SCHEMA_BASIC = vol.All(
vol.Optional(CONF_RGBWW_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_RGBWW_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_WHITE_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_WHITE_SCALE, default=DEFAULT_WHITE_SCALE): vol.All(
vol.Coerce(int), vol.Range(min=1)
),
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_WHITE_VALUE_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(
@ -259,6 +269,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
CONF_RGBWW_COMMAND_TOPIC,
CONF_RGBWW_STATE_TOPIC,
CONF_STATE_TOPIC,
CONF_WHITE_COMMAND_TOPIC,
CONF_WHITE_VALUE_COMMAND_TOPIC,
CONF_WHITE_VALUE_STATE_TOPIC,
CONF_XY_COMMAND_TOPIC,
@ -316,35 +327,40 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
optimistic or topic[CONF_WHITE_VALUE_STATE_TOPIC] is None
)
self._optimistic_xy_color = optimistic or topic[CONF_XY_STATE_TOPIC] is None
self._supported_color_modes = set()
supported_color_modes = set()
if topic[CONF_COLOR_TEMP_COMMAND_TOPIC] is not None:
self._supported_color_modes.add(COLOR_MODE_COLOR_TEMP)
supported_color_modes.add(COLOR_MODE_COLOR_TEMP)
self._color_mode = COLOR_MODE_COLOR_TEMP
if topic[CONF_HS_COMMAND_TOPIC] is not None:
self._supported_color_modes.add(COLOR_MODE_HS)
supported_color_modes.add(COLOR_MODE_HS)
self._color_mode = COLOR_MODE_HS
if topic[CONF_RGB_COMMAND_TOPIC] is not None:
self._supported_color_modes.add(COLOR_MODE_RGB)
supported_color_modes.add(COLOR_MODE_RGB)
self._color_mode = COLOR_MODE_RGB
if topic[CONF_RGBW_COMMAND_TOPIC] is not None:
self._supported_color_modes.add(COLOR_MODE_RGBW)
supported_color_modes.add(COLOR_MODE_RGBW)
self._color_mode = COLOR_MODE_RGBW
if topic[CONF_RGBWW_COMMAND_TOPIC] is not None:
self._supported_color_modes.add(COLOR_MODE_RGBWW)
supported_color_modes.add(COLOR_MODE_RGBWW)
self._color_mode = COLOR_MODE_RGBWW
if topic[CONF_WHITE_COMMAND_TOPIC] is not None:
supported_color_modes.add(COLOR_MODE_WHITE)
if topic[CONF_XY_COMMAND_TOPIC] is not None:
self._supported_color_modes.add(COLOR_MODE_XY)
supported_color_modes.add(COLOR_MODE_XY)
self._color_mode = COLOR_MODE_XY
if len(self._supported_color_modes) > 1:
if len(supported_color_modes) > 1:
self._color_mode = COLOR_MODE_UNKNOWN
if not self._supported_color_modes:
if not supported_color_modes:
if topic[CONF_BRIGHTNESS_COMMAND_TOPIC] is not None:
self._color_mode = COLOR_MODE_BRIGHTNESS
self._supported_color_modes.add(COLOR_MODE_BRIGHTNESS)
supported_color_modes.add(COLOR_MODE_BRIGHTNESS)
else:
self._color_mode = COLOR_MODE_ONOFF
self._supported_color_modes.add(COLOR_MODE_ONOFF)
supported_color_modes.add(COLOR_MODE_ONOFF)
# Validate the color_modes configuration
self._supported_color_modes = valid_supported_color_modes(supported_color_modes)
if topic[CONF_WHITE_VALUE_COMMAND_TOPIC] is not None:
self._legacy_mode = True
@ -817,7 +833,11 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
# If brightness is being used instead of an on command, make sure
# there is a brightness input. Either set the brightness to our
# saved value or the maximum value if this is the first call
elif on_command_type == "brightness" and ATTR_BRIGHTNESS not in kwargs:
elif (
on_command_type == "brightness"
and ATTR_BRIGHTNESS not in kwargs
and ATTR_WHITE not in kwargs
):
kwargs[ATTR_BRIGHTNESS] = self._brightness if self._brightness else 255
hs_color = kwargs.get(ATTR_HS_COLOR)
@ -971,6 +991,17 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
publish(CONF_EFFECT_COMMAND_TOPIC, effect)
should_update |= set_optimistic(ATTR_EFFECT, effect)
if ATTR_WHITE in kwargs and self._topic[CONF_WHITE_COMMAND_TOPIC] is not None:
percent_white = float(kwargs[ATTR_WHITE]) / 255
white_scale = self._config[CONF_WHITE_SCALE]
device_white_value = min(round(percent_white * white_scale), white_scale)
publish(CONF_WHITE_COMMAND_TOPIC, device_white_value)
should_update |= set_optimistic(
ATTR_BRIGHTNESS,
kwargs[ATTR_WHITE],
COLOR_MODE_WHITE,
)
if (
ATTR_WHITE_VALUE in kwargs
and self._topic[CONF_WHITE_VALUE_COMMAND_TOPIC] is not None

View File

@ -2268,6 +2268,83 @@ async def test_on_command_rgbww_template(hass, mqtt_mock):
mqtt_mock.async_publish.assert_called_once_with("test_light/set", "OFF", 0, False)
async def test_on_command_white(hass, mqtt_mock):
"""Test sending commands for RGB + white light."""
config = {
light.DOMAIN: {
"platform": "mqtt",
"name": "test",
"command_topic": "tasmota_B94927/cmnd/POWER",
"value_template": "{{ value_json.POWER }}",
"payload_off": "OFF",
"payload_on": "ON",
"brightness_command_topic": "tasmota_B94927/cmnd/Dimmer",
"brightness_scale": 100,
"on_command_type": "brightness",
"brightness_value_template": "{{ value_json.Dimmer }}",
"rgb_command_topic": "tasmota_B94927/cmnd/Color2",
"rgb_value_template": "{{value_json.Color.split(',')[0:3]|join(',')}}",
"white_command_topic": "tasmota_B94927/cmnd/White",
"white_scale": 100,
"color_mode_value_template": "{% if value_json.White %} white {% else %} rgb {% endif %}",
"qos": "0",
}
}
color_modes = ["rgb", "white"]
assert await async_setup_component(hass, light.DOMAIN, config)
await hass.async_block_till_done()
state = hass.states.get("light.test")
assert state.state == STATE_OFF
assert state.attributes.get("brightness") is None
assert state.attributes.get("rgb_color") is None
assert state.attributes.get(light.ATTR_COLOR_MODE) is None
assert state.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES) == color_modes
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_turn_on(hass, "light.test", brightness=192)
mqtt_mock.async_publish.assert_has_calls(
[
call("tasmota_B94927/cmnd/Dimmer", "75", 0, False),
],
any_order=True,
)
mqtt_mock.async_publish.reset_mock()
await common.async_turn_on(hass, "light.test", white=255)
mqtt_mock.async_publish.assert_has_calls(
[
call("tasmota_B94927/cmnd/White", "100", 0, False),
],
any_order=True,
)
mqtt_mock.async_publish.reset_mock()
await common.async_turn_on(hass, "light.test", white=64)
mqtt_mock.async_publish.assert_has_calls(
[
call("tasmota_B94927/cmnd/White", "25", 0, False),
],
any_order=True,
)
mqtt_mock.async_publish.reset_mock()
await common.async_turn_on(hass, "light.test")
mqtt_mock.async_publish.assert_has_calls(
[
call("tasmota_B94927/cmnd/Dimmer", "25", 0, False),
],
any_order=True,
)
mqtt_mock.async_publish.reset_mock()
await common.async_turn_off(hass, "light.test")
mqtt_mock.async_publish.assert_called_once_with(
"tasmota_B94927/cmnd/POWER", "OFF", 0, False
)
async def test_explicit_color_mode(hass, mqtt_mock):
"""Test explicit color mode over mqtt."""
config = {
@ -2499,6 +2576,70 @@ async def test_explicit_color_mode_templated(hass, mqtt_mock):
assert light_state.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES) == color_modes
async def test_white_state_update(hass, mqtt_mock):
"""Test state updates for RGB + white light."""
config = {
light.DOMAIN: {
"platform": "mqtt",
"name": "test",
"state_topic": "tasmota_B94927/tele/STATE",
"command_topic": "tasmota_B94927/cmnd/POWER",
"value_template": "{{ value_json.POWER }}",
"payload_off": "OFF",
"payload_on": "ON",
"brightness_command_topic": "tasmota_B94927/cmnd/Dimmer",
"brightness_state_topic": "tasmota_B94927/tele/STATE",
"brightness_scale": 100,
"on_command_type": "brightness",
"brightness_value_template": "{{ value_json.Dimmer }}",
"rgb_command_topic": "tasmota_B94927/cmnd/Color2",
"rgb_state_topic": "tasmota_B94927/tele/STATE",
"rgb_value_template": "{{value_json.Color.split(',')[0:3]|join(',')}}",
"white_command_topic": "tasmota_B94927/cmnd/White",
"white_scale": 100,
"color_mode_state_topic": "tasmota_B94927/tele/STATE",
"color_mode_value_template": "{% if value_json.White %} white {% else %} rgb {% endif %}",
"qos": "0",
}
}
color_modes = ["rgb", "white"]
assert await async_setup_component(hass, light.DOMAIN, config)
await hass.async_block_till_done()
state = hass.states.get("light.test")
assert state.state == STATE_OFF
assert state.attributes.get("brightness") is None
assert state.attributes.get("rgb_color") is None
assert state.attributes.get(light.ATTR_COLOR_MODE) is None
assert state.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES) == color_modes
assert not state.attributes.get(ATTR_ASSUMED_STATE)
async_fire_mqtt_message(
hass,
"tasmota_B94927/tele/STATE",
'{"POWER":"ON","Dimmer":50,"Color":"0,0,0,128","White":50}',
)
state = hass.states.get("light.test")
assert state.state == STATE_ON
assert state.attributes.get("brightness") == 128
assert state.attributes.get("rgb_color") is None
assert state.attributes.get(light.ATTR_COLOR_MODE) == "white"
assert state.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES) == color_modes
async_fire_mqtt_message(
hass,
"tasmota_B94927/tele/STATE",
'{"POWER":"ON","Dimmer":50,"Color":"128,64,32,0","White":0}',
)
state = hass.states.get("light.test")
assert state.state == STATE_ON
assert state.attributes.get("brightness") == 128
assert state.attributes.get("rgb_color") == (128, 64, 32)
assert state.attributes.get(light.ATTR_COLOR_MODE) == "rgb"
assert state.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES) == color_modes
async def test_effect(hass, mqtt_mock):
"""Test effect."""
config = {