Add composable filter templates, skip keepalive for serial devices
Filter Template meta-filter: reference existing PP templates inside others for composable, DRY filter chains. Filters are recursively expanded at pipeline build time with cycle detection. New `select` option type with dynamic choices populated by the API. Keepalive optimization: serial devices (Adalight, AmbiLED) don't need keepalive — they hold last frame indefinitely. Check `standby_required` capability at processor start, skip keepalive sends for serial targets, and hide keepalive metrics in the UI. Rename "Standby Interval" to "Keep Alive Interval" throughout the frontend. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ from wled_controller.api.auth import AuthRequired
|
||||
from wled_controller.api.dependencies import (
|
||||
get_device_store,
|
||||
get_picture_source_store,
|
||||
get_pp_template_store,
|
||||
get_processor_manager,
|
||||
get_template_store,
|
||||
)
|
||||
@@ -385,26 +386,44 @@ async def test_template(
|
||||
# ===== FILTER TYPE ENDPOINTS =====
|
||||
|
||||
@router.get("/api/v1/filters", response_model=FilterTypeListResponse, tags=["Filters"])
|
||||
async def list_filter_types(_auth: AuthRequired):
|
||||
async def list_filter_types(
|
||||
_auth: AuthRequired,
|
||||
pp_store=Depends(get_pp_template_store),
|
||||
):
|
||||
"""List all available postprocessing filter types and their options schemas."""
|
||||
all_filters = FilterRegistry.get_all()
|
||||
|
||||
# Pre-build template choices for the filter_template filter
|
||||
template_choices = None
|
||||
if pp_store:
|
||||
try:
|
||||
templates = pp_store.get_all_templates()
|
||||
template_choices = [{"value": t.id, "label": t.name} for t in templates]
|
||||
except Exception:
|
||||
template_choices = []
|
||||
|
||||
responses = []
|
||||
for filter_id, filter_cls in all_filters.items():
|
||||
schema = filter_cls.get_options_schema()
|
||||
opt_schemas = []
|
||||
for opt in schema:
|
||||
choices = opt.choices
|
||||
# Enrich filter_template choices with current template list
|
||||
if filter_id == "filter_template" and opt.key == "template_id" and template_choices is not None:
|
||||
choices = template_choices
|
||||
opt_schemas.append(FilterOptionDefSchema(
|
||||
key=opt.key,
|
||||
label=opt.label,
|
||||
type=opt.option_type,
|
||||
default=opt.default,
|
||||
min_value=opt.min_value,
|
||||
max_value=opt.max_value,
|
||||
step=opt.step,
|
||||
choices=choices,
|
||||
))
|
||||
responses.append(FilterTypeResponse(
|
||||
filter_id=filter_cls.filter_id,
|
||||
filter_name=filter_cls.filter_name,
|
||||
options_schema=[
|
||||
FilterOptionDefSchema(
|
||||
key=opt.key,
|
||||
label=opt.label,
|
||||
type=opt.option_type,
|
||||
default=opt.default,
|
||||
min_value=opt.min_value,
|
||||
max_value=opt.max_value,
|
||||
step=opt.step,
|
||||
)
|
||||
for opt in schema
|
||||
],
|
||||
options_schema=opt_schemas,
|
||||
))
|
||||
return FilterTypeListResponse(filters=responses, count=len(responses))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Filter-related schemas."""
|
||||
|
||||
from typing import Any, Dict, List
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
@@ -17,11 +17,12 @@ class FilterOptionDefSchema(BaseModel):
|
||||
|
||||
key: str = Field(description="Option key")
|
||||
label: str = Field(description="Display label")
|
||||
type: str = Field(description="Option type (float or int)")
|
||||
type: str = Field(description="Option type (float, int, bool, or select)")
|
||||
default: Any = Field(description="Default value")
|
||||
min_value: Any = Field(description="Minimum value")
|
||||
max_value: Any = Field(description="Maximum value")
|
||||
step: Any = Field(description="Step increment")
|
||||
choices: Optional[List[Dict[str, str]]] = Field(default=None, description="Available choices for select type")
|
||||
|
||||
|
||||
class FilterTypeResponse(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user