ACT-001: actuator-first sensor lifecycle
This commit is contained in:
34
app/main.py
34
app/main.py
@@ -1,4 +1,5 @@
|
||||
from contextlib import asynccontextmanager
|
||||
import asyncio
|
||||
from contextlib import asynccontextmanager, suppress
|
||||
from collections.abc import AsyncIterator
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
@@ -7,6 +8,9 @@ from fastapi import FastAPI
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from app.actuators.lifecycle import ActuatorReconciliationService
|
||||
from app.actuators.store import ActuatorStore
|
||||
from app.api.v1.actuators import router as actuators_router
|
||||
from app.api.v1.entities import router as entities_router
|
||||
from app.api.v1.automations import router as automations_router
|
||||
from app.automations.store import AutomationStore
|
||||
@@ -22,10 +26,14 @@ from backend.routes.ml import init_ml_routes
|
||||
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
||||
settings = app.state.settings
|
||||
client: HaClient | None = None
|
||||
reconcile_task: asyncio.Task[None] | None = None
|
||||
app.state.registry = ModelRegistry(settings.model_store)
|
||||
app.state.automation_store = AutomationStore(settings.automation_store)
|
||||
app.state.actuator_store = ActuatorStore(settings.actuator_store)
|
||||
if hasattr(app.state, "ha_reader"):
|
||||
del app.state.ha_reader
|
||||
if hasattr(app.state, "actuator_service"):
|
||||
del app.state.actuator_service
|
||||
if settings.ha_configured:
|
||||
client = HaClient(
|
||||
settings=HaClientSettings(
|
||||
@@ -34,9 +42,21 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
||||
)
|
||||
)
|
||||
app.state.ha_reader = HaReader(client=client)
|
||||
app.state.actuator_service = ActuatorReconciliationService(
|
||||
ha_reader=app.state.ha_reader,
|
||||
store=app.state.actuator_store,
|
||||
registry=app.state.registry,
|
||||
settings=settings,
|
||||
)
|
||||
await asyncio.to_thread(app.state.actuator_service.reconcile_all, "startup")
|
||||
reconcile_task = asyncio.create_task(_periodic_reconciliation(app))
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if reconcile_task is not None:
|
||||
reconcile_task.cancel()
|
||||
with suppress(asyncio.CancelledError):
|
||||
await reconcile_task
|
||||
if client is not None:
|
||||
client.close()
|
||||
|
||||
@@ -44,13 +64,14 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
||||
app = FastAPI(
|
||||
title="SillyHome Next API",
|
||||
description="Lokales Smart-Home-Intelligenzsystem für Home Assistant.",
|
||||
version="0.3.0",
|
||||
version="0.4.0",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
app.state.settings = load_settings()
|
||||
register_exception_handlers(app)
|
||||
app.include_router(entities_router)
|
||||
app.include_router(automations_router)
|
||||
app.include_router(actuators_router)
|
||||
init_ml_routes(app, model_store=app.state.settings.model_store)
|
||||
|
||||
STATIC_DIR = Path(__file__).with_name("static")
|
||||
@@ -65,3 +86,12 @@ def health() -> dict[str, str]:
|
||||
@app.get("/")
|
||||
def root() -> FileResponse:
|
||||
return FileResponse(STATIC_DIR / "index.html")
|
||||
|
||||
|
||||
async def _periodic_reconciliation(app: FastAPI) -> None:
|
||||
while True:
|
||||
await asyncio.sleep(app.state.settings.reconcile_interval_seconds)
|
||||
service = getattr(app.state, "actuator_service", None)
|
||||
if not isinstance(service, ActuatorReconciliationService):
|
||||
continue
|
||||
await asyncio.to_thread(service.reconcile_all, "scheduled")
|
||||
|
||||
Reference in New Issue
Block a user