Nanoleaf¶
Control Nanoleaf light panels (Light Panels, Canvas, Shapes, Elements, Lines) over the local HTTP API. No cloud account required.
Pairing¶
Generate an auth token using the CLI nanoleaf pair command. Hold the power button on your Nanoleaf device until the LED starts flashing, then run:
ts-ha nanoleaf pair 192.168.1.60 # by IP address
ts-ha nanoleaf pair nanoleaf-panels.local # by mDNS hostname
Press Enter when prompted. The command prints an auth token — save it in your .env or config file.
Registering devices¶
Register Nanoleaf devices in a factory function passed to services.nanoleaf in your entry point:
import { createEngine, NanoleafService } from "@ts-ha/core";
const engine = createEngine({
automationsDir: "./automations",
services: {
nanoleaf: (http, logger) => {
const svc = new NanoleafService(http, logger);
svc.register("panels", {
host: "192.168.1.60", // IP, hostname, or .local name
token: "xxxxxxxxxxxxxxxxxxx", // from pairing
// port: 16021, // optional, defaults to 16021
});
return svc;
},
},
});
Bulk registration¶
Use registerMany() to register multiple devices at once:
svc.registerMany({
panels: { host: "192.168.1.60", token: "xxx" },
bedroom: { host: "192.168.1.61", token: "yyy", port: 16021 },
});
Available methods¶
| Method | Description |
|---|---|
turnOn(name) |
Turn the panels on |
turnOff(name) |
Turn the panels off |
toggle(name) |
Toggle power |
setBrightness(name, value, duration?) |
Set brightness 0–100 with optional transition in seconds |
setColor(name, hue, saturation) |
Set HSB color; hue 0–360, saturation 0–100 |
setColorTemp(name, kelvin) |
Set color temperature 1200–6500 K |
setState(name, state) |
Set multiple properties at once (on, brightness, hue, sat, ct, effect) |
getState(name) |
Get the full device state |
getEffects(name) |
List all available effects by name |
getCurrentEffect(name) |
Get the name of the currently active effect |
setEffect(name, effectName) |
Activate a named effect |
identify(name) |
Flash the panels for physical identification |
getPanelLayout(name) |
Get panel positions and IDs |
getDeviceInfo(name) |
Get device info (model, firmware version, serial number) |
Example: activate a scene when motion is detected¶
import type { NanoleafService } from "@ts-ha/core";
export default class NanoleafMotion extends Automation {
readonly name = "nanoleaf-motion";
readonly triggers: Trigger[] = [
{
type: "mqtt",
topic: "zigbee2mqtt/office_sensor",
filter: (p) => (p as OccupancyPayload).occupancy === true,
},
];
async execute(): Promise<void> {
const nanoleaf = this.services.get<NanoleafService>("nanoleaf");
if (!nanoleaf) return;
await nanoleaf.turnOn("panels");
await nanoleaf.setBrightness("panels", 80, 1);
await nanoleaf.setEffect("panels", "Northern Lights");
}
}
Example: set a warm evening scene¶
const nanoleaf = this.services.get<NanoleafService>("nanoleaf");
if (!nanoleaf) return;
await nanoleaf.setColorTemp("panels", 2700); // warm white
await nanoleaf.setBrightness("panels", 40, 3); // 40%, 3s transition
Web UI and HomeKit¶
Registered Nanoleaf devices are also exposed through the engine's shared,
source-neutral device layer (Engine.devices) — the same layer Zigbee,
Shelly, and state-toggle devices go through — so they appear in the web UI
and (when configured) HomeKit automatically, with no separate wiring beyond
registering the service.
- Addressing: each device is addressed as the qualified identifier
nanoleaf:<name>(the name passed toregister()/registerMany()), e.g.nanoleaf:panels— see API Reference for the qualified identifier format. - Liveness: Nanoleaf has no push transport. Every device is refreshed on
a fixed interval,
NANOLEAF_POLL_MS(default10000), rather than reacting to a stream. A single unreachable device is marked unreachable and logged without disrupting the poll for the others. - Effects as a capability: the effect list is described as an enumerated
writable property, not a bespoke Nanoleaf panel — the web UI's generic
device-detail view renders it as a plain select control populated from
getEffects(), refreshed whenever the device's own effect list changes (design.md D17). - Optimistic actuation: because Nanoleaf is polled rather than push-backed, the web UI's revert deadline for an unconfirmed command on a Nanoleaf device is derived from its reported refresh interval plus a margin — not a short fixed deadline as for a push-backed device — read from the device's own descriptor rather than hardcoded per source (design.md D21).
See HomeKit: Supported device types and Device Registry: Capability vocabulary for how the same mapped capability schema is projected onto HAP.