Skip to content

About

WiFi connected ESP32 NeoPixel LED controller - 23 effects, 5 variations, buttons & web UI, REST API and MCP..

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Pixels String

A WiFi-enabled ESP32 LED pixel string controller with 24 stunning effects, 5 variations per effect, a physical button interface, and an embedded web dashboard for browser-based control. It also exposes a full REST API for programmatic control over HTTP and a built-in MCP (Model Context Protocol) server so AI assistants can drive the LEDs directly.

Pixels String Dashboard

<iframe width="315" height="560" src="https://www.youtube.com/embed/ldeJXF3cC08" title="YouTube video player" frameborder="0"></iframe>

Features

  • 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, Glitter
  • 5 Variations Per Effect - Cycle through variations with successive taps on the same button
  • Web Dashboard - Embedded HTML/CSS/JS dashboard served directly from the ESP32, with animated icons and API feedback
  • Saved Presets - Save and name your custom pixel configurations in the browser, then reapply them with one click
  • Physical Button - Single-click to cycle effects, double-click for variations, long-press for power on/off
  • Apple Home (HomeKit) - Exposes the strip as a HomeKit Lightbulb (on/off, brightness, color) via HomeSpan
  • REST API - Full programmatic control over HTTP
  • Static IP & mDNS - Reliable network configuration with custom hostname
  • Persistent LED Count - Number of LEDs is stored in NVS (non-volatile storage) and configurable via API
  • Auto-reconnect - Monitors WiFi and reconnects automatically

Hardware Requirements

  • 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
LED Strip Data GPIO 2
Button (optional) GPIO 9 (INPUT_PULLUP)

Getting Started

1. Clone and Configure

git clone https://github.com/shajanjp/pixels-string.git
cd pixels-string

2. WiFi & Network Config

Copy the example config and fill in your network details:

cp config.example.h config.h

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 - your credentials stay out of version control.

3. (Optional) Modify the Dashboard

Edit site/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 ESP32

Open pixels-string.ino in the Arduino IDE or VS Code with PlatformIO, select your ESP32 board, and upload.

5. Find Your Device

  • Check your router's DHCP client list for the static IP you configured
  • Or use mDNS: http://pixels-string.local

Web Dashboard

The ESP32 serves a full-featured dashboard at its root URL. Each effect is shown as 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 has a Saved Presets panel. From the Custom Pixels modal you can give your current configuration a name and save it to the browser. It appears as a button on the home page (with a 7-dot preview painted to mirror your pattern) and can be reapplied with a single click. Presets store the pattern, colors, percentage, and custom ranges, and saving a name that already exists overwrites it. Each preset button has a small delete (×) control that appears on hover. Because presets are stored in the browser's localStorage (key pixelsString.presets), they are per-browser and do not require the device to save anything.

Dashboard Screenshot

REST API

All endpoints are served on port 80.

Set Effect by Name

GET /api/effect?name=<name> - Set the active effect by name.

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, GLITTER

GET /api/effect?name=AURORA

Set Effect by Index

GET /api/effect?index=<0-24> - Set effect by numeric index.

GET /api/effect?index=2    → Aurora

Note: index 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 listed in the table below.

Set Variation

GET /api/variation?index=<0-4> - Set the variation of the current effect.

GET /api/variation?index=2

Set Power State

GET /api/power?state=<on|off> - Turn LEDs on or off.

GET /api/power?state=off

Set Brightness

GET /api/brightness?value=<0-255> - 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

Get Device State

GET /api/info - Returns a JSON object with the current state.

{
  "effect": "AURORA",
  "effectIndex": 2,
  "variation": 2,
  "variationMax": 4,
  "power": "on",
  "numEffects": 25,
  "ledCount": 50,
  "brightness": 255,
  "colorOrder": "GRB"
}

Get Device Configuration

GET /config - Returns the current device configuration as JSON: color order, LED count, brightness, active effect, and power state.

{
  "colorOrder": "GRB",
  "colorOrderValue": 2,
  "ledCount": 50,
  "brightness": 255,
  "effect": "AURORA",
  "power": "on"
}

Set Color Order

GET /config?colorOrder=<RGB|RBG|GRB|GBR|BRG|BGR> - Set the NeoPixel color order dynamically. This matches the physical wiring of the strip: some strips are RGB, others are GRB, BRG, BGR, etc. Accepts the color order by name (case-insensitive) or by a numeric index (0=RGB, 1=RBG, 2=GRB, 3=GBR, 4=BRG, 5=BGR, e.g. 5 = BGR). Applies immediately to all effects and patterns, is saved to NVS, and persists across reboots.

GET /config?colorOrder=BGR

Set LED Count

GET /config?ledCount=<1-300> - Set the number of LEDs. Applies immediately, is saved to NVS, and persists across reboots.

GET /config?ledCount=100

Both parameters can be combined in a single request, e.g. GET /config?colorOrder=GRB&ledCount=120. Any request to /config (with or without parameters) returns the full current configuration as JSON, so the response serves as confirmation:

{
  "colorOrder": "GRB",
  "colorOrderValue": 2,
  "ledCount": 120,
  "brightness": 255,
  "effect": "AURORA",
  "power": "on"
}

List Available Endpoints

GET /help - Plain-text listing of all available endpoints.

Set Pixel Colors

GET /api/pixels/set - Set pixel colors with customizable patterns. All parameters are passed as query strings.

Query Parameters

Parameter Required Description
pattern Yes Lighting pattern: solid, striped, gradient, or custom.
color Yes* Hex color code(s) without #. Can be repeated for multiple colors. Not used by custom.
range Yes* (Custom only) START-END:COLOR, 1-indexed and inclusive. Can be repeated up to 20 times.
brightness No Sets the global brightness 0-255. Persisted and applied to all effects (including this pattern). Omit to keep the current global brightness.
percentage No 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 is required for solid/striped/gradient; range is required for custom.

Color Format

  • 6-digit hex: RRGGBB (e.g. FF0000 for red).
  • Optionally allows 3-digit shorthand: F00 -> FF0000.
  • Case insensitive (ff0000 == FF0000).
  • Multiple colors are supplied by repeating the color key.

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.

Examples

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

MCP (Model Context Protocol)

The device speaks the Model Context Protocol over HTTP (POST /mcp, JSON-RPC 2.0), so AI assistants (Claude Desktop, MCP Inspector, etc.) can control the LEDs directly. Register it as a remote MCP server with the device URL:

{
  "mcpServers": {
    "pixels-string": {
      "url": "http://192.168.1.55/mcp"
    }
  }
}

Two tools are exposed, both backed by the existing effect/pattern/brightness code:

apply_animation

Switch the strip to a built-in animation by name (case-insensitive, same names as /api/effect).

Argument Type Required Description
name string Yes Animation name, e.g. FIREFLIES, AURORA, SNAKE

set_color

Light the strip in a custom solid color at the given brightness (reuses the solid-pattern + global brightness pipeline).

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.

No session or SSE stream is required: each request is answered with a JSON-RPC response and the connection closes, so the endpoint works with any MCP client that supports HTTP servers.

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

Turning on any effect switch runs that animation (e.g. the "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

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
Compilation error: text section exceeds available space in board

To fix it, change the Partition Scheme to 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" .

This is required. It also changes the flash layout, so the first upload after switching will re-partition the chip.

Pairing with Apple Home

  1. Flash the sketch and power the ESP32.
  2. Open the Serial Monitor (115200 baud) and watch HomeSpan start up. It prints the device's pairing QR and setup code.
  3. On your iPhone/iPad, open the Home app > + > Add Accessory, and scan the QR (or tap More options and enter the setup code).
  4. The device appears as LED Strip.

The setup code is set in 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.

Effects & Variations

# 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

Index 15 is the internal STATIC_PIXEL mode used by /api/pixels/set; it has a single variation and is intentionally omitted from the table above and from the button cycle. The public effects therefore occupy indexes 0-14 and 16-24.

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

Project Structure

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 accessory
├── 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.h        # Auto-generated: minified dashboard embedded as a C string
├── html-to-header.js       # Node.js script to minify site/dashboard.html into dashboard_html.h
├── package.json            # Node dependencies (html-minifier)
├── test-mcp.sh             # Curl-based test harness for the MCP endpoint
├── .mcp.json               # MCP server registration (for MCP clients)
├── .github/
│   └── workflows/static.yml  # Deploys the site/ folder to GitHub Pages
└── site/                   # Web dashboard source + project website
    ├── dashboard.html      # Web dashboard source (HTML/CSS/JS) → dashboard_html.h
    ├── index.html          # Project 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-dashboard-screenshot.jpg
    └── pixels-string-dashboard-mobile-screenshot.jpg

All sketch files are compiled as a single translation unit: pixels-string.ino includes the module headers and defines the shared globals they reference via globals.h. The dashboard source lives in site/dashboard.html and is minified into dashboard_html.h by html-to-header.js before compilation.

Maximum LEDs

The software limit is 300 LEDs (MAX_LEDS). Adjust this in globals.h if your hardware supports fewer or more (within ESP32 memory constraints).

License

ISC

About

WiFi connected ESP32 NeoPixel LED controller - 23 effects, 5 variations, buttons & web UI, REST API and MCP..

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages