Files
wled-screen-controller-mixed/server/src/wled_controller/config.py
alexei.dolgolyov 87e7eee743 Add Pattern Templates for Key Colors targets with visual canvas editor
Introduce Pattern Template entity as a reusable rectangle layout that
Key Colors targets reference via pattern_template_id. This replaces
inline rectangle storage with a shared template system.

Backend:
- New PatternTemplate data model, store (JSON persistence), CRUD API
- KC targets now reference pattern_template_id instead of inline rectangles
- ProcessorManager resolves pattern template at KC processing start
- Picture source test endpoint supports capture_duration=0 for single frame
- Delete protection: 409 when template is referenced by a KC target

Frontend:
- Pattern Templates section in Key Colors sub-tab with card UI
- Visual canvas editor with drag-to-move, 8-point resize handles
- Background capture from any picture source for visual alignment
- Precise coordinate list synced bidirectionally with canvas
- Resizable editor container, viewport-constrained modal
- KC target editor uses pattern template dropdown instead of inline rects
- Localization (en/ru) for all new UI elements

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 18:07:40 +03:00

160 lines
4.0 KiB
Python

"""Configuration management for LED Grab."""
import os
from pathlib import Path
from typing import List, Literal
import yaml
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class ServerConfig(BaseSettings):
"""Server configuration."""
host: str = "0.0.0.0"
port: int = 8080
log_level: str = "INFO"
cors_origins: List[str] = ["*"]
class AuthConfig(BaseSettings):
"""Authentication configuration."""
api_keys: dict[str, str] = {} # label: key mapping (required for security)
class ProcessingConfig(BaseSettings):
"""Processing configuration."""
default_fps: int = 30
max_fps: int = 90
min_fps: int = 10
border_width: int = 10
interpolation_mode: Literal["average", "median", "dominant"] = "average"
class ScreenCaptureConfig(BaseSettings):
"""Screen capture configuration."""
buffer_size: int = 2
class WLEDConfig(BaseSettings):
"""WLED client configuration."""
timeout: int = 5
retry_attempts: int = 3
retry_delay: int = 1
protocol: Literal["http", "https"] = "http"
max_brightness: int = 255
class StorageConfig(BaseSettings):
"""Storage configuration."""
devices_file: str = "data/devices.json"
templates_file: str = "data/capture_templates.json"
postprocessing_templates_file: str = "data/postprocessing_templates.json"
picture_sources_file: str = "data/picture_sources.json"
picture_targets_file: str = "data/picture_targets.json"
pattern_templates_file: str = "data/pattern_templates.json"
class LoggingConfig(BaseSettings):
"""Logging configuration."""
format: Literal["json", "text"] = "json"
file: str = "logs/wled_controller.log"
max_size_mb: int = 100
backup_count: int = 5
class Config(BaseSettings):
"""Main application configuration."""
model_config = SettingsConfigDict(
env_prefix="WLED_",
env_nested_delimiter="__",
case_sensitive=False,
)
server: ServerConfig = Field(default_factory=ServerConfig)
auth: AuthConfig = Field(default_factory=AuthConfig)
processing: ProcessingConfig = Field(default_factory=ProcessingConfig)
screen_capture: ScreenCaptureConfig = Field(default_factory=ScreenCaptureConfig)
wled: WLEDConfig = Field(default_factory=WLEDConfig)
storage: StorageConfig = Field(default_factory=StorageConfig)
logging: LoggingConfig = Field(default_factory=LoggingConfig)
@classmethod
def from_yaml(cls, config_path: str | Path) -> "Config":
"""Load configuration from YAML file.
Args:
config_path: Path to YAML configuration file
Returns:
Config instance
"""
config_path = Path(config_path)
if not config_path.exists():
raise FileNotFoundError(f"Configuration file not found: {config_path}")
with open(config_path, "r") as f:
config_data = yaml.safe_load(f)
return cls(**config_data)
@classmethod
def load(cls) -> "Config":
"""Load configuration from default locations.
Tries to load from:
1. Environment variable WLED_CONFIG_PATH
2. ./config/default_config.yaml
3. Default values
Returns:
Config instance
"""
config_path = os.getenv("WLED_CONFIG_PATH")
if config_path:
return cls.from_yaml(config_path)
# Try default location
default_path = Path("config/default_config.yaml")
if default_path.exists():
return cls.from_yaml(default_path)
# Use defaults
return cls()
# Global configuration instance
config: Config | None = None
def get_config() -> Config:
"""Get global configuration instance.
Returns:
Config instance
"""
global config
if config is None:
config = Config.load()
return config
def reload_config() -> Config:
"""Reload configuration from file.
Returns:
New Config instance
"""
global config
config = Config.load()
return config