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 <noreply@anthropic.com>
This commit is contained in:
4
TODO.md
4
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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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,
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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}'"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
@@ -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):
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(() => ({})),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 || [];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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__(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}")
|
||||
@@ -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
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
]
|
||||
|
||||
@@ -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"],
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user