HA controls + wider layout: toggle lights/switches, cover controls, more sensors
- Backend: call_ha_service() for controlling entities via HA REST API - Backend: POST /api/ha/control with JWT auth + cache invalidation - Backend: Parse switches, binary_sensors, humidity, climate entities - Frontend: HA card now xl:col-span-2 (double width) - Frontend: Interactive toggles for lights/switches, cover up/stop/down - Frontend: Temperature + humidity sensors, climate display, binary sensors - Frontend: Two-column internal layout (controls left, sensors right) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4e7c1909ee
commit
f5b7c53f18
6 changed files with 683 additions and 168 deletions
|
|
@ -1,15 +1,17 @@
|
|||
"""Home Assistant data router."""
|
||||
"""Home Assistant data router — read states & control entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any, Dict
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from fastapi import APIRouter
|
||||
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 fetch_ha_data
|
||||
from server.services.ha_service import call_ha_service, fetch_ha_data
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -18,22 +20,18 @@ 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.
|
||||
"""Return Home Assistant entity data (cached)."""
|
||||
|
||||
The exact shape depends on what ``fetch_ha_data`` returns; on failure an
|
||||
error stub is returned instead::
|
||||
|
||||
{ "error": true, "message": "..." }
|
||||
"""
|
||||
|
||||
# --- cache hit? -----------------------------------------------------------
|
||||
cached = await cache.get(CACHE_KEY)
|
||||
if cached is not None:
|
||||
return cached
|
||||
|
||||
# --- cache miss -----------------------------------------------------------
|
||||
try:
|
||||
data: Dict[str, Any] = await fetch_ha_data(
|
||||
get_settings().ha_url,
|
||||
|
|
@ -45,3 +43,41 @@ async def get_ha() -> Dict[str, Any]:
|
|||
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue