Replace single "adaptive" type with adaptive_mode sub-selector by two distinct source types in the dropdown. Removes the adaptive_mode field entirely — the source_type itself carries the mode. Clearer UX with "Adaptive (Time of Day)" and "Adaptive (Scene)" as separate options. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
171 lines
6.0 KiB
Python
171 lines
6.0 KiB
Python
"""Value source routes: CRUD for value sources."""
|
|
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
|
|
from wled_controller.api.auth import AuthRequired
|
|
from wled_controller.api.dependencies import (
|
|
get_picture_target_store,
|
|
get_processor_manager,
|
|
get_value_source_store,
|
|
)
|
|
from wled_controller.api.schemas.value_sources import (
|
|
ValueSourceCreate,
|
|
ValueSourceListResponse,
|
|
ValueSourceResponse,
|
|
ValueSourceUpdate,
|
|
)
|
|
from wled_controller.storage.value_source import ValueSource
|
|
from wled_controller.storage.value_source_store import ValueSourceStore
|
|
from wled_controller.storage.picture_target_store import PictureTargetStore
|
|
from wled_controller.core.processing.processor_manager import ProcessorManager
|
|
from wled_controller.utils import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def _to_response(source: ValueSource) -> ValueSourceResponse:
|
|
"""Convert a ValueSource to a ValueSourceResponse."""
|
|
d = source.to_dict()
|
|
return ValueSourceResponse(
|
|
id=d["id"],
|
|
name=d["name"],
|
|
source_type=d["source_type"],
|
|
value=d.get("value"),
|
|
waveform=d.get("waveform"),
|
|
speed=d.get("speed"),
|
|
min_value=d.get("min_value"),
|
|
max_value=d.get("max_value"),
|
|
audio_source_id=d.get("audio_source_id"),
|
|
mode=d.get("mode"),
|
|
sensitivity=d.get("sensitivity"),
|
|
smoothing=d.get("smoothing"),
|
|
schedule=d.get("schedule"),
|
|
picture_source_id=d.get("picture_source_id"),
|
|
scene_behavior=d.get("scene_behavior"),
|
|
description=d.get("description"),
|
|
created_at=source.created_at,
|
|
updated_at=source.updated_at,
|
|
)
|
|
|
|
|
|
@router.get("/api/v1/value-sources", response_model=ValueSourceListResponse, tags=["Value Sources"])
|
|
async def list_value_sources(
|
|
_auth: AuthRequired,
|
|
source_type: Optional[str] = Query(None, description="Filter by source_type: static, animated, audio, adaptive_time, or adaptive_scene"),
|
|
store: ValueSourceStore = Depends(get_value_source_store),
|
|
):
|
|
"""List all value sources, optionally filtered by type."""
|
|
sources = store.get_all_sources()
|
|
if source_type:
|
|
sources = [s for s in sources if s.source_type == source_type]
|
|
return ValueSourceListResponse(
|
|
sources=[_to_response(s) for s in sources],
|
|
count=len(sources),
|
|
)
|
|
|
|
|
|
@router.post("/api/v1/value-sources", response_model=ValueSourceResponse, status_code=201, tags=["Value Sources"])
|
|
async def create_value_source(
|
|
data: ValueSourceCreate,
|
|
_auth: AuthRequired,
|
|
store: ValueSourceStore = Depends(get_value_source_store),
|
|
):
|
|
"""Create a new value source."""
|
|
try:
|
|
source = store.create_source(
|
|
name=data.name,
|
|
source_type=data.source_type,
|
|
value=data.value,
|
|
waveform=data.waveform,
|
|
speed=data.speed,
|
|
min_value=data.min_value,
|
|
max_value=data.max_value,
|
|
audio_source_id=data.audio_source_id,
|
|
mode=data.mode,
|
|
sensitivity=data.sensitivity,
|
|
smoothing=data.smoothing,
|
|
description=data.description,
|
|
schedule=data.schedule,
|
|
picture_source_id=data.picture_source_id,
|
|
scene_behavior=data.scene_behavior,
|
|
)
|
|
return _to_response(source)
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
@router.get("/api/v1/value-sources/{source_id}", response_model=ValueSourceResponse, tags=["Value Sources"])
|
|
async def get_value_source(
|
|
source_id: str,
|
|
_auth: AuthRequired,
|
|
store: ValueSourceStore = Depends(get_value_source_store),
|
|
):
|
|
"""Get a value source by ID."""
|
|
try:
|
|
source = store.get_source(source_id)
|
|
return _to_response(source)
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=404, detail=str(e))
|
|
|
|
|
|
@router.put("/api/v1/value-sources/{source_id}", response_model=ValueSourceResponse, tags=["Value Sources"])
|
|
async def update_value_source(
|
|
source_id: str,
|
|
data: ValueSourceUpdate,
|
|
_auth: AuthRequired,
|
|
store: ValueSourceStore = Depends(get_value_source_store),
|
|
pm: ProcessorManager = Depends(get_processor_manager),
|
|
):
|
|
"""Update an existing value source."""
|
|
try:
|
|
source = store.update_source(
|
|
source_id=source_id,
|
|
name=data.name,
|
|
value=data.value,
|
|
waveform=data.waveform,
|
|
speed=data.speed,
|
|
min_value=data.min_value,
|
|
max_value=data.max_value,
|
|
audio_source_id=data.audio_source_id,
|
|
mode=data.mode,
|
|
sensitivity=data.sensitivity,
|
|
smoothing=data.smoothing,
|
|
description=data.description,
|
|
schedule=data.schedule,
|
|
picture_source_id=data.picture_source_id,
|
|
scene_behavior=data.scene_behavior,
|
|
)
|
|
# Hot-reload running value streams
|
|
pm.update_value_source(source_id)
|
|
return _to_response(source)
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
@router.delete("/api/v1/value-sources/{source_id}", tags=["Value Sources"])
|
|
async def delete_value_source(
|
|
source_id: str,
|
|
_auth: AuthRequired,
|
|
store: ValueSourceStore = Depends(get_value_source_store),
|
|
target_store: PictureTargetStore = Depends(get_picture_target_store),
|
|
):
|
|
"""Delete a value source."""
|
|
try:
|
|
# Check if any targets reference this value source
|
|
from wled_controller.storage.wled_picture_target import WledPictureTarget
|
|
for target in target_store.get_all_targets():
|
|
if isinstance(target, WledPictureTarget):
|
|
if getattr(target, "brightness_value_source_id", "") == source_id:
|
|
raise ValueError(
|
|
f"Cannot delete: referenced by target '{target.name}'"
|
|
)
|
|
|
|
store.delete_source(source_id)
|
|
return {"status": "deleted", "id": source_id}
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|