feat(devices): Yeelight LAN target type
Adds support for Xiaomi/Yeelight smart bulbs and lightstrips that speak the bulb-vendor's JSON-RPC protocol over TCP port 55443 with SSDP-style LAN discovery on 239.255.255.250:1982. Backend: - YeelightClient is a single-pixel adapter: it averages the incoming strip down to one RGB triple, packs it into the 24-bit color int the bulb expects, and pushes it via set_rgb with sudden+0ms effect. - Brightness folds into the RGB scaling on the wire so we burn one command per frame instead of two. - A configurable client-side rate gate (yeelight_min_interval_ms, default 500) keeps us under the bulb's ~1 cmd/sec cap. Frames that arrive inside the gate no-op without TX. Music mode (~60 Hz via reverse-TCP) is deferred -- the MVP caps at ~2 Hz and that's fine for a strip-to- single-pixel averaging device. - SSDP discovery scans 239.255.255.250:1982 with the bulb-specific ST: wifi_bulb header; replies are parsed into DiscoveredDevice entries. Multicast failures (no network, firewall) yield [] rather than raising -- discovery is best-effort. - Health check opens a TCP socket to the bulb and closes it. - YeelightConfig joins the typed config union; Device storage gains a yeelight_min_interval_ms field; full to_dict/from_dict/to_config wiring. - 34 unit tests cover URL parsing, RGB packing, strip averaging, rate limiting, SSDP response parsing, provider validate/discover/health, and Device.to_config round-trip. Frontend: - 'yeelight' in DEVICE_TYPE_KEYS (next to 'hue'), lightbulb icon (intentional family-grouping signal with Hue). - isYeelightDevice predicate + per-type field show/hide in create and settings modals. - Rate-limit number input (default 500 ms) in both modals with hint text explaining the trade-off. - Locale strings in en/ru/zh. - Drive-by: types.ts DeviceType union backfilled with 'ddp' and 'ble' for type-safety consistency. Yeelight bulbs are now reachable from the existing "Scan network" button -- no new discovery UI affordance was needed.
This commit is contained in:
@@ -666,28 +666,30 @@ 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
|
||||
### Phase 1.1 — Standalone DDP target ✅ shipped (commit `8f1140a`)
|
||||
|
||||
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)
|
||||
DDP packet layer (previously WLED-internal) promoted to a first-class device
|
||||
type. Pixelblaze, ESPixelStick, xLights/Falcon endpoints, and generic DDP
|
||||
receivers are now drivable directly without WLED in the path.
|
||||
|
||||
### Phase 1.2 — Yeelight LAN
|
||||
|
||||
Xiaomi/Yeelight bulbs, port 55443 TCP JSON. Use `python-yeelight` or direct protocol.
|
||||
Xiaomi/Yeelight bulbs, port 55443 TCP JSON. Direct protocol (no
|
||||
`python-yeelight` dependency — implementation is ~200 lines).
|
||||
|
||||
- [ ] `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
|
||||
- [x] `YeelightConfig` dataclass with `yeelight_min_interval_ms` rate limit
|
||||
- [x] `YeelightClient` in `core/devices/yeelight_client.py` — TCP JSON-RPC,
|
||||
averaging single-pixel adapter, client-side rate gate
|
||||
- [x] SSDP-style discovery (Yeelight's variant on `239.255.255.250:1982`)
|
||||
- [x] `YeelightDeviceProvider` with validate/health/discover
|
||||
- [x] Storage + API schemas + route handler wiring
|
||||
- [x] 34 unit tests (URL parsing, RGB packing, averaging, rate limit, SSDP
|
||||
parsing, provider validate/discover, Device.to_config round-trip)
|
||||
- [ ] Frontend: Yeelight in device-type picker + edit form (spawned to a
|
||||
`frontend-design` subagent)
|
||||
- [ ] Locale strings (en/ru/zh)
|
||||
- [ ] Music mode (~60 Hz updates via reverse-TCP) — follow-up, current
|
||||
MVP caps at ~2 Hz via the client-side rate gate
|
||||
|
||||
### Phase 1.3 — WiZ Connected
|
||||
|
||||
|
||||
Reference in New Issue
Block a user