"""Home Assistant data router — read states & control entities.""" from __future__ import annotations import logging from typing import Any, Dict, Optional from fastapi import APIRouter, Depends from pydantic import BaseModel from server.auth import require_admin from server.cache import cache from server.config import get_settings from server.services.ha_service import call_ha_service, fetch_ha_data logger = logging.getLogger(__name__) router = APIRouter(prefix="/api", tags=["homeassistant"]) CACHE_KEY = "ha" # --------------------------------------------------------------------------- # GET /api/ha — read-only, no auth needed # --------------------------------------------------------------------------- @router.get("/ha") async def get_ha() -> Dict[str, Any]: """Return Home Assistant entity data (cached).""" cached = await cache.get(CACHE_KEY) if cached is not None: return cached try: data: Dict[str, Any] = await fetch_ha_data( get_settings().ha_url, get_settings().ha_token, ) except Exception as exc: logger.exception("Failed to fetch Home Assistant data") return {"error": True, "message": str(exc)} await cache.set(CACHE_KEY, data, get_settings().ha_cache_ttl) return data # --------------------------------------------------------------------------- # POST /api/ha/control — requires auth # --------------------------------------------------------------------------- class HAControlRequest(BaseModel): entity_id: str action: str # toggle, turn_on, turn_off, open, close, stop data: Optional[Dict[str, Any]] = None @router.post("/ha/control") async def control_ha( body: HAControlRequest, admin_user: str = Depends(require_admin), # noqa: ARG001 ) -> Dict[str, Any]: """Control a Home Assistant entity (toggle light, open cover, etc.).""" settings = get_settings() if not settings.ha_url or not settings.ha_token: return {"ok": False, "error": "Home Assistant not configured"} result = await call_ha_service( url=settings.ha_url, token=settings.ha_token, entity_id=body.entity_id, action=body.action, service_data=body.data, ) # Invalidate cache so the next GET reflects the new state if result.get("ok"): await cache.invalidate(CACHE_KEY) logger.info("[HA] Cache invalidated after %s on %s", body.action, body.entity_id) return result