Rework API input CSS: segments, remove led_count, HAOS light, test preview

API Input CSS rework:
- Remove led_count field from ApiInputColorStripSource (always auto-sizes)
- Add segment-based payload: solid, per_pixel, gradient modes
- Segments applied in order (last wins on overlap), auto-grow buffer
- Backward compatible: legacy {"colors": [...]} still works
- Pydantic validation: mode-specific field requirements

Test preview:
- Enable test preview button on api_input cards
- Hide LED/FPS controls for api_input (sender controls those)
- Show input source selector for all CSS tests (preselected)
- FPS sparkline chart using shared createFpsSparkline (same as target cards)
- Server only sends frames when push_generation changes (no idle frames)

HAOS integration:
- New light.py: ApiInputLight entity per api_input source (RGB + brightness)
- turn_on pushes solid segment, turn_off pushes fallback color
- Register wled_screen_controller.set_leds service for arbitrary segments
- New services.yaml with field definitions
- Coordinator: push_colors() and push_segments() methods
- Platform.LIGHT added to platforms list

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-17 14:47:42 +03:00
parent 823cb90d2d
commit 8a6ffca446
25 changed files with 1085 additions and 326 deletions

View File

@@ -792,16 +792,14 @@ class ApiInputColorStripSource(ColorStripSource):
External clients push [R,G,B] arrays via REST POST or WebSocket. The stream
buffers the latest frame and serves it to targets. When no data has been
received within `timeout` seconds, LEDs revert to `fallback_color`.
LED count auto-sizes from the connected device when led_count == 0.
LED count auto-sizes from the connected device via configure().
"""
led_count: int = 0 # 0 = auto-size from device
fallback_color: list = field(default_factory=lambda: [0, 0, 0]) # [R, G, B]
timeout: float = 5.0 # seconds before reverting to fallback
def to_dict(self) -> dict:
d = super().to_dict()
d["led_count"] = self.led_count
d["fallback_color"] = list(self.fallback_color)
d["timeout"] = self.timeout
return d
@@ -810,14 +808,14 @@ class ApiInputColorStripSource(ColorStripSource):
def create_from_kwargs(cls, *, id: str, name: str, source_type: str,
created_at: datetime, updated_at: datetime,
description=None, clock_id=None, tags=None,
led_count=0, fallback_color=None, timeout=None,
fallback_color=None, timeout=None,
**_kwargs):
fb = _validate_rgb(fallback_color, [0, 0, 0])
return cls(
id=id, name=name, source_type="api_input",
created_at=created_at, updated_at=updated_at,
description=description, clock_id=clock_id, tags=tags or [],
led_count=led_count, fallback_color=fb,
fallback_color=fb,
timeout=float(timeout) if timeout is not None else 5.0,
)
@@ -827,8 +825,6 @@ class ApiInputColorStripSource(ColorStripSource):
self.fallback_color = fallback_color
if kwargs.get("timeout") is not None:
self.timeout = float(kwargs["timeout"])
if kwargs.get("led_count") is not None:
self.led_count = kwargs["led_count"]
@dataclass