CSS: add StaticColorStripSource type with auto-sized LED count
Introduces a new 'static' source type that fills all device LEDs with a single constant RGB color — no screen capture or processing required. - StaticColorStripSource storage model (color + led_count=0 auto-size) - StaticColorStripStream: no background thread, configure() sizes to device LED count at processor start; hot-updates preserve runtime size - ColorStripStreamManager dispatches static sources (no LiveStream needed) - WledTargetProcessor calls stream.configure(device_led_count) on start - API schemas/routes: source_type Literal["picture","static"]; color field; overlay/calibration-test endpoints return 400 for static - Frontend: type selector modal, color picker, type-aware card rendering (🎨 icon + color swatch), LED count field hidden for static type - Locale keys: color_strip.type, color_strip.static_color (en + ru) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,7 @@ from wled_controller.core.capture.calibration import (
|
||||
)
|
||||
from wled_controller.core.capture.screen_capture import get_available_displays
|
||||
from wled_controller.core.processing.processor_manager import ProcessorManager
|
||||
from wled_controller.storage.color_strip_source import PictureColorStripSource
|
||||
from wled_controller.storage.color_strip_source import PictureColorStripSource, StaticColorStripSource
|
||||
from wled_controller.storage.color_strip_store import ColorStripStore
|
||||
from wled_controller.storage.picture_source import ProcessedPictureSource, ScreenCapturePictureSource
|
||||
from wled_controller.storage.picture_source_store import PictureSourceStore
|
||||
@@ -57,6 +57,7 @@ def _css_to_response(source, overlay_active: bool = False) -> ColorStripSourceRe
|
||||
interpolation_mode=getattr(source, "interpolation_mode", None),
|
||||
led_count=getattr(source, "led_count", 0),
|
||||
calibration=calibration,
|
||||
color=getattr(source, "color", None),
|
||||
description=source.description,
|
||||
overlay_active=overlay_active,
|
||||
created_at=source.created_at,
|
||||
@@ -117,6 +118,7 @@ async def create_color_strip_source(
|
||||
interpolation_mode=data.interpolation_mode,
|
||||
led_count=data.led_count,
|
||||
calibration=calibration,
|
||||
color=data.color,
|
||||
description=data.description,
|
||||
)
|
||||
return _css_to_response(source)
|
||||
@@ -169,6 +171,7 @@ async def update_color_strip_source(
|
||||
interpolation_mode=data.interpolation_mode,
|
||||
led_count=data.led_count,
|
||||
calibration=calibration,
|
||||
color=data.color,
|
||||
description=data.description,
|
||||
)
|
||||
|
||||
@@ -255,6 +258,11 @@ async def test_css_calibration(
|
||||
if body.edges:
|
||||
try:
|
||||
source = store.get_source(source_id)
|
||||
if isinstance(source, StaticColorStripSource):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Calibration test is not applicable for static color strip sources",
|
||||
)
|
||||
if isinstance(source, PictureColorStripSource) and source.calibration:
|
||||
calibration = source.calibration
|
||||
except ValueError as e:
|
||||
@@ -296,6 +304,8 @@ async def start_css_overlay(
|
||||
"""Start screen overlay visualization for a color strip source."""
|
||||
try:
|
||||
source = store.get_source(source_id)
|
||||
if isinstance(source, StaticColorStripSource):
|
||||
raise HTTPException(status_code=400, detail="Overlay is not supported for static color strip sources")
|
||||
if not isinstance(source, PictureColorStripSource):
|
||||
raise HTTPException(status_code=400, detail="Overlay only supported for picture color strip sources")
|
||||
if not source.calibration:
|
||||
|
||||
@@ -12,7 +12,8 @@ class ColorStripSourceCreate(BaseModel):
|
||||
"""Request to create a color strip source."""
|
||||
|
||||
name: str = Field(description="Source name", min_length=1, max_length=100)
|
||||
source_type: Literal["picture"] = Field(default="picture", description="Source type")
|
||||
source_type: Literal["picture", "static"] = Field(default="picture", description="Source type")
|
||||
# picture-type fields
|
||||
picture_source_id: str = Field(default="", description="Picture source ID (for picture type)")
|
||||
fps: int = Field(default=30, description="Target frames per second", ge=10, le=90)
|
||||
brightness: float = Field(default=1.0, description="Brightness multiplier (0.0-2.0)", ge=0.0, le=2.0)
|
||||
@@ -20,8 +21,11 @@ class ColorStripSourceCreate(BaseModel):
|
||||
gamma: float = Field(default=1.0, description="Gamma correction (1.0=none, <1=brighter, >1=darker mids)", ge=0.1, le=3.0)
|
||||
smoothing: float = Field(default=0.3, description="Temporal smoothing (0.0=none, 1.0=full)", ge=0.0, le=1.0)
|
||||
interpolation_mode: str = Field(default="average", description="LED color interpolation mode (average, median, dominant)")
|
||||
led_count: int = Field(default=0, description="Total LED count (0 = auto from calibration)", ge=0)
|
||||
calibration: Optional[Calibration] = Field(None, description="LED calibration (position and count per edge)")
|
||||
# static-type fields
|
||||
color: Optional[List[int]] = Field(None, description="Static RGB color [R, G, B] (0-255 each, for static type)")
|
||||
# shared
|
||||
led_count: int = Field(default=0, description="Total LED count (0 = auto from calibration / device)", ge=0)
|
||||
description: Optional[str] = Field(None, description="Optional description", max_length=500)
|
||||
|
||||
|
||||
@@ -29,6 +33,7 @@ class ColorStripSourceUpdate(BaseModel):
|
||||
"""Request to update a color strip source."""
|
||||
|
||||
name: Optional[str] = Field(None, description="Source name", min_length=1, max_length=100)
|
||||
# picture-type fields
|
||||
picture_source_id: Optional[str] = Field(None, description="Picture source ID")
|
||||
fps: Optional[int] = Field(None, description="Target FPS", ge=10, le=90)
|
||||
brightness: Optional[float] = Field(None, description="Brightness multiplier (0.0-2.0)", ge=0.0, le=2.0)
|
||||
@@ -36,8 +41,11 @@ class ColorStripSourceUpdate(BaseModel):
|
||||
gamma: Optional[float] = Field(None, description="Gamma correction (0.1-3.0)", ge=0.1, le=3.0)
|
||||
smoothing: Optional[float] = Field(None, description="Temporal smoothing (0.0-1.0)", ge=0.0, le=1.0)
|
||||
interpolation_mode: Optional[str] = Field(None, description="Interpolation mode (average, median, dominant)")
|
||||
led_count: Optional[int] = Field(None, description="Total LED count (0 = auto from calibration)", ge=0)
|
||||
calibration: Optional[Calibration] = Field(None, description="LED calibration")
|
||||
# static-type fields
|
||||
color: Optional[List[int]] = Field(None, description="Static RGB color [R, G, B] (0-255 each, for static type)")
|
||||
# shared
|
||||
led_count: Optional[int] = Field(None, description="Total LED count (0 = auto from calibration / device)", ge=0)
|
||||
description: Optional[str] = Field(None, description="Optional description", max_length=500)
|
||||
|
||||
|
||||
@@ -47,6 +55,7 @@ class ColorStripSourceResponse(BaseModel):
|
||||
id: str = Field(description="Source ID")
|
||||
name: str = Field(description="Source name")
|
||||
source_type: str = Field(description="Source type")
|
||||
# picture-type fields
|
||||
picture_source_id: Optional[str] = Field(None, description="Picture source ID")
|
||||
fps: Optional[int] = Field(None, description="Target FPS")
|
||||
brightness: Optional[float] = Field(None, description="Brightness multiplier")
|
||||
@@ -54,8 +63,11 @@ class ColorStripSourceResponse(BaseModel):
|
||||
gamma: Optional[float] = Field(None, description="Gamma correction")
|
||||
smoothing: Optional[float] = Field(None, description="Temporal smoothing")
|
||||
interpolation_mode: Optional[str] = Field(None, description="Interpolation mode")
|
||||
led_count: int = Field(0, description="Total LED count (0 = auto from calibration)")
|
||||
calibration: Optional[Calibration] = Field(None, description="LED calibration")
|
||||
# static-type fields
|
||||
color: Optional[List[int]] = Field(None, description="Static RGB color [R, G, B]")
|
||||
# shared
|
||||
led_count: int = Field(0, description="Total LED count (0 = auto from calibration / device)")
|
||||
description: Optional[str] = Field(None, description="Description")
|
||||
overlay_active: bool = Field(False, description="Whether the screen overlay is currently active")
|
||||
created_at: datetime = Field(description="Creation timestamp")
|
||||
|
||||
Reference in New Issue
Block a user