Fix state automation trigger (#38014) (#38032)

for scenarios of enabling/disabling (~ creating/removing) entities,
so it does not trigger in removal if a `to: xxx` is defined, and also
does not trigger in creation if a `from: xxx` is present.
pull/38225/head
Eugenio Panadero 2020-07-23 21:17:11 +02:00 committed by GitHub
parent 6652af5cc0
commit 8cfffd00d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 58 additions and 29 deletions

View File

@ -73,16 +73,13 @@ async def async_attach_trigger(
from_s = event.data.get("old_state")
to_s = event.data.get("new_state")
old_state = getattr(from_s, "state", None)
new_state = getattr(to_s, "state", None)
if (
(from_s is not None and not match_from_state(from_s.state))
or (to_s is not None and not match_to_state(to_s.state))
or (
not match_all
and from_s is not None
and to_s is not None
and from_s.state == to_s.state
)
not match_from_state(old_state)
or not match_to_state(new_state)
or (not match_all and old_state == new_state)
):
return
@ -104,15 +101,6 @@ async def async_attach_trigger(
)
)
# Ignore changes to state attributes if from/to is in use
if (
not match_all
and from_s is not None
and to_s is not None
and from_s.state == to_s.state
):
return
if not time_delta:
call_action()
return

View File

@ -519,28 +519,69 @@ async def test_if_fires_on_entity_change_with_for(hass, calls):
assert 1 == len(calls)
async def test_if_fires_on_entity_removal(hass, calls):
"""Test for firing on entity removal, when new_state is None."""
context = Context()
hass.states.async_set("test.entity", "hello")
await hass.async_block_till_done()
async def test_if_fires_on_entity_creation_and_removal(hass, calls):
"""Test for firing on entity creation and removal, with to/from constraints."""
# set automations for multiple combinations to/from
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: {
"trigger": {"platform": "state", "entity_id": "test.entity"},
"action": {"service": "test.automation"},
}
automation.DOMAIN: [
{
"trigger": {"platform": "state", "entity_id": "test.entity_0"},
"action": {"service": "test.automation"},
},
{
"trigger": {
"platform": "state",
"from": "hello",
"entity_id": "test.entity_1",
},
"action": {"service": "test.automation"},
},
{
"trigger": {
"platform": "state",
"to": "world",
"entity_id": "test.entity_2",
},
"action": {"service": "test.automation"},
},
],
},
)
await hass.async_block_till_done()
assert hass.states.async_remove("test.entity", context=context)
# use contexts to identify trigger entities
context_0 = Context()
context_1 = Context()
context_2 = Context()
# automation with match_all triggers on creation
hass.states.async_set("test.entity_0", "any", context=context_0)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].context.parent_id == context.id
assert calls[0].context.parent_id == context_0.id
# create entities, trigger on test.entity_2 ('to' matches, no 'from')
hass.states.async_set("test.entity_1", "hello", context=context_1)
hass.states.async_set("test.entity_2", "world", context=context_2)
await hass.async_block_till_done()
assert len(calls) == 2
assert calls[1].context.parent_id == context_2.id
# removal of both, trigger on test.entity_1 ('from' matches, no 'to')
assert hass.states.async_remove("test.entity_1", context=context_1)
assert hass.states.async_remove("test.entity_2", context=context_2)
await hass.async_block_till_done()
assert len(calls) == 3
assert calls[2].context.parent_id == context_1.id
# automation with match_all triggers on removal
assert hass.states.async_remove("test.entity_0", context=context_0)
await hass.async_block_till_done()
assert len(calls) == 4
assert calls[3].context.parent_id == context_0.id
async def test_if_fires_on_for_condition(hass, calls):