Add profile conditions, scene presets, MQTT integration, and Scenes tab
Feature 1 — Profile Conditions: time-of-day, system idle (Win32 GetLastInputInfo), and display state (GUID_CONSOLE_DISPLAY_STATE) condition types for automatic profile activation. Feature 2 — Scene Presets: snapshot/restore system that captures target running states, device brightness, and profile enables. Server-side capture with 5-step activation order. Dedicated Scenes tab with CardSection-based card grid, command palette integration, and dashboard quick-activate section. Feature 3 — MQTT Integration: MQTTService singleton with aiomqtt, MQTTLEDClient device provider for pixel output, MQTT profile condition type with topic/payload matching, and frontend support for MQTT device type and condition editor. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
"""Scene preset data models — snapshot of current system state."""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class TargetSnapshot:
|
||||
"""Snapshot of a single target's mutable state."""
|
||||
|
||||
target_id: str
|
||||
running: bool = False
|
||||
color_strip_source_id: str = ""
|
||||
brightness_value_source_id: str = ""
|
||||
fps: int = 30
|
||||
auto_start: bool = False
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"target_id": self.target_id,
|
||||
"running": self.running,
|
||||
"color_strip_source_id": self.color_strip_source_id,
|
||||
"brightness_value_source_id": self.brightness_value_source_id,
|
||||
"fps": self.fps,
|
||||
"auto_start": self.auto_start,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "TargetSnapshot":
|
||||
return cls(
|
||||
target_id=data["target_id"],
|
||||
running=data.get("running", False),
|
||||
color_strip_source_id=data.get("color_strip_source_id", ""),
|
||||
brightness_value_source_id=data.get("brightness_value_source_id", ""),
|
||||
fps=data.get("fps", 30),
|
||||
auto_start=data.get("auto_start", False),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DeviceBrightnessSnapshot:
|
||||
"""Snapshot of a device's software brightness."""
|
||||
|
||||
device_id: str
|
||||
software_brightness: int = 255
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"device_id": self.device_id,
|
||||
"software_brightness": self.software_brightness,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "DeviceBrightnessSnapshot":
|
||||
return cls(
|
||||
device_id=data["device_id"],
|
||||
software_brightness=data.get("software_brightness", 255),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProfileSnapshot:
|
||||
"""Snapshot of a profile's enabled state."""
|
||||
|
||||
profile_id: str
|
||||
enabled: bool = True
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"profile_id": self.profile_id,
|
||||
"enabled": self.enabled,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "ProfileSnapshot":
|
||||
return cls(
|
||||
profile_id=data["profile_id"],
|
||||
enabled=data.get("enabled", True),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ScenePreset:
|
||||
"""A named snapshot of system state that can be restored."""
|
||||
|
||||
id: str
|
||||
name: str
|
||||
description: str = ""
|
||||
color: str = "#4fc3f7" # accent color for the card
|
||||
targets: List[TargetSnapshot] = field(default_factory=list)
|
||||
devices: List[DeviceBrightnessSnapshot] = field(default_factory=list)
|
||||
profiles: List[ProfileSnapshot] = field(default_factory=list)
|
||||
order: int = 0
|
||||
created_at: datetime = field(default_factory=datetime.utcnow)
|
||||
updated_at: datetime = field(default_factory=datetime.utcnow)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"color": self.color,
|
||||
"targets": [t.to_dict() for t in self.targets],
|
||||
"devices": [d.to_dict() for d in self.devices],
|
||||
"profiles": [p.to_dict() for p in self.profiles],
|
||||
"order": self.order,
|
||||
"created_at": self.created_at.isoformat(),
|
||||
"updated_at": self.updated_at.isoformat(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "ScenePreset":
|
||||
return cls(
|
||||
id=data["id"],
|
||||
name=data["name"],
|
||||
description=data.get("description", ""),
|
||||
color=data.get("color", "#4fc3f7"),
|
||||
targets=[TargetSnapshot.from_dict(t) for t in data.get("targets", [])],
|
||||
devices=[DeviceBrightnessSnapshot.from_dict(d) for d in data.get("devices", [])],
|
||||
profiles=[ProfileSnapshot.from_dict(p) for p in data.get("profiles", [])],
|
||||
order=data.get("order", 0),
|
||||
created_at=datetime.fromisoformat(data.get("created_at", datetime.utcnow().isoformat())),
|
||||
updated_at=datetime.fromisoformat(data.get("updated_at", datetime.utcnow().isoformat())),
|
||||
)
|
||||
Reference in New Issue
Block a user