426484adf8
Adds support for Nanoleaf controllers (Light Panels / Canvas / Shapes /
Lines / Elements) via the documented HTTP REST API on port 16021.
First concrete consumer of the pair-UX scaffold from commit 2f31680 --
the abstraction is no longer speculative.
Backend:
- NanoleafClient is a single-pixel HTTP adapter: averages the strip to
one RGB triple, converts to Nanoleaf's HSB scale (H 0-360 / S 0-100 /
B 0-100), and PUTs to /api/v1/<token>/state with duration:0 so
transitions are instant for ambilight. Brightness is clamped to >=1
because Nanoleaf rejects brightness=0.
- pair_nanoleaf(host) implements the two-step handshake: POST
/api/v1/new during the 30-second pairing window the controller opens
after the user holds the power button for 5 s.
200 -> {auth_token: "..."}
403 -> raises PairingNotReady ("Hold the power button...")
other / transport error -> RuntimeError wrapping the cause
- NanoleafDeviceProvider.pair_device returns {nanoleaf_token: ...}
forwarded by POST /api/v1/devices/pair to the frontend for inclusion
in the subsequent create payload.
- mDNS discovery via _nanoleafapi._tcp (and the v1 variant); failures
yield [] rather than raising.
- Health check probes /api/v1 without a token (401/403 still proves
the host is alive).
- NanoleafConfig has nanoleaf_token + nanoleaf_min_interval_ms
(default 100 ms = ~10 Hz; HTTP overhead caps practical max ~20 Hz).
- Auth token encrypted at rest via _enc/_dec, matching Hue / BLE-Govee.
- 42 unit tests cover URL parsing, RGB->HSB conversion, pairing
handshake (200 / 403 / 500 / missing-token / transport-error),
state mutations, brightness clamp, set_power / set_brightness /
set_color, connection lifecycle, provider validate / pair /
discover / capabilities, and Device.to_config round-trip including
the encrypted-token roundtrip via to_dict + from_dict.
Frontend:
- 'nanoleaf' in DEVICE_TYPE_KEYS (next to 'govee'), HEXAGON icon
(deliberate departure from the smart-bulb lightbulb family --
Nanoleaf is panels, not bulbs, and the brand identity is hexagonal).
- isNanoleafDevice predicate + per-type field show/hide.
- Pair flow integration: when the device type is Nanoleaf, the add-
device modal retitles its submit button to "Pair Device" and
intercepts the submit. handleAddDevice awaits
runPairingFlow({deviceType: 'nanoleaf', url}), merges result.fields
({nanoleaf_token}) into the create body, then POSTs. On
PairingCancelled the user stays on the modal silently.
- Settings modal exposes the rate-limit field and a read-only
"Paired" indicator reusing the pair-modal success badge. The token
itself is never rendered to the DOM and never sent on update --
re-pairing requires delete + re-add.
- Per-type pairing instructions in en/ru/zh
(device.nanoleaf.pair.instructions) that the scaffold's i18n lookup
resolves automatically.
- Bundle: +6.4 KiB (pairing-flow.ts was tree-shaken before this
commit; now both it and the Nanoleaf branches are baked in).
The pair-UX scaffold is now proven, not speculative. Tuya and Twinkly
can follow the same shape when their phases arrive.