Add multi-segment LED targets, replace single color strip source + skip fields
Each target now has a segments list where each segment maps a color strip source to a pixel range (start/end) on the device with optional reverse. This enables composing multiple visualizations on a single LED strip. Old targets auto-migrate from the single source format on load. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,7 @@ from wled_controller.api.schemas.picture_targets import (
|
||||
PictureTargetUpdate,
|
||||
TargetMetricsResponse,
|
||||
TargetProcessingState,
|
||||
TargetSegmentSchema,
|
||||
)
|
||||
from wled_controller.config import get_config
|
||||
from wled_controller.core.capture_engines import EngineRegistry
|
||||
@@ -93,12 +94,18 @@ def _target_to_response(target) -> PictureTargetResponse:
|
||||
name=target.name,
|
||||
target_type=target.target_type,
|
||||
device_id=target.device_id,
|
||||
color_strip_source_id=target.color_strip_source_id,
|
||||
segments=[
|
||||
TargetSegmentSchema(
|
||||
color_strip_source_id=s.color_strip_source_id,
|
||||
start=s.start,
|
||||
end=s.end,
|
||||
reverse=s.reverse,
|
||||
)
|
||||
for s in target.segments
|
||||
],
|
||||
fps=target.fps,
|
||||
keepalive_interval=target.keepalive_interval,
|
||||
state_check_interval=target.state_check_interval,
|
||||
led_skip_start=target.led_skip_start,
|
||||
led_skip_end=target.led_skip_end,
|
||||
description=target.description,
|
||||
created_at=target.created_at,
|
||||
updated_at=target.updated_at,
|
||||
@@ -150,12 +157,10 @@ async def create_target(
|
||||
name=data.name,
|
||||
target_type=data.target_type,
|
||||
device_id=data.device_id,
|
||||
color_strip_source_id=data.color_strip_source_id,
|
||||
segments=[s.model_dump() for s in data.segments] if data.segments else None,
|
||||
fps=data.fps,
|
||||
keepalive_interval=data.keepalive_interval,
|
||||
state_check_interval=data.state_check_interval,
|
||||
led_skip_start=data.led_skip_start,
|
||||
led_skip_end=data.led_skip_end,
|
||||
picture_source_id=data.picture_source_id,
|
||||
key_colors_settings=kc_settings,
|
||||
description=data.description,
|
||||
@@ -262,17 +267,15 @@ async def update_target(
|
||||
kc_settings = _kc_schema_to_settings(data.key_colors_settings)
|
||||
|
||||
# Update in store
|
||||
segments_dicts = [s.model_dump() for s in data.segments] if data.segments is not None else None
|
||||
target = target_store.update_target(
|
||||
target_id=target_id,
|
||||
name=data.name,
|
||||
device_id=data.device_id,
|
||||
color_strip_source_id=data.color_strip_source_id,
|
||||
segments=segments_dicts,
|
||||
fps=data.fps,
|
||||
keepalive_interval=data.keepalive_interval,
|
||||
state_check_interval=data.state_check_interval,
|
||||
led_skip_start=data.led_skip_start,
|
||||
led_skip_end=data.led_skip_end,
|
||||
picture_source_id=data.picture_source_id,
|
||||
key_colors_settings=kc_settings,
|
||||
description=data.description,
|
||||
)
|
||||
@@ -284,10 +287,8 @@ async def update_target(
|
||||
settings_changed=(data.fps is not None or
|
||||
data.keepalive_interval is not None or
|
||||
data.state_check_interval is not None or
|
||||
data.led_skip_start is not None or
|
||||
data.led_skip_end is not None or
|
||||
data.key_colors_settings is not None),
|
||||
source_changed=data.color_strip_source_id is not None,
|
||||
segments_changed=data.segments is not None,
|
||||
device_changed=data.device_id is not None,
|
||||
)
|
||||
except ValueError:
|
||||
@@ -755,20 +756,23 @@ async def start_target_overlay(
|
||||
# can start even when processing is not currently running.
|
||||
calibration = None
|
||||
display_info = None
|
||||
if isinstance(target, WledPictureTarget) and target.color_strip_source_id:
|
||||
try:
|
||||
css = color_strip_store.get_source(target.color_strip_source_id)
|
||||
if isinstance(css, PictureColorStripSource) and css.calibration:
|
||||
calibration = css.calibration
|
||||
# Resolve the display this CSS is capturing
|
||||
from wled_controller.api.routes.color_strip_sources import _resolve_display_index
|
||||
display_index = _resolve_display_index(css.picture_source_id, picture_source_store)
|
||||
displays = get_available_displays()
|
||||
if displays:
|
||||
display_index = min(display_index, len(displays) - 1)
|
||||
display_info = displays[display_index]
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not pre-load CSS calibration for overlay on {target_id}: {e}")
|
||||
if isinstance(target, WledPictureTarget) and target.segments:
|
||||
# Use the first segment's CSS for calibration/overlay
|
||||
first_css_id = target.segments[0].color_strip_source_id
|
||||
if first_css_id:
|
||||
try:
|
||||
css = color_strip_store.get_source(first_css_id)
|
||||
if isinstance(css, PictureColorStripSource) and css.calibration:
|
||||
calibration = css.calibration
|
||||
# Resolve the display this CSS is capturing
|
||||
from wled_controller.api.routes.color_strip_sources import _resolve_display_index
|
||||
display_index = _resolve_display_index(css.picture_source_id, picture_source_store)
|
||||
displays = get_available_displays()
|
||||
if displays:
|
||||
display_index = min(display_index, len(displays) - 1)
|
||||
display_info = displays[display_index]
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not pre-load CSS calibration for overlay on {target_id}: {e}")
|
||||
|
||||
await manager.start_overlay(target_id, target.name, calibration=calibration, display_info=display_info)
|
||||
return {"status": "started", "target_id": target_id}
|
||||
|
||||
Reference in New Issue
Block a user