diff --git a/src/main/java/org/trigon/hardware/misc/leds/AddressableLEDStrip.java b/src/main/java/org/trigon/hardware/misc/leds/AddressableLEDStrip.java new file mode 100644 index 00000000..0a0533f3 --- /dev/null +++ b/src/main/java/org/trigon/hardware/misc/leds/AddressableLEDStrip.java @@ -0,0 +1,209 @@ +package org.trigon.hardware.misc.leds; + +import com.ctre.phoenix.led.LarsonAnimation; +import edu.wpi.first.wpilibj.AddressableLED; +import edu.wpi.first.wpilibj.AddressableLEDBuffer; +import edu.wpi.first.wpilibj.Timer; +import edu.wpi.first.wpilibj.util.Color; + +import java.util.function.Supplier; + +/** + * A LED strip that is controlled by an AddressableLED. + */ +public class AddressableLEDStrip extends LEDStrip { + private static AddressableLED LED; + private static AddressableLEDBuffer LED_BUFFER; + + private int lastBreatheLED; + private double lastLEDAnimationChangeTime = 0; + private double rainbowFirstPixelHue = 0; + private boolean isLEDAnimationChanged = false; + private int amountOfColorFlowLEDs = 0; + + /** + * Sets and configures the AddressableLED and AddressableLEDBuffer instances to be used for controlling the LED strip. + * Must be set before using any LED strips. Should only be called once. + * + * @param port the port of the LED strip + * @param totalAmountOfLEDs the total amount of LEDs in all LED strips + */ + public static void initiateAddressableLED(int port, int totalAmountOfLEDs) { + if (LED_BUFFER == null) + LED_BUFFER = new AddressableLEDBuffer(totalAmountOfLEDs); + + if (LED == null) { + LED = new AddressableLED(port); + LED.setLength(totalAmountOfLEDs); + LED.start(); + } + } + + /** + * Constructs a new AddressableLEDStrip. Before any commands are sent to the LED strip, the setAddressableLED and setAddressableLEDBuffer methods must be called. + * + * @param inverted whether the LED strip is inverted + * @param numberOfLEDs the amount of LEDs in the strip + * @param indexOffset the offset of the first LED in the strip + */ + AddressableLEDStrip(boolean inverted, int numberOfLEDs, int indexOffset) { + super(inverted, numberOfLEDs, indexOffset); + resetLEDSettings(); + } + + @Override + public void periodic() { + currentAnimation.run(); + LED.setData(LED_BUFFER); + } + + @Override + void clearLEDColors() { + staticColor(Color.kBlack); + } + + @Override + void blink(Color firstColor, double speed) { + final double correctedSpeed = 1 - speed; + final double currentTime = Timer.getFPGATimestamp(); + + if (currentTime - lastLEDAnimationChangeTime > correctedSpeed) { + lastLEDAnimationChangeTime = currentTime; + isLEDAnimationChanged = !isLEDAnimationChanged; + } + + if (isLEDAnimationChanged) { + staticColor(firstColor); + return; + } + clearLEDColors(); + } + + @Override + void staticColor(Color color) { + setLEDColors(color, 0, numberOfLEDs - 1); + } + + @Override + void breathe(Color color, int breathingLEDs, double speed, boolean inverted, LarsonAnimation.BounceMode bounceMode) { + clearLEDColors(); + final boolean correctedInverted = this.inverted != inverted; + final double moveLEDTimeSeconds = 1 - speed; + final double currentTime = Timer.getFPGATimestamp(); + + if (currentTime - lastLEDAnimationChangeTime > moveLEDTimeSeconds) { + lastLEDAnimationChangeTime = currentTime; + if (correctedInverted) + lastBreatheLED--; + else + lastBreatheLED++; + } + + checkIfBreathingHasHitEnd(breathingLEDs, correctedInverted, bounceMode); + setBreathingLEDs(color, breathingLEDs, bounceMode); + } + + @Override + void colorFlow(Color color, double speed, boolean inverted) { + clearLEDColors(); + final boolean correctedInverted = this.inverted != inverted; + final double moveLEDTimeSeconds = 1 - speed; + final double currentTime = Timer.getFPGATimestamp(); + + if (currentTime - lastLEDAnimationChangeTime > moveLEDTimeSeconds) { + lastLEDAnimationChangeTime = currentTime; + if (isLEDAnimationChanged) + amountOfColorFlowLEDs--; + else + amountOfColorFlowLEDs++; + } + + checkIfColorFlowHasHitEnd(); + setLEDColors(color, correctedInverted ? numberOfLEDs - amountOfColorFlowLEDs - 1 : 0, correctedInverted ? numberOfLEDs - 1 : amountOfColorFlowLEDs); + } + + @Override + void alternateColor(Color firstColor, Color secondColor) { + for (int i = 0; i < numberOfLEDs; i++) + LED_BUFFER.setLED(i + indexOffset, i % 2 == 0 ? firstColor : secondColor); + } + + @Override + void rainbow(double brightness, double speed, boolean inverted) { + final boolean correctedInverted = this.inverted != inverted; + final int adjustedBrightness = (int) (brightness * 255); + final int hueIncrement = (int) (speed * 8); + + for (int led = 0; led < numberOfLEDs; led++) { + final int hue = (int) (rainbowFirstPixelHue + (led * 180 / numberOfLEDs) % 180); + LED_BUFFER.setHSV(led + indexOffset, hue, 255, adjustedBrightness); + } + + if (correctedInverted) { + rainbowFirstPixelHue -= hueIncrement; + if (rainbowFirstPixelHue < 0) + rainbowFirstPixelHue += 180; + return; + } + rainbowFirstPixelHue += hueIncrement; + rainbowFirstPixelHue %= 180; + } + + @Override + void sectionColor(Supplier[] colors) { + final int amountOfSections = colors.length; + final int LEDsPerSection = (int) Math.floor(numberOfLEDs / amountOfSections); + + for (int i = 0; i < amountOfSections; i++) + setLEDColors( + inverted ? colors[amountOfSections - i - 1].get() : colors[i].get(), + LEDsPerSection * i, + i == amountOfSections - 1 ? numberOfLEDs - 1 : LEDsPerSection * (i + 1) - 1 + ); + } + + @Override + void resetLEDSettings() { + lastBreatheLED = indexOffset; + lastLEDAnimationChangeTime = Timer.getFPGATimestamp(); + rainbowFirstPixelHue = 0; + isLEDAnimationChanged = false; + amountOfColorFlowLEDs = 0; + } + + private void checkIfBreathingHasHitEnd(int amountOfBreathingLEDs, boolean inverted, LarsonAnimation.BounceMode bounceMode) { + final int bounceModeAddition = switch (bounceMode) { + case Back -> amountOfBreathingLEDs; + case Center -> amountOfBreathingLEDs / 2; + default -> 0; + }; + + if (inverted ? (lastBreatheLED < indexOffset + bounceModeAddition) : (lastBreatheLED >= numberOfLEDs + indexOffset + bounceModeAddition)) + lastBreatheLED = inverted ? indexOffset + numberOfLEDs : indexOffset; + } + + private void setBreathingLEDs(Color color, int breathingLEDs, LarsonAnimation.BounceMode bounceMode) { + for (int i = 0; i < breathingLEDs; i++) { + if (lastBreatheLED - i >= indexOffset && lastBreatheLED - i < indexOffset + numberOfLEDs) + LED_BUFFER.setLED(lastBreatheLED - i, color); + + else if (lastBreatheLED - i < indexOffset + numberOfLEDs) { + if (bounceMode.equals(LarsonAnimation.BounceMode.Back) || bounceMode.equals(LarsonAnimation.BounceMode.Center) && i > breathingLEDs / 2) + return; + LED_BUFFER.setLED(lastBreatheLED - i + numberOfLEDs, color); + } + } + } + + private void checkIfColorFlowHasHitEnd() { + if (amountOfColorFlowLEDs >= numberOfLEDs || amountOfColorFlowLEDs < 0) { + amountOfColorFlowLEDs = amountOfColorFlowLEDs < 0 ? amountOfColorFlowLEDs + 1 : amountOfColorFlowLEDs - 1; + isLEDAnimationChanged = !isLEDAnimationChanged; + } + } + + private void setLEDColors(Color color, int startIndex, int endIndex) { + for (int i = 0; i <= endIndex - startIndex; i++) + LED_BUFFER.setLED(startIndex + indexOffset + i, color); + } +} \ No newline at end of file diff --git a/src/main/java/org/trigon/hardware/misc/leds/CANdleLEDStrip.java b/src/main/java/org/trigon/hardware/misc/leds/CANdleLEDStrip.java new file mode 100644 index 00000000..1f3a6017 --- /dev/null +++ b/src/main/java/org/trigon/hardware/misc/leds/CANdleLEDStrip.java @@ -0,0 +1,159 @@ +package org.trigon.hardware.misc.leds; + +import com.ctre.phoenix.led.*; +import edu.wpi.first.wpilibj.util.Color; +import org.trigon.hardware.RobotHardwareStats; + +import java.util.function.Supplier; + +/** + * A LED strip that is controlled by a CANdle, and uses AddressableLED for simulation. + */ +public class CANdleLEDStrip extends LEDStrip { + private static CANdle CANDLE; + private static int LAST_CREATED_LED_STRIP_ANIMATION_SLOT = 0; + private final int animationSlot; + + /** + * Sets the CANdle instance to be used for controlling the LED strips. Must be set before using any LED strips. Should only be called once + * + * @param candle the CANdle instance to be used + */ + public static void setCANdle(CANdle candle) { + if (CANDLE == null) + CANDLE = candle; + } + + /** + * Sets the total amount of LEDs in all LED strips for simulation. + * Must be set before using any LED strips in simulation. Should only be called once. + * + * @param totalAmountOfLEDs the total amount of LEDs in all LED strips + */ + public static void setTotalAmountOfLEDs(int totalAmountOfLEDs) { + if (RobotHardwareStats.isSimulation() || RobotHardwareStats.isReplay()) + AddressableLEDStrip.initiateAddressableLED(0, totalAmountOfLEDs); + } + + /** + * Constructs a new CANdleLEDStrip. Before any commands are sent to the LED strip, the setCANdle method must be called. + * + * @param inverted whether the LED strip is inverted + * @param numberOfLEDs the amount of LEDs in the strip + * @param indexOffset the offset of the first LED in the strip + */ + CANdleLEDStrip(boolean inverted, int numberOfLEDs, int indexOffset) { + super(inverted, numberOfLEDs, indexOffset); + animationSlot = LAST_CREATED_LED_STRIP_ANIMATION_SLOT; + LAST_CREATED_LED_STRIP_ANIMATION_SLOT++; + } + + @Override + void clearLEDColors() { + CANDLE.clearAnimation(animationSlot); + } + + @Override + void blink(Color firstColor, double speed) { + CANDLE.animate( + new SingleFadeAnimation( + (int) firstColor.red, + (int) firstColor.green, + (int) firstColor.blue, + 0, + speed, + this.numberOfLEDs, + indexOffset + ), + animationSlot + ); + } + + @Override + void staticColor(Color color) { + CANDLE.setLEDs((int) color.red, (int) color.green, (int) color.blue, 0, indexOffset, numberOfLEDs); + } + + @Override + void breathe(Color color, int amountOfBreathingLEDs, double speed, boolean inverted, LarsonAnimation.BounceMode bounceMode) { + CANDLE.animate( + new LarsonAnimation( + (int) color.red, + (int) color.green, + (int) color.blue, + 0, + speed, + this.numberOfLEDs, + bounceMode, + amountOfBreathingLEDs, + indexOffset + ), + animationSlot + ); + } + + @Override + void alternateColor(Color firstColor, Color secondColor) { + for (int i = 0; i < numberOfLEDs; i++) + CANDLE.setLEDs( + (int) (i % 2 == 0 ? firstColor.red : secondColor.red), + (int) (i % 2 == 0 ? firstColor.green : secondColor.green), + (int) (i % 2 == 0 ? firstColor.blue : secondColor.blue), + 0, + i + indexOffset, + 1 + ); + } + + @Override + void colorFlow(Color color, double speed, boolean inverted) { + final boolean correctedInverted = this.inverted != inverted; + CANDLE.animate( + new ColorFlowAnimation( + (int) color.red, + (int) color.green, + (int) color.blue, + 0, + speed, + this.numberOfLEDs, + correctedInverted ? ColorFlowAnimation.Direction.Backward : ColorFlowAnimation.Direction.Forward, + indexOffset + ), + animationSlot + ); + } + + @Override + void rainbow(double brightness, double speed, boolean inverted) { + final boolean correctedInverted = this.inverted != inverted; + CANDLE.animate( + new RainbowAnimation( + brightness, + speed, + this.numberOfLEDs, + correctedInverted, + indexOffset + ), + animationSlot + ); + } + + @Override + void sectionColor(Supplier[] colors) { + final int LEDSPerSection = (int) Math.floor(numberOfLEDs / colors.length); + setSectionColor(colors.length, LEDSPerSection, colors); + } + + private void setSectionColor(int amountOfSections, int LEDSPerSection, Supplier[] colors) { + for (int i = 0; i < amountOfSections; i++) { + CANDLE.setLEDs( + (int) (inverted ? colors[amountOfSections - i - 1].get().red : colors[i].get().red), + (int) (inverted ? colors[amountOfSections - i - 1].get().green : colors[i].get().green), + (int) (inverted ? colors[amountOfSections - i - 1].get().blue : colors[i].get().blue), + 0, + LEDSPerSection * i + indexOffset, + i == amountOfSections - 1 ? numberOfLEDs - 1 : LEDSPerSection * (i + 1) - 1 + ); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/trigon/hardware/misc/leds/LEDCommands.java b/src/main/java/org/trigon/hardware/misc/leds/LEDCommands.java new file mode 100644 index 00000000..a798d404 --- /dev/null +++ b/src/main/java/org/trigon/hardware/misc/leds/LEDCommands.java @@ -0,0 +1,155 @@ +package org.trigon.hardware.misc.leds; + +import com.ctre.phoenix.led.LarsonAnimation; +import edu.wpi.first.wpilibj.util.Color; +import edu.wpi.first.wpilibj2.command.Command; +import edu.wpi.first.wpilibj2.command.StartEndCommand; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * A class that contains static functions for getting LED commands. These commands work with both types of LEDStrips. + */ +public class LEDCommands { + /** + * Gets a command that sets the color of the LED strip to the given color. + * + * @param color the color to set the LED strip to + * @param ledStrips the LED strips to be used + * @return the command + */ + public static Command getStaticColorCommand(Color color, LEDStrip... ledStrips) { + return new StartEndCommand( + () -> { + runForLEDs((LEDStrip::clearLEDColors), ledStrips); + runForLEDs(LEDStrip -> LEDStrip.setCurrentAnimation(() -> LEDStrip.staticColor(color)), ledStrips); + }, + () -> runForLEDs(LEDStrip::clearLEDColors, ledStrips), + ledStrips + ).ignoringDisable(true); + } + + /** + * Gets a command that blinks the LED strip with a specific color. + * + * @param firstColor the color to blink + * @param speed how fast the LED strip should blink on a scale between 0 and 1 + * @param ledStrips the LED strips to be used + * @return the command + */ + public static Command getBlinkingCommand(Color firstColor, double speed, LEDStrip... ledStrips) { + return new StartEndCommand( + () -> { + runForLEDs((LEDStrip::clearLEDColors), ledStrips); + runForLEDs(LEDStrip -> LEDStrip.setCurrentAnimation(() -> LEDStrip.blink(firstColor, speed)), ledStrips); + }, + () -> runForLEDs(LEDStrip::clearLEDColors, ledStrips), + ledStrips + ).ignoringDisable(true); + } + + /** + * Gets a command that "breathes" a pocket of light across the LED strip. + * + * @param color the color to breathe + * @param amountOfBreathingLEDs the amount of breathing LEDs between 1 and 7 + * @param speed the speed of the breathing on a scale between 0 and 1 + * @param inverted whether the breathing should be inverted + * @param bounceMode when the pocket of LEDs should restart to the start of the strip + * @param ledStrips the LED strips to be used + * @return the command + */ + public static Command getBreatheCommand(Color color, int amountOfBreathingLEDs, double speed, boolean inverted, LarsonAnimation.BounceMode bounceMode, LEDStrip... ledStrips) { + return new StartEndCommand( + () -> { + runForLEDs((LEDStrip::clearLEDColors), ledStrips); + runForLEDs(LEDStrip -> LEDStrip.setCurrentAnimation(() -> LEDStrip.breathe(color, amountOfBreathingLEDs, speed, inverted, bounceMode)), ledStrips); + }, + () -> runForLEDs(LEDStrip::clearLEDColors, ledStrips), + ledStrips + ).ignoringDisable(true); + } + + /** + * Gets a command that flows a color through the LED strip. + * + * @param color the color to flow through the LED strip + * @param speed how fast should the color travel the strip on a scale between 0 and 1 + * @param inverted whether the flow should be inverted + * @param ledStrips the LED strips to be used + * @return the command + */ + public static Command getColorFlowCommand(Color color, double speed, boolean inverted, LEDStrip... ledStrips) { + return new StartEndCommand( + () -> { + runForLEDs((LEDStrip::clearLEDColors), ledStrips); + runForLEDs(LEDStrip -> LEDStrip.setCurrentAnimation(() -> LEDStrip.colorFlow(color, speed, inverted)), ledStrips); + }, + () -> runForLEDs(LEDStrip::clearLEDColors, ledStrips), + ledStrips + ).ignoringDisable(true); + } + + /** + * Gets a command that displays two colors in an alternating pattern on the LED strips. + * + * @param firstColor the first color + * @param secondColor the second color + * @param ledStrips the LED strips to be used + * @return the command + */ + public static Command getAlternateColorCommand(Color firstColor, Color secondColor, LEDStrip... ledStrips) { + return new StartEndCommand( + () -> { + runForLEDs((LEDStrip::clearLEDColors), ledStrips); + runForLEDs(LEDStrip -> LEDStrip.setCurrentAnimation(() -> LEDStrip.alternateColor(firstColor, secondColor)), ledStrips); + }, + () -> runForLEDs(LEDStrip::clearLEDColors, ledStrips), + ledStrips + ).ignoringDisable(true); + } + + /** + * Gets a command that splits the LED strip into different sections. + * + * @param colors an array of colors to set the sections to. The length of the array dictates the amount of sections + * @param ledStrips the LED strips to be used + * @return the command + */ + public static Command getSectionColorCommand(Supplier[] colors, LEDStrip... ledStrips) { + return new StartEndCommand( + () -> { + runForLEDs((LEDStrip::clearLEDColors), ledStrips); + runForLEDs(LEDStrip -> LEDStrip.setCurrentAnimation(() -> LEDStrip.sectionColor(colors)), ledStrips); + }, + () -> runForLEDs(LEDStrip::clearLEDColors, ledStrips), + ledStrips + ).ignoringDisable(true); + } + + /** + * Gets a command that displays a rainbow pattern on the LED strips. + * + * @param brightness the brightness of the rainbow on a scale from 0 to 1 + * @param speed the speed of the rainbow's movement on a scale from 0 to 1 + * @param inverted whether the rainbow should be inverted + * @param ledStrips the LED strips to be used + * @return the command + */ + public static Command getRainbowCommand(double brightness, double speed, boolean inverted, LEDStrip... ledStrips) { + return new StartEndCommand( + () -> { + runForLEDs((LEDStrip::clearLEDColors), ledStrips); + runForLEDs(LEDStrip -> LEDStrip.setCurrentAnimation(() -> LEDStrip.rainbow(brightness, speed, inverted)), ledStrips); + }, + () -> runForLEDs(LEDStrip::clearLEDColors, ledStrips), + ledStrips + ).ignoringDisable(true); + } + + private static void runForLEDs(Consumer action, LEDStrip... ledStrips) { + for (LEDStrip LEDStrip : ledStrips) + action.accept(LEDStrip); + } +} \ No newline at end of file diff --git a/src/main/java/org/trigon/hardware/misc/leds/LEDStrip.java b/src/main/java/org/trigon/hardware/misc/leds/LEDStrip.java new file mode 100644 index 00000000..c2802a0a --- /dev/null +++ b/src/main/java/org/trigon/hardware/misc/leds/LEDStrip.java @@ -0,0 +1,145 @@ +package org.trigon.hardware.misc.leds; + +import com.ctre.phoenix.led.LarsonAnimation; +import edu.wpi.first.wpilibj.util.Color; +import edu.wpi.first.wpilibj2.command.Command; +import edu.wpi.first.wpilibj2.command.SubsystemBase; +import org.trigon.hardware.RobotHardwareStats; + +import java.util.function.Supplier; + +/** + * A wrapper class for LED strips. This class provides a set of methods for controlling LED strips. + */ +public abstract class LEDStrip extends SubsystemBase { + public static LEDStrip[] LED_STRIPS = new LEDStrip[0]; + protected final int indexOffset; + protected final boolean inverted; + protected final int numberOfLEDs; + protected Runnable currentAnimation = () -> { + }; + + /** + * Creates a new AddressableLEDStrip. + * + * @param inverted whether the LED strip is inverted + * @param numberOfLEDs the amount of LEDs in the strip + * @param indexOffset the offset of the first LED in the strip + * @return the created AddressableLEDStrip + */ + public static AddressableLEDStrip createAddressableLEDStrip(boolean inverted, int numberOfLEDs, int indexOffset) { + return new AddressableLEDStrip(inverted, numberOfLEDs, indexOffset); + } + + /** + * Creates a new CANdleLEDStrip. In simulation or replay mode, an AddressableLEDStrip is created instead. + * + * @param inverted whether the LED strip is inverted + * @param numberOfLEDs the amount of LEDs in the strip + * @param indexOffset the offset of the first LED in the strip + * @return the created LEDStrip + */ + public static LEDStrip createCANdleLEDStrip(boolean inverted, int numberOfLEDs, int indexOffset) { + if (RobotHardwareStats.isReplay() || RobotHardwareStats.isSimulation()) + return new AddressableLEDStrip(inverted, numberOfLEDs, indexOffset); + return new CANdleLEDStrip(inverted, numberOfLEDs, indexOffset); + } + + /** + * Sets the default command for all LED strips. + * + * @param command the default command to be set + */ + public static void setDefaultCommandForAllLEDS(Command command) { + for (LEDStrip ledStrip : LED_STRIPS) + ledStrip.setDefaultCommand(command); + } + + public LEDStrip(boolean inverted, int numberOfLEDs, int indexOffset) { + this.inverted = inverted; + this.numberOfLEDs = numberOfLEDs; + this.indexOffset = indexOffset; + + addLEDStripToLEDStripsArray(this); + } + + public int getNumberOfLEDS() { + return numberOfLEDs; + } + + void setCurrentAnimation(Runnable currentAnimation) { + this.currentAnimation = currentAnimation; + currentAnimation.run(); + } + + void resetLEDSettings() { + } + + /** + * Sets the color of the LED strip to the given color. + * + * @param color the color to set the LED strip to + */ + abstract void staticColor(Color color); + + /** + * Blinks the LED strip with a specific color. + * + * @param firstColor the color to blink + * @param speed how fast the LED strip should blink on a scale between 0 and 1 + */ + abstract void blink(Color firstColor, double speed); + + /** + * "Breathes" a pocket of LEDs with a given color. + * + * @param color the color of the breathing LEDs + * @param breathingLEDs the amount of breathing LEDs + * @param speed how fast should the color travel the strip on a scale between 0 and 1 + * @param inverted whether the breathing should be inverted + * @param bounceMode when the breathing LEDs should restart at the start of the strip + */ + abstract void breathe(Color color, int breathingLEDs, double speed, boolean inverted, LarsonAnimation.BounceMode bounceMode); + + /** + * Flows a color through the LED strip. + * + * @param color the color to flow through the LED strip + * @param speed how fast should the color travel the strip on a scale between 0 and 1 + * @param inverted whether the color flow should be inverted + */ + abstract void colorFlow(Color color, double speed, boolean inverted); + + /** + * Displays two colors in an alternating pattern on the LED strip. + * + * @param firstColor the first color + * @param secondColor the second color + */ + abstract void alternateColor(Color firstColor, Color secondColor); + + /** + * Splits the LED strip into different sections. + * + * @param colors an array of the colors to color the sections with. The length of the array dictates the amount of sections + */ + abstract void sectionColor(Supplier[] colors); + + /** + * Displays a rainbow pattern on the LED strip. + * + * @param brightness the brightness of the rainbow on a scale from 0 to 1 + * @param speed the speed of the rainbow's movement on a scale from 0 to 1 + * @param inverted whether the rainbow should be inverted + */ + abstract void rainbow(double brightness, double speed, boolean inverted); + + abstract void clearLEDColors(); + + private void addLEDStripToLEDStripsArray(LEDStrip ledStrip) { + final LEDStrip[] newLEDStrips = new LEDStrip[LED_STRIPS.length + 1]; + System.arraycopy(LED_STRIPS, 0, newLEDStrips, 0, LED_STRIPS.length); + newLEDStrips[LED_STRIPS.length] = ledStrip; + LED_STRIPS = newLEDStrips; + } +} \ No newline at end of file diff --git a/src/main/java/org/trigon/hardware/phoenix6/talonfx/TalonFXMotor.java b/src/main/java/org/trigon/hardware/phoenix6/talonfx/TalonFXMotor.java index bf0b786a..d933b626 100644 --- a/src/main/java/org/trigon/hardware/phoenix6/talonfx/TalonFXMotor.java +++ b/src/main/java/org/trigon/hardware/phoenix6/talonfx/TalonFXMotor.java @@ -110,4 +110,4 @@ private TalonFXIO generateIO(int id, String canbus) { return new SimulationTalonFXIO(id); return new RealTalonFXIO(id, canbus); } -} +} \ No newline at end of file