- Portal IconSelect popups to document.body with position:fixed to prevent clipping by modal overflow-y:auto - Replace custom scene selectors in automation editor with EntitySelect command-palette pickers (main scene + fallback scene) - Add IconSelect grid for automation deactivation mode (none/revert/fallback) - Add IconSelect grid for automation condition type and match type - Replace mapped zone source dropdowns with EntitySelect pickers - Replace scene target selector with EntityPalette.pick() pattern - Remove effect palette preview bar from CSS editor - Remove sensitivity badge from audio color strip source cards - Clean up unused scene-selector CSS and scene-target-add-row CSS - Add locale keys for all new UI elements across en/ru/zh Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
192 lines
10 KiB
Python
192 lines
10 KiB
Python
"""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 or COM3)")
|
|
device_type: str = Field(default="wled", description="LED device type (e.g., wled, adalight)")
|
|
led_count: Optional[int] = Field(None, ge=1, le=10000, description="Number of LEDs (required for adalight)")
|
|
baud_rate: Optional[int] = Field(None, description="Serial baud rate (for adalight devices)")
|
|
auto_shutdown: Optional[bool] = Field(default=None, description="Turn off device when server stops (defaults to true for adalight)")
|
|
send_latency_ms: Optional[int] = Field(None, ge=0, le=5000, description="Simulated send latency in ms (mock devices)")
|
|
rgbw: Optional[bool] = Field(None, description="RGBW mode (mock devices)")
|
|
zone_mode: Optional[str] = Field(None, description="OpenRGB zone mode: combined or separate")
|
|
|
|
|
|
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="Device URL or serial port")
|
|
enabled: Optional[bool] = Field(None, description="Whether device is enabled")
|
|
led_count: Optional[int] = Field(None, ge=1, le=10000, description="Number of LEDs (for devices with manual_led_count capability)")
|
|
baud_rate: Optional[int] = Field(None, description="Serial baud rate (for adalight devices)")
|
|
auto_shutdown: Optional[bool] = Field(None, description="Turn off device when server stops")
|
|
send_latency_ms: Optional[int] = Field(None, ge=0, le=5000, description="Simulated send latency in ms (mock devices)")
|
|
rgbw: Optional[bool] = Field(None, description="RGBW mode (mock devices)")
|
|
zone_mode: Optional[str] = Field(None, description="OpenRGB zone mode: combined or separate")
|
|
|
|
|
|
class CalibrationLineSchema(BaseModel):
|
|
"""One LED line in advanced calibration."""
|
|
|
|
picture_source_id: str = Field(description="Picture source (monitor) to sample from")
|
|
edge: Literal["top", "right", "bottom", "left"] = Field(description="Screen edge to sample")
|
|
led_count: int = Field(ge=1, description="Number of LEDs in this line")
|
|
span_start: float = Field(default=0.0, ge=0.0, le=1.0, description="Start fraction along edge")
|
|
span_end: float = Field(default=1.0, ge=0.0, le=1.0, description="End fraction along edge")
|
|
reverse: bool = Field(default=False, description="Reverse LED direction")
|
|
border_width: int = Field(default=10, ge=1, le=100, description="Sampling depth in pixels")
|
|
|
|
|
|
class Calibration(BaseModel):
|
|
"""Calibration configuration for pixel-to-LED mapping."""
|
|
|
|
mode: Literal["simple", "advanced"] = Field(
|
|
default="simple",
|
|
description="Calibration mode: simple (4-edge) or advanced (multi-source lines)"
|
|
)
|
|
# Advanced mode: ordered list of lines
|
|
lines: Optional[List[CalibrationLineSchema]] = Field(
|
|
default=None,
|
|
description="Line list for advanced mode (ignored in simple mode)"
|
|
)
|
|
# Simple mode fields
|
|
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")
|
|
baud_rate: Optional[int] = Field(None, description="Serial baud rate")
|
|
auto_shutdown: bool = Field(default=False, description="Restore device to idle state when targets stop")
|
|
send_latency_ms: int = Field(default=0, description="Simulated send latency in ms (mock devices)")
|
|
rgbw: bool = Field(default=False, description="RGBW mode (mock devices)")
|
|
zone_mode: str = Field(default="combined", description="OpenRGB zone mode: combined or separate")
|
|
capabilities: List[str] = Field(default_factory=list, description="Device type capabilities")
|
|
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_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")
|
|
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")
|
|
|
|
|
|
class DiscoveredDeviceResponse(BaseModel):
|
|
"""A single device found via network discovery."""
|
|
|
|
name: str = Field(description="Device name (from mDNS or firmware)")
|
|
url: str = Field(description="Device URL")
|
|
device_type: str = Field(default="wled", description="Device type")
|
|
ip: str = Field(description="IP address")
|
|
mac: str = Field(default="", description="MAC address")
|
|
led_count: Optional[int] = Field(None, description="LED count (if reachable)")
|
|
version: Optional[str] = Field(None, description="Firmware version")
|
|
already_added: bool = Field(default=False, description="Whether this device is already in the system")
|
|
|
|
|
|
class DiscoverDevicesResponse(BaseModel):
|
|
"""Response from device discovery scan."""
|
|
|
|
devices: List[DiscoveredDeviceResponse] = Field(description="Discovered devices")
|
|
count: int = Field(description="Total devices found")
|
|
scan_duration_ms: float = Field(description="How long the scan took in milliseconds")
|
|
|
|
|
|
class OpenRGBZoneResponse(BaseModel):
|
|
"""A single zone on an OpenRGB device."""
|
|
|
|
name: str = Field(description="Zone name (e.g. JRAINBOW2)")
|
|
led_count: int = Field(description="Number of LEDs in this zone")
|
|
zone_type: str = Field(description="Zone type (linear, single, matrix)")
|
|
|
|
|
|
class OpenRGBZonesResponse(BaseModel):
|
|
"""Response from OpenRGB zone listing."""
|
|
|
|
device_name: str = Field(description="OpenRGB device name")
|
|
zones: List[OpenRGBZoneResponse] = Field(description="Available zones")
|