diff --git a/homeassistant/components/template/trigger.py b/homeassistant/components/template/trigger.py index 80ad585486b..9e6ee086c73 100644 --- a/homeassistant/components/template/trigger.py +++ b/homeassistant/components/template/trigger.py @@ -37,21 +37,49 @@ async def async_attach_trigger( template.attach(hass, time_delta) delay_cancel = None job = HassJob(action) + armed = False + + # Arm at setup if the template is already false. + try: + if not result_as_boolean(value_template.async_render()): + armed = True + except exceptions.TemplateError as ex: + _LOGGER.warning( + "Error initializing 'template' trigger for '%s': %s", + automation_info["name"], + ex, + ) @callback def template_listener(event, updates): """Listen for state changes and calls action.""" - nonlocal delay_cancel + nonlocal delay_cancel, armed result = updates.pop().result + if isinstance(result, exceptions.TemplateError): + _LOGGER.warning( + "Error evaluating 'template' trigger for '%s': %s", + automation_info["name"], + result, + ) + return + if delay_cancel: # pylint: disable=not-callable delay_cancel() delay_cancel = None if not result_as_boolean(result): + armed = True return + # Only fire when previously armed. + if not armed: + return + + # Fire! + armed = False + entity_id = event and event.data.get("entity_id") from_s = event and event.data.get("old_state") to_s = event and event.data.get("new_state") diff --git a/tests/components/template/test_trigger.py b/tests/components/template/test_trigger.py index de4974cb1b6..3ba79e85bf2 100644 --- a/tests/components/template/test_trigger.py +++ b/tests/components/template/test_trigger.py @@ -43,13 +43,15 @@ async def test_if_fires_on_change_bool(hass, calls): automation.DOMAIN: { "trigger": { "platform": "template", - "value_template": "{{ states.test.entity.state and true }}", + "value_template": '{{ states.test.entity.state == "world" and true }}', }, "action": {"service": "test.automation"}, } }, ) + assert len(calls) == 0 + hass.states.async_set("test.entity", "world") await hass.async_block_till_done() assert len(calls) == 1 @@ -75,13 +77,15 @@ async def test_if_fires_on_change_str(hass, calls): automation.DOMAIN: { "trigger": { "platform": "template", - "value_template": '{{ states.test.entity.state and "true" }}', + "value_template": '{{ states.test.entity.state == "world" and "true" }}', }, "action": {"service": "test.automation"}, } }, ) + assert len(calls) == 0 + hass.states.async_set("test.entity", "world") await hass.async_block_till_done() assert len(calls) == 1 @@ -96,7 +100,7 @@ async def test_if_fires_on_change_str_crazy(hass, calls): automation.DOMAIN: { "trigger": { "platform": "template", - "value_template": '{{ states.test.entity.state and "TrUE" }}', + "value_template": '{{ states.test.entity.state == "world" and "TrUE" }}', }, "action": {"service": "test.automation"}, } @@ -108,6 +112,62 @@ async def test_if_fires_on_change_str_crazy(hass, calls): assert len(calls) == 1 +async def test_if_not_fires_when_true_at_setup(hass, calls): + """Test for not firing during startup.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "template", + "value_template": '{{ states.test.entity.state == "hello" }}', + }, + "action": {"service": "test.automation"}, + } + }, + ) + + assert len(calls) == 0 + + hass.states.async_set("test.entity", "hello", force_update=True) + await hass.async_block_till_done() + assert len(calls) == 0 + + +async def test_if_not_fires_because_fail(hass, calls): + """Test for not firing after TemplateError.""" + hass.states.async_set("test.number", "1") + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "template", + "value_template": "{{ 84 / states.test.number.state|int == 42 }}", + }, + "action": {"service": "test.automation"}, + } + }, + ) + + assert len(calls) == 0 + + hass.states.async_set("test.number", "2") + await hass.async_block_till_done() + assert len(calls) == 1 + + hass.states.async_set("test.number", "0") + await hass.async_block_till_done() + assert len(calls) == 1 + + hass.states.async_set("test.number", "2") + await hass.async_block_till_done() + assert len(calls) == 1 + + async def test_if_not_fires_on_change_bool(hass, calls): """Test for not firing on boolean change.""" assert await async_setup_component( @@ -117,7 +177,7 @@ async def test_if_not_fires_on_change_bool(hass, calls): automation.DOMAIN: { "trigger": { "platform": "template", - "value_template": "{{ states.test.entity.state and false }}", + "value_template": '{{ states.test.entity.state == "world" and false }}', }, "action": {"service": "test.automation"}, } @@ -198,7 +258,7 @@ async def test_if_fires_on_two_change(hass, calls): automation.DOMAIN: { "trigger": { "platform": "template", - "value_template": "{{ states.test.entity.state and true }}", + "value_template": "{{ states.test.entity.state == 'world' }}", }, "action": {"service": "test.automation"}, }