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:
2026-03-27 22:42:48 +03:00
parent f3d07fc47f
commit 2153dde4b7
26 changed files with 1912 additions and 119 deletions
+53 -33
View File
@@ -21,7 +21,9 @@ from wled_controller.storage.value_source_store import ValueSourceStore
from wled_controller.storage.automation_store import AutomationStore
from wled_controller.storage.scene_preset_store import ScenePresetStore
from wled_controller.storage.sync_clock_store import SyncClockStore
from wled_controller.storage.color_strip_processing_template_store import ColorStripProcessingTemplateStore
from wled_controller.storage.color_strip_processing_template_store import (
ColorStripProcessingTemplateStore,
)
from wled_controller.storage.gradient_store import GradientStore
from wled_controller.storage.weather_source_store import WeatherSourceStore
from wled_controller.storage.asset_store import AssetStore
@@ -30,6 +32,8 @@ from wled_controller.core.weather.weather_manager import WeatherManager
from wled_controller.core.backup.auto_backup import AutoBackupEngine
from wled_controller.core.processing.sync_clock_manager import SyncClockManager
from wled_controller.core.update.update_service import UpdateService
from wled_controller.storage.home_assistant_store import HomeAssistantStore
from wled_controller.core.home_assistant.ha_manager import HomeAssistantManager
T = TypeVar("T")
@@ -136,6 +140,14 @@ def get_asset_store() -> AssetStore:
return _get("asset_store", "Asset store")
def get_ha_store() -> HomeAssistantStore:
return _get("ha_store", "Home Assistant store")
def get_ha_manager() -> HomeAssistantManager:
return _get("ha_manager", "Home Assistant manager")
def get_database() -> Database:
return _get("database", "Database")
@@ -157,12 +169,14 @@ def fire_entity_event(entity_type: str, action: str, entity_id: str) -> None:
"""
pm = _deps.get("processor_manager")
if pm is not None:
pm.fire_event({
"type": "entity_changed",
"entity_type": entity_type,
"action": action,
"id": entity_id,
})
pm.fire_event(
{
"type": "entity_changed",
"entity_type": entity_type,
"action": action,
"id": entity_id,
}
)
# ── Initialization ──────────────────────────────────────────────────────
@@ -193,31 +207,37 @@ def init_dependencies(
weather_manager: WeatherManager | None = None,
update_service: UpdateService | None = None,
asset_store: AssetStore | None = None,
ha_store: HomeAssistantStore | None = None,
ha_manager: HomeAssistantManager | None = None,
):
"""Initialize global dependencies."""
_deps.update({
"database": database,
"device_store": device_store,
"template_store": template_store,
"processor_manager": processor_manager,
"pp_template_store": pp_template_store,
"pattern_template_store": pattern_template_store,
"picture_source_store": picture_source_store,
"output_target_store": output_target_store,
"color_strip_store": color_strip_store,
"audio_source_store": audio_source_store,
"audio_template_store": audio_template_store,
"value_source_store": value_source_store,
"automation_store": automation_store,
"scene_preset_store": scene_preset_store,
"automation_engine": automation_engine,
"auto_backup_engine": auto_backup_engine,
"sync_clock_store": sync_clock_store,
"sync_clock_manager": sync_clock_manager,
"cspt_store": cspt_store,
"gradient_store": gradient_store,
"weather_source_store": weather_source_store,
"weather_manager": weather_manager,
"update_service": update_service,
"asset_store": asset_store,
})
_deps.update(
{
"database": database,
"device_store": device_store,
"template_store": template_store,
"processor_manager": processor_manager,
"pp_template_store": pp_template_store,
"pattern_template_store": pattern_template_store,
"picture_source_store": picture_source_store,
"output_target_store": output_target_store,
"color_strip_store": color_strip_store,
"audio_source_store": audio_source_store,
"audio_template_store": audio_template_store,
"value_source_store": value_source_store,
"automation_store": automation_store,
"scene_preset_store": scene_preset_store,
"automation_engine": automation_engine,
"auto_backup_engine": auto_backup_engine,
"sync_clock_store": sync_clock_store,
"sync_clock_manager": sync_clock_manager,
"cspt_store": cspt_store,
"gradient_store": gradient_store,
"weather_source_store": weather_source_store,
"weather_manager": weather_manager,
"update_service": update_service,
"asset_store": asset_store,
"ha_store": ha_store,
"ha_manager": ha_manager,
}
)