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:
@@ -180,6 +180,34 @@ class StartupCondition(Condition):
|
||||
return cls()
|
||||
|
||||
|
||||
@dataclass
|
||||
class HomeAssistantCondition(Condition):
|
||||
"""Activate based on a Home Assistant entity state."""
|
||||
|
||||
condition_type: str = "home_assistant"
|
||||
ha_source_id: str = "" # references HomeAssistantSource
|
||||
entity_id: str = "" # e.g. "binary_sensor.front_door"
|
||||
state: str = "" # expected state value
|
||||
match_mode: str = "exact" # "exact" | "contains" | "regex"
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
d = super().to_dict()
|
||||
d["ha_source_id"] = self.ha_source_id
|
||||
d["entity_id"] = self.entity_id
|
||||
d["state"] = self.state
|
||||
d["match_mode"] = self.match_mode
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "HomeAssistantCondition":
|
||||
return cls(
|
||||
ha_source_id=data.get("ha_source_id", ""),
|
||||
entity_id=data.get("entity_id", ""),
|
||||
state=data.get("state", ""),
|
||||
match_mode=data.get("match_mode", "exact"),
|
||||
)
|
||||
|
||||
|
||||
_CONDITION_MAP: Dict[str, Type[Condition]] = {
|
||||
"always": AlwaysCondition,
|
||||
"application": ApplicationCondition,
|
||||
@@ -189,6 +217,7 @@ _CONDITION_MAP: Dict[str, Type[Condition]] = {
|
||||
"mqtt": MQTTCondition,
|
||||
"webhook": WebhookCondition,
|
||||
"startup": StartupCondition,
|
||||
"home_assistant": HomeAssistantCondition,
|
||||
}
|
||||
|
||||
|
||||
@@ -243,6 +272,10 @@ class Automation:
|
||||
deactivation_mode=data.get("deactivation_mode", "none"),
|
||||
deactivation_scene_preset_id=data.get("deactivation_scene_preset_id"),
|
||||
tags=data.get("tags", []),
|
||||
created_at=datetime.fromisoformat(data.get("created_at", datetime.now(timezone.utc).isoformat())),
|
||||
updated_at=datetime.fromisoformat(data.get("updated_at", datetime.now(timezone.utc).isoformat())),
|
||||
created_at=datetime.fromisoformat(
|
||||
data.get("created_at", datetime.now(timezone.utc).isoformat())
|
||||
),
|
||||
updated_at=datetime.fromisoformat(
|
||||
data.get("updated_at", datetime.now(timezone.utc).isoformat())
|
||||
),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user