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:
2026-02-20 17:49:48 +03:00
parent 0a23cb7043
commit 2a8e2daefc
12 changed files with 430 additions and 155 deletions

View File

@@ -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: