257 files

This commit is contained in:
Home Assistant Version Control
2026-10-02 16:06:24 +00:00
parent 7c55603942
commit c29f5cc088
258 changed files with 24621 additions and 3136 deletions
@@ -7,7 +7,7 @@ import math
from abc import ABC, abstractmethod
from collections.abc import Coroutine
from datetime import datetime
from typing import TYPE_CHECKING, Any
from typing import Any, Protocol
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, State, callback
from homeassistant.helpers.event import (
@@ -15,10 +15,6 @@ from homeassistant.helpers.event import (
async_track_state_change_event,
)
if TYPE_CHECKING:
from ...coordinator import MaintenanceCoordinator
from ...sensor import MaintenanceSensor
from ...const import (
EVENT_TRIGGER_ACTIVATED,
EVENT_TRIGGER_DEACTIVATED,
@@ -28,6 +24,49 @@ from ...helpers.managed_timer import ManagedTimer
_LOGGER = logging.getLogger(__name__)
class TriggerCoordinator(Protocol):
"""What a trigger needs from its coordinator: the object's, or a compound
condition's proxy that routes these per condition."""
async def async_add_trigger_history_entry(self, task_id: str, trigger_value: float | None = None) -> None: ...
async def async_auto_complete_on_recovery(self, task_id: str, trigger_value: float) -> None: ...
async def async_persist_trigger_runtime(
self,
task_id: str,
runtime_data: dict[str, Any],
entity_id: str | None = None,
*,
immediate: bool = False,
) -> None: ...
async def async_request_refresh(self) -> None: ...
def note_trigger_edge(self, task_id: str, *, recovered: bool = True) -> None: ...
class TriggerHost(Protocol):
"""What a trigger needs from the entity it drives: the task's sensor, or
a compound condition's proxy entity."""
@property
def entity_id(self) -> str: ...
@property
def _task_id(self) -> str: ...
@property
def coordinator(self) -> TriggerCoordinator: ...
def async_update_trigger_state(
self,
is_triggered: bool,
current_value: float | None = None,
trigger_entity_id: str | None = None,
) -> bool | None: ...
# The initial-evaluation retry: an entity that is unknown/unavailable at
# setup is re-checked this often, this many times, before the state-change
# listener alone is trusted to catch its recovery (issue #1 family).
@@ -45,7 +84,7 @@ class BaseTrigger(ABC):
def __init__(
self,
hass: HomeAssistant,
entity: MaintenanceSensor,
entity: TriggerHost,
trigger_config: dict[str, Any],
) -> None:
"""Initialize the trigger."""
@@ -75,7 +114,7 @@ class BaseTrigger(ABC):
self._logged_unavailable = False # Log-once pattern for unavailable
@property
def _coordinator(self) -> MaintenanceCoordinator:
def _coordinator(self) -> TriggerCoordinator:
"""Get the coordinator from the entity."""
return self.entity.coordinator
@@ -84,6 +123,14 @@ class BaseTrigger(ABC):
"""Get the task ID from the entity."""
return self.entity._task_id
def _event_ids(self) -> dict[str, Any]:
"""The task's ids for the trigger events — EXAMPLES.md tells
automations to deep-link with them (`?entry_id=…&task_id=…`), but
only the completed / skipped / reset events carried them (audit
2026-09-28)."""
entry = getattr(self._coordinator, "entry", None)
return {"entry_id": getattr(entry, "entry_id", None), "task_id": self._task_id}
async def async_setup(self) -> None:
"""Set up the trigger: validate entity and register listener.
@@ -307,6 +354,12 @@ class BaseTrigger(ABC):
"""
@property
def _is_compound_condition(self) -> bool:
"""This trigger watches one condition of a compound (its entity is
the condition's proxy), not the task itself."""
return getattr(self.entity, "is_compound_condition", False) is True
def _request_coordinator_refresh(self) -> None:
"""#175: a trigger flip must reach the coordinator now, not on the
next 5-minute tick. Notifications (and the ``maintenance_supporter_
@@ -332,6 +385,12 @@ class BaseTrigger(ABC):
current_value=value,
trigger_entity_id=self.entity_id,
)
if self._is_compound_condition:
# One condition of a compound: the compound decides whether the
# TASK activated and announces it. Firing here sent
# maintenance_supporter_trigger_activated with the task's ids when
# one half of an AND flipped (bug audit 2026-09-29).
return
# Add history entry for the trigger activation
self._track(self._coordinator.async_add_trigger_history_entry(self._task_id, trigger_value=value))
@@ -343,6 +402,7 @@ class BaseTrigger(ABC):
EVENT_TRIGGER_ACTIVATED,
{
"entity_id": self.entity.entity_id,
**self._event_ids(),
"trigger_entity": self.entity_id,
"trigger_attribute": self.attribute,
"trigger_value": value,
@@ -367,6 +427,8 @@ class BaseTrigger(ABC):
current_value=value,
trigger_entity_id=self.entity_id,
)
if self._is_compound_condition:
return # the compound announces the task's recovery (see activation)
self._request_coordinator_refresh()
@@ -375,6 +437,7 @@ class BaseTrigger(ABC):
EVENT_TRIGGER_DEACTIVATED,
{
"entity_id": self.entity.entity_id,
**self._event_ids(),
"trigger_entity": self.entity_id,
"trigger_attribute": self.attribute,
"trigger_value": value,