# Overview
Pixels String turns an ESP32 dev board and a NeoPixel-compatible LED strip into a self-contained ambient lighting system. It runs fully on-device, with no cloud services or phone apps required.
The device hosts its own web dashboard directly from the ESP32, so any browser on your network can control every effect, variation, color, and brightness. A physical button gives you the same control without a browser. A complete REST API lets you integrate the strip into scripts, home automation, or other projects.
All settings are persisted in NVS (non-volatile storage), so your LED count, color order, brightness, and active effect survive reboots and power loss. WiFi is monitored continuously and reconnects automatically.
What's in the box
- 24 built-in LED effects, each with 5 variations (120 total looks)
- Embedded web dashboard with animated per-effect icons and live API feedback
- Physical button: single-click, double-click, and long-press controls
- Full REST API for programmatic control over HTTP
- MCP server (JSON-RPC 2.0) so AI assistants can run animations and set colors
- Apple Home (HomeKit) support via HomeSpan: a Lightbulb plus one Switch per effect
- Static IP and mDNS hostname for reliable, predictable addressing
- Persistent LED count, color order, and brightness via NVS
- WiFi auto-reconnect with 10-second retry loop
Features & Capabilities
Everything the controller does out of the box.
24 LED Effects
Fireflies, Rainbow Swipe, Aurora, Comet, Chasing Dots, Cylon, Dual Comet, Sparkle Sweep, Police, Plasma, Rainbow Gradient, Pulse Wave, Single Runner, Audio Visualizer, Heartbeat, Twinkle, Fire Flicker, Bouncing Balls, Lightning Storm, Kaleidoscope, Colliding Fill, Paint Splat, Snake, and Glitter.
5 Variations Per Effect
Every effect ships with 5 distinct variations. Tap the same button (or click the same card) to cycle through them, from firefly counts to color palettes to speeds.
Web Dashboard
A full HTML/CSS/JS dashboard is served straight from the ESP32. Animated icons, glowing active-effect highlight, toast notifications, and instant API feedback.
Physical Button
Single-click cycles effects, double-click cycles variations, and a 2-second long-press toggles power. Debounced with internal pull-up on GPIO 9.
REST API
9 HTTP endpoints for effects, variations, power, brightness, pixel patterns, and configuration. JSON responses make scripting and automation trivial.
MCP for AI Assistants
Speaks the Model Context Protocol over HTTP (POST /mcp, JSON-RPC 2.0), so Claude Desktop, MCP Inspector, and other assistants can run animations and set colors directly.
Apple Home (HomeKit)
Built on the HomeSpan library, the strip appears in Apple Home as a Lightbulb with on/off, brightness, and color wheel control - plus a Switch for each of the 24 effects. Control it from the Home app or with Siri.
Custom Range Control
Paint exact blocks of LEDs with the Custom Pixels modal - dual-slider ranges, a live strip preview, and up to 20 color segments via the custom pattern.
Saved Presets
Name and save your current custom configuration in the browser, then reapply it with a single click from a dedicated presets panel on the dashboard - complete with a color-dot preview.
Static IP & mDNS
Configurable static IP with gateway and subnet, plus an mDNS hostname (default pixels-string.local) so you never have to hunt for the device.
Persistent Settings
LED count, color order, and brightness are stored in NVS and survive reboots. The active effect and power state persist in memory during a session.
Auto-Reconnect
WiFi is monitored continuously and reconnects automatically with a 10-second retry loop, keeping the dashboard and API reachable.
# Effects & Variations
All 24 effects and what each variation does. Set any effect with /api/effect?name=... or by index.
| # | Effect | Variations |
|---|---|---|
| 0 | FIREFLIES | 1 / 2 / 3 / 5 / 8 fireflies (speed scales with count) |
| 1 | RAINBOW_SWIPE | Forward / Reverse / Fast / Slow / Medium+Forward |
| 2 | AURORA | Blue-green / Warm / Cool violet / Pink-green / Rainbow-tinted |
| 3 | COMET | Position-hue / Fixed hue / Time-hue / White / Rainbow tail |
| 4 | CHASING_DOTS | 2 / 3 / 4 / 5 / 6 chasers |
| 5 | CYLON | Hue shift at ends / Hue shift+ / Time-hue / White / Red |
| 6 | DUAL_COMET | White flash / Rainbow flash / Color-ring flash / Yellow / Red flash |
| 7 | SPARKLE_SWEEP | Density and width combos (5 variations) |
| 8 | POLICE | Alternating / Split halves / Three-segment / All flash / Sweeping |
| 9 | PLASMA | 2 / 3 / 4 / 5 / 5 blobs (wider) |
| 10 | RAINBOW_GRADIENT | Window size 6 / 10 / 14 / 18 / 22 |
| 11 | PULSE_WAVE | Sigma & speed combinations (5 variations) |
| 12 | SINGLE_RUNNER | Random hue / Rainbow-position / Time-hue / White / Tail trail |
| 13 | AUDIO_VISUALIZER | Simulated spectrum analyzers with varying band counts & styles |
| 14 | HEARTBEAT | Mac breathing / Lub-dub center / Alternating side / Travelling / Dual pulse |
| 16 | TWINKLE | White slow / Golden / Multicolor / Dense fast / Cold blue + supernovae |
| 17 | FIRE_FLICKER | Candle / Campfire / Inferno / Blue flame / Rainbow fire |
| 18 | BOUNCING_BALLS | 1 slow ball / 2 balls / 3 sized balls / 4 white with trails / 5 rainbow elastic |
| 19 | LIGHTNING_STORM | Full-strip flash / Random segments / Rolling wave / Violet afterglow / Frequent storm |
| 20 | KALEIDOSCOPE | 2-fold mirror / 4-fold twinkle / 8-fold plasma / Rotating washes / Laser-show centre |
| 21 | COLLIDING_FILL | Classic ends-inward / Colour swap / Teleport fill / Random side / Center-out fill |
| 22 | PAINT_SPLAT | Classic mix / Splash burst / Lava lamp / Rainbow bands / Galactic stars |
| 23 | SNAKE | Classic green / Speed boost / Rainbow / Obstacle course / Poison food |
| 24 | GLITTER | Transition speeds, slow→rapid (5 variations); 30-60% of LEDs lit, each easing between palette colours |
0-4. Index 15 is the internal STATIC_PIXEL mode used by /api/pixels/set; it has a single variation and is intentionally omitted here (and from the button cycle). The public effects occupy indexes 0-14 and 16-24.# Quick Start
From a fresh clone to a glowing strip - requirements, wiring, setup, and configuration.
# Hardware Requirements
You need an ESP32, an LED strip, and a power supply. Everything else is optional.
- ESP32 development board
- NeoPixel-compatible LED strip (WS2812B, SK6812, etc.)
- 3.3V-5V level shifter (recommended for longer strips)
- Physical button (optional, GPIO 9 with internal pull-up)
- 5V power supply (sized for your LED count)
Pin Connections
| Component | GPIO Pin | Notes |
|---|---|---|
| LED Strip Data | GPIO 2 | Default PIN in the sketch |
| Button (optional) | GPIO 9 | Wired as INPUT_PULLUP to GND |
MAX_LEDS = 300 in globals.h. Adjust it if your hardware supports fewer or more, within ESP32 memory constraints.# Getting Started
From a fresh clone to a glowing strip in five steps.
1. Clone and configure
git clone https://github.com/shajanjp/pixels-string.git cd pixels-string
2. WiFi and network config
Copy the example config and fill in your network details:
cp config.example.h config.h
Then edit config.h:
#define WIFI_SSID "your-ssid" #define WIFI_PASSWORD "your-password" #define STATIC_IP IPAddress(192, 168, 1, 55) #define GATEWAY IPAddress(192, 168, 1, 1) #define SUBNET IPAddress(255, 255, 255, 0) #define MDNS_HOSTNAME "pixels-string"
config.h is gitignored, so your credentials never end up in version control.3. (Optional) Modify the dashboard
The dashboard is embedded in the firmware. Edit dashboard.html, then regenerate the embedded header:
npm install node html-to-header.js
This minifies the HTML, CSS, and JS and produces dashboard_html.h, which is compiled into the firmware.
4. Upload to your ESP32
Open pixels-string.ino in the Arduino IDE or VS Code with PlatformIO, select your ESP32 board, and upload. Required libraries:
Adafruit_NeoPixel- LED strip driverHomeSpan- Apple HomeKit support (Lightbulb + effect switches)WiFi,ESPmDNS,Preferences- included with the ESP32 core
Sketch too big, switch the partition scheme to No OTA (2MB APP/2MB SPIFFS) under Tools > Partition Scheme. This is required, and the first upload after switching will re-partition the chip.5. Find your device
- Check your router's DHCP client list for the static IP you configured
- Or use mDNS:
http://pixels-string.local
# Configuration
What lives in config.h and what can be changed at runtime.
Network settings (config.h)
| Setting | Default | Description |
|---|---|---|
WIFI_SSID | "your-ssid" | WiFi network name |
WIFI_PASSWORD | "your-password" | WiFi password |
STATIC_IP | 192.168.1.55 | Static IP assigned to the device |
GATEWAY | 192.168.1.1 | Network gateway |
SUBNET | 255.255.255.0 | Network subnet mask |
MDNS_HOSTNAME | "pixels-string" | mDNS name, resolves to pixels-string.local |
Runtime settings (persisted in NVS)
These are configured via the API and survive reboots:
| Setting | Range | Description |
|---|---|---|
ledCount | 1 - 300 | Number of LEDs on the strip |
colorOrder | RGB / RBG / GRB / GBR / BRG / BGR | NeoPixel color order matching your strip's wiring |
brightness | 0 - 255 | Global brightness applied to every effect |
/config?colorOrder=... - it applies immediately and is saved to NVS.# Control & API
The web dashboard, the physical button, the REST API, and MCP for AI assistants.
# Using the Device
Control the strip two ways: the web dashboard or the physical button.
Web dashboard
Open the device's root URL (http://pixels-string.local or your static IP) in any browser. The ESP32 serves a full-featured dashboard where every effect is a card with an animated icon:
- Click a card to activate that effect
- Click the same card again to cycle through its 5 variations - a toast notification shows the variation number
- The active effect is highlighted with a glowing border
The dashboard also exposes power and brightness controls plus a Custom Pixels editor with solid, striped, gradient, and custom range patterns, all with live API feedback.
Saved Presets
A Saved Presets panel lets you keep and reuse your custom looks. From the Custom Pixels modal, give your current configuration a name and save it - the preset appears as a button on the dashboard's home page, painted with a 7-dot preview that mirrors your pattern:
- Save - name and save the current pattern, colors, percentage, and custom ranges from the Custom Pixels modal
- Reapply with one click - clicking a preset button sends the same
/api/pixels/setrequest as the modal's Apply button - Overwrite - saving a name that already exists replaces that preset
- Delete - a small × control appears on each preset button on hover
- Per-browser - presets are stored in the browser's
localStorage, so no state is saved on the device
Physical button controls
| Action | Result |
|---|---|
| Single click | Cycle to the next effect |
| Double click | Cycle variation (0 -> 1 -> 2 -> 3 -> 4 -> 0) |
| Long press (2s) | Toggle power on/off |
# REST API
All endpoints are served on port 80 and accept plain GET requests with query parameters. JSON endpoints return readable, unescaped JSON for easy parsing.
GET /help on the device for a plain-text listing of every available endpoint.Set the active effect by name or by numeric index.
Valid names
FIREFLIES, RAINBOW_SWIPE, AURORA, COMET, CHASING_DOTS, CYLON, DUAL_COMET, SPARKLE_SWEEP, POLICE, PLASMA, RAINBOW_GRADIENT, PULSE_WAVE, SINGLE_RUNNER, AUDIO_VISUALIZER, HEARTBEAT, TWINKLE, FIRE_FLICKER, BOUNCING_BALLS, LIGHTNING_STORM, KALEIDOSCOPE, COLLIDING_FILL, PAINT_SPLAT, SNAKE
GET /api/effect?name=AURORA GET /api/effect?index=2 # index 2 = Aurora
15 is the internal STATIC_PIXEL mode (used by /api/pixels/set). All other indexes 0-14 and 16-24 map to the public effects above.Set the variation of the current effect.
GET /api/variation?index=2
Turn the LEDs on or off.
GET /api/power?state=off
Set the global LED brightness. Applied to every effect and pattern, and saved to NVS so it persists across reboots. 0 turns the LEDs off, 255 is maximum. Read the current value from /api/info.
GET /api/brightness?value=128
Returns a JSON object with the current device state.
{
"effect": "AURORA",
"effectIndex": 2,
"variation": 2,
"variationMax": 4,
"power": "on",
"numEffects": 24,
"ledCount": 50,
"brightness": 255,
"colorOrder": "GRB"
}
Read or set the device configuration. Without parameters it returns the current config as JSON. Parameters apply immediately, are saved to NVS, and persist across reboots. Either or both parameters can be combined in one request.
colorOrder
optional
RGB, RBG, GRB, GBR, BRG, or BGR (case-insensitive), or a numeric index 0-5 (e.g. 5 = BGR; 2 = GRB)
ledCount
optional
Number of LEDs, 1-300
GET /config # read current config GET /config?colorOrder=BGR # set color order GET /config?ledCount=100 # set LED count GET /config?colorOrder=GRB&ledCount=120 # both at once
Every request to /config returns the full configuration as JSON, so the response doubles as confirmation:
{
"colorOrder": "GRB",
"colorOrderValue": 2,
"ledCount": 120,
"brightness": 255,
"effect": "AURORA",
"power": "on"
}
Set pixel colors with customizable patterns. All parameters are passed as query strings.
pattern
required
Lighting pattern: solid, striped, gradient, or custom.
color
required*
Hex color code(s) without #. Can be repeated for multiple colors. Required for solid/striped/gradient; not used by custom.
range
required*
START-END:COLOR, 1-indexed and inclusive. Can be repeated up to 20 times. Required for custom only.
brightness
optional
Sets global brightness 0-255, persisted and applied to all effects. Omit to keep the current global brightness.
percentage
optional
Applies the pattern to only the first N% of LEDs (0-100); the remaining LEDs are turned off. Omit or use 100 for the whole strip. Ignored by custom.
Color format
- 6-digit hex:
RRGGBB(e.g.FF0000for red) - 3-digit shorthand allowed:
F00=FF0000 - Case insensitive (
ff0000==FF0000) - Multiple colors supplied by repeating the
colorkey
Custom pattern (ranges)
Each range param paints a solid color over a contiguous block of LEDs, using 1-indexed inclusive bounds (START and END are both LED numbers, 1 to the LED count).
- Up to 20 ranges are accepted
- Where ranges overlap, the later range wins
- LEDs not covered by any range keep their previous value
GET /api/pixels/set?pattern=solid&color=FF0000 GET /api/pixels/set?pattern=striped&color=FF0000&color=00FF00&color=0000FF GET /api/pixels/set?pattern=gradient&color=FF0000&color=0000FF&brightness=128 GET /api/pixels/set?pattern=gradient&color=FF0000&color=0000FF&percentage=50 GET /api/pixels/set?pattern=custom&range=1-10:FF0000&range=11-20:0000FF&range=21-25:00FF00
Plain-text listing of all available endpoints. Handy when working on a headless device.
# MCP (Model Context Protocol)
The device speaks the Model Context Protocol over HTTP, so AI assistants can control the LEDs directly.
The ESP32 hosts a minimal MCP server at POST /mcp (JSON-RPC 2.0). Register it as a remote MCP server in any MCP client - Claude Desktop, MCP Inspector, and others - using the device URL:
{
"mcpServers": {
"pixels-string": {
"url": "http://192.168.1.55/mcp"
}
}
}
Exposed tools
Two tools are exposed, both backed by the existing effect, pattern, and brightness code:
| Tool | What it does |
|---|---|
apply_animation | Switch the strip to a built-in animation by name (case-insensitive, same names as /api/effect) |
set_color | Light the strip in a custom solid color at a given brightness (reuses the solid-pattern + global brightness pipeline) |
apply_animation
| Argument | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Animation name, e.g. FIREFLIES, AURORA, SNAKE |
set_color
| Argument | Type | Required | Description |
|---|---|---|---|
color | string | Yes | Hex color, 3 or 6 digits, case-insensitive (FF0000 or f00) |
brightness | integer | No | 0-255; keeps the current brightness when omitted. Persisted to NVS like /api/brightness. |
# Apple Home (HomeKit)
Thanks to the HomeSpan library, the ESP32 also exposes the LED strip to Apple Home as a Lightbulb accessory. Once paired, you can control it from the Home app, Control Center, and with Siri.
What HomeKit controls
The device exposes two accessories.
| Accessory | Apple Home control | Device action |
|---|---|---|
| LED Strip (Lightbulb) | On / Off | Power on/off |
| Brightness slider | Global brightness (0-100%) | |
| Color wheel | Solid color (switches to static color mode) | |
| LED Effects (Switches) | Each effect is a Switch | Tap one to run that animation |
AURORA switch runs the aurora effect) and powers the strip on. Only one effect switch stays ON at a time. The LED Strip lightbulb represents solid-color mode: using it (on/off/brightness/color) puts the strip back into solid color.Because HomeKit has no native “effect/mode” menu, each of the 24 animations is exposed as its own switch. You can trim this list in homekit.h if you only want a few favorites (delete the corresponding new EffectSwitch(...) lines).
Wiring & networking
HomeSpan takes over WiFi connection and mDNS (it must own the connection to start its HomeKit server). It keeps your static IP from config.h, and its HAP server runs on port 8080 so it does not clash with the REST API on port 80.
Required partition scheme (IMPORTANT - do this first)
HomeSpan makes the firmware ~1.58 MB, which is too large for the default partition scheme (1.3 MB app slot). If you upload without changing it you will get Sketch too big / text section exceeds available space in board. To fix it, give the app a 2 MB slot:
- In the Arduino IDE: Tools > Partition Scheme > No OTA (2MB APP/2MB SPIFFS)
- With
arduino-cli:arduino-cli compile --fqbn "esp32:esp32:esp32:PartitionScheme=no_ota" .
Pairing with Apple Home
- Flash the sketch and power the ESP32.
- Open the Serial Monitor (115200 baud) and watch HomeSpan start up. It prints the device’s pairing QR and setup code.
- On your iPhone/iPad, open the Home app >
+> Add Accessory, and scan the QR (or tap More options and enter the setup code). - The device appears as LED Strip.
homekit.h (HOMESPAN_SETUP_CODE, default 46637726). Change it to any 8 digits and re-flash, or change it later from the HomeSpan CLI (type S in the serial monitor). To un-pair or reset pairing data, use the HomeSpan CLI (U in the serial monitor) or hold the HomeSpan control action.# Project Structure
A quick map of the repository.
pixels-string/
├── pixels-string.ino # Main sketch: globals, utilities, button, WiFi, setup/loop
├── globals.h # Shared declarations: Effect enum, externs, prototypes
├── effects.h # LED animations, static pixel patterns, effect lifecycle
├── homekit.h # Apple HomeKit (HomeSpan) Lightbulb + effect switches
├── mcp.h # Minimal MCP server (JSON-RPC 2.0 at POST /mcp)
├── rest_api.h # REST endpoints for the web dashboard (/api/*, /config, /help)
├── config.example.h # WiFi & network config template
├── config.h # Your actual config (gitignored)
├── dashboard.html # Web dashboard source (HTML/CSS/JS)
├── dashboard_html.h # Auto-generated: minified HTML embedded as a C string
├── html-to-header.js # Node.js script to minify HTML & generate header
├── package.json # Node dependencies (html-minifier)
├── pixels-string-dashboard-screenshot.jpg
└── site/ # Project website
├── index.html # Landing page
├── favicon.png # Browser icon
├── apple-touch-icon.png # iOS home screen icon
├── og-image.png # Social sharing image
└── site.webmanifest # Web app manifest
pixels-string.ino includes the module headers and defines the shared globals they reference via globals.h.