mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 09:17:10 +00:00
Allow triggering on all state changes, ignoring attributes (#59713)
* Allow triggering on all state changes, ignoring attributes * Add comment * Apply suggestions from code review Co-authored-by: Franck Nijhof <git@frenck.dev> Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
parent
9256a033a6
commit
4f01631bd6
@ -39,8 +39,8 @@ BASE_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend(
|
|||||||
TRIGGER_STATE_SCHEMA = BASE_SCHEMA.extend(
|
TRIGGER_STATE_SCHEMA = BASE_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
# These are str on purpose. Want to catch YAML conversions
|
# These are str on purpose. Want to catch YAML conversions
|
||||||
vol.Optional(CONF_FROM): vol.Any(str, [str]),
|
vol.Optional(CONF_FROM): vol.Any(str, [str], None),
|
||||||
vol.Optional(CONF_TO): vol.Any(str, [str]),
|
vol.Optional(CONF_TO): vol.Any(str, [str], None),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -75,11 +75,15 @@ async def async_attach_trigger(
|
|||||||
) -> CALLBACK_TYPE:
|
) -> CALLBACK_TYPE:
|
||||||
"""Listen for state changes based on configuration."""
|
"""Listen for state changes based on configuration."""
|
||||||
entity_id = config.get(CONF_ENTITY_ID)
|
entity_id = config.get(CONF_ENTITY_ID)
|
||||||
from_state = config.get(CONF_FROM, MATCH_ALL)
|
if (from_state := config.get(CONF_FROM)) is None:
|
||||||
to_state = config.get(CONF_TO, MATCH_ALL)
|
from_state = MATCH_ALL
|
||||||
|
if (to_state := config.get(CONF_TO)) is None:
|
||||||
|
to_state = MATCH_ALL
|
||||||
time_delta = config.get(CONF_FOR)
|
time_delta = config.get(CONF_FOR)
|
||||||
template.attach(hass, time_delta)
|
template.attach(hass, time_delta)
|
||||||
match_all = from_state == MATCH_ALL and to_state == MATCH_ALL
|
# If neither CONF_FROM or CONF_TO are specified,
|
||||||
|
# fire on all changes to the state or an attribute
|
||||||
|
match_all = CONF_FROM not in config and CONF_TO not in config
|
||||||
unsub_track_same = {}
|
unsub_track_same = {}
|
||||||
period: dict[str, timedelta] = {}
|
period: dict[str, timedelta] = {}
|
||||||
match_from_state = process_state_match(from_state)
|
match_from_state = process_state_match(from_state)
|
||||||
|
@ -106,7 +106,7 @@ async def test_if_fires_on_entity_change_with_from_filter(hass, calls):
|
|||||||
|
|
||||||
|
|
||||||
async def test_if_fires_on_entity_change_with_to_filter(hass, calls):
|
async def test_if_fires_on_entity_change_with_to_filter(hass, calls):
|
||||||
"""Test for firing on entity change with no filter."""
|
"""Test for firing on entity change with to filter."""
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
automation.DOMAIN,
|
automation.DOMAIN,
|
||||||
@ -128,6 +128,54 @@ async def test_if_fires_on_entity_change_with_to_filter(hass, calls):
|
|||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_if_fires_on_entity_change_with_from_filter_all(hass, calls):
|
||||||
|
"""Test for firing on entity change with filter."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
automation.DOMAIN,
|
||||||
|
{
|
||||||
|
automation.DOMAIN: {
|
||||||
|
"trigger": {
|
||||||
|
"platform": "state",
|
||||||
|
"entity_id": "test.entity",
|
||||||
|
"from": None,
|
||||||
|
},
|
||||||
|
"action": {"service": "test.automation"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
hass.states.async_set("test.entity", "world")
|
||||||
|
hass.states.async_set("test.entity", "world", {"attribute": 5})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_if_fires_on_entity_change_with_to_filter_all(hass, calls):
|
||||||
|
"""Test for firing on entity change with to filter."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
automation.DOMAIN,
|
||||||
|
{
|
||||||
|
automation.DOMAIN: {
|
||||||
|
"trigger": {
|
||||||
|
"platform": "state",
|
||||||
|
"entity_id": "test.entity",
|
||||||
|
"to": None,
|
||||||
|
},
|
||||||
|
"action": {"service": "test.automation"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
hass.states.async_set("test.entity", "world")
|
||||||
|
hass.states.async_set("test.entity", "world", {"attribute": 5})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
|
||||||
async def test_if_fires_on_attribute_change_with_to_filter(hass, calls):
|
async def test_if_fires_on_attribute_change_with_to_filter(hass, calls):
|
||||||
"""Test for not firing on attribute change."""
|
"""Test for not firing on attribute change."""
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
@ -1217,6 +1265,94 @@ async def test_attribute_if_fires_on_entity_where_attr_stays_constant(hass, call
|
|||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_attribute_if_fires_on_entity_where_attr_stays_constant_filter(
|
||||||
|
hass, calls
|
||||||
|
):
|
||||||
|
"""Test for firing if attribute stays the same."""
|
||||||
|
hass.states.async_set("test.entity", "bla", {"name": "other_name"})
|
||||||
|
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
automation.DOMAIN,
|
||||||
|
{
|
||||||
|
automation.DOMAIN: {
|
||||||
|
"trigger": {
|
||||||
|
"platform": "state",
|
||||||
|
"entity_id": "test.entity",
|
||||||
|
"attribute": "name",
|
||||||
|
"to": "best_name",
|
||||||
|
},
|
||||||
|
"action": {"service": "test.automation"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Leave all attributes the same
|
||||||
|
hass.states.async_set(
|
||||||
|
"test.entity", "bla", {"name": "best_name", "other": "old_value"}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
# Change the untracked attribute
|
||||||
|
hass.states.async_set(
|
||||||
|
"test.entity", "bla", {"name": "best_name", "other": "new_value"}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
# Change the tracked attribute
|
||||||
|
hass.states.async_set(
|
||||||
|
"test.entity", "bla", {"name": "other_name", "other": "old_value"}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_attribute_if_fires_on_entity_where_attr_stays_constant_all(hass, calls):
|
||||||
|
"""Test for firing if attribute stays the same."""
|
||||||
|
hass.states.async_set("test.entity", "bla", {"name": "hello", "other": "old_value"})
|
||||||
|
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
automation.DOMAIN,
|
||||||
|
{
|
||||||
|
automation.DOMAIN: {
|
||||||
|
"trigger": {
|
||||||
|
"platform": "state",
|
||||||
|
"entity_id": "test.entity",
|
||||||
|
"attribute": "name",
|
||||||
|
"to": None,
|
||||||
|
},
|
||||||
|
"action": {"service": "test.automation"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Leave all attributes the same
|
||||||
|
hass.states.async_set(
|
||||||
|
"test.entity", "bla", {"name": "name_1", "other": "old_value"}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
# Change the untracked attribute
|
||||||
|
hass.states.async_set(
|
||||||
|
"test.entity", "bla", {"name": "name_1", "other": "new_value"}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
# Change the tracked attribute
|
||||||
|
hass.states.async_set(
|
||||||
|
"test.entity", "bla", {"name": "name_2", "other": "old_value"}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(calls) == 2
|
||||||
|
|
||||||
|
|
||||||
async def test_attribute_if_not_fires_on_entities_change_with_for_after_stop(
|
async def test_attribute_if_not_fires_on_entities_change_with_for_after_stop(
|
||||||
hass, calls
|
hass, calls
|
||||||
):
|
):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user