b5a6885126
Introduce abstract LEDClient base class with factory pattern so new LED controller types can plug in alongside WLED. ProcessorManager is now fully type-agnostic — all device-specific logic (health checks, state snapshot/restore, fast send) lives behind the LEDClient interface. - New led_client.py: LEDClient ABC, DeviceHealth, factory functions - WLEDClient inherits LEDClient, encapsulates WLED health checks and state management - device_type field on Device storage model (defaults to "wled") - Rename target_type "wled" → "led" with backward-compat migration - Frontend: "WLED" tab → "LED", device type badge, type selector in add-device modal, device type shown in target device dropdown - All wled_* API fields renamed to device_* for generic naming Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
116 lines
5.8 KiB
Python
116 lines
5.8 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)")
|
|
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")
|