"""Picture target schemas (CRUD, processing state, metrics).""" from datetime import datetime from typing import Dict, Optional, List from pydantic import BaseModel, Field DEFAULT_STATE_CHECK_INTERVAL = 30 # seconds between health checks class KeyColorRectangleSchema(BaseModel): """A named rectangle for key color extraction (relative coords 0.0-1.0).""" name: str = Field(description="Rectangle name", min_length=1, max_length=50) x: float = Field(default=0.0, description="Left edge (0.0-1.0)", ge=0.0, le=1.0) y: float = Field(default=0.0, description="Top edge (0.0-1.0)", ge=0.0, le=1.0) width: float = Field(default=1.0, description="Width (0.0-1.0)", gt=0.0, le=1.0) height: float = Field(default=1.0, description="Height (0.0-1.0)", gt=0.0, le=1.0) class KeyColorsSettingsSchema(BaseModel): """Settings for key colors extraction.""" fps: int = Field(default=10, description="Extraction rate (1-60)", ge=1, le=60) interpolation_mode: str = Field(default="average", description="Color mode (average, median, dominant)") smoothing: float = Field(default=0.3, description="Temporal smoothing (0.0-1.0)", ge=0.0, le=1.0) pattern_template_id: str = Field(default="", description="Pattern template ID for rectangle layout") brightness: float = Field(default=1.0, description="Output brightness (0.0-1.0)", ge=0.0, le=1.0) brightness_value_source_id: str = Field(default="", description="Brightness value source ID") class ExtractedColorResponse(BaseModel): """A single extracted color.""" r: int = Field(description="Red (0-255)") g: int = Field(description="Green (0-255)") b: int = Field(description="Blue (0-255)") hex: str = Field(description="Hex color (#rrggbb)") class KeyColorsResponse(BaseModel): """Extracted key colors for a target.""" target_id: str = Field(description="Target ID") colors: Dict[str, ExtractedColorResponse] = Field(description="Rectangle name -> color") timestamp: Optional[datetime] = Field(None, description="Extraction timestamp") class PictureTargetCreate(BaseModel): """Request to create a picture target.""" name: str = Field(description="Target name", min_length=1, max_length=100) target_type: str = Field(default="led", description="Target type (led, key_colors)") # LED target fields device_id: str = Field(default="", description="LED device ID") color_strip_source_id: str = Field(default="", description="Color strip source ID") brightness_value_source_id: str = Field(default="", description="Brightness value source ID") fps: int = Field(default=30, ge=1, le=90, description="Target send FPS (1-90)") keepalive_interval: float = Field(default=1.0, description="Keepalive send interval when screen is static (0.5-5.0s)", ge=0.5, le=5.0) state_check_interval: int = Field(default=DEFAULT_STATE_CHECK_INTERVAL, description="Device health check interval (5-600s)", ge=5, le=600) min_brightness_threshold: int = Field(default=0, ge=0, le=254, description="Min brightness threshold (0=disabled); below this → off") adaptive_fps: bool = Field(default=False, description="Auto-reduce FPS when device is unresponsive") protocol: str = Field(default="ddp", pattern="^(ddp|http)$", description="Send protocol: ddp (UDP) or http (JSON API)") # KC target fields picture_source_id: str = Field(default="", description="Picture source ID (for key_colors targets)") key_colors_settings: Optional[KeyColorsSettingsSchema] = Field(None, description="Key colors settings (for key_colors targets)") description: Optional[str] = Field(None, description="Optional description", max_length=500) auto_start: bool = Field(default=False, description="Auto-start on server boot") class PictureTargetUpdate(BaseModel): """Request to update a picture target.""" name: Optional[str] = Field(None, description="Target name", min_length=1, max_length=100) # LED target fields device_id: Optional[str] = Field(None, description="LED device ID") color_strip_source_id: Optional[str] = Field(None, description="Color strip source ID") brightness_value_source_id: Optional[str] = Field(None, description="Brightness value source ID") fps: Optional[int] = Field(None, ge=1, le=90, description="Target send FPS (1-90)") keepalive_interval: Optional[float] = Field(None, description="Keepalive interval (0.5-5.0s)", ge=0.5, le=5.0) state_check_interval: Optional[int] = Field(None, description="Health check interval (5-600s)", ge=5, le=600) min_brightness_threshold: Optional[int] = Field(None, ge=0, le=254, description="Min brightness threshold (0=disabled); below this → off") adaptive_fps: Optional[bool] = Field(None, description="Auto-reduce FPS when device is unresponsive") protocol: Optional[str] = Field(None, pattern="^(ddp|http)$", description="Send protocol: ddp (UDP) or http (JSON API)") # KC target fields picture_source_id: Optional[str] = Field(None, description="Picture source ID (for key_colors targets)") key_colors_settings: Optional[KeyColorsSettingsSchema] = Field(None, description="Key colors settings (for key_colors targets)") description: Optional[str] = Field(None, description="Optional description", max_length=500) auto_start: Optional[bool] = Field(None, description="Auto-start on server boot") class PictureTargetResponse(BaseModel): """Picture target response.""" id: str = Field(description="Target ID") name: str = Field(description="Target name") target_type: str = Field(description="Target type") # LED target fields device_id: str = Field(default="", description="LED device ID") color_strip_source_id: str = Field(default="", description="Color strip source ID") brightness_value_source_id: str = Field(default="", description="Brightness value source ID") fps: Optional[int] = Field(None, description="Target send FPS") keepalive_interval: float = Field(default=1.0, description="Keepalive interval (s)") state_check_interval: int = Field(default=DEFAULT_STATE_CHECK_INTERVAL, description="Health check interval (s)") min_brightness_threshold: int = Field(default=0, description="Min brightness threshold (0=disabled)") adaptive_fps: bool = Field(default=False, description="Auto-reduce FPS when device is unresponsive") protocol: str = Field(default="ddp", description="Send protocol (ddp or http)") # KC target fields picture_source_id: str = Field(default="", description="Picture source ID (key_colors)") key_colors_settings: Optional[KeyColorsSettingsSchema] = Field(None, description="Key colors settings") description: Optional[str] = Field(None, description="Description") auto_start: bool = Field(default=False, description="Auto-start on server boot") created_at: datetime = Field(description="Creation timestamp") updated_at: datetime = Field(description="Last update timestamp") class PictureTargetListResponse(BaseModel): """List of picture targets response.""" targets: List[PictureTargetResponse] = Field(description="List of picture targets") count: int = Field(description="Number of targets") class TargetProcessingState(BaseModel): """Processing state for a picture target.""" target_id: str = Field(description="Target ID") device_id: Optional[str] = Field(None, description="Device ID") color_strip_source_id: str = Field(default="", description="Color strip source ID") processing: bool = Field(description="Whether processing is active") fps_actual: Optional[float] = Field(None, description="Actual FPS achieved") fps_potential: Optional[float] = Field(None, description="Potential FPS (processing speed without throttle)") fps_target: Optional[int] = Field(None, description="Target FPS") frames_skipped: Optional[int] = Field(None, description="Frames skipped (no screen change)") frames_keepalive: Optional[int] = Field(None, description="Keepalive frames sent during standby") fps_current: Optional[int] = Field(None, description="Frames sent in the last second") timing_send_ms: Optional[float] = Field(None, description="DDP send time (ms)") timing_extract_ms: Optional[float] = Field(None, description="Border pixel extraction time (ms)") timing_map_leds_ms: Optional[float] = Field(None, description="LED color mapping time (ms)") timing_smooth_ms: Optional[float] = Field(None, description="Temporal smoothing time (ms)") timing_total_ms: Optional[float] = Field(None, description="Total processing time per frame (ms)") timing_audio_read_ms: Optional[float] = Field(None, description="Audio device read time (ms)") timing_audio_fft_ms: Optional[float] = Field(None, description="Audio FFT analysis time (ms)") timing_audio_render_ms: Optional[float] = Field(None, description="Audio visualization render time (ms)") timing_calc_colors_ms: Optional[float] = Field(None, description="Color calculation time (ms, KC targets)") timing_broadcast_ms: Optional[float] = Field(None, description="WebSocket broadcast time (ms, KC targets)") display_index: Optional[int] = Field(None, description="Current display index") overlay_active: bool = Field(default=False, description="Whether visualization overlay is active") last_update: Optional[datetime] = Field(None, description="Last successful update") errors: List[str] = Field(default_factory=list, description="Recent errors") device_online: bool = Field(default=False, description="Whether device is reachable") device_latency_ms: Optional[float] = Field(None, description="Health check latency in ms") device_name: Optional[str] = Field(None, description="Device name reported by firmware") device_version: Optional[str] = Field(None, description="Firmware version") device_led_count: Optional[int] = Field(None, description="LED count reported by device") device_rgbw: Optional[bool] = Field(None, description="Whether device uses RGBW LEDs") device_led_type: Optional[str] = Field(None, description="LED chip type (e.g. WS2812B, SK6812 RGBW)") device_fps: Optional[int] = Field(None, description="Device-reported FPS (WLED internal refresh rate)") device_last_checked: Optional[datetime] = Field(None, description="Last health check time") device_error: Optional[str] = Field(None, description="Last health check error") device_streaming_reachable: Optional[bool] = Field(None, description="Device reachable during streaming (HTTP probe)") fps_effective: Optional[int] = Field(None, description="Effective FPS after adaptive reduction") class TargetMetricsResponse(BaseModel): """Target metrics response.""" target_id: str = Field(description="Target ID") device_id: Optional[str] = Field(None, description="Device ID") processing: bool = Field(description="Whether processing is active") fps_actual: Optional[float] = Field(None, description="Actual FPS") fps_target: Optional[int] = Field(None, description="Target FPS") uptime_seconds: float = Field(description="Processing uptime in seconds") frames_processed: int = Field(description="Total frames processed") errors_count: int = Field(description="Total error count") last_error: Optional[str] = Field(None, description="Last error message") last_update: Optional[datetime] = Field(None, description="Last update timestamp") class KCTestRectangleResponse(BaseModel): """A rectangle with its extracted color from a KC test.""" name: str = Field(description="Rectangle name") x: float = Field(description="Left edge (0.0-1.0)") y: float = Field(description="Top edge (0.0-1.0)") width: float = Field(description="Width (0.0-1.0)") height: float = Field(description="Height (0.0-1.0)") color: ExtractedColorResponse = Field(description="Extracted color for this rectangle") class KCTestResponse(BaseModel): """Response from testing a KC target.""" image: str = Field(description="Base64 data URI of the captured frame") rectangles: List[KCTestRectangleResponse] = Field(description="Rectangles with extracted colors") interpolation_mode: str = Field(description="Color extraction mode used") pattern_template_name: str = Field(description="Pattern template name")