diff --git a/homeassistant/components/mqtt/light/__init__.py b/homeassistant/components/mqtt/light/__init__.py index d48b4ae4762..fe33756e51e 100644 --- a/homeassistant/components/mqtt/light/__init__.py +++ b/homeassistant/components/mqtt/light/__init__.py @@ -39,7 +39,7 @@ async def async_setup_platform( hass: HomeAssistantType, config: ConfigType, async_add_entities, discovery_info=None ): """Set up MQTT light through configuration.yaml.""" - await _async_setup_entity(config, async_add_entities) + await _async_setup_entity(hass, config, async_add_entities) async def async_setup_entry(hass, config_entry, async_add_entities): @@ -51,7 +51,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): try: config = PLATFORM_SCHEMA(discovery_payload) await _async_setup_entity( - config, async_add_entities, config_entry, discovery_data + hass, config, async_add_entities, config_entry, discovery_data ) except Exception: clear_discovery_hash(hass, discovery_data[ATTR_DISCOVERY_HASH]) @@ -63,7 +63,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async def _async_setup_entity( - config, async_add_entities, config_entry=None, discovery_data=None + hass, config, async_add_entities, config_entry=None, discovery_data=None ): """Set up a MQTT Light.""" setup_entity = { @@ -72,5 +72,5 @@ async def _async_setup_entity( "template": async_setup_entity_template, } await setup_entity[config[CONF_SCHEMA]]( - config, async_add_entities, config_entry, discovery_data + hass, config, async_add_entities, config_entry, discovery_data ) diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index 03b0646307f..54af71b3e05 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -29,21 +29,12 @@ from homeassistant.components.mqtt import ( subscription, ) from homeassistant.const import ( - CONF_BRIGHTNESS, - CONF_COLOR_TEMP, CONF_DEVICE, - CONF_EFFECT, - CONF_HS, CONF_NAME, CONF_OPTIMISTIC, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, - CONF_RGB, - CONF_STATE, CONF_UNIQUE_ID, - CONF_VALUE_TEMPLATE, - CONF_WHITE_VALUE, - CONF_XY, STATE_ON, ) from homeassistant.core import callback @@ -97,6 +88,18 @@ DEFAULT_ON_COMMAND_TYPE = "last" VALUES_ON_COMMAND_TYPE = ["first", "last", "brightness"] +COMMAND_TEMPLATE_KEYS = [CONF_COLOR_TEMP_COMMAND_TEMPLATE, CONF_RGB_COMMAND_TEMPLATE] +VALUE_TEMPLATE_KEYS = [ + CONF_BRIGHTNESS_VALUE_TEMPLATE, + CONF_COLOR_TEMP_VALUE_TEMPLATE, + CONF_EFFECT_VALUE_TEMPLATE, + CONF_HS_VALUE_TEMPLATE, + CONF_RGB_VALUE_TEMPLATE, + CONF_STATE_VALUE_TEMPLATE, + CONF_WHITE_VALUE_TEMPLATE, + CONF_XY_VALUE_TEMPLATE, +] + PLATFORM_SCHEMA_BASIC = ( mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( { @@ -151,12 +154,10 @@ PLATFORM_SCHEMA_BASIC = ( async def async_setup_entity_basic( - config, async_add_entities, config_entry, discovery_data=None + hass, config, async_add_entities, config_entry, discovery_data=None ): """Set up a MQTT Light.""" - config.setdefault(CONF_STATE_VALUE_TEMPLATE, config.get(CONF_VALUE_TEMPLATE)) - - async_add_entities([MqttLight(config, config_entry, discovery_data)]) + async_add_entities([MqttLight(hass, config, config_entry, discovery_data)]) class MqttLight( @@ -169,8 +170,9 @@ class MqttLight( ): """Representation of a MQTT light.""" - def __init__(self, config, config_entry, discovery_data): + def __init__(self, hass, config, config_entry, discovery_data): """Initialize MQTT light.""" + self.hass = hass self._state = False self._sub_state = None self._brightness = None @@ -181,7 +183,8 @@ class MqttLight( self._topic = None self._payload = None - self._templates = None + self._command_templates = None + self._value_templates = None self._optimistic = False self._optimistic_rgb = False self._optimistic_brightness = False @@ -244,20 +247,24 @@ class MqttLight( } self._topic = topic self._payload = {"on": config[CONF_PAYLOAD_ON], "off": config[CONF_PAYLOAD_OFF]} - self._templates = { - CONF_BRIGHTNESS: config.get(CONF_BRIGHTNESS_VALUE_TEMPLATE), - CONF_COLOR_TEMP: config.get(CONF_COLOR_TEMP_VALUE_TEMPLATE), - CONF_COLOR_TEMP_COMMAND_TEMPLATE: config.get( - CONF_COLOR_TEMP_COMMAND_TEMPLATE - ), - CONF_EFFECT: config.get(CONF_EFFECT_VALUE_TEMPLATE), - CONF_HS: config.get(CONF_HS_VALUE_TEMPLATE), - CONF_RGB: config.get(CONF_RGB_VALUE_TEMPLATE), - CONF_RGB_COMMAND_TEMPLATE: config.get(CONF_RGB_COMMAND_TEMPLATE), - CONF_STATE: config.get(CONF_STATE_VALUE_TEMPLATE), - CONF_WHITE_VALUE: config.get(CONF_WHITE_VALUE_TEMPLATE), - CONF_XY: config.get(CONF_XY_VALUE_TEMPLATE), - } + + value_templates = {} + for key in VALUE_TEMPLATE_KEYS: + value_templates[key] = lambda value: value + for key in VALUE_TEMPLATE_KEYS & config.keys(): + tpl = config[key] + value_templates[key] = tpl.async_render_with_possible_json_value + tpl.hass = self.hass + self._value_templates = value_templates + + command_templates = {} + for key in COMMAND_TEMPLATE_KEYS: + command_templates[key] = None + for key in COMMAND_TEMPLATE_KEYS & config.keys(): + tpl = config[key] + command_templates[key] = tpl.async_render + tpl.hass = self.hass + self._command_templates = command_templates optimistic = config[CONF_OPTIMISTIC] self._optimistic = optimistic or topic[CONF_STATE_TOPIC] is None @@ -286,13 +293,6 @@ class MqttLight( async def _subscribe_topics(self): """(Re)Subscribe to topics.""" topics = {} - templates = {} - for key, tpl in list(self._templates.items()): - if tpl is None: - templates[key] = lambda value: value - else: - tpl.hass = self.hass - templates[key] = tpl.async_render_with_possible_json_value last_state = await self.async_get_last_state() @@ -300,7 +300,7 @@ class MqttLight( @log_messages(self.hass, self.entity_id) def state_received(msg): """Handle new MQTT messages.""" - payload = templates[CONF_STATE](msg.payload) + payload = self._value_templates[CONF_STATE_VALUE_TEMPLATE](msg.payload) if not payload: _LOGGER.debug("Ignoring empty state message from '%s'", msg.topic) return @@ -324,7 +324,7 @@ class MqttLight( @log_messages(self.hass, self.entity_id) def brightness_received(msg): """Handle new MQTT messages for the brightness.""" - payload = templates[CONF_BRIGHTNESS](msg.payload) + payload = self._value_templates[CONF_BRIGHTNESS_VALUE_TEMPLATE](msg.payload) if not payload: _LOGGER.debug("Ignoring empty brightness message from '%s'", msg.topic) return @@ -356,7 +356,7 @@ class MqttLight( @log_messages(self.hass, self.entity_id) def rgb_received(msg): """Handle new MQTT messages for RGB.""" - payload = templates[CONF_RGB](msg.payload) + payload = self._value_templates[CONF_RGB_VALUE_TEMPLATE](msg.payload) if not payload: _LOGGER.debug("Ignoring empty rgb message from '%s'", msg.topic) return @@ -388,7 +388,7 @@ class MqttLight( @log_messages(self.hass, self.entity_id) def color_temp_received(msg): """Handle new MQTT messages for color temperature.""" - payload = templates[CONF_COLOR_TEMP](msg.payload) + payload = self._value_templates[CONF_COLOR_TEMP_VALUE_TEMPLATE](msg.payload) if not payload: _LOGGER.debug("Ignoring empty color temp message from '%s'", msg.topic) return @@ -418,7 +418,7 @@ class MqttLight( @log_messages(self.hass, self.entity_id) def effect_received(msg): """Handle new MQTT messages for effect.""" - payload = templates[CONF_EFFECT](msg.payload) + payload = self._value_templates[CONF_EFFECT_VALUE_TEMPLATE](msg.payload) if not payload: _LOGGER.debug("Ignoring empty effect message from '%s'", msg.topic) return @@ -448,7 +448,7 @@ class MqttLight( @log_messages(self.hass, self.entity_id) def hs_received(msg): """Handle new MQTT messages for hs color.""" - payload = templates[CONF_HS](msg.payload) + payload = self._value_templates[CONF_HS_VALUE_TEMPLATE](msg.payload) if not payload: _LOGGER.debug("Ignoring empty hs message from '%s'", msg.topic) return @@ -480,7 +480,7 @@ class MqttLight( @log_messages(self.hass, self.entity_id) def white_value_received(msg): """Handle new MQTT messages for white value.""" - payload = templates[CONF_WHITE_VALUE](msg.payload) + payload = self._value_templates[CONF_WHITE_VALUE_TEMPLATE](msg.payload) if not payload: _LOGGER.debug("Ignoring empty white value message from '%s'", msg.topic) return @@ -512,7 +512,7 @@ class MqttLight( @log_messages(self.hass, self.entity_id) def xy_received(msg): """Handle new MQTT messages for xy color.""" - payload = templates[CONF_XY](msg.payload) + payload = self._value_templates[CONF_XY_VALUE_TEMPLATE](msg.payload) if not payload: _LOGGER.debug("Ignoring empty xy-color message from '%s'", msg.topic) return @@ -692,11 +692,9 @@ class MqttLight( rgb = color_util.color_hsv_to_RGB( hs_color[0], hs_color[1], brightness / 255 * 100 ) - tpl = self._templates[CONF_RGB_COMMAND_TEMPLATE] + tpl = self._command_templates[CONF_RGB_COMMAND_TEMPLATE] if tpl: - rgb_color_str = tpl.async_render( - {"red": rgb[0], "green": rgb[1], "blue": rgb[2]} - ) + rgb_color_str = tpl({"red": rgb[0], "green": rgb[1], "blue": rgb[2]}) else: rgb_color_str = f"{rgb[0]},{rgb[1]},{rgb[2]}" @@ -772,11 +770,9 @@ class MqttLight( rgb = color_util.color_hsv_to_RGB( self._hs[0], self._hs[1], kwargs[ATTR_BRIGHTNESS] / 255 * 100 ) - tpl = self._templates[CONF_RGB_COMMAND_TEMPLATE] + tpl = self._command_templates[CONF_RGB_COMMAND_TEMPLATE] if tpl: - rgb_color_str = tpl.async_render( - {"red": rgb[0], "green": rgb[1], "blue": rgb[2]} - ) + rgb_color_str = tpl({"red": rgb[0], "green": rgb[1], "blue": rgb[2]}) else: rgb_color_str = f"{rgb[0]},{rgb[1]},{rgb[2]}" @@ -797,10 +793,10 @@ class MqttLight( and self._topic[CONF_COLOR_TEMP_COMMAND_TOPIC] is not None ): color_temp = int(kwargs[ATTR_COLOR_TEMP]) - tpl = self._templates[CONF_COLOR_TEMP_COMMAND_TEMPLATE] + tpl = self._command_templates[CONF_COLOR_TEMP_COMMAND_TEMPLATE] if tpl: - color_temp = tpl.async_render({"value": color_temp}) + color_temp = tpl({"value": color_temp}) mqtt.async_publish( self.hass, diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index 4c8b9c41405..39ea550d877 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -125,7 +125,7 @@ PLATFORM_SCHEMA_JSON = ( async def async_setup_entity_json( - config: ConfigType, async_add_entities, config_entry, discovery_data + hass, config: ConfigType, async_add_entities, config_entry, discovery_data ): """Set up a MQTT JSON Light.""" async_add_entities([MqttLightJson(config, config_entry, discovery_data)]) diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index 0c512f0e1a3..9c2758524a3 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -98,7 +98,7 @@ PLATFORM_SCHEMA_TEMPLATE = ( async def async_setup_entity_template( - config, async_add_entities, config_entry, discovery_data + hass, config, async_add_entities, config_entry, discovery_data ): """Set up a MQTT Template light.""" async_add_entities([MqttLightTemplate(config, config_entry, discovery_data)]) diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index 547d8adad9e..75e1c12a46c 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -503,7 +503,20 @@ async def help_test_discovery_removal(hass, mqtt_mock, caplog, domain, data): assert state is None -async def help_test_discovery_update(hass, mqtt_mock, caplog, domain, data1, data2): +async def help_test_discovery_update( + hass, + mqtt_mock, + caplog, + domain, + discovery_data1, + discovery_data2, + state_data1=None, + state_data2=None, + state1=None, + state2=None, + attributes1=None, + attributes2=None, +): """Test update of discovered component. This is a test helper for the MqttDiscoveryUpdate mixin. @@ -511,19 +524,35 @@ async def help_test_discovery_update(hass, mqtt_mock, caplog, domain, data1, dat entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0] await async_start(hass, "homeassistant", entry) - async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data1) + async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", discovery_data1) await hass.async_block_till_done() + if state_data1: + for (topic, data) in state_data1: + async_fire_mqtt_message(hass, topic, data) state = hass.states.get(f"{domain}.beer") assert state is not None assert state.name == "Beer" + if state1: + assert state.state == state1 + if attributes1: + for (attr, value) in attributes1: + assert state.attributes.get(attr) == value - async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data2) + async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", discovery_data2) await hass.async_block_till_done() + if state_data2: + for (topic, data) in state_data2: + async_fire_mqtt_message(hass, topic, data) state = hass.states.get(f"{domain}.beer") assert state is not None assert state.name == "Milk" + if state2: + assert state.state == state2 + if attributes2: + for (attr, value) in attributes2: + assert state.attributes.get(attr) == value state = hass.states.get(f"{domain}.milk") assert state is None diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index 75d3e694838..d83cd7fda7f 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -716,9 +716,8 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): 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], white_value=80 - ) + await common.async_turn_on(hass, "light.test", rgb_color=[255, 128, 0]) + await common.async_turn_on(hass, "light.test", white_value=80, color_temp=125) mqtt_mock.async_publish.assert_has_calls( [ @@ -728,6 +727,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): call("test_light_rgb/hs/set", "359.0,78.0", 2, False), call("test_light_rgb/white_value/set", 80, 2, False), call("test_light_rgb/xy/set", "0.14,0.131", 2, False), + call("test_light_rgb/color_temp/set", 125, 2, False), ], any_order=True, ) @@ -1439,15 +1439,26 @@ async def test_discovery_update_light(hass, mqtt_mock, caplog): data1 = ( '{ "name": "Beer",' ' "state_topic": "test_topic",' - ' "command_topic": "test_topic" }' + ' "command_topic": "test_topic",' + ' "state_value_template": "{{value_json.power1}}" }' ) data2 = ( '{ "name": "Milk",' ' "state_topic": "test_topic",' - ' "command_topic": "test_topic" }' + ' "command_topic": "test_topic",' + ' "state_value_template": "{{value_json.power2}}" }' ) await help_test_discovery_update( - hass, mqtt_mock, caplog, light.DOMAIN, data1, data2 + hass, + mqtt_mock, + caplog, + light.DOMAIN, + data1, + data2, + state_data1=[("test_topic", '{"power1":"ON"}')], + state1="on", + state_data2=[("test_topic", '{"power2":"OFF"}')], + state2="off", )