feat(devices): standalone DDP target type

Promotes the existing DDP packet layer (previously WLED-internal) to a
first-class device type so any DDP-speaking receiver (Pixelblaze,
ESPixelStick, xLights/Falcon endpoints, generic firmware) can be driven
directly without WLED in the path.

Backend:
- New DDPLEDClient wraps the DDPClient transport as a proper LEDClient
  with supports_fast_send=True (synchronous UDP push on the hot loop).
- New DDPDeviceProvider — no native discovery, manual LED count,
  capabilities = {manual_led_count, health_check}.
- DDPConfig joins the typed config union; Device storage gains
  ddp_port / ddp_destination_id / ddp_color_order fields with safe
  defaults (0/1/1 -> port 4048, destination 1=display, RGB byte order).
- URL scheme: ddp://host[:port] or bare host[:port] (default 4048).
- Health check resolves the host via async DNS; UDP has no reply
  channel so reachability is best-effort by design.
- 29 new tests in test_ddp_led_client.py cover URL parsing, packet
  hot path (brightness, list/numpy input shapes, fast vs async send),
  provider validate/discover/capabilities, config round-trip via
  Device.to_config() and to_dict/from_dict.

Frontend:
- 'ddp' in DEVICE_TYPE_KEYS (next to 'dmx'), paper-plane icon.
- isDdpDevice predicate + per-type field show/hide in the create &
  settings modals.
- Color-order picker uses IconSelect (project rule bans plain select).
- Locale strings added in en/ru/zh.

Note: this commit also carries two pre-existing in-flight hunks that
were intermixed in the same files and could not be split out
non-interactively:
- api/routes/devices.py: URL-scheme inference for bare WLED hosts,
  safer error messages, exception-isolated parallel discovery.
- storage/device_store.py: secret_box helpers + at-rest encryption of
  Hue / BLE-Govee / MQTT credentials.
Both are independent of DDP and intentional per the user.
This commit is contained in:
2026-05-16 01:26:45 +03:00
parent 337984c618
commit 8f1140abad
19 changed files with 1109 additions and 28 deletions
+76
View File
@@ -659,3 +659,79 @@ Replace flat `DeviceInfo` + `**kwargs` provider contract with a discriminated un
- [x] Phases 2+3 — narrow `LEDDeviceProvider.create_client` to typed configs; migrate 3 call sites; delete `DeviceInfo` + `_get_device_info` + `_DEVICE_FIELD_DEFAULTS` (single PR)
- [x] Phase 4 — migrate `tests/test_group_device.py` to `GroupConfig`/`ProviderDeps`; remove legacy `GroupLEDClient` init path; 47-test config suite with 100% coverage on `device_config.py`
- [ ] Phase 5 (separate PR, optional) — Pydantic v2 discriminated union in `api/schemas/devices.py`; scope frontend POST/PATCH payloads by `device_type`
## Expand device support (Phase 1: open protocols)
Branch: `feat/expand-device-support`.
Goal: maximize the universe of LED controllers LedGrab can drive by adding aggregator + open-protocol providers in roughly-this order. Each driver follows the established `LEDDeviceProvider` + `*Config` + tests pattern.
### Phase 1.1 — Standalone DDP target
The DDP packet layer already exists (`ddp_client.py`) — currently only used inside `WLEDClient`. Promote it to a first-class device type so any DDP-speaking controller (Pixelblaze, ESPixelStick, xLights/Falcon endpoints, generic DDP receivers) can be driven directly without WLED firmware in the path.
- [ ] `DDPConfig` dataclass in `device_config.py` (port, destination_id, color_order)
- [ ] `DDPLEDClient` in `core/devices/ddp_led_client.py` — `LEDClient` wrapper around the existing `DDPClient` transport with `supports_fast_send=True` for the hot loop
- [ ] `DDPDeviceProvider` in `core/devices/ddp_provider.py` — discovery is a no-op (DDP has no native discovery; UI accepts manual IP), validate_device pings the host, capabilities = `{"manual_led_count", "health_check"}`
- [ ] Register provider in `led_client._register_builtin_providers`
- [ ] Add `ddp` branch to `Device.to_config()` in `device_store.py` + storage fields for DDP-specific options
- [ ] API schemas: extend device schema to accept DDP fields
- [ ] Unit tests for client (packet construction is already tested under `test_ddp_client.py`; new tests cover the LEDClient wrapper, provider validate/health, config round-trip)
- [ ] Frontend: add DDP to the device-type picker + edit form (spawned to a `frontend-design` subagent)
- [ ] Locale strings (en/ru/zh)
### Phase 1.2 — Yeelight LAN
Xiaomi/Yeelight bulbs, port 55443 TCP JSON. Use `python-yeelight` or direct protocol.
- [ ] `YeelightConfig` + `YeelightLEDClient` + `YeelightDeviceProvider`
- [ ] mDNS / SSDP discovery (Yeelight uses SSDP-like UDP multicast `239.255.255.250:1982`)
- [ ] Single-pixel output: map strip → averaged RGB → bulb color
- [ ] Frontend additions + locales
### Phase 1.3 — WiZ Connected
Philips' UDP-local budget tier. Port 38899 JSON UDP.
- [ ] Reuse the discovery scaffolding from Yeelight (UDP broadcast pattern)
- [ ] `WiZConfig` + `WiZLEDClient` + `WiZDeviceProvider`
- [ ] Frontend additions + locales
### Phase 2 — Unified discovery + pairing UX layer
After phase 1 the codebase will have 3 fresh examples of "ping the LAN, listen for replies, present a list". Factor that out into a generic discovery scaffold + a "first-run pairing" UX component before adding Tuya/Govee/etc., which each need a one-time pairing dance.
- [ ] Generic `NetworkDiscoveryService` that fan-outs mDNS + SSDP + UDP-broadcast probes in parallel
- [ ] Unified "scan network for devices" UI affordance instead of per-type buttons
- [ ] Reusable "pair device" component (consent button, countdown, retry)
### Phase 3 — Big aggregator unlocks
- [ ] ESPHome native API (`aioesphomeapi`)
- [ ] Tuya Local (`tinytuya`) — biggest single market unlock; needs the pairing UX from Phase 2
- [ ] Matter over IP (forward-looking)
- [ ] Hyperion JSON downstream
### Phase 4 — Major consumer brands
- [ ] Govee LAN API (2023+)
- [ ] Twinkly
- [ ] LIFX LAN
- [ ] Nanoleaf OpenAPI
- [ ] Mi-Light / MiBoxer UDP gateway
### Phase 5 — Open pixel protocols (cheap completionism)
- [ ] OPC (Open Pixel Control)
- [ ] TPM2.net
### Phase 6 — PC gaming RGB completion
- [ ] Corsair iCUE SDK
- [ ] Logitech LIGHTSYNC
- [ ] ASUS Aura SDK
### Phase 7 — Proprietary USB HID ambient kits
- [ ] Generic HID-ambient framework + VID/PID registry
- [ ] First reverse-engineered target (probably Govee Immersion / DreamView)