Add LED device abstraction layer for multi-controller support

Introduce abstract LEDClient base class with factory pattern so new
LED controller types can plug in alongside WLED. ProcessorManager is
now fully type-agnostic — all device-specific logic (health checks,
state snapshot/restore, fast send) lives behind the LEDClient interface.

- New led_client.py: LEDClient ABC, DeviceHealth, factory functions
- WLEDClient inherits LEDClient, encapsulates WLED health checks and state management
- device_type field on Device storage model (defaults to "wled")
- Rename target_type "wled" → "led" with backward-compat migration
- Frontend: "WLED" tab → "LED", device type badge, type selector in
  add-device modal, device type shown in target device dropdown
- All wled_* API fields renamed to device_* for generic naming

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-16 12:41:02 +03:00
parent afce183f79
commit b5a6885126
18 changed files with 667 additions and 346 deletions

View File

@@ -7,7 +7,7 @@
"auth.login": "Login",
"auth.logout": "Logout",
"auth.authenticated": "● Authenticated",
"auth.title": "Login to WLED Controller",
"auth.title": "Login to LED Grab",
"auth.message": "Please enter your API key to authenticate and access the LED Grab.",
"auth.label": "API Key:",
"auth.placeholder": "Enter your API key...",
@@ -101,13 +101,16 @@
"devices.wled_webui_link": "WLED Web UI",
"devices.wled_note_webui": "(open your device's IP in a browser).",
"devices.wled_note2": "This controller sends pixel color data and controls brightness per device.",
"device.type": "Device Type:",
"device.type.hint": "Select the type of LED controller",
"device.url.hint": "IP address or hostname of the device (e.g. http://192.168.1.100)",
"device.name": "Device Name:",
"device.name.placeholder": "Living Room TV",
"device.url": "URL:",
"device.url.placeholder": "http://192.168.1.100",
"device.led_count": "LED Count:",
"device.led_count.hint": "Number of LEDs configured in your WLED device",
"device.led_count.hint.auto": "Auto-detected from WLED device",
"device.led_count.hint": "Number of LEDs configured in the device",
"device.led_count.hint.auto": "Auto-detected from device",
"device.button.add": "Add Device",
"device.button.start": "Start",
"device.button.stop": "Stop",
@@ -115,7 +118,7 @@
"device.button.capture_settings": "Capture Settings",
"device.button.calibrate": "Calibrate",
"device.button.remove": "Remove",
"device.button.webui": "Open WLED Web UI",
"device.button.webui": "Open Device Web UI",
"device.status.connected": "Connected",
"device.status.disconnected": "Disconnected",
"device.status.error": "Error",
@@ -136,26 +139,26 @@
"device.metrics.frames_skipped": "Skipped",
"device.metrics.keepalive": "Keepalive",
"device.metrics.errors": "Errors",
"device.health.online": "WLED Online",
"device.health.offline": "WLED Offline",
"device.health.online": "Online",
"device.health.offline": "Offline",
"device.health.checking": "Checking...",
"device.tutorial.start": "Start tutorial",
"device.tip.metadata": "Device info (LED count, type, color channels) is auto-detected from WLED",
"device.tip.metadata": "Device info (LED count, type, color channels) is auto-detected from the device",
"device.tip.brightness": "Slide to adjust device brightness",
"device.tip.start": "Start or stop screen capture processing",
"device.tip.settings": "Configure general device settings (name, URL, health check)",
"device.tip.capture_settings": "Configure capture settings (display, capture template)",
"device.tip.calibrate": "Calibrate LED positions, direction, and coverage",
"device.tip.webui": "Open WLED's built-in web interface for advanced configuration",
"device.tip.add": "Click here to add a new WLED device",
"device.tip.webui": "Open the device's built-in web interface for advanced configuration",
"device.tip.add": "Click here to add a new LED device",
"settings.title": "Device Settings",
"settings.general.title": "General Settings",
"settings.capture.title": "Capture Settings",
"settings.capture.saved": "Capture settings updated",
"settings.capture.failed": "Failed to save capture settings",
"settings.brightness": "Brightness:",
"settings.brightness.hint": "Global brightness for this WLED device (0-100%)",
"settings.url.hint": "IP address or hostname of your WLED device",
"settings.brightness.hint": "Global brightness for this device (0-100%)",
"settings.url.hint": "IP address or hostname of the device",
"settings.display_index": "Display:",
"settings.display_index.hint": "Which screen to capture for this device",
"settings.fps": "Target FPS:",
@@ -164,7 +167,7 @@
"settings.capture_template.hint": "Screen capture engine and configuration for this device",
"settings.button.cancel": "Cancel",
"settings.health_interval": "Health Check Interval (s):",
"settings.health_interval.hint": "How often to check the WLED device status (5-600 seconds)",
"settings.health_interval.hint": "How often to check the device status (5-600 seconds)",
"settings.button.save": "Save Changes",
"settings.saved": "Settings saved successfully",
"settings.failed": "Failed to save settings",
@@ -176,7 +179,9 @@
"calibration.tip.span": "Drag green bars to adjust coverage span",
"calibration.tip.test": "Click an edge to toggle test LEDs",
"calibration.tip.toggle_inputs": "Click total LED count to toggle edge inputs",
"calibration.tip.skip_leds": "Skip LEDs at the start or end of the strip — skipped LEDs stay off",
"calibration.tip.border_width": "How many pixels from the screen edge to sample for LED colors",
"calibration.tip.skip_leds_start": "Skip LEDs at the start of the strip — skipped LEDs stay off",
"calibration.tip.skip_leds_end": "Skip LEDs at the end of the strip — skipped LEDs stay off",
"calibration.tutorial.start": "Start tutorial",
"calibration.start_position": "Starting Position:",
"calibration.position.bottom_left": "Bottom Left",
@@ -196,6 +201,8 @@
"calibration.skip_start.hint": "Number of LEDs to turn off at the beginning of the strip (0 = none)",
"calibration.skip_end": "Skip LEDs (End):",
"calibration.skip_end.hint": "Number of LEDs to turn off at the end of the strip (0 = none)",
"calibration.border_width": "Border (px):",
"calibration.border_width.hint": "How many pixels from the screen edge to sample for LED colors (1-100)",
"calibration.button.cancel": "Cancel",
"calibration.button.save": "Save",
"calibration.saved": "Calibration saved successfully",
@@ -313,7 +320,8 @@
"streams.validate_image.invalid": "Image not accessible",
"targets.title": "⚡ Targets",
"targets.description": "Targets bridge picture sources to output devices. Each target references a device and a source, with its own processing settings.",
"targets.subtab.wled": "WLED",
"targets.subtab.wled": "LED",
"targets.subtab.led": "LED",
"targets.section.devices": "💡 Devices",
"targets.section.targets": "⚡ Targets",
"targets.add": "Add Target",
@@ -324,7 +332,7 @@
"targets.name": "Target Name:",
"targets.name.placeholder": "My Target",
"targets.device": "Device:",
"targets.device.hint": "Which WLED device to send LED data to",
"targets.device.hint": "Select the LED device to send data to",
"targets.device.none": "-- Select a device --",
"targets.source": "Source:",
"targets.source.hint": "Which picture source to capture and process",
@@ -341,7 +349,7 @@
"targets.smoothing": "Smoothing:",
"targets.smoothing.hint": "Temporal blending between frames (0=none, 1=full). Reduces flicker.",
"targets.standby_interval": "Standby Interval:",
"targets.standby_interval.hint": "How often to resend the last frame when the screen is static, keeping WLED in live mode (0.5-5.0s)",
"targets.standby_interval.hint": "How often to resend the last frame when the screen is static, keeping the device in live mode (0.5-5.0s)",
"targets.created": "Target created successfully",
"targets.updated": "Target updated successfully",
"targets.deleted": "Target deleted successfully",

View File

@@ -101,13 +101,16 @@
"devices.wled_webui_link": "веб-интерфейс WLED",
"devices.wled_note_webui": "(откройте IP устройства в браузере).",
"devices.wled_note2": "Этот контроллер отправляет данные о цвете пикселей и управляет яркостью для каждого устройства.",
"device.type": "Тип устройства:",
"device.type.hint": "Выберите тип LED контроллера",
"device.url.hint": "IP адрес или имя хоста устройства (напр. http://192.168.1.100)",
"device.name": "Имя Устройства:",
"device.name.placeholder": "ТВ в Гостиной",
"device.url": "URL:",
"device.url.placeholder": "http://192.168.1.100",
"device.led_count": "Количество Светодиодов:",
"device.led_count.hint": "Количество светодиодов, настроенных в вашем WLED устройстве",
"device.led_count.hint.auto": "Автоматически определяется из WLED устройства",
"device.led_count.hint": "Количество светодиодов, настроенных в устройстве",
"device.led_count.hint.auto": "Автоматически определяется из устройства",
"device.button.add": "Добавить Устройство",
"device.button.start": "Запустить",
"device.button.stop": "Остановить",
@@ -115,7 +118,7 @@
"device.button.capture_settings": "Настройки захвата",
"device.button.calibrate": "Калибровка",
"device.button.remove": "Удалить",
"device.button.webui": "Открыть веб-интерфейс WLED",
"device.button.webui": "Открыть веб-интерфейс устройства",
"device.status.connected": "Подключено",
"device.status.disconnected": "Отключено",
"device.status.error": "Ошибка",
@@ -136,26 +139,26 @@
"device.metrics.frames_skipped": "Пропущено",
"device.metrics.keepalive": "Keepalive",
"device.metrics.errors": "Ошибки",
"device.health.online": "WLED Онлайн",
"device.health.offline": "WLED Недоступен",
"device.health.online": "Онлайн",
"device.health.offline": "Недоступен",
"device.health.checking": "Проверка...",
"device.tutorial.start": "Начать обучение",
"device.tip.metadata": "Информация об устройстве (кол-во LED, тип, цветовые каналы) определяется автоматически из WLED",
"device.tip.metadata": "Информация об устройстве (кол-во LED, тип, цветовые каналы) определяется автоматически",
"device.tip.brightness": "Перетащите для регулировки яркости",
"device.tip.start": "Запуск или остановка захвата экрана",
"device.tip.settings": "Основные настройки устройства (имя, URL, интервал проверки)",
"device.tip.capture_settings": "Настройки захвата (дисплей, шаблон захвата)",
"device.tip.calibrate": "Калибровка позиций LED, направления и зоны покрытия",
"device.tip.webui": "Открыть встроенный веб-интерфейс WLED для расширенной настройки",
"device.tip.add": "Нажмите, чтобы добавить новое WLED устройство",
"device.tip.webui": "Открыть встроенный веб-интерфейс устройства для расширенной настройки",
"device.tip.add": "Нажмите, чтобы добавить новое LED устройство",
"settings.title": "Настройки Устройства",
"settings.general.title": "Основные Настройки",
"settings.capture.title": "Настройки Захвата",
"settings.capture.saved": "Настройки захвата обновлены",
"settings.capture.failed": "Не удалось сохранить настройки захвата",
"settings.brightness": "Яркость:",
"settings.brightness.hint": "Общая яркость для этого WLED устройства (0-100%)",
"settings.url.hint": "IP адрес или имя хоста вашего WLED устройства",
"settings.brightness.hint": "Общая яркость для этого устройства (0-100%)",
"settings.url.hint": "IP адрес или имя хоста устройства",
"settings.display_index": "Дисплей:",
"settings.display_index.hint": "Какой экран захватывать для этого устройства",
"settings.fps": "Целевой FPS:",
@@ -164,7 +167,7 @@
"settings.capture_template.hint": "Движок захвата экрана и конфигурация для этого устройства",
"settings.button.cancel": "Отмена",
"settings.health_interval": "Интервал Проверки (с):",
"settings.health_interval.hint": "Как часто проверять статус WLED устройства (5-600 секунд)",
"settings.health_interval.hint": "Как часто проверять статус устройства (5-600 секунд)",
"settings.button.save": "Сохранить Изменения",
"settings.saved": "Настройки успешно сохранены",
"settings.failed": "Не удалось сохранить настройки",
@@ -176,7 +179,9 @@
"calibration.tip.span": "Перетащите зелёные полосы для настройки зоны покрытия",
"calibration.tip.test": "Нажмите на край для теста LED",
"calibration.tip.toggle_inputs": "Нажмите на общее количество LED для скрытия боковых полей",
"calibration.tip.skip_leds": "Пропуск LED в начале или конце ленты — пропущенные LED остаются выключенными",
"calibration.tip.border_width": "Сколько пикселей от края экрана использовать для цветов LED",
"calibration.tip.skip_leds_start": "Пропуск LED в начале ленты — пропущенные LED остаются выключенными",
"calibration.tip.skip_leds_end": "Пропуск LED в конце ленты — пропущенные LED остаются выключенными",
"calibration.tutorial.start": "Начать обучение",
"calibration.start_position": "Начальная Позиция:",
"calibration.position.bottom_left": "Нижний Левый",
@@ -196,6 +201,8 @@
"calibration.skip_start.hint": "Количество LED, которые будут выключены в начале ленты (0 = нет)",
"calibration.skip_end": "Пропуск LED (конец):",
"calibration.skip_end.hint": "Количество LED, которые будут выключены в конце ленты (0 = нет)",
"calibration.border_width": "Граница (px):",
"calibration.border_width.hint": "Сколько пикселей от края экрана выбирать для цвета LED (1-100)",
"calibration.button.cancel": "Отмена",
"calibration.button.save": "Сохранить",
"calibration.saved": "Калибровка успешно сохранена",
@@ -313,7 +320,8 @@
"streams.validate_image.invalid": "Изображение недоступно",
"targets.title": "⚡ Цели",
"targets.description": "Цели связывают источники изображений с устройствами вывода. Каждая цель ссылается на устройство и источник, с собственными настройками обработки.",
"targets.subtab.wled": "WLED",
"targets.subtab.wled": "LED",
"targets.subtab.led": "LED",
"targets.section.devices": "💡 Устройства",
"targets.section.targets": "⚡ Цели",
"targets.add": "Добавить Цель",
@@ -324,7 +332,7 @@
"targets.name": "Имя Цели:",
"targets.name.placeholder": "Моя Цель",
"targets.device": "Устройство:",
"targets.device.hint": "На какое WLED устройство отправлять данные LED",
"targets.device.hint": "Выберите LED устройство для передачи данных",
"targets.device.none": "-- Выберите устройство --",
"targets.source": "Источник:",
"targets.source.hint": "Какой источник изображения захватывать и обрабатывать",
@@ -341,7 +349,7 @@
"targets.smoothing": "Сглаживание:",
"targets.smoothing.hint": "Временное смешивание между кадрами (0=нет, 1=полное). Уменьшает мерцание.",
"targets.standby_interval": "Интервал ожидания:",
"targets.standby_interval.hint": "Как часто повторно отправлять последний кадр при статичном экране для удержания WLED в режиме live (0.5-5.0с)",
"targets.standby_interval.hint": "Как часто повторно отправлять последний кадр при статичном экране для удержания устройства в режиме live (0.5-5.0с)",
"targets.created": "Цель успешно создана",
"targets.updated": "Цель успешно обновлена",
"targets.deleted": "Цель успешно удалена",