diff --git a/homeassistant/components/mqtt/models.py b/homeassistant/components/mqtt/models.py index 84bf704a262..d40b882d81b 100644 --- a/homeassistant/components/mqtt/models.py +++ b/homeassistant/components/mqtt/models.py @@ -17,6 +17,8 @@ from homeassistant.helpers.typing import TemplateVarsType _SENTINEL = object() +ATTR_THIS = "this" + PublishPayloadType = Union[str, bytes, int, float, None] @@ -57,7 +59,8 @@ class MqttCommandTemplate: entity: Entity | None = None, ) -> None: """Instantiate a command template.""" - self._attr_command_template = command_template + self._template_state: template.TemplateStateFromEntityId | None = None + self._command_template = command_template if command_template is None: return @@ -91,17 +94,23 @@ class MqttCommandTemplate: return payload - if self._attr_command_template is None: + if self._command_template is None: return value - values = {"value": value} + values: dict[str, Any] = {"value": value} if self._entity: values[ATTR_ENTITY_ID] = self._entity.entity_id values[ATTR_NAME] = self._entity.name + if not self._template_state: + self._template_state = template.TemplateStateFromEntityId( + self._command_template.hass, self._entity.entity_id + ) + values[ATTR_THIS] = self._template_state + if variables is not None: values.update(variables) return _convert_outgoing_payload( - self._attr_command_template.async_render(values, parse_result=False) + self._command_template.async_render(values, parse_result=False) ) @@ -117,6 +126,7 @@ class MqttValueTemplate: config_attributes: TemplateVarsType = None, ) -> None: """Instantiate a value template.""" + self._template_state: template.TemplateStateFromEntityId | None = None self._value_template = value_template self._config_attributes = config_attributes if value_template is None: @@ -150,6 +160,11 @@ class MqttValueTemplate: if self._entity: values[ATTR_ENTITY_ID] = self._entity.entity_id values[ATTR_NAME] = self._entity.name + if not self._template_state and self._value_template.hass: + self._template_state = template.TemplateStateFromEntityId( + self._value_template.hass, self._entity.entity_id + ) + values[ATTR_THIS] = self._template_state if default == _SENTINEL: return self._value_template.async_render_with_possible_json_value( diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index fe8f483adf2..fd77dcec3a7 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -299,7 +299,7 @@ async def test_command_template_variables(hass, mqtt_mock_entry_with_yaml_config "command_topic": topic, "name": "Test Select", "options": ["milk", "beer"], - "command_template": '{"option": "{{ value }}", "entity_id": "{{ entity_id }}", "name": "{{ name }}"}', + "command_template": '{"option": "{{ value }}", "entity_id": "{{ entity_id }}", "name": "{{ name }}", "this_object_state": "{{ this.state }}"}', } }, ) @@ -319,7 +319,7 @@ async def test_command_template_variables(hass, mqtt_mock_entry_with_yaml_config mqtt_mock.async_publish.assert_called_once_with( topic, - '{"option": "beer", "entity_id": "select.test_select", "name": "Test Select"}', + '{"option": "beer", "entity_id": "select.test_select", "name": "Test Select", "this_object_state": "milk"}', 0, False, ) @@ -327,6 +327,20 @@ async def test_command_template_variables(hass, mqtt_mock_entry_with_yaml_config state = hass.states.get("select.test_select") assert state.state == "beer" + # Test that TemplateStateFromEntityId is not called again + with patch( + "homeassistant.helpers.template.TemplateStateFromEntityId", MagicMock() + ) as template_state_calls: + await hass.services.async_call( + "select", + "select_option", + {"entity_id": "select.test_select", "option": "milk"}, + blocking=True, + ) + assert template_state_calls.call_count == 0 + state = hass.states.get("select.test_select") + assert state.state == "milk" + async def test_value_template_value(hass): """Test the rendering of MQTT value template.""" @@ -359,10 +373,25 @@ async def test_value_template_value(hass): # test value template with entity entity = Entity() entity.hass = hass + entity.entity_id = "select.test" tpl = template.Template("{{ value_json.id }}") val_tpl = mqtt.MqttValueTemplate(tpl, entity=entity) assert val_tpl.async_render_with_possible_json_value('{"id": 4321}') == "4321" + # test this object in a template + tpl2 = template.Template("{{ this.entity_id }}") + val_tpl2 = mqtt.MqttValueTemplate(tpl2, entity=entity) + assert val_tpl2.async_render_with_possible_json_value("bla") == "select.test" + + with patch( + "homeassistant.helpers.template.TemplateStateFromEntityId", MagicMock() + ) as template_state_calls: + tpl3 = template.Template("{{ this.entity_id }}") + val_tpl3 = mqtt.MqttValueTemplate(tpl3, entity=entity) + val_tpl3.async_render_with_possible_json_value("call1") + val_tpl3.async_render_with_possible_json_value("call2") + assert template_state_calls.call_count == 1 + async def test_service_call_without_topic_does_not_publish( hass, mqtt_mock_entry_no_yaml_config