"""Device-related schemas (CRUD, calibration, device state).""" from datetime import datetime from typing import Dict, List, Literal, Optional from pydantic import BaseModel, Field class DeviceCreate(BaseModel): """Request to create/attach an LED device.""" name: str = Field(description="Device name", min_length=1, max_length=100) url: str = Field(description="Device URL (e.g., http://192.168.1.100)") device_type: str = Field(default="wled", description="LED device type (e.g., wled)") class DeviceUpdate(BaseModel): """Request to update device information.""" name: Optional[str] = Field(None, description="Device name", min_length=1, max_length=100) url: Optional[str] = Field(None, description="WLED device URL") enabled: Optional[bool] = Field(None, description="Whether device is enabled") class Calibration(BaseModel): """Calibration configuration for pixel-to-LED mapping.""" layout: Literal["clockwise", "counterclockwise"] = Field( default="clockwise", description="LED strip layout direction" ) start_position: Literal["top_left", "top_right", "bottom_left", "bottom_right"] = Field( default="bottom_left", description="Starting corner of the LED strip" ) offset: int = Field( default=0, ge=0, description="Number of LEDs from physical LED 0 to start corner (along strip direction)" ) leds_top: int = Field(default=0, ge=0, description="Number of LEDs on the top edge") leds_right: int = Field(default=0, ge=0, description="Number of LEDs on the right edge") leds_bottom: int = Field(default=0, ge=0, description="Number of LEDs on the bottom edge") leds_left: int = Field(default=0, ge=0, description="Number of LEDs on the left edge") # Per-edge span: fraction of screen side covered by LEDs (0.0-1.0) span_top_start: float = Field(default=0.0, ge=0.0, le=1.0, description="Start of top edge coverage") span_top_end: float = Field(default=1.0, ge=0.0, le=1.0, description="End of top edge coverage") span_right_start: float = Field(default=0.0, ge=0.0, le=1.0, description="Start of right edge coverage") span_right_end: float = Field(default=1.0, ge=0.0, le=1.0, description="End of right edge coverage") span_bottom_start: float = Field(default=0.0, ge=0.0, le=1.0, description="Start of bottom edge coverage") span_bottom_end: float = Field(default=1.0, ge=0.0, le=1.0, description="End of bottom edge coverage") span_left_start: float = Field(default=0.0, ge=0.0, le=1.0, description="Start of left edge coverage") span_left_end: float = Field(default=1.0, ge=0.0, le=1.0, description="End of left edge coverage") # Skip LEDs at start/end of strip skip_leds_start: int = Field(default=0, ge=0, description="LEDs to skip (black out) at the start of the strip") skip_leds_end: int = Field(default=0, ge=0, description="LEDs to skip (black out) at the end of the strip") border_width: int = Field(default=10, ge=1, le=100, description="Border width in pixels for edge sampling") class CalibrationTestModeRequest(BaseModel): """Request to set calibration test mode with multiple edges.""" edges: Dict[str, List[int]] = Field( default_factory=dict, description="Map of active edge names to RGB colors. " "E.g. {'top': [255, 0, 0], 'left': [255, 255, 0]}. " "Empty dict = exit test mode." ) class CalibrationTestModeResponse(BaseModel): """Response for calibration test mode.""" test_mode: bool = Field(description="Whether test mode is active") active_edges: List[str] = Field(default_factory=list, description="Currently lit edges") device_id: str = Field(description="Device ID") class DeviceResponse(BaseModel): """Device information response.""" id: str = Field(description="Device ID") name: str = Field(description="Device name") url: str = Field(description="Device URL") device_type: str = Field(default="wled", description="LED device type") led_count: int = Field(description="Total number of LEDs") enabled: bool = Field(description="Whether device is enabled") calibration: Optional[Calibration] = Field(None, description="Calibration configuration") created_at: datetime = Field(description="Creation timestamp") updated_at: datetime = Field(description="Last update timestamp") class DeviceListResponse(BaseModel): """List of devices response.""" devices: List[DeviceResponse] = Field(description="List of devices") count: int = Field(description="Number of devices") class DeviceStateResponse(BaseModel): """Device health/connection state response.""" device_id: str = Field(description="Device ID") device_type: str = Field(default="wled", description="LED device type") 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_last_checked: Optional[datetime] = Field(None, description="Last health check time") device_error: Optional[str] = Field(None, description="Last health check error") test_mode: bool = Field(default=False, description="Whether calibration test mode is active") test_mode_edges: List[str] = Field(default_factory=list, description="Currently lit edges in test mode")