From 353a1c2d85b93a79951fb1aad5f35ad85990fedc Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Mon, 9 Mar 2026 10:55:36 +0300 Subject: [PATCH] Rename picture-targets to output-targets across entire codebase Rename all Python modules, classes, API endpoints, config keys, frontend fetch URLs, and Home Assistant integration URLs from picture-targets to output-targets. Store loads both new and legacy JSON keys for backward compatibility with existing data files. Co-Authored-By: Claude Opus 4.6 --- TODO.md | 4 +- .../wled_screen_controller/config_flow.py | 2 +- .../wled_screen_controller/coordinator.py | 18 +-- .../wled_screen_controller/ws_manager.py | 2 +- server/config/default_config.yaml | 2 +- server/config/test_config.yaml | 2 +- server/src/wled_controller/api/__init__.py | 4 +- .../src/wled_controller/api/dependencies.py | 18 +-- .../api/routes/color_strip_sources.py | 6 +- .../src/wled_controller/api/routes/devices.py | 6 +- .../{picture_targets.py => output_targets.py} | 114 +++++++++--------- .../api/routes/pattern_templates.py | 10 +- .../api/routes/picture_sources.py | 6 +- .../api/routes/scene_presets.py | 10 +- .../src/wled_controller/api/routes/system.py | 2 +- .../api/routes/value_sources.py | 10 +- .../wled_controller/api/schemas/__init__.py | 18 +-- .../{picture_targets.py => output_targets.py} | 22 ++-- .../api/schemas/pattern_templates.py | 2 +- server/src/wled_controller/config.py | 2 +- .../core/processing/target_processor.py | 2 +- .../core/scenes/scene_activator.py | 6 +- server/src/wled_controller/main.py | 14 +-- .../static/js/core/command-palette.js | 4 +- .../static/js/features/dashboard.js | 16 +-- .../static/js/features/kc-targets.js | 16 +-- .../static/js/features/scene-presets.js | 4 +- .../static/js/features/targets.js | 34 +++--- server/src/wled_controller/static/sw.js | 2 +- .../wled_controller/storage/device_store.py | 2 +- ..._target.py => key_colors_output_target.py} | 8 +- .../{picture_target.py => output_target.py} | 16 +-- ...target_store.py => output_target_store.py} | 79 ++++++------ .../storage/pattern_template.py | 2 +- .../storage/pattern_template_store.py | 10 +- ...icture_target.py => wled_output_target.py} | 10 +- server/tests/test_processor_manager.py | 2 +- 37 files changed, 243 insertions(+), 244 deletions(-) rename server/src/wled_controller/api/routes/{picture_targets.py => output_targets.py} (88%) rename server/src/wled_controller/api/schemas/{picture_targets.py => output_targets.py} (95%) rename server/src/wled_controller/storage/{key_colors_picture_target.py => key_colors_output_target.py} (94%) rename server/src/wled_controller/storage/{picture_target.py => output_target.py} (80%) rename server/src/wled_controller/storage/{picture_target_store.py => output_target_store.py} (72%) rename server/src/wled_controller/storage/{wled_picture_target.py => wled_output_target.py} (94%) diff --git a/TODO.md b/TODO.md index 9a0802f..7a23438 100644 --- a/TODO.md +++ b/TODO.md @@ -17,9 +17,7 @@ Priority: `P1` quick win · `P2` moderate · `P3` large effort ## Output Targets -- [ ] `P1` **Rename `picture-targets` to `output-targets`** — Rename API endpoints and internal references for clarity - - Complexity: low — mechanical rename across routes, schemas, store, frontend fetch calls; no logic changes, but many files touched (~20+), needs care with stored JSON migration - - Impact: low-medium — improves API clarity for future integrations (OpenRGB, Art-Net) +- [x] `P1` **Rename `picture-targets` to `output-targets`** — Rename API endpoints and internal references for clarity - [x] `P2` **OpenRGB** — Control PC peripherals (keyboard, mouse, RAM, fans) as ambient targets - [ ] `P2` **Art-Net / sACN (E1.31)** — Stage/theatrical lighting protocols, DMX controllers - Complexity: medium — UDP-based protocols with well-documented specs; similar architecture to DDP client; needs DMX universe/channel mapping UI diff --git a/custom_components/wled_screen_controller/config_flow.py b/custom_components/wled_screen_controller/config_flow.py index 1be5083..7ec453d 100644 --- a/custom_components/wled_screen_controller/config_flow.py +++ b/custom_components/wled_screen_controller/config_flow.py @@ -61,7 +61,7 @@ async def validate_server( headers = {"Authorization": f"Bearer {api_key}"} try: async with session.get( - f"{server_url}/api/v1/picture-targets", + f"{server_url}/api/v1/output-targets", headers=headers, timeout=timeout, ) as resp: diff --git a/custom_components/wled_screen_controller/coordinator.py b/custom_components/wled_screen_controller/coordinator.py index 8d85e11..d7e77a6 100644 --- a/custom_components/wled_screen_controller/coordinator.py +++ b/custom_components/wled_screen_controller/coordinator.py @@ -146,9 +146,9 @@ class WLEDScreenControllerCoordinator(DataUpdateCoordinator): self.server_version = "unknown" async def _fetch_targets(self) -> list[dict[str, Any]]: - """Fetch all picture targets.""" + """Fetch all output targets.""" async with self.session.get( - f"{self.server_url}/api/v1/picture-targets", + f"{self.server_url}/api/v1/output-targets", headers=self._auth_headers, timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT), ) as resp: @@ -159,7 +159,7 @@ class WLEDScreenControllerCoordinator(DataUpdateCoordinator): async def _fetch_target_state(self, target_id: str) -> dict[str, Any]: """Fetch target processing state.""" async with self.session.get( - f"{self.server_url}/api/v1/picture-targets/{target_id}/state", + f"{self.server_url}/api/v1/output-targets/{target_id}/state", headers=self._auth_headers, timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT), ) as resp: @@ -169,7 +169,7 @@ class WLEDScreenControllerCoordinator(DataUpdateCoordinator): async def _fetch_target_metrics(self, target_id: str) -> dict[str, Any]: """Fetch target metrics.""" async with self.session.get( - f"{self.server_url}/api/v1/picture-targets/{target_id}/metrics", + f"{self.server_url}/api/v1/output-targets/{target_id}/metrics", headers=self._auth_headers, timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT), ) as resp: @@ -277,7 +277,7 @@ class WLEDScreenControllerCoordinator(DataUpdateCoordinator): """Set brightness for a Key Colors target (0-255 mapped to 0.0-1.0).""" brightness_float = round(brightness / 255, 4) async with self.session.put( - f"{self.server_url}/api/v1/picture-targets/{target_id}", + f"{self.server_url}/api/v1/output-targets/{target_id}", headers={**self._auth_headers, "Content-Type": "application/json"}, json={"key_colors_settings": {"brightness": brightness_float}}, timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT), @@ -353,9 +353,9 @@ class WLEDScreenControllerCoordinator(DataUpdateCoordinator): await self.async_request_refresh() async def update_target(self, target_id: str, **kwargs: Any) -> None: - """Update a picture target's fields.""" + """Update a output target's fields.""" async with self.session.put( - f"{self.server_url}/api/v1/picture-targets/{target_id}", + f"{self.server_url}/api/v1/output-targets/{target_id}", headers={**self._auth_headers, "Content-Type": "application/json"}, json=kwargs, timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT), @@ -372,7 +372,7 @@ class WLEDScreenControllerCoordinator(DataUpdateCoordinator): async def start_processing(self, target_id: str) -> None: """Start processing for a target.""" async with self.session.post( - f"{self.server_url}/api/v1/picture-targets/{target_id}/start", + f"{self.server_url}/api/v1/output-targets/{target_id}/start", headers=self._auth_headers, timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT), ) as resp: @@ -390,7 +390,7 @@ class WLEDScreenControllerCoordinator(DataUpdateCoordinator): async def stop_processing(self, target_id: str) -> None: """Stop processing for a target.""" async with self.session.post( - f"{self.server_url}/api/v1/picture-targets/{target_id}/stop", + f"{self.server_url}/api/v1/output-targets/{target_id}/stop", headers=self._auth_headers, timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT), ) as resp: diff --git a/custom_components/wled_screen_controller/ws_manager.py b/custom_components/wled_screen_controller/ws_manager.py index dcdd399..15a225e 100644 --- a/custom_components/wled_screen_controller/ws_manager.py +++ b/custom_components/wled_screen_controller/ws_manager.py @@ -40,7 +40,7 @@ class KeyColorsWebSocketManager: ws_base = self._server_url.replace("http://", "ws://").replace( "https://", "wss://" ) - return f"{ws_base}/api/v1/picture-targets/{target_id}/ws?token={self._api_key}" + return f"{ws_base}/api/v1/output-targets/{target_id}/ws?token={self._api_key}" async def start_listening(self, target_id: str) -> None: """Start WebSocket connection for a target.""" diff --git a/server/config/default_config.yaml b/server/config/default_config.yaml index 1b01b40..6364208 100644 --- a/server/config/default_config.yaml +++ b/server/config/default_config.yaml @@ -17,7 +17,7 @@ storage: templates_file: "data/capture_templates.json" postprocessing_templates_file: "data/postprocessing_templates.json" picture_sources_file: "data/picture_sources.json" - picture_targets_file: "data/picture_targets.json" + output_targets_file: "data/output_targets.json" pattern_templates_file: "data/pattern_templates.json" mqtt: diff --git a/server/config/test_config.yaml b/server/config/test_config.yaml index 222606e..34d01e4 100644 --- a/server/config/test_config.yaml +++ b/server/config/test_config.yaml @@ -15,7 +15,7 @@ storage: templates_file: "data/capture_templates.json" postprocessing_templates_file: "data/postprocessing_templates.json" picture_sources_file: "data/picture_sources.json" - picture_targets_file: "data/picture_targets.json" + output_targets_file: "data/output_targets.json" pattern_templates_file: "data/pattern_templates.json" logging: diff --git a/server/src/wled_controller/api/__init__.py b/server/src/wled_controller/api/__init__.py index f3e8c0c..60cdd38 100644 --- a/server/src/wled_controller/api/__init__.py +++ b/server/src/wled_controller/api/__init__.py @@ -8,7 +8,7 @@ from .routes.templates import router as templates_router from .routes.postprocessing import router as postprocessing_router from .routes.picture_sources import router as picture_sources_router from .routes.pattern_templates import router as pattern_templates_router -from .routes.picture_targets import router as picture_targets_router +from .routes.output_targets import router as output_targets_router from .routes.color_strip_sources import router as color_strip_sources_router from .routes.audio import router as audio_router from .routes.audio_sources import router as audio_sources_router @@ -31,7 +31,7 @@ router.include_router(audio_router) router.include_router(audio_sources_router) router.include_router(audio_templates_router) router.include_router(value_sources_router) -router.include_router(picture_targets_router) +router.include_router(output_targets_router) router.include_router(automations_router) router.include_router(scene_presets_router) router.include_router(webhooks_router) diff --git a/server/src/wled_controller/api/dependencies.py b/server/src/wled_controller/api/dependencies.py index 380e4b9..de6f4c9 100644 --- a/server/src/wled_controller/api/dependencies.py +++ b/server/src/wled_controller/api/dependencies.py @@ -6,7 +6,7 @@ from wled_controller.storage.template_store import TemplateStore from wled_controller.storage.postprocessing_template_store import PostprocessingTemplateStore from wled_controller.storage.pattern_template_store import PatternTemplateStore from wled_controller.storage.picture_source_store import PictureSourceStore -from wled_controller.storage.picture_target_store import PictureTargetStore +from wled_controller.storage.output_target_store import OutputTargetStore from wled_controller.storage.color_strip_store import ColorStripStore from wled_controller.storage.audio_source_store import AudioSourceStore from wled_controller.storage.audio_template_store import AudioTemplateStore @@ -25,7 +25,7 @@ _template_store: TemplateStore | None = None _pp_template_store: PostprocessingTemplateStore | None = None _pattern_template_store: PatternTemplateStore | None = None _picture_source_store: PictureSourceStore | None = None -_picture_target_store: PictureTargetStore | None = None +_output_target_store: OutputTargetStore | None = None _color_strip_store: ColorStripStore | None = None _audio_source_store: AudioSourceStore | None = None _audio_template_store: AudioTemplateStore | None = None @@ -73,11 +73,11 @@ def get_picture_source_store() -> PictureSourceStore: return _picture_source_store -def get_picture_target_store() -> PictureTargetStore: - """Get picture target store dependency.""" - if _picture_target_store is None: +def get_output_target_store() -> OutputTargetStore: + """Get output target store dependency.""" + if _output_target_store is None: raise RuntimeError("Picture target store not initialized") - return _picture_target_store + return _output_target_store def get_color_strip_store() -> ColorStripStore: @@ -164,7 +164,7 @@ def init_dependencies( pp_template_store: PostprocessingTemplateStore | None = None, pattern_template_store: PatternTemplateStore | None = None, picture_source_store: PictureSourceStore | None = None, - picture_target_store: PictureTargetStore | None = None, + output_target_store: OutputTargetStore | None = None, color_strip_store: ColorStripStore | None = None, audio_source_store: AudioSourceStore | None = None, audio_template_store: AudioTemplateStore | None = None, @@ -178,7 +178,7 @@ def init_dependencies( ): """Initialize global dependencies.""" global _device_store, _template_store, _processor_manager - global _pp_template_store, _pattern_template_store, _picture_source_store, _picture_target_store + global _pp_template_store, _pattern_template_store, _picture_source_store, _output_target_store global _color_strip_store, _audio_source_store, _audio_template_store global _value_source_store, _automation_store, _scene_preset_store, _automation_engine, _auto_backup_engine global _sync_clock_store, _sync_clock_manager @@ -188,7 +188,7 @@ def init_dependencies( _pp_template_store = pp_template_store _pattern_template_store = pattern_template_store _picture_source_store = picture_source_store - _picture_target_store = picture_target_store + _output_target_store = output_target_store _color_strip_store = color_strip_store _audio_source_store = audio_source_store _audio_template_store = audio_template_store diff --git a/server/src/wled_controller/api/routes/color_strip_sources.py b/server/src/wled_controller/api/routes/color_strip_sources.py index 8fa5f2b..d2ac582 100644 --- a/server/src/wled_controller/api/routes/color_strip_sources.py +++ b/server/src/wled_controller/api/routes/color_strip_sources.py @@ -9,7 +9,7 @@ from wled_controller.api.auth import AuthRequired from wled_controller.api.dependencies import ( get_color_strip_store, get_picture_source_store, - get_picture_target_store, + get_output_target_store, get_processor_manager, ) from wled_controller.api.schemas.color_strip_sources import ( @@ -35,7 +35,7 @@ from wled_controller.storage.color_strip_source import ApiInputColorStripSource, from wled_controller.storage.color_strip_store import ColorStripStore from wled_controller.storage.picture_source import ProcessedPictureSource, ScreenCapturePictureSource from wled_controller.storage.picture_source_store import PictureSourceStore -from wled_controller.storage.picture_target_store import PictureTargetStore +from wled_controller.storage.output_target_store import OutputTargetStore from wled_controller.utils import get_logger from wled_controller.config import get_config @@ -295,7 +295,7 @@ async def delete_color_strip_source( source_id: str, _auth: AuthRequired, store: ColorStripStore = Depends(get_color_strip_store), - target_store: PictureTargetStore = Depends(get_picture_target_store), + target_store: OutputTargetStore = Depends(get_output_target_store), ): """Delete a color strip source. Returns 409 if referenced by any LED target.""" try: diff --git a/server/src/wled_controller/api/routes/devices.py b/server/src/wled_controller/api/routes/devices.py index 755ba74..3865a51 100644 --- a/server/src/wled_controller/api/routes/devices.py +++ b/server/src/wled_controller/api/routes/devices.py @@ -13,7 +13,7 @@ from wled_controller.core.devices.led_client import ( ) from wled_controller.api.dependencies import ( get_device_store, - get_picture_target_store, + get_output_target_store, get_processor_manager, ) from wled_controller.api.schemas.devices import ( @@ -29,7 +29,7 @@ from wled_controller.api.schemas.devices import ( ) from wled_controller.core.processing.processor_manager import ProcessorManager from wled_controller.storage import DeviceStore -from wled_controller.storage.picture_target_store import PictureTargetStore +from wled_controller.storage.output_target_store import OutputTargetStore from wled_controller.utils import get_logger logger = get_logger(__name__) @@ -342,7 +342,7 @@ async def delete_device( device_id: str, _auth: AuthRequired, store: DeviceStore = Depends(get_device_store), - target_store: PictureTargetStore = Depends(get_picture_target_store), + target_store: OutputTargetStore = Depends(get_output_target_store), manager: ProcessorManager = Depends(get_processor_manager), ): """Delete/detach a device. Returns 409 if referenced by a target.""" diff --git a/server/src/wled_controller/api/routes/picture_targets.py b/server/src/wled_controller/api/routes/output_targets.py similarity index 88% rename from server/src/wled_controller/api/routes/picture_targets.py rename to server/src/wled_controller/api/routes/output_targets.py index ac46902..85a436b 100644 --- a/server/src/wled_controller/api/routes/picture_targets.py +++ b/server/src/wled_controller/api/routes/output_targets.py @@ -1,4 +1,4 @@ -"""Picture target routes: CRUD, processing control, settings, state, metrics.""" +"""Output target routes: CRUD, processing control, settings, state, metrics.""" import asyncio import base64 @@ -16,21 +16,21 @@ from wled_controller.api.dependencies import ( get_device_store, get_pattern_template_store, get_picture_source_store, - get_picture_target_store, + get_output_target_store, get_pp_template_store, get_processor_manager, get_template_store, ) -from wled_controller.api.schemas.picture_targets import ( +from wled_controller.api.schemas.output_targets import ( ExtractedColorResponse, KCTestRectangleResponse, KCTestResponse, KeyColorsResponse, KeyColorsSettingsSchema, - PictureTargetCreate, - PictureTargetListResponse, - PictureTargetResponse, - PictureTargetUpdate, + OutputTargetCreate, + OutputTargetListResponse, + OutputTargetResponse, + OutputTargetUpdate, TargetMetricsResponse, TargetProcessingState, ) @@ -51,12 +51,12 @@ from wled_controller.storage.pattern_template_store import PatternTemplateStore from wled_controller.storage.picture_source import ScreenCapturePictureSource, StaticImagePictureSource from wled_controller.storage.picture_source_store import PictureSourceStore from wled_controller.storage.template_store import TemplateStore -from wled_controller.storage.wled_picture_target import WledPictureTarget -from wled_controller.storage.key_colors_picture_target import ( +from wled_controller.storage.wled_output_target import WledOutputTarget +from wled_controller.storage.key_colors_output_target import ( KeyColorsSettings, - KeyColorsPictureTarget, + KeyColorsOutputTarget, ) -from wled_controller.storage.picture_target_store import PictureTargetStore +from wled_controller.storage.output_target_store import OutputTargetStore from wled_controller.utils import get_logger logger = get_logger(__name__) @@ -88,10 +88,10 @@ def _kc_schema_to_settings(schema: KeyColorsSettingsSchema) -> KeyColorsSettings ) -def _target_to_response(target) -> PictureTargetResponse: - """Convert a PictureTarget to PictureTargetResponse.""" - if isinstance(target, WledPictureTarget): - return PictureTargetResponse( +def _target_to_response(target) -> OutputTargetResponse: + """Convert an OutputTarget to OutputTargetResponse.""" + if isinstance(target, WledOutputTarget): + return OutputTargetResponse( id=target.id, name=target.name, target_type=target.target_type, @@ -109,8 +109,8 @@ def _target_to_response(target) -> PictureTargetResponse: created_at=target.created_at, updated_at=target.updated_at, ) - elif isinstance(target, KeyColorsPictureTarget): - return PictureTargetResponse( + elif isinstance(target, KeyColorsOutputTarget): + return OutputTargetResponse( id=target.id, name=target.name, target_type=target.target_type, @@ -122,7 +122,7 @@ def _target_to_response(target) -> PictureTargetResponse: updated_at=target.updated_at, ) else: - return PictureTargetResponse( + return OutputTargetResponse( id=target.id, name=target.name, target_type=target.target_type, @@ -135,15 +135,15 @@ def _target_to_response(target) -> PictureTargetResponse: # ===== CRUD ENDPOINTS ===== -@router.post("/api/v1/picture-targets", response_model=PictureTargetResponse, tags=["Targets"], status_code=201) +@router.post("/api/v1/output-targets", response_model=OutputTargetResponse, tags=["Targets"], status_code=201) async def create_target( - data: PictureTargetCreate, + data: OutputTargetCreate, _auth: AuthRequired, - target_store: PictureTargetStore = Depends(get_picture_target_store), + target_store: OutputTargetStore = Depends(get_output_target_store), device_store: DeviceStore = Depends(get_device_store), manager: ProcessorManager = Depends(get_processor_manager), ): - """Create a new picture target.""" + """Create a new output target.""" try: # Validate device exists if provided if data.device_id: @@ -188,18 +188,18 @@ async def create_target( raise HTTPException(status_code=500, detail=str(e)) -@router.get("/api/v1/picture-targets", response_model=PictureTargetListResponse, tags=["Targets"]) +@router.get("/api/v1/output-targets", response_model=OutputTargetListResponse, tags=["Targets"]) async def list_targets( _auth: AuthRequired, - target_store: PictureTargetStore = Depends(get_picture_target_store), + target_store: OutputTargetStore = Depends(get_output_target_store), ): - """List all picture targets.""" + """List all output targets.""" targets = target_store.get_all_targets() responses = [_target_to_response(t) for t in targets] - return PictureTargetListResponse(targets=responses, count=len(responses)) + return OutputTargetListResponse(targets=responses, count=len(responses)) -@router.get("/api/v1/picture-targets/batch/states", tags=["Processing"]) +@router.get("/api/v1/output-targets/batch/states", tags=["Processing"]) async def batch_target_states( _auth: AuthRequired, manager: ProcessorManager = Depends(get_processor_manager), @@ -208,7 +208,7 @@ async def batch_target_states( return {"states": manager.get_all_target_states()} -@router.get("/api/v1/picture-targets/batch/metrics", tags=["Metrics"]) +@router.get("/api/v1/output-targets/batch/metrics", tags=["Metrics"]) async def batch_target_metrics( _auth: AuthRequired, manager: ProcessorManager = Depends(get_processor_manager), @@ -217,13 +217,13 @@ async def batch_target_metrics( return {"metrics": manager.get_all_target_metrics()} -@router.get("/api/v1/picture-targets/{target_id}", response_model=PictureTargetResponse, tags=["Targets"]) +@router.get("/api/v1/output-targets/{target_id}", response_model=OutputTargetResponse, tags=["Targets"]) async def get_target( target_id: str, _auth: AuthRequired, - target_store: PictureTargetStore = Depends(get_picture_target_store), + target_store: OutputTargetStore = Depends(get_output_target_store), ): - """Get a picture target by ID.""" + """Get a output target by ID.""" try: target = target_store.get_target(target_id) return _target_to_response(target) @@ -231,16 +231,16 @@ async def get_target( raise HTTPException(status_code=404, detail=str(e)) -@router.put("/api/v1/picture-targets/{target_id}", response_model=PictureTargetResponse, tags=["Targets"]) +@router.put("/api/v1/output-targets/{target_id}", response_model=OutputTargetResponse, tags=["Targets"]) async def update_target( target_id: str, - data: PictureTargetUpdate, + data: OutputTargetUpdate, _auth: AuthRequired, - target_store: PictureTargetStore = Depends(get_picture_target_store), + target_store: OutputTargetStore = Depends(get_output_target_store), device_store: DeviceStore = Depends(get_device_store), manager: ProcessorManager = Depends(get_processor_manager), ): - """Update a picture target.""" + """Update a output target.""" try: # Validate device exists if changing if data.device_id is not None and data.device_id: @@ -258,7 +258,7 @@ async def update_target( except ValueError: existing_target = None - if isinstance(existing_target, KeyColorsPictureTarget): + if isinstance(existing_target, KeyColorsOutputTarget): ex = existing_target.settings merged = KeyColorsSettingsSchema( fps=incoming.get("fps", ex.fps), @@ -325,14 +325,14 @@ async def update_target( raise HTTPException(status_code=500, detail=str(e)) -@router.delete("/api/v1/picture-targets/{target_id}", status_code=204, tags=["Targets"]) +@router.delete("/api/v1/output-targets/{target_id}", status_code=204, tags=["Targets"]) async def delete_target( target_id: str, _auth: AuthRequired, - target_store: PictureTargetStore = Depends(get_picture_target_store), + target_store: OutputTargetStore = Depends(get_output_target_store), manager: ProcessorManager = Depends(get_processor_manager), ): - """Delete a picture target. Stops processing first if active.""" + """Delete a output target. Stops processing first if active.""" try: # Stop processing if running try: @@ -360,14 +360,14 @@ async def delete_target( # ===== PROCESSING CONTROL ENDPOINTS ===== -@router.post("/api/v1/picture-targets/{target_id}/start", tags=["Processing"]) +@router.post("/api/v1/output-targets/{target_id}/start", tags=["Processing"]) async def start_processing( target_id: str, _auth: AuthRequired, - target_store: PictureTargetStore = Depends(get_picture_target_store), + target_store: OutputTargetStore = Depends(get_output_target_store), manager: ProcessorManager = Depends(get_processor_manager), ): - """Start processing for a picture target.""" + """Start processing for a output target.""" try: # Verify target exists in store target_store.get_target(target_id) @@ -386,13 +386,13 @@ async def start_processing( raise HTTPException(status_code=500, detail=str(e)) -@router.post("/api/v1/picture-targets/{target_id}/stop", tags=["Processing"]) +@router.post("/api/v1/output-targets/{target_id}/stop", tags=["Processing"]) async def stop_processing( target_id: str, _auth: AuthRequired, manager: ProcessorManager = Depends(get_processor_manager), ): - """Stop processing for a picture target.""" + """Stop processing for a output target.""" try: await manager.stop_processing(target_id) @@ -408,7 +408,7 @@ async def stop_processing( # ===== STATE & METRICS ENDPOINTS ===== -@router.get("/api/v1/picture-targets/{target_id}/state", response_model=TargetProcessingState, tags=["Processing"]) +@router.get("/api/v1/output-targets/{target_id}/state", response_model=TargetProcessingState, tags=["Processing"]) async def get_target_state( target_id: str, _auth: AuthRequired, @@ -426,7 +426,7 @@ async def get_target_state( raise HTTPException(status_code=500, detail=str(e)) -@router.get("/api/v1/picture-targets/{target_id}/metrics", response_model=TargetMetricsResponse, tags=["Metrics"]) +@router.get("/api/v1/output-targets/{target_id}/metrics", response_model=TargetMetricsResponse, tags=["Metrics"]) async def get_target_metrics( target_id: str, _auth: AuthRequired, @@ -446,7 +446,7 @@ async def get_target_metrics( # ===== KEY COLORS ENDPOINTS ===== -@router.get("/api/v1/picture-targets/{target_id}/colors", response_model=KeyColorsResponse, tags=["Key Colors"]) +@router.get("/api/v1/output-targets/{target_id}/colors", response_model=KeyColorsResponse, tags=["Key Colors"]) async def get_target_colors( target_id: str, _auth: AuthRequired, @@ -471,11 +471,11 @@ async def get_target_colors( raise HTTPException(status_code=404, detail=str(e)) -@router.post("/api/v1/picture-targets/{target_id}/test", response_model=KCTestResponse, tags=["Key Colors"]) +@router.post("/api/v1/output-targets/{target_id}/test", response_model=KCTestResponse, tags=["Key Colors"]) async def test_kc_target( target_id: str, _auth: AuthRequired, - target_store: PictureTargetStore = Depends(get_picture_target_store), + target_store: OutputTargetStore = Depends(get_output_target_store), source_store: PictureSourceStore = Depends(get_picture_source_store), template_store: TemplateStore = Depends(get_template_store), pattern_store: PatternTemplateStore = Depends(get_pattern_template_store), @@ -494,7 +494,7 @@ async def test_kc_target( except ValueError as e: raise HTTPException(status_code=404, detail=str(e)) - if not isinstance(target, KeyColorsPictureTarget): + if not isinstance(target, KeyColorsOutputTarget): raise HTTPException(status_code=400, detail="Target is not a key_colors target") settings = target.settings @@ -670,7 +670,7 @@ async def test_kc_target( logger.error(f"Error cleaning up test stream: {e}") -@router.websocket("/api/v1/picture-targets/{target_id}/ws") +@router.websocket("/api/v1/output-targets/{target_id}/ws") async def target_colors_ws( websocket: WebSocket, target_id: str, @@ -710,7 +710,7 @@ async def target_colors_ws( manager.remove_kc_ws_client(target_id, websocket) -@router.websocket("/api/v1/picture-targets/{target_id}/led-preview/ws") +@router.websocket("/api/v1/output-targets/{target_id}/led-preview/ws") async def led_preview_ws( websocket: WebSocket, target_id: str, @@ -788,12 +788,12 @@ async def events_ws( # ===== OVERLAY VISUALIZATION ===== -@router.post("/api/v1/picture-targets/{target_id}/overlay/start", tags=["Visualization"]) +@router.post("/api/v1/output-targets/{target_id}/overlay/start", tags=["Visualization"]) async def start_target_overlay( target_id: str, _auth: AuthRequired, manager: ProcessorManager = Depends(get_processor_manager), - target_store: PictureTargetStore = Depends(get_picture_target_store), + target_store: OutputTargetStore = Depends(get_output_target_store), color_strip_store: ColorStripStore = Depends(get_color_strip_store), picture_source_store: PictureSourceStore = Depends(get_picture_source_store), ): @@ -815,7 +815,7 @@ 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: + if isinstance(target, WledOutputTarget) and target.color_strip_source_id: first_css_id = target.color_strip_source_id if first_css_id: try: @@ -844,7 +844,7 @@ async def start_target_overlay( raise HTTPException(status_code=500, detail=str(e)) -@router.post("/api/v1/picture-targets/{target_id}/overlay/stop", tags=["Visualization"]) +@router.post("/api/v1/output-targets/{target_id}/overlay/stop", tags=["Visualization"]) async def stop_target_overlay( target_id: str, _auth: AuthRequired, @@ -862,7 +862,7 @@ async def stop_target_overlay( raise HTTPException(status_code=500, detail=str(e)) -@router.get("/api/v1/picture-targets/{target_id}/overlay/status", tags=["Visualization"]) +@router.get("/api/v1/output-targets/{target_id}/overlay/status", tags=["Visualization"]) async def get_overlay_status( target_id: str, _auth: AuthRequired, diff --git a/server/src/wled_controller/api/routes/pattern_templates.py b/server/src/wled_controller/api/routes/pattern_templates.py index 9053bd6..a578cf1 100644 --- a/server/src/wled_controller/api/routes/pattern_templates.py +++ b/server/src/wled_controller/api/routes/pattern_templates.py @@ -5,7 +5,7 @@ from fastapi import APIRouter, HTTPException, Depends from wled_controller.api.auth import AuthRequired from wled_controller.api.dependencies import ( get_pattern_template_store, - get_picture_target_store, + get_output_target_store, ) from wled_controller.api.schemas.pattern_templates import ( PatternTemplateCreate, @@ -13,10 +13,10 @@ from wled_controller.api.schemas.pattern_templates import ( PatternTemplateResponse, PatternTemplateUpdate, ) -from wled_controller.api.schemas.picture_targets import KeyColorRectangleSchema -from wled_controller.storage.key_colors_picture_target import KeyColorRectangle +from wled_controller.api.schemas.output_targets import KeyColorRectangleSchema +from wled_controller.storage.key_colors_output_target import KeyColorRectangle from wled_controller.storage.pattern_template_store import PatternTemplateStore -from wled_controller.storage.picture_target_store import PictureTargetStore +from wled_controller.storage.output_target_store import OutputTargetStore from wled_controller.utils import get_logger logger = get_logger(__name__) @@ -127,7 +127,7 @@ async def delete_pattern_template( template_id: str, _auth: AuthRequired, store: PatternTemplateStore = Depends(get_pattern_template_store), - target_store: PictureTargetStore = Depends(get_picture_target_store), + target_store: OutputTargetStore = Depends(get_output_target_store), ): """Delete a pattern template.""" try: diff --git a/server/src/wled_controller/api/routes/picture_sources.py b/server/src/wled_controller/api/routes/picture_sources.py index e3e59a6..8f972e6 100644 --- a/server/src/wled_controller/api/routes/picture_sources.py +++ b/server/src/wled_controller/api/routes/picture_sources.py @@ -13,7 +13,7 @@ from fastapi.responses import Response from wled_controller.api.auth import AuthRequired from wled_controller.api.dependencies import ( get_picture_source_store, - get_picture_target_store, + get_output_target_store, get_pp_template_store, get_template_store, ) @@ -33,7 +33,7 @@ from wled_controller.api.schemas.picture_sources import ( ) from wled_controller.core.capture_engines import EngineRegistry from wled_controller.core.filters import FilterRegistry, ImagePool -from wled_controller.storage.picture_target_store import PictureTargetStore +from wled_controller.storage.output_target_store import OutputTargetStore from wled_controller.storage.template_store import TemplateStore from wled_controller.storage.postprocessing_template_store import PostprocessingTemplateStore from wled_controller.storage.picture_source_store import PictureSourceStore @@ -254,7 +254,7 @@ async def delete_picture_source( stream_id: str, _auth: AuthRequired, store: PictureSourceStore = Depends(get_picture_source_store), - target_store: PictureTargetStore = Depends(get_picture_target_store), + target_store: OutputTargetStore = Depends(get_output_target_store), ): """Delete a picture source.""" try: diff --git a/server/src/wled_controller/api/routes/scene_presets.py b/server/src/wled_controller/api/routes/scene_presets.py index 8e1493d..b168f8b 100644 --- a/server/src/wled_controller/api/routes/scene_presets.py +++ b/server/src/wled_controller/api/routes/scene_presets.py @@ -7,7 +7,7 @@ from fastapi import APIRouter, Depends, HTTPException from wled_controller.api.auth import AuthRequired from wled_controller.api.dependencies import ( - get_picture_target_store, + get_output_target_store, get_processor_manager, get_scene_preset_store, ) @@ -23,7 +23,7 @@ from wled_controller.core.scenes.scene_activator import ( apply_scene_state, capture_current_snapshot, ) -from wled_controller.storage.picture_target_store import PictureTargetStore +from wled_controller.storage.output_target_store import OutputTargetStore from wled_controller.storage.scene_preset import ScenePreset from wled_controller.storage.scene_preset_store import ScenePresetStore from wled_controller.utils import get_logger @@ -62,7 +62,7 @@ async def create_scene_preset( data: ScenePresetCreate, _auth: AuthRequired, store: ScenePresetStore = Depends(get_scene_preset_store), - target_store: PictureTargetStore = Depends(get_picture_target_store), + target_store: OutputTargetStore = Depends(get_output_target_store), manager: ProcessorManager = Depends(get_processor_manager), ): """Capture current state as a new scene preset.""" @@ -176,7 +176,7 @@ async def recapture_scene_preset( preset_id: str, _auth: AuthRequired, store: ScenePresetStore = Depends(get_scene_preset_store), - target_store: PictureTargetStore = Depends(get_picture_target_store), + target_store: OutputTargetStore = Depends(get_output_target_store), manager: ProcessorManager = Depends(get_processor_manager), ): """Re-capture current state into an existing preset (updates snapshot).""" @@ -214,7 +214,7 @@ async def activate_scene_preset( preset_id: str, _auth: AuthRequired, store: ScenePresetStore = Depends(get_scene_preset_store), - target_store: PictureTargetStore = Depends(get_picture_target_store), + target_store: OutputTargetStore = Depends(get_output_target_store), manager: ProcessorManager = Depends(get_processor_manager), ): """Activate a scene preset — restore the captured state.""" diff --git a/server/src/wled_controller/api/routes/system.py b/server/src/wled_controller/api/routes/system.py index 67b511f..5607dc3 100644 --- a/server/src/wled_controller/api/routes/system.py +++ b/server/src/wled_controller/api/routes/system.py @@ -265,7 +265,7 @@ STORE_MAP = { "capture_templates": "templates_file", "postprocessing_templates": "postprocessing_templates_file", "picture_sources": "picture_sources_file", - "picture_targets": "picture_targets_file", + "output_targets": "output_targets_file", "pattern_templates": "pattern_templates_file", "color_strip_sources": "color_strip_sources_file", "audio_sources": "audio_sources_file", diff --git a/server/src/wled_controller/api/routes/value_sources.py b/server/src/wled_controller/api/routes/value_sources.py index 9702a30..097e61f 100644 --- a/server/src/wled_controller/api/routes/value_sources.py +++ b/server/src/wled_controller/api/routes/value_sources.py @@ -8,7 +8,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query, WebSocket, WebSock from wled_controller.api.auth import AuthRequired from wled_controller.api.dependencies import ( - get_picture_target_store, + get_output_target_store, get_processor_manager, get_value_source_store, ) @@ -21,7 +21,7 @@ from wled_controller.api.schemas.value_sources import ( ) 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.storage.output_target_store import OutputTargetStore from wled_controller.core.processing.processor_manager import ProcessorManager from wled_controller.utils import get_logger @@ -157,14 +157,14 @@ 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), + target_store: OutputTargetStore = Depends(get_output_target_store), ): """Delete a value source.""" try: # Check if any targets reference this value source - from wled_controller.storage.wled_picture_target import WledPictureTarget + from wled_controller.storage.wled_output_target import WledOutputTarget for target in target_store.get_all_targets(): - if isinstance(target, WledPictureTarget): + if isinstance(target, WledOutputTarget): if getattr(target, "brightness_value_source_id", "") == source_id: raise ValueError( f"Cannot delete: referenced by target '{target.name}'" diff --git a/server/src/wled_controller/api/schemas/__init__.py b/server/src/wled_controller/api/schemas/__init__.py index f8ce30d..65ac26f 100644 --- a/server/src/wled_controller/api/schemas/__init__.py +++ b/server/src/wled_controller/api/schemas/__init__.py @@ -30,11 +30,11 @@ from .color_strip_sources import ( ColorStripSourceUpdate, CSSCalibrationTestRequest, ) -from .picture_targets import ( - PictureTargetCreate, - PictureTargetListResponse, - PictureTargetResponse, - PictureTargetUpdate, +from .output_targets import ( + OutputTargetCreate, + OutputTargetListResponse, + OutputTargetResponse, + OutputTargetUpdate, TargetMetricsResponse, TargetProcessingState, ) @@ -100,10 +100,10 @@ __all__ = [ "ColorStripSourceResponse", "ColorStripSourceUpdate", "CSSCalibrationTestRequest", - "PictureTargetCreate", - "PictureTargetListResponse", - "PictureTargetResponse", - "PictureTargetUpdate", + "OutputTargetCreate", + "OutputTargetListResponse", + "OutputTargetResponse", + "OutputTargetUpdate", "TargetMetricsResponse", "TargetProcessingState", "EngineInfo", diff --git a/server/src/wled_controller/api/schemas/picture_targets.py b/server/src/wled_controller/api/schemas/output_targets.py similarity index 95% rename from server/src/wled_controller/api/schemas/picture_targets.py rename to server/src/wled_controller/api/schemas/output_targets.py index 0f72456..18ad182 100644 --- a/server/src/wled_controller/api/schemas/picture_targets.py +++ b/server/src/wled_controller/api/schemas/output_targets.py @@ -1,4 +1,4 @@ -"""Picture target schemas (CRUD, processing state, metrics).""" +"""Output target schemas (CRUD, processing state, metrics).""" from datetime import datetime from typing import Dict, Optional, List @@ -46,8 +46,8 @@ class KeyColorsResponse(BaseModel): timestamp: Optional[datetime] = Field(None, description="Extraction timestamp") -class PictureTargetCreate(BaseModel): - """Request to create a picture target.""" +class OutputTargetCreate(BaseModel): + """Request to create an output target.""" name: str = Field(description="Target name", min_length=1, max_length=100) target_type: str = Field(default="led", description="Target type (led, key_colors)") @@ -67,8 +67,8 @@ class PictureTargetCreate(BaseModel): description: Optional[str] = Field(None, description="Optional description", max_length=500) -class PictureTargetUpdate(BaseModel): - """Request to update a picture target.""" +class OutputTargetUpdate(BaseModel): + """Request to update an output target.""" name: Optional[str] = Field(None, description="Target name", min_length=1, max_length=100) # LED target fields @@ -87,8 +87,8 @@ class PictureTargetUpdate(BaseModel): description: Optional[str] = Field(None, description="Optional description", max_length=500) -class PictureTargetResponse(BaseModel): - """Picture target response.""" +class OutputTargetResponse(BaseModel): + """Output target response.""" id: str = Field(description="Target ID") name: str = Field(description="Target name") @@ -111,15 +111,15 @@ class PictureTargetResponse(BaseModel): updated_at: datetime = Field(description="Last update timestamp") -class PictureTargetListResponse(BaseModel): - """List of picture targets response.""" +class OutputTargetListResponse(BaseModel): + """List of output targets response.""" - targets: List[PictureTargetResponse] = Field(description="List of picture targets") + targets: List[OutputTargetResponse] = Field(description="List of output targets") count: int = Field(description="Number of targets") class TargetProcessingState(BaseModel): - """Processing state for a picture target.""" + """Processing state for an output target.""" target_id: str = Field(description="Target ID") device_id: Optional[str] = Field(None, description="Device ID") diff --git a/server/src/wled_controller/api/schemas/pattern_templates.py b/server/src/wled_controller/api/schemas/pattern_templates.py index f9d1d89..cbfafec 100644 --- a/server/src/wled_controller/api/schemas/pattern_templates.py +++ b/server/src/wled_controller/api/schemas/pattern_templates.py @@ -5,7 +5,7 @@ from typing import List, Optional from pydantic import BaseModel, Field -from .picture_targets import KeyColorRectangleSchema +from .output_targets import KeyColorRectangleSchema class PatternTemplateCreate(BaseModel): diff --git a/server/src/wled_controller/config.py b/server/src/wled_controller/config.py index 8afd968..38eed8e 100644 --- a/server/src/wled_controller/config.py +++ b/server/src/wled_controller/config.py @@ -31,7 +31,7 @@ class StorageConfig(BaseSettings): templates_file: str = "data/capture_templates.json" postprocessing_templates_file: str = "data/postprocessing_templates.json" picture_sources_file: str = "data/picture_sources.json" - picture_targets_file: str = "data/picture_targets.json" + output_targets_file: str = "data/output_targets.json" pattern_templates_file: str = "data/pattern_templates.json" color_strip_sources_file: str = "data/color_strip_sources.json" audio_sources_file: str = "data/audio_sources.json" diff --git a/server/src/wled_controller/core/processing/target_processor.py b/server/src/wled_controller/core/processing/target_processor.py index 1e770a1..665eed8 100644 --- a/server/src/wled_controller/core/processing/target_processor.py +++ b/server/src/wled_controller/core/processing/target_processor.py @@ -1,7 +1,7 @@ """Abstract base class for target processors. A TargetProcessor encapsulates the processing loop and state for a single -picture target. Concrete subclasses (WledTargetProcessor, KCTargetProcessor) +output target. Concrete subclasses (WledTargetProcessor, KCTargetProcessor) implement the target-specific capture→process→output pipeline. ProcessorManager creates and owns TargetProcessor instances, delegating diff --git a/server/src/wled_controller/core/scenes/scene_activator.py b/server/src/wled_controller/core/scenes/scene_activator.py index ca0a113..e996748 100644 --- a/server/src/wled_controller/core/scenes/scene_activator.py +++ b/server/src/wled_controller/core/scenes/scene_activator.py @@ -6,7 +6,7 @@ These functions are used by both the scene-presets API route and the automation from typing import List, Optional, Set, Tuple from wled_controller.core.processing.processor_manager import ProcessorManager -from wled_controller.storage.picture_target_store import PictureTargetStore +from wled_controller.storage.output_target_store import OutputTargetStore from wled_controller.storage.scene_preset import ( ScenePreset, TargetSnapshot, @@ -17,7 +17,7 @@ logger = get_logger(__name__) def capture_current_snapshot( - target_store: PictureTargetStore, + target_store: OutputTargetStore, processor_manager: ProcessorManager, target_ids: Optional[Set[str]] = None, ) -> List[TargetSnapshot]: @@ -45,7 +45,7 @@ def capture_current_snapshot( async def apply_scene_state( preset: ScenePreset, - target_store: PictureTargetStore, + target_store: OutputTargetStore, processor_manager: ProcessorManager, ) -> Tuple[str, List[str]]: """Apply a scene preset's state to the system. diff --git a/server/src/wled_controller/main.py b/server/src/wled_controller/main.py index 6031d7f..cbe1f5f 100644 --- a/server/src/wled_controller/main.py +++ b/server/src/wled_controller/main.py @@ -21,7 +21,7 @@ from wled_controller.storage.template_store import TemplateStore from wled_controller.storage.postprocessing_template_store import PostprocessingTemplateStore from wled_controller.storage.pattern_template_store import PatternTemplateStore from wled_controller.storage.picture_source_store import PictureSourceStore -from wled_controller.storage.picture_target_store import PictureTargetStore +from wled_controller.storage.output_target_store import OutputTargetStore from wled_controller.storage.color_strip_store import ColorStripStore from wled_controller.storage.audio_source_store import AudioSourceStore from wled_controller.storage.audio_template_store import AudioTemplateStore @@ -50,7 +50,7 @@ device_store = DeviceStore(config.storage.devices_file) template_store = TemplateStore(config.storage.templates_file) pp_template_store = PostprocessingTemplateStore(config.storage.postprocessing_templates_file) picture_source_store = PictureSourceStore(config.storage.picture_sources_file) -picture_target_store = PictureTargetStore(config.storage.picture_targets_file) +output_target_store = OutputTargetStore(config.storage.output_targets_file) pattern_template_store = PatternTemplateStore(config.storage.pattern_templates_file) color_strip_store = ColorStripStore(config.storage.color_strip_sources_file) audio_source_store = AudioSourceStore(config.storage.audio_sources_file) @@ -113,7 +113,7 @@ async def lifespan(app: FastAPI): automation_store, processor_manager, mqtt_service=mqtt_service, scene_preset_store=scene_preset_store, - target_store=picture_target_store, + target_store=output_target_store, device_store=device_store, ) @@ -131,7 +131,7 @@ async def lifespan(app: FastAPI): pp_template_store=pp_template_store, pattern_template_store=pattern_template_store, picture_source_store=picture_source_store, - picture_target_store=picture_target_store, + output_target_store=output_target_store, color_strip_store=color_strip_store, audio_source_store=audio_source_store, audio_template_store=audio_template_store, @@ -163,8 +163,8 @@ async def lifespan(app: FastAPI): logger.info(f"Registered {len(devices)} devices for health monitoring") - # Register picture targets in processor manager - targets = picture_target_store.get_all_targets() + # Register output targets in processor manager + targets = output_target_store.get_all_targets() registered_targets = 0 for target in targets: try: @@ -174,7 +174,7 @@ async def lifespan(app: FastAPI): except Exception as e: logger.error(f"Failed to register target {target.id}: {e}") - logger.info(f"Registered {registered_targets} picture target(s)") + logger.info(f"Registered {registered_targets} output target(s)") # Start background health monitoring for all devices await processor_manager.start_health_monitoring() diff --git a/server/src/wled_controller/static/js/core/command-palette.js b/server/src/wled_controller/static/js/core/command-palette.js index 353ec11..5d19ced 100644 --- a/server/src/wled_controller/static/js/core/command-palette.js +++ b/server/src/wled_controller/static/js/core/command-palette.js @@ -112,7 +112,7 @@ function _buildItems(results, states = {}) { // Maps endpoint → response key that holds the array const _responseKeys = [ ['/devices', 'devices'], - ['/picture-targets', 'targets'], + ['/output-targets', 'targets'], ['/color-strip-sources', 'sources'], ['/automations', 'automations'], ['/capture-templates', 'templates'], @@ -126,7 +126,7 @@ const _responseKeys = [ async function _fetchAllEntities() { const [statesData, ...results] = await Promise.all([ - fetchWithAuth('/picture-targets/batch/states', { retry: false, timeout: 5000 }) + fetchWithAuth('/output-targets/batch/states', { retry: false, timeout: 5000 }) .then(r => r.ok ? r.json() : {}) .then(data => data.states || {}) .catch(() => ({})), diff --git a/server/src/wled_controller/static/js/features/dashboard.js b/server/src/wled_controller/static/js/features/dashboard.js index 7716288..7e732e7 100644 --- a/server/src/wled_controller/static/js/features/dashboard.js +++ b/server/src/wled_controller/static/js/features/dashboard.js @@ -419,12 +419,12 @@ export async function loadDashboard(forceFullRender = false) { try { // Fire all requests in a single batch to avoid sequential RTTs const [targetsResp, automationsResp, devicesResp, cssResp, batchStatesResp, batchMetricsResp, scenePresets, syncClocksResp] = await Promise.all([ - fetchWithAuth('/picture-targets'), + fetchWithAuth('/output-targets'), fetchWithAuth('/automations').catch(() => null), fetchWithAuth('/devices').catch(() => null), fetchWithAuth('/color-strip-sources').catch(() => null), - fetchWithAuth('/picture-targets/batch/states').catch(() => null), - fetchWithAuth('/picture-targets/batch/metrics').catch(() => null), + fetchWithAuth('/output-targets/batch/states').catch(() => null), + fetchWithAuth('/output-targets/batch/metrics').catch(() => null), loadScenePresets(), fetchWithAuth('/sync-clocks').catch(() => null), ]); @@ -746,7 +746,7 @@ export async function dashboardToggleAutomation(automationId, enable) { export async function dashboardStartTarget(targetId) { try { - const response = await fetchWithAuth(`/picture-targets/${targetId}/start`, { + const response = await fetchWithAuth(`/output-targets/${targetId}/start`, { method: 'POST', }); if (response.ok) { @@ -764,7 +764,7 @@ export async function dashboardStartTarget(targetId) { export async function dashboardStopTarget(targetId) { try { - const response = await fetchWithAuth(`/picture-targets/${targetId}/stop`, { + const response = await fetchWithAuth(`/output-targets/${targetId}/stop`, { method: 'POST', }); if (response.ok) { @@ -783,15 +783,15 @@ export async function dashboardStopTarget(targetId) { export async function dashboardStopAll() { try { const [targetsResp, statesResp] = await Promise.all([ - fetchWithAuth('/picture-targets'), - fetchWithAuth('/picture-targets/batch/states'), + fetchWithAuth('/output-targets'), + fetchWithAuth('/output-targets/batch/states'), ]); const data = await targetsResp.json(); const statesData = statesResp.ok ? await statesResp.json() : { states: {} }; const states = statesData.states || {}; const running = (data.targets || []).filter(t => states[t.id]?.processing); await Promise.all(running.map(t => - fetchWithAuth(`/picture-targets/${t.id}/stop`, { method: 'POST' }).catch(() => {}) + fetchWithAuth(`/output-targets/${t.id}/stop`, { method: 'POST' }).catch(() => {}) )); loadDashboard(); } catch (error) { diff --git a/server/src/wled_controller/static/js/features/kc-targets.js b/server/src/wled_controller/static/js/features/kc-targets.js index b15859d..4c30786 100644 --- a/server/src/wled_controller/static/js/features/kc-targets.js +++ b/server/src/wled_controller/static/js/features/kc-targets.js @@ -300,7 +300,7 @@ export function createKCTargetCard(target, sourceMap, patternTemplateMap, valueS // ===== KEY COLORS TEST ===== export async function fetchKCTest(targetId) { - const response = await fetch(`${API_BASE}/picture-targets/${targetId}/test`, { + const response = await fetch(`${API_BASE}/output-targets/${targetId}/test`, { method: 'POST', headers: getHeaders(), }); @@ -539,7 +539,7 @@ export async function showKCEditor(targetId = null, cloneData = null) { _ensurePatternEntitySelect(patTemplates); if (targetId) { - const resp = await fetch(`${API_BASE}/picture-targets/${targetId}`, { headers: getHeaders() }); + const resp = await fetch(`${API_BASE}/output-targets/${targetId}`, { headers: getHeaders() }); if (!resp.ok) throw new Error('Failed to load target'); const target = await resp.json(); const kcSettings = target.key_colors_settings || {}; @@ -653,13 +653,13 @@ export async function saveKCEditor() { try { let response; if (targetId) { - response = await fetchWithAuth(`/picture-targets/${targetId}`, { + response = await fetchWithAuth(`/output-targets/${targetId}`, { method: 'PUT', body: JSON.stringify(payload), }); } else { payload.target_type = 'key_colors'; - response = await fetchWithAuth('/picture-targets', { + response = await fetchWithAuth('/output-targets', { method: 'POST', body: JSON.stringify(payload), }); @@ -683,7 +683,7 @@ export async function saveKCEditor() { export async function cloneKCTarget(targetId) { try { - const resp = await fetchWithAuth(`/picture-targets/${targetId}`); + const resp = await fetchWithAuth(`/output-targets/${targetId}`); if (!resp.ok) throw new Error('Failed to load target'); const target = await resp.json(); showKCEditor(null, target); @@ -699,7 +699,7 @@ export async function deleteKCTarget(targetId) { try { disconnectKCWebSocket(targetId); - const response = await fetchWithAuth(`/picture-targets/${targetId}`, { + const response = await fetchWithAuth(`/output-targets/${targetId}`, { method: 'DELETE', }); if (response.ok) { @@ -726,7 +726,7 @@ export function updateKCBrightnessLabel(targetId, value) { export async function saveKCBrightness(targetId, value) { const brightness = parseInt(value) / 255; try { - await fetch(`${API_BASE}/picture-targets/${targetId}`, { + await fetch(`${API_BASE}/output-targets/${targetId}`, { method: 'PUT', headers: getHeaders(), body: JSON.stringify({ key_colors_settings: { brightness } }), @@ -747,7 +747,7 @@ export function connectKCWebSocket(targetId) { if (!key) return; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - const wsUrl = `${protocol}//${window.location.host}${API_BASE}/picture-targets/${targetId}/ws?token=${encodeURIComponent(key)}`; + const wsUrl = `${protocol}//${window.location.host}${API_BASE}/output-targets/${targetId}/ws?token=${encodeURIComponent(key)}`; try { const ws = new WebSocket(wsUrl); diff --git a/server/src/wled_controller/static/js/features/scene-presets.js b/server/src/wled_controller/static/js/features/scene-presets.js index 7669ec4..9819fc1 100644 --- a/server/src/wled_controller/static/js/features/scene-presets.js +++ b/server/src/wled_controller/static/js/features/scene-presets.js @@ -128,7 +128,7 @@ export async function openScenePresetCapture() { selectorGroup.style.display = ''; targetList.innerHTML = ''; try { - const resp = await fetchWithAuth('/picture-targets'); + const resp = await fetchWithAuth('/output-targets'); if (resp.ok) { const data = await resp.json(); _allTargets = data.targets || []; @@ -328,7 +328,7 @@ export async function cloneScenePreset(presetId) { selectorGroup.style.display = ''; targetList.innerHTML = ''; try { - const resp = await fetchWithAuth('/picture-targets'); + const resp = await fetchWithAuth('/output-targets'); if (resp.ok) { const data = await resp.json(); _allTargets = data.targets || []; diff --git a/server/src/wled_controller/static/js/features/targets.js b/server/src/wled_controller/static/js/features/targets.js index 3e3c639..76d5e65 100644 --- a/server/src/wled_controller/static/js/features/targets.js +++ b/server/src/wled_controller/static/js/features/targets.js @@ -337,7 +337,7 @@ export async function showTargetEditor(targetId = null, cloneData = null) { if (targetId) { // Editing existing target - const resp = await fetch(`${API_BASE}/picture-targets/${targetId}`, { headers: getHeaders() }); + const resp = await fetch(`${API_BASE}/output-targets/${targetId}`, { headers: getHeaders() }); if (!resp.ok) throw new Error('Failed to load target'); const target = await resp.json(); @@ -478,13 +478,13 @@ export async function saveTargetEditor() { try { let response; if (targetId) { - response = await fetchWithAuth(`/picture-targets/${targetId}`, { + response = await fetchWithAuth(`/output-targets/${targetId}`, { method: 'PUT', body: JSON.stringify(payload), }); } else { payload.target_type = 'led'; - response = await fetchWithAuth('/picture-targets', { + response = await fetchWithAuth('/output-targets', { method: 'POST', body: JSON.stringify(payload), }); @@ -550,7 +550,7 @@ export async function loadTargetsTab() { // use DataCache for picture sources, audio sources, value sources, sync clocks const [devicesResp, targetsResp, cssResp, patResp, psArr, valueSrcArr, asSrcArr] = await Promise.all([ fetchWithAuth('/devices'), - fetchWithAuth('/picture-targets'), + fetchWithAuth('/output-targets'), fetchWithAuth('/color-strip-sources').catch(() => null), fetchWithAuth('/pattern-templates').catch(() => null), streamsCache.fetch().catch(() => []), @@ -591,8 +591,8 @@ export async function loadTargetsTab() { // Fetch all device states, target states, and target metrics in batch const [batchDevStatesResp, batchTgtStatesResp, batchTgtMetricsResp] = await Promise.all([ fetchWithAuth('/devices/batch/states'), - fetchWithAuth('/picture-targets/batch/states'), - fetchWithAuth('/picture-targets/batch/metrics'), + fetchWithAuth('/output-targets/batch/states'), + fetchWithAuth('/output-targets/batch/metrics'), ]); const allDeviceStates = batchDevStatesResp.ok ? (await batchDevStatesResp.json()).states : {}; const allTargetStates = batchTgtStatesResp.ok ? (await batchTgtStatesResp.json()).states : {}; @@ -608,7 +608,7 @@ export async function loadTargetsTab() { let latestColors = null; if (target.target_type === 'key_colors' && state.processing) { try { - const colorsResp = await fetch(`${API_BASE}/picture-targets/${target.id}/colors`, { headers: getHeaders() }); + const colorsResp = await fetch(`${API_BASE}/output-targets/${target.id}/colors`, { headers: getHeaders() }); if (colorsResp.ok) latestColors = await colorsResp.json(); } catch {} } @@ -1046,7 +1046,7 @@ async function _targetAction(action) { export async function startTargetProcessing(targetId) { await _targetAction(async () => { - const response = await fetchWithAuth(`/picture-targets/${targetId}/start`, { + const response = await fetchWithAuth(`/output-targets/${targetId}/start`, { method: 'POST', }); if (response.ok) { @@ -1060,7 +1060,7 @@ export async function startTargetProcessing(targetId) { export async function stopTargetProcessing(targetId) { await _targetAction(async () => { - const response = await fetchWithAuth(`/picture-targets/${targetId}/stop`, { + const response = await fetchWithAuth(`/output-targets/${targetId}/stop`, { method: 'POST', }); if (response.ok) { @@ -1083,8 +1083,8 @@ export async function stopAllKCTargets() { async function _stopAllByType(targetType) { try { const [targetsResp, statesResp] = await Promise.all([ - fetchWithAuth('/picture-targets'), - fetchWithAuth('/picture-targets/batch/states'), + fetchWithAuth('/output-targets'), + fetchWithAuth('/output-targets/batch/states'), ]); const data = await targetsResp.json(); const statesData = statesResp.ok ? await statesResp.json() : { states: {} }; @@ -1096,7 +1096,7 @@ async function _stopAllByType(targetType) { return; } await Promise.all(running.map(t => - fetchWithAuth(`/picture-targets/${t.id}/stop`, { method: 'POST' }).catch(() => {}) + fetchWithAuth(`/output-targets/${t.id}/stop`, { method: 'POST' }).catch(() => {}) )); showToast(t('targets.stop_all.stopped', { count: running.length }), 'success'); loadTargetsTab(); @@ -1108,7 +1108,7 @@ async function _stopAllByType(targetType) { export async function startTargetOverlay(targetId) { await _targetAction(async () => { - const response = await fetchWithAuth(`/picture-targets/${targetId}/overlay/start`, { + const response = await fetchWithAuth(`/output-targets/${targetId}/overlay/start`, { method: 'POST', }); if (response.ok) { @@ -1122,7 +1122,7 @@ export async function startTargetOverlay(targetId) { export async function stopTargetOverlay(targetId) { await _targetAction(async () => { - const response = await fetchWithAuth(`/picture-targets/${targetId}/overlay/stop`, { + const response = await fetchWithAuth(`/output-targets/${targetId}/overlay/stop`, { method: 'POST', }); if (response.ok) { @@ -1136,7 +1136,7 @@ export async function stopTargetOverlay(targetId) { export async function cloneTarget(targetId) { try { - const resp = await fetch(`${API_BASE}/picture-targets/${targetId}`, { headers: getHeaders() }); + const resp = await fetch(`${API_BASE}/output-targets/${targetId}`, { headers: getHeaders() }); if (!resp.ok) throw new Error('Failed to load target'); const target = await resp.json(); showTargetEditor(null, target); @@ -1151,7 +1151,7 @@ export async function deleteTarget(targetId) { if (!confirmed) return; await _targetAction(async () => { - const response = await fetchWithAuth(`/picture-targets/${targetId}`, { + const response = await fetchWithAuth(`/output-targets/${targetId}`, { method: 'DELETE', }); if (response.ok) { @@ -1282,7 +1282,7 @@ function connectLedPreviewWS(targetId) { if (!key) return; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - const wsUrl = `${protocol}//${window.location.host}${API_BASE}/picture-targets/${targetId}/led-preview/ws?token=${encodeURIComponent(key)}`; + const wsUrl = `${protocol}//${window.location.host}${API_BASE}/output-targets/${targetId}/led-preview/ws?token=${encodeURIComponent(key)}`; try { const ws = new WebSocket(wsUrl); diff --git a/server/src/wled_controller/static/sw.js b/server/src/wled_controller/static/sw.js index 2728b8a..179f9db 100644 --- a/server/src/wled_controller/static/sw.js +++ b/server/src/wled_controller/static/sw.js @@ -7,7 +7,7 @@ * - Navigation: network-first with offline fallback */ -const CACHE_NAME = 'ledgrab-v23'; +const CACHE_NAME = 'ledgrab-v24'; // Only pre-cache static assets (no auth required). // Do NOT pre-cache '/' — it requires API key auth and would cache an error page. diff --git a/server/src/wled_controller/storage/device_store.py b/server/src/wled_controller/storage/device_store.py index bcf9c24..e019db7 100644 --- a/server/src/wled_controller/storage/device_store.py +++ b/server/src/wled_controller/storage/device_store.py @@ -16,7 +16,7 @@ class Device: A device holds connection state and output settings. Calibration, processing settings, and picture source assignments - now live on ColorStripSource and WledPictureTarget respectively. + now live on ColorStripSource and WledOutputTarget respectively. """ def __init__( diff --git a/server/src/wled_controller/storage/key_colors_picture_target.py b/server/src/wled_controller/storage/key_colors_output_target.py similarity index 94% rename from server/src/wled_controller/storage/key_colors_picture_target.py rename to server/src/wled_controller/storage/key_colors_output_target.py index d7dd72b..d93a753 100644 --- a/server/src/wled_controller/storage/key_colors_picture_target.py +++ b/server/src/wled_controller/storage/key_colors_output_target.py @@ -1,10 +1,10 @@ -"""Key colors picture target — extracts key colors from image rectangles.""" +"""Key colors output target — extracts key colors from image rectangles.""" from dataclasses import dataclass, field from datetime import datetime from typing import List, Optional -from wled_controller.storage.picture_target import PictureTarget +from wled_controller.storage.output_target import OutputTarget @dataclass @@ -71,7 +71,7 @@ class KeyColorsSettings: @dataclass -class KeyColorsPictureTarget(PictureTarget): +class KeyColorsOutputTarget(OutputTarget): """Key colors extractor target — extracts key colors from image rectangles.""" picture_source_id: str = "" @@ -119,7 +119,7 @@ class KeyColorsPictureTarget(PictureTarget): return d @classmethod - def from_dict(cls, data: dict) -> "KeyColorsPictureTarget": + def from_dict(cls, data: dict) -> "KeyColorsOutputTarget": settings_data = data.get("settings", {}) settings = KeyColorsSettings.from_dict(settings_data) diff --git a/server/src/wled_controller/storage/picture_target.py b/server/src/wled_controller/storage/output_target.py similarity index 80% rename from server/src/wled_controller/storage/picture_target.py rename to server/src/wled_controller/storage/output_target.py index 3d1274b..1f46ec4 100644 --- a/server/src/wled_controller/storage/picture_target.py +++ b/server/src/wled_controller/storage/output_target.py @@ -1,4 +1,4 @@ -"""Picture target base data model.""" +"""Output target base data model.""" from dataclasses import dataclass from datetime import datetime @@ -6,8 +6,8 @@ from typing import Optional @dataclass -class PictureTarget: - """Base class for picture targets.""" +class OutputTarget: + """Base class for output targets.""" id: str name: str @@ -50,13 +50,13 @@ class PictureTarget: } @classmethod - def from_dict(cls, data: dict) -> "PictureTarget": + def from_dict(cls, data: dict) -> "OutputTarget": """Create from dictionary, dispatching to the correct subclass.""" target_type = data.get("target_type", "led") if target_type == "led": - from wled_controller.storage.wled_picture_target import WledPictureTarget - return WledPictureTarget.from_dict(data) + from wled_controller.storage.wled_output_target import WledOutputTarget + return WledOutputTarget.from_dict(data) if target_type == "key_colors": - from wled_controller.storage.key_colors_picture_target import KeyColorsPictureTarget - return KeyColorsPictureTarget.from_dict(data) + from wled_controller.storage.key_colors_output_target import KeyColorsOutputTarget + return KeyColorsOutputTarget.from_dict(data) raise ValueError(f"Unknown target type: {target_type}") diff --git a/server/src/wled_controller/storage/picture_target_store.py b/server/src/wled_controller/storage/output_target_store.py similarity index 72% rename from server/src/wled_controller/storage/picture_target_store.py rename to server/src/wled_controller/storage/output_target_store.py index 0c0cd9d..10ce17f 100644 --- a/server/src/wled_controller/storage/picture_target_store.py +++ b/server/src/wled_controller/storage/output_target_store.py @@ -1,4 +1,4 @@ -"""Picture target storage using JSON files.""" +"""Output target storage using JSON files.""" import json import uuid @@ -6,11 +6,11 @@ from datetime import datetime from pathlib import Path from typing import Dict, List, Optional -from wled_controller.storage.picture_target import PictureTarget -from wled_controller.storage.wled_picture_target import WledPictureTarget -from wled_controller.storage.key_colors_picture_target import ( +from wled_controller.storage.output_target import OutputTarget +from wled_controller.storage.wled_output_target import WledOutputTarget +from wled_controller.storage.key_colors_output_target import ( KeyColorsSettings, - KeyColorsPictureTarget, + KeyColorsOutputTarget, ) from wled_controller.utils import atomic_write_json, get_logger @@ -19,17 +19,17 @@ logger = get_logger(__name__) DEFAULT_STATE_CHECK_INTERVAL = 30 # seconds -class PictureTargetStore: - """Persistent storage for picture targets.""" +class OutputTargetStore: + """Persistent storage for output targets.""" def __init__(self, file_path: str): - """Initialize picture target store. + """Initialize output target store. Args: file_path: Path to targets JSON file """ self.file_path = Path(file_path) - self._targets: Dict[str, PictureTarget] = {} + self._targets: Dict[str, OutputTarget] = {} self._load() def _load(self) -> None: @@ -41,52 +41,53 @@ class PictureTargetStore: with open(self.file_path, "r", encoding="utf-8") as f: data = json.load(f) - targets_data = data.get("picture_targets", {}) + # Support both new "output_targets" and legacy "picture_targets" keys + targets_data = data.get("output_targets") or data.get("picture_targets", {}) loaded = 0 for target_id, target_dict in targets_data.items(): try: - target = PictureTarget.from_dict(target_dict) + target = OutputTarget.from_dict(target_dict) self._targets[target_id] = target loaded += 1 except Exception as e: - logger.error(f"Failed to load picture target {target_id}: {e}", exc_info=True) + logger.error(f"Failed to load output target {target_id}: {e}", exc_info=True) if loaded > 0: - logger.info(f"Loaded {loaded} picture targets from storage") + logger.info(f"Loaded {loaded} output targets from storage") except Exception as e: - logger.error(f"Failed to load picture targets from {self.file_path}: {e}") + logger.error(f"Failed to load output targets from {self.file_path}: {e}") raise - logger.info(f"Picture target store initialized with {len(self._targets)} targets") + logger.info(f"Output target store initialized with {len(self._targets)} targets") def _save(self) -> None: """Save all targets to file.""" try: data = { "version": "1.0.0", - "picture_targets": { + "output_targets": { target_id: target.to_dict() for target_id, target in self._targets.items() }, } atomic_write_json(self.file_path, data) except Exception as e: - logger.error(f"Failed to save picture targets to {self.file_path}: {e}") + logger.error(f"Failed to save output targets to {self.file_path}: {e}") raise - def get_all_targets(self) -> List[PictureTarget]: - """Get all picture targets.""" + def get_all_targets(self) -> List[OutputTarget]: + """Get all output targets.""" return list(self._targets.values()) - def get_target(self, target_id: str) -> PictureTarget: + def get_target(self, target_id: str) -> OutputTarget: """Get target by ID. Raises: ValueError: If target not found """ if target_id not in self._targets: - raise ValueError(f"Picture target not found: {target_id}") + raise ValueError(f"Output target not found: {target_id}") return self._targets[target_id] def create_target( @@ -105,8 +106,8 @@ class PictureTargetStore: key_colors_settings: Optional[KeyColorsSettings] = None, description: Optional[str] = None, picture_source_id: str = "", - ) -> PictureTarget: - """Create a new picture target. + ) -> OutputTarget: + """Create a new output target. Raises: ValueError: If validation fails @@ -117,13 +118,13 @@ class PictureTargetStore: # Check for duplicate name for target in self._targets.values(): if target.name == name: - raise ValueError(f"Picture target with name '{name}' already exists") + raise ValueError(f"Output target with name '{name}' already exists") target_id = f"pt_{uuid.uuid4().hex[:8]}" now = datetime.utcnow() if target_type == "led": - target: PictureTarget = WledPictureTarget( + target: OutputTarget = WledOutputTarget( id=target_id, name=name, target_type="led", @@ -141,7 +142,7 @@ class PictureTargetStore: updated_at=now, ) elif target_type == "key_colors": - target = KeyColorsPictureTarget( + target = KeyColorsOutputTarget( id=target_id, name=name, target_type="key_colors", @@ -157,7 +158,7 @@ class PictureTargetStore: self._targets[target_id] = target self._save() - logger.info(f"Created picture target: {name} ({target_id}, type={target_type})") + logger.info(f"Created output target: {name} ({target_id}, type={target_type})") return target def update_target( @@ -175,14 +176,14 @@ class PictureTargetStore: protocol: Optional[str] = None, key_colors_settings: Optional[KeyColorsSettings] = None, description: Optional[str] = None, - ) -> PictureTarget: - """Update a picture target. + ) -> OutputTarget: + """Update an output target. Raises: ValueError: If target not found or validation fails """ if target_id not in self._targets: - raise ValueError(f"Picture target not found: {target_id}") + raise ValueError(f"Output target not found: {target_id}") target = self._targets[target_id] @@ -190,7 +191,7 @@ class PictureTargetStore: # Check for duplicate name (exclude self) for other in self._targets.values(): if other.id != target_id and other.name == name: - raise ValueError(f"Picture target with name '{name}' already exists") + raise ValueError(f"Output target with name '{name}' already exists") target.update_fields( name=name, @@ -210,42 +211,42 @@ class PictureTargetStore: target.updated_at = datetime.utcnow() self._save() - logger.info(f"Updated picture target: {target_id}") + logger.info(f"Updated output target: {target_id}") return target def delete_target(self, target_id: str) -> None: - """Delete a picture target. + """Delete an output target. Raises: ValueError: If target not found """ if target_id not in self._targets: - raise ValueError(f"Picture target not found: {target_id}") + raise ValueError(f"Output target not found: {target_id}") del self._targets[target_id] self._save() - logger.info(f"Deleted picture target: {target_id}") + logger.info(f"Deleted output target: {target_id}") - def get_targets_for_device(self, device_id: str) -> List[PictureTarget]: + def get_targets_for_device(self, device_id: str) -> List[OutputTarget]: """Get all targets that reference a specific device.""" return [ t for t in self._targets.values() - if isinstance(t, WledPictureTarget) and t.device_id == device_id + if isinstance(t, WledOutputTarget) and t.device_id == device_id ] def get_targets_referencing_source(self, source_id: str) -> List[str]: """Return names of KC targets that reference a picture source.""" return [ target.name for target in self._targets.values() - if isinstance(target, KeyColorsPictureTarget) and target.picture_source_id == source_id + if isinstance(target, KeyColorsOutputTarget) and target.picture_source_id == source_id ] def get_targets_referencing_css(self, css_id: str) -> List[str]: """Return names of LED targets that reference a color strip source.""" return [ target.name for target in self._targets.values() - if isinstance(target, WledPictureTarget) + if isinstance(target, WledOutputTarget) and target.color_strip_source_id == css_id ] diff --git a/server/src/wled_controller/storage/pattern_template.py b/server/src/wled_controller/storage/pattern_template.py index 30543df..08e83a3 100644 --- a/server/src/wled_controller/storage/pattern_template.py +++ b/server/src/wled_controller/storage/pattern_template.py @@ -4,7 +4,7 @@ from dataclasses import dataclass, field from datetime import datetime from typing import List, Optional -from wled_controller.storage.key_colors_picture_target import KeyColorRectangle +from wled_controller.storage.key_colors_output_target import KeyColorRectangle @dataclass diff --git a/server/src/wled_controller/storage/pattern_template_store.py b/server/src/wled_controller/storage/pattern_template_store.py index 5e44232..a80e64e 100644 --- a/server/src/wled_controller/storage/pattern_template_store.py +++ b/server/src/wled_controller/storage/pattern_template_store.py @@ -6,7 +6,7 @@ from datetime import datetime from pathlib import Path from typing import Dict, List, Optional -from wled_controller.storage.key_colors_picture_target import KeyColorRectangle +from wled_controller.storage.key_colors_output_target import KeyColorRectangle from wled_controller.storage.pattern_template import PatternTemplate from wled_controller.utils import atomic_write_json, get_logger @@ -203,11 +203,11 @@ class PatternTemplateStore: logger.info(f"Deleted pattern template: {template_id}") - def get_targets_referencing(self, template_id: str, picture_target_store) -> List[str]: + def get_targets_referencing(self, template_id: str, output_target_store) -> List[str]: """Return names of KC targets that reference this template.""" - from wled_controller.storage.key_colors_picture_target import KeyColorsPictureTarget + from wled_controller.storage.key_colors_output_target import KeyColorsOutputTarget return [ - target.name for target in picture_target_store.get_all_targets() - if isinstance(target, KeyColorsPictureTarget) and target.settings.pattern_template_id == template_id + target.name for target in output_target_store.get_all_targets() + if isinstance(target, KeyColorsOutputTarget) and target.settings.pattern_template_id == template_id ] diff --git a/server/src/wled_controller/storage/wled_picture_target.py b/server/src/wled_controller/storage/wled_output_target.py similarity index 94% rename from server/src/wled_controller/storage/wled_picture_target.py rename to server/src/wled_controller/storage/wled_output_target.py index e38c0bb..e6fce93 100644 --- a/server/src/wled_controller/storage/wled_picture_target.py +++ b/server/src/wled_controller/storage/wled_output_target.py @@ -1,17 +1,17 @@ -"""LED picture target — sends color strip sources to an LED device.""" +"""LED output target — sends color strip sources to an LED device.""" from dataclasses import dataclass from datetime import datetime from typing import Optional -from wled_controller.storage.picture_target import PictureTarget +from wled_controller.storage.output_target import OutputTarget DEFAULT_STATE_CHECK_INTERVAL = 30 # seconds @dataclass -class WledPictureTarget(PictureTarget): - """LED picture target — pairs an LED device with a ColorStripSource.""" +class WledOutputTarget(OutputTarget): + """LED output target — pairs an LED device with a ColorStripSource.""" device_id: str = "" color_strip_source_id: str = "" @@ -104,7 +104,7 @@ class WledPictureTarget(PictureTarget): return d @classmethod - def from_dict(cls, data: dict) -> "WledPictureTarget": + def from_dict(cls, data: dict) -> "WledOutputTarget": """Create from dictionary.""" return cls( id=data["id"], diff --git a/server/tests/test_processor_manager.py b/server/tests/test_processor_manager.py index 31d4076..8118017 100644 --- a/server/tests/test_processor_manager.py +++ b/server/tests/test_processor_manager.py @@ -253,7 +253,7 @@ def test_get_target_metrics(processor_manager): def test_target_type_detection(processor_manager): """Test target type detection via processor instances.""" - from wled_controller.storage.key_colors_picture_target import KeyColorsSettings + from wled_controller.storage.key_colors_output_target import KeyColorsSettings from wled_controller.core.processing.kc_target_processor import KCTargetProcessor from wled_controller.core.processing.wled_target_processor import WledTargetProcessor