Object-oriented addressable LED control for VEX V5 robots.
Built on PROS Β· Non-blocking tasks Β· Zone system Β· Frame-based playback
Built and maintained by Team 71881M "Mutex" (Capital Cougars) for VEX V5RC.
| Feature | Details | |
|---|---|---|
| π¨ | 14 Procedural Animations | Rainbow, Meteor, Fire, Scanner, Bounce, Pulse, Breathe, Strobe, Theater Chase, Wipe, Sparkle, Confetti, Cycle |
| ποΈ | Frame-Based Animation | C-array pixel sequences with loop, cycle, and timed playback |
| πΊοΈ | Named Zone System | Independent named sub-ranges per strip β overlap detection included |
| β‘ | Non-Blocking | Every animation runs in a dedicated PROS background task |
| π | HSV + RGB Math | Conversion, interpolation, scaling, blending |
| π‘ | Brightness Pipeline | Global per-strip brightness applied at write time |
| π | ADI Expander Support | First-class V5 expander support |
Download the latest luxlib@X.X.X.zip from the Releases page, then:
pros c fetch luxlib@1.0.0.zip
pros c apply luxlibDone. #include "luxlib/luxlib.hpp" is now available.
- Copy
luxlib.hppβinclude/andled_control.cppβsrc/ - Create
include/luxlib/designs/with an emptydesigns.hinside - Add
#include "luxlib/luxlib.hpp"wherever needed
Note: If using the ADI Expander, ensure
main.hincludes expander headers beforeluxlib.hpp.
#include "luxlib/luxlib.hpp"
using namespace luxlib;
// Expander port 1, ADI port 'A', 60 LEDs
LedStrip drive(1, 'A', 60, "DriveLEDs");
void initialize() {
drive.set_all(LED_BLUE);
}
void autonomous() {
drive.animate_fire(); // Runs in background
}
void opcontrol() {
drive.animate_rainbow(); // Loops forever, non-blocking
while (true) {
// Robot code here
pros::delay(20);
}
}Direct brain wiring? Pass
0as the expander port:LedStrip strip(0, 'A', 30);
// LedStrip(expander_port, adi_port, num_leds, name = "LedStrip")
LedStrip intake(0, 'B', 20, "Intake"); // Brain port B, 20 LEDs
LedStrip drive(1, 'A', 60, "Drive"); // Expander port 1, ADI A, 60 LEDsMultiple strips operate fully independently β tasks, zones, and brightness do not interact.
All 14 constants are uint32_t in 0xRRGGBB format:
LED_OFF LED_RED LED_GREEN LED_BLUE LED_WHITE LED_YELLOW
LED_ORANGE LED_PURPLE LED_CYAN LED_PINK LED_WARM LED_TEAL LED_LIME LED_INDIGO
You can also use any hex literal or the color utilities:
strip.set_all(0xFF4500); // Orange-red
strip.set_all(rgb_to_hex(100, 200, 50)); // From components// add_zone(name, start, end) β inclusive on both ends
strip.add_zone("Drive", 0, 39);
strip.add_zone("Intake", 40, 49);
strip.add_zone("Shooter", 50, 59);
// Pass zone name to any animate_* or fill call. "" = full strip.
strip.fill_zone("Intake", LED_GREEN);
strip.animate_breathe(LED_CYAN, 15, "Drive");Overlapping zones are rejected with a terminal error.
strip.set_brightness(0.3f); // 30% β applied at write time, buffer unchanged
strip.turn_off(); // Save buffer, cut hardware output
strip.turn_on(); // Restore buffer, re-enable output
strip.save_buffer(); // Snapshot state
strip.load_buffer(); // Restore snapshot
// In-place manipulation
strip.rotate(3); // Rotate right 3 positions
strip.gradient(0, 59, LED_RED, LED_BLUE, true); // HSV gradient
strip.pulse(LED_WHITE, 15, 6); // Soft pulse at pos 15Calling any animate_* stops the current animation and starts the new one immediately.
strip.animate_rainbow();
strip.timeout(5000); // Auto-stop after 5s (non-blocking)
strip.set_cycles(3); // Auto-stop after 3 cycles
strip.stop(); // Stop and clear immediately| Animation | Defaults | Description |
|---|---|---|
animate_pulse |
(color, width=8, step_ms=30, zone="", reverse=false, bg=OFF) |
Band sweeps, wrapping |
animate_bounce |
(color, width=6, step_ms=20, zone="", bg=OFF) |
Band bounces end to end |
animate_scanner |
(color, width=8, step_ms=20, zone="") |
KITT-style with fade tail |
animate_breathe |
(color, step_ms=10, zone="") |
Quadratic in/out fade loop |
animate_rainbow |
(step_ms=20, reverse=false, speed=1.0f, zone="") |
Full-spectrum scroll |
animate_cycle |
(colors[], count, step_ms=30, reverse=false, zone="") |
Scroll a custom palette |
animate_strobe |
(color, on_ms=50, off_ms=50, bg=OFF, zone="") |
Timed flash |
animate_theater_chase |
(color, bg=OFF, step_ms=50, spacing=3, reverse=false, zone="") |
Marquee scroll |
animate_meteor |
(color, size=6, tail_fade=0.7f, step_ms=20, reverse=false, zone="") |
Comet with fading tail |
animate_wipe |
(color, step_ms=20, reverse=false, bg=OFF, zone="") |
Fill one LED at a time |
animate_sparkle |
(color, density=3, step_ms=30, bg=OFF, zone="") |
Random flashing pixels |
animate_confetti |
(colors[], count, density=3, step_ms=30, zone="") |
Sparkle from palette |
animate_fire |
(intensity=0.8f, step_ms=30, reverse=false, zone="") |
Cellular automaton fire |
Define exact pixel layouts as C arrays and play them at runtime β ideal for logo reveals, alliance intros, and match sequences.
static const uint32_t frame0[60] = { 0xFF0000, 0xFF0000, /* ... */ };
static const uint32_t frame1[60] = { /* ... */ };
static const LedFrame frames[] = {
{ frame0, 200 },
{ frame1, 200 },
};
const LedAnimation my_anim = { frames, 2, true }; // loop = true
strip.show(frame); // Hold forever
strip.show(frame, 2000); // Hold 2s then clear
strip.play(my_anim, 5000); // Play for 5s
strip.play_cycles(my_anim, 3); // Play 3 loopsLuxLib ships a browser-based pixel editor. Paint frames on a 60-LED model, build a timeline, and export ready-to-compile C code β no server required.
βΆ Open the Animation Designer
Workflow:
- Open
led_designer.htmlin any browser - Paint LEDs β Save Frame β repeat
- Export Animation (.c) β drop into project
#includeit indesigns.h
// designs.h
#include "my_reveal.c"// usage
extern const LedAnimation my_reveal;
strip.play_cycles(my_reveal, 1); // Play once at match startstrip.add_zone("Drive", 0, 39);
strip.add_zone("Intake", 40, 49);
strip.add_zone("Shooter", 50, 59);
void opcontrol() {
strip.animate_breathe(LED_BLUE, 15, "Drive");
}
void on_intake_active() { strip.fill_zone("Intake", LED_GREEN); }
void on_shooter_spinup() { strip.animate_strobe(LED_YELLOW, 80, 80, LED_OFF, "Shooter"); }
void on_shooter_ready() { strip.fill_zone("Shooter", LED_GREEN); }LedStrip left(1, 'A', 30, "Left");
LedStrip right(1, 'B', 30, "Right");
LedStrip intake(0, 'C', 15, "Intake");
void opcontrol() {
left.animate_rainbow();
right.animate_rainbow(20, true); // Mirrored
intake.animate_breathe(LED_GREEN);
// All three run simultaneously
}My LEDs flicker or show wrong colors.
WS2812B requires a 5V data line. If the V5 brain outputs 3.3V on ADI, use a level shifter. Also check strip data direction β WS2812Bs are unidirectional.
Can I run two animations on two zones simultaneously?
Not with one LedStrip object β it has one task slot. Use two LedStrip instances, or write a custom PROS task that calls fill_zone for each zone in a loop.
Does stop() block?
No. It removes the task and clears the strip synchronously and returns in microseconds.
What if I call animate_* while another animation is running?
The new call invokes stop() internally, kills the running task, and starts immediately.
PRs and issues are welcome. When adding a new animation, also add an entry to the LuxAnimType enum in luxlib.hpp and a wrapper in led_control.cpp following the existing pattern.
LuxLib is developed by Team 71881M "Mutex" (Capital Cougars). Not affiliated with VEX Robotics or PROS. WS2812B is a trademark of Worldsemi.