Batch unrouted HA state events

This commit is contained in:
2026-06-18 13:00:57 +02:00
parent 149f11a18e
commit 937c79a19b
6 changed files with 56 additions and 11 deletions

View File

@@ -16,6 +16,7 @@ from app.core.models import (
BackupBundle,
ControlProfile,
ControlState,
EntityState,
HandoffMode,
LearningState,
RuntimeState,
@@ -49,7 +50,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
app = FastAPI(
title="SillyHome Future API",
description="SillyHome v2 event-core side project.",
version="2.0.0-alpha.6",
version="2.0.0-alpha.7",
lifespan=lifespan,
)
@@ -188,16 +189,34 @@ def _load_initial_ha_states(client: FutureHaClient) -> None:
async def _ha_listener_loop(client: FutureHaClient) -> None:
routed_triggers: set[str] = set()
next_route_refresh = 0.0
pending_unrouted: list[StateEvent] = []
last_unrouted_flush = 0.0
while True:
await asyncio.to_thread(_set_websocket_status, "connecting")
try:
async for event in client.listen_state_events():
loop_time = asyncio.get_running_loop().time()
if loop_time >= next_route_refresh:
routed_triggers = await asyncio.to_thread(_routed_trigger_ids)
next_route_refresh = loop_time + 5
await asyncio.to_thread(_set_websocket_status, "connected")
await asyncio.to_thread(
event_core.process_state_event,
event,
execute=_execute_ha_decision,
)
if event.entity_id in routed_triggers:
if pending_unrouted:
await asyncio.to_thread(_merge_unrouted_events, pending_unrouted)
pending_unrouted = []
await asyncio.to_thread(
event_core.process_state_event,
event,
execute=_execute_ha_decision,
)
else:
pending_unrouted.append(event)
if len(pending_unrouted) >= 100 or loop_time - last_unrouted_flush >= 2:
await asyncio.to_thread(_merge_unrouted_events, pending_unrouted)
pending_unrouted = []
last_unrouted_flush = loop_time
await asyncio.sleep(0)
except asyncio.CancelledError:
raise
@@ -214,6 +233,32 @@ def _set_websocket_status(status: str) -> None:
stores.save_runtime(runtime)
def _routed_trigger_ids() -> set[str]:
result: set[str] = set()
for profile in stores.learning().profiles.values():
for pattern in profile.patterns:
if pattern.trigger_entity_id is not None:
result.add(pattern.trigger_entity_id)
return result
def _merge_unrouted_events(events: list[StateEvent]) -> None:
if not events:
return
runtime = stores.runtime()
for event in events:
runtime.entities[event.entity_id] = EntityState(
entity_id=event.entity_id,
domain=event.entity_id.split(".", 1)[0],
state=event.new_state,
changed_at=event.changed_at,
area_name=event.attributes.get("area_name"),
device_id=event.attributes.get("device_id"),
friendly_name=event.attributes.get("friendly_name"),
)
stores.save_runtime(runtime)
def _execute_ha_decision(decision: object) -> bool:
if ha_client is None or not hasattr(decision, "actuator_entity_id"):
return False