Frame interpolation, FPS hot-update, timing metrics, KC brightness fixes
- CSS: add frame interpolation option — blends between consecutive captured frames on idle ticks so LED output runs at full target FPS even when capture rate is lower (e.g. capture 30fps, output 60fps) - WledTargetProcessor: re-read stream.target_fps each loop tick so FPS changes to the CSS source take effect without restarting the target - WledTargetProcessor: restore per-stage timing metrics on target card by pulling extract/map/smooth/total from CSS stream get_last_timing() - TargetProcessingState schema: add missing timing_extract_ms, timing_map_leds_ms, timing_smooth_ms, timing_total_ms fields - KC targets: add extraction FPS badge to target card props row - KC targets: fix 500 error when changing brightness — update_fields now accepts (and ignores) WLED-specific kwargs - KC targets: fix partial key_colors_settings update wiping pattern_template_id — update route merges only explicitly-set fields using model_dump(exclude_unset=True) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -70,6 +70,7 @@ def _css_to_response(source, overlay_active: bool = False) -> ColorStripSourceRe
|
||||
color=getattr(source, "color", None),
|
||||
stops=stops,
|
||||
description=source.description,
|
||||
frame_interpolation=getattr(source, "frame_interpolation", None),
|
||||
overlay_active=overlay_active,
|
||||
created_at=source.created_at,
|
||||
updated_at=source.updated_at,
|
||||
@@ -134,6 +135,7 @@ async def create_color_strip_source(
|
||||
color=data.color,
|
||||
stops=stops,
|
||||
description=data.description,
|
||||
frame_interpolation=data.frame_interpolation,
|
||||
)
|
||||
return _css_to_response(source)
|
||||
|
||||
@@ -190,6 +192,7 @@ async def update_color_strip_source(
|
||||
color=data.color,
|
||||
stops=stops,
|
||||
description=data.description,
|
||||
frame_interpolation=data.frame_interpolation,
|
||||
)
|
||||
|
||||
# Hot-reload running stream (no restart needed for in-place param changes)
|
||||
|
||||
@@ -214,7 +214,28 @@ async def update_target(
|
||||
if not device:
|
||||
raise HTTPException(status_code=422, detail=f"Device {data.device_id} not found")
|
||||
|
||||
kc_settings = _kc_schema_to_settings(data.key_colors_settings) if data.key_colors_settings else None
|
||||
# Build KC settings with partial-update support: only apply fields that were
|
||||
# explicitly provided in the request body, merging with the existing settings.
|
||||
kc_settings = None
|
||||
if data.key_colors_settings is not None:
|
||||
incoming = data.key_colors_settings.model_dump(exclude_unset=True)
|
||||
try:
|
||||
existing_target = target_store.get_target(target_id)
|
||||
except ValueError:
|
||||
existing_target = None
|
||||
|
||||
if isinstance(existing_target, KeyColorsPictureTarget):
|
||||
ex = existing_target.settings
|
||||
merged = KeyColorsSettingsSchema(
|
||||
fps=incoming.get("fps", ex.fps),
|
||||
interpolation_mode=incoming.get("interpolation_mode", ex.interpolation_mode),
|
||||
smoothing=incoming.get("smoothing", ex.smoothing),
|
||||
pattern_template_id=incoming.get("pattern_template_id", ex.pattern_template_id),
|
||||
brightness=incoming.get("brightness", ex.brightness),
|
||||
)
|
||||
kc_settings = _kc_schema_to_settings(merged)
|
||||
else:
|
||||
kc_settings = _kc_schema_to_settings(data.key_colors_settings)
|
||||
|
||||
# Update in store
|
||||
target = target_store.update_target(
|
||||
|
||||
@@ -40,6 +40,7 @@ class ColorStripSourceCreate(BaseModel):
|
||||
# shared
|
||||
led_count: int = Field(default=0, description="Total LED count (0 = auto from calibration / device)", ge=0)
|
||||
description: Optional[str] = Field(None, description="Optional description", max_length=500)
|
||||
frame_interpolation: bool = Field(default=False, description="Blend between consecutive captured frames for smoother output")
|
||||
|
||||
|
||||
class ColorStripSourceUpdate(BaseModel):
|
||||
@@ -62,6 +63,7 @@ class ColorStripSourceUpdate(BaseModel):
|
||||
# shared
|
||||
led_count: Optional[int] = Field(None, description="Total LED count (0 = auto from calibration / device)", ge=0)
|
||||
description: Optional[str] = Field(None, description="Optional description", max_length=500)
|
||||
frame_interpolation: Optional[bool] = Field(None, description="Blend between consecutive captured frames")
|
||||
|
||||
|
||||
class ColorStripSourceResponse(BaseModel):
|
||||
@@ -86,6 +88,7 @@ class ColorStripSourceResponse(BaseModel):
|
||||
# shared
|
||||
led_count: int = Field(0, description="Total LED count (0 = auto from calibration / device)")
|
||||
description: Optional[str] = Field(None, description="Description")
|
||||
frame_interpolation: Optional[bool] = Field(None, description="Blend between consecutive captured frames")
|
||||
overlay_active: bool = Field(False, description="Whether the screen overlay is currently active")
|
||||
created_at: datetime = Field(description="Creation timestamp")
|
||||
updated_at: datetime = Field(description="Last update timestamp")
|
||||
|
||||
@@ -116,6 +116,10 @@ class TargetProcessingState(BaseModel):
|
||||
frames_keepalive: Optional[int] = Field(None, description="Keepalive frames sent during standby")
|
||||
fps_current: Optional[int] = Field(None, description="Frames sent in the last second")
|
||||
timing_send_ms: Optional[float] = Field(None, description="DDP send time (ms)")
|
||||
timing_extract_ms: Optional[float] = Field(None, description="Border pixel extraction time (ms)")
|
||||
timing_map_leds_ms: Optional[float] = Field(None, description="LED color mapping time (ms)")
|
||||
timing_smooth_ms: Optional[float] = Field(None, description="Temporal smoothing time (ms)")
|
||||
timing_total_ms: Optional[float] = Field(None, description="Total processing time per frame (ms)")
|
||||
timing_calc_colors_ms: Optional[float] = Field(None, description="Color calculation time (ms, KC targets)")
|
||||
timing_broadcast_ms: Optional[float] = Field(None, description="WebSocket broadcast time (ms, KC targets)")
|
||||
display_index: Optional[int] = Field(None, description="Current display index")
|
||||
|
||||
Reference in New Issue
Block a user