Batch unrouted HA state events
This commit is contained in:
57
app/main.py
57
app/main.py
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user