Nanoleaf Panels
Local-LAN control for Nanoleaf Shapes, Canvas, Lines, Elements, and Light Panels via the documented local OpenAPI (the /api/v1/ interface) on port 16021. Pairing is done on the controller — hold the power button until the LED flashes — after which GEM stores a bearer token on the device and talks to it directly. No cloud account is required.
The whole panel set is controlled as one light: GEM commands the controller, not individual panels. Per-panel pixel control (touch effects, music sync, individual-tile animation) needs Nanoleaf's External Control (UDP streaming) extension, which this driver does not implement.
Prerequisites
- Firmware 1.5.0 or newer. Older controllers running 1.4 firmware do not expose the local OpenAPI. Update with the Nanoleaf mobile app first.
- Static DHCP reservation on the controller is strongly recommended. Nanoleaf does not advertise mDNS reliably after a Wi-Fi drop, and the controller never pushes state — GEM polls it.
- Same Layer 2 segment as the GEM server. The Nanoleaf mobile app discovers via mDNS; if you cannot reach the controller from a computer on the same VLAN, neither can GEM.
Setup
- On the Devices page choose System → Devices → Add Device and pick the driver
nanoleaf(display name Nanoleaf Panels). The Device Type auto-fills to light. - Enter the controller's LAN IP in Controller IP. Find it in the Nanoleaf mobile app under Layout → ⋯ → Controller Info, or in your router's DHCP table (the MAC starts with
00:55:DA). Save. - Put the controller into pairing mode: hold the power button for about 5–7 seconds until the LED flashes. You then have ~30 seconds.
- Run the
pair_tokencommand from the command tester. On success the returned token is written to the device'sauth_tokenattribute automatically and stored encrypted — you never type it in. - Reload the device from the Devices grid. The driver connects and starts its background state poll only on (re)connect, so after pairing the device will not come up Connected or begin reflecting state until you reload it.
- Run
get_infoto confirm the panel set is reachable and to see the firmware and panel layout. The light is now controllable:on,off,set_level,set_color,set_ct,set_hsv, andset_effectare all available.
The token survives reboots. You only re-run pair_token if the controller is factory-reset, downgraded, or the token is revoked in the Nanoleaf app — and re-pairing always requires the controller to be put back into pairing mode first.
Device attribute reference
| Attribute | Required | Default | Notes |
|---|---|---|---|
ip | yes | — | Controller LAN IP (Controller IP in the editor). |
port | no | 16021 | OpenAPI port. Always 16021 on consumer controllers; only change for proxied deployments. |
auth_token | populated by pairing | — | Bearer token issued by pair_token. Stored encrypted; do not edit by hand. |
status_interval | no | 10000 ms | State-poll cadence (minimum 2000 ms). The controller pushes nothing, so polling is the only state path. |
default_transition_ms | no | 500 ms | Default fade duration for set_level only (see Commands). set_ct / set_color / set_hsv send no fade. |
State attributes
On every poll — and on each command that changes state — the driver writes the controller's current state back onto the device. Build attribute triggers, UI tiles, and history on these names:
| Attribute | Type | Meaning |
|---|---|---|
power_state | string | on / off. |
level | int | Brightness 0–100. |
color_temp | int | Color temperature in Kelvin (last set_ct value, or the polled state). |
An attribute is written only when the polled value differs from what the device already holds, so triggers fire on real changes. Hue/saturation are set when you send a color but are not polled back as separate attributes.
Commands
| Command | Arguments | Notes |
|---|---|---|
on / off / toggle | — | Power. toggle turns the panels on unless the last cached state was already on. |
set_level | level (0–100); optional duration_ms | Brightness. A level above 0 also powers the panels on. The fade (duration_ms, or default_transition_ms when omitted) is quantized to tenths of a second; a duration that rounds to zero — roughly under 50 ms — is applied instantly. |
set_ct | ct (Kelvin, clamped 1200–6500) | Color temperature. Sent with no fade. |
set_color | color ("FF8800", "#FF8800", or {h,s,v}) | Solid color; the value/v channel also sets brightness. Sent with no fade. |
set_hsv | hue (0–360), saturation (0–100), value (0–100) | Lower-level color set. Sent with no fade. |
set_effect | effect (name) | Activates an installed effect by its exact name. List the names with get_effects. |
identify | — | Briefly flashes the panels white, then restores — useful for picking out one controller among several. |
pair_token | — | Issue a new auth token. The controller must be in pairing mode. |
get_info | — | Full controller payload (firmware, panel layout, capabilities). |
get_state | — | One-shot power / brightness / color-temperature read. |
get_effects | — | Installed effect names, returned as {effects: […]}. |
Only level is offered as a fillable field in the command tester; duration_ms and the object form of color are reachable from macros that pass custom arguments.
Known limitations
- Effects ("scenes") are user-installed. The list from
get_effectsis per-controller and differs between deployments. GEM does not enumerate effects at boot — runget_effectsfrom the command tester when you need the names. - External Control UDP streaming is out of scope. Per-panel pixel control requires switching the controller into
extControl v2mode and streaming UDP packets. Use the Nanoleaf mobile app for those features. - No persistent connection. Each command is a fresh HTTP request; the driver does not hold a socket open. The controller is slow to accept a new connection after a long idle, so expect 1–2 s of latency on the first request after a pause.
Troubleshooting
| Symptom | Likely cause / fix |
|---|---|
controller not in pairing mode (403 on pair_token) | Hold the power button 5–7 s until the LED flashes white, then retry within 30 seconds. |
unauthorized (401) on commands | The stored auth_token is no longer valid (factory reset, firmware downgrade, or revoked in the app). Re-run pair_token, then Reload the device. The device may still show Connected — the controller answered the request — even though the token is bad. |
| Device stays offline, or state never updates after pairing | The poll loop only starts on connect. Reload the device from the Devices grid after running pair_token. |
ECONNREFUSED / connection error | Controller is offline or its IP changed. Confirm the DHCP lease and reachability with ping. |
| Changes made in the Nanoleaf app take a few seconds to appear | The controller never pushes; GEM polls every status_interval. Lower it for faster reflection, but the controller may rate-limit polling below ~2 seconds. |
Related documentation
- Devices — the device editor, secure attributes, and Reload.
- Commands — the command tester used to run
pair_token,get_info, andget_effects. - Subsystems — organizing the device under the Lights subsystem.