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:
@@ -310,22 +310,17 @@ class AutomationEngine:
|
||||
# For "revert" mode, capture current state before activating
|
||||
if automation.deactivation_mode == "revert":
|
||||
from wled_controller.core.scenes.scene_activator import capture_current_snapshot
|
||||
targets, devices, automations = capture_current_snapshot(
|
||||
self._target_store, self._device_store, self._store, self._manager,
|
||||
)
|
||||
targets = capture_current_snapshot(self._target_store, self._manager)
|
||||
self._pre_activation_snapshots[automation.id] = ScenePreset(
|
||||
id=f"_revert_{automation.id}",
|
||||
name=f"Pre-activation snapshot for {automation.name}",
|
||||
targets=targets,
|
||||
devices=devices,
|
||||
profiles=automations,
|
||||
)
|
||||
|
||||
# Apply the scene
|
||||
from wled_controller.core.scenes.scene_activator import apply_scene_state
|
||||
status, errors = await apply_scene_state(
|
||||
preset, self._target_store, self._device_store, self._store,
|
||||
self, self._manager, skip_automations=True,
|
||||
preset, self._target_store, self._manager,
|
||||
)
|
||||
|
||||
self._active_automations[automation.id] = True
|
||||
@@ -352,11 +347,10 @@ class AutomationEngine:
|
||||
|
||||
if deactivation_mode == "revert":
|
||||
snapshot = self._pre_activation_snapshots.pop(automation_id, None)
|
||||
if snapshot and self._target_store and self._device_store:
|
||||
if snapshot and self._target_store:
|
||||
from wled_controller.core.scenes.scene_activator import apply_scene_state
|
||||
status, errors = await apply_scene_state(
|
||||
snapshot, self._target_store, self._device_store, self._store,
|
||||
self, self._manager, skip_automations=True,
|
||||
snapshot, self._target_store, self._manager,
|
||||
)
|
||||
if errors:
|
||||
logger.warning(f"Automation {automation_id} revert errors: {errors}")
|
||||
@@ -367,13 +361,12 @@ class AutomationEngine:
|
||||
|
||||
elif deactivation_mode == "fallback_scene":
|
||||
fallback_id = automation.deactivation_scene_preset_id if automation else None
|
||||
if fallback_id and self._scene_preset_store and self._target_store and self._device_store:
|
||||
if fallback_id and self._scene_preset_store and self._target_store:
|
||||
try:
|
||||
fallback = self._scene_preset_store.get_preset(fallback_id)
|
||||
from wled_controller.core.scenes.scene_activator import apply_scene_state
|
||||
status, errors = await apply_scene_state(
|
||||
fallback, self._target_store, self._device_store, self._store,
|
||||
self, self._manager, skip_automations=True,
|
||||
fallback, self._target_store, self._manager,
|
||||
)
|
||||
if errors:
|
||||
logger.warning(f"Automation {automation_id} fallback errors: {errors}")
|
||||
|
||||
@@ -3,15 +3,11 @@
|
||||
These functions are used by both the scene-presets API route and the automation engine.
|
||||
"""
|
||||
|
||||
from typing import List, Optional, Tuple
|
||||
from typing import List, Optional, Set, Tuple
|
||||
|
||||
from wled_controller.core.processing.processor_manager import ProcessorManager
|
||||
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 (
|
||||
AutomationSnapshot,
|
||||
DeviceBrightnessSnapshot,
|
||||
ScenePreset,
|
||||
TargetSnapshot,
|
||||
)
|
||||
@@ -22,16 +18,18 @@ logger = get_logger(__name__)
|
||||
|
||||
def capture_current_snapshot(
|
||||
target_store: PictureTargetStore,
|
||||
device_store: DeviceStore,
|
||||
automation_store: AutomationStore,
|
||||
processor_manager: ProcessorManager,
|
||||
) -> Tuple[List[TargetSnapshot], List[DeviceBrightnessSnapshot], List[AutomationSnapshot]]:
|
||||
"""Capture current system state as snapshot lists.
|
||||
target_ids: Optional[Set[str]] = None,
|
||||
) -> List[TargetSnapshot]:
|
||||
"""Capture current target state as a snapshot list.
|
||||
|
||||
Returns (targets, devices, automations) snapshot tuples.
|
||||
Args:
|
||||
target_ids: If provided, only capture these targets. Captures all if None.
|
||||
"""
|
||||
targets = []
|
||||
for t in target_store.get_all_targets():
|
||||
if target_ids is not None and t.id not in target_ids:
|
||||
continue
|
||||
proc = processor_manager._processors.get(t.id)
|
||||
running = proc.is_running if proc else False
|
||||
targets.append(TargetSnapshot(
|
||||
@@ -43,44 +41,20 @@ def capture_current_snapshot(
|
||||
auto_start=getattr(t, "auto_start", False),
|
||||
))
|
||||
|
||||
devices = []
|
||||
for d in device_store.get_all_devices():
|
||||
devices.append(DeviceBrightnessSnapshot(
|
||||
device_id=d.id,
|
||||
software_brightness=getattr(d, "software_brightness", 255),
|
||||
))
|
||||
|
||||
automations = []
|
||||
for a in automation_store.get_all_automations():
|
||||
automations.append(AutomationSnapshot(
|
||||
automation_id=a.id,
|
||||
enabled=a.enabled,
|
||||
))
|
||||
|
||||
return targets, devices, automations
|
||||
return targets
|
||||
|
||||
|
||||
async def apply_scene_state(
|
||||
preset: ScenePreset,
|
||||
target_store: PictureTargetStore,
|
||||
device_store: DeviceStore,
|
||||
automation_store: AutomationStore,
|
||||
automation_engine,
|
||||
processor_manager: ProcessorManager,
|
||||
*,
|
||||
skip_automations: bool = False,
|
||||
) -> Tuple[str, List[str]]:
|
||||
"""Apply a scene preset's state to the system.
|
||||
|
||||
Args:
|
||||
preset: The scene preset to activate.
|
||||
target_store: Target store for reading/updating targets.
|
||||
device_store: Device store for reading/updating devices.
|
||||
automation_store: Automation store for reading/updating automations.
|
||||
automation_engine: Automation engine for deactivation and re-evaluation.
|
||||
processor_manager: Processor manager for starting/stopping targets.
|
||||
skip_automations: If True, skip toggling automation enable states (used when
|
||||
called from the automation engine itself to avoid recursion).
|
||||
|
||||
Returns:
|
||||
(status, errors) where status is "activated" or "partial" and
|
||||
@@ -88,21 +62,7 @@ async def apply_scene_state(
|
||||
"""
|
||||
errors: List[str] = []
|
||||
|
||||
# 1. Toggle automation enable states
|
||||
if not skip_automations:
|
||||
for auto_snap in preset.automations:
|
||||
try:
|
||||
a = automation_store.get_automation(auto_snap.automation_id)
|
||||
if a.enabled != auto_snap.enabled:
|
||||
if not auto_snap.enabled:
|
||||
await automation_engine.deactivate_if_active(auto_snap.automation_id)
|
||||
automation_store.update_automation(auto_snap.automation_id, enabled=auto_snap.enabled)
|
||||
except ValueError:
|
||||
errors.append(f"Automation {auto_snap.automation_id} not found (skipped)")
|
||||
except Exception as e:
|
||||
errors.append(f"Automation {auto_snap.automation_id}: {e}")
|
||||
|
||||
# 2. Stop targets that should be stopped
|
||||
# 1. Stop targets that should be stopped
|
||||
for ts in preset.targets:
|
||||
if not ts.running:
|
||||
try:
|
||||
@@ -112,7 +72,7 @@ async def apply_scene_state(
|
||||
except Exception as e:
|
||||
errors.append(f"Stop target {ts.target_id}: {e}")
|
||||
|
||||
# 3. Update target configs (CSS, brightness source, FPS)
|
||||
# 2. Update target configs (CSS, brightness source, FPS)
|
||||
for ts in preset.targets:
|
||||
try:
|
||||
target = target_store.get_target(ts.target_id)
|
||||
@@ -147,7 +107,7 @@ async def apply_scene_state(
|
||||
except Exception as e:
|
||||
errors.append(f"Target {ts.target_id} config: {e}")
|
||||
|
||||
# 4. Start targets that should be running
|
||||
# 3. Start targets that should be running
|
||||
for ts in preset.targets:
|
||||
if ts.running:
|
||||
try:
|
||||
@@ -157,28 +117,6 @@ async def apply_scene_state(
|
||||
except Exception as e:
|
||||
errors.append(f"Start target {ts.target_id}: {e}")
|
||||
|
||||
# 5. Set device brightness
|
||||
for ds in preset.devices:
|
||||
try:
|
||||
device = device_store.get_device(ds.device_id)
|
||||
if device.software_brightness != ds.software_brightness:
|
||||
device_store.update_device(ds.device_id, software_brightness=ds.software_brightness)
|
||||
# Update live processor brightness
|
||||
dev_state = processor_manager._devices.get(ds.device_id)
|
||||
if dev_state:
|
||||
dev_state.software_brightness = ds.software_brightness
|
||||
except ValueError:
|
||||
errors.append(f"Device {ds.device_id} not found (skipped)")
|
||||
except Exception as e:
|
||||
errors.append(f"Device {ds.device_id} brightness: {e}")
|
||||
|
||||
# Trigger automation re-evaluation after all changes
|
||||
if not skip_automations:
|
||||
try:
|
||||
await automation_engine.trigger_evaluate()
|
||||
except Exception as e:
|
||||
errors.append(f"Automation re-evaluation: {e}")
|
||||
|
||||
status = "activated" if not errors else "partial"
|
||||
if errors:
|
||||
logger.warning(f"Scene activation errors: {errors}")
|
||||
|
||||
Reference in New Issue
Block a user