Some checks failed
Validate / validate (push) Failing after 8s
Allow LEDs to cover only a fraction of each screen edge via draggable span bars in the calibration UI. Per-edge start/end (0.0-1.0) values control which portion of the screen border is sampled for LED colors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
213 lines
9.2 KiB
Python
213 lines
9.2 KiB
Python
"""Pydantic schemas for API request and response models."""
|
||
|
||
from datetime import datetime
|
||
from typing import Dict, List, Literal, Optional
|
||
|
||
from pydantic import BaseModel, Field, HttpUrl
|
||
|
||
from wled_controller.core.processor_manager import DEFAULT_STATE_CHECK_INTERVAL
|
||
|
||
|
||
# Health and Version Schemas
|
||
|
||
class HealthResponse(BaseModel):
|
||
"""Health check response."""
|
||
|
||
status: Literal["healthy", "unhealthy"] = Field(description="Service health status")
|
||
timestamp: datetime = Field(description="Current server time")
|
||
version: str = Field(description="Application version")
|
||
|
||
|
||
class VersionResponse(BaseModel):
|
||
"""Version information response."""
|
||
|
||
version: str = Field(description="Application version")
|
||
python_version: str = Field(description="Python version")
|
||
api_version: str = Field(description="API version")
|
||
|
||
|
||
# Display Schemas
|
||
|
||
class DisplayInfo(BaseModel):
|
||
"""Display/monitor information."""
|
||
|
||
index: int = Field(description="Display index")
|
||
name: str = Field(description="Display name")
|
||
width: int = Field(description="Display width in pixels")
|
||
height: int = Field(description="Display height in pixels")
|
||
x: int = Field(description="Display X position")
|
||
y: int = Field(description="Display Y position")
|
||
is_primary: bool = Field(default=False, description="Whether this is the primary display")
|
||
refresh_rate: int = Field(description="Display refresh rate in Hz")
|
||
|
||
|
||
class DisplayListResponse(BaseModel):
|
||
"""List of available displays."""
|
||
|
||
displays: List[DisplayInfo] = Field(description="Available displays")
|
||
count: int = Field(description="Number of displays")
|
||
|
||
|
||
# Device Schemas
|
||
|
||
class DeviceCreate(BaseModel):
|
||
"""Request to create/attach a WLED device."""
|
||
|
||
name: str = Field(description="Device name", min_length=1, max_length=100)
|
||
url: str = Field(description="WLED device URL (e.g., http://192.168.1.100)")
|
||
|
||
|
||
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 ColorCorrection(BaseModel):
|
||
"""Color correction settings."""
|
||
|
||
gamma: float = Field(default=2.2, description="Gamma correction", ge=0.1, le=5.0)
|
||
saturation: float = Field(default=1.0, description="Saturation multiplier", ge=0.0, le=2.0)
|
||
brightness: float = Field(default=1.0, description="Brightness multiplier", ge=0.0, le=1.0)
|
||
|
||
|
||
class ProcessingSettings(BaseModel):
|
||
"""Processing settings for a device."""
|
||
|
||
display_index: int = Field(default=0, description="Display to capture", ge=0)
|
||
fps: int = Field(default=30, description="Target frames per second", ge=1, le=60)
|
||
border_width: int = Field(default=10, description="Border width in pixels", ge=1, le=100)
|
||
brightness: float = Field(default=1.0, description="Global brightness (0.0-1.0)", ge=0.0, le=1.0)
|
||
state_check_interval: int = Field(
|
||
default=DEFAULT_STATE_CHECK_INTERVAL, ge=5, le=600,
|
||
description="Seconds between WLED health checks"
|
||
)
|
||
color_correction: Optional[ColorCorrection] = Field(
|
||
default_factory=ColorCorrection,
|
||
description="Color correction settings"
|
||
)
|
||
|
||
|
||
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")
|
||
|
||
|
||
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="WLED device URL")
|
||
led_count: int = Field(description="Total number of LEDs")
|
||
enabled: bool = Field(description="Whether device is enabled")
|
||
status: Literal["connected", "disconnected", "error"] = Field(
|
||
description="Connection status"
|
||
)
|
||
settings: ProcessingSettings = Field(description="Processing settings")
|
||
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")
|
||
|
||
|
||
# Processing State Schemas
|
||
|
||
class ProcessingState(BaseModel):
|
||
"""Processing state for a device."""
|
||
|
||
device_id: str = Field(description="Device ID")
|
||
processing: bool = Field(description="Whether processing is active")
|
||
fps_actual: Optional[float] = Field(None, description="Actual FPS achieved")
|
||
fps_target: int = Field(description="Target FPS")
|
||
display_index: int = Field(description="Current display index")
|
||
last_update: Optional[datetime] = Field(None, description="Last successful update")
|
||
errors: List[str] = Field(default_factory=list, description="Recent errors")
|
||
wled_online: bool = Field(default=False, description="Whether WLED device is reachable")
|
||
wled_latency_ms: Optional[float] = Field(None, description="WLED health check latency in ms")
|
||
wled_name: Optional[str] = Field(None, description="WLED device name")
|
||
wled_version: Optional[str] = Field(None, description="WLED firmware version")
|
||
wled_led_count: Optional[int] = Field(None, description="LED count reported by WLED device")
|
||
wled_rgbw: Optional[bool] = Field(None, description="Whether WLED device uses RGBW LEDs")
|
||
wled_led_type: Optional[str] = Field(None, description="LED chip type (e.g. WS2812B, SK6812 RGBW)")
|
||
wled_last_checked: Optional[datetime] = Field(None, description="Last health check time")
|
||
wled_error: Optional[str] = Field(None, description="Last health check error")
|
||
|
||
|
||
class MetricsResponse(BaseModel):
|
||
"""Device metrics response."""
|
||
|
||
device_id: str = Field(description="Device ID")
|
||
processing: bool = Field(description="Whether processing is active")
|
||
fps_actual: Optional[float] = Field(None, description="Actual FPS")
|
||
fps_target: int = Field(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")
|
||
|
||
|
||
# Error Schemas
|
||
|
||
class ErrorResponse(BaseModel):
|
||
"""Error response."""
|
||
|
||
error: str = Field(description="Error type")
|
||
message: str = Field(description="Error message")
|
||
detail: Optional[Dict] = Field(None, description="Additional error details")
|
||
timestamp: datetime = Field(default_factory=datetime.utcnow, description="Error timestamp")
|