How do I drive a WS2812 strip with a rainbow effect?
Configure the strip on the Pico's PIO, animate a slow hue shift via cron
WS2812 (NeoPixel) strips are addressable RGB LEDs. On the Pico, DeviceSDK drives them through the PIO peripheral; on ESP32 boards, the firmware uses the led_strip component. The script-side API is the same.
Wiring
| WS2812 | Pico W |
|---|---|
| VCC | 5V (VBUS) for short strips, external supply for >8 LEDs |
| GND | GND (shared with Pico GND if external supply) |
| DIN | GP2 |
Strips with more than ~8 LEDs draw too much current to power from VBUS — give them their own 5V supply and tie grounds together.
devicesdk.ts
import { defineConfig } from "@devicesdk/cli";
export default defineConfig({
projectId: "rainbow-strip",
devices: {
strip: {
className: "Rainbow",
main: "./src/devices/strip.ts",
deviceType: "pico-w",
wifi: { ssid: "YOUR_WIFI_SSID", password: "YOUR_WIFI_PASSWORD" },
},
},
});
src/devices/strip.ts
import { DeviceEntrypoint } from "@devicesdk/core";
const NUM_LEDS = 30;
const PIN = 2;
function hsvToRgb(h: number, s: number, v: number): [number, number, number] {
// h: 0..360, s/v: 0..1
const c = v * s;
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
const m = v - c;
let [r, g, b] = [0, 0, 0];
if (h < 60) [r, g, b] = [c, x, 0];
else if (h < 120) [r, g, b] = [x, c, 0];
else if (h < 180) [r, g, b] = [0, c, x];
else if (h < 240) [r, g, b] = [0, x, c];
else if (h < 300) [r, g, b] = [x, 0, c];
else [r, g, b] = [c, 0, x];
return [
Math.round((r + m) * 255),
Math.round((g + m) * 255),
Math.round((b + m) * 255),
];
}
export class Rainbow extends DeviceEntrypoint {
// Update once a second — the hue offset advances each tick, the rainbow rolls.
crons = { tick: "* * * * *" };
async onDeviceConnect() {
await this.env.DEVICE.pioWs2812Configure(PIN, NUM_LEDS);
// Render once on boot so the strip lights even before the first cron.
await this.render(0);
}
async onCron() {
const offset = (await this.env.DEVICE.kv.get<number>("offset")) ?? 0;
const next = (offset + 30) % 360; // shift 30° per tick
await this.env.DEVICE.kv.put("offset", next);
await this.render(next);
}
private async render(offset: number) {
const pixels: [number, number, number][] = [];
for (let i = 0; i < NUM_LEDS; i++) {
const hue = (offset + (i * 360) / NUM_LEDS) % 360;
// Cap brightness at 0.2 so the strip doesn't blast eyes / draw too much current.
pixels.push(hsvToRgb(hue, 1, 0.2));
}
await this.env.DEVICE.pioWs2812Update(pixels);
}
}
What this demonstrates
pioWs2812Configureonce per connect,pioWs2812Updateper frame.- A small HSV→RGB helper — agents that hallucinate
hsv()etc. instead should be reminded that the script runs in a sandboxed runtime, no Node, no browser. crons = { tick: "* * * * *" }for a once-per-minute animation. Faster animations need to be driven by the firmware’s PWM/PIO state machine; the script can only update at cron tick rates.- Capping the brightness via the
vchannel (here 0.2) keeps the current draw and the literal wattage manageable.
Going further
- Tie the rainbow speed to a dial or potentiometer read via ADC.
- Mirror state into a Home Assistant
lightentity — declare it underha.entitiesindevicesdk.ts.