From a0d41e1d974df1a042c96cebf6ab35df9272a781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ndor=20Oroszi?= Date: Tue, 3 Jan 2023 12:58:00 +0100 Subject: [PATCH] Add hs_command_template and xy_command_template to mqtt light default schema (#84988) * Add mqtt light hs_command_template * Add mqtt light xy_command_template --- .../components/mqtt/abbreviations.py | 2 + .../components/mqtt/light/schema_basic.py | 18 +++- tests/components/mqtt/test_light.py | 84 +++++++++++++++++-- 3 files changed, 96 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/mqtt/abbreviations.py b/homeassistant/components/mqtt/abbreviations.py index 913a6e13400..68a19015414 100644 --- a/homeassistant/components/mqtt/abbreviations.py +++ b/homeassistant/components/mqtt/abbreviations.py @@ -78,6 +78,7 @@ ABBREVIATIONS = { "hold_stat_tpl": "hold_state_template", "hold_stat_t": "hold_state_topic", "hs_cmd_t": "hs_command_topic", + "hs_cmd_tpl": "hs_command_template", "hs_stat_t": "hs_state_topic", "hs_val_tpl": "hs_value_template", "ic": "icon", @@ -250,6 +251,7 @@ ABBREVIATIONS = { "whit_val_stat_t": "white_value_state_topic", "whit_val_tpl": "white_value_template", "xy_cmd_t": "xy_command_topic", + "xy_cmd_tpl": "xy_command_template", "xy_stat_t": "xy_state_topic", "xy_val_tpl": "xy_value_template", "l_ver_t": "latest_version_topic", diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index 41f4c15af15..2d27d86e9e9 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -88,6 +88,7 @@ CONF_EFFECT_COMMAND_TOPIC = "effect_command_topic" CONF_EFFECT_LIST = "effect_list" CONF_EFFECT_STATE_TOPIC = "effect_state_topic" CONF_EFFECT_VALUE_TEMPLATE = "effect_value_template" +CONF_HS_COMMAND_TEMPLATE = "hs_command_template" CONF_HS_COMMAND_TOPIC = "hs_command_topic" CONF_HS_STATE_TOPIC = "hs_state_topic" CONF_HS_VALUE_TEMPLATE = "hs_value_template" @@ -105,6 +106,7 @@ CONF_RGBWW_COMMAND_TEMPLATE = "rgbww_command_template" CONF_RGBWW_COMMAND_TOPIC = "rgbww_command_topic" CONF_RGBWW_STATE_TOPIC = "rgbww_state_topic" CONF_RGBWW_VALUE_TEMPLATE = "rgbww_value_template" +CONF_XY_COMMAND_TEMPLATE = "xy_command_template" CONF_XY_COMMAND_TOPIC = "xy_command_topic" CONF_XY_STATE_TOPIC = "xy_state_topic" CONF_XY_VALUE_TEMPLATE = "xy_value_template" @@ -147,9 +149,11 @@ COMMAND_TEMPLATE_KEYS = [ CONF_BRIGHTNESS_COMMAND_TEMPLATE, CONF_COLOR_TEMP_COMMAND_TEMPLATE, CONF_EFFECT_COMMAND_TEMPLATE, + CONF_HS_COMMAND_TEMPLATE, CONF_RGB_COMMAND_TEMPLATE, CONF_RGBW_COMMAND_TEMPLATE, CONF_RGBWW_COMMAND_TEMPLATE, + CONF_XY_COMMAND_TEMPLATE, ] VALUE_TEMPLATE_KEYS = [ CONF_BRIGHTNESS_VALUE_TEMPLATE, @@ -185,6 +189,7 @@ _PLATFORM_SCHEMA_BASE = ( vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_EFFECT_STATE_TOPIC): valid_subscribe_topic, vol.Optional(CONF_EFFECT_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_HS_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_HS_COMMAND_TOPIC): valid_publish_topic, vol.Optional(CONF_HS_STATE_TOPIC): valid_subscribe_topic, vol.Optional(CONF_HS_VALUE_TEMPLATE): cv.template, @@ -213,6 +218,7 @@ _PLATFORM_SCHEMA_BASE = ( vol.Optional(CONF_WHITE_SCALE, default=DEFAULT_WHITE_SCALE): vol.All( vol.Coerce(int), vol.Range(min=1) ), + vol.Optional(CONF_XY_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_XY_COMMAND_TOPIC): valid_publish_topic, vol.Optional(CONF_XY_STATE_TOPIC): valid_subscribe_topic, vol.Optional(CONF_XY_VALUE_TEMPLATE): cv.template, @@ -763,7 +769,11 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): hs_color: str | None = kwargs.get(ATTR_HS_COLOR) if hs_color and self._topic[CONF_HS_COMMAND_TOPIC] is not None: - await publish(CONF_HS_COMMAND_TOPIC, f"{hs_color[0]},{hs_color[1]}") + device_hs_payload = self._command_templates[CONF_HS_COMMAND_TEMPLATE]( + f"{hs_color[0]},{hs_color[1]}", + {"hue": hs_color[0], "sat": hs_color[1]}, + ) + await publish(CONF_HS_COMMAND_TOPIC, device_hs_payload) should_update |= set_optimistic(ATTR_HS_COLOR, hs_color, ColorMode.HS) rgb: tuple[int, int, int] | None @@ -797,7 +807,11 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): if (xy_color := kwargs.get(ATTR_XY_COLOR)) and self._topic[ CONF_XY_COMMAND_TOPIC ] is not None: - await publish(CONF_XY_COMMAND_TOPIC, f"{xy_color[0]},{xy_color[1]}") + device_xy_payload = self._command_templates[CONF_XY_COMMAND_TEMPLATE]( + f"{xy_color[0]},{xy_color[1]}", + {"x": xy_color[0], "y": xy_color[1]}, + ) + await publish(CONF_XY_COMMAND_TOPIC, device_xy_payload) should_update |= set_optimistic(ATTR_XY_COLOR, xy_color, ColorMode.XY) if ( diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index 7a654825596..0577bdf4ea4 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -2861,18 +2861,18 @@ async def test_max_mireds(hass, mqtt_mock_entry_with_yaml_config): "hs_command_topic", {"rgb_color": [255, 128, 0]}, "30.118,100.0", - None, - None, - None, + "hs_command_template", + "hue", + b"3", ), ( light.SERVICE_TURN_ON, "xy_command_topic", {"hs_color": [30.118, 100.0]}, "0.611,0.375", - None, - None, - None, + "xy_command_template", + "x * 10", + b"6", ), ( light.SERVICE_TURN_OFF, @@ -3113,6 +3113,78 @@ async def test_sending_mqtt_effect_command_with_template( assert state.attributes.get("effect") == "colorloop" +async def test_sending_mqtt_hs_command_with_template( + hass, mqtt_mock_entry_with_yaml_config +): + """Test the sending of HS Color command with template.""" + config = { + light.DOMAIN: { + "name": "test", + "command_topic": "test_light_hs/set", + "hs_command_topic": "test_light_hs/hs_color/set", + "hs_command_template": '{"hue": {{ hue | int }}, "sat": {{ sat | int}}}', + "qos": 0, + } + } + + assert await async_setup_component(hass, mqtt.DOMAIN, {mqtt.DOMAIN: config}) + await hass.async_block_till_done() + mqtt_mock = await mqtt_mock_entry_with_yaml_config() + + state = hass.states.get("light.test") + assert state.state == STATE_UNKNOWN + + await common.async_turn_on(hass, "light.test", hs_color=(30, 100)) + + mqtt_mock.async_publish.assert_has_calls( + [ + call("test_light_hs/set", "ON", 0, False), + call("test_light_hs/hs_color/set", '{"hue": 30, "sat": 100}', 0, False), + ], + any_order=True, + ) + + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes["hs_color"] == (30, 100) + + +async def test_sending_mqtt_xy_command_with_template( + hass, mqtt_mock_entry_with_yaml_config +): + """Test the sending of XY Color command with template.""" + config = { + light.DOMAIN: { + "name": "test", + "command_topic": "test_light_xy/set", + "xy_command_topic": "test_light_xy/xy_color/set", + "xy_command_template": '{"Color": "{{ (x * 65536) | round | int }},{{ (y * 65536) | round | int }}"}', + "qos": 0, + } + } + + assert await async_setup_component(hass, mqtt.DOMAIN, {mqtt.DOMAIN: config}) + await hass.async_block_till_done() + mqtt_mock = await mqtt_mock_entry_with_yaml_config() + + state = hass.states.get("light.test") + assert state.state == STATE_UNKNOWN + + await common.async_turn_on(hass, "light.test", xy_color=(0.151, 0.343)) + + mqtt_mock.async_publish.assert_has_calls( + [ + call("test_light_xy/set", "ON", 0, False), + call("test_light_xy/xy_color/set", '{"Color": "9896,22479"}', 0, False), + ], + any_order=True, + ) + + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes["xy_color"] == (0.151, 0.343) + + async def test_setup_manual_entity_from_yaml(hass): """Test setup manual configured MQTT entity.""" platform = light.DOMAIN