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.