"""Output 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 HALightMappingSchema(BaseModel): """Maps an LED range to one HA light entity.""" entity_id: str = Field(description="HA light entity ID (e.g. 'light.living_room')") led_start: int = Field(default=0, ge=0, description="Start LED index (0-based)") led_end: int = Field(default=-1, description="End LED index (-1 = last)") brightness_scale: float = Field( default=1.0, ge=0.0, le=1.0, description="Brightness multiplier" ) class OutputTargetCreate(BaseModel): """Request to create an output target.""" name: str = Field(description="Target name", min_length=1, max_length=100) target_type: str = Field(default="led", description="Target type (led, ha_light)") # 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)", ) # HA light target fields ha_source_id: str = Field( default="", description="Home Assistant source ID (for ha_light targets)" ) ha_light_mappings: Optional[List[HALightMappingSchema]] = Field( None, description="LED-to-light mappings (for ha_light targets)" ) update_rate: float = Field( default=2.0, ge=0.5, le=5.0, description="Service call rate in Hz (for ha_light targets)" ) transition: float = Field( default=0.5, ge=0.0, le=10.0, description="HA transition seconds (for ha_light targets)" ) color_tolerance: int = Field( default=5, ge=0, le=50, description="Skip service call if RGB delta < this (for ha_light targets)", ) description: Optional[str] = Field(None, description="Optional description", max_length=500) tags: List[str] = Field(default_factory=list, description="User-defined tags") class OutputTargetUpdate(BaseModel): """Request to update an output 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)" ) # HA light target fields ha_source_id: Optional[str] = Field( None, description="Home Assistant source ID (for ha_light targets)" ) ha_light_mappings: Optional[List[HALightMappingSchema]] = Field( None, description="LED-to-light mappings (for ha_light targets)" ) update_rate: Optional[float] = Field( None, ge=0.5, le=5.0, description="Service call rate Hz (for ha_light targets)" ) transition: Optional[float] = Field( None, ge=0.0, le=10.0, description="HA transition seconds (for ha_light targets)" ) color_tolerance: Optional[int] = Field( None, ge=0, le=50, description="RGB delta tolerance (for ha_light targets)" ) description: Optional[str] = Field(None, description="Optional description", max_length=500) tags: Optional[List[str]] = None class OutputTargetResponse(BaseModel): """Output 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)") # HA light target fields ha_source_id: str = Field(default="", description="Home Assistant source ID (ha_light)") ha_light_mappings: Optional[List[HALightMappingSchema]] = Field( None, description="LED-to-light mappings (ha_light)" ) update_rate: Optional[float] = Field(None, description="Service call rate Hz (ha_light)") ha_transition: Optional[float] = Field(None, description="HA transition seconds (ha_light)") color_tolerance: Optional[int] = Field(None, description="RGB delta tolerance (ha_light)") description: Optional[str] = Field(None, description="Description") tags: List[str] = Field(default_factory=list, description="User-defined tags") created_at: datetime = Field(description="Creation timestamp") updated_at: datetime = Field(description="Last update timestamp") class OutputTargetListResponse(BaseModel): """List of output targets response.""" targets: List[OutputTargetResponse] = Field(description="List of output targets") count: int = Field(description="Number of targets") class TargetProcessingState(BaseModel): """Processing state for an output 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)" ) 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 BulkTargetRequest(BaseModel): """Request body for bulk start/stop operations.""" ids: List[str] = Field(description="List of target IDs to operate on") class BulkTargetResponse(BaseModel): """Response for bulk start/stop operations.""" started: List[str] = Field( default_factory=list, description="IDs that were successfully started" ) stopped: List[str] = Field( default_factory=list, description="IDs that were successfully stopped" ) errors: Dict[str, str] = Field( default_factory=dict, description="Map of target ID to error message for failures" )