Fix delay_on and delay_off restarting when a new trigger occurs during the delay (#145050)

This commit is contained in:
Petro31 2025-06-11 09:08:10 -04:00 committed by GitHub
parent 0362012bb3
commit 3e0aab55a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 74 additions and 6 deletions

View File

@ -352,6 +352,8 @@ class TriggerBinarySensorEntity(TriggerEntity, BinarySensorEntity, RestoreEntity
self._to_render_simple.append(key)
self._parse_result.add(key)
self._last_delay_from: bool | None = None
self._last_delay_to: bool | None = None
self._delay_cancel: CALLBACK_TYPE | None = None
self._auto_off_cancel: CALLBACK_TYPE | None = None
self._auto_off_time: datetime | None = None
@ -388,6 +390,20 @@ class TriggerBinarySensorEntity(TriggerEntity, BinarySensorEntity, RestoreEntity
"""Handle update of the data."""
self._process_data()
raw = self._rendered.get(CONF_STATE)
state = template.result_as_boolean(raw)
key = CONF_DELAY_ON if state else CONF_DELAY_OFF
delay = self._rendered.get(key) or self._config.get(key)
if (
self._delay_cancel
and delay
and self._attr_is_on == self._last_delay_from
and state == self._last_delay_to
):
return
if self._delay_cancel:
self._delay_cancel()
self._delay_cancel = None
@ -401,12 +417,6 @@ class TriggerBinarySensorEntity(TriggerEntity, BinarySensorEntity, RestoreEntity
self.async_write_ha_state()
return
raw = self._rendered.get(CONF_STATE)
state = template.result_as_boolean(raw)
key = CONF_DELAY_ON if state else CONF_DELAY_OFF
delay = self._rendered.get(key) or self._config.get(key)
# state without delay. None means rendering failed.
if self._attr_is_on == state or state is None or delay is None:
self._set_state(state)
@ -422,6 +432,8 @@ class TriggerBinarySensorEntity(TriggerEntity, BinarySensorEntity, RestoreEntity
return
# state with delay. Cancelled if new trigger received
self._last_delay_from = self._attr_is_on
self._last_delay_to = state
self._delay_cancel = async_call_later(
self.hass, delay.total_seconds(), partial(self._set_state, state)
)

View File

@ -1225,6 +1225,62 @@ async def test_template_with_trigger_templated_delay_on(hass: HomeAssistant) ->
assert state.state == STATE_OFF
@pytest.mark.parametrize(("count", "domain"), [(1, "template")])
@pytest.mark.parametrize(
("config", "delay_state"),
[
(
{
"template": {
"trigger": {"platform": "event", "event_type": "test_event"},
"binary_sensor": {
"name": "test",
"state": "{{ trigger.event.data.beer == 2 }}",
"device_class": "motion",
"delay_on": '{{ ({ "seconds": 10 }) }}',
},
},
},
STATE_ON,
),
(
{
"template": {
"trigger": {"platform": "event", "event_type": "test_event"},
"binary_sensor": {
"name": "test",
"state": "{{ trigger.event.data.beer != 2 }}",
"device_class": "motion",
"delay_off": '{{ ({ "seconds": 10 }) }}',
},
},
},
STATE_OFF,
),
],
)
@pytest.mark.usefixtures("start_ha")
async def test_trigger_template_delay_with_multiple_triggers(
hass: HomeAssistant, delay_state: str
) -> None:
"""Test trigger based binary sensor with multiple triggers occurring during the delay."""
future = dt_util.utcnow()
for _ in range(10):
# State should still be unknown
state = hass.states.get("binary_sensor.test")
assert state.state == STATE_UNKNOWN
hass.bus.async_fire("test_event", {"beer": 2}, context=Context())
await hass.async_block_till_done()
future += timedelta(seconds=1)
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.test")
assert state.state == delay_state
@pytest.mark.parametrize(("count", "domain"), [(1, "template")])
@pytest.mark.parametrize(
"config",