diff --git a/server/src/ledgrab/api/schemas/color_strip_sources.py b/server/src/ledgrab/api/schemas/color_strip_sources.py index fb312a6..774f1f6 100644 --- a/server/src/ledgrab/api/schemas/color_strip_sources.py +++ b/server/src/ledgrab/api/schemas/color_strip_sources.py @@ -655,10 +655,22 @@ class ColorStripSourceListResponse(BaseModel): class SegmentPayload(BaseModel): - """A single segment for segment-based LED color updates.""" + """A single segment for segment-based LED color updates. - start: int = Field(ge=0, description="Starting LED index") - length: int = Field(ge=1, description="Number of LEDs in segment") + ``start`` and ``length`` are optional: when omitted, the segment defaults + to ``start=0`` and ``length=led_count - start`` (i.e. the rest of the + strip from ``start``). Sending a single segment with only ``mode`` and + ``color`` therefore fills the entire strip. + """ + + start: Optional[int] = Field( + None, ge=0, description="Starting LED index (default 0 = beginning of strip)" + ) + length: Optional[int] = Field( + None, + ge=1, + description="Number of LEDs in segment (default = led_count - start)", + ) mode: Literal["solid", "per_pixel", "gradient"] = Field(description="Fill mode") color: Optional[List[int]] = Field(None, description="RGB for solid mode [R,G,B]") colors: Optional[List[List[int]]] = Field( diff --git a/server/src/ledgrab/core/processing/api_input_stream.py b/server/src/ledgrab/core/processing/api_input_stream.py index b707c23..56014ec 100644 --- a/server/src/ledgrab/core/processing/api_input_stream.py +++ b/server/src/ledgrab/core/processing/api_input_stream.py @@ -148,23 +148,37 @@ class ApiInputColorStripStream(ColorStripStream): """Apply segment-based color updates to the buffer. Each segment defines a range and fill mode. Segments are applied in - order (last wins on overlap). The buffer is auto-grown if needed. + order (last wins on overlap). + + ``start`` and ``length`` are optional: ``start`` defaults to 0, + ``length`` defaults to ``led_count - start`` (i.e. the remainder of + the strip). The buffer is only auto-grown for segments that supply + an explicit ``length`` extending past the current end — implicit + "to the end" segments adapt to whatever the current strip size is. Args: segments: list of dicts with keys: - start (int) – starting LED index - length (int) – number of LEDs in segment - mode (str) – "solid" | "per_pixel" | "gradient" - color (list) – [R,G,B] for solid mode - colors (list) – [[R,G,B], ...] for per_pixel/gradient + start (int, optional) – starting LED index (default 0) + length (int, optional) – number of LEDs in segment + (default = led_count - start) + mode (str) – "solid" | "per_pixel" | "gradient" + color (list) – [R,G,B] for solid mode + colors (list) – [[R,G,B], ...] for per_pixel/gradient """ - # Compute required buffer size from all segments - max_index = max(seg["start"] + seg["length"] for seg in segments) + # Compute required buffer size from segments that supply an explicit + # length. Segments without a length take the strip as-is and so do + # not trigger growth. + explicit_max = 0 + for seg in segments: + seg_start = seg.get("start") or 0 + seg_len = seg.get("length") + if seg_len is not None: + explicit_max = max(explicit_max, seg_start + seg_len) with self._lock: - # Auto-grow buffer if needed - if max_index > self._led_count: - self._ensure_capacity(max_index) + # Auto-grow buffer if any explicit segment extends past current end + if explicit_max > self._led_count: + self._ensure_capacity(explicit_max) # Start from current buffer (or fallback if timed out) if self._timed_out: @@ -173,8 +187,12 @@ class ApiInputColorStripStream(ColorStripStream): buf = self._colors.copy() for seg in segments: - start = seg["start"] - length = seg["length"] + seg_start = seg.get("start") + start = 0 if seg_start is None else seg_start + seg_len = seg.get("length") + length = max(0, self._led_count - start) if seg_len is None else seg_len + if length <= 0: + continue mode = seg["mode"] end = start + length