Add this object to MQTT templates (#77142)

* Add `this` object to MQTT templates

* Only set once, remove hass guard

* Set once if there is a state

* Add tests TemplateStateFromEntityId calls once
This commit is contained in:
Jan Bouwhuis 2022-08-23 08:43:07 +02:00 committed by GitHub
parent c76dec138a
commit be2366d773
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 50 additions and 6 deletions

View file

@ -17,6 +17,8 @@ from homeassistant.helpers.typing import TemplateVarsType
_SENTINEL = object() _SENTINEL = object()
ATTR_THIS = "this"
PublishPayloadType = Union[str, bytes, int, float, None] PublishPayloadType = Union[str, bytes, int, float, None]
@ -57,7 +59,8 @@ class MqttCommandTemplate:
entity: Entity | None = None, entity: Entity | None = None,
) -> None: ) -> None:
"""Instantiate a command template.""" """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: if command_template is None:
return return
@ -91,17 +94,23 @@ class MqttCommandTemplate:
return payload return payload
if self._attr_command_template is None: if self._command_template is None:
return value return value
values = {"value": value} values: dict[str, Any] = {"value": value}
if self._entity: if self._entity:
values[ATTR_ENTITY_ID] = self._entity.entity_id values[ATTR_ENTITY_ID] = self._entity.entity_id
values[ATTR_NAME] = self._entity.name 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: if variables is not None:
values.update(variables) values.update(variables)
return _convert_outgoing_payload( 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, config_attributes: TemplateVarsType = None,
) -> None: ) -> None:
"""Instantiate a value template.""" """Instantiate a value template."""
self._template_state: template.TemplateStateFromEntityId | None = None
self._value_template = value_template self._value_template = value_template
self._config_attributes = config_attributes self._config_attributes = config_attributes
if value_template is None: if value_template is None:
@ -150,6 +160,11 @@ class MqttValueTemplate:
if self._entity: if self._entity:
values[ATTR_ENTITY_ID] = self._entity.entity_id values[ATTR_ENTITY_ID] = self._entity.entity_id
values[ATTR_NAME] = self._entity.name 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: if default == _SENTINEL:
return self._value_template.async_render_with_possible_json_value( return self._value_template.async_render_with_possible_json_value(

View file

@ -299,7 +299,7 @@ async def test_command_template_variables(hass, mqtt_mock_entry_with_yaml_config
"command_topic": topic, "command_topic": topic,
"name": "Test Select", "name": "Test Select",
"options": ["milk", "beer"], "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( mqtt_mock.async_publish.assert_called_once_with(
topic, 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, 0,
False, 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") state = hass.states.get("select.test_select")
assert state.state == "beer" 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): async def test_value_template_value(hass):
"""Test the rendering of MQTT value template.""" """Test the rendering of MQTT value template."""
@ -359,10 +373,25 @@ async def test_value_template_value(hass):
# test value template with entity # test value template with entity
entity = Entity() entity = Entity()
entity.hass = hass entity.hass = hass
entity.entity_id = "select.test"
tpl = template.Template("{{ value_json.id }}") tpl = template.Template("{{ value_json.id }}")
val_tpl = mqtt.MqttValueTemplate(tpl, entity=entity) val_tpl = mqtt.MqttValueTemplate(tpl, entity=entity)
assert val_tpl.async_render_with_possible_json_value('{"id": 4321}') == "4321" 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( async def test_service_call_without_topic_does_not_publish(
hass, mqtt_mock_entry_no_yaml_config hass, mqtt_mock_entry_no_yaml_config