Fix realtime HA state-change execution

This commit is contained in:
2026-06-16 10:50:28 +02:00
parent 309b33b812
commit c5f42a39a9
7 changed files with 223 additions and 25 deletions

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
import logging
from collections.abc import Sequence
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo
@@ -18,6 +19,7 @@ from app.actuators.store import ActuatorStore
from app.config import Settings
from app.ha.exceptions import HaClientError
from app.ha.history import LogbookEntry, StateHistoryPoint, StateHistorySeries
from app.ha.models import HaEntitySummary
from app.ha.reader import HaReader
_MAX_PATTERNS = 500
@@ -193,23 +195,26 @@ class BehaviorEngine:
*,
context_state_overrides: dict[str, str | None] | None = None,
context_changed_at_overrides: dict[str, datetime | None] | None = None,
current_entities: Sequence[HaEntitySummary] | None = None,
) -> ActuatorRecord:
record = self._store.get(actuator_entity_id)
now = datetime.now(timezone.utc)
try:
entities = {entity.entity_id: entity for entity in self._ha_reader.read_entities()}
except HaClientError as exc:
logger.warning("Current HA state unavailable for %s: %s", actuator_entity_id, exc)
return self._save_behavior(
record,
record.behavior.model_copy(
update={
"last_evaluated_at": now,
"prediction": None,
"reason": f"Aktueller Home-Assistant-Zustand ist nicht verfügbar: {exc}",
}
),
)
if current_entities is None:
try:
current_entities = self._ha_reader.read_entities()
except HaClientError as exc:
logger.warning("Current HA state unavailable for %s: %s", actuator_entity_id, exc)
return self._save_behavior(
record,
record.behavior.model_copy(
update={
"last_evaluated_at": now,
"prediction": None,
"reason": f"Aktueller Home-Assistant-Zustand ist nicht verfügbar: {exc}",
}
),
)
entities = {entity.entity_id: entity for entity in current_entities}
actuator = entities.get(actuator_entity_id)
if actuator is None:
return self._save_behavior(
@@ -630,17 +635,25 @@ class BehaviorEngine:
)
return self._store.upsert(updated)
def handle_state_change(self, entity_id: str, new_state: dict[str, object] | None) -> None:
def handle_state_change(
self,
entity_id: str,
new_state: dict[str, object] | None,
*,
current_entities: Sequence[HaEntitySummary] | None = None,
) -> None:
"""Wird bei jedem HA-State-Change aufgerufen und löst sofortige Vorhersage aus.
- Wenn entity_id ein Aktor ist: evaluate() direkt.
- Wenn entity_id ein Kontext-Entity ist: alle betroffenen Aktoren evaluieren.
- Wenn current_entities gesetzt ist, kommt die Auswertung direkt aus dem
WebSocket-State-Cache statt aus einer frischen REST-Abfrage.
"""
# Aktor direkt evaluieren
for record in self._store.list():
if record.actuator_entity_id == entity_id:
try:
self.evaluate(record.actuator_entity_id)
self.evaluate(record.actuator_entity_id, current_entities=current_entities)
except Exception:
logger.exception("Event-basierte Vorhersage fehlgeschlagen für %s", record.actuator_entity_id)
return
@@ -661,6 +674,7 @@ class BehaviorEngine:
actuator_entity_id,
context_state_overrides={entity_id: event_state},
context_changed_at_overrides={entity_id: event_changed_at},
current_entities=current_entities,
)
except Exception:
logger.exception("Event-basierte Vorhersage fehlgeschlagen für %s", actuator_entity_id)