189 files

This commit is contained in:
Home Assistant Version Control
2026-09-08 18:05:32 +00:00
parent 79fefc0a7c
commit 303886f212
189 changed files with 21402 additions and 3514 deletions
@@ -371,6 +371,12 @@ class RuntimeTrigger(BaseTrigger):
"""Reset accumulated runtime (called after maintenance completion)."""
super().reset()
self._accumulated_seconds = 0.0
# A fresh start is a fresh session for the #149 cap too: the session
# that ran up to the completion has been booked (and cleared), the
# one starting now may book the cap again. Leaving the old booking in
# place froze a still-ON entity at zero until it next turned off
# (bug review 2026-09-04).
self._session_booked = 0.0
# If entity is currently ON, keep tracking from now (fresh start)
if self._on_since_dt is not None:
now = dt_util.utcnow()
@@ -92,6 +92,28 @@ class StateChangeTrigger(BaseTrigger):
if self._restored_pending_dt is None:
self._restored_pending_state = None
def _in_alert_state(self, state: str | None) -> bool | None:
"""Whether *state* is the latch's ALERT state (#167).
A single-shot trigger (target_changes == 1) is a latch that recovers
when the entity leaves its alert state. What "alert" means follows
the configured pattern:
- To-state set: alert == the To-state (leaving it recovers).
- Only a From-state set ("from ok to any" — a dock/error sensor with
a dozen fault states): alert == anything BUT the From-state, so the
latch recovers when the entity returns to it. Before #167 the
recovery was hard-wired to the To-state and such a latch never
cleared — although the dialog offers both fields as optional and
auto-complete-on-recovery right next to them.
- Neither set: no latch semantics (None) — a pure change counter.
"""
if self._to_state is not None:
return _norm_state(state) == self._to_state
if self._from_state is not None:
return _norm_state(state) != self._from_state
return None
async def async_setup(self) -> None:
"""Set up state change trigger.
@@ -150,7 +172,7 @@ class StateChangeTrigger(BaseTrigger):
"""
if self._change_count < self._target_changes:
return
if self._to_state is not None and self._target_changes == 1 and _norm_state(live_state) != self._to_state:
if self._target_changes == 1 and self._in_alert_state(live_state) is False:
self._change_count = 0
self._current_value = 0.0
self._triggered = False
@@ -386,16 +408,13 @@ class StateChangeTrigger(BaseTrigger):
# Latch recovery: a single-shot state alarm (target_changes == 1 — an
# adopted problem sensor or an appliance event) clears when the entity
# leaves its alert state. Reset the counter so the next occurrence can
# fire again, and run the deactivation path — which auto-completes on
# recovery when opted in. Multi-count triggers keep accumulating and
# only reset on manual completion, so they are untouched here.
elif (
self._to_state is not None
and self._target_changes == 1
and self._triggered
and _norm_state(new_val) != self._to_state
):
# leaves its alert state (see _in_alert_state for what that means on
# a from-only pattern, #167). Reset the counter so the next
# occurrence can fire again, and run the deactivation path — which
# auto-completes on recovery when opted in. Multi-count triggers keep
# accumulating and only reset on manual completion, so they are
# untouched here.
elif self._target_changes == 1 and self._triggered and self._in_alert_state(new_val) is False:
self._change_count = 0
self._current_value = 0.0
self._persist_runtime_soon()