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
|
## Output Targets
|
||||||
|
|
||||||
- [ ] `P1` **Rename `picture-targets` to `output-targets`** — Rename API endpoints and internal references for clarity
|
- [x] `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] `P2` **OpenRGB** — Control PC peripherals (keyboard, mouse, RAM, fans) as ambient targets
|
- [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
|
- [ ] `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
|
- 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}"}
|
headers = {"Authorization": f"Bearer {api_key}"}
|
||||||
try:
|
try:
|
||||||
async with session.get(
|
async with session.get(
|
||||||
f"{server_url}/api/v1/picture-targets",
|
f"{server_url}/api/v1/output-targets",
|
||||||
headers=headers,
|
headers=headers,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
) as resp:
|
) as resp:
|
||||||
|
|||||||
@@ -146,9 +146,9 @@ class WLEDScreenControllerCoordinator(DataUpdateCoordinator):
|
|||||||
self.server_version = "unknown"
|
self.server_version = "unknown"
|
||||||
|
|
||||||
async def _fetch_targets(self) -> list[dict[str, Any]]:
|
async def _fetch_targets(self) -> list[dict[str, Any]]:
|
||||||
"""Fetch all picture targets."""
|
"""Fetch all output targets."""
|
||||||
async with self.session.get(
|
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,
|
headers=self._auth_headers,
|
||||||
timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT),
|
timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT),
|
||||||
) as resp:
|
) as resp:
|
||||||
@@ -159,7 +159,7 @@ class WLEDScreenControllerCoordinator(DataUpdateCoordinator):
|
|||||||
async def _fetch_target_state(self, target_id: str) -> dict[str, Any]:
|
async def _fetch_target_state(self, target_id: str) -> dict[str, Any]:
|
||||||
"""Fetch target processing state."""
|
"""Fetch target processing state."""
|
||||||
async with self.session.get(
|
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,
|
headers=self._auth_headers,
|
||||||
timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT),
|
timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT),
|
||||||
) as resp:
|
) as resp:
|
||||||
@@ -169,7 +169,7 @@ class WLEDScreenControllerCoordinator(DataUpdateCoordinator):
|
|||||||
async def _fetch_target_metrics(self, target_id: str) -> dict[str, Any]:
|
async def _fetch_target_metrics(self, target_id: str) -> dict[str, Any]:
|
||||||
"""Fetch target metrics."""
|
"""Fetch target metrics."""
|
||||||
async with self.session.get(
|
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,
|
headers=self._auth_headers,
|
||||||
timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT),
|
timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT),
|
||||||
) as resp:
|
) as resp:
|
||||||
@@ -277,7 +277,7 @@ class WLEDScreenControllerCoordinator(DataUpdateCoordinator):
|
|||||||
"""Set brightness for a Key Colors target (0-255 mapped to 0.0-1.0)."""
|
"""Set brightness for a Key Colors target (0-255 mapped to 0.0-1.0)."""
|
||||||
brightness_float = round(brightness / 255, 4)
|
brightness_float = round(brightness / 255, 4)
|
||||||
async with self.session.put(
|
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"},
|
headers={**self._auth_headers, "Content-Type": "application/json"},
|
||||||
json={"key_colors_settings": {"brightness": brightness_float}},
|
json={"key_colors_settings": {"brightness": brightness_float}},
|
||||||
timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT),
|
timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT),
|
||||||
@@ -353,9 +353,9 @@ class WLEDScreenControllerCoordinator(DataUpdateCoordinator):
|
|||||||
await self.async_request_refresh()
|
await self.async_request_refresh()
|
||||||
|
|
||||||
async def update_target(self, target_id: str, **kwargs: Any) -> None:
|
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(
|
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"},
|
headers={**self._auth_headers, "Content-Type": "application/json"},
|
||||||
json=kwargs,
|
json=kwargs,
|
||||||
timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT),
|
timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT),
|
||||||
@@ -372,7 +372,7 @@ class WLEDScreenControllerCoordinator(DataUpdateCoordinator):
|
|||||||
async def start_processing(self, target_id: str) -> None:
|
async def start_processing(self, target_id: str) -> None:
|
||||||
"""Start processing for a target."""
|
"""Start processing for a target."""
|
||||||
async with self.session.post(
|
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,
|
headers=self._auth_headers,
|
||||||
timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT),
|
timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT),
|
||||||
) as resp:
|
) as resp:
|
||||||
@@ -390,7 +390,7 @@ class WLEDScreenControllerCoordinator(DataUpdateCoordinator):
|
|||||||
async def stop_processing(self, target_id: str) -> None:
|
async def stop_processing(self, target_id: str) -> None:
|
||||||
"""Stop processing for a target."""
|
"""Stop processing for a target."""
|
||||||
async with self.session.post(
|
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,
|
headers=self._auth_headers,
|
||||||
timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT),
|
timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT),
|
||||||
) as resp:
|
) as resp:
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class KeyColorsWebSocketManager:
|
|||||||
ws_base = self._server_url.replace("http://", "ws://").replace(
|
ws_base = self._server_url.replace("http://", "ws://").replace(
|
||||||
"https://", "wss://"
|
"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:
|
async def start_listening(self, target_id: str) -> None:
|
||||||
"""Start WebSocket connection for a target."""
|
"""Start WebSocket connection for a target."""
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ storage:
|
|||||||
templates_file: "data/capture_templates.json"
|
templates_file: "data/capture_templates.json"
|
||||||
postprocessing_templates_file: "data/postprocessing_templates.json"
|
postprocessing_templates_file: "data/postprocessing_templates.json"
|
||||||
picture_sources_file: "data/picture_sources.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"
|
pattern_templates_file: "data/pattern_templates.json"
|
||||||
|
|
||||||
mqtt:
|
mqtt:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ storage:
|
|||||||
templates_file: "data/capture_templates.json"
|
templates_file: "data/capture_templates.json"
|
||||||
postprocessing_templates_file: "data/postprocessing_templates.json"
|
postprocessing_templates_file: "data/postprocessing_templates.json"
|
||||||
picture_sources_file: "data/picture_sources.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"
|
pattern_templates_file: "data/pattern_templates.json"
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from .routes.templates import router as templates_router
|
|||||||
from .routes.postprocessing import router as postprocessing_router
|
from .routes.postprocessing import router as postprocessing_router
|
||||||
from .routes.picture_sources import router as picture_sources_router
|
from .routes.picture_sources import router as picture_sources_router
|
||||||
from .routes.pattern_templates import router as pattern_templates_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.color_strip_sources import router as color_strip_sources_router
|
||||||
from .routes.audio import router as audio_router
|
from .routes.audio import router as audio_router
|
||||||
from .routes.audio_sources import router as audio_sources_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_sources_router)
|
||||||
router.include_router(audio_templates_router)
|
router.include_router(audio_templates_router)
|
||||||
router.include_router(value_sources_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(automations_router)
|
||||||
router.include_router(scene_presets_router)
|
router.include_router(scene_presets_router)
|
||||||
router.include_router(webhooks_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.postprocessing_template_store import PostprocessingTemplateStore
|
||||||
from wled_controller.storage.pattern_template_store import PatternTemplateStore
|
from wled_controller.storage.pattern_template_store import PatternTemplateStore
|
||||||
from wled_controller.storage.picture_source_store import PictureSourceStore
|
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.color_strip_store import ColorStripStore
|
||||||
from wled_controller.storage.audio_source_store import AudioSourceStore
|
from wled_controller.storage.audio_source_store import AudioSourceStore
|
||||||
from wled_controller.storage.audio_template_store import AudioTemplateStore
|
from wled_controller.storage.audio_template_store import AudioTemplateStore
|
||||||
@@ -25,7 +25,7 @@ _template_store: TemplateStore | None = None
|
|||||||
_pp_template_store: PostprocessingTemplateStore | None = None
|
_pp_template_store: PostprocessingTemplateStore | None = None
|
||||||
_pattern_template_store: PatternTemplateStore | None = None
|
_pattern_template_store: PatternTemplateStore | None = None
|
||||||
_picture_source_store: PictureSourceStore | 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
|
_color_strip_store: ColorStripStore | None = None
|
||||||
_audio_source_store: AudioSourceStore | None = None
|
_audio_source_store: AudioSourceStore | None = None
|
||||||
_audio_template_store: AudioTemplateStore | None = None
|
_audio_template_store: AudioTemplateStore | None = None
|
||||||
@@ -73,11 +73,11 @@ def get_picture_source_store() -> PictureSourceStore:
|
|||||||
return _picture_source_store
|
return _picture_source_store
|
||||||
|
|
||||||
|
|
||||||
def get_picture_target_store() -> PictureTargetStore:
|
def get_output_target_store() -> OutputTargetStore:
|
||||||
"""Get picture target store dependency."""
|
"""Get output target store dependency."""
|
||||||
if _picture_target_store is None:
|
if _output_target_store is None:
|
||||||
raise RuntimeError("Picture target store not initialized")
|
raise RuntimeError("Picture target store not initialized")
|
||||||
return _picture_target_store
|
return _output_target_store
|
||||||
|
|
||||||
|
|
||||||
def get_color_strip_store() -> ColorStripStore:
|
def get_color_strip_store() -> ColorStripStore:
|
||||||
@@ -164,7 +164,7 @@ def init_dependencies(
|
|||||||
pp_template_store: PostprocessingTemplateStore | None = None,
|
pp_template_store: PostprocessingTemplateStore | None = None,
|
||||||
pattern_template_store: PatternTemplateStore | None = None,
|
pattern_template_store: PatternTemplateStore | None = None,
|
||||||
picture_source_store: PictureSourceStore | 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,
|
color_strip_store: ColorStripStore | None = None,
|
||||||
audio_source_store: AudioSourceStore | None = None,
|
audio_source_store: AudioSourceStore | None = None,
|
||||||
audio_template_store: AudioTemplateStore | None = None,
|
audio_template_store: AudioTemplateStore | None = None,
|
||||||
@@ -178,7 +178,7 @@ def init_dependencies(
|
|||||||
):
|
):
|
||||||
"""Initialize global dependencies."""
|
"""Initialize global dependencies."""
|
||||||
global _device_store, _template_store, _processor_manager
|
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 _color_strip_store, _audio_source_store, _audio_template_store
|
||||||
global _value_source_store, _automation_store, _scene_preset_store, _automation_engine, _auto_backup_engine
|
global _value_source_store, _automation_store, _scene_preset_store, _automation_engine, _auto_backup_engine
|
||||||
global _sync_clock_store, _sync_clock_manager
|
global _sync_clock_store, _sync_clock_manager
|
||||||
@@ -188,7 +188,7 @@ def init_dependencies(
|
|||||||
_pp_template_store = pp_template_store
|
_pp_template_store = pp_template_store
|
||||||
_pattern_template_store = pattern_template_store
|
_pattern_template_store = pattern_template_store
|
||||||
_picture_source_store = picture_source_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
|
_color_strip_store = color_strip_store
|
||||||
_audio_source_store = audio_source_store
|
_audio_source_store = audio_source_store
|
||||||
_audio_template_store = audio_template_store
|
_audio_template_store = audio_template_store
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from wled_controller.api.auth import AuthRequired
|
|||||||
from wled_controller.api.dependencies import (
|
from wled_controller.api.dependencies import (
|
||||||
get_color_strip_store,
|
get_color_strip_store,
|
||||||
get_picture_source_store,
|
get_picture_source_store,
|
||||||
get_picture_target_store,
|
get_output_target_store,
|
||||||
get_processor_manager,
|
get_processor_manager,
|
||||||
)
|
)
|
||||||
from wled_controller.api.schemas.color_strip_sources import (
|
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.color_strip_store import ColorStripStore
|
||||||
from wled_controller.storage.picture_source import ProcessedPictureSource, ScreenCapturePictureSource
|
from wled_controller.storage.picture_source import ProcessedPictureSource, ScreenCapturePictureSource
|
||||||
from wled_controller.storage.picture_source_store import PictureSourceStore
|
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.utils import get_logger
|
||||||
from wled_controller.config import get_config
|
from wled_controller.config import get_config
|
||||||
|
|
||||||
@@ -295,7 +295,7 @@ async def delete_color_strip_source(
|
|||||||
source_id: str,
|
source_id: str,
|
||||||
_auth: AuthRequired,
|
_auth: AuthRequired,
|
||||||
store: ColorStripStore = Depends(get_color_strip_store),
|
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."""
|
"""Delete a color strip source. Returns 409 if referenced by any LED target."""
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from wled_controller.core.devices.led_client import (
|
|||||||
)
|
)
|
||||||
from wled_controller.api.dependencies import (
|
from wled_controller.api.dependencies import (
|
||||||
get_device_store,
|
get_device_store,
|
||||||
get_picture_target_store,
|
get_output_target_store,
|
||||||
get_processor_manager,
|
get_processor_manager,
|
||||||
)
|
)
|
||||||
from wled_controller.api.schemas.devices import (
|
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.core.processing.processor_manager import ProcessorManager
|
||||||
from wled_controller.storage import DeviceStore
|
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
|
from wled_controller.utils import get_logger
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
@@ -342,7 +342,7 @@ async def delete_device(
|
|||||||
device_id: str,
|
device_id: str,
|
||||||
_auth: AuthRequired,
|
_auth: AuthRequired,
|
||||||
store: DeviceStore = Depends(get_device_store),
|
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),
|
manager: ProcessorManager = Depends(get_processor_manager),
|
||||||
):
|
):
|
||||||
"""Delete/detach a device. Returns 409 if referenced by a target."""
|
"""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 asyncio
|
||||||
import base64
|
import base64
|
||||||
@@ -16,21 +16,21 @@ from wled_controller.api.dependencies import (
|
|||||||
get_device_store,
|
get_device_store,
|
||||||
get_pattern_template_store,
|
get_pattern_template_store,
|
||||||
get_picture_source_store,
|
get_picture_source_store,
|
||||||
get_picture_target_store,
|
get_output_target_store,
|
||||||
get_pp_template_store,
|
get_pp_template_store,
|
||||||
get_processor_manager,
|
get_processor_manager,
|
||||||
get_template_store,
|
get_template_store,
|
||||||
)
|
)
|
||||||
from wled_controller.api.schemas.picture_targets import (
|
from wled_controller.api.schemas.output_targets import (
|
||||||
ExtractedColorResponse,
|
ExtractedColorResponse,
|
||||||
KCTestRectangleResponse,
|
KCTestRectangleResponse,
|
||||||
KCTestResponse,
|
KCTestResponse,
|
||||||
KeyColorsResponse,
|
KeyColorsResponse,
|
||||||
KeyColorsSettingsSchema,
|
KeyColorsSettingsSchema,
|
||||||
PictureTargetCreate,
|
OutputTargetCreate,
|
||||||
PictureTargetListResponse,
|
OutputTargetListResponse,
|
||||||
PictureTargetResponse,
|
OutputTargetResponse,
|
||||||
PictureTargetUpdate,
|
OutputTargetUpdate,
|
||||||
TargetMetricsResponse,
|
TargetMetricsResponse,
|
||||||
TargetProcessingState,
|
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 import ScreenCapturePictureSource, StaticImagePictureSource
|
||||||
from wled_controller.storage.picture_source_store import PictureSourceStore
|
from wled_controller.storage.picture_source_store import PictureSourceStore
|
||||||
from wled_controller.storage.template_store import TemplateStore
|
from wled_controller.storage.template_store import TemplateStore
|
||||||
from wled_controller.storage.wled_picture_target import WledPictureTarget
|
from wled_controller.storage.wled_output_target import WledOutputTarget
|
||||||
from wled_controller.storage.key_colors_picture_target import (
|
from wled_controller.storage.key_colors_output_target import (
|
||||||
KeyColorsSettings,
|
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
|
from wled_controller.utils import get_logger
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
@@ -88,10 +88,10 @@ def _kc_schema_to_settings(schema: KeyColorsSettingsSchema) -> KeyColorsSettings
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _target_to_response(target) -> PictureTargetResponse:
|
def _target_to_response(target) -> OutputTargetResponse:
|
||||||
"""Convert a PictureTarget to PictureTargetResponse."""
|
"""Convert an OutputTarget to OutputTargetResponse."""
|
||||||
if isinstance(target, WledPictureTarget):
|
if isinstance(target, WledOutputTarget):
|
||||||
return PictureTargetResponse(
|
return OutputTargetResponse(
|
||||||
id=target.id,
|
id=target.id,
|
||||||
name=target.name,
|
name=target.name,
|
||||||
target_type=target.target_type,
|
target_type=target.target_type,
|
||||||
@@ -109,8 +109,8 @@ def _target_to_response(target) -> PictureTargetResponse:
|
|||||||
created_at=target.created_at,
|
created_at=target.created_at,
|
||||||
updated_at=target.updated_at,
|
updated_at=target.updated_at,
|
||||||
)
|
)
|
||||||
elif isinstance(target, KeyColorsPictureTarget):
|
elif isinstance(target, KeyColorsOutputTarget):
|
||||||
return PictureTargetResponse(
|
return OutputTargetResponse(
|
||||||
id=target.id,
|
id=target.id,
|
||||||
name=target.name,
|
name=target.name,
|
||||||
target_type=target.target_type,
|
target_type=target.target_type,
|
||||||
@@ -122,7 +122,7 @@ def _target_to_response(target) -> PictureTargetResponse:
|
|||||||
updated_at=target.updated_at,
|
updated_at=target.updated_at,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return PictureTargetResponse(
|
return OutputTargetResponse(
|
||||||
id=target.id,
|
id=target.id,
|
||||||
name=target.name,
|
name=target.name,
|
||||||
target_type=target.target_type,
|
target_type=target.target_type,
|
||||||
@@ -135,15 +135,15 @@ def _target_to_response(target) -> PictureTargetResponse:
|
|||||||
|
|
||||||
# ===== CRUD ENDPOINTS =====
|
# ===== 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(
|
async def create_target(
|
||||||
data: PictureTargetCreate,
|
data: OutputTargetCreate,
|
||||||
_auth: AuthRequired,
|
_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),
|
device_store: DeviceStore = Depends(get_device_store),
|
||||||
manager: ProcessorManager = Depends(get_processor_manager),
|
manager: ProcessorManager = Depends(get_processor_manager),
|
||||||
):
|
):
|
||||||
"""Create a new picture target."""
|
"""Create a new output target."""
|
||||||
try:
|
try:
|
||||||
# Validate device exists if provided
|
# Validate device exists if provided
|
||||||
if data.device_id:
|
if data.device_id:
|
||||||
@@ -188,18 +188,18 @@ async def create_target(
|
|||||||
raise HTTPException(status_code=500, detail=str(e))
|
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(
|
async def list_targets(
|
||||||
_auth: AuthRequired,
|
_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()
|
targets = target_store.get_all_targets()
|
||||||
responses = [_target_to_response(t) for t in 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(
|
async def batch_target_states(
|
||||||
_auth: AuthRequired,
|
_auth: AuthRequired,
|
||||||
manager: ProcessorManager = Depends(get_processor_manager),
|
manager: ProcessorManager = Depends(get_processor_manager),
|
||||||
@@ -208,7 +208,7 @@ async def batch_target_states(
|
|||||||
return {"states": manager.get_all_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(
|
async def batch_target_metrics(
|
||||||
_auth: AuthRequired,
|
_auth: AuthRequired,
|
||||||
manager: ProcessorManager = Depends(get_processor_manager),
|
manager: ProcessorManager = Depends(get_processor_manager),
|
||||||
@@ -217,13 +217,13 @@ async def batch_target_metrics(
|
|||||||
return {"metrics": manager.get_all_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(
|
async def get_target(
|
||||||
target_id: str,
|
target_id: str,
|
||||||
_auth: AuthRequired,
|
_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:
|
try:
|
||||||
target = target_store.get_target(target_id)
|
target = target_store.get_target(target_id)
|
||||||
return _target_to_response(target)
|
return _target_to_response(target)
|
||||||
@@ -231,16 +231,16 @@ async def get_target(
|
|||||||
raise HTTPException(status_code=404, detail=str(e))
|
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(
|
async def update_target(
|
||||||
target_id: str,
|
target_id: str,
|
||||||
data: PictureTargetUpdate,
|
data: OutputTargetUpdate,
|
||||||
_auth: AuthRequired,
|
_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),
|
device_store: DeviceStore = Depends(get_device_store),
|
||||||
manager: ProcessorManager = Depends(get_processor_manager),
|
manager: ProcessorManager = Depends(get_processor_manager),
|
||||||
):
|
):
|
||||||
"""Update a picture target."""
|
"""Update a output target."""
|
||||||
try:
|
try:
|
||||||
# Validate device exists if changing
|
# Validate device exists if changing
|
||||||
if data.device_id is not None and data.device_id:
|
if data.device_id is not None and data.device_id:
|
||||||
@@ -258,7 +258,7 @@ async def update_target(
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
existing_target = None
|
existing_target = None
|
||||||
|
|
||||||
if isinstance(existing_target, KeyColorsPictureTarget):
|
if isinstance(existing_target, KeyColorsOutputTarget):
|
||||||
ex = existing_target.settings
|
ex = existing_target.settings
|
||||||
merged = KeyColorsSettingsSchema(
|
merged = KeyColorsSettingsSchema(
|
||||||
fps=incoming.get("fps", ex.fps),
|
fps=incoming.get("fps", ex.fps),
|
||||||
@@ -325,14 +325,14 @@ async def update_target(
|
|||||||
raise HTTPException(status_code=500, detail=str(e))
|
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(
|
async def delete_target(
|
||||||
target_id: str,
|
target_id: str,
|
||||||
_auth: AuthRequired,
|
_auth: AuthRequired,
|
||||||
target_store: PictureTargetStore = Depends(get_picture_target_store),
|
target_store: OutputTargetStore = Depends(get_output_target_store),
|
||||||
manager: ProcessorManager = Depends(get_processor_manager),
|
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:
|
try:
|
||||||
# Stop processing if running
|
# Stop processing if running
|
||||||
try:
|
try:
|
||||||
@@ -360,14 +360,14 @@ async def delete_target(
|
|||||||
|
|
||||||
# ===== PROCESSING CONTROL ENDPOINTS =====
|
# ===== 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(
|
async def start_processing(
|
||||||
target_id: str,
|
target_id: str,
|
||||||
_auth: AuthRequired,
|
_auth: AuthRequired,
|
||||||
target_store: PictureTargetStore = Depends(get_picture_target_store),
|
target_store: OutputTargetStore = Depends(get_output_target_store),
|
||||||
manager: ProcessorManager = Depends(get_processor_manager),
|
manager: ProcessorManager = Depends(get_processor_manager),
|
||||||
):
|
):
|
||||||
"""Start processing for a picture target."""
|
"""Start processing for a output target."""
|
||||||
try:
|
try:
|
||||||
# Verify target exists in store
|
# Verify target exists in store
|
||||||
target_store.get_target(target_id)
|
target_store.get_target(target_id)
|
||||||
@@ -386,13 +386,13 @@ async def start_processing(
|
|||||||
raise HTTPException(status_code=500, detail=str(e))
|
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(
|
async def stop_processing(
|
||||||
target_id: str,
|
target_id: str,
|
||||||
_auth: AuthRequired,
|
_auth: AuthRequired,
|
||||||
manager: ProcessorManager = Depends(get_processor_manager),
|
manager: ProcessorManager = Depends(get_processor_manager),
|
||||||
):
|
):
|
||||||
"""Stop processing for a picture target."""
|
"""Stop processing for a output target."""
|
||||||
try:
|
try:
|
||||||
await manager.stop_processing(target_id)
|
await manager.stop_processing(target_id)
|
||||||
|
|
||||||
@@ -408,7 +408,7 @@ async def stop_processing(
|
|||||||
|
|
||||||
# ===== STATE & METRICS ENDPOINTS =====
|
# ===== 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(
|
async def get_target_state(
|
||||||
target_id: str,
|
target_id: str,
|
||||||
_auth: AuthRequired,
|
_auth: AuthRequired,
|
||||||
@@ -426,7 +426,7 @@ async def get_target_state(
|
|||||||
raise HTTPException(status_code=500, detail=str(e))
|
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(
|
async def get_target_metrics(
|
||||||
target_id: str,
|
target_id: str,
|
||||||
_auth: AuthRequired,
|
_auth: AuthRequired,
|
||||||
@@ -446,7 +446,7 @@ async def get_target_metrics(
|
|||||||
|
|
||||||
# ===== KEY COLORS ENDPOINTS =====
|
# ===== 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(
|
async def get_target_colors(
|
||||||
target_id: str,
|
target_id: str,
|
||||||
_auth: AuthRequired,
|
_auth: AuthRequired,
|
||||||
@@ -471,11 +471,11 @@ async def get_target_colors(
|
|||||||
raise HTTPException(status_code=404, detail=str(e))
|
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(
|
async def test_kc_target(
|
||||||
target_id: str,
|
target_id: str,
|
||||||
_auth: AuthRequired,
|
_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),
|
source_store: PictureSourceStore = Depends(get_picture_source_store),
|
||||||
template_store: TemplateStore = Depends(get_template_store),
|
template_store: TemplateStore = Depends(get_template_store),
|
||||||
pattern_store: PatternTemplateStore = Depends(get_pattern_template_store),
|
pattern_store: PatternTemplateStore = Depends(get_pattern_template_store),
|
||||||
@@ -494,7 +494,7 @@ async def test_kc_target(
|
|||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise HTTPException(status_code=404, detail=str(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")
|
raise HTTPException(status_code=400, detail="Target is not a key_colors target")
|
||||||
|
|
||||||
settings = target.settings
|
settings = target.settings
|
||||||
@@ -670,7 +670,7 @@ async def test_kc_target(
|
|||||||
logger.error(f"Error cleaning up test stream: {e}")
|
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(
|
async def target_colors_ws(
|
||||||
websocket: WebSocket,
|
websocket: WebSocket,
|
||||||
target_id: str,
|
target_id: str,
|
||||||
@@ -710,7 +710,7 @@ async def target_colors_ws(
|
|||||||
manager.remove_kc_ws_client(target_id, websocket)
|
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(
|
async def led_preview_ws(
|
||||||
websocket: WebSocket,
|
websocket: WebSocket,
|
||||||
target_id: str,
|
target_id: str,
|
||||||
@@ -788,12 +788,12 @@ async def events_ws(
|
|||||||
|
|
||||||
# ===== OVERLAY VISUALIZATION =====
|
# ===== 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(
|
async def start_target_overlay(
|
||||||
target_id: str,
|
target_id: str,
|
||||||
_auth: AuthRequired,
|
_auth: AuthRequired,
|
||||||
manager: ProcessorManager = Depends(get_processor_manager),
|
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),
|
color_strip_store: ColorStripStore = Depends(get_color_strip_store),
|
||||||
picture_source_store: PictureSourceStore = Depends(get_picture_source_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.
|
# can start even when processing is not currently running.
|
||||||
calibration = None
|
calibration = None
|
||||||
display_info = 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
|
first_css_id = target.color_strip_source_id
|
||||||
if first_css_id:
|
if first_css_id:
|
||||||
try:
|
try:
|
||||||
@@ -844,7 +844,7 @@ async def start_target_overlay(
|
|||||||
raise HTTPException(status_code=500, detail=str(e))
|
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(
|
async def stop_target_overlay(
|
||||||
target_id: str,
|
target_id: str,
|
||||||
_auth: AuthRequired,
|
_auth: AuthRequired,
|
||||||
@@ -862,7 +862,7 @@ async def stop_target_overlay(
|
|||||||
raise HTTPException(status_code=500, detail=str(e))
|
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(
|
async def get_overlay_status(
|
||||||
target_id: str,
|
target_id: str,
|
||||||
_auth: AuthRequired,
|
_auth: AuthRequired,
|
||||||
@@ -5,7 +5,7 @@ from fastapi import APIRouter, HTTPException, Depends
|
|||||||
from wled_controller.api.auth import AuthRequired
|
from wled_controller.api.auth import AuthRequired
|
||||||
from wled_controller.api.dependencies import (
|
from wled_controller.api.dependencies import (
|
||||||
get_pattern_template_store,
|
get_pattern_template_store,
|
||||||
get_picture_target_store,
|
get_output_target_store,
|
||||||
)
|
)
|
||||||
from wled_controller.api.schemas.pattern_templates import (
|
from wled_controller.api.schemas.pattern_templates import (
|
||||||
PatternTemplateCreate,
|
PatternTemplateCreate,
|
||||||
@@ -13,10 +13,10 @@ from wled_controller.api.schemas.pattern_templates import (
|
|||||||
PatternTemplateResponse,
|
PatternTemplateResponse,
|
||||||
PatternTemplateUpdate,
|
PatternTemplateUpdate,
|
||||||
)
|
)
|
||||||
from wled_controller.api.schemas.picture_targets import KeyColorRectangleSchema
|
from wled_controller.api.schemas.output_targets import KeyColorRectangleSchema
|
||||||
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_store import PatternTemplateStore
|
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
|
from wled_controller.utils import get_logger
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
@@ -127,7 +127,7 @@ async def delete_pattern_template(
|
|||||||
template_id: str,
|
template_id: str,
|
||||||
_auth: AuthRequired,
|
_auth: AuthRequired,
|
||||||
store: PatternTemplateStore = Depends(get_pattern_template_store),
|
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."""
|
"""Delete a pattern template."""
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from fastapi.responses import Response
|
|||||||
from wled_controller.api.auth import AuthRequired
|
from wled_controller.api.auth import AuthRequired
|
||||||
from wled_controller.api.dependencies import (
|
from wled_controller.api.dependencies import (
|
||||||
get_picture_source_store,
|
get_picture_source_store,
|
||||||
get_picture_target_store,
|
get_output_target_store,
|
||||||
get_pp_template_store,
|
get_pp_template_store,
|
||||||
get_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.capture_engines import EngineRegistry
|
||||||
from wled_controller.core.filters import FilterRegistry, ImagePool
|
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.template_store import TemplateStore
|
||||||
from wled_controller.storage.postprocessing_template_store import PostprocessingTemplateStore
|
from wled_controller.storage.postprocessing_template_store import PostprocessingTemplateStore
|
||||||
from wled_controller.storage.picture_source_store import PictureSourceStore
|
from wled_controller.storage.picture_source_store import PictureSourceStore
|
||||||
@@ -254,7 +254,7 @@ async def delete_picture_source(
|
|||||||
stream_id: str,
|
stream_id: str,
|
||||||
_auth: AuthRequired,
|
_auth: AuthRequired,
|
||||||
store: PictureSourceStore = Depends(get_picture_source_store),
|
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."""
|
"""Delete a picture source."""
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from fastapi import APIRouter, Depends, HTTPException
|
|||||||
|
|
||||||
from wled_controller.api.auth import AuthRequired
|
from wled_controller.api.auth import AuthRequired
|
||||||
from wled_controller.api.dependencies import (
|
from wled_controller.api.dependencies import (
|
||||||
get_picture_target_store,
|
get_output_target_store,
|
||||||
get_processor_manager,
|
get_processor_manager,
|
||||||
get_scene_preset_store,
|
get_scene_preset_store,
|
||||||
)
|
)
|
||||||
@@ -23,7 +23,7 @@ from wled_controller.core.scenes.scene_activator import (
|
|||||||
apply_scene_state,
|
apply_scene_state,
|
||||||
capture_current_snapshot,
|
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 import ScenePreset
|
||||||
from wled_controller.storage.scene_preset_store import ScenePresetStore
|
from wled_controller.storage.scene_preset_store import ScenePresetStore
|
||||||
from wled_controller.utils import get_logger
|
from wled_controller.utils import get_logger
|
||||||
@@ -62,7 +62,7 @@ async def create_scene_preset(
|
|||||||
data: ScenePresetCreate,
|
data: ScenePresetCreate,
|
||||||
_auth: AuthRequired,
|
_auth: AuthRequired,
|
||||||
store: ScenePresetStore = Depends(get_scene_preset_store),
|
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),
|
manager: ProcessorManager = Depends(get_processor_manager),
|
||||||
):
|
):
|
||||||
"""Capture current state as a new scene preset."""
|
"""Capture current state as a new scene preset."""
|
||||||
@@ -176,7 +176,7 @@ async def recapture_scene_preset(
|
|||||||
preset_id: str,
|
preset_id: str,
|
||||||
_auth: AuthRequired,
|
_auth: AuthRequired,
|
||||||
store: ScenePresetStore = Depends(get_scene_preset_store),
|
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),
|
manager: ProcessorManager = Depends(get_processor_manager),
|
||||||
):
|
):
|
||||||
"""Re-capture current state into an existing preset (updates snapshot)."""
|
"""Re-capture current state into an existing preset (updates snapshot)."""
|
||||||
@@ -214,7 +214,7 @@ async def activate_scene_preset(
|
|||||||
preset_id: str,
|
preset_id: str,
|
||||||
_auth: AuthRequired,
|
_auth: AuthRequired,
|
||||||
store: ScenePresetStore = Depends(get_scene_preset_store),
|
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),
|
manager: ProcessorManager = Depends(get_processor_manager),
|
||||||
):
|
):
|
||||||
"""Activate a scene preset — restore the captured state."""
|
"""Activate a scene preset — restore the captured state."""
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ STORE_MAP = {
|
|||||||
"capture_templates": "templates_file",
|
"capture_templates": "templates_file",
|
||||||
"postprocessing_templates": "postprocessing_templates_file",
|
"postprocessing_templates": "postprocessing_templates_file",
|
||||||
"picture_sources": "picture_sources_file",
|
"picture_sources": "picture_sources_file",
|
||||||
"picture_targets": "picture_targets_file",
|
"output_targets": "output_targets_file",
|
||||||
"pattern_templates": "pattern_templates_file",
|
"pattern_templates": "pattern_templates_file",
|
||||||
"color_strip_sources": "color_strip_sources_file",
|
"color_strip_sources": "color_strip_sources_file",
|
||||||
"audio_sources": "audio_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.auth import AuthRequired
|
||||||
from wled_controller.api.dependencies import (
|
from wled_controller.api.dependencies import (
|
||||||
get_picture_target_store,
|
get_output_target_store,
|
||||||
get_processor_manager,
|
get_processor_manager,
|
||||||
get_value_source_store,
|
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 import ValueSource
|
||||||
from wled_controller.storage.value_source_store import ValueSourceStore
|
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.core.processing.processor_manager import ProcessorManager
|
||||||
from wled_controller.utils import get_logger
|
from wled_controller.utils import get_logger
|
||||||
|
|
||||||
@@ -157,14 +157,14 @@ async def delete_value_source(
|
|||||||
source_id: str,
|
source_id: str,
|
||||||
_auth: AuthRequired,
|
_auth: AuthRequired,
|
||||||
store: ValueSourceStore = Depends(get_value_source_store),
|
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."""
|
"""Delete a value source."""
|
||||||
try:
|
try:
|
||||||
# Check if any targets reference this value source
|
# 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():
|
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:
|
if getattr(target, "brightness_value_source_id", "") == source_id:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Cannot delete: referenced by target '{target.name}'"
|
f"Cannot delete: referenced by target '{target.name}'"
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ from .color_strip_sources import (
|
|||||||
ColorStripSourceUpdate,
|
ColorStripSourceUpdate,
|
||||||
CSSCalibrationTestRequest,
|
CSSCalibrationTestRequest,
|
||||||
)
|
)
|
||||||
from .picture_targets import (
|
from .output_targets import (
|
||||||
PictureTargetCreate,
|
OutputTargetCreate,
|
||||||
PictureTargetListResponse,
|
OutputTargetListResponse,
|
||||||
PictureTargetResponse,
|
OutputTargetResponse,
|
||||||
PictureTargetUpdate,
|
OutputTargetUpdate,
|
||||||
TargetMetricsResponse,
|
TargetMetricsResponse,
|
||||||
TargetProcessingState,
|
TargetProcessingState,
|
||||||
)
|
)
|
||||||
@@ -100,10 +100,10 @@ __all__ = [
|
|||||||
"ColorStripSourceResponse",
|
"ColorStripSourceResponse",
|
||||||
"ColorStripSourceUpdate",
|
"ColorStripSourceUpdate",
|
||||||
"CSSCalibrationTestRequest",
|
"CSSCalibrationTestRequest",
|
||||||
"PictureTargetCreate",
|
"OutputTargetCreate",
|
||||||
"PictureTargetListResponse",
|
"OutputTargetListResponse",
|
||||||
"PictureTargetResponse",
|
"OutputTargetResponse",
|
||||||
"PictureTargetUpdate",
|
"OutputTargetUpdate",
|
||||||
"TargetMetricsResponse",
|
"TargetMetricsResponse",
|
||||||
"TargetProcessingState",
|
"TargetProcessingState",
|
||||||
"EngineInfo",
|
"EngineInfo",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Picture target schemas (CRUD, processing state, metrics)."""
|
"""Output target schemas (CRUD, processing state, metrics)."""
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, Optional, List
|
from typing import Dict, Optional, List
|
||||||
@@ -46,8 +46,8 @@ class KeyColorsResponse(BaseModel):
|
|||||||
timestamp: Optional[datetime] = Field(None, description="Extraction timestamp")
|
timestamp: Optional[datetime] = Field(None, description="Extraction timestamp")
|
||||||
|
|
||||||
|
|
||||||
class PictureTargetCreate(BaseModel):
|
class OutputTargetCreate(BaseModel):
|
||||||
"""Request to create a picture target."""
|
"""Request to create an output target."""
|
||||||
|
|
||||||
name: str = Field(description="Target name", min_length=1, max_length=100)
|
name: str = Field(description="Target name", min_length=1, max_length=100)
|
||||||
target_type: str = Field(default="led", description="Target type (led, key_colors)")
|
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)
|
description: Optional[str] = Field(None, description="Optional description", max_length=500)
|
||||||
|
|
||||||
|
|
||||||
class PictureTargetUpdate(BaseModel):
|
class OutputTargetUpdate(BaseModel):
|
||||||
"""Request to update a picture target."""
|
"""Request to update an output target."""
|
||||||
|
|
||||||
name: Optional[str] = Field(None, description="Target name", min_length=1, max_length=100)
|
name: Optional[str] = Field(None, description="Target name", min_length=1, max_length=100)
|
||||||
# LED target fields
|
# LED target fields
|
||||||
@@ -87,8 +87,8 @@ class PictureTargetUpdate(BaseModel):
|
|||||||
description: Optional[str] = Field(None, description="Optional description", max_length=500)
|
description: Optional[str] = Field(None, description="Optional description", max_length=500)
|
||||||
|
|
||||||
|
|
||||||
class PictureTargetResponse(BaseModel):
|
class OutputTargetResponse(BaseModel):
|
||||||
"""Picture target response."""
|
"""Output target response."""
|
||||||
|
|
||||||
id: str = Field(description="Target ID")
|
id: str = Field(description="Target ID")
|
||||||
name: str = Field(description="Target name")
|
name: str = Field(description="Target name")
|
||||||
@@ -111,15 +111,15 @@ class PictureTargetResponse(BaseModel):
|
|||||||
updated_at: datetime = Field(description="Last update timestamp")
|
updated_at: datetime = Field(description="Last update timestamp")
|
||||||
|
|
||||||
|
|
||||||
class PictureTargetListResponse(BaseModel):
|
class OutputTargetListResponse(BaseModel):
|
||||||
"""List of picture targets response."""
|
"""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")
|
count: int = Field(description="Number of targets")
|
||||||
|
|
||||||
|
|
||||||
class TargetProcessingState(BaseModel):
|
class TargetProcessingState(BaseModel):
|
||||||
"""Processing state for a picture target."""
|
"""Processing state for an output target."""
|
||||||
|
|
||||||
target_id: str = Field(description="Target ID")
|
target_id: str = Field(description="Target ID")
|
||||||
device_id: Optional[str] = Field(None, description="Device 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 pydantic import BaseModel, Field
|
||||||
|
|
||||||
from .picture_targets import KeyColorRectangleSchema
|
from .output_targets import KeyColorRectangleSchema
|
||||||
|
|
||||||
|
|
||||||
class PatternTemplateCreate(BaseModel):
|
class PatternTemplateCreate(BaseModel):
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class StorageConfig(BaseSettings):
|
|||||||
templates_file: str = "data/capture_templates.json"
|
templates_file: str = "data/capture_templates.json"
|
||||||
postprocessing_templates_file: str = "data/postprocessing_templates.json"
|
postprocessing_templates_file: str = "data/postprocessing_templates.json"
|
||||||
picture_sources_file: str = "data/picture_sources.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"
|
pattern_templates_file: str = "data/pattern_templates.json"
|
||||||
color_strip_sources_file: str = "data/color_strip_sources.json"
|
color_strip_sources_file: str = "data/color_strip_sources.json"
|
||||||
audio_sources_file: str = "data/audio_sources.json"
|
audio_sources_file: str = "data/audio_sources.json"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Abstract base class for target processors.
|
"""Abstract base class for target processors.
|
||||||
|
|
||||||
A TargetProcessor encapsulates the processing loop and state for a single
|
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.
|
implement the target-specific capture→process→output pipeline.
|
||||||
|
|
||||||
ProcessorManager creates and owns TargetProcessor instances, delegating
|
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 typing import List, Optional, Set, Tuple
|
||||||
|
|
||||||
from wled_controller.core.processing.processor_manager import ProcessorManager
|
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 (
|
from wled_controller.storage.scene_preset import (
|
||||||
ScenePreset,
|
ScenePreset,
|
||||||
TargetSnapshot,
|
TargetSnapshot,
|
||||||
@@ -17,7 +17,7 @@ logger = get_logger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def capture_current_snapshot(
|
def capture_current_snapshot(
|
||||||
target_store: PictureTargetStore,
|
target_store: OutputTargetStore,
|
||||||
processor_manager: ProcessorManager,
|
processor_manager: ProcessorManager,
|
||||||
target_ids: Optional[Set[str]] = None,
|
target_ids: Optional[Set[str]] = None,
|
||||||
) -> List[TargetSnapshot]:
|
) -> List[TargetSnapshot]:
|
||||||
@@ -45,7 +45,7 @@ def capture_current_snapshot(
|
|||||||
|
|
||||||
async def apply_scene_state(
|
async def apply_scene_state(
|
||||||
preset: ScenePreset,
|
preset: ScenePreset,
|
||||||
target_store: PictureTargetStore,
|
target_store: OutputTargetStore,
|
||||||
processor_manager: ProcessorManager,
|
processor_manager: ProcessorManager,
|
||||||
) -> Tuple[str, List[str]]:
|
) -> Tuple[str, List[str]]:
|
||||||
"""Apply a scene preset's state to the system.
|
"""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.postprocessing_template_store import PostprocessingTemplateStore
|
||||||
from wled_controller.storage.pattern_template_store import PatternTemplateStore
|
from wled_controller.storage.pattern_template_store import PatternTemplateStore
|
||||||
from wled_controller.storage.picture_source_store import PictureSourceStore
|
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.color_strip_store import ColorStripStore
|
||||||
from wled_controller.storage.audio_source_store import AudioSourceStore
|
from wled_controller.storage.audio_source_store import AudioSourceStore
|
||||||
from wled_controller.storage.audio_template_store import AudioTemplateStore
|
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)
|
template_store = TemplateStore(config.storage.templates_file)
|
||||||
pp_template_store = PostprocessingTemplateStore(config.storage.postprocessing_templates_file)
|
pp_template_store = PostprocessingTemplateStore(config.storage.postprocessing_templates_file)
|
||||||
picture_source_store = PictureSourceStore(config.storage.picture_sources_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)
|
pattern_template_store = PatternTemplateStore(config.storage.pattern_templates_file)
|
||||||
color_strip_store = ColorStripStore(config.storage.color_strip_sources_file)
|
color_strip_store = ColorStripStore(config.storage.color_strip_sources_file)
|
||||||
audio_source_store = AudioSourceStore(config.storage.audio_sources_file)
|
audio_source_store = AudioSourceStore(config.storage.audio_sources_file)
|
||||||
@@ -113,7 +113,7 @@ async def lifespan(app: FastAPI):
|
|||||||
automation_store, processor_manager,
|
automation_store, processor_manager,
|
||||||
mqtt_service=mqtt_service,
|
mqtt_service=mqtt_service,
|
||||||
scene_preset_store=scene_preset_store,
|
scene_preset_store=scene_preset_store,
|
||||||
target_store=picture_target_store,
|
target_store=output_target_store,
|
||||||
device_store=device_store,
|
device_store=device_store,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ async def lifespan(app: FastAPI):
|
|||||||
pp_template_store=pp_template_store,
|
pp_template_store=pp_template_store,
|
||||||
pattern_template_store=pattern_template_store,
|
pattern_template_store=pattern_template_store,
|
||||||
picture_source_store=picture_source_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,
|
color_strip_store=color_strip_store,
|
||||||
audio_source_store=audio_source_store,
|
audio_source_store=audio_source_store,
|
||||||
audio_template_store=audio_template_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")
|
logger.info(f"Registered {len(devices)} devices for health monitoring")
|
||||||
|
|
||||||
# Register picture targets in processor manager
|
# Register output targets in processor manager
|
||||||
targets = picture_target_store.get_all_targets()
|
targets = output_target_store.get_all_targets()
|
||||||
registered_targets = 0
|
registered_targets = 0
|
||||||
for target in targets:
|
for target in targets:
|
||||||
try:
|
try:
|
||||||
@@ -174,7 +174,7 @@ async def lifespan(app: FastAPI):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to register target {target.id}: {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
|
# Start background health monitoring for all devices
|
||||||
await processor_manager.start_health_monitoring()
|
await processor_manager.start_health_monitoring()
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ function _buildItems(results, states = {}) {
|
|||||||
// Maps endpoint → response key that holds the array
|
// Maps endpoint → response key that holds the array
|
||||||
const _responseKeys = [
|
const _responseKeys = [
|
||||||
['/devices', 'devices'],
|
['/devices', 'devices'],
|
||||||
['/picture-targets', 'targets'],
|
['/output-targets', 'targets'],
|
||||||
['/color-strip-sources', 'sources'],
|
['/color-strip-sources', 'sources'],
|
||||||
['/automations', 'automations'],
|
['/automations', 'automations'],
|
||||||
['/capture-templates', 'templates'],
|
['/capture-templates', 'templates'],
|
||||||
@@ -126,7 +126,7 @@ const _responseKeys = [
|
|||||||
|
|
||||||
async function _fetchAllEntities() {
|
async function _fetchAllEntities() {
|
||||||
const [statesData, ...results] = await Promise.all([
|
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(r => r.ok ? r.json() : {})
|
||||||
.then(data => data.states || {})
|
.then(data => data.states || {})
|
||||||
.catch(() => ({})),
|
.catch(() => ({})),
|
||||||
|
|||||||
@@ -419,12 +419,12 @@ export async function loadDashboard(forceFullRender = false) {
|
|||||||
try {
|
try {
|
||||||
// Fire all requests in a single batch to avoid sequential RTTs
|
// Fire all requests in a single batch to avoid sequential RTTs
|
||||||
const [targetsResp, automationsResp, devicesResp, cssResp, batchStatesResp, batchMetricsResp, scenePresets, syncClocksResp] = await Promise.all([
|
const [targetsResp, automationsResp, devicesResp, cssResp, batchStatesResp, batchMetricsResp, scenePresets, syncClocksResp] = await Promise.all([
|
||||||
fetchWithAuth('/picture-targets'),
|
fetchWithAuth('/output-targets'),
|
||||||
fetchWithAuth('/automations').catch(() => null),
|
fetchWithAuth('/automations').catch(() => null),
|
||||||
fetchWithAuth('/devices').catch(() => null),
|
fetchWithAuth('/devices').catch(() => null),
|
||||||
fetchWithAuth('/color-strip-sources').catch(() => null),
|
fetchWithAuth('/color-strip-sources').catch(() => null),
|
||||||
fetchWithAuth('/picture-targets/batch/states').catch(() => null),
|
fetchWithAuth('/output-targets/batch/states').catch(() => null),
|
||||||
fetchWithAuth('/picture-targets/batch/metrics').catch(() => null),
|
fetchWithAuth('/output-targets/batch/metrics').catch(() => null),
|
||||||
loadScenePresets(),
|
loadScenePresets(),
|
||||||
fetchWithAuth('/sync-clocks').catch(() => null),
|
fetchWithAuth('/sync-clocks').catch(() => null),
|
||||||
]);
|
]);
|
||||||
@@ -746,7 +746,7 @@ export async function dashboardToggleAutomation(automationId, enable) {
|
|||||||
|
|
||||||
export async function dashboardStartTarget(targetId) {
|
export async function dashboardStartTarget(targetId) {
|
||||||
try {
|
try {
|
||||||
const response = await fetchWithAuth(`/picture-targets/${targetId}/start`, {
|
const response = await fetchWithAuth(`/output-targets/${targetId}/start`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
@@ -764,7 +764,7 @@ export async function dashboardStartTarget(targetId) {
|
|||||||
|
|
||||||
export async function dashboardStopTarget(targetId) {
|
export async function dashboardStopTarget(targetId) {
|
||||||
try {
|
try {
|
||||||
const response = await fetchWithAuth(`/picture-targets/${targetId}/stop`, {
|
const response = await fetchWithAuth(`/output-targets/${targetId}/stop`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
@@ -783,15 +783,15 @@ export async function dashboardStopTarget(targetId) {
|
|||||||
export async function dashboardStopAll() {
|
export async function dashboardStopAll() {
|
||||||
try {
|
try {
|
||||||
const [targetsResp, statesResp] = await Promise.all([
|
const [targetsResp, statesResp] = await Promise.all([
|
||||||
fetchWithAuth('/picture-targets'),
|
fetchWithAuth('/output-targets'),
|
||||||
fetchWithAuth('/picture-targets/batch/states'),
|
fetchWithAuth('/output-targets/batch/states'),
|
||||||
]);
|
]);
|
||||||
const data = await targetsResp.json();
|
const data = await targetsResp.json();
|
||||||
const statesData = statesResp.ok ? await statesResp.json() : { states: {} };
|
const statesData = statesResp.ok ? await statesResp.json() : { states: {} };
|
||||||
const states = statesData.states || {};
|
const states = statesData.states || {};
|
||||||
const running = (data.targets || []).filter(t => states[t.id]?.processing);
|
const running = (data.targets || []).filter(t => states[t.id]?.processing);
|
||||||
await Promise.all(running.map(t =>
|
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();
|
loadDashboard();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ export function createKCTargetCard(target, sourceMap, patternTemplateMap, valueS
|
|||||||
// ===== KEY COLORS TEST =====
|
// ===== KEY COLORS TEST =====
|
||||||
|
|
||||||
export async function fetchKCTest(targetId) {
|
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',
|
method: 'POST',
|
||||||
headers: getHeaders(),
|
headers: getHeaders(),
|
||||||
});
|
});
|
||||||
@@ -539,7 +539,7 @@ export async function showKCEditor(targetId = null, cloneData = null) {
|
|||||||
_ensurePatternEntitySelect(patTemplates);
|
_ensurePatternEntitySelect(patTemplates);
|
||||||
|
|
||||||
if (targetId) {
|
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');
|
if (!resp.ok) throw new Error('Failed to load target');
|
||||||
const target = await resp.json();
|
const target = await resp.json();
|
||||||
const kcSettings = target.key_colors_settings || {};
|
const kcSettings = target.key_colors_settings || {};
|
||||||
@@ -653,13 +653,13 @@ export async function saveKCEditor() {
|
|||||||
try {
|
try {
|
||||||
let response;
|
let response;
|
||||||
if (targetId) {
|
if (targetId) {
|
||||||
response = await fetchWithAuth(`/picture-targets/${targetId}`, {
|
response = await fetchWithAuth(`/output-targets/${targetId}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
payload.target_type = 'key_colors';
|
payload.target_type = 'key_colors';
|
||||||
response = await fetchWithAuth('/picture-targets', {
|
response = await fetchWithAuth('/output-targets', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
@@ -683,7 +683,7 @@ export async function saveKCEditor() {
|
|||||||
|
|
||||||
export async function cloneKCTarget(targetId) {
|
export async function cloneKCTarget(targetId) {
|
||||||
try {
|
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');
|
if (!resp.ok) throw new Error('Failed to load target');
|
||||||
const target = await resp.json();
|
const target = await resp.json();
|
||||||
showKCEditor(null, target);
|
showKCEditor(null, target);
|
||||||
@@ -699,7 +699,7 @@ export async function deleteKCTarget(targetId) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
disconnectKCWebSocket(targetId);
|
disconnectKCWebSocket(targetId);
|
||||||
const response = await fetchWithAuth(`/picture-targets/${targetId}`, {
|
const response = await fetchWithAuth(`/output-targets/${targetId}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
@@ -726,7 +726,7 @@ export function updateKCBrightnessLabel(targetId, value) {
|
|||||||
export async function saveKCBrightness(targetId, value) {
|
export async function saveKCBrightness(targetId, value) {
|
||||||
const brightness = parseInt(value) / 255;
|
const brightness = parseInt(value) / 255;
|
||||||
try {
|
try {
|
||||||
await fetch(`${API_BASE}/picture-targets/${targetId}`, {
|
await fetch(`${API_BASE}/output-targets/${targetId}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: getHeaders(),
|
headers: getHeaders(),
|
||||||
body: JSON.stringify({ key_colors_settings: { brightness } }),
|
body: JSON.stringify({ key_colors_settings: { brightness } }),
|
||||||
@@ -747,7 +747,7 @@ export function connectKCWebSocket(targetId) {
|
|||||||
if (!key) return;
|
if (!key) return;
|
||||||
|
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
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 {
|
try {
|
||||||
const ws = new WebSocket(wsUrl);
|
const ws = new WebSocket(wsUrl);
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ export async function openScenePresetCapture() {
|
|||||||
selectorGroup.style.display = '';
|
selectorGroup.style.display = '';
|
||||||
targetList.innerHTML = '';
|
targetList.innerHTML = '';
|
||||||
try {
|
try {
|
||||||
const resp = await fetchWithAuth('/picture-targets');
|
const resp = await fetchWithAuth('/output-targets');
|
||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
_allTargets = data.targets || [];
|
_allTargets = data.targets || [];
|
||||||
@@ -328,7 +328,7 @@ export async function cloneScenePreset(presetId) {
|
|||||||
selectorGroup.style.display = '';
|
selectorGroup.style.display = '';
|
||||||
targetList.innerHTML = '';
|
targetList.innerHTML = '';
|
||||||
try {
|
try {
|
||||||
const resp = await fetchWithAuth('/picture-targets');
|
const resp = await fetchWithAuth('/output-targets');
|
||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
_allTargets = data.targets || [];
|
_allTargets = data.targets || [];
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ export async function showTargetEditor(targetId = null, cloneData = null) {
|
|||||||
|
|
||||||
if (targetId) {
|
if (targetId) {
|
||||||
// Editing existing target
|
// 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');
|
if (!resp.ok) throw new Error('Failed to load target');
|
||||||
const target = await resp.json();
|
const target = await resp.json();
|
||||||
|
|
||||||
@@ -478,13 +478,13 @@ export async function saveTargetEditor() {
|
|||||||
try {
|
try {
|
||||||
let response;
|
let response;
|
||||||
if (targetId) {
|
if (targetId) {
|
||||||
response = await fetchWithAuth(`/picture-targets/${targetId}`, {
|
response = await fetchWithAuth(`/output-targets/${targetId}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
payload.target_type = 'led';
|
payload.target_type = 'led';
|
||||||
response = await fetchWithAuth('/picture-targets', {
|
response = await fetchWithAuth('/output-targets', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
@@ -550,7 +550,7 @@ export async function loadTargetsTab() {
|
|||||||
// use DataCache for picture sources, audio sources, value sources, sync clocks
|
// use DataCache for picture sources, audio sources, value sources, sync clocks
|
||||||
const [devicesResp, targetsResp, cssResp, patResp, psArr, valueSrcArr, asSrcArr] = await Promise.all([
|
const [devicesResp, targetsResp, cssResp, patResp, psArr, valueSrcArr, asSrcArr] = await Promise.all([
|
||||||
fetchWithAuth('/devices'),
|
fetchWithAuth('/devices'),
|
||||||
fetchWithAuth('/picture-targets'),
|
fetchWithAuth('/output-targets'),
|
||||||
fetchWithAuth('/color-strip-sources').catch(() => null),
|
fetchWithAuth('/color-strip-sources').catch(() => null),
|
||||||
fetchWithAuth('/pattern-templates').catch(() => null),
|
fetchWithAuth('/pattern-templates').catch(() => null),
|
||||||
streamsCache.fetch().catch(() => []),
|
streamsCache.fetch().catch(() => []),
|
||||||
@@ -591,8 +591,8 @@ export async function loadTargetsTab() {
|
|||||||
// Fetch all device states, target states, and target metrics in batch
|
// Fetch all device states, target states, and target metrics in batch
|
||||||
const [batchDevStatesResp, batchTgtStatesResp, batchTgtMetricsResp] = await Promise.all([
|
const [batchDevStatesResp, batchTgtStatesResp, batchTgtMetricsResp] = await Promise.all([
|
||||||
fetchWithAuth('/devices/batch/states'),
|
fetchWithAuth('/devices/batch/states'),
|
||||||
fetchWithAuth('/picture-targets/batch/states'),
|
fetchWithAuth('/output-targets/batch/states'),
|
||||||
fetchWithAuth('/picture-targets/batch/metrics'),
|
fetchWithAuth('/output-targets/batch/metrics'),
|
||||||
]);
|
]);
|
||||||
const allDeviceStates = batchDevStatesResp.ok ? (await batchDevStatesResp.json()).states : {};
|
const allDeviceStates = batchDevStatesResp.ok ? (await batchDevStatesResp.json()).states : {};
|
||||||
const allTargetStates = batchTgtStatesResp.ok ? (await batchTgtStatesResp.json()).states : {};
|
const allTargetStates = batchTgtStatesResp.ok ? (await batchTgtStatesResp.json()).states : {};
|
||||||
@@ -608,7 +608,7 @@ export async function loadTargetsTab() {
|
|||||||
let latestColors = null;
|
let latestColors = null;
|
||||||
if (target.target_type === 'key_colors' && state.processing) {
|
if (target.target_type === 'key_colors' && state.processing) {
|
||||||
try {
|
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();
|
if (colorsResp.ok) latestColors = await colorsResp.json();
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
@@ -1046,7 +1046,7 @@ async function _targetAction(action) {
|
|||||||
|
|
||||||
export async function startTargetProcessing(targetId) {
|
export async function startTargetProcessing(targetId) {
|
||||||
await _targetAction(async () => {
|
await _targetAction(async () => {
|
||||||
const response = await fetchWithAuth(`/picture-targets/${targetId}/start`, {
|
const response = await fetchWithAuth(`/output-targets/${targetId}/start`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
@@ -1060,7 +1060,7 @@ export async function startTargetProcessing(targetId) {
|
|||||||
|
|
||||||
export async function stopTargetProcessing(targetId) {
|
export async function stopTargetProcessing(targetId) {
|
||||||
await _targetAction(async () => {
|
await _targetAction(async () => {
|
||||||
const response = await fetchWithAuth(`/picture-targets/${targetId}/stop`, {
|
const response = await fetchWithAuth(`/output-targets/${targetId}/stop`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
@@ -1083,8 +1083,8 @@ export async function stopAllKCTargets() {
|
|||||||
async function _stopAllByType(targetType) {
|
async function _stopAllByType(targetType) {
|
||||||
try {
|
try {
|
||||||
const [targetsResp, statesResp] = await Promise.all([
|
const [targetsResp, statesResp] = await Promise.all([
|
||||||
fetchWithAuth('/picture-targets'),
|
fetchWithAuth('/output-targets'),
|
||||||
fetchWithAuth('/picture-targets/batch/states'),
|
fetchWithAuth('/output-targets/batch/states'),
|
||||||
]);
|
]);
|
||||||
const data = await targetsResp.json();
|
const data = await targetsResp.json();
|
||||||
const statesData = statesResp.ok ? await statesResp.json() : { states: {} };
|
const statesData = statesResp.ok ? await statesResp.json() : { states: {} };
|
||||||
@@ -1096,7 +1096,7 @@ async function _stopAllByType(targetType) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await Promise.all(running.map(t =>
|
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');
|
showToast(t('targets.stop_all.stopped', { count: running.length }), 'success');
|
||||||
loadTargetsTab();
|
loadTargetsTab();
|
||||||
@@ -1108,7 +1108,7 @@ async function _stopAllByType(targetType) {
|
|||||||
|
|
||||||
export async function startTargetOverlay(targetId) {
|
export async function startTargetOverlay(targetId) {
|
||||||
await _targetAction(async () => {
|
await _targetAction(async () => {
|
||||||
const response = await fetchWithAuth(`/picture-targets/${targetId}/overlay/start`, {
|
const response = await fetchWithAuth(`/output-targets/${targetId}/overlay/start`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
@@ -1122,7 +1122,7 @@ export async function startTargetOverlay(targetId) {
|
|||||||
|
|
||||||
export async function stopTargetOverlay(targetId) {
|
export async function stopTargetOverlay(targetId) {
|
||||||
await _targetAction(async () => {
|
await _targetAction(async () => {
|
||||||
const response = await fetchWithAuth(`/picture-targets/${targetId}/overlay/stop`, {
|
const response = await fetchWithAuth(`/output-targets/${targetId}/overlay/stop`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
@@ -1136,7 +1136,7 @@ export async function stopTargetOverlay(targetId) {
|
|||||||
|
|
||||||
export async function cloneTarget(targetId) {
|
export async function cloneTarget(targetId) {
|
||||||
try {
|
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');
|
if (!resp.ok) throw new Error('Failed to load target');
|
||||||
const target = await resp.json();
|
const target = await resp.json();
|
||||||
showTargetEditor(null, target);
|
showTargetEditor(null, target);
|
||||||
@@ -1151,7 +1151,7 @@ export async function deleteTarget(targetId) {
|
|||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
|
|
||||||
await _targetAction(async () => {
|
await _targetAction(async () => {
|
||||||
const response = await fetchWithAuth(`/picture-targets/${targetId}`, {
|
const response = await fetchWithAuth(`/output-targets/${targetId}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
@@ -1282,7 +1282,7 @@ function connectLedPreviewWS(targetId) {
|
|||||||
if (!key) return;
|
if (!key) return;
|
||||||
|
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
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 {
|
try {
|
||||||
const ws = new WebSocket(wsUrl);
|
const ws = new WebSocket(wsUrl);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* - Navigation: network-first with offline fallback
|
* - Navigation: network-first with offline fallback
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const CACHE_NAME = 'ledgrab-v23';
|
const CACHE_NAME = 'ledgrab-v24';
|
||||||
|
|
||||||
// Only pre-cache static assets (no auth required).
|
// Only pre-cache static assets (no auth required).
|
||||||
// Do NOT pre-cache '/' — it requires API key auth and would cache an error page.
|
// 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.
|
A device holds connection state and output settings.
|
||||||
Calibration, processing settings, and picture source assignments
|
Calibration, processing settings, and picture source assignments
|
||||||
now live on ColorStripSource and WledPictureTarget respectively.
|
now live on ColorStripSource and WledOutputTarget respectively.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
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 dataclasses import dataclass, field
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from wled_controller.storage.picture_target import PictureTarget
|
from wled_controller.storage.output_target import OutputTarget
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -71,7 +71,7 @@ class KeyColorsSettings:
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class KeyColorsPictureTarget(PictureTarget):
|
class KeyColorsOutputTarget(OutputTarget):
|
||||||
"""Key colors extractor target — extracts key colors from image rectangles."""
|
"""Key colors extractor target — extracts key colors from image rectangles."""
|
||||||
|
|
||||||
picture_source_id: str = ""
|
picture_source_id: str = ""
|
||||||
@@ -119,7 +119,7 @@ class KeyColorsPictureTarget(PictureTarget):
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: dict) -> "KeyColorsPictureTarget":
|
def from_dict(cls, data: dict) -> "KeyColorsOutputTarget":
|
||||||
settings_data = data.get("settings", {})
|
settings_data = data.get("settings", {})
|
||||||
settings = KeyColorsSettings.from_dict(settings_data)
|
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 dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -6,8 +6,8 @@ from typing import Optional
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PictureTarget:
|
class OutputTarget:
|
||||||
"""Base class for picture targets."""
|
"""Base class for output targets."""
|
||||||
|
|
||||||
id: str
|
id: str
|
||||||
name: str
|
name: str
|
||||||
@@ -50,13 +50,13 @@ class PictureTarget:
|
|||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: dict) -> "PictureTarget":
|
def from_dict(cls, data: dict) -> "OutputTarget":
|
||||||
"""Create from dictionary, dispatching to the correct subclass."""
|
"""Create from dictionary, dispatching to the correct subclass."""
|
||||||
target_type = data.get("target_type", "led")
|
target_type = data.get("target_type", "led")
|
||||||
if target_type == "led":
|
if target_type == "led":
|
||||||
from wled_controller.storage.wled_picture_target import WledPictureTarget
|
from wled_controller.storage.wled_output_target import WledOutputTarget
|
||||||
return WledPictureTarget.from_dict(data)
|
return WledOutputTarget.from_dict(data)
|
||||||
if target_type == "key_colors":
|
if target_type == "key_colors":
|
||||||
from wled_controller.storage.key_colors_picture_target import KeyColorsPictureTarget
|
from wled_controller.storage.key_colors_output_target import KeyColorsOutputTarget
|
||||||
return KeyColorsPictureTarget.from_dict(data)
|
return KeyColorsOutputTarget.from_dict(data)
|
||||||
raise ValueError(f"Unknown target type: {target_type}")
|
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 json
|
||||||
import uuid
|
import uuid
|
||||||
@@ -6,11 +6,11 @@ from datetime import datetime
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
from wled_controller.storage.picture_target import PictureTarget
|
from wled_controller.storage.output_target import OutputTarget
|
||||||
from wled_controller.storage.wled_picture_target import WledPictureTarget
|
from wled_controller.storage.wled_output_target import WledOutputTarget
|
||||||
from wled_controller.storage.key_colors_picture_target import (
|
from wled_controller.storage.key_colors_output_target import (
|
||||||
KeyColorsSettings,
|
KeyColorsSettings,
|
||||||
KeyColorsPictureTarget,
|
KeyColorsOutputTarget,
|
||||||
)
|
)
|
||||||
from wled_controller.utils import atomic_write_json, get_logger
|
from wled_controller.utils import atomic_write_json, get_logger
|
||||||
|
|
||||||
@@ -19,17 +19,17 @@ logger = get_logger(__name__)
|
|||||||
DEFAULT_STATE_CHECK_INTERVAL = 30 # seconds
|
DEFAULT_STATE_CHECK_INTERVAL = 30 # seconds
|
||||||
|
|
||||||
|
|
||||||
class PictureTargetStore:
|
class OutputTargetStore:
|
||||||
"""Persistent storage for picture targets."""
|
"""Persistent storage for output targets."""
|
||||||
|
|
||||||
def __init__(self, file_path: str):
|
def __init__(self, file_path: str):
|
||||||
"""Initialize picture target store.
|
"""Initialize output target store.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
file_path: Path to targets JSON file
|
file_path: Path to targets JSON file
|
||||||
"""
|
"""
|
||||||
self.file_path = Path(file_path)
|
self.file_path = Path(file_path)
|
||||||
self._targets: Dict[str, PictureTarget] = {}
|
self._targets: Dict[str, OutputTarget] = {}
|
||||||
self._load()
|
self._load()
|
||||||
|
|
||||||
def _load(self) -> None:
|
def _load(self) -> None:
|
||||||
@@ -41,52 +41,53 @@ class PictureTargetStore:
|
|||||||
with open(self.file_path, "r", encoding="utf-8") as f:
|
with open(self.file_path, "r", encoding="utf-8") as f:
|
||||||
data = json.load(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
|
loaded = 0
|
||||||
for target_id, target_dict in targets_data.items():
|
for target_id, target_dict in targets_data.items():
|
||||||
try:
|
try:
|
||||||
target = PictureTarget.from_dict(target_dict)
|
target = OutputTarget.from_dict(target_dict)
|
||||||
self._targets[target_id] = target
|
self._targets[target_id] = target
|
||||||
loaded += 1
|
loaded += 1
|
||||||
except Exception as e:
|
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:
|
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:
|
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
|
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:
|
def _save(self) -> None:
|
||||||
"""Save all targets to file."""
|
"""Save all targets to file."""
|
||||||
try:
|
try:
|
||||||
data = {
|
data = {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"picture_targets": {
|
"output_targets": {
|
||||||
target_id: target.to_dict()
|
target_id: target.to_dict()
|
||||||
for target_id, target in self._targets.items()
|
for target_id, target in self._targets.items()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
atomic_write_json(self.file_path, data)
|
atomic_write_json(self.file_path, data)
|
||||||
except Exception as e:
|
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
|
raise
|
||||||
|
|
||||||
def get_all_targets(self) -> List[PictureTarget]:
|
def get_all_targets(self) -> List[OutputTarget]:
|
||||||
"""Get all picture targets."""
|
"""Get all output targets."""
|
||||||
return list(self._targets.values())
|
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.
|
"""Get target by ID.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If target not found
|
ValueError: If target not found
|
||||||
"""
|
"""
|
||||||
if target_id not in self._targets:
|
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]
|
return self._targets[target_id]
|
||||||
|
|
||||||
def create_target(
|
def create_target(
|
||||||
@@ -105,8 +106,8 @@ class PictureTargetStore:
|
|||||||
key_colors_settings: Optional[KeyColorsSettings] = None,
|
key_colors_settings: Optional[KeyColorsSettings] = None,
|
||||||
description: Optional[str] = None,
|
description: Optional[str] = None,
|
||||||
picture_source_id: str = "",
|
picture_source_id: str = "",
|
||||||
) -> PictureTarget:
|
) -> OutputTarget:
|
||||||
"""Create a new picture target.
|
"""Create a new output target.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If validation fails
|
ValueError: If validation fails
|
||||||
@@ -117,13 +118,13 @@ class PictureTargetStore:
|
|||||||
# Check for duplicate name
|
# Check for duplicate name
|
||||||
for target in self._targets.values():
|
for target in self._targets.values():
|
||||||
if target.name == name:
|
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]}"
|
target_id = f"pt_{uuid.uuid4().hex[:8]}"
|
||||||
now = datetime.utcnow()
|
now = datetime.utcnow()
|
||||||
|
|
||||||
if target_type == "led":
|
if target_type == "led":
|
||||||
target: PictureTarget = WledPictureTarget(
|
target: OutputTarget = WledOutputTarget(
|
||||||
id=target_id,
|
id=target_id,
|
||||||
name=name,
|
name=name,
|
||||||
target_type="led",
|
target_type="led",
|
||||||
@@ -141,7 +142,7 @@ class PictureTargetStore:
|
|||||||
updated_at=now,
|
updated_at=now,
|
||||||
)
|
)
|
||||||
elif target_type == "key_colors":
|
elif target_type == "key_colors":
|
||||||
target = KeyColorsPictureTarget(
|
target = KeyColorsOutputTarget(
|
||||||
id=target_id,
|
id=target_id,
|
||||||
name=name,
|
name=name,
|
||||||
target_type="key_colors",
|
target_type="key_colors",
|
||||||
@@ -157,7 +158,7 @@ class PictureTargetStore:
|
|||||||
self._targets[target_id] = target
|
self._targets[target_id] = target
|
||||||
self._save()
|
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
|
return target
|
||||||
|
|
||||||
def update_target(
|
def update_target(
|
||||||
@@ -175,14 +176,14 @@ class PictureTargetStore:
|
|||||||
protocol: Optional[str] = None,
|
protocol: Optional[str] = None,
|
||||||
key_colors_settings: Optional[KeyColorsSettings] = None,
|
key_colors_settings: Optional[KeyColorsSettings] = None,
|
||||||
description: Optional[str] = None,
|
description: Optional[str] = None,
|
||||||
) -> PictureTarget:
|
) -> OutputTarget:
|
||||||
"""Update a picture target.
|
"""Update an output target.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If target not found or validation fails
|
ValueError: If target not found or validation fails
|
||||||
"""
|
"""
|
||||||
if target_id not in self._targets:
|
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]
|
target = self._targets[target_id]
|
||||||
|
|
||||||
@@ -190,7 +191,7 @@ class PictureTargetStore:
|
|||||||
# Check for duplicate name (exclude self)
|
# Check for duplicate name (exclude self)
|
||||||
for other in self._targets.values():
|
for other in self._targets.values():
|
||||||
if other.id != target_id and other.name == name:
|
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(
|
target.update_fields(
|
||||||
name=name,
|
name=name,
|
||||||
@@ -210,42 +211,42 @@ class PictureTargetStore:
|
|||||||
target.updated_at = datetime.utcnow()
|
target.updated_at = datetime.utcnow()
|
||||||
self._save()
|
self._save()
|
||||||
|
|
||||||
logger.info(f"Updated picture target: {target_id}")
|
logger.info(f"Updated output target: {target_id}")
|
||||||
return target
|
return target
|
||||||
|
|
||||||
def delete_target(self, target_id: str) -> None:
|
def delete_target(self, target_id: str) -> None:
|
||||||
"""Delete a picture target.
|
"""Delete an output target.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If target not found
|
ValueError: If target not found
|
||||||
"""
|
"""
|
||||||
if target_id not in self._targets:
|
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]
|
del self._targets[target_id]
|
||||||
self._save()
|
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."""
|
"""Get all targets that reference a specific device."""
|
||||||
return [
|
return [
|
||||||
t for t in self._targets.values()
|
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]:
|
def get_targets_referencing_source(self, source_id: str) -> List[str]:
|
||||||
"""Return names of KC targets that reference a picture source."""
|
"""Return names of KC targets that reference a picture source."""
|
||||||
return [
|
return [
|
||||||
target.name for target in self._targets.values()
|
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]:
|
def get_targets_referencing_css(self, css_id: str) -> List[str]:
|
||||||
"""Return names of LED targets that reference a color strip source."""
|
"""Return names of LED targets that reference a color strip source."""
|
||||||
return [
|
return [
|
||||||
target.name for target in self._targets.values()
|
target.name for target in self._targets.values()
|
||||||
if isinstance(target, WledPictureTarget)
|
if isinstance(target, WledOutputTarget)
|
||||||
and target.color_strip_source_id == css_id
|
and target.color_strip_source_id == css_id
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@ from dataclasses import dataclass, field
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List, Optional
|
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
|
@dataclass
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from datetime import datetime
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Optional
|
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.storage.pattern_template import PatternTemplate
|
||||||
from wled_controller.utils import atomic_write_json, get_logger
|
from wled_controller.utils import atomic_write_json, get_logger
|
||||||
|
|
||||||
@@ -203,11 +203,11 @@ class PatternTemplateStore:
|
|||||||
|
|
||||||
logger.info(f"Deleted pattern template: {template_id}")
|
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."""
|
"""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 [
|
return [
|
||||||
target.name for target in picture_target_store.get_all_targets()
|
target.name for target in output_target_store.get_all_targets()
|
||||||
if isinstance(target, KeyColorsPictureTarget) and target.settings.pattern_template_id == template_id
|
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 dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional
|
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
|
DEFAULT_STATE_CHECK_INTERVAL = 30 # seconds
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class WledPictureTarget(PictureTarget):
|
class WledOutputTarget(OutputTarget):
|
||||||
"""LED picture target — pairs an LED device with a ColorStripSource."""
|
"""LED output target — pairs an LED device with a ColorStripSource."""
|
||||||
|
|
||||||
device_id: str = ""
|
device_id: str = ""
|
||||||
color_strip_source_id: str = ""
|
color_strip_source_id: str = ""
|
||||||
@@ -104,7 +104,7 @@ class WledPictureTarget(PictureTarget):
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: dict) -> "WledPictureTarget":
|
def from_dict(cls, data: dict) -> "WledOutputTarget":
|
||||||
"""Create from dictionary."""
|
"""Create from dictionary."""
|
||||||
return cls(
|
return cls(
|
||||||
id=data["id"],
|
id=data["id"],
|
||||||
@@ -253,7 +253,7 @@ def test_get_target_metrics(processor_manager):
|
|||||||
|
|
||||||
def test_target_type_detection(processor_manager):
|
def test_target_type_detection(processor_manager):
|
||||||
"""Test target type detection via processor instances."""
|
"""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.kc_target_processor import KCTargetProcessor
|
||||||
from wled_controller.core.processing.wled_target_processor import WledTargetProcessor
|
from wled_controller.core.processing.wled_target_processor import WledTargetProcessor
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user