Simplify scenes to capture only target state, add target selector

- Remove DeviceBrightnessSnapshot and AutomationSnapshot from scene data model
- Simplify capture_current_snapshot and apply_scene_state to targets only
- Remove device/automation dependencies from scene preset API routes
- Add target selector (combobox + add/remove) to scene capture modal
- Fix stale profiles reference bug in scene_preset_store recapture
- Update automation engine call sites for simplified scene functions
- Sync scene presets cache between automations and scene-presets modules

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 18:55:11 +03:00
parent 0eb0f44ddb
commit ff4e7f8adb
14 changed files with 157 additions and 204 deletions

View File

@@ -7,9 +7,6 @@ from fastapi import APIRouter, Depends, HTTPException
from wled_controller.api.auth import AuthRequired
from wled_controller.api.dependencies import (
get_automation_engine,
get_automation_store,
get_device_store,
get_picture_target_store,
get_processor_manager,
get_scene_preset_store,
@@ -26,12 +23,9 @@ from wled_controller.core.scenes.scene_activator import (
apply_scene_state,
capture_current_snapshot,
)
from wled_controller.storage import DeviceStore
from wled_controller.storage.picture_target_store import PictureTargetStore
from wled_controller.storage.automation_store import AutomationStore
from wled_controller.storage.scene_preset import ScenePreset
from wled_controller.storage.scene_preset_store import ScenePresetStore
from wled_controller.core.automations.automation_engine import AutomationEngine
from wled_controller.utils import get_logger
logger = get_logger(__name__)
@@ -52,14 +46,6 @@ def _preset_to_response(preset: ScenePreset) -> ScenePresetResponse:
"fps": t.fps,
"auto_start": t.auto_start,
} for t in preset.targets],
devices=[{
"device_id": d.device_id,
"software_brightness": d.software_brightness,
} for d in preset.devices],
automations=[{
"automation_id": a.automation_id,
"enabled": a.enabled,
} for a in preset.automations],
order=preset.order,
created_at=preset.created_at,
updated_at=preset.updated_at,
@@ -79,14 +65,11 @@ async def create_scene_preset(
_auth: AuthRequired,
store: ScenePresetStore = Depends(get_scene_preset_store),
target_store: PictureTargetStore = Depends(get_picture_target_store),
device_store: DeviceStore = Depends(get_device_store),
automation_store: AutomationStore = Depends(get_automation_store),
manager: ProcessorManager = Depends(get_processor_manager),
):
"""Capture current state as a new scene preset."""
targets, devices, automations = capture_current_snapshot(
target_store, device_store, automation_store, manager,
)
target_ids = set(data.target_ids) if data.target_ids is not None else None
targets = capture_current_snapshot(target_store, manager, target_ids)
now = datetime.utcnow()
preset = ScenePreset(
@@ -95,8 +78,6 @@ async def create_scene_preset(
description=data.description,
color=data.color,
targets=targets,
devices=devices,
automations=automations,
order=store.count(),
created_at=now,
updated_at=now,
@@ -199,21 +180,22 @@ async def recapture_scene_preset(
_auth: AuthRequired,
store: ScenePresetStore = Depends(get_scene_preset_store),
target_store: PictureTargetStore = Depends(get_picture_target_store),
device_store: DeviceStore = Depends(get_device_store),
automation_store: AutomationStore = Depends(get_automation_store),
manager: ProcessorManager = Depends(get_processor_manager),
):
"""Re-capture current state into an existing preset (updates snapshot)."""
targets, devices, automations = capture_current_snapshot(
target_store, device_store, automation_store, manager,
)
try:
existing = store.get_preset(preset_id)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
# Only recapture targets that are already in the preset
existing_ids = {t.target_id for t in existing.targets}
targets = capture_current_snapshot(target_store, manager, existing_ids)
new_snapshot = ScenePreset(
id=preset_id,
name="",
targets=targets,
devices=devices,
automations=automations,
)
try:
@@ -236,9 +218,6 @@ async def activate_scene_preset(
_auth: AuthRequired,
store: ScenePresetStore = Depends(get_scene_preset_store),
target_store: PictureTargetStore = Depends(get_picture_target_store),
device_store: DeviceStore = Depends(get_device_store),
automation_store: AutomationStore = Depends(get_automation_store),
engine: AutomationEngine = Depends(get_automation_engine),
manager: ProcessorManager = Depends(get_processor_manager),
):
"""Activate a scene preset — restore the captured state."""
@@ -247,9 +226,7 @@ async def activate_scene_preset(
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
status, errors = await apply_scene_state(
preset, target_store, device_store, automation_store, engine, manager,
)
status, errors = await apply_scene_state(preset, target_store, manager)
if not errors:
logger.info(f"Scene preset '{preset.name}' activated successfully")

View File

@@ -15,22 +15,13 @@ class TargetSnapshotSchema(BaseModel):
auto_start: bool = False
class DeviceBrightnessSnapshotSchema(BaseModel):
device_id: str
software_brightness: int = 255
class AutomationSnapshotSchema(BaseModel):
automation_id: str
enabled: bool = True
class ScenePresetCreate(BaseModel):
"""Create a scene preset by capturing current state."""
name: str = Field(description="Preset name", min_length=1, max_length=100)
description: str = Field(default="", max_length=500)
color: str = Field(default="#4fc3f7", description="Card accent color")
target_ids: Optional[List[str]] = Field(None, description="Target IDs to capture (all if omitted)")
class ScenePresetUpdate(BaseModel):
@@ -50,8 +41,6 @@ class ScenePresetResponse(BaseModel):
description: str
color: str
targets: List[TargetSnapshotSchema]
devices: List[DeviceBrightnessSnapshotSchema]
automations: List[AutomationSnapshotSchema]
order: int
created_at: datetime
updated_at: datetime