Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/spectrum3847/2026-Spectrum/llms.txt

Use this file to discover all available pages before exploring further.

The LED subsystem provides real-time visual feedback to drive teams and referees during a match. Spectrum’s 2026 codebase uses a CTRE CANdle device (CAN ID 1, CANIVORE bus) to drive an RGB LED strip with 20 addressable LEDs. The implementation follows the same States-based architecture as every other subsystem: a main class (CANdleLeds) configures hardware, and LedStates declares triggers and binds animations.
The CANdleLeds and LedStates classes are currently scaffolded but commented out — the hardware integration is ready to be enabled when the physical CANdle is wired. The patterns shown below reflect the intended design already written in the source.

CANdleLeds configuration

CANdleLeds configures the CANdle at startup:
CANdleLeds.java
CANdle = new CANdle(1, new CANBus(Rio.CANIVORE));
CANdleConfig =
        new CANdleConfiguration()
                .withLED(
                        new LEDConfigs()
                                .withStripType(StripTypeValue.RGB)
                                .withBrightnessScalar(0.5)
                                .withLossOfSignalBehavior(
                                        LossOfSignalBehaviorValue.DisableLEDs));
CANdle.getConfigurator().apply(CANdleConfig);
CANdle.setControl(
        new SingleFadeAnimation(0, 20)
                .withColor(new RGBWColor(156, 11, 255, 0))
                .withFrameRate(Hertz.of(100)));
Key configuration choices:
  • Strip type: RGB (no white channel used)
  • Brightness: 50% scalar applied globally
  • Loss-of-signal behavior: LEDs disable if CAN communication is lost, preventing stuck animations during a brownout
  • Default animation: Purple single-fade at 100 Hz frame rate on startup

Multiple strip instances via LEDBufferView

SpectrumLEDs (from SpectrumLib) supports creating multiple subsystem instances, each targeting a different LEDBufferView slice of the full strip. This lets you command the front strip, back strip, or any segment independently without conflicts.
LedStates reference pattern
// Each instance controls its own segment of the strip
SpectrumLEDs frontStrip = new SpectrumLEDs(frontConfig);   // LEDs 0–9
SpectrumLEDs rearStrip  = new SpectrumLEDs(rearConfig);    // LEDs 10–19

Default command and mode-based animations

LedStates calls setupDefaultCommand() and bindTriggers() from setupDefaultCommand() and setupStates() on the subsystem. The default command can detect the current DriverStation mode and dispatch mode-specific animations:
  • Autonomous: Alliance-color single fade
  • Teleop (normal shift): Alliance-color single fade, switching between blue and red based on ShiftHelpers.isCurrentShiftBlue()
  • Disabled: No command is scheduled (LEDs idle)

Trigger-based animation priorities

LedStates declares time-based and state-based triggers and binds animations using .onTrue(Commands.runOnce(...)):
LedStates.java
public static final Trigger endgame =
        new Trigger(() -> DriverStation.getMatchTime() <= 30).and(Util.teleop);

public static final Trigger aboutToChangeShift =
        new Trigger(
                        () -> {
                            double t = DriverStation.getMatchTime();
                            return (t <= 108 && t >= 105)
                                    || (t <= 83 && t >= 80)
                                    || (t <= 58 && t >= 55)
                                    || (t <= 33 && t >= 30);
                        })
                .and(Util.teleop);
TriggerAnimationPriority
autoAlliance-color single fade15
transitionShift (140–133 s)Alliance-color single fade15
transitionAboutToEnd (133–130 s)Alliance-color strobe25
redShiftRed single fade10
blueShiftBlue single fade10
aboutToChangeShiftCurrent-alliance strobe25
endgame (≤ 30 s)Yellow-green single fade (#CFff04)20
Higher priority values take precedence. The bothInShift guard (auto.or(transitionShift, endgame)) prevents shift-color animations from running when a higher-priority animation is already active.

Creating new LED commands

Follow these steps to add a new animation:
1

Define the animation

Create a SingleFadeAnimation, StrobeAnimation, or other CTRE LED control object with the desired color and LED range.
LedStates.java
SingleFadeAnimation hasFuelAnimation =
        new SingleFadeAnimation(0, 20)
                .withSlot(0)
                .withColor(new RGBWColor(0, 255, 0)); // green
2

Create a command

Wrap the animation in a Commands.runOnce that calls candle.setControl(...).
LedStates.java
public static Command setLedsGreen() {
    return Commands.runOnce(() -> candle.setControl(hasFuelAnimation));
}
3

Declare a trigger

Declare the trigger as a public static final variable in LedStates so it is accessible from RobotStates.
LedStates.java
public static final Trigger hasFuel =
        new Trigger(() -> Robot.getIndexerBed().hasFuel());
4

Bind the trigger

Call the binding inside bindTriggers().
LedStates.java
static void bindTriggers() {
    hasFuel.onTrue(setLedsGreen());
    // existing bindings...
}

Binding to robot state triggers

LED commands can also be driven by RobotStates triggers instead of time. For example, to strobe when the launcher is aiming:
RobotStates.java (example binding)
// Pilot presses action button → LED feedback
Robot.pilot.getGamepad().a().onTrue(LedStates.setLedsGreen());
This approach keeps LED logic decoupled from mechanism logic while still responding to the same underlying state machine.

CANdleLeds source

Hardware init, CANdle configuration, and brightness settings.

LedStates source

All time-based and state-based triggers with animation bindings.

Build docs developers (and LLMs) love