feat: game integration system
Receive real-time events from games (CS2, Dota 2, LoL, etc.) and drive LED effects through the existing color strip and value source pipelines. Core: - GameEventBus (thread-safe pub/sub) with standardized 23-type event vocabulary - GameAdapter ABC + AdapterRegistry + MappingAdapter (YAML-driven) - Built-in adapters: CS2 GSI, Dota 2 GSI, LoL Live Client, Generic Webhook - Community YAML adapters: Minecraft, Valorant, Rocket League - GameEventColorStripStream with 5 effects (flash/pulse/sweep/color_shift/breathing) - GameEventValueSource with EMA smoothing and timeout - 4 built-in effect presets (FPS Combat, MOBA Health, Racing, Generic Alert) - Auto-setup for Valve GSI games (Steam path detection, cfg file writing) - Demo capture engine exposed to non-demo mode Frontend: - Game tab in Streams tree navigation with integration cards - Game integration editor modal with adapter picker, config fields, event mappings - game_event source type in CSS and ValueSource editors - Setup instructions overlay (markdown rendered) - Live event monitor and connection test API: - Full CRUD for game integrations - Event ingestion endpoint (adapter-level auth) - Adapter metadata, presets, auto-setup, status/diagnostics endpoints
This commit is contained in:
@@ -4,7 +4,7 @@ import uuid
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional
|
||||
|
||||
from wled_controller.storage.automation import Automation, Condition
|
||||
from wled_controller.storage.automation import Automation, Rule
|
||||
from wled_controller.storage.base_sqlite_store import BaseSqliteStore
|
||||
from wled_controller.storage.database import Database
|
||||
from wled_controller.utils import get_logger
|
||||
@@ -28,13 +28,22 @@ class AutomationStore(BaseSqliteStore[Automation]):
|
||||
self,
|
||||
name: str,
|
||||
enabled: bool = True,
|
||||
condition_logic: str = "or",
|
||||
conditions: Optional[List[Condition]] = None,
|
||||
rule_logic: str = "or",
|
||||
rules: Optional[List[Rule]] = None,
|
||||
scene_preset_id: Optional[str] = None,
|
||||
deactivation_mode: str = "none",
|
||||
deactivation_scene_preset_id: Optional[str] = None,
|
||||
tags: Optional[List[str]] = None,
|
||||
# Legacy parameter aliases
|
||||
condition_logic: Optional[str] = None,
|
||||
conditions: Optional[List[Rule]] = None,
|
||||
) -> Automation:
|
||||
# Support legacy parameter names
|
||||
if condition_logic is not None and rule_logic == "or":
|
||||
rule_logic = condition_logic
|
||||
if conditions is not None and rules is None:
|
||||
rules = conditions
|
||||
|
||||
for a in self._items.values():
|
||||
if a.name == name:
|
||||
raise ValueError(f"Automation with name '{name}' already exists")
|
||||
@@ -46,8 +55,8 @@ class AutomationStore(BaseSqliteStore[Automation]):
|
||||
id=automation_id,
|
||||
name=name,
|
||||
enabled=enabled,
|
||||
condition_logic=condition_logic,
|
||||
conditions=conditions or [],
|
||||
rule_logic=rule_logic,
|
||||
rules=rules or [],
|
||||
scene_preset_id=scene_preset_id,
|
||||
deactivation_mode=deactivation_mode,
|
||||
deactivation_scene_preset_id=deactivation_scene_preset_id,
|
||||
@@ -66,13 +75,22 @@ class AutomationStore(BaseSqliteStore[Automation]):
|
||||
automation_id: str,
|
||||
name: Optional[str] = None,
|
||||
enabled: Optional[bool] = None,
|
||||
condition_logic: Optional[str] = None,
|
||||
conditions: Optional[List[Condition]] = None,
|
||||
rule_logic: Optional[str] = None,
|
||||
rules: Optional[List[Rule]] = None,
|
||||
scene_preset_id: str = "__unset__",
|
||||
deactivation_mode: Optional[str] = None,
|
||||
deactivation_scene_preset_id: str = "__unset__",
|
||||
tags: Optional[List[str]] = None,
|
||||
# Legacy parameter aliases
|
||||
condition_logic: Optional[str] = None,
|
||||
conditions: Optional[List[Rule]] = None,
|
||||
) -> Automation:
|
||||
# Support legacy parameter names
|
||||
if condition_logic is not None and rule_logic is None:
|
||||
rule_logic = condition_logic
|
||||
if conditions is not None and rules is None:
|
||||
rules = conditions
|
||||
|
||||
automation = self.get(automation_id)
|
||||
|
||||
if name is not None:
|
||||
@@ -80,16 +98,18 @@ class AutomationStore(BaseSqliteStore[Automation]):
|
||||
automation.name = name
|
||||
if enabled is not None:
|
||||
automation.enabled = enabled
|
||||
if condition_logic is not None:
|
||||
automation.condition_logic = condition_logic
|
||||
if conditions is not None:
|
||||
automation.conditions = conditions
|
||||
if rule_logic is not None:
|
||||
automation.rule_logic = rule_logic
|
||||
if rules is not None:
|
||||
automation.rules = rules
|
||||
if scene_preset_id != "__unset__":
|
||||
automation.scene_preset_id = None if scene_preset_id == "" else scene_preset_id
|
||||
if deactivation_mode is not None:
|
||||
automation.deactivation_mode = deactivation_mode
|
||||
if deactivation_scene_preset_id != "__unset__":
|
||||
automation.deactivation_scene_preset_id = None if deactivation_scene_preset_id == "" else deactivation_scene_preset_id
|
||||
automation.deactivation_scene_preset_id = (
|
||||
None if deactivation_scene_preset_id == "" else deactivation_scene_preset_id
|
||||
)
|
||||
if tags is not None:
|
||||
automation.tags = tags
|
||||
|
||||
|
||||
Reference in New Issue
Block a user