Overview
LuxLib wraps pros::ADILed with a clean API so you can do in one line what used to take fifty β without ever blocking your autonomous or opcontrol loops.
Every animation runs in its own background PROS task. Call strip.animate_rainbow() and walk away. The strip handles itself.
Installation
Method 1 β PROS Template (Recommended)
Download luxlib@1.0.0.zip from the Releases page, then:
pros c fetch luxlib@1.0.0.zip
pros c apply luxlib
Done. luxlib/luxlib.hpp is now available in your project.
Method 2 β Manual
- Copy
luxlib.hppβinclude/andled_control.cppβsrc/ - Create
include/luxlib/designs/with an emptydesigns.hinside - Add
#include "luxlib/luxlib.hpp"where needed
Using the ADI Expander? Make sure main.h includes the expander headers before luxlib.hpp.
Quick Start
#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 in background
while (true) {
// Robot code here β LEDs don't block anything
pros::delay(20);
}
}
No expander? Pass 0 as the expander port: LedStrip strip(0, 'A', 30);
The LedStrip Object
Everything in LuxLib centers on the LedStrip class. Each instance maps to one physical WS2812B strip on one ADI port. Multiple instances operate fully independently.
// LedStrip(expander_port, adi_port, num_leds, name = "LedStrip")
LedStrip intake(0, 'B', 20, "Intake"); // Direct brain, port B, 20 LEDs
LedStrip drive(1, 'A', 60, "Drive"); // Expander port 1, ADI A, 60 LEDs
LedStrip shooter(2, 'C', 12); // Name defaults to "LedStrip"
Colors & Utilities
LuxLib ships 14 color constants as uint32_t in 0xRRGGBB format, plus global color utility functions β no object required.
You can also pass any hex literal or build your own:
// Pack / unpack
uint32_t rgb_to_hex(uint8_t r, uint8_t g, uint8_t b);
LuxRGB hex_to_rgb(uint32_t color);
// Brightness scale (0.0 = off, 1.0 = full)
uint32_t scale_color(uint32_t color, float factor);
// HSV β RGB (LedHSV: h 0β360, s 0β1, v 0β1)
uint32_t hsv_to_rgb(LedHSV hsv);
LedHSV rgb_to_hsv(uint32_t color);
// Interpolation β RGB (passes through grey) or HSV (vivid, shortest hue path)
uint32_t interpolate_rgb(uint32_t start, uint32_t end, float t);
uint32_t interpolate_rgb(uint32_t start, uint32_t end, int step, int width);
uint32_t interpolate_hsv(uint32_t start, uint32_t end, float t);
uint32_t interpolate_hsv(uint32_t start, uint32_t end, int step, int width);
Zone System
A zone is a named sub-range of a strip, letting you address physical sections independently. LuxLib rejects overlapping zones with a terminal error.
// add_zone(name, start, end) β inclusive on both ends
strip.add_zone("LeftDrive", 0, 19);
strip.add_zone("RightDrive", 20, 39);
strip.add_zone("Intake", 40, 59);
// Pass zone name to any animate_* or fill call. "" or omit = full strip.
strip.fill_zone("Intake", LED_GREEN);
strip.animate_breathe(LED_CYAN, 15, "LeftDrive");
strip.animate_rainbow(20, false, 1.0f, "RightDrive");
Brightness & Buffer
Each strip has a global brightness (0.0β1.0, default 1.0) applied at write-time. The internal buffer always stores full-color values. turn_off() does not stop an animation task β the task keeps running; turn_on() picks up where it left off.
// Brightness
strip.set_brightness(0.3f); strip.get_brightness();
strip.turn_off(); strip.turn_on();
strip.set_enabled(false); strip.set_enabled(true);
// Buffer writes
strip.set_pixel(5, LED_RED);
strip.set_all(LED_BLUE);
strip.fill_zone(10, 20, LED_GREEN);
strip.set_buffer(my_vector);
strip.clear();
// Snapshot / restore
strip.save_buffer(); strip.load_buffer();
// In-place manipulation
strip.color_shift(10, -20, 0); // Shift RGB channels (clamped)
strip.rotate(3); strip.rotate(-3, "LeftDrive");
strip.gradient(0, 59, LED_RED, LED_BLUE); // RGB gradient
strip.gradient(0, 59, LED_RED, LED_BLUE, true); // HSV gradient (vivid)
strip.pulse(LED_WHITE, 15, 6); // Soft pulse shape at pos 15
strip.blend_frames(frameA, frameB, 0.5f); // Crossfade two frames
Procedural Animations
Calling any animate_* function immediately stops the current animation and starts the new one in a background task. Use timeout() or set_cycles() to auto-stop without blocking.
strip.stop(); // Stop and clear
strip.animate_rainbow();
strip.timeout(5000); // Auto-stop after 5s
strip.set_cycles(3); // Auto-stop after 3 cycles
| Animation | Signature (defaults) | Description |
|---|---|---|
| animate_pulse | (color, width=8, step_ms=30, zone="", reverse=false, bg=OFF) | Band sweeps across, 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 scan with fade tail |
| animate_breathe | (color, step_ms=10, zone="") | Quadratic fade in/out 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 on/off |
| animate_theater_chase | (color, bg=OFF, step_ms=50, spacing=3, reverse=false, zone="") | Marquee spaced-pixel 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, then erase |
| 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 a custom palette |
| animate_fire | (intensity=0.8f, step_ms=30, reverse=false, zone="") | Cellular automaton fire sim |
Frame-Based Animations
Define exact pixel layouts as C arrays and play them back at runtime β ideal for logo reveals, alliance intros, or match-start sequences.
Structs
struct LedFrame {
const uint32_t* pixels; // num_leds color values (0xRRGGBB)
uint32_t delay_ms;
};
struct LedAnimation {
const LedFrame* frames;
int frame_count;
bool loop; // true = repeat forever
};
Playback
strip.show(frame); // Hold frame forever
strip.show(frame, 2000); // Hold for 2s then clear
strip.play(anim, 5000); // Play for 5 seconds
strip.play_cycles(anim, 3); // Play 3 full loops
strip.play_timed(anim, 5000); // Alias for play()
LED Animation Designer
LuxLib ships a browser-based pixel editor. Paint frames on a 60-LED model, build a timeline, and export ready-to-compile C code.
Open led_designer.html β paint LEDs β Save Frame β repeat β Export Animation (.c) β drop into project β #include in designs.h.
/* Exported file looks like this */
#include "main.h"
static const LedFrame my_reveal_frames[] = {
{ { 0x000000, 0xFF0000, 0xFF0000 /* ... */ }, 300 },
{ { 0xFF0000, 0xFF0000, 0xFF0000 /* ... */ }, 500 },
};
const LedAnimation my_reveal = { my_reveal_frames, 2, true };
// designs.h
#include "my_reveal.c"
// usage
extern const LedAnimation my_reveal;
strip.play_cycles(my_reveal, 1); // Play once at match start
API Quick Reference
Core Writes
| Method | Description |
|---|---|
| set_pixel(index, color) | Single LED |
| set_all(color) | Fill entire strip |
| fill_zone(start, end, color) | Fill by index range |
| fill_zone(name, color) | Fill by zone name |
| gradient(start, end, c1, c2, hsv=false) | Paint gradient |
| clear() | All off, buffer zeroed |
Global Controls
| Method | Description |
|---|---|
| set_brightness(float) | 0.0β1.0 |
| turn_off() / turn_on() | Save/restore buffer + hardware toggle |
| set_enabled(bool) | Combined toggle |
| save_buffer() / load_buffer() | Snapshot / restore |
| stop() | Kill task, clear strip |
| timeout(ms) | Auto-stop after ms |
| set_cycles(n) | Auto-stop after n cycles |
Examples
Alliance Color + Zone State Feedback
strip.add_zone("Drive", 0, 39);
strip.add_zone("Intake", 40, 49);
strip.add_zone("Shooter", 50, 59);
void initialize() {
strip.set_all(isRedAlliance ? LED_RED : LED_BLUE);
}
void opcontrol() {
strip.animate_breathe(LED_BLUE, 15, "Drive");
strip.fill_zone("Intake", LED_OFF);
}
void on_shooter_spinup() {
strip.animate_strobe(LED_YELLOW, 80, 80, LED_OFF, "Shooter");
}
void on_shooter_ready() {
strip.fill_zone("Shooter", LED_GREEN);
}
Multi-Strip Mirrored Drive
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, fully independent
}
FAQ
My LEDs flicker or show wrong colors.
WS2812B requires a 5V data line. If the V5 brain outputs 3.3V on ADI, you may need a level shifter. Also verify your strip's data direction β WS2812Bs are unidirectional.
Can I run two animations on two zones of the same strip simultaneously?
Not directly β each LedStrip object has one task slot. For true simultaneous zone animations, use two LedStrip objects, or write a custom PROS task that calls fill_zone for each zone in a loop.
What happens if I call animate_rainbow() while play_cycles() is running?
animate_rainbow() calls stop() internally before starting. Any running task is killed immediately and the new animation starts.
Does stop() block?
No. It calls m_task->remove() synchronously, clears the strip, and returns in microseconds.