pibooth-wled-neopixel
A pibooth plugin that lights a NeoPixel/WS281x strip or ring using a separate ESP32 running WLED, connected to the Raspberry Pi over USB - not the SPI/PWM control this author's other plugin, pibooth-neopixel_spi, uses.
How it's different from pibooth-neopixel_spi
| pibooth-neopixel_spi | pibooth-wled-neopixel (this repo) | |
|---|---|---|
| What drives the pixels | The Raspberry Pi itself, over SPI | A separate ESP32 running WLED |
| How pibooth talks to it | Direct SPI writes from Python | JSON commands over a USB-serial link |
| Effects engine | Implemented in Python on the Pi | WLED's own effects engine on the ESP32 |
| Good for | LEDs wired straight into the Pi | A booth where the LED ring/strip already has (or you want) its own WLED controller, e.g. shared with other projects, or wired somewhere the Pi's SPI bus can't reach |
Both plugins implement the same pibooth state hooks, so pick whichever matches your wiring - don't enable both at once against the same strip.
How it works
The ESP32 runs completely stock, unmodified WLED firmware - no custom sketch
needed. WLED already exposes its JSON API on the USB serial port it uses for
programming/debugging, in exactly the same form as its HTTP /json/state API
(see the WLED serial interface docs and
JSON API docs). This plugin just opens
that serial port from the Pi and writes small JSON objects to it as pibooth moves
through its states - no WiFi required at all once WLED has been configured.
Hardware & WLED setup (one-off)
- Flash WLED onto the ESP32 and, over WiFi during initial setup, tell it how many LEDs you have, which GPIO pin drives the data line, and the colour order - all from the normal WLED web UI/app. Do not use GPIO1 or GPIO3 for the LED data pin: those are the ESP32's UART0 pins used by the USB-serial connection, and WLED disables serial JSON responses (GPIO1) or all serial input (GPIO3) if they're reassigned to LED output.
- Leave WLED's Sync Interfaces > Serial baud rate at its default (115200) unless
you have a reason to change it - just make sure
[WLED] baudrateinpibooth.cfgmatches whatever you pick. - Connect the ESP32 to the Raspberry Pi 5 with a USB cable (power + the serial link both ride over the same cable).
- On the Pi, find the serial device:
ls /dev/serial/by-id/
You'll normally see one entry, e.g. usb-Silicon_Labs_CP2102_...-if00-port0.
Using the /dev/serial/by-id/... path (rather than /dev/ttyUSB0) in
pibooth.cfg is strongly recommended, since /dev/ttyUSB0's number can shift if
another USB-serial device is ever plugged in.
5. Add your user to the dialout group so pibooth can open the port without root:
sudo usermod -aG dialout $USER
(log out/in, or reboot, for this to take effect)
Installation
pip install pibooth-wled-neopixel
This also installs pyserial. Install it into the same Python environment as
pibooth. pibooth discovers installed plugins automatically, so there is nothing to
add to [GENERAL] plugins. Run pibooth once to regenerate pibooth.cfg with the
new [WLED] section, or run pibooth --config to edit it from the UI.
To upgrade: pip install --upgrade pibooth-wled-neopixel.
Manual install (without pip)
Copy pibooth_wled_neopixel.py somewhere on the Pi, pip install pyserial, then
reference it from pibooth.cfg:
[GENERAL]
plugins = /home/pi/pibooth_wled_neopixel.py
(plugins accepts a comma-separated list.) Don't do both, or the plugin is loaded
twice.
Configuration reference
All options live under [WLED] in pibooth.cfg.
Connection & general
| Option | Default | Meaning |
|---|---|---|
port |
auto |
Serial port of the ESP32 (auto to detect it, or an explicit path such as /dev/serial/by-id/usb-...-if00) |
baudrate |
115200 |
Must match WLED's Sync Interfaces > Serial baud rate |
serial_timeout |
0.3 |
Serial read/write timeout, seconds |
led_count |
0 |
Number of LEDs on the strip/ring (0 = auto-detect from WLED at startup) |
segment_id |
0 |
WLED segment ID used for solid colours and the countdown's lit pixels |
countdown_bg_segment_id |
1 |
WLED segment ID used for the countdown's background pixels (must be free - see caveats below) |
brightness |
180 |
Master brightness sent once at startup, 0-255 |
transition_ms |
400 |
Crossfade time for state changes, ms (the capture stage is always instant) |
Live preview countdown
While pibooth is in its preview/"get ready" state, the plugin can animate a bar
that shrinks (or grows) across the strip in real time, in sync with pibooth's own
[WINDOW] preview_delay setting - no separate duration to configure.
| Option | Default | Meaning |
|---|---|---|
preview_countdown |
True |
Animate a live shrinking/growing bar during the preview delay |
preview_countdown_color |
0,220,255 |
Colour of the remaining-time part of the bar |
preview_countdown_off_color |
0,20,40 |
Colour of the elapsed-time part of the bar |
preview_countdown_direction |
down |
down shrinks as time runs out, up grows |
preview_countdown_interval_ms |
120 |
How often the bar is refreshed over serial, ms |
Per-stage colour & effect
Each pibooth state below has four options: <stage>_color (R,G,B), <stage>_fx
(a WLED effect, see below), <stage>_sx (effect speed, 0-255) and <stage>_ix
(effect intensity, 0-255) - e.g. wait_color, wait_fx, wait_sx, wait_ix.
Setting a stage's fx to -1 leaves the LEDs untouched when pibooth enters that
state.
<stage>_fx accepts either a raw WLED effect ID number (in which case that
stage's own _sx/_ix are used exactly as set) or one of the named patterns
in the table below, ported over from this author's original
pibooth-neopixel_spi plugin -
in which case _sx/_ix are set automatically to sensible values for that
pattern and the stage's own _sx/_ix options are ignored. A name that isn't
recognised (typo, etc.) logs a warning and falls back to Solid, so a bad config
value can never silently misbehave.
| Stage | pibooth hook | Default colour | Default fx | When it fires |
|---|---|---|---|---|
wait |
state_wait_enter |
40,20,0 | breathe |
Idle / attract screen |
choose |
state_choose_enter |
0,90,200 | solid |
Choosing the number of photos |
chosen |
state_chosen_enter |
0,180,120 | solid |
Layout chosen, about to start |
preview |
state_preview_enter |
0,40,90 | solid |
Preview / get ready - this is the countdown's background colour; preview_countdown_color/preview_countdown_off_color above take over as soon as the countdown starts |
capture |
state_capture_enter |
255,255,255 | solid |
Applied instantly (no crossfade), so it's already at full brightness before the first shot of a multi-photo sequence and stays on for every shot |
processing |
state_processing_enter |
110,0,200 | breathe |
Processing the photo(s) |
print |
state_print_enter |
0,200,90 | solid |
Print choice |
finish |
state_finish_enter |
255,255,255 | colorloop |
Thank you / finished |
failsafe |
state_failsafe_enter |
255,0,0 | blink |
Something went wrong |
Named patterns
These are the pattern names accepted by any <stage>_fx option, each mapped onto
a native WLED effect (with a matching speed/intensity chosen to feel similar to
this plugin's SPI-driven predecessor). Aliases are alternate spellings that map
to the same effect - both work, they're just there so config files written for
the old plugin's pattern names still make sense:
| Pattern name | Alias | WLED effect ID | WLED's current display name | sx | ix |
|---|---|---|---|---|---|
solid |
0 | Solid | 0 | 0 | |
blink |
1 | Blink | 120 | 128 | |
breathe |
pulse |
2 | Breathe | 60 | 128 |
wipe |
color_wipe |
3 | Wipe | 120 | 128 |
colorloop |
8 | Colorloop | 140 | 200 | |
rainbow |
9 | Rainbow | 140 | 200 | |
theater_chase |
13 | Theater | 150 | 128 | |
twinkle |
17 | Twinkle | 120 | 128 | |
sparkle |
20 | Sparkle | 180 | 40 | |
chase_rainbow |
chase_multi |
30 | Chase Rainbow | 150 | 128 |
comet |
41 | Lighthouse (see note below) | 180 | 100 | |
fireworks |
42 | Fireworks | 150 | 128 | |
fire |
45 | Fire Flicker | 150 | 128 | |
gradient |
46 | Gradient | 60 | 128 | |
ocean |
101 | Pacifica | 80 | 128 |
Note on
comet: WLED has renamed the display name of this effect to "Lighthouse" in current versions of the app/web UI - internally it's still the same effect (FX_MODE_COMET, ID 41) this plugin'scometpattern name refers to. If you go looking for "Comet" in your WLED effects list and can't find it, this is why - look for "Lighthouse" instead. Effect IDs themselves are stable across WLED versions (WLED only ever appends new effects, it never renumbers or reuses existing IDs), but display names occasionally change like this, so if a pattern doesn't look like you expect, verify by ID rather than by name.
Not ported: the original plugin's attract_sequence feature - cycling
through several different patterns with individual per-entry durations while
idle - is a distinct feature (pattern sequencing, on top of picking a single
pattern per stage) and hasn't been carried over here. Let the author know if
you'd like it added.
Verifying an effect ID/name for yourself
To see WLED's current effect list for your own device: open its web UI, go to
the Effects tab, and hover over any effect to see its ID; or, while still on
WiFi, fetch http://<esp32-ip>/json/eff, which returns the full list as a JSON
array indexed by effect ID (e.g. array index 41 is whatever ID 41 currently
displays as).
Each <stage>_color value is a comma-separated R,G,B triple by default, but you
can add a 4th value (R,G,B,W) to also drive the white channel on an RGBW strip
(e.g. SK6812) - capture_color = 255,255,255,255 for instance. This is always
logical R,G,B(,W), never the strip's physical wiring order. WLED's JSON API
documents col as an "RGB(W) color", and separately has its own per-output
Color Order setting (LED Preferences page in the WLED UI/app, e.g. RGB, GRB,
BRG - most WS2812B strips are actually wired GRB even though people call them "RGB
strips") which WLED applies internally right before it writes bytes to the strip.
That remapping is entirely WLED's job - this plugin (like the WLED app, HTTP
calls, MQTT etc.) always sends plain logical RGB(W) and never needs to know or
compensate for how your strip is physically wired. If red and green ever look
swapped, fix the Color Order setting on the ESP32 in WLED itself, not the
colours in pibooth.cfg.
The shipped defaults above only use the pattern names solid, breathe,
colorloop and blink (WLED effect IDs 0, 2, 8 and 1), all part of WLED's
original effect set - but as the pattern table above shows, far more are
available, and any WLED effect can also be used by setting <stage>_fx directly
to its numeric ID (with <stage>_sx/<stage>_ix to taste) instead of a pattern
name. WLED only ever appends new effects to the end of the list, so existing IDs
(and presets built on them) never change meaning - open your ESP32's WLED web UI,
hover over the effects list to see each one's ID, and set the option to that
number.
Caveats
- The countdown uses two WLED segments. While a preview countdown is running,
the plugin repeatedly resizes segment
segment_id(the lit portion) andcountdown_bg_segment_id(the rest of the strip). If you've already set up other segments on the device for something else, pick free segment IDs for both options. Because these are edited live from outside the WLED app, the WLED app/web UI's own segment list may look momentarily out of sync while a countdown is running or just after one finishes - this is cosmetic and doesn't affect pibooth's control of the strip. led_countauto-detection needs a working connection at pibooth startup. If the ESP32 isn't plugged in yet, or detection otherwise fails, the plugin logs a warning and disables the live countdown (falling back to just the staticpreview_color) until you setled_countby hand.- A flaky or unplugged ESP32 never crashes or interrupts the booth - every serial operation is guarded, logged, and retried in the background (throttled to once every 5 seconds) rather than raised.
Troubleshooting
- Nothing lights up / "no serial port found" in the log - check
ls /dev/serial/by-id/, set[WLED] portexplicitly to that path, and confirm your user is in thedialoutgroup. - Connects, but commands seem to do nothing - double check the ESP32's LED data
GPIO isn't set to pin 1 or 3 in WLED (see Hardware setup above), and that
baudratematches WLED's serial setting. - Countdown bar doesn't animate - it needs a known
led_count; check the log at startup for an auto-detect warning, or setled_countmanually. - Run with
pibooth -v(orpibooth-diag) for verbose logging - every command this plugin sends (and every connection problem) is logged under thewled:prefix.
Testing without hardware
Real behaviour needs a Raspberry Pi and an ESP32. To exercise the plugin without
hardware, replace serial.Serial with a fake that prints each line written to it,
then call the state_*_enter hooks in turn.
Releasing (maintainers)
- Bump
__version__inpibooth_wled_neopixel.pyand add aCHANGELOG.mdentry. - Check locally:
python -m pip install build twine && python -m build && twine check dist/* - Publish a GitHub release tagged
v<version>. Thepublish.ymlworkflow builds and uploads to PyPI using trusted publishing.
One-off setup: on pypi.org add a (pending) trusted publisher for this repo with
workflow publish.yml and environment pypi, and create a pypi environment in
the GitHub repo settings. To upload manually instead, run twine upload dist/*.
License
MIT - see LICENSE.
Metadata
Release files for pibooth-wled-neopixel 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pibooth_wled_neopixel-1.0.0.tar.gz | 17.9 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pibooth_wled_neopixel-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 35.9 kB
Release files / pibooth_wled_neopixel-1.0.0.tar.gz
| Download URL | pibooth_wled_neopixel-1.0.0.tar.gz |
|---|---|
| Size | 17.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
44f808446a674c7905150d69bae249d212075ec20b2103140e6961fce21c925f
|
|
BLAKE2b-256 checksum How to use checksums |
b7a4ddab887780be45d78b2aa30f405c93e7cf4526f10e95081af1230ecf7ebb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logRelease files / pibooth_wled_neopixel-1.0.0-py3-none-any.whl
| Download URL | pibooth_wled_neopixel-1.0.0-py3-none-any.whl |
|---|---|
| Size | 17.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c96a89ebbad337c2eddbf963165b2377154308cac238891f74679ff538113b0b
|
|
BLAKE2b-256 checksum How to use checksums |
bff68c14e01cd9db2e8a721d9a8ecda6157859c70b7fe4862ca6210c0ffaada1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logSecurity for pibooth-wled-neopixel 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for pibooth_wled_neopixel-1.0.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.