feat: Home Assistant integration — WebSocket connection, automation conditions, UI
Add full Home Assistant integration via WebSocket API: - HARuntime: persistent WebSocket client with auth, auto-reconnect, entity state cache - HAManager: ref-counted runtime pool (like WeatherManager) - HomeAssistantCondition: new automation trigger type matching entity states - REST API: CRUD for HA sources + /test, /entities, /status endpoints - /api/v1/system/integrations-status: combined MQTT + HA dashboard indicators - Frontend: HA Sources tab in Streams, condition type in automation editor - Modal editor with host, token, SSL, entity filters - websockets>=13.0 dependency added
This commit is contained in:
97
server/src/wled_controller/api/schemas/home_assistant.py
Normal file
97
server/src/wled_controller/api/schemas/home_assistant.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""Home Assistant source schemas (CRUD + test + entities)."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class HomeAssistantSourceCreate(BaseModel):
|
||||
"""Request to create a Home Assistant source."""
|
||||
|
||||
name: str = Field(description="Source name", min_length=1, max_length=100)
|
||||
host: str = Field(description="HA host:port (e.g. '192.168.1.100:8123')", min_length=1)
|
||||
token: str = Field(description="Long-Lived Access Token", min_length=1)
|
||||
use_ssl: bool = Field(default=False, description="Use wss:// instead of ws://")
|
||||
entity_filters: List[str] = Field(
|
||||
default_factory=list, description="Entity ID filter patterns (e.g. ['sensor.*'])"
|
||||
)
|
||||
description: Optional[str] = Field(None, description="Optional description", max_length=500)
|
||||
tags: List[str] = Field(default_factory=list, description="User-defined tags")
|
||||
|
||||
|
||||
class HomeAssistantSourceUpdate(BaseModel):
|
||||
"""Request to update a Home Assistant source."""
|
||||
|
||||
name: Optional[str] = Field(None, description="Source name", min_length=1, max_length=100)
|
||||
host: Optional[str] = Field(None, description="HA host:port", min_length=1)
|
||||
token: Optional[str] = Field(None, description="Long-Lived Access Token", min_length=1)
|
||||
use_ssl: Optional[bool] = Field(None, description="Use wss://")
|
||||
entity_filters: Optional[List[str]] = Field(None, description="Entity ID filter patterns")
|
||||
description: Optional[str] = Field(None, description="Optional description", max_length=500)
|
||||
tags: Optional[List[str]] = None
|
||||
|
||||
|
||||
class HomeAssistantSourceResponse(BaseModel):
|
||||
"""Home Assistant source response."""
|
||||
|
||||
id: str = Field(description="Source ID")
|
||||
name: str = Field(description="Source name")
|
||||
host: str = Field(description="HA host:port")
|
||||
use_ssl: bool = Field(description="Whether SSL is enabled")
|
||||
entity_filters: List[str] = Field(default_factory=list, description="Entity filter patterns")
|
||||
connected: bool = Field(default=False, description="Whether the WebSocket connection is active")
|
||||
entity_count: int = Field(default=0, description="Number of cached entities")
|
||||
description: Optional[str] = Field(None, description="Description")
|
||||
tags: List[str] = Field(default_factory=list, description="User-defined tags")
|
||||
created_at: datetime = Field(description="Creation timestamp")
|
||||
updated_at: datetime = Field(description="Last update timestamp")
|
||||
|
||||
|
||||
class HomeAssistantSourceListResponse(BaseModel):
|
||||
"""List of Home Assistant sources."""
|
||||
|
||||
sources: List[HomeAssistantSourceResponse] = Field(description="List of HA sources")
|
||||
count: int = Field(description="Number of sources")
|
||||
|
||||
|
||||
class HomeAssistantEntityResponse(BaseModel):
|
||||
"""A single HA entity."""
|
||||
|
||||
entity_id: str = Field(description="Entity ID (e.g. 'sensor.temperature')")
|
||||
state: str = Field(description="Current state value")
|
||||
friendly_name: str = Field(description="Human-readable name")
|
||||
domain: str = Field(description="Entity domain (e.g. 'sensor', 'binary_sensor')")
|
||||
|
||||
|
||||
class HomeAssistantEntityListResponse(BaseModel):
|
||||
"""List of entities from a HA instance."""
|
||||
|
||||
entities: List[HomeAssistantEntityResponse] = Field(description="List of entities")
|
||||
count: int = Field(description="Number of entities")
|
||||
|
||||
|
||||
class HomeAssistantTestResponse(BaseModel):
|
||||
"""Connection test result."""
|
||||
|
||||
success: bool = Field(description="Whether connection and auth succeeded")
|
||||
ha_version: Optional[str] = Field(None, description="Home Assistant version")
|
||||
entity_count: int = Field(default=0, description="Number of entities found")
|
||||
error: Optional[str] = Field(None, description="Error message if connection failed")
|
||||
|
||||
|
||||
class HomeAssistantConnectionStatus(BaseModel):
|
||||
"""Connection status for dashboard indicators."""
|
||||
|
||||
source_id: str
|
||||
name: str
|
||||
connected: bool
|
||||
entity_count: int
|
||||
|
||||
|
||||
class HomeAssistantStatusResponse(BaseModel):
|
||||
"""Overall HA integration status for dashboard."""
|
||||
|
||||
connections: List[HomeAssistantConnectionStatus]
|
||||
total_sources: int
|
||||
connected_count: int
|
||||
Reference in New Issue
Block a user