daily-briefing/server/routers/homeassistant.py

82 lines
2.3 KiB
Python
Raw Permalink Normal View History

"""Home Assistant data router — read states & control entities."""
from __future__ import annotations
import logging
from typing import Any, Dict, Optional
from fastapi import APIRouter
from pydantic import BaseModel
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,
) -> 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