Remove target segments, use single color strip source per target

Segments are redundant now that the "mapped" CSS type handles spatial
multiplexing internally. Each target now references one color_strip_source_id
instead of an array of segments with start/end/reverse ranges.

Backward compat: existing targets with old segments format are migrated
on load by extracting the first segment's CSS source ID.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-24 00:00:26 +03:00
parent 9efb08acb6
commit 808037775f
14 changed files with 171 additions and 513 deletions

View File

@@ -1,7 +1,7 @@
"""Picture target schemas (CRUD, processing state, metrics)."""
from datetime import datetime
from typing import Dict, List, Optional
from typing import Dict, Optional, List
from pydantic import BaseModel, Field
@@ -45,15 +45,6 @@ class KeyColorsResponse(BaseModel):
timestamp: Optional[datetime] = Field(None, description="Extraction timestamp")
class TargetSegmentSchema(BaseModel):
"""A segment mapping a color strip source to a pixel range on the device."""
color_strip_source_id: str = Field(default="", description="Color strip source ID")
start: int = Field(default=0, ge=0, description="Start pixel (inclusive)")
end: int = Field(default=0, ge=0, description="End pixel (exclusive, 0 = auto-fit)")
reverse: bool = Field(default=False, description="Reverse pixel order within segment")
class PictureTargetCreate(BaseModel):
"""Request to create a picture target."""
@@ -61,7 +52,7 @@ class PictureTargetCreate(BaseModel):
target_type: str = Field(default="led", description="Target type (led, key_colors)")
# LED target fields
device_id: str = Field(default="", description="LED device ID")
segments: List[TargetSegmentSchema] = Field(default_factory=list, description="LED segments")
color_strip_source_id: str = Field(default="", description="Color strip source ID")
fps: int = Field(default=30, ge=1, le=90, description="Target send FPS (1-90)")
keepalive_interval: float = Field(default=1.0, description="Keepalive send interval when screen is static (0.5-5.0s)", ge=0.5, le=5.0)
state_check_interval: int = Field(default=DEFAULT_STATE_CHECK_INTERVAL, description="Device health check interval (5-600s)", ge=5, le=600)
@@ -77,7 +68,7 @@ class PictureTargetUpdate(BaseModel):
name: Optional[str] = Field(None, description="Target name", min_length=1, max_length=100)
# LED target fields
device_id: Optional[str] = Field(None, description="LED device ID")
segments: Optional[List[TargetSegmentSchema]] = Field(None, description="LED segments")
color_strip_source_id: Optional[str] = Field(None, description="Color strip source ID")
fps: Optional[int] = Field(None, ge=1, le=90, description="Target send FPS (1-90)")
keepalive_interval: Optional[float] = Field(None, description="Keepalive interval (0.5-5.0s)", ge=0.5, le=5.0)
state_check_interval: Optional[int] = Field(None, description="Health check interval (5-600s)", ge=5, le=600)
@@ -95,7 +86,7 @@ class PictureTargetResponse(BaseModel):
target_type: str = Field(description="Target type")
# LED target fields
device_id: str = Field(default="", description="LED device ID")
segments: List[TargetSegmentSchema] = Field(default_factory=list, description="LED segments")
color_strip_source_id: str = Field(default="", description="Color strip source ID")
fps: Optional[int] = Field(None, description="Target send FPS")
keepalive_interval: float = Field(default=1.0, description="Keepalive interval (s)")
state_check_interval: int = Field(default=DEFAULT_STATE_CHECK_INTERVAL, description="Health check interval (s)")
@@ -119,7 +110,7 @@ class TargetProcessingState(BaseModel):
target_id: str = Field(description="Target ID")
device_id: Optional[str] = Field(None, description="Device ID")
segments: List[TargetSegmentSchema] = Field(default_factory=list, description="LED segments")
color_strip_source_id: str = Field(default="", description="Color strip source ID")
processing: bool = Field(description="Whether processing is active")
fps_actual: Optional[float] = Field(None, description="Actual FPS achieved")
fps_potential: Optional[float] = Field(None, description="Potential FPS (processing speed without throttle)")