Date: Thu, 23 Oct 2025 22:03:31 -0400
Subject: [PATCH 02/29] add seperate rev subsytem and update docs
---
.../java/frc/lib/io/motor/MotorIORev.java | 5 +-
.../java/frc/lib/io/motor/MotorIORevSim.java | 28 +--
.../java/frc/lib/io/motor/MotorIOSim.java | 61 +++++--
.../java/frc/lib/io/motor/MotorIOTalonFX.java | 164 ++++++++++++-----
src/main/java/frc/robot/RobotContainer.java | 13 +-
.../revRotary/RevRotarySubsystem.java | 91 ++++++++++
.../RevRotarySubsystemConstants.java | 171 ++++++++++++++++++
.../subsystems/rotary/RotarySubsystem.java | 82 ++++++++-
.../rotary/RotarySubsystemConstants.java | 76 ++++++--
9 files changed, 601 insertions(+), 90 deletions(-)
create mode 100644 src/main/java/frc/robot/subsystems/revRotary/RevRotarySubsystem.java
create mode 100644 src/main/java/frc/robot/subsystems/revRotary/RevRotarySubsystemConstants.java
diff --git a/src/main/java/frc/lib/io/motor/MotorIORev.java b/src/main/java/frc/lib/io/motor/MotorIORev.java
index 5795e49a..83ba3caf 100644
--- a/src/main/java/frc/lib/io/motor/MotorIORev.java
+++ b/src/main/java/frc/lib/io/motor/MotorIORev.java
@@ -26,6 +26,7 @@
import edu.wpi.first.units.measure.Velocity;
import edu.wpi.first.units.measure.Voltage;
import edu.wpi.first.wpilibj.motorcontrol.Spark;
+import frc.lib.util.Device;
import com.revrobotics.spark.SparkLowLevel.MotorType;
public class MotorIORev implements MotorIO {
@@ -118,14 +119,14 @@ protected com.revrobotics.spark.SparkBase.ControlType getRevControlType(ControlT
*/
public MotorIORev(
String name,
- int id,
+ Device.CAN id,
boolean isFlex,
SparkBaseConfig config)
{
this.name = name;
if (isFlex) {
- motor = new SparkFlex(id, MotorType.kBrushless);
+ motor = new SparkFlex(id.id(), MotorType.kBrushless);
} else {
// motor = new SparkMax(id, MotorType.kBrushless);
}
diff --git a/src/main/java/frc/lib/io/motor/MotorIORevSim.java b/src/main/java/frc/lib/io/motor/MotorIORevSim.java
index ae797ed0..16761689 100644
--- a/src/main/java/frc/lib/io/motor/MotorIORevSim.java
+++ b/src/main/java/frc/lib/io/motor/MotorIORevSim.java
@@ -26,24 +26,28 @@
import edu.wpi.first.units.measure.AngularAcceleration;
import edu.wpi.first.units.measure.AngularVelocity;
import edu.wpi.first.units.measure.Velocity;
+import frc.lib.util.Device;
/**
- * Simulates a REV motor controller (SparkFlex or SparkMax) for FRC robot code.
- *
- * Extends {@link MotorIORev} and implements {@link MotorIOSim} to enable simulation of leader and
- * follower motors, closed-loop control, and state updates for testing without hardware.
- *
+ * Simulated implementation of {@link MotorIORev} for REV Robotics motors using WPILib simulation.
+ * Implements {@link MotorIOSim} to provide simulation-specific behavior.
*
*
- * Features:
+ * Constructor arguments:
*
- * - Supports SparkFlex and SparkMax controllers
- * - Configures followers with inversion
- * - Simulates position and velocity control
- * - Updates simulated inputs
+ * - name - Name of the motor
+ * - id - CAN ID of the motor
+ * - isFlex - True if using SparkFlex, false for SparkMax
+ * - gearBox - DCMotor gearbox model
+ * - config - Motor configuration
+ * - followerData - Varargs of follower motor data (ID and inversion)
*
- *
+ *
+ *
+ * This class wraps a simulated SparkFlex or SparkMax motor, allowing position and velocity control
+ * in a simulation environment. It provides methods to run the motor in position or velocity mode,
+ * update simulation inputs, and manage simulation state.
*
* @see MotorIORev
* @see MotorIOSim
@@ -71,7 +75,7 @@ public record RevFollowerFollower(int id, boolean opposesLeader) {
*/
public MotorIORevSim(
String name,
- int id,
+ Device.CAN id,
boolean isFlex,
DCMotor gearBox,
SparkBaseConfig config,
diff --git a/src/main/java/frc/lib/io/motor/MotorIOSim.java b/src/main/java/frc/lib/io/motor/MotorIOSim.java
index 53d82ca6..d56053a2 100644
--- a/src/main/java/frc/lib/io/motor/MotorIOSim.java
+++ b/src/main/java/frc/lib/io/motor/MotorIOSim.java
@@ -19,38 +19,62 @@
import edu.wpi.first.units.measure.AngularAcceleration;
import edu.wpi.first.units.measure.AngularVelocity;
+/**
+ * Extension of MotorIO interface for simulated motors.
+ *
+ *
+ * This interface adds methods specific to simulation, allowing WPILib physics simulations to feed
+ * data back into the motor IO layer. This creates a realistic simulation where the simulated
+ * physics affects the motor's sensor readings.
+ */
public interface MotorIOSim extends MotorIO {
/**
- * Setter for the simulated mechanism position, typically taken from a WPILib mechanism
- * simulation
+ * Sets the simulated mechanism position.
+ *
+ *
+ * This is called by WPILib mechanism simulations (like SingleJointedArmSim) to update the
+ * motor's position sensor based on the simulated physics. This creates a feedback loop where
+ * motor output affects the simulation, and the simulation updates the sensors.
*
- * @param position The new mechanism position
+ * @param position The new mechanism position from the physics simulation
*/
public default void setPosition(Angle position)
{}
/**
- * Setter for the simulated mechanism velocity, typically taken from a WPILib mechanism
- * simulation
+ * Sets the simulated motor rotor velocity.
+ *
+ *
+ * The rotor is the spinning part inside the motor. This method updates the velocity sensor
+ * reading based on what the physics simulation calculated. Rotor velocity is before any gear
+ * reduction.
*
- * @param velocity The new mechanism velocity
+ * @param velocity The new motor rotor velocity from the physics simulation
*/
public default void setRotorVelocity(AngularVelocity velocity)
{}
/**
- * Setter for the simulated mechanism acceleration, typically taken from a WPILib mechanism
- * simulation
+ * Sets the simulated motor rotor acceleration.
*
- * @param acceleration The new mechanism acceleration
+ *
+ * This updates the acceleration value based on the physics simulation. Acceleration is useful
+ * for advanced control algorithms and can help detect mechanism problems.
+ *
+ * @param acceleration The new motor rotor acceleration from the physics simulation
*/
public default void setRotorAcceleration(AngularAcceleration acceleration)
{}
/**
- * Getter for the gear ratio to the sensor
+ * Gets the gear ratio from the motor rotor to the sensor.
+ *
+ *
+ * This is the first stage of gearing, from the motor's internal rotor to wherever the encoder
+ * is mounted. For example, if there's a 2:1 reduction between the motor and encoder, this
+ * returns 2.0.
*
- * @return The gear ratio to the sensor
+ * @return The rotor-to-sensor gear ratio
*/
public default double getRotorToSensorRatio()
{
@@ -58,15 +82,26 @@ public default double getRotorToSensorRatio()
}
/**
- * Getter for the gear ratio from the sensor to the mechanism
+ * Gets the gear ratio from the sensor to the final mechanism.
*
- * @return The gear ratio from the sensor to the mechanism
+ *
+ * This is the second stage of gearing, from the encoder to the actual mechanism you're
+ * controlling. For example, if there's a 2:1 reduction between the encoder and your arm pivot,
+ * this returns 2.0. The total reduction is rotor-to-sensor × sensor-to-mechanism.
+ *
+ * @return The sensor-to-mechanism gear ratio
*/
public default double getSensorToMechanismRatio()
{
return 0.0;
}
+ /**
+ * Closes and cleans up simulation resources.
+ *
+ *
+ * Called when the simulation is shutting down to properly release any resources.
+ */
public default void close()
{}
}
diff --git a/src/main/java/frc/lib/io/motor/MotorIOTalonFX.java b/src/main/java/frc/lib/io/motor/MotorIOTalonFX.java
index cf251c74..fb8f507d 100644
--- a/src/main/java/frc/lib/io/motor/MotorIOTalonFX.java
+++ b/src/main/java/frc/lib/io/motor/MotorIOTalonFX.java
@@ -45,6 +45,16 @@
*/
public class MotorIOTalonFX implements MotorIO {
+ /**
+ * Configuration data for a TalonFX motor that follows another TalonFX motor.
+ *
+ *
+ * Follower motors mirror the output of a main motor, useful for mechanisms that require
+ * multiple motors working together (like a dual-motor elevator).
+ *
+ * @param id The CAN device ID of the follower motor
+ * @param opposesMain Whether this follower should spin opposite to the main motor
+ */
public record TalonFXFollower(Device.CAN id, boolean opposesMain) {
}
@@ -84,11 +94,17 @@ public record TalonFXFollower(Device.CAN id, boolean opposesMain) {
/**
* Constructs and initializes a TalonFX motor.
- *
- * @param name The name of the motor(s)
- * @param config Configuration to apply to the motor(s)
+ *
+ *
+ * This constructor applies the provided configuration to the main motor and all followers. It
+ * sets up the follower relationship, initializes status signals for telemetry, and configures
+ * signal update rates. All followers must be on the same CAN bus as the main motor.
+ *
+ * @param name The name of the motor(s) for logging and identification
+ * @param config Configuration to apply to the motor(s) including PID, limits, and gear ratios
* @param main CAN ID of the main motor
- * @param followerData Configuration data for the follower(s)
+ * @param followerData Configuration data for the follower motor(s), can be empty if no
+ * followers
*/
public MotorIOTalonFX(String name, TalonFXConfiguration config, Device.CAN main,
TalonFXFollower... followerData)
@@ -149,8 +165,12 @@ public MotorIOTalonFX(String name, TalonFXConfiguration config, Device.CAN main,
/**
* Checks if the motor is currently running a position control mode.
- *
- * @return True if the motor is using a position control mode.
+ *
+ *
+ * Position control modes command the motor to move to and hold a specific position. This
+ * includes Motion Magic modes that use motion profiling.
+ *
+ * @return true if the motor is using a position control mode
*/
protected boolean isRunningPositionControl()
{
@@ -164,8 +184,11 @@ protected boolean isRunningPositionControl()
/**
* Checks if the motor is currently running a velocity control mode.
- *
- * @return True if the motor is using a velocity control mode.
+ *
+ *
+ * Velocity control modes command the motor to spin at a specific speed.
+ *
+ * @return true if the motor is using a velocity control mode
*/
protected boolean isRunningVelocityControl()
{
@@ -178,8 +201,12 @@ protected boolean isRunningVelocityControl()
/**
* Checks if the motor is running any Motion Magic mode.
- *
- * @return True if the motor is using a Motion Magic mode.
+ *
+ *
+ * Motion Magic is CTRE's advanced motion profiling control mode that automatically generates
+ * smooth motion with controlled velocity, acceleration, and jerk (rate of acceleration).
+ *
+ * @return true if the motor is using a Motion Magic mode
*/
protected boolean isRunningMotionMagic()
{
@@ -192,9 +219,13 @@ protected boolean isRunningMotionMagic()
}
/**
- * Returns the current control type.
- *
- * @return The current control type.
+ * Determines the current control type being used by the motor.
+ *
+ *
+ * This identifies which control mode the motor is currently in (coast, brake, voltage, current,
+ * position, velocity, etc.).
+ *
+ * @return The current ControlType
*/
protected ControlType getCurrentControlType()
{
@@ -219,8 +250,13 @@ protected ControlType getCurrentControlType()
/**
* Updates the passed-in MotorInputs structure with the latest sensor readings.
- *
- * @param inputs Motor input structure to populate.
+ *
+ *
+ * This method refreshes all status signals from the motor and populates the inputs object with
+ * current telemetry data. The data varies based on the current control mode - for example,
+ * position error and trajectory data are only populated during position control.
+ *
+ * @param inputs Motor input structure to populate with current sensor data
*/
@Override
public void updateInputs(MotorInputs inputs)
@@ -282,7 +318,12 @@ public void updateInputs(MotorInputs inputs)
}
/**
- * Sets the motor to coast mode.
+ * Sets the motor to coast mode (no active braking).
+ *
+ *
+ * In coast mode, the motor spins freely when not powered. The mechanism will slow down
+ * gradually due to friction but won't actively try to stop. Use this when you want the
+ * mechanism to move freely.
*/
@Override
public void runCoast()
@@ -291,7 +332,12 @@ public void runCoast()
}
/**
- * Sets the motor to brake mode.
+ * Sets the motor to brake mode (active braking).
+ *
+ *
+ * In brake mode, the motor actively resists rotation when not powered. This causes the
+ * mechanism to stop quickly and hold position. Use this when you need the mechanism to stay in
+ * place.
*/
@Override
public void runBrake()
@@ -301,8 +347,11 @@ public void runBrake()
/**
* Runs the motor using direct voltage control.
- *
- * @param voltage Desired voltage output.
+ *
+ *
+ * Voltage control directly applies a voltage to the motor.
+ *
+ * @param voltage Desired voltage output (positive = forward, negative = reverse)
*/
@Override
public void runVoltage(Voltage voltage)
@@ -311,9 +360,14 @@ public void runVoltage(Voltage voltage)
}
/**
- * Runs the motor with a specified current output.
- *
- * @param current Desired torque-producing current.
+ * Runs the motor with a specified torque-producing current output.
+ *
+ *
+ * Current control directly controls the current flowing through the motor, which is
+ * proportional to torque output. This provides more consistent torque regardless of motor
+ * speed, compared to voltage control.
+ *
+ * @param current Desired torque-producing current (not total supply current)
*/
@Override
public void runCurrent(Current current)
@@ -322,10 +376,14 @@ public void runCurrent(Current current)
}
/**
- * Runs the motor with a specified current output and duty cycle.
- *
- * @param current Desired torque-producing current.
- * @param dutyCycle Desired dutycycle of current output, limiting top speed
+ * Runs the motor with a specified current output limited by duty cycle.
+ *
+ *
+ * This version allows you to limit the maximum speed of the motor while using current control.
+ * The duty cycle caps the percentage of available voltage.
+ *
+ * @param current Desired torque-producing current
+ * @param dutyCycle Maximum duty cycle (0.0 to 1.0) limiting top speed
*/
@Override
public void runCurrent(Current current, double dutyCycle)
@@ -335,9 +393,13 @@ public void runCurrent(Current current, double dutyCycle)
}
/**
- * Runs the motor using duty cycle (percentage of available voltage).
- *
- * @param dutyCycle Fractional output between 0 and 1.
+ * Runs the motor using duty cycle control (percentage of available voltage) with FOC.
+ *
+ *
+ * Duty cycle control outputs a percentage of the current battery voltage. Unlike direct voltage
+ * control, this automatically adjusts for battery voltage changes.
+ *
+ * @param dutyCycle Fractional output between 0.0 and 1.0 (will be clamped to this range)
*/
@Override
public void runDutyCycle(double dutyCycle)
@@ -347,13 +409,19 @@ public void runDutyCycle(double dutyCycle)
}
/**
- * Runs the motor to a specific position.
- *
- * @param position Target position.
- * @param cruiseVelocity Cruise velocity.
- * @param acceleration Max acceleration.
- * @param maxJerk Max jerk (rate of acceleration).
- * @param slot PID slot index.
+ * Runs the motor to a specific position using Motion Magic with dynamic profiling.
+ *
+ *
+ * Motion Magic generates a smooth motion profile with controlled cruise velocity, acceleration,
+ * and jerk. Dynamic Motion Magic allows these parameters to be changed on-the-fly for each new
+ * position command. This provides smooth, predictable motion that's easy on mechanisms and
+ * looks professional.
+ *
+ * @param position Target position to move to
+ * @param cruiseVelocity Maximum velocity during the move
+ * @param acceleration Maximum acceleration during speed-up and slow-down
+ * @param maxJerk Maximum jerk (rate of acceleration change) for smoothness
+ * @param slot PID slot index to use for position control gains
*/
@Override
public void runPosition(Angle position, AngularVelocity cruiseVelocity,
@@ -366,11 +434,15 @@ public void runPosition(Angle position, AngularVelocity cruiseVelocity,
}
/**
- * Runs the motor at a target velocity.
- *
- * @param velocity Desired velocity.
- * @param acceleration Max acceleration.
- * @param slot PID slot index.
+ * Runs the motor at a target velocity using FOC current control.
+ *
+ *
+ * Velocity control maintains a constant speed using a PID controller. The motor will
+ * automatically adjust output to maintain the target velocity as load changes.
+ *
+ * @param velocity Desired angular velocity (positive = forward, negative = reverse)
+ * @param acceleration Maximum acceleration when changing speeds
+ * @param slot PID slot index to use for velocity control gains
*/
@Override
public void runVelocity(AngularVelocity velocity, AngularAcceleration acceleration,
@@ -381,6 +453,16 @@ public void runVelocity(AngularVelocity velocity, AngularAcceleration accelerati
.withSlot(slot.getNum()));
}
+ /**
+ * Manually sets the encoder position to a specific value.
+ *
+ *
+ * This is useful for zeroing the encoder when the mechanism is at a known position, or for
+ * resetting position after hitting a hard stop. Use carefully as incorrect position can cause
+ * the mechanism to move unexpectedly.
+ *
+ * @param position The new position value to set the encoder to
+ */
@Override
public void setEncoderPosition(Angle position)
{
diff --git a/src/main/java/frc/robot/RobotContainer.java b/src/main/java/frc/robot/RobotContainer.java
index 8470d4cf..916a4603 100644
--- a/src/main/java/frc/robot/RobotContainer.java
+++ b/src/main/java/frc/robot/RobotContainer.java
@@ -67,6 +67,8 @@
import frc.robot.subsystems.leds.LEDsConstants;
import frc.robot.subsystems.linear.Linear;
import frc.robot.subsystems.linear.LinearConstants;
+import frc.robot.subsystems.revRotary.RevRotarySubsystem;
+import frc.robot.subsystems.revRotary.RevRotarySubsystemConstants;
import frc.robot.subsystems.rotary.RotarySubsystem;
import frc.robot.subsystems.rotary.RotarySubsystemConstants;
import frc.robot.subsystems.rotary.RotarySubsystem.Setpoint;
@@ -107,6 +109,7 @@ public class RobotContainer {
private final Linear linear;
private final Vision vision;
private final RotarySubsystem rotary;
+ private final RevRotarySubsystem revRotary;
// Controller
private final CommandXboxControllerExtended controller = new CommandXboxControllerExtended(0);
@@ -151,6 +154,7 @@ public RobotContainer()
VisionConstants.aprilTagLayout,
PoseStrategy.CONSTRAINED_SOLVEPNP));
rotary = new RotarySubsystem(RotarySubsystemConstants.getReal());
+ revRotary = new RevRotarySubsystem(RevRotarySubsystemConstants.getReal());
}
case SIM -> {
@@ -183,8 +187,8 @@ public RobotContainer()
VisionConstants.aprilTagLayout,
PoseStrategy.CONSTRAINED_SOLVEPNP,
visionSim.get()));
- // rotary = new RotarySubsystem(RotarySubsystemConstants.getSim());
- rotary = new RotarySubsystem(RotarySubsystemConstants.getRevSim());
+ rotary = new RotarySubsystem(RotarySubsystemConstants.getSim());
+ revRotary = new RevRotarySubsystem(RevRotarySubsystemConstants.getSim());
}
default -> {
@@ -206,6 +210,7 @@ public RobotContainer()
linear = new Linear(LinearConstants.getReplay());
rotary = new RotarySubsystem(RotarySubsystemConstants.getReplay());
+ revRotary = new RevRotarySubsystem(RevRotarySubsystemConstants.getReplay());
visionSim = Optional.empty();
vision = new Vision(
@@ -304,6 +309,10 @@ private void configureButtonBindings()
SmartDashboard.putData("Rotary: Stow", rotary.setSetpoint(RotarySubsystem.Setpoint.STOW));
SmartDashboard.putData("Rotary: Raised",
rotary.setSetpoint(RotarySubsystem.Setpoint.RAISED));
+ SmartDashboard.putData("REV Rotary: Stow",
+ revRotary.setSetpoint(RevRotarySubsystem.Setpoint.STOW));
+ SmartDashboard.putData("REV Rotary: Raised",
+ revRotary.setSetpoint(RevRotarySubsystem.Setpoint.RAISED));
LoggedTunableNumber ballVel = new LoggedTunableNumber("Ball Sim Velocity (fps)", 15);
SmartDashboard.putData("Shoot Ball", Commands
diff --git a/src/main/java/frc/robot/subsystems/revRotary/RevRotarySubsystem.java b/src/main/java/frc/robot/subsystems/revRotary/RevRotarySubsystem.java
new file mode 100644
index 00000000..ce5d6bdb
--- /dev/null
+++ b/src/main/java/frc/robot/subsystems/revRotary/RevRotarySubsystem.java
@@ -0,0 +1,91 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package frc.robot.subsystems.revRotary;
+
+import static edu.wpi.first.units.Units.Degrees;
+import edu.wpi.first.units.measure.Angle;
+import edu.wpi.first.units.measure.AngularVelocity;
+import edu.wpi.first.wpilibj2.command.Command;
+import edu.wpi.first.wpilibj2.command.Commands;
+import edu.wpi.first.wpilibj2.command.SubsystemBase;
+import frc.lib.io.motor.MotorIO.PIDSlot;
+import frc.lib.mechanisms.rotary.RotaryMechanism;
+import frc.lib.util.LoggedTunableNumber;
+import frc.lib.util.LoggerHelper;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+public class RevRotarySubsystem extends SubsystemBase {
+
+ private final RotaryMechanism io;
+
+ private static final LoggedTunableNumber STOW_SETPOINT = new LoggedTunableNumber("TEST", 0.0);
+ private static final LoggedTunableNumber RAISED_SETPOINT =
+ new LoggedTunableNumber("RAISED", 90);
+
+ @RequiredArgsConstructor
+ @SuppressWarnings("Immutable")
+ @Getter
+ public enum Setpoint {
+ STOW(Degrees.of(STOW_SETPOINT.get())),
+ RAISED(Degrees.of(RAISED_SETPOINT.get()));
+
+ private final Angle setpoint;
+ }
+
+
+ public RevRotarySubsystem(RotaryMechanism io)
+ {
+ this.io = io;
+
+ setSetpoint(RevRotarySubsystemConstants.DEFAULT_SETPOINT).ignoringDisable(true).schedule();
+ }
+
+ @Override
+ public void periodic()
+ {
+ LoggerHelper.recordCurrentCommand(RevRotarySubsystemConstants.NAME, this);
+ io.periodic();
+ }
+
+ public Command setSetpoint(Setpoint setpoint)
+ {
+ return this.runOnce(
+ () -> io.runPosition(setpoint.getSetpoint(),
+ RevRotarySubsystemConstants.CRUISE_VELOCITY,
+ RevRotarySubsystemConstants.ACCELERATION, RevRotarySubsystemConstants.JERK,
+ PIDSlot.SLOT_0))
+ .withName("Go To " + setpoint.toString() + " Setpoint");
+ };
+
+ public boolean nearGoal(Angle targetPosition)
+ {
+ return io.nearGoal(targetPosition, RevRotarySubsystemConstants.TOLERANCE);
+ }
+
+ public Command waitUntilGoalCommand(Angle position)
+ {
+ return Commands.waitUntil(() -> {
+ return nearGoal(position);
+ });
+ }
+
+ public Command setGoalCommandWithWait(Setpoint setpoint)
+ {
+ return waitUntilGoalCommand(setpoint.getSetpoint())
+ .deadlineFor(setSetpoint(setpoint))
+ .withName("Go To " + setpoint.toString() + " Setpoint with wait");
+ }
+
+ public AngularVelocity getVelocity()
+ {
+ return io.getVelocity();
+ }
+
+ public void close()
+ {
+ io.close();
+ }
+}
diff --git a/src/main/java/frc/robot/subsystems/revRotary/RevRotarySubsystemConstants.java b/src/main/java/frc/robot/subsystems/revRotary/RevRotarySubsystemConstants.java
new file mode 100644
index 00000000..f290992a
--- /dev/null
+++ b/src/main/java/frc/robot/subsystems/revRotary/RevRotarySubsystemConstants.java
@@ -0,0 +1,171 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+package frc.robot.subsystems.revRotary;
+
+import static edu.wpi.first.units.Units.Degrees;
+import static edu.wpi.first.units.Units.KilogramSquareMeters;
+import static edu.wpi.first.units.Units.Kilograms;
+import static edu.wpi.first.units.Units.Rotations;
+import static edu.wpi.first.units.Units.Second;
+import java.util.Optional;
+import static edu.wpi.first.units.Units.Meters;
+import com.ctre.phoenix6.configs.*;
+import com.ctre.phoenix6.signals.FeedbackSensorSourceValue;
+import com.ctre.phoenix6.signals.InvertedValue;
+import com.ctre.phoenix6.signals.NeutralModeValue;
+import edu.wpi.first.math.geometry.Translation3d;
+import edu.wpi.first.math.system.plant.DCMotor;
+import edu.wpi.first.units.AngularAccelerationUnit;
+import edu.wpi.first.units.Units;
+import edu.wpi.first.units.measure.Angle;
+import edu.wpi.first.units.measure.AngularAcceleration;
+import edu.wpi.first.units.measure.AngularVelocity;
+import edu.wpi.first.units.measure.Distance;
+import edu.wpi.first.units.measure.Mass;
+import edu.wpi.first.units.measure.MomentOfInertia;
+import edu.wpi.first.units.measure.Velocity;
+import edu.wpi.first.wpilibj.simulation.SingleJointedArmSim;
+import frc.lib.io.motor.MotorIO;
+import frc.lib.io.motor.MotorIORev;
+import frc.lib.io.motor.MotorIORevSim;
+import frc.lib.io.motor.MotorIOSim;
+import frc.lib.mechanisms.rotary.*;
+import frc.lib.mechanisms.rotary.RotaryMechanism.RotaryMechCharacteristics;
+import frc.robot.Ports;
+import com.revrobotics.spark.ClosedLoopSlot;
+import com.revrobotics.spark.config.ClosedLoopConfig;
+import com.revrobotics.spark.config.SparkBaseConfig;
+import com.revrobotics.spark.config.SparkFlexConfig;
+import com.revrobotics.spark.config.SparkBaseConfig.IdleMode;
+
+
+/** Add your docs here. */
+public class RevRotarySubsystemConstants {
+ public static String NAME = "REVRotary";
+
+ public static final Angle TOLERANCE = Degrees.of(2.0);
+
+ public static final AngularVelocity CRUISE_VELOCITY =
+ Units.RadiansPerSecond.of(1);
+ public static final AngularAcceleration ACCELERATION =
+ CRUISE_VELOCITY.div(0.1).per(Units.Second);
+ public static final Velocity JERK = ACCELERATION.per(Second);
+
+ private static final double ROTOR_TO_SENSOR = (2.0 / 1.0);
+ private static final double SENSOR_TO_MECHANISM = (2.0 / 1.0);
+
+ public static final Translation3d OFFSET = Translation3d.kZero;
+
+ public static final Angle MIN_ANGLE = Degrees.of(0.0);
+ public static final Angle MAX_ANGLE = Rotations.of(.5);
+ public static final Angle STARTING_ANGLE = Rotations.of(0.0);
+ public static final Distance ARM_LENGTH = Meters.of(1.0);
+
+ public static final RotaryMechCharacteristics CONSTANTS =
+ new RotaryMechCharacteristics(OFFSET, ARM_LENGTH, MIN_ANGLE, MAX_ANGLE, STARTING_ANGLE);
+
+ public static final Mass ARM_MASS = Kilograms.of(.01);
+ public static final DCMotor DCMOTOR = DCMotor.getKrakenX60(1);
+ public static final MomentOfInertia MOI = KilogramSquareMeters
+ .of(SingleJointedArmSim.estimateMOI(ARM_LENGTH.in(Meters), ARM_MASS.in(Kilograms)));
+
+ private static final Angle ENCODER_OFFSET = Rotations.of(0.0);
+
+ public static final RevRotarySubsystem.Setpoint DEFAULT_SETPOINT =
+ RevRotarySubsystem.Setpoint.STOW;
+
+
+
+ // Positional PID
+ private static ClosedLoopConfig SLOT0CONFIG = new ClosedLoopConfig()
+ .pid(30.0, 0, 0, ClosedLoopSlot.kSlot0);
+
+ /**
+ * Creates and returns the TalonFX motor controller configuration for the rotary mechanism.
+ *
+ *
+ * This configuration includes:
+ *
+ * - Current limits to prevent motor damage and brownouts
+ * - Voltage limits for power output
+ * - Brake mode to hold position when not moving
+ * - Software limit switches to prevent mechanism damage
+ * - Gear ratios for proper position/velocity feedback
+ * - Remote CANcoder feedback for absolute positioning
+ * - PID gains for control
+ *
+ *
+ * @return A configured TalonFXConfiguration object ready to apply to a motor controller
+ */
+ public static SparkBaseConfig getREVConfig()
+ {
+ SparkFlexConfig config = new SparkFlexConfig();
+
+ config.voltageCompensation(12.0);
+ config.idleMode(IdleMode.kBrake);
+ config.inverted(false);
+ config.apply(SLOT0CONFIG);
+
+ return config;
+ }
+
+ /**
+ * Creates the real robot implementation of the rotary mechanism.
+ *
+ *
+ * This method instantiates the actual hardware objects (TalonFX motors and CANcoder) that will
+ * be used when running on a real robot.
+ *
+ * @return A RotaryMechanismReal object configured with real hardware
+ */
+ public static RotaryMechanismReal getReal()
+ {
+ MotorIO io = new MotorIORev(NAME, Ports.RotarySubsystemMotorMain, true, getREVConfig());
+
+ return new RotaryMechanismReal(io, CONSTANTS, null);
+ }
+
+ /**
+ * Creates the simulation implementation of the rotary mechanism.
+ *
+ *
+ * This method creates a physics-based simulation of the mechanism using WPILib's simulation
+ * classes. It models the motor, moment of inertia, and other physical properties to provide
+ * realistic behavior in simulation.
+ *
+ * @return A RotaryMechanismSim object configured for physics simulation
+ */
+ public static RotaryMechanismSim getSim()
+ {
+ MotorIOSim io = new MotorIORevSim(
+ NAME,
+ Ports.RotarySubsystemMotorMain,
+ true,
+ DCMOTOR,
+ getREVConfig());
+
+ return new RotaryMechanismSim(
+ io,
+ DCMOTOR,
+ MOI,
+ false,
+ CONSTANTS,
+ Optional.empty());
+ }
+
+ /**
+ * Creates the log replay implementation of the rotary mechanism.
+ *
+ *
+ * This is used with AdvantageKit's log replay feature, which allows you to replay logged data
+ * and debug robot code without having the actual robot or running simulation.
+ *
+ * @return A RotaryMechanism object for log replay
+ */
+ public static RotaryMechanism getReplay()
+ {
+ return new RotaryMechanism(NAME, CONSTANTS) {};
+ }
+}
diff --git a/src/main/java/frc/robot/subsystems/rotary/RotarySubsystem.java b/src/main/java/frc/robot/subsystems/rotary/RotarySubsystem.java
index fc9648f6..cd0a7716 100644
--- a/src/main/java/frc/robot/subsystems/rotary/RotarySubsystem.java
+++ b/src/main/java/frc/robot/subsystems/rotary/RotarySubsystem.java
@@ -25,17 +25,35 @@ public class RotarySubsystem extends SubsystemBase {
private static final LoggedTunableNumber RAISED_SETPOINT =
new LoggedTunableNumber("RAISED", 90);
+ /**
+ * Predefined positions for the rotary mechanism.
+ *
+ *
+ * These setpoints define common positions that the mechanism moves to during operation. The
+ * actual angle values can be tuned live during testing using LoggedTunableNumber.
+ */
@RequiredArgsConstructor
@SuppressWarnings("Immutable")
@Getter
public enum Setpoint {
+ /**
+ * The stowed (stored) position - where the mechanism rests safely inside the robot frame
+ */
STOW(Degrees.of(STOW_SETPOINT.get())),
RAISED(Degrees.of(RAISED_SETPOINT.get()));
private final Angle setpoint;
}
-
+ /**
+ * Constructs a new RotarySubsystem.
+ *
+ *
+ * This constructor initializes the subsystem and immediately schedules a command to move to the
+ * default setpoint.
+ *
+ * @param io The RotaryMechanism IO layer (real hardware, simulation, or replay)
+ */
public RotarySubsystem(RotaryMechanism io)
{
this.io = io;
@@ -43,6 +61,13 @@ public RotarySubsystem(RotaryMechanism io)
setSetpoint(RotarySubsystemConstants.DEFAULT_SETPOINT).ignoringDisable(true).schedule();
}
+ /**
+ * Called every robot loop iteration (every 20ms by default).
+ *
+ *
+ * This method logs which command is currently using this subsystem and updates all mechanism
+ * telemetry for AdvantageKit logging.
+ */
@Override
public void periodic()
{
@@ -50,6 +75,17 @@ public void periodic()
io.periodic();
}
+ /**
+ * Creates a command to move the mechanism to a predefined setpoint.
+ *
+ *
+ * This command uses Motion Magic control, which creates smooth motion profiles with controlled
+ * velocity and acceleration. The command completes instantly - it only starts the motion and
+ * does not wait for the mechanism to reach the target.
+ *
+ * @param setpoint The predefined position to move to (STOW or RAISED)
+ * @return A Command that starts motion to the setpoint
+ */
public Command setSetpoint(Setpoint setpoint)
{
return this.runOnce(
@@ -59,11 +95,32 @@ public Command setSetpoint(Setpoint setpoint)
.withName("Go To " + setpoint.toString() + " Setpoint");
};
+ /**
+ * Checks if the mechanism is near the goal position within the defined tolerance.
+ *
+ *
+ * This is useful for determining when the mechanism has reached its target position. The
+ * tolerance is defined in RotarySubsystemConstants.TOLERANCE.
+ *
+ * @param targetPosition The position to check against
+ * @return true if the current position is within tolerance of the target
+ */
public boolean nearGoal(Angle targetPosition)
{
return io.nearGoal(targetPosition, RotarySubsystemConstants.TOLERANCE);
}
+ /**
+ * Creates a command that waits until the mechanism reaches the specified position.
+ *
+ *
+ * This command finishes when nearGoal() returns true. It's useful for creating command
+ * sequences where you need to wait for the mechanism to finish moving before continuing to the
+ * next step.
+ *
+ * @param position The position to wait for
+ * @return A Command that finishes when the position is reached
+ */
public Command waitUntilGoalCommand(Angle position)
{
return Commands.waitUntil(() -> {
@@ -71,6 +128,17 @@ public Command waitUntilGoalCommand(Angle position)
});
}
+ /**
+ * Creates a command that moves to a setpoint and waits until it's reached.
+ *
+ *
+ * This combines setSetpoint() and waitUntilGoalCommand() into a single command. The deadline
+ * composition ensures that if the wait timeout occurs, both the wait and the motion command are
+ * cancelled together.
+ *
+ * @param setpoint The predefined position to move to and wait for
+ * @return A Command that moves to the setpoint and waits until it's reached
+ */
public Command setGoalCommandWithWait(Setpoint setpoint)
{
return waitUntilGoalCommand(setpoint.getSetpoint())
@@ -78,11 +146,23 @@ public Command setGoalCommandWithWait(Setpoint setpoint)
.withName("Go To " + setpoint.toString() + " Setpoint with wait");
}
+ /**
+ * Gets the current velocity of the mechanism.
+ *
+ * @return The current angular velocity
+ */
public AngularVelocity getVelocity()
{
return io.getVelocity();
}
+ /**
+ * Closes and cleans up resources used by this subsystem.
+ *
+ *
+ * This should be called when the subsystem is no longer needed, typically during robot
+ * shutdown. It ensures proper cleanup of hardware resources.
+ */
public void close()
{
io.close();
diff --git a/src/main/java/frc/robot/subsystems/rotary/RotarySubsystemConstants.java b/src/main/java/frc/robot/subsystems/rotary/RotarySubsystemConstants.java
index 9fd7cec8..aa085a3f 100644
--- a/src/main/java/frc/robot/subsystems/rotary/RotarySubsystemConstants.java
+++ b/src/main/java/frc/robot/subsystems/rotary/RotarySubsystemConstants.java
@@ -65,8 +65,7 @@ public class RotarySubsystemConstants {
new RotaryMechCharacteristics(OFFSET, ARM_LENGTH, MIN_ANGLE, MAX_ANGLE, STARTING_ANGLE);
public static final Mass ARM_MASS = Kilograms.of(.01);
- // public static final DCMotor DCMOTOR = DCMotor.getKrakenX60(1);
- public static final DCMotor DCMOTOR = DCMotor.getNeoVortex(1);
+ public static final DCMotor DCMOTOR = DCMotor.getKrakenX60(1);
public static final MomentOfInertia MOI = KilogramSquareMeters
.of(SingleJointedArmSim.estimateMOI(ARM_LENGTH.in(Meters), ARM_MASS.in(Kilograms)));
@@ -80,6 +79,23 @@ public class RotarySubsystemConstants {
.withKI(0.0)
.withKD(5.0);
+ /**
+ * Creates and returns the TalonFX motor controller configuration for the rotary mechanism.
+ *
+ *
+ * This configuration includes:
+ *
+ * - Current limits to prevent motor damage and brownouts
+ * - Voltage limits for power output
+ * - Brake mode to hold position when not moving
+ * - Software limit switches to prevent mechanism damage
+ * - Gear ratios for proper position/velocity feedback
+ * - Remote CANcoder feedback for absolute positioning
+ * - PID gains for control
+ *
+ *
+ * @return A configured TalonFXConfiguration object ready to apply to a motor controller
+ */
public static TalonFXConfiguration getFXConfig()
{
TalonFXConfiguration config = new TalonFXConfiguration();
@@ -115,6 +131,17 @@ public static TalonFXConfiguration getFXConfig()
return config;
}
+ /**
+ * Creates and returns the CANcoder absolute encoder configuration.
+ *
+ *
+ * The CANcoder provides absolute position feedback, meaning it knows the true position of the
+ * mechanism even after power cycling. The magnet offset calibrates the encoder's zero position.
+ *
+ * @param sim Whether this configuration is for simulation (true) or real robot (false). In
+ * simulation, the offset is set to 0.0 since it's not needed.
+ * @return A configured CANcoderConfiguration object
+ */
public static CANcoderConfiguration getCANcoderConfig(boolean sim)
{
CANcoderConfiguration config = new CANcoderConfiguration();
@@ -124,6 +151,15 @@ public static CANcoderConfiguration getCANcoderConfig(boolean sim)
return config;
}
+ /**
+ * Creates the real robot implementation of the rotary mechanism.
+ *
+ *
+ * This method instantiates the actual hardware objects (TalonFX motors and CANcoder) that will
+ * be used when running on a real robot.
+ *
+ * @return A RotaryMechanismReal object configured with real hardware
+ */
public static RotaryMechanismReal getReal()
{
return new RotaryMechanismReal(
@@ -134,23 +170,16 @@ public static RotaryMechanismReal getReal()
NAME + "Encoder", getCANcoderConfig(false))));
}
- public static RotaryMechanismSim getRevSim()
- {
- return new RotaryMechanismSim(
- new MotorIORevSim(
- NAME,
- Ports.RotarySubsystemMotorMain.id(),
- true,
- DCMOTOR, // Missing gearBox parameter
- new SparkFlexConfig()
- .voltageCompensation(12.0)
- .idleMode(IdleMode.kBrake)
- .inverted(false)),
- DCMOTOR, MOI, false, CONSTANTS,
- Optional.of(new AbsoluteEncoderIOCANCoderSim(Ports.RotarySubsystemEncoder,
- NAME + "Encoder", getCANcoderConfig(true))));
- }
-
+ /**
+ * Creates the simulation implementation of the rotary mechanism.
+ *
+ *
+ * This method creates a physics-based simulation of the mechanism using WPILib's simulation
+ * classes. It models the motor, moment of inertia, and other physical properties to provide
+ * realistic behavior in simulation.
+ *
+ * @return A RotaryMechanismSim object configured for physics simulation
+ */
public static RotaryMechanismSim getSim()
{
return new RotaryMechanismSim(
@@ -164,6 +193,15 @@ public static RotaryMechanismSim getSim()
NAME + "Encoder", getCANcoderConfig(true))));
}
+ /**
+ * Creates the log replay implementation of the rotary mechanism.
+ *
+ *
+ * This is used with AdvantageKit's log replay feature, which allows you to replay logged data
+ * and debug robot code without having the actual robot or running simulation.
+ *
+ * @return A RotaryMechanism object for log replay
+ */
public static RotaryMechanism getReplay()
{
return new RotaryMechanism(NAME, CONSTANTS) {};
From 6b067e88f182ae0584cd1f448e351012592f9379 Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Mon, 24 Nov 2025 15:05:20 -0500
Subject: [PATCH 03/29] refactor MotorIORev: clean up imports and improve code
formatting
---
src/main/java/frc/lib/io/motor/MotorIORev.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/main/java/frc/lib/io/motor/MotorIORev.java b/src/main/java/frc/lib/io/motor/MotorIORev.java
index 83ba3caf..ba8170ab 100644
--- a/src/main/java/frc/lib/io/motor/MotorIORev.java
+++ b/src/main/java/frc/lib/io/motor/MotorIORev.java
@@ -25,7 +25,6 @@
import edu.wpi.first.units.measure.Current;
import edu.wpi.first.units.measure.Velocity;
import edu.wpi.first.units.measure.Voltage;
-import edu.wpi.first.wpilibj.motorcontrol.Spark;
import frc.lib.util.Device;
import com.revrobotics.spark.SparkLowLevel.MotorType;
@@ -200,7 +199,8 @@ public void runPosition(Angle position, AngularVelocity cruiseVelocity,
AngularAcceleration acceleration,
Velocity maxJerk, PIDSlot slot)
{
- controller.setReference(position.in(Rotation), getRevControlType(ControlType.POSITION),
+ controller.setReference(position.in(Rotation),
+ getRevControlType(ControlType.POSITION),
getClosedLoopSlot(slot));
}
From e152663f53520eca870f87ffbe1257e66d28bf38 Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Mon, 24 Nov 2025 15:17:55 -0500
Subject: [PATCH 04/29] merge dev
---
src/main/java/frc/lib/devices/ObjectDetection.java | 6 +++---
.../java/frc/lib/io/objectDetection/ObjectDetectionIO.java | 2 +-
.../io/objectDetection/ObjectDetectionIOPhotonVision.java | 2 +-
.../frc/lib/io/objectDetection/ObjectDetectionIOSim.java | 2 +-
.../frc/robot/subsystems/objectDetector/ObjectDetector.java | 2 +-
.../subsystems/objectDetector/ObjectDetectorConstants.java | 4 +++-
src/main/java/frc/robot/subsystems/rotary/Rotary.java | 2 +-
7 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/src/main/java/frc/lib/devices/ObjectDetection.java b/src/main/java/frc/lib/devices/ObjectDetection.java
index 7316d199..b7dd71e8 100644
--- a/src/main/java/frc/lib/devices/ObjectDetection.java
+++ b/src/main/java/frc/lib/devices/ObjectDetection.java
@@ -6,9 +6,6 @@
import org.littletonrobotics.junction.Logger;
import java.util.ArrayList;
-import frc.lib.io.objectdetection.ObjectDetectionIO;
-import frc.lib.io.objectdetection.ObjectDetectionIOInputsAutoLogged;
-import frc.lib.io.objectdetection.ObjectDetectionIO.TargetObservation;
import java.util.Arrays;
import java.lang.Math;
import edu.wpi.first.math.geometry.Pose2d;
@@ -16,6 +13,9 @@
import edu.wpi.first.math.geometry.Transform3d;
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.geometry.Translation2d;
+import frc.lib.io.objectDetection.ObjectDetectionIO;
+import frc.lib.io.objectDetection.ObjectDetectionIO.TargetObservation;
+import frc.lib.io.objectDetection.ObjectDetectionIOInputsAutoLogged;
/**
* Device level implementation of an object detection camera. While the IO layer is responsible for
diff --git a/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIO.java b/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIO.java
index 303685b8..a6c4cb6c 100644
--- a/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIO.java
+++ b/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIO.java
@@ -13,7 +13,7 @@
* not, see .
*/
-package frc.lib.io.objectdetection;
+package frc.lib.io.objectDetection;
import org.littletonrobotics.junction.AutoLog;
diff --git a/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIOPhotonVision.java b/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIOPhotonVision.java
index 924cf91f..87faa2e6 100644
--- a/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIOPhotonVision.java
+++ b/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIOPhotonVision.java
@@ -13,7 +13,7 @@
* not, see .
*/
-package frc.lib.io.objectdetection;
+package frc.lib.io.objectDetection;
import org.photonvision.PhotonCamera;
import org.photonvision.targeting.PhotonPipelineResult;
diff --git a/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIOSim.java b/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIOSim.java
index 743c0129..63454001 100644
--- a/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIOSim.java
+++ b/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIOSim.java
@@ -2,7 +2,7 @@
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
-package frc.lib.io.objectdetection;
+package frc.lib.io.objectDetection;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/frc/robot/subsystems/objectDetector/ObjectDetector.java b/src/main/java/frc/robot/subsystems/objectDetector/ObjectDetector.java
index b05114e2..7421c2a9 100644
--- a/src/main/java/frc/robot/subsystems/objectDetector/ObjectDetector.java
+++ b/src/main/java/frc/robot/subsystems/objectDetector/ObjectDetector.java
@@ -16,7 +16,7 @@
package frc.robot.subsystems.objectDetector;
import frc.lib.devices.ObjectDetection;
-import frc.lib.io.objectdetection.ObjectDetectionIO;
+import frc.lib.io.objectDetection.ObjectDetectionIO;
import frc.robot.subsystems.drive.Drive;
import org.littletonrobotics.junction.Logger;
import edu.wpi.first.math.geometry.Translation2d;
diff --git a/src/main/java/frc/robot/subsystems/objectDetector/ObjectDetectorConstants.java b/src/main/java/frc/robot/subsystems/objectDetector/ObjectDetectorConstants.java
index ddedd791..a2c1d2ae 100644
--- a/src/main/java/frc/robot/subsystems/objectDetector/ObjectDetectorConstants.java
+++ b/src/main/java/frc/robot/subsystems/objectDetector/ObjectDetectorConstants.java
@@ -13,7 +13,9 @@
import edu.wpi.first.units.measure.Angle;
import edu.wpi.first.units.Units;
import edu.wpi.first.wpilibj.Timer;
-import frc.lib.io.objectdetection.*;
+import frc.lib.io.objectDetection.ObjectDetectionIO;
+import frc.lib.io.objectDetection.ObjectDetectionIOPhotonVision;
+import frc.lib.io.objectDetection.ObjectDetectionIOSim;
import frc.robot.Constants;
import frc.robot.subsystems.drive.Drive;
diff --git a/src/main/java/frc/robot/subsystems/rotary/Rotary.java b/src/main/java/frc/robot/subsystems/rotary/Rotary.java
index e8d97910..5ff0f2ad 100644
--- a/src/main/java/frc/robot/subsystems/rotary/Rotary.java
+++ b/src/main/java/frc/robot/subsystems/rotary/Rotary.java
@@ -59,7 +59,7 @@ public enum Setpoint {
*
* @param io The RotaryMechanism IO layer (real hardware, simulation, or replay)
*/
- public RotarySubsystem(RotaryMechanism io)
+ public Rotary(RotaryMechanism io)
{
this.io = io;
this.robotstate = RobotState.getInstance();
From cbb9d840226a783b15958192050f4dbd075cec71 Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Mon, 24 Nov 2025 15:19:54 -0500
Subject: [PATCH 05/29] merge rotary
---
.../frc/robot/subsystems/rotary/Rotary.java | 104 ++++--------------
1 file changed, 23 insertions(+), 81 deletions(-)
diff --git a/src/main/java/frc/robot/subsystems/rotary/Rotary.java b/src/main/java/frc/robot/subsystems/rotary/Rotary.java
index 5ff0f2ad..1600fcc3 100644
--- a/src/main/java/frc/robot/subsystems/rotary/Rotary.java
+++ b/src/main/java/frc/robot/subsystems/rotary/Rotary.java
@@ -30,35 +30,23 @@ public class Rotary extends SubsystemBase {
private static final LoggedTunableNumber RAISED_SETPOINT =
new LoggedTunableNumber("RAISED", -90);
- /**
- * Predefined positions for the rotary mechanism.
- *
- *
- * These setpoints define common positions that the mechanism moves to during operation. The
- * actual angle values can be tuned live during testing using LoggedTunableNumber.
- */
@RequiredArgsConstructor
@SuppressWarnings("Immutable")
@Getter
public enum Setpoint {
- /**
- * The stowed (stored) position - where the mechanism rests safely inside the robot frame
- */
+ HOME(Degrees.of(0.0)),
STOW(Degrees.of(STOW_SETPOINT.get())),
RAISED(Degrees.of(RAISED_SETPOINT.get()));
private final Angle setpoint;
}
- /**
- * Constructs a new RotarySubsystem.
- *
- *
- * This constructor initializes the subsystem and immediately schedules a command to move to the
- * default setpoint.
- *
- * @param io The RotaryMechanism IO layer (real hardware, simulation, or replay)
- */
+ private Debouncer homeDebouncer = new Debouncer(0.1, DebounceType.kRising);
+ private Trigger homedTrigger;
+
+ private final RobotState robotstate;
+ private Setpoint setpoint = Setpoint.STOW;
+
public Rotary(RotaryMechanism io)
{
this.io = io;
@@ -71,13 +59,6 @@ public Rotary(RotaryMechanism io)
}
- /**
- * Called every robot loop iteration (every 20ms by default).
- *
- *
- * This method logs which command is currently using this subsystem and updates all mechanism
- * telemetry for AdvantageKit logging.
- */
@Override
public void periodic()
{
@@ -87,17 +68,6 @@ public void periodic()
}
- /**
- * Creates a command to move the mechanism to a predefined setpoint.
- *
- *
- * This command uses Motion Magic control, which creates smooth motion profiles with controlled
- * velocity and acceleration. The command completes instantly - it only starts the motion and
- * does not wait for the mechanism to reach the target.
- *
- * @param setpoint The predefined position to move to (STOW or RAISED)
- * @return A Command that starts motion to the setpoint
- */
public Command setSetpoint(Setpoint setpoint)
{
return this.runOnce(
@@ -107,32 +77,11 @@ public Command setSetpoint(Setpoint setpoint)
.withName("Go To " + setpoint.toString() + " Setpoint");
};
- /**
- * Checks if the mechanism is near the goal position within the defined tolerance.
- *
- *
- * This is useful for determining when the mechanism has reached its target position. The
- * tolerance is defined in RotarySubsystemConstants.TOLERANCE.
- *
- * @param targetPosition The position to check against
- * @return true if the current position is within tolerance of the target
- */
public boolean nearGoal(Angle targetPosition)
{
return io.nearGoal(targetPosition, RotaryConstants.TOLERANCE);
}
- /**
- * Creates a command that waits until the mechanism reaches the specified position.
- *
- *
- * This command finishes when nearGoal() returns true. It's useful for creating command
- * sequences where you need to wait for the mechanism to finish moving before continuing to the
- * next step.
- *
- * @param position The position to wait for
- * @return A Command that finishes when the position is reached
- */
public Command waitUntilGoalCommand(Angle position)
{
return Commands.waitUntil(() -> {
@@ -140,17 +89,6 @@ public Command waitUntilGoalCommand(Angle position)
});
}
- /**
- * Creates a command that moves to a setpoint and waits until it's reached.
- *
- *
- * This combines setSetpoint() and waitUntilGoalCommand() into a single command. The deadline
- * composition ensures that if the wait timeout occurs, both the wait and the motion command are
- * cancelled together.
- *
- * @param setpoint The predefined position to move to and wait for
- * @return A Command that moves to the setpoint and waits until it's reached
- */
public Command setGoalCommandWithWait(Setpoint setpoint)
{
return waitUntilGoalCommand(setpoint.getSetpoint())
@@ -158,23 +96,27 @@ public Command setGoalCommandWithWait(Setpoint setpoint)
.withName("Go To " + setpoint.toString() + " Setpoint with wait");
}
- /**
- * Gets the current velocity of the mechanism.
- *
- * @return The current angular velocity
- */
+ public Command setStateCommand(Setpoint setpoint)
+ {
+ return this.runOnce(() -> this.setpoint = setpoint)
+ .withName("Elevator Set State: " + setpoint.name());
+ }
+
+ public Command homeCommand()
+ {
+ return Commands.sequence(runOnce(() -> io.runVoltage(Volts.of(-2))),
+ Commands.waitUntil(homedTrigger),
+ runOnce(() -> io.setEncoderPosition(Setpoint.HOME.getSetpoint())),
+ this.setStateCommand(Setpoint.STOW))
+ .withName("Homing");
+
+ }
+
public AngularVelocity getVelocity()
{
return io.getVelocity();
}
- /**
- * Closes and cleans up resources used by this subsystem.
- *
- *
- * This should be called when the subsystem is no longer needed, typically during robot
- * shutdown. It ensures proper cleanup of hardware resources.
- */
public void close()
{
io.close();
From 631cc5336eab27d95f255795203ff0e23a254596 Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Mon, 24 Nov 2025 15:27:46 -0500
Subject: [PATCH 06/29] merge dev
---
src/main/java/frc/robot/RobotContainer.java | 121 +++-----------------
1 file changed, 16 insertions(+), 105 deletions(-)
diff --git a/src/main/java/frc/robot/RobotContainer.java b/src/main/java/frc/robot/RobotContainer.java
index 99ccb580..8fc784a4 100644
--- a/src/main/java/frc/robot/RobotContainer.java
+++ b/src/main/java/frc/robot/RobotContainer.java
@@ -68,11 +68,9 @@
import frc.robot.subsystems.leds.LEDsConstants;
import frc.robot.subsystems.linear.Linear;
import frc.robot.subsystems.linear.LinearConstants;
-import frc.robot.subsystems.revRotary.RevRotarySubsystem;
-import frc.robot.subsystems.revRotary.RevRotarySubsystemConstants;
-import frc.robot.subsystems.rotary.RotarySubsystem;
-import frc.robot.subsystems.rotary.RotarySubsystemConstants;
-import frc.robot.subsystems.rotary.RotarySubsystem.Setpoint;
+import frc.robot.subsystems.rotary.Rotary;
+import frc.robot.subsystems.rotary.RotaryConstants;
+import frc.robot.subsystems.rotary.Rotary.Setpoint;
import frc.robot.subsystems.servo1.Servo1;
import frc.robot.subsystems.servo1.Servo1Constants;
import frc.robot.subsystems.vision.Vision;
@@ -111,8 +109,8 @@ public class RobotContainer {
private final Flywheel flywheel;
private final Linear linear;
private final Vision vision;
- private final RotarySubsystem rotary;
- private final RevRotarySubsystem revRotary;
+ private final Rotary rotary;
+ private final ObjectDetector objectDetector;
// Controller
private final CommandXboxControllerExtended controller = new CommandXboxControllerExtended(0);
@@ -129,99 +127,16 @@ public class RobotContainer {
*/
public RobotContainer()
{
- switch (Constants.currentMode) {
- case REAL -> {
- // Real robot, instantiate hardware IO implementations
- drive = new Drive(
- new GyroIOPigeon2(),
- new ModuleIOTalonFX(DriveConstants.FrontLeft),
- new ModuleIOTalonFX(DriveConstants.FrontRight),
- new ModuleIOTalonFX(DriveConstants.BackLeft),
- new ModuleIOTalonFX(DriveConstants.BackRight));
-
- leds = new LEDs(LEDsConstants.getLightsIOReal());
- laserCAN1 = new LaserCAN1(LaserCAN1Constants.getReal(), drive);
- beamBreak1 = new BeamBreak1(BeamBreak1Constants.getReal());
- servo1 = new Servo1(Servo1Constants.getReal());
- flywheel = new Flywheel(FlywheelConstants.getReal());
-
- linear = new Linear(LinearConstants.getReal());
-
- visionSim = Optional.empty();
- vision = new Vision(
- drive::addVisionMeasurement,
- () -> drive.getTimestampedHeading(),
- new VisionIOPhotonVision(
- VisionConstants.camera0Name,
- VisionConstants.robotToCamera0,
- VisionConstants.aprilTagLayout,
- PoseStrategy.CONSTRAINED_SOLVEPNP));
- rotary = new RotarySubsystem(RotarySubsystemConstants.getReal());
- revRotary = new RevRotarySubsystem(RevRotarySubsystemConstants.getReal());
- }
-
- case SIM -> {
- // Sim robot, instantiate physics sim IO implementations
- drive = new Drive(
- new GyroIO() {},
- new ModuleIOSim(DriveConstants.FrontLeft),
- new ModuleIOSim(DriveConstants.FrontRight),
- new ModuleIOSim(DriveConstants.BackLeft),
- new ModuleIOSim(DriveConstants.BackRight));
-
- leds = new LEDs(LEDsConstants.getLightsIOSim());
- laserCAN1 =
- new LaserCAN1(LaserCAN1Constants.getSim(), drive);
- beamBreak1 = new BeamBreak1(
- BeamBreak1Constants.getSim());
- servo1 = new Servo1(Servo1Constants.getSim());
- flywheel = new Flywheel(FlywheelConstants.getSim());
-
- linear = new Linear(LinearConstants.getSim());
-
- visionSim = Optional.of(VisionConstants.getSystemSim());
- vision = new Vision(
- drive::addVisionMeasurement,
- () -> drive.getTimestampedHeading(),
- new VisionIOPhotonVisionSim(
- () -> drive.getPose(),
- VisionConstants.camera0Name,
- VisionConstants.robotToCamera0,
- VisionConstants.aprilTagLayout,
- PoseStrategy.CONSTRAINED_SOLVEPNP,
- visionSim.get()));
- rotary = new RotarySubsystem(RotarySubsystemConstants.getSim());
- revRotary = new RevRotarySubsystem(RevRotarySubsystemConstants.getSim());
- }
-
- default -> {
- // Replayed robot, disable IO implementations
- drive = new Drive(
- new GyroIO() {},
- new ModuleIO() {},
- new ModuleIO() {},
- new ModuleIO() {},
- new ModuleIO() {});
-
- leds = new LEDs(LEDsConstants.getLightsIOReplay());
- laserCAN1 =
- new LaserCAN1(LaserCAN1Constants.getReplay(), drive);
- beamBreak1 =
- new BeamBreak1(BeamBreak1Constants.getReplay());
- servo1 = new Servo1(Servo1Constants.getReplay());
- flywheel = new Flywheel(FlywheelConstants.getReplay());
-
- linear = new Linear(LinearConstants.getReplay());
- rotary = new RotarySubsystem(RotarySubsystemConstants.getReplay());
- revRotary = new RevRotarySubsystem(RevRotarySubsystemConstants.getReplay());
-
- visionSim = Optional.empty();
- vision = new Vision(
- drive::addVisionMeasurement,
- () -> drive.getTimestampedHeading(),
- new VisionIO() {});
- }
- }
+ drive = DriveConstants.get();
+ laserCAN1 = LaserCAN1Constants.get();
+ flywheel = FlywheelConstants.get();
+ leds = LEDsConstants.get();
+ beamBreak1 = BeamBreak1Constants.get();
+ linear = LinearConstants.get();
+ rotary = RotaryConstants.get();
+ servo1 = Servo1Constants.get();
+ vision = VisionConstants.get(drive); // TODO: Will be refactored in the future to RobotState
+ objectDetector = ObjectDetectorConstants.get(drive);
conditionalChooser = new LoggedDashboardChooser<>("Conditional Choice");
conditionalChooser.addOption("True", true);
@@ -311,11 +226,7 @@ private void configureButtonBindings()
SmartDashboard.putData("Linear: Home", linear.homeCommand());
SmartDashboard.putData("Rotary: Stow", rotary.setSetpoint(Rotary.Setpoint.STOW));
SmartDashboard.putData("Rotary: Raised",
- rotary.setSetpoint(RotarySubsystem.Setpoint.RAISED));
- SmartDashboard.putData("REV Rotary: Stow",
- revRotary.setSetpoint(RevRotarySubsystem.Setpoint.STOW));
- SmartDashboard.putData("REV Rotary: Raised",
- revRotary.setSetpoint(RevRotarySubsystem.Setpoint.RAISED));
+ rotary.setSetpoint(Rotary.Setpoint.RAISED));
LoggedTunableNumber ballVel = new LoggedTunableNumber("Ball Sim Velocity (fps)", 15);
SmartDashboard.putData("Shoot Ball", Commands
From 1e90100db1f69ef28b5e7e4fa4dd5241568015c0 Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Mon, 24 Nov 2025 15:27:52 -0500
Subject: [PATCH 07/29] merge dev
---
.../RevRotarySubsystemConstants.java | 4 +-
.../subsystems/rotary/RotaryConstants.java | 74 ++++++-------------
2 files changed, 24 insertions(+), 54 deletions(-)
diff --git a/src/main/java/frc/robot/subsystems/revRotary/RevRotarySubsystemConstants.java b/src/main/java/frc/robot/subsystems/revRotary/RevRotarySubsystemConstants.java
index f290992a..feb460a0 100644
--- a/src/main/java/frc/robot/subsystems/revRotary/RevRotarySubsystemConstants.java
+++ b/src/main/java/frc/robot/subsystems/revRotary/RevRotarySubsystemConstants.java
@@ -32,6 +32,7 @@
import frc.lib.io.motor.MotorIORevSim;
import frc.lib.io.motor.MotorIOSim;
import frc.lib.mechanisms.rotary.*;
+import frc.lib.mechanisms.rotary.RotaryMechanism.RotaryAxis;
import frc.lib.mechanisms.rotary.RotaryMechanism.RotaryMechCharacteristics;
import frc.robot.Ports;
import com.revrobotics.spark.ClosedLoopSlot;
@@ -64,7 +65,8 @@ public class RevRotarySubsystemConstants {
public static final Distance ARM_LENGTH = Meters.of(1.0);
public static final RotaryMechCharacteristics CONSTANTS =
- new RotaryMechCharacteristics(OFFSET, ARM_LENGTH, MIN_ANGLE, MAX_ANGLE, STARTING_ANGLE);
+ new RotaryMechCharacteristics(OFFSET, ARM_LENGTH, MIN_ANGLE, MAX_ANGLE, STARTING_ANGLE,
+ RotaryAxis.PITCH);
public static final Mass ARM_MASS = Kilograms.of(.01);
public static final DCMotor DCMOTOR = DCMotor.getKrakenX60(1);
diff --git a/src/main/java/frc/robot/subsystems/rotary/RotaryConstants.java b/src/main/java/frc/robot/subsystems/rotary/RotaryConstants.java
index 5432868c..546a6da3 100644
--- a/src/main/java/frc/robot/subsystems/rotary/RotaryConstants.java
+++ b/src/main/java/frc/robot/subsystems/rotary/RotaryConstants.java
@@ -158,59 +158,27 @@ public static CANcoderConfiguration getCANcoderConfig(boolean sim)
return config;
}
- /**
- * Creates the real robot implementation of the rotary mechanism.
- *
- *
- * This method instantiates the actual hardware objects (TalonFX motors and CANcoder) that will
- * be used when running on a real robot.
- *
- * @return A RotaryMechanismReal object configured with real hardware
- */
- public static RotaryMechanismReal getReal()
- {
- return new RotaryMechanismReal(
- new MotorIOTalonFX(NAME, getFXConfig(), Ports.RotarySubsystemMotorMain,
- new TalonFXFollower(Ports.RotarySubsystemMotorFollower, false)),
- CONSTANTS,
- Optional.of(new AbsoluteEncoderIOCANCoderSim(Ports.RotarySubsystemEncoder,
- NAME + "Encoder", getCANcoderConfig(false))));
- }
-
- /**
- * Creates the simulation implementation of the rotary mechanism.
- *
- *
- * This method creates a physics-based simulation of the mechanism using WPILib's simulation
- * classes. It models the motor, moment of inertia, and other physical properties to provide
- * realistic behavior in simulation.
- *
- * @return A RotaryMechanismSim object configured for physics simulation
- */
- public static RotaryMechanismSim getSim()
- {
- return new RotaryMechanismSim(
- new MotorIOTalonFXSim(
- NAME,
- getFXConfig(),
- Ports.RotarySubsystemMotorMain,
- new TalonFXFollower(Ports.RotarySubsystemMotorFollower, false)),
- DCMOTOR, MOI, false, CONSTANTS,
- Optional.of(new AbsoluteEncoderIOCANCoderSim(Ports.RotarySubsystemEncoder,
- NAME + "Encoder", getCANcoderConfig(true))));
- }
-
- /**
- * Creates the log replay implementation of the rotary mechanism.
- *
- *
- * This is used with AdvantageKit's log replay feature, which allows you to replay logged data
- * and debug robot code without having the actual robot or running simulation.
- *
- * @return A RotaryMechanism object for log replay
- */
- public static RotaryMechanism getReplay()
+ public static Rotary get()
{
- return new RotaryMechanism(NAME, CONSTANTS) {};
+ switch (Constants.currentMode) {
+ case REAL:
+ return new Rotary(new RotaryMechanismReal(
+ new MotorIOTalonFX(NAME, getFXConfig(), Ports.RotarySubsystemMotorMain,
+ new TalonFXFollower(Ports.RotarySubsystemMotorFollower, false)),
+ CONSTANTS,
+ Optional.of(new AbsoluteEncoderIOCANCoderSim(Ports.RotarySubsystemEncoder,
+ NAME + "Encoder", getCANcoderConfig(false)))));
+ case SIM:
+ return new Rotary(new RotaryMechanismSim(
+ new MotorIOTalonFXSim(NAME, getFXConfig(), Ports.RotarySubsystemMotorMain,
+ new TalonFXFollower(Ports.RotarySubsystemMotorFollower, false)),
+ DCMOTOR, MOI, false, CONSTANTS,
+ Optional.of(new AbsoluteEncoderIOCANCoderSim(Ports.RotarySubsystemEncoder,
+ NAME + "Encoder", getCANcoderConfig(true)))));
+ case REPLAY:
+ return new Rotary(new RotaryMechanism(NAME, CONSTANTS) {});
+ default:
+ throw new IllegalStateException("Unrecognized Robot Mode");
+ }
}
}
From b9de937b7628a32a5deaf6de4dd3e98981a9158d Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Mon, 24 Nov 2025 17:40:26 -0500
Subject: [PATCH 08/29] partially working
---
.../java/frc/lib/io/motor/MotorIORev.java | 13 +----
.../mechanisms/rotary/RotaryMechanism.java | 9 +++
.../frc/lib/util/LoggedTunableNumber.java | 19 ++++--
src/main/java/frc/robot/RobotContainer.java | 9 ++-
...RevRotarySubsystem.java => RevRotary.java} | 37 +++++++-----
...Constants.java => RevRotaryConstants.java} | 58 ++++++++++++++-----
.../frc/robot/subsystems/rotary/Rotary.java | 26 ++++++---
.../subsystems/rotary/RotaryConstants.java | 3 -
8 files changed, 118 insertions(+), 56 deletions(-)
rename src/main/java/frc/robot/subsystems/revRotary/{RevRotarySubsystem.java => RevRotary.java} (64%)
rename src/main/java/frc/robot/subsystems/revRotary/{RevRotarySubsystemConstants.java => RevRotaryConstants.java} (76%)
diff --git a/src/main/java/frc/lib/io/motor/MotorIORev.java b/src/main/java/frc/lib/io/motor/MotorIORev.java
index ba8170ab..22c73157 100644
--- a/src/main/java/frc/lib/io/motor/MotorIORev.java
+++ b/src/main/java/frc/lib/io/motor/MotorIORev.java
@@ -100,20 +100,11 @@ protected com.revrobotics.spark.SparkBase.ControlType getRevControlType(ControlT
* system and can be manually controlled by the user. It also configures various settings for
* motion profiling and closed-loop control.
*
+ * @param name The name of the motor for identification purposes.
* @param id The unique identifier for the motor (CAN ID).
* @param isFlex Boolean indicating whether the motor is a SparkFlex (true) or a SparkMax
* (false).
- * @param gearRatio The gear ratio applied to the motor's output.
- * @param wheelRadius The radius of the wheel attached to the motor, used for motion
- * calculations.
- * @param isBrake Boolean indicating whether the motor should use brake mode (true) or coast
- * mode (false).
- * @param inverted Boolean indicating whether the motor should be inverted relative to the
- * control signal.
- * @param motorType The type of motor (e.g., brushless, brushed) to configure.
- * @param motionProfile The motion profile settings for the motor, including PID and velocity
- * parameters.
- *
+ * @param config The configuration settings for the motor.
* @see MotorIO
*/
public MotorIORev(
diff --git a/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanism.java b/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanism.java
index e0d8f1cd..6b1401b0 100644
--- a/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanism.java
+++ b/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanism.java
@@ -7,8 +7,10 @@
import frc.lib.io.motor.MotorIO.ControlType;
import frc.lib.io.motor.MotorInputsAutoLogged;
import frc.lib.mechanisms.Mechanism;
+import static edu.wpi.first.units.Units.Degree;
import java.util.Optional;
import java.util.function.Supplier;
+import org.littletonrobotics.junction.Logger;
import edu.wpi.first.math.MathUtil;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Translation3d;
@@ -24,6 +26,8 @@ public static enum RotaryAxis {
ROLL
}
+ private final String name;
+
public static record RotaryMechCharacteristics(
Translation3d offset,
Distance armLength,
@@ -39,6 +43,7 @@ public static record RotaryMechCharacteristics(
public RotaryMechanism(String name, RotaryMechCharacteristics characteristics)
{
+ this.name = name;
visualizer = new RotaryVisualizer(name, characteristics);
}
@@ -75,6 +80,10 @@ public void periodic()
visualizer.setCurrentAngle(inputs.position);
visualizer.setTrajectoryAngle(getTrajectoryAngle());
visualizer.setGoalAngle(getGoalAngle());
+
+ Logger.recordOutput(
+ this.name + "/PositionDegrees",
+ inputs.position.in(Degree));
}
@Override
diff --git a/src/main/java/frc/lib/util/LoggedTunableNumber.java b/src/main/java/frc/lib/util/LoggedTunableNumber.java
index 50f4526c..0db3a4e9 100644
--- a/src/main/java/frc/lib/util/LoggedTunableNumber.java
+++ b/src/main/java/frc/lib/util/LoggedTunableNumber.java
@@ -69,9 +69,6 @@ public void initDefault(double defaultValue)
if (!hasDefault) {
hasDefault = true;
this.defaultValue = defaultValue;
- if (Constants.tuningMode) {
- dashboardNumber = new LoggedNetworkNumber(key, defaultValue);
- }
}
}
@@ -84,8 +81,22 @@ public double get()
{
if (!hasDefault) {
return 0.0;
+ } else if (Constants.tuningMode) {
+ if (dashboardNumber == null) {
+ dashboardNumber = new LoggedNetworkNumber(key, defaultValue);
+ }
+ double value = dashboardNumber.get();
+ if (key.contains("STOW")) {
+ System.out.println("TunableNumber " + key + " returning dashboard value: " + value);
+ }
+ return value;
} else {
- return Constants.tuningMode ? dashboardNumber.get() : defaultValue;
+ if (key.contains("STOW")) {
+ System.out
+ .println("TunableNumber " + key + " returning default value: " + defaultValue
+ + " (tuningMode=" + Constants.tuningMode + ")");
+ }
+ return defaultValue;
}
}
diff --git a/src/main/java/frc/robot/RobotContainer.java b/src/main/java/frc/robot/RobotContainer.java
index 8fc784a4..cb197564 100644
--- a/src/main/java/frc/robot/RobotContainer.java
+++ b/src/main/java/frc/robot/RobotContainer.java
@@ -35,6 +35,7 @@
import frc.lib.io.vision.VisionIO;
import frc.lib.io.vision.VisionIOPhotonVision;
import frc.lib.io.vision.VisionIOPhotonVisionSim;
+import frc.lib.mechanisms.rotary.RotaryMechanism;
import frc.lib.util.LoggedDashboardChooser;
import frc.lib.util.LoggedTunableNumber;
import frc.lib.util.LoggedTuneableProfiledPID;
@@ -77,6 +78,8 @@
import frc.robot.subsystems.vision.VisionConstants;
import frc.robot.subsystems.objectDetector.ObjectDetector;
import frc.robot.subsystems.objectDetector.ObjectDetectorConstants;
+import frc.robot.subsystems.revRotary.RevRotary;
+import frc.robot.subsystems.revRotary.RevRotaryConstants;
import frc.robot.util.BallSimulator;
import frc.robot.subsystems.lasercan1.LaserCAN1;
import frc.robot.subsystems.lasercan1.LaserCAN1Constants;
@@ -110,6 +113,7 @@ public class RobotContainer {
private final Linear linear;
private final Vision vision;
private final Rotary rotary;
+ private final RevRotary revRotary;
private final ObjectDetector objectDetector;
// Controller
@@ -134,6 +138,7 @@ public RobotContainer()
beamBreak1 = BeamBreak1Constants.get();
linear = LinearConstants.get();
rotary = RotaryConstants.get();
+ revRotary = RevRotaryConstants.get();
servo1 = Servo1Constants.get();
vision = VisionConstants.get(drive); // TODO: Will be refactored in the future to RobotState
objectDetector = ObjectDetectorConstants.get(drive);
@@ -227,7 +232,9 @@ private void configureButtonBindings()
SmartDashboard.putData("Rotary: Stow", rotary.setSetpoint(Rotary.Setpoint.STOW));
SmartDashboard.putData("Rotary: Raised",
rotary.setSetpoint(Rotary.Setpoint.RAISED));
-
+ SmartDashboard.putData("RevRotary: Stow", revRotary.setSetpoint(RevRotary.Setpoint.STOW));
+ SmartDashboard.putData("RevRotary: Raised",
+ revRotary.setSetpoint(RevRotary.Setpoint.RAISED));
LoggedTunableNumber ballVel = new LoggedTunableNumber("Ball Sim Velocity (fps)", 15);
SmartDashboard.putData("Shoot Ball", Commands
.runOnce(() -> BallSimulator.launch(FeetPerSecond.of(ballVel.getAsDouble()),
diff --git a/src/main/java/frc/robot/subsystems/revRotary/RevRotarySubsystem.java b/src/main/java/frc/robot/subsystems/revRotary/RevRotary.java
similarity index 64%
rename from src/main/java/frc/robot/subsystems/revRotary/RevRotarySubsystem.java
rename to src/main/java/frc/robot/subsystems/revRotary/RevRotary.java
index ce5d6bdb..312f47db 100644
--- a/src/main/java/frc/robot/subsystems/revRotary/RevRotarySubsystem.java
+++ b/src/main/java/frc/robot/subsystems/revRotary/RevRotary.java
@@ -14,55 +14,65 @@
import frc.lib.mechanisms.rotary.RotaryMechanism;
import frc.lib.util.LoggedTunableNumber;
import frc.lib.util.LoggerHelper;
+import frc.robot.RobotState;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
-public class RevRotarySubsystem extends SubsystemBase {
+public class RevRotary extends SubsystemBase {
private final RotaryMechanism io;
+ private final RobotState robotstate;
+ private Setpoint setpoint = Setpoint.STOW;
- private static final LoggedTunableNumber STOW_SETPOINT = new LoggedTunableNumber("TEST", 0.0);
+ private static final LoggedTunableNumber STOW_SETPOINT =
+ new LoggedTunableNumber("REV TEST", 0.0);
private static final LoggedTunableNumber RAISED_SETPOINT =
- new LoggedTunableNumber("RAISED", 90);
+ new LoggedTunableNumber("REV RAISED", 90);
@RequiredArgsConstructor
- @SuppressWarnings("Immutable")
@Getter
public enum Setpoint {
- STOW(Degrees.of(STOW_SETPOINT.get())),
- RAISED(Degrees.of(RAISED_SETPOINT.get()));
+ STOW(STOW_SETPOINT),
+ RAISED(RAISED_SETPOINT);
- private final Angle setpoint;
+ private final LoggedTunableNumber tunableNumber;
+
+ public Angle getSetpoint()
+ {
+ return Degrees.of(tunableNumber.get());
+ }
}
- public RevRotarySubsystem(RotaryMechanism io)
+ public RevRotary(RotaryMechanism io)
{
this.io = io;
+ this.robotstate = RobotState.getInstance();
- setSetpoint(RevRotarySubsystemConstants.DEFAULT_SETPOINT).ignoringDisable(true).schedule();
+ setSetpoint(RevRotaryConstants.DEFAULT_SETPOINT).ignoringDisable(true).schedule();
}
@Override
public void periodic()
{
- LoggerHelper.recordCurrentCommand(RevRotarySubsystemConstants.NAME, this);
+ LoggerHelper.recordCurrentCommand(RevRotaryConstants.NAME, this);
io.periodic();
+ // robotstate.setRotaryPose(io.getPoseSupplier().get());
}
public Command setSetpoint(Setpoint setpoint)
{
return this.runOnce(
() -> io.runPosition(setpoint.getSetpoint(),
- RevRotarySubsystemConstants.CRUISE_VELOCITY,
- RevRotarySubsystemConstants.ACCELERATION, RevRotarySubsystemConstants.JERK,
+ RevRotaryConstants.CRUISE_VELOCITY,
+ RevRotaryConstants.ACCELERATION, RevRotaryConstants.JERK,
PIDSlot.SLOT_0))
.withName("Go To " + setpoint.toString() + " Setpoint");
};
public boolean nearGoal(Angle targetPosition)
{
- return io.nearGoal(targetPosition, RevRotarySubsystemConstants.TOLERANCE);
+ return io.nearGoal(targetPosition, RevRotaryConstants.TOLERANCE);
}
public Command waitUntilGoalCommand(Angle position)
@@ -88,4 +98,5 @@ public void close()
{
io.close();
}
+
}
diff --git a/src/main/java/frc/robot/subsystems/revRotary/RevRotarySubsystemConstants.java b/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java
similarity index 76%
rename from src/main/java/frc/robot/subsystems/revRotary/RevRotarySubsystemConstants.java
rename to src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java
index feb460a0..6fece74f 100644
--- a/src/main/java/frc/robot/subsystems/revRotary/RevRotarySubsystemConstants.java
+++ b/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java
@@ -11,10 +11,7 @@
import static edu.wpi.first.units.Units.Second;
import java.util.Optional;
import static edu.wpi.first.units.Units.Meters;
-import com.ctre.phoenix6.configs.*;
-import com.ctre.phoenix6.signals.FeedbackSensorSourceValue;
-import com.ctre.phoenix6.signals.InvertedValue;
-import com.ctre.phoenix6.signals.NeutralModeValue;
+import frc.robot.Constants;
import edu.wpi.first.math.geometry.Translation3d;
import edu.wpi.first.math.system.plant.DCMotor;
import edu.wpi.first.units.AngularAccelerationUnit;
@@ -43,7 +40,7 @@
/** Add your docs here. */
-public class RevRotarySubsystemConstants {
+public class RevRotaryConstants {
public static String NAME = "REVRotary";
public static final Angle TOLERANCE = Degrees.of(2.0);
@@ -56,6 +53,7 @@ public class RevRotarySubsystemConstants {
private static final double ROTOR_TO_SENSOR = (2.0 / 1.0);
private static final double SENSOR_TO_MECHANISM = (2.0 / 1.0);
+ private static final double GEAR_RATIO = ROTOR_TO_SENSOR * SENSOR_TO_MECHANISM;
public static final Translation3d OFFSET = Translation3d.kZero;
@@ -75,14 +73,14 @@ public class RevRotarySubsystemConstants {
private static final Angle ENCODER_OFFSET = Rotations.of(0.0);
- public static final RevRotarySubsystem.Setpoint DEFAULT_SETPOINT =
- RevRotarySubsystem.Setpoint.STOW;
+ public static final RevRotary.Setpoint DEFAULT_SETPOINT =
+ RevRotary.Setpoint.STOW;
// Positional PID
private static ClosedLoopConfig SLOT0CONFIG = new ClosedLoopConfig()
- .pid(30.0, 0, 0, ClosedLoopSlot.kSlot0);
+ .pid(30.0, 0, 5.0, ClosedLoopSlot.kSlot0); // Added D gain to match TalonFX config
/**
* Creates and returns the TalonFX motor controller configuration for the rotary mechanism.
@@ -108,6 +106,17 @@ public static SparkBaseConfig getREVConfig()
config.voltageCompensation(12.0);
config.idleMode(IdleMode.kBrake);
config.inverted(false);
+
+ // Add gear ratio configuration for position/velocity conversion
+ config.encoder.positionConversionFactor(1.0 / GEAR_RATIO);
+ config.encoder.velocityConversionFactor(1.0 / GEAR_RATIO / 60.0); // RPM to RPS
+
+ // Add soft limits to match TalonFX behavior
+ config.softLimit.forwardSoftLimit(MAX_ANGLE.in(Rotations));
+ config.softLimit.forwardSoftLimitEnabled(false);
+ config.softLimit.reverseSoftLimit(MIN_ANGLE.in(Rotations));
+ config.softLimit.reverseSoftLimitEnabled(false);
+
config.apply(SLOT0CONFIG);
return config;
@@ -122,11 +131,11 @@ public static SparkBaseConfig getREVConfig()
*
* @return A RotaryMechanismReal object configured with real hardware
*/
- public static RotaryMechanismReal getReal()
+ public static RevRotary getReal()
{
MotorIO io = new MotorIORev(NAME, Ports.RotarySubsystemMotorMain, true, getREVConfig());
- return new RotaryMechanismReal(io, CONSTANTS, null);
+ return new RevRotary(new RotaryMechanismReal(io, CONSTANTS, Optional.empty()));
}
/**
@@ -139,7 +148,7 @@ public static RotaryMechanismReal getReal()
*
* @return A RotaryMechanismSim object configured for physics simulation
*/
- public static RotaryMechanismSim getSim()
+ public static RevRotary getSim()
{
MotorIOSim io = new MotorIORevSim(
NAME,
@@ -148,13 +157,13 @@ public static RotaryMechanismSim getSim()
DCMOTOR,
getREVConfig());
- return new RotaryMechanismSim(
+ return new RevRotary(new RotaryMechanismSim(
io,
DCMOTOR,
MOI,
false,
CONSTANTS,
- Optional.empty());
+ Optional.empty()));
}
/**
@@ -166,8 +175,27 @@ public static RotaryMechanismSim getSim()
*
* @return A RotaryMechanism object for log replay
*/
- public static RotaryMechanism getReplay()
+ public static RevRotary getReplay()
+ {
+ return new RevRotary(new RotaryMechanism(NAME, CONSTANTS) {});
+ }
+
+ /**
+ * Method to get the appropriate RotaryMechanism based on the current robot mode.
+ *
+ * @return RotaryMechanism instance for the current mode (real, sim, or replay)
+ */
+ public static RevRotary get()
{
- return new RotaryMechanism(NAME, CONSTANTS) {};
+ switch (Constants.currentMode) {
+ case REAL:
+ return getReal();
+ case SIM:
+ return getSim();
+ case REPLAY:
+ return getReplay();
+ default:
+ throw new IllegalStateException("Unrecognized Robot Mode");
+ }
}
}
diff --git a/src/main/java/frc/robot/subsystems/rotary/Rotary.java b/src/main/java/frc/robot/subsystems/rotary/Rotary.java
index 1600fcc3..b497ffc1 100644
--- a/src/main/java/frc/robot/subsystems/rotary/Rotary.java
+++ b/src/main/java/frc/robot/subsystems/rotary/Rotary.java
@@ -26,19 +26,27 @@
public class Rotary extends SubsystemBase {
private final RotaryMechanism io;
- private static final LoggedTunableNumber STOW_SETPOINT = new LoggedTunableNumber("TEST", 0.0);
- private static final LoggedTunableNumber RAISED_SETPOINT =
- new LoggedTunableNumber("RAISED", -90);
+ private static LoggedTunableNumber STOW_SETPOINT =
+ new LoggedTunableNumber("Rotary STOW", 0.0);
+ private static LoggedTunableNumber RAISED_SETPOINT =
+ new LoggedTunableNumber("Rotary RAISED", -90);
@RequiredArgsConstructor
- @SuppressWarnings("Immutable")
@Getter
public enum Setpoint {
- HOME(Degrees.of(0.0)),
- STOW(Degrees.of(STOW_SETPOINT.get())),
- RAISED(Degrees.of(RAISED_SETPOINT.get()));
-
- private final Angle setpoint;
+ HOME(null),
+ STOW(STOW_SETPOINT),
+ RAISED(RAISED_SETPOINT);
+
+ private final LoggedTunableNumber tunableNumber;
+
+ public Angle getSetpoint()
+ {
+ if (tunableNumber == null) {
+ return Degrees.of(0.0);
+ }
+ return Degrees.of(tunableNumber.get());
+ }
}
private Debouncer homeDebouncer = new Debouncer(0.1, DebounceType.kRising);
diff --git a/src/main/java/frc/robot/subsystems/rotary/RotaryConstants.java b/src/main/java/frc/robot/subsystems/rotary/RotaryConstants.java
index 546a6da3..344c0b3a 100644
--- a/src/main/java/frc/robot/subsystems/rotary/RotaryConstants.java
+++ b/src/main/java/frc/robot/subsystems/rotary/RotaryConstants.java
@@ -28,7 +28,6 @@
import edu.wpi.first.units.measure.Velocity;
import edu.wpi.first.wpilibj.simulation.SingleJointedArmSim;
import frc.lib.io.absoluteencoder.AbsoluteEncoderIOCANCoderSim;
-import frc.lib.io.motor.MotorIORevSim;
import frc.lib.io.motor.MotorIOTalonFX;
import frc.lib.io.motor.MotorIOTalonFX.TalonFXFollower;
import frc.lib.io.motor.MotorIOTalonFXSim;
@@ -37,8 +36,6 @@
import frc.lib.mechanisms.rotary.RotaryMechanism.RotaryMechCharacteristics;
import frc.robot.Constants;
import frc.robot.Ports;
-import com.revrobotics.spark.config.SparkFlexConfig;
-import com.revrobotics.spark.config.SparkBaseConfig.IdleMode;
public class RotaryConstants {
From 6bdb24fbce21b13c546c09c50d6c95dce1664d6c Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Mon, 24 Nov 2025 17:51:53 -0500
Subject: [PATCH 09/29] rev sim mostly working
---
src/main/java/frc/lib/mechanisms/rotary/RotaryMechanism.java | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanism.java b/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanism.java
index 6b1401b0..ab6b500c 100644
--- a/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanism.java
+++ b/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanism.java
@@ -8,6 +8,8 @@
import frc.lib.io.motor.MotorInputsAutoLogged;
import frc.lib.mechanisms.Mechanism;
import static edu.wpi.first.units.Units.Degree;
+import static edu.wpi.first.units.Units.Degrees;
+import static edu.wpi.first.units.Units.Radian;
import java.util.Optional;
import java.util.function.Supplier;
import org.littletonrobotics.junction.Logger;
@@ -83,7 +85,7 @@ public void periodic()
Logger.recordOutput(
this.name + "/PositionDegrees",
- inputs.position.in(Degree));
+ inputs.position.in(Degrees));
}
@Override
From ee5d54974a926eb7b89c5e6931124fe0287d0c31 Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Mon, 24 Nov 2025 18:01:43 -0500
Subject: [PATCH 10/29] working rev sim
---
.../java/frc/robot/subsystems/revRotary/RevRotary.java | 2 +-
.../robot/subsystems/revRotary/RevRotaryConstants.java | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/main/java/frc/robot/subsystems/revRotary/RevRotary.java b/src/main/java/frc/robot/subsystems/revRotary/RevRotary.java
index 312f47db..7363dd25 100644
--- a/src/main/java/frc/robot/subsystems/revRotary/RevRotary.java
+++ b/src/main/java/frc/robot/subsystems/revRotary/RevRotary.java
@@ -25,7 +25,7 @@ public class RevRotary extends SubsystemBase {
private Setpoint setpoint = Setpoint.STOW;
private static final LoggedTunableNumber STOW_SETPOINT =
- new LoggedTunableNumber("REV TEST", 0.0);
+ new LoggedTunableNumber("REV STOW", 0.0);
private static final LoggedTunableNumber RAISED_SETPOINT =
new LoggedTunableNumber("REV RAISED", 90);
diff --git a/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java b/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java
index 6fece74f..d1aebc10 100644
--- a/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java
+++ b/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java
@@ -112,10 +112,10 @@ public static SparkBaseConfig getREVConfig()
config.encoder.velocityConversionFactor(1.0 / GEAR_RATIO / 60.0); // RPM to RPS
// Add soft limits to match TalonFX behavior
- config.softLimit.forwardSoftLimit(MAX_ANGLE.in(Rotations));
- config.softLimit.forwardSoftLimitEnabled(false);
- config.softLimit.reverseSoftLimit(MIN_ANGLE.in(Rotations));
- config.softLimit.reverseSoftLimitEnabled(false);
+ // config.softLimit.forwardSoftLimit(MAX_ANGLE.in(Rotations));
+ // config.softLimit.forwardSoftLimitEnabled(false);
+ // config.softLimit.reverseSoftLimit(MIN_ANGLE.in(Rotations));
+ // config.softLimit.reverseSoftLimitEnabled(false);
config.apply(SLOT0CONFIG);
From 53a3377028890184315f88782ef450bbd59d1cce Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Tue, 25 Nov 2025 13:35:41 -0500
Subject: [PATCH 11/29] Refactor rotary subsystem
---
.github/instructions/default.instructions.md | 82 +++++++
src/main/java/frc/lib/io/motor/MotorIO.java | 65 +++++-
.../java/frc/lib/io/motor/MotorIORev.java | 6 +-
.../java/frc/lib/io/motor/MotorIORevSim.java | 14 +-
.../java/frc/lib/mechanisms/Mechanism.java | 78 +++++--
.../mechanisms/rotary/RotaryMechanism.java | 2 -
.../rotary/RotaryMechanismReal.java | 12 ++
.../mechanisms/rotary/RotaryMechanismSim.java | 18 ++
src/main/java/frc/robot/Ports.java | 2 +
.../robot/subsystems/revRotary/RevRotary.java | 2 -
.../revRotary/RevRotaryConstants.java | 2 +-
.../frc/robot/subsystems/rotary/Rotary.java | 5 -
.../subsystems/revRotary/RevRotaryTest.java | 202 ++++++++++++++++++
.../robot/subsystems/rotary/RotaryTest.java | 95 +++++++-
14 files changed, 526 insertions(+), 59 deletions(-)
create mode 100644 .github/instructions/default.instructions.md
create mode 100644 src/test/java/frc/robot/subsystems/revRotary/RevRotaryTest.java
diff --git a/.github/instructions/default.instructions.md b/.github/instructions/default.instructions.md
new file mode 100644
index 00000000..1458f4f4
--- /dev/null
+++ b/.github/instructions/default.instructions.md
@@ -0,0 +1,82 @@
+# FRC Team W8 Library - AI Assistant Instructions
+
+You are a sr Java engineer with 10+ years of expereince. You are helping high school students on an FRC (FIRST Robotics Competition) team. Write clear, educational code that students can learn from and maintain.
+
+## Technology Stack
+
+- **Language**: Java
+- **Framework**: WPILib - https://frcdocs.wpi.edu/en/latest/docs/software/what-is-wpilib.html
+- **Vendor Libraries**:
+ - CTRE (Phoenix 6) for motors/sensors: https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/api-usage/api-overview.html
+ - REV (REVLib) for motors/sensors: https://docs.revrobotics.com/revlib
+- **Logging**: AdvantageKit - https://docs.advantagekit.org/
+ - Logs are viewed in AdvantageScope
+
+## Library Architecture (`lib` folder)
+
+This codebase uses a **hardware abstraction layer** to make robot code portable and testable:
+
+### Key Patterns
+
+1. **IO Interfaces**: Define hardware operations (e.g., `ElevatorIO`, `DriveIO`)
+ - Methods for reading sensors and controlling actuators
+ - Contain nested `Inputs` class for sensor data logged by AdvantageKit
+
+2. **IO Implementations**: Hardware-specific code (e.g., `ElevatorIOSparkMax`, `ElevatorIOSim`)
+ - Real hardware implementation uses vendor libraries (CTRE, REV)
+ - Simulation implementations for testing without hardware
+ - Keep vendor-specific code isolated here
+
+3. **Subsystems**: High-level robot logic extends WPILib's `SubsystemBase`
+ - Accept an `IO` interface in the constructor (dependency injection)
+ - Contain robot behavior and state machines
+ - Call `Logger.processInputs()` to log sensor data
+
+### Best Practices
+
+- **Keep subsystems hardware-agnostic**: They should only use the IO interface, never vendor classes directly
+- **Log everything**: Use AdvantageKit's `@AutoLog` for all sensor inputs
+- **One IO implementation per hardware type**: Separate real vs. simulation, different motor controllers, etc.
+- **Throw from default interface methods**: Default methods in IO interfaces should throw `NotImplementedException` to ensure they are explicitly implemented where needed.
+- **Commands for actions**: Use WPILib's command-based framework for robot behaviors
+- **Constants in separate classes**: Keep tunable values organized and easy to find
+
+### Example Usage
+
+```java
+// IO Interface defines contract
+public interface DriveIO {
+ @AutoLog
+ class DriveInputs {
+ double leftVelocityMPS = 0.0;
+ double rightVelocityMPS = 0.0;
+ }
+
+ void updateInputs(DriveInputs inputs);
+ void setVoltage(double left, double right);
+}
+
+// Subsystem uses IO interface
+public class Drive extends SubsystemBase {
+ private final DriveIO io;
+ private final DriveInputsAutoLogged inputs = new DriveInputsAutoLogged();
+
+ public Drive(DriveIO io) {
+ this.io = io;
+ }
+
+ @Override
+ public void periodic() {
+ io.updateInputs(inputs);
+ Logger.processInputs("Drive", inputs);
+ }
+}
+```
+
+## Guidance for Students
+
+- Explain your code with comments when introducing new concepts
+- Prefer readability over cleverness
+- Break complex logic into small, named methods
+- Use descriptive variable names (`leftMotorVolts` not `lmv`)
+- Test code in simulation before deploying to robot hardware
diff --git a/src/main/java/frc/lib/io/motor/MotorIO.java b/src/main/java/frc/lib/io/motor/MotorIO.java
index 5606904c..237d5228 100644
--- a/src/main/java/frc/lib/io/motor/MotorIO.java
+++ b/src/main/java/frc/lib/io/motor/MotorIO.java
@@ -21,6 +21,7 @@
import static edu.wpi.first.units.Units.RadiansPerSecond;
import static edu.wpi.first.units.Units.Volts;
+import org.apache.commons.lang3.NotImplementedException;
import org.littletonrobotics.junction.AutoLog;
import edu.wpi.first.units.AngularAccelerationUnit;
@@ -97,7 +98,7 @@ abstract class MotorInputs {
*/
public default String getName()
{
- return "";
+ throw new NotImplementedException("This method has not been implemented");
}
/**
@@ -107,19 +108,25 @@ public default String getName()
* @param inputs The structure to populate with updated sensor values.
*/
public default void updateInputs(MotorInputs inputs)
- {}
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
/**
* Sets the motor to coast mode.
*/
public default void runCoast()
- {}
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
/**
* Sets the motor to brake mode.
*/
public default void runBrake()
- {}
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
/**
* Runs the motor using direct voltage control.
@@ -127,7 +134,9 @@ public default void runBrake()
* @param voltage Desired voltage output.
*/
public default void runVoltage(Voltage voltage)
- {}
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
/**
* Runs the motor with a specified current output.
@@ -135,7 +144,9 @@ public default void runVoltage(Voltage voltage)
* @param current Desired torque-producing current.
*/
public default void runCurrent(Current current)
- {}
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
/**
* Runs the motor with a specified current output and duty cycle.
@@ -144,7 +155,9 @@ public default void runCurrent(Current current)
* @param dutyCycle Desired dutycycle of current output, limiting top speed
*/
public default void runCurrent(Current current, double dutyCycle)
- {}
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
/**
* Runs the motor using duty cycle (percentage of available voltage).
@@ -152,7 +165,9 @@ public default void runCurrent(Current current, double dutyCycle)
* @param dutyCycle Fractional output between 0 and 1.
*/
public default void runDutyCycle(double dutyCycle)
- {}
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
/**
* Runs the motor to a specific position.
@@ -166,7 +181,20 @@ public default void runDutyCycle(double dutyCycle)
public default void runPosition(Angle position, AngularVelocity cruiseVelocity,
AngularAcceleration acceleration,
Velocity maxJerk, PIDSlot slot)
- {}
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
+
+ /**
+ * Runs the motor to a specific position.
+ *
+ * @param position Target position.
+ * @param slot PID slot index.
+ */
+ public default void runPosition(Angle position, PIDSlot slot)
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
/**
* Runs the motor at a target velocity.
@@ -177,7 +205,20 @@ public default void runPosition(Angle position, AngularVelocity cruiseVelocity,
*/
public default void runVelocity(AngularVelocity velocity, AngularAcceleration acceleration,
PIDSlot slot)
- {}
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
+
+ /**
+ * Runs the motor at a target velocity.
+ *
+ * @param velocity Desired velocity.
+ * @param slot PID slot index.
+ */
+ public default void runVelocity(AngularVelocity velocity, PIDSlot slot)
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
/**
* Sets the position of the motor's internal encoder
@@ -185,5 +226,7 @@ public default void runVelocity(AngularVelocity velocity, AngularAcceleration ac
* @param position Desired position to set encoder to
*/
public default void setEncoderPosition(Angle position)
- {}
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
}
diff --git a/src/main/java/frc/lib/io/motor/MotorIORev.java b/src/main/java/frc/lib/io/motor/MotorIORev.java
index 22c73157..646723cc 100644
--- a/src/main/java/frc/lib/io/motor/MotorIORev.java
+++ b/src/main/java/frc/lib/io/motor/MotorIORev.java
@@ -186,9 +186,7 @@ public void runDutyCycle(double percent)
}
@Override
- public void runPosition(Angle position, AngularVelocity cruiseVelocity,
- AngularAcceleration acceleration,
- Velocity maxJerk, PIDSlot slot)
+ public void runPosition(Angle position, PIDSlot slot)
{
controller.setReference(position.in(Rotation),
getRevControlType(ControlType.POSITION),
@@ -197,7 +195,7 @@ public void runPosition(Angle position, AngularVelocity cruiseVelocity,
@Override
- public void runVelocity(AngularVelocity velocity, AngularAcceleration acceleration,
+ public void runVelocity(AngularVelocity velocity,
PIDSlot slot)
{
controller.setReference(velocity.in(RotationsPerSecond) * 60,
diff --git a/src/main/java/frc/lib/io/motor/MotorIORevSim.java b/src/main/java/frc/lib/io/motor/MotorIORevSim.java
index 16761689..bc0d838f 100644
--- a/src/main/java/frc/lib/io/motor/MotorIORevSim.java
+++ b/src/main/java/frc/lib/io/motor/MotorIORevSim.java
@@ -8,24 +8,16 @@
import static edu.wpi.first.units.Units.Volts;
import com.revrobotics.sim.SparkFlexSim;
-import com.revrobotics.spark.SparkBase;
import com.revrobotics.spark.SparkBase.PersistMode;
import com.revrobotics.spark.SparkBase.ResetMode;
import com.revrobotics.spark.SparkClosedLoopController;
import com.revrobotics.spark.SparkFlex;
-import com.revrobotics.spark.SparkLowLevel.MotorType;
-import com.revrobotics.spark.SparkMax;
import com.revrobotics.spark.config.SparkBaseConfig;
import edu.wpi.first.math.system.plant.DCMotor;
import com.revrobotics.spark.SparkSim;
-import com.revrobotics.spark.config.SparkFlexConfig;
-import com.revrobotics.spark.config.SparkMaxConfig;
-import edu.wpi.first.units.AngularAccelerationUnit;
import edu.wpi.first.units.measure.Angle;
-import edu.wpi.first.units.measure.AngularAcceleration;
import edu.wpi.first.units.measure.AngularVelocity;
-import edu.wpi.first.units.measure.Velocity;
import frc.lib.util.Device;
@@ -98,9 +90,7 @@ public MotorIORevSim(
}
@Override
- public void runPosition(Angle position, AngularVelocity cruiseVelocity,
- AngularAcceleration acceleration,
- Velocity maxJerk, PIDSlot slot)
+ public void runPosition(Angle position, PIDSlot slot)
{
simState.setPosition(position.in(Rotations));
@@ -108,7 +98,7 @@ public void runPosition(Angle position, AngularVelocity cruiseVelocity,
@Override
- public void runVelocity(AngularVelocity velocity, AngularAcceleration acceleration,
+ public void runVelocity(AngularVelocity velocity,
PIDSlot slot)
{
simState.setVelocity(velocity.in(RotationsPerSecond) * 60);
diff --git a/src/main/java/frc/lib/mechanisms/Mechanism.java b/src/main/java/frc/lib/mechanisms/Mechanism.java
index e0ab8ea7..2c294d3b 100644
--- a/src/main/java/frc/lib/mechanisms/Mechanism.java
+++ b/src/main/java/frc/lib/mechanisms/Mechanism.java
@@ -15,10 +15,8 @@
package frc.lib.mechanisms;
-import static edu.wpi.first.units.Units.Amps;
-import static edu.wpi.first.units.Units.Radians;
-import static edu.wpi.first.units.Units.RadiansPerSecond;
import java.util.function.Supplier;
+import org.apache.commons.lang3.NotImplementedException;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.units.AngularAccelerationUnit;
import edu.wpi.first.units.measure.Angle;
@@ -33,19 +31,25 @@ public interface Mechanism {
/** Call this method periodically */
public default void periodic()
- {}
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
/**
* Sets the mechanism to coast mode.
*/
public default void runCoast()
- {}
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
/**
* Sets the mechanism to brake mode.
*/
public default void runBrake()
- {}
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
/**
* Runs the mechanism using direct voltage control.
@@ -53,7 +57,9 @@ public default void runBrake()
* @param voltage Desired voltage output.
*/
public default void runVoltage(Voltage voltage)
- {}
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
/**
* Runs the mechanism with a specified current output.
@@ -61,7 +67,9 @@ public default void runVoltage(Voltage voltage)
* @param current Desired torque-producing current.
*/
public default void runCurrent(Current current)
- {}
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
/**
* Runs the mechanism using duty cycle (percentage of available voltage).
@@ -69,7 +77,9 @@ public default void runCurrent(Current current)
* @param dutyCycle Fractional output between 0 and 1.
*/
public default void runDutyCycle(double dutyCycle)
- {}
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
/**
* Runs the mechanism to a specific position.
@@ -83,7 +93,20 @@ public default void runDutyCycle(double dutyCycle)
public default void runPosition(Angle position, AngularVelocity cruiseVelocity,
AngularAcceleration acceleration,
Velocity maxJerk, PIDSlot slot)
- {}
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
+
+ /**
+ * Runs the mechanism to a specific position.
+ *
+ * @param position Target position.
+ * @param slot PID slot index.
+ */
+ public default void runPosition(Angle position, PIDSlot slot)
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
/**
* Runs the mechanism at a target velocity.
@@ -94,7 +117,20 @@ public default void runPosition(Angle position, AngularVelocity cruiseVelocity,
*/
public default void runVelocity(AngularVelocity velocity, AngularAcceleration acceleration,
PIDSlot slot)
- {}
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
+
+ /**
+ * Runs the mechanism at a target velocity.
+ *
+ * @param velocity Desired velocity.
+ * @param slot PID slot index.
+ */
+ public default void runVelocity(AngularVelocity velocity, PIDSlot slot)
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
/**
* Sets the position of the motor's internal encoder
@@ -102,11 +138,13 @@ public default void runVelocity(AngularVelocity velocity, AngularAcceleration ac
* @param position Desired position to set encoder to
*/
public default void setEncoderPosition(Angle position)
- {}
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
public default Current getSupplyCurrent()
{
- return Amps.of(0.0);
+ throw new NotImplementedException("This method has not been implemented");
}
/**
@@ -116,21 +154,23 @@ public default Current getSupplyCurrent()
*/
public default Angle getPosition()
{
- return Radians.of(0.0);
+ throw new NotImplementedException("This method has not been implemented");
}
public default Current getTorqueCurrent()
{
- return Amps.of(0);
+ throw new NotImplementedException("This method has not been implemented");
}
public default AngularVelocity getVelocity()
{
- return RadiansPerSecond.of(0.0);
+ throw new NotImplementedException("This method has not been implemented");
}
public default void close()
- {}
+ {
+ throw new NotImplementedException("This method has not been implemented");
+ }
/**
* Supplier for the Pose3d of the mechanism
@@ -139,6 +179,8 @@ public default void close()
*/
public default Supplier getPoseSupplier()
{
- return () -> Pose3d.kZero;
+ throw new NotImplementedException("This method has not been implemented");
}
+
+
}
diff --git a/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanism.java b/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanism.java
index ab6b500c..e763ab91 100644
--- a/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanism.java
+++ b/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanism.java
@@ -7,9 +7,7 @@
import frc.lib.io.motor.MotorIO.ControlType;
import frc.lib.io.motor.MotorInputsAutoLogged;
import frc.lib.mechanisms.Mechanism;
-import static edu.wpi.first.units.Units.Degree;
import static edu.wpi.first.units.Units.Degrees;
-import static edu.wpi.first.units.Units.Radian;
import java.util.Optional;
import java.util.function.Supplier;
import org.littletonrobotics.junction.Logger;
diff --git a/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanismReal.java b/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanismReal.java
index be56d0e9..afd44225 100644
--- a/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanismReal.java
+++ b/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanismReal.java
@@ -89,6 +89,18 @@ public void runPosition(Angle position, AngularVelocity cruiseVelocity,
io.runPosition(position, cruiseVelocity, acceleration, maxJerk, slot);
}
+ @Override
+ public void runPosition(Angle position, PIDSlot slot)
+ {
+ io.runPosition(position, slot);
+ }
+
+ @Override
+ public void runVelocity(AngularVelocity velocity, PIDSlot slot)
+ {
+ io.runVelocity(velocity, slot);
+ }
+
@Override
public void runVelocity(AngularVelocity velocity, AngularAcceleration acceleration,
PIDSlot slot)
diff --git a/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanismSim.java b/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanismSim.java
index 0934f1e7..ed508765 100644
--- a/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanismSim.java
+++ b/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanismSim.java
@@ -145,6 +145,18 @@ public void runPosition(Angle position, AngularVelocity cruiseVelocity,
io.runPosition(position, cruiseVelocity, acceleration, maxJerk, slot);
}
+ @Override
+ public void runPosition(Angle position, PIDSlot slot)
+ {
+ io.runPosition(position, slot);
+ }
+
+ @Override
+ public void runVelocity(AngularVelocity velocity, PIDSlot slot)
+ {
+ io.runVelocity(velocity, slot);
+ }
+
@Override
public void runVelocity(AngularVelocity velocity, AngularAcceleration acceleration,
PIDSlot slot)
@@ -163,4 +175,10 @@ public AngularVelocity getVelocity()
{
return inputs.velocity;
}
+
+ @Override
+ public void close()
+ {
+ io.close();
+ }
}
diff --git a/src/main/java/frc/robot/Ports.java b/src/main/java/frc/robot/Ports.java
index c4795462..872dfdb8 100644
--- a/src/main/java/frc/robot/Ports.java
+++ b/src/main/java/frc/robot/Ports.java
@@ -35,6 +35,8 @@ public class Ports {
public static final Device.CAN pdh = new CAN(50, "rio");
+ public static final Device.CAN revRotarySubsytemMotorMain = new CAN(7, "rio");
+
public static final Device.CAN RotarySubsystemMotorMain = new CAN(3, "rio");
public static final Device.CAN RotarySubsystemMotorFollower = new CAN(4, "rio");
public static final Device.CAN RotarySubsystemEncoder = new CAN(6, "rio");
diff --git a/src/main/java/frc/robot/subsystems/revRotary/RevRotary.java b/src/main/java/frc/robot/subsystems/revRotary/RevRotary.java
index 7363dd25..b8c9defc 100644
--- a/src/main/java/frc/robot/subsystems/revRotary/RevRotary.java
+++ b/src/main/java/frc/robot/subsystems/revRotary/RevRotary.java
@@ -64,8 +64,6 @@ public Command setSetpoint(Setpoint setpoint)
{
return this.runOnce(
() -> io.runPosition(setpoint.getSetpoint(),
- RevRotaryConstants.CRUISE_VELOCITY,
- RevRotaryConstants.ACCELERATION, RevRotaryConstants.JERK,
PIDSlot.SLOT_0))
.withName("Go To " + setpoint.toString() + " Setpoint");
};
diff --git a/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java b/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java
index d1aebc10..778b3979 100644
--- a/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java
+++ b/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java
@@ -133,7 +133,7 @@ public static SparkBaseConfig getREVConfig()
*/
public static RevRotary getReal()
{
- MotorIO io = new MotorIORev(NAME, Ports.RotarySubsystemMotorMain, true, getREVConfig());
+ MotorIO io = new MotorIORev(NAME, Ports.revRotarySubsytemMotorMain, true, getREVConfig());
return new RevRotary(new RotaryMechanismReal(io, CONSTANTS, Optional.empty()));
}
diff --git a/src/main/java/frc/robot/subsystems/rotary/Rotary.java b/src/main/java/frc/robot/subsystems/rotary/Rotary.java
index b497ffc1..f424c73e 100644
--- a/src/main/java/frc/robot/subsystems/rotary/Rotary.java
+++ b/src/main/java/frc/robot/subsystems/rotary/Rotary.java
@@ -124,9 +124,4 @@ public AngularVelocity getVelocity()
{
return io.getVelocity();
}
-
- public void close()
- {
- io.close();
- }
}
diff --git a/src/test/java/frc/robot/subsystems/revRotary/RevRotaryTest.java b/src/test/java/frc/robot/subsystems/revRotary/RevRotaryTest.java
new file mode 100644
index 00000000..98816ed0
--- /dev/null
+++ b/src/test/java/frc/robot/subsystems/revRotary/RevRotaryTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2025 Windham Windup
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program. If
+ * not, see .
+ */
+
+package frc.robot.subsystems.revRotary;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import edu.wpi.first.hal.HAL;
+import edu.wpi.first.wpilibj.Timer;
+import edu.wpi.first.wpilibj.simulation.DriverStationSim;
+import edu.wpi.first.wpilibj2.command.Command;
+import frc.robot.TestUtil;
+
+public class RevRotaryTest implements AutoCloseable {
+ RevRotary revRotary;
+
+ @BeforeEach
+ void setup()
+ {
+ assert HAL.initialize(500, 0); // initialize the HAL, crash if failed
+
+ revRotary = RevRotaryConstants.get();
+
+ /* enable the robot */
+ DriverStationSim.setEnabled(true);
+ DriverStationSim.notifyNewData();
+
+ /* delay ~100ms so the devices can start up and enable */
+ Timer.delay(0.100);
+ }
+
+ @SuppressWarnings("PMD.SignatureDeclareThrowsException")
+ @AfterEach
+ void shutdown() throws Exception
+ {
+ close();
+ }
+
+ @Test
+ void testGoToStow()
+ {
+ TestUtil.runTest(revRotary.setSetpoint(RevRotary.Setpoint.STOW), 3, revRotary);
+ try {
+ // Check to see if RevRotary subsystem is within tolerance of STOW setpoint
+ assertTrue(revRotary.nearGoal(RevRotary.Setpoint.STOW.getSetpoint()));
+ } catch (Exception e) {
+ fail("Failed to run RevRotary Subsystem to STOW: " + e.getMessage());
+ }
+ }
+
+ @Test
+ void testGoToRaised()
+ {
+ TestUtil.runTest(revRotary.setSetpoint(RevRotary.Setpoint.RAISED), 3, revRotary);
+ try {
+ // Check to see if RevRotary subsystem is within tolerance of RAISED setpoint
+ assertTrue(revRotary.nearGoal(RevRotary.Setpoint.RAISED.getSetpoint()));
+ } catch (Exception e) {
+ fail("Failed to run RevRotary Subsystem to RAISED: " + e.getMessage());
+ }
+ }
+
+ @Test
+ void testGoToGoalWithWait()
+ {
+ TestUtil.runTest(revRotary.setGoalCommandWithWait(RevRotary.Setpoint.STOW), 3, revRotary);
+ try {
+ // Check position to verify the subsystem is in tolerance of STOW setpoint
+ assertTrue(revRotary.nearGoal(RevRotary.Setpoint.STOW.getSetpoint()));
+ } catch (Exception e) {
+ fail("Failed to run RevRotary Subsystem to STOW with wait: " + e.getMessage());
+ }
+ }
+
+ @Test
+ void testSetpointEnum()
+ {
+ // Test that all setpoints can be retrieved and have valid values
+ try {
+ RevRotary.Setpoint stow = RevRotary.Setpoint.STOW;
+ RevRotary.Setpoint raised = RevRotary.Setpoint.RAISED;
+
+ assertNotNull(stow.getSetpoint());
+ assertNotNull(raised.getSetpoint());
+ assertNotNull(stow.getTunableNumber());
+ assertNotNull(raised.getTunableNumber());
+ } catch (Exception e) {
+ fail("Failed to retrieve setpoint values: " + e.getMessage());
+ }
+ }
+
+ @Test
+ void testNearGoalTolerance()
+ {
+ // Move to STOW position
+ TestUtil.runTest(revRotary.setSetpoint(RevRotary.Setpoint.STOW), 3, revRotary);
+
+ try {
+ // Test that we're near the STOW goal
+ assertTrue(revRotary.nearGoal(RevRotary.Setpoint.STOW.getSetpoint()));
+
+ // Test that we're not near the RAISED goal when at STOW
+ assertFalse(revRotary.nearGoal(RevRotary.Setpoint.RAISED.getSetpoint()));
+ } catch (Exception e) {
+ fail("Failed near goal tolerance test: " + e.getMessage());
+ }
+ }
+
+ @Test
+ void testGetVelocity()
+ {
+ // Start a movement
+ TestUtil.runTest(revRotary.setSetpoint(RevRotary.Setpoint.RAISED), 0.5, revRotary);
+
+ try {
+ // Velocity should be accessible during movement
+ assertNotNull(revRotary.getVelocity());
+ } catch (Exception e) {
+ fail("Failed get velocity test: " + e.getMessage());
+ }
+ }
+
+ @Test
+ void testMultipleSetpoints()
+ {
+ // Test moving between multiple setpoints
+ TestUtil.runTest(revRotary.setSetpoint(RevRotary.Setpoint.STOW), 3, revRotary);
+ assertTrue(revRotary.nearGoal(RevRotary.Setpoint.STOW.getSetpoint()));
+
+ TestUtil.runTest(revRotary.setSetpoint(RevRotary.Setpoint.RAISED), 3, revRotary);
+ assertTrue(revRotary.nearGoal(RevRotary.Setpoint.RAISED.getSetpoint()));
+
+ TestUtil.runTest(revRotary.setSetpoint(RevRotary.Setpoint.STOW), 3, revRotary);
+ assertTrue(revRotary.nearGoal(RevRotary.Setpoint.STOW.getSetpoint()));
+ }
+
+ @Test
+ void testCommandNaming()
+ {
+ // Test that commands have descriptive names for debugging
+ Command stowCmd = revRotary.setSetpoint(RevRotary.Setpoint.STOW);
+ assertTrue(stowCmd.getName().contains("STOW"));
+
+ Command raisedCmd = revRotary.setSetpoint(RevRotary.Setpoint.RAISED);
+ assertTrue(raisedCmd.getName().contains("RAISED"));
+
+ Command waitCmd = revRotary.setGoalCommandWithWait(RevRotary.Setpoint.STOW);
+ assertTrue(waitCmd.getName().contains("wait"));
+ }
+
+ @Test
+ void testPeriodicExecution()
+ {
+ // Test that periodic() can be called without errors
+ try {
+ revRotary.periodic();
+ revRotary.periodic();
+ revRotary.periodic();
+ } catch (Exception e) {
+ fail("Failed periodic execution test: " + e.getMessage());
+ }
+ }
+
+ @Test
+ void testRapidSetpointChanges()
+ {
+ // Test that rapid setpoint changes are handled correctly
+ TestUtil.runTest(revRotary.setSetpoint(RevRotary.Setpoint.RAISED), 1, revRotary);
+ TestUtil.runTest(revRotary.setSetpoint(RevRotary.Setpoint.STOW), 1, revRotary);
+ TestUtil.runTest(revRotary.setSetpoint(RevRotary.Setpoint.RAISED), 3, revRotary);
+
+ try {
+ // Should end up at the final setpoint
+ assertTrue(revRotary.nearGoal(RevRotary.Setpoint.RAISED.getSetpoint()));
+ } catch (Exception e) {
+ fail("Failed rapid setpoint changes test: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public void close()
+ {
+ revRotary.close();
+ }
+}
diff --git a/src/test/java/frc/robot/subsystems/rotary/RotaryTest.java b/src/test/java/frc/robot/subsystems/rotary/RotaryTest.java
index 42a7681b..6b74db9e 100644
--- a/src/test/java/frc/robot/subsystems/rotary/RotaryTest.java
+++ b/src/test/java/frc/robot/subsystems/rotary/RotaryTest.java
@@ -16,6 +16,8 @@
package frc.robot.subsystems.rotary;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -24,6 +26,7 @@
import edu.wpi.first.wpilibj.Timer;
import edu.wpi.first.wpilibj.simulation.DriverStationSim;
import frc.robot.TestUtil;
+import edu.wpi.first.wpilibj2.command.Command;
public class RotaryTest implements AutoCloseable {
Rotary rotary;
@@ -62,7 +65,7 @@ void goToGoal()
}
}
- @Test // marks this method as a test
+ @Test
void goToGoalWithWait()
{
TestUtil.runTest(rotary.setGoalCommandWithWait(Rotary.Setpoint.STOW), 3, rotary);
@@ -74,9 +77,93 @@ void goToGoalWithWait()
}
}
- @Override
- public void close()
+ @Test
+ void testSetpointEnum()
+ {
+ // Test that all setpoints can be retrieved and have valid values
+ try {
+ Rotary.Setpoint stow = Rotary.Setpoint.STOW;
+ Rotary.Setpoint raised = Rotary.Setpoint.RAISED;
+ Rotary.Setpoint home = Rotary.Setpoint.HOME;
+
+ assertNotNull(stow.getSetpoint());
+ assertNotNull(raised.getSetpoint());
+ assertNotNull(home.getSetpoint());
+ } catch (Exception e) {
+ fail("Failed to retrieve setpoint values: " + e.getMessage());
+ }
+ }
+
+ @Test
+ void testNearGoalTolerance()
+ {
+ // Move to STOW position
+ TestUtil.runTest(rotary.setSetpoint(Rotary.Setpoint.STOW), 3, rotary);
+
+ try {
+ // Test that we're near the goal
+ assertTrue(rotary.nearGoal(Rotary.Setpoint.STOW.getSetpoint()));
+
+ // Test that we're not near a different goal
+ assertFalse(rotary.nearGoal(Rotary.Setpoint.RAISED.getSetpoint()));
+ } catch (Exception e) {
+ fail("Failed near goal tolerance test: " + e.getMessage());
+ }
+ }
+
+
+
+ @Test
+ void testSetStateCommand()
{
- rotary.close();
+ // Test that setState command executes without errors
+ TestUtil.runTest(rotary.setStateCommand(Rotary.Setpoint.RAISED), 1, rotary);
+ // State setting is internal, so we just verify no exceptions thrown
}
+
+ @Test
+ void testGetVelocity()
+ {
+ // Start a movement
+ TestUtil.runTest(rotary.setSetpoint(Rotary.Setpoint.RAISED), 0.5, rotary);
+
+ try {
+ // Velocity should be non-zero during movement
+ assertNotNull(rotary.getVelocity());
+ } catch (Exception e) {
+ fail("Failed get velocity test: " + e.getMessage());
+ }
+ }
+
+ @Test
+ void testMultipleSetpoints()
+ {
+ // Test moving between multiple setpoints
+ TestUtil.runTest(rotary.setSetpoint(Rotary.Setpoint.STOW), 3, rotary);
+ assertTrue(rotary.nearGoal(Rotary.Setpoint.STOW.getSetpoint()));
+
+ TestUtil.runTest(rotary.setSetpoint(Rotary.Setpoint.RAISED), 3, rotary);
+ assertTrue(rotary.nearGoal(Rotary.Setpoint.RAISED.getSetpoint()));
+
+ TestUtil.runTest(rotary.setSetpoint(Rotary.Setpoint.STOW), 3, rotary);
+ assertTrue(rotary.nearGoal(Rotary.Setpoint.STOW.getSetpoint()));
+ }
+
+ @Test
+ void testCommandNaming()
+ {
+ // Test that commands have descriptive names
+ Command stowCmd = rotary.setSetpoint(Rotary.Setpoint.STOW);
+ assertTrue(stowCmd.getName().contains("STOW"));
+
+ Command raisedCmd = rotary.setSetpoint(Rotary.Setpoint.RAISED);
+ assertTrue(raisedCmd.getName().contains("RAISED"));
+
+ Command waitCmd = rotary.setGoalCommandWithWait(Rotary.Setpoint.STOW);
+ assertTrue(waitCmd.getName().contains("wait"));
+ }
+
+ @Override
+ public void close()
+ {}
}
From cb649e153de054ef526c2dc12b677e3ec127b665 Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Tue, 25 Nov 2025 14:49:16 -0500
Subject: [PATCH 12/29] test working
---
src/main/java/frc/lib/io/motor/MotorIO.java | 9 ++
.../java/frc/lib/io/motor/MotorIORev.java | 13 ++-
.../java/frc/lib/io/motor/MotorIORevSim.java | 2 +-
.../mechanisms/rotary/RotaryMechanismSim.java | 4 +-
src/main/java/frc/robot/RobotContainer.java | 11 +++
.../revRotary/RevRotaryConstants.java | 2 +-
.../java/frc/robot/RobotContainerTest.java | 83 ++++++++++++-------
.../subsystems/revRotary/RevRotaryTest.java | 12 ++-
8 files changed, 99 insertions(+), 37 deletions(-)
diff --git a/src/main/java/frc/lib/io/motor/MotorIO.java b/src/main/java/frc/lib/io/motor/MotorIO.java
index 237d5228..0c14f017 100644
--- a/src/main/java/frc/lib/io/motor/MotorIO.java
+++ b/src/main/java/frc/lib/io/motor/MotorIO.java
@@ -229,4 +229,13 @@ public default void setEncoderPosition(Angle position)
{
throw new NotImplementedException("This method has not been implemented");
}
+
+ /**
+ * Closes and cleans up motor controller resources. Called when the motor is no longer needed to
+ * properly release hardware.
+ */
+ public default void close()
+ {
+ // Default implementation does nothing for interfaces that don't need cleanup
+ }
}
diff --git a/src/main/java/frc/lib/io/motor/MotorIORev.java b/src/main/java/frc/lib/io/motor/MotorIORev.java
index 646723cc..36e6a9d5 100644
--- a/src/main/java/frc/lib/io/motor/MotorIORev.java
+++ b/src/main/java/frc/lib/io/motor/MotorIORev.java
@@ -215,9 +215,18 @@ private SparkBaseConfig newEmptyConfig()
return (motor instanceof SparkFlex) ? new SparkFlexConfig() : new SparkMaxConfig();
}
- protected void close()
+ @Override
+ public void close()
{
- this.motor.close();
+ if (this.motor != null) {
+ // Clear references to help with cleanup
+ this.controller = null;
+ this.encoder = null;
+
+ // Close the motor (this should release the CAN ID)
+ this.motor.close();
+ this.motor = null;
+ }
}
protected SparkFlex getMotor()
diff --git a/src/main/java/frc/lib/io/motor/MotorIORevSim.java b/src/main/java/frc/lib/io/motor/MotorIORevSim.java
index bc0d838f..82270ea5 100644
--- a/src/main/java/frc/lib/io/motor/MotorIORevSim.java
+++ b/src/main/java/frc/lib/io/motor/MotorIORevSim.java
@@ -139,7 +139,7 @@ public void updateInputs(MotorInputs inputs)
@Override
public void close()
{
- super.motor.close();
+ super.close();
}
}
diff --git a/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanismSim.java b/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanismSim.java
index ed508765..0011efb4 100644
--- a/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanismSim.java
+++ b/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanismSim.java
@@ -179,6 +179,8 @@ public AngularVelocity getVelocity()
@Override
public void close()
{
- io.close();
+ if (io != null) {
+ io.close();
+ }
}
}
diff --git a/src/main/java/frc/robot/RobotContainer.java b/src/main/java/frc/robot/RobotContainer.java
index cb197564..c8aa1661 100644
--- a/src/main/java/frc/robot/RobotContainer.java
+++ b/src/main/java/frc/robot/RobotContainer.java
@@ -324,4 +324,15 @@ public void checkStartPose()
false);
}
}
+
+ /**
+ * Helper method for unit testing.
+ *
+ * If we have any REV subsystems, close their resources here. IF we don't do this then we will
+ * get duplicate CAN ID errors when running multiple tests.ß
+ */
+ public void closeREVSubsystems()
+ {
+ revRotary.close();
+ }
}
diff --git a/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java b/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java
index 778b3979..d9c08eb1 100644
--- a/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java
+++ b/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java
@@ -152,7 +152,7 @@ public static RevRotary getSim()
{
MotorIOSim io = new MotorIORevSim(
NAME,
- Ports.RotarySubsystemMotorMain,
+ Ports.revRotarySubsytemMotorMain,
true,
DCMOTOR,
getREVConfig());
diff --git a/src/test/java/frc/robot/RobotContainerTest.java b/src/test/java/frc/robot/RobotContainerTest.java
index badacdfc..48159bbf 100644
--- a/src/test/java/frc/robot/RobotContainerTest.java
+++ b/src/test/java/frc/robot/RobotContainerTest.java
@@ -1,36 +1,57 @@
-/* Copyright (C) 2025 Windham Windup
+/*
+ * Copyright (C) 2025 Windham Windup
*
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * any later version.
+ * This program is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
*
- * This program is distributed in
- the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
+ * You should have received a copy of the GNU General Public License along with this program. If
+ * not, see .
*/
- package frc.robot;
-
- import static org.junit.jupiter.api.Assertions.fail;
-
- import org.junit.jupiter.api.Test;
-
- public class RobotContainerTest {
-
- @Test
- public void createRobotContainer() {
- // Instantiate RobotContainer
- try {
- new RobotContainer();
- } catch (Exception e) {
- e.printStackTrace();
- fail("Failed to instantiate RobotContainer, see stack trace above.");
- }
- }
- }
\ No newline at end of file
+package frc.robot;
+
+import static org.junit.jupiter.api.Assertions.fail;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import edu.wpi.first.hal.HAL;
+import edu.wpi.first.wpilibj.Timer;
+import edu.wpi.first.wpilibj.simulation.DriverStationSim;
+import edu.wpi.first.wpilibj2.command.CommandScheduler;
+
+public class RobotContainerTest {
+ private RobotContainer robotContainer;
+
+ @BeforeEach
+ void setup()
+ {
+ assert HAL.initialize(500, 0);
+
+
+ /* enable the robot */
+ DriverStationSim.setEnabled(true);
+ DriverStationSim.notifyNewData();
+
+ /* delay ~100ms so the devices can start up and enable */
+ Timer.delay(0.100);
+ }
+
+ @Test
+ public void createRobotContainer()
+ {
+ try {
+ robotContainer = new RobotContainer();
+ robotContainer.closeREVSubsystems();
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail("Failed to instantiate RobotContainer, see stack trace above.");
+ }
+ }
+}
diff --git a/src/test/java/frc/robot/subsystems/revRotary/RevRotaryTest.java b/src/test/java/frc/robot/subsystems/revRotary/RevRotaryTest.java
index 98816ed0..82406449 100644
--- a/src/test/java/frc/robot/subsystems/revRotary/RevRotaryTest.java
+++ b/src/test/java/frc/robot/subsystems/revRotary/RevRotaryTest.java
@@ -19,15 +19,21 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
+import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.TestMethodOrder;
import edu.wpi.first.hal.HAL;
import edu.wpi.first.wpilibj.Timer;
import edu.wpi.first.wpilibj.simulation.DriverStationSim;
import edu.wpi.first.wpilibj2.command.Command;
+import edu.wpi.first.wpilibj2.command.CommandScheduler;
import frc.robot.TestUtil;
+@TestMethodOrder(MethodOrderer.MethodName.class)
public class RevRotaryTest implements AutoCloseable {
RevRotary revRotary;
@@ -50,6 +56,7 @@ void setup()
@AfterEach
void shutdown() throws Exception
{
+ // Close the subsystem and its resources
close();
}
@@ -197,6 +204,9 @@ void testRapidSetpointChanges()
@Override
public void close()
{
- revRotary.close();
+ if (revRotary != null) {
+ revRotary.close();
+ revRotary = null;
+ }
}
}
From ecf6f8f02b86fead452307848cc13373987088e7 Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Tue, 25 Nov 2025 15:04:06 -0500
Subject: [PATCH 13/29] update sim
---
src/main/java/frc/lib/io/motor/MotorIORev.java | 11 +++++------
src/main/java/frc/lib/io/motor/MotorIORevSim.java | 12 +++---------
src/test/java/frc/robot/RobotContainerTest.java | 2 --
.../robot/subsystems/revRotary/RevRotaryTest.java | 3 ---
4 files changed, 8 insertions(+), 20 deletions(-)
diff --git a/src/main/java/frc/lib/io/motor/MotorIORev.java b/src/main/java/frc/lib/io/motor/MotorIORev.java
index 36e6a9d5..6093ac3c 100644
--- a/src/main/java/frc/lib/io/motor/MotorIORev.java
+++ b/src/main/java/frc/lib/io/motor/MotorIORev.java
@@ -9,28 +9,27 @@
import java.util.EnumMap;
import com.revrobotics.RelativeEncoder;
import com.revrobotics.spark.ClosedLoopSlot;
+import com.revrobotics.spark.SparkBase;
import com.revrobotics.spark.SparkBase.PersistMode;
import com.revrobotics.spark.SparkBase.ResetMode;
import com.revrobotics.spark.SparkClosedLoopController;
import com.revrobotics.spark.SparkFlex;
+import com.revrobotics.spark.SparkMax;
import com.revrobotics.spark.config.SparkBaseConfig;
import com.revrobotics.spark.config.SparkBaseConfig.IdleMode;
import com.revrobotics.spark.config.SparkFlexConfig;
import com.revrobotics.spark.config.SparkMaxConfig;
-import edu.wpi.first.units.AngularAccelerationUnit;
import edu.wpi.first.units.measure.Angle;
-import edu.wpi.first.units.measure.AngularAcceleration;
import edu.wpi.first.units.measure.AngularVelocity;
import edu.wpi.first.units.measure.Current;
-import edu.wpi.first.units.measure.Velocity;
import edu.wpi.first.units.measure.Voltage;
import frc.lib.util.Device;
import com.revrobotics.spark.SparkLowLevel.MotorType;
public class MotorIORev implements MotorIO {
- public SparkFlex motor;
+ public SparkBase motor;
public SparkClosedLoopController controller;
private RelativeEncoder encoder;
@@ -118,7 +117,7 @@ public MotorIORev(
if (isFlex) {
motor = new SparkFlex(id.id(), MotorType.kBrushless);
} else {
- // motor = new SparkMax(id, MotorType.kBrushless);
+ motor = new SparkMax(id.id(), MotorType.kBrushless);
}
controller = motor.getClosedLoopController();
@@ -229,7 +228,7 @@ public void close()
}
}
- protected SparkFlex getMotor()
+ protected SparkBase getMotor()
{
return this.motor;
}
diff --git a/src/main/java/frc/lib/io/motor/MotorIORevSim.java b/src/main/java/frc/lib/io/motor/MotorIORevSim.java
index 82270ea5..74435b50 100644
--- a/src/main/java/frc/lib/io/motor/MotorIORevSim.java
+++ b/src/main/java/frc/lib/io/motor/MotorIORevSim.java
@@ -7,11 +7,10 @@
import static edu.wpi.first.units.Units.Rotation;
import static edu.wpi.first.units.Units.Volts;
-import com.revrobotics.sim.SparkFlexSim;
import com.revrobotics.spark.SparkBase.PersistMode;
import com.revrobotics.spark.SparkBase.ResetMode;
+import com.revrobotics.spark.SparkBase;
import com.revrobotics.spark.SparkClosedLoopController;
-import com.revrobotics.spark.SparkFlex;
import com.revrobotics.spark.config.SparkBaseConfig;
import edu.wpi.first.math.system.plant.DCMotor;
import com.revrobotics.spark.SparkSim;
@@ -49,7 +48,7 @@ public class MotorIORevSim extends MotorIORev implements MotorIOSim {
public record RevFollowerFollower(int id, boolean opposesLeader) {
}
- public SparkFlex motor;
+ public SparkBase motor;
public SparkClosedLoopController controller;
private SparkSim simState;
@@ -77,12 +76,7 @@ public MotorIORevSim(
motor = this.getMotor();
- if (isFlex) {
- simState = new SparkFlexSim(motor, gearBox);
- } else {
- // motor = new SparkMax(id, MotorType.kBrushless);
- }
-
+ simState = new SparkSim(motor, gearBox);
motor.configure(config, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters);
diff --git a/src/test/java/frc/robot/RobotContainerTest.java b/src/test/java/frc/robot/RobotContainerTest.java
index 48159bbf..68bd7c0e 100644
--- a/src/test/java/frc/robot/RobotContainerTest.java
+++ b/src/test/java/frc/robot/RobotContainerTest.java
@@ -17,14 +17,12 @@
import static org.junit.jupiter.api.Assertions.fail;
-import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import edu.wpi.first.hal.HAL;
import edu.wpi.first.wpilibj.Timer;
import edu.wpi.first.wpilibj.simulation.DriverStationSim;
-import edu.wpi.first.wpilibj2.command.CommandScheduler;
public class RobotContainerTest {
private RobotContainer robotContainer;
diff --git a/src/test/java/frc/robot/subsystems/revRotary/RevRotaryTest.java b/src/test/java/frc/robot/subsystems/revRotary/RevRotaryTest.java
index 82406449..1b816366 100644
--- a/src/test/java/frc/robot/subsystems/revRotary/RevRotaryTest.java
+++ b/src/test/java/frc/robot/subsystems/revRotary/RevRotaryTest.java
@@ -19,9 +19,7 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
-import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.MethodOrderer;
@@ -30,7 +28,6 @@
import edu.wpi.first.wpilibj.Timer;
import edu.wpi.first.wpilibj.simulation.DriverStationSim;
import edu.wpi.first.wpilibj2.command.Command;
-import edu.wpi.first.wpilibj2.command.CommandScheduler;
import frc.robot.TestUtil;
@TestMethodOrder(MethodOrderer.MethodName.class)
From e285b3e61902871b0ae917d92d7f36c1413a910e Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Tue, 25 Nov 2025 15:21:15 -0500
Subject: [PATCH 14/29] Update
src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
.../frc/robot/subsystems/revRotary/RevRotaryConstants.java | 4 ----
1 file changed, 4 deletions(-)
diff --git a/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java b/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java
index d9c08eb1..d2826af0 100644
--- a/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java
+++ b/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java
@@ -112,10 +112,6 @@ public static SparkBaseConfig getREVConfig()
config.encoder.velocityConversionFactor(1.0 / GEAR_RATIO / 60.0); // RPM to RPS
// Add soft limits to match TalonFX behavior
- // config.softLimit.forwardSoftLimit(MAX_ANGLE.in(Rotations));
- // config.softLimit.forwardSoftLimitEnabled(false);
- // config.softLimit.reverseSoftLimit(MIN_ANGLE.in(Rotations));
- // config.softLimit.reverseSoftLimitEnabled(false);
config.apply(SLOT0CONFIG);
From f29b6e6b78a87a1f8ca0dc05fe6ddc9e8386fe11 Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Tue, 25 Nov 2025 15:21:29 -0500
Subject: [PATCH 15/29] Update src/main/java/frc/robot/RobotContainer.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/main/java/frc/robot/RobotContainer.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/frc/robot/RobotContainer.java b/src/main/java/frc/robot/RobotContainer.java
index c8aa1661..a224818d 100644
--- a/src/main/java/frc/robot/RobotContainer.java
+++ b/src/main/java/frc/robot/RobotContainer.java
@@ -329,7 +329,7 @@ public void checkStartPose()
* Helper method for unit testing.
*
* If we have any REV subsystems, close their resources here. IF we don't do this then we will
- * get duplicate CAN ID errors when running multiple tests.ß
+ * get duplicate CAN ID errors when running multiple tests.
*/
public void closeREVSubsystems()
{
From b125589a55e6b62aab34373916b147daee77fd19 Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Tue, 25 Nov 2025 15:21:39 -0500
Subject: [PATCH 16/29] Update src/main/java/frc/lib/io/motor/MotorIORev.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/main/java/frc/lib/io/motor/MotorIORev.java | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/main/java/frc/lib/io/motor/MotorIORev.java b/src/main/java/frc/lib/io/motor/MotorIORev.java
index 6093ac3c..ad6303ba 100644
--- a/src/main/java/frc/lib/io/motor/MotorIORev.java
+++ b/src/main/java/frc/lib/io/motor/MotorIORev.java
@@ -141,7 +141,6 @@ public void updateInputs(MotorInputs inputs)
inputs.appliedVoltage = Volts.of(motor.getAppliedOutput() * motor.getBusVoltage());
inputs.supplyCurrent = Amps.of(motor.getOutputCurrent());
inputs.temperature = Celsius.of(motor.getMotorTemperature());
- inputs.positionError = null;
inputs.positionError = Angle.ofBaseUnits(0, Rotation);
inputs.velocityError = AngularVelocity.ofBaseUnits(0, RotationsPerSecond);
inputs.goalPosition = Angle.ofBaseUnits(0, Rotation);
From 80e922b902d53a911424ac22a1ebdf7ad67b733e Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Tue, 25 Nov 2025 15:21:55 -0500
Subject: [PATCH 17/29] Update .github/instructions/default.instructions.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
.github/instructions/default.instructions.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/instructions/default.instructions.md b/.github/instructions/default.instructions.md
index 1458f4f4..5b44e087 100644
--- a/.github/instructions/default.instructions.md
+++ b/.github/instructions/default.instructions.md
@@ -1,6 +1,6 @@
# FRC Team W8 Library - AI Assistant Instructions
-You are a sr Java engineer with 10+ years of expereince. You are helping high school students on an FRC (FIRST Robotics Competition) team. Write clear, educational code that students can learn from and maintain.
+You are a sr Java engineer with 10+ years of experience. You are helping high school students on an FRC (FIRST Robotics Competition) team. Write clear, educational code that students can learn from and maintain.
## Technology Stack
From c9cc5b54ababc5b44138cbb29bd12ac258943035 Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Tue, 25 Nov 2025 15:22:28 -0500
Subject: [PATCH 18/29] Update
src/main/java/frc/robot/subsystems/revRotary/RevRotary.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/main/java/frc/robot/subsystems/revRotary/RevRotary.java | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/main/java/frc/robot/subsystems/revRotary/RevRotary.java b/src/main/java/frc/robot/subsystems/revRotary/RevRotary.java
index b8c9defc..45eae9f4 100644
--- a/src/main/java/frc/robot/subsystems/revRotary/RevRotary.java
+++ b/src/main/java/frc/robot/subsystems/revRotary/RevRotary.java
@@ -22,7 +22,6 @@ public class RevRotary extends SubsystemBase {
private final RotaryMechanism io;
private final RobotState robotstate;
- private Setpoint setpoint = Setpoint.STOW;
private static final LoggedTunableNumber STOW_SETPOINT =
new LoggedTunableNumber("REV STOW", 0.0);
From afd56fdef4895a888e6387c84b746999b879b74d Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Tue, 25 Nov 2025 15:22:49 -0500
Subject: [PATCH 19/29] Update
src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
.../revRotary/RevRotaryConstants.java | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java b/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java
index d2826af0..d0ba8fb8 100644
--- a/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java
+++ b/src/main/java/frc/robot/subsystems/revRotary/RevRotaryConstants.java
@@ -83,21 +83,20 @@ public class RevRotaryConstants {
.pid(30.0, 0, 5.0, ClosedLoopSlot.kSlot0); // Added D gain to match TalonFX config
/**
- * Creates and returns the TalonFX motor controller configuration for the rotary mechanism.
+ * Creates and returns the SparkFlex/SparkMax motor controller configuration for the rotary mechanism.
*
*
* This configuration includes:
*
- * - Current limits to prevent motor damage and brownouts
- * - Voltage limits for power output
- * - Brake mode to hold position when not moving
- * - Software limit switches to prevent mechanism damage
- * - Gear ratios for proper position/velocity feedback
- * - Remote CANcoder feedback for absolute positioning
- * - PID gains for control
+ * - Voltage compensation for consistent power output
+ * - Idle (brake) mode to hold position when not moving
+ * - Motor inversion setting
+ * - Encoder position and velocity conversion factors for proper feedback scaling
+ * - PID gains applied to the selected closed-loop slot
+ *
*
*
- * @return A configured TalonFXConfiguration object ready to apply to a motor controller
+ * @return A configured {@link SparkBaseConfig} object ready to apply to a REV Robotics SparkFlex or SparkMax motor controller
*/
public static SparkBaseConfig getREVConfig()
{
From 97d8fc8767f14110c028500c1cda9bff3fcd1d1b Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Tue, 25 Nov 2025 15:23:31 -0500
Subject: [PATCH 20/29] Update
src/test/java/frc/robot/subsystems/rotary/RotaryTest.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/test/java/frc/robot/subsystems/rotary/RotaryTest.java | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/test/java/frc/robot/subsystems/rotary/RotaryTest.java b/src/test/java/frc/robot/subsystems/rotary/RotaryTest.java
index 6b74db9e..49081fab 100644
--- a/src/test/java/frc/robot/subsystems/rotary/RotaryTest.java
+++ b/src/test/java/frc/robot/subsystems/rotary/RotaryTest.java
@@ -165,5 +165,10 @@ void testCommandNaming()
@Override
public void close()
- {}
+ {
+ if (rotary != null) {
+ rotary.close();
+ rotary = null;
+ }
+ }
}
From 21602a8636e65ec756d3795f63bd4ef7ded008d0 Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Tue, 25 Nov 2025 15:23:54 -0500
Subject: [PATCH 21/29] Update
src/main/java/frc/lib/io/motor/MotorIORevSim.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/main/java/frc/lib/io/motor/MotorIORevSim.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/main/java/frc/lib/io/motor/MotorIORevSim.java b/src/main/java/frc/lib/io/motor/MotorIORevSim.java
index 74435b50..42552389 100644
--- a/src/main/java/frc/lib/io/motor/MotorIORevSim.java
+++ b/src/main/java/frc/lib/io/motor/MotorIORevSim.java
@@ -105,6 +105,7 @@ public double getRotorToSensorRatio()
return 1;
}
+ @Override
public double getSensorToMechanismRatio()
{
return 1;
From 81f0df1bde59765eaa6227285d0b0a9b3791dd4d Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Tue, 25 Nov 2025 15:24:06 -0500
Subject: [PATCH 22/29] Update src/main/java/frc/lib/io/motor/MotorIOSim.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/main/java/frc/lib/io/motor/MotorIOSim.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/main/java/frc/lib/io/motor/MotorIOSim.java b/src/main/java/frc/lib/io/motor/MotorIOSim.java
index d56053a2..7e2c3d4d 100644
--- a/src/main/java/frc/lib/io/motor/MotorIOSim.java
+++ b/src/main/java/frc/lib/io/motor/MotorIOSim.java
@@ -102,6 +102,7 @@ public default double getSensorToMechanismRatio()
*
* Called when the simulation is shutting down to properly release any resources.
*/
+ @Override
public default void close()
{}
}
From 078adda2bce8f144fe6b35decb131b9d943fdadb Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 25 Nov 2025 20:26:15 +0000
Subject: [PATCH 23/29] Initial plan
From 2448e4161c82833f820db4534eba363f2d8390da Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Tue, 25 Nov 2025 15:28:31 -0500
Subject: [PATCH 24/29] add close method
---
src/main/java/frc/robot/subsystems/rotary/Rotary.java | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/main/java/frc/robot/subsystems/rotary/Rotary.java b/src/main/java/frc/robot/subsystems/rotary/Rotary.java
index f424c73e..b497ffc1 100644
--- a/src/main/java/frc/robot/subsystems/rotary/Rotary.java
+++ b/src/main/java/frc/robot/subsystems/rotary/Rotary.java
@@ -124,4 +124,9 @@ public AngularVelocity getVelocity()
{
return io.getVelocity();
}
+
+ public void close()
+ {
+ io.close();
+ }
}
From 01e8bb324d94ce1e43a956d628092455d0a8f524 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 25 Nov 2025 20:30:32 +0000
Subject: [PATCH 25/29] Address review comments: fix debug prints,
initialization logic, naming typo, and unused field
Co-authored-by: garrett928 <22306616+garrett928@users.noreply.github.com>
---
.../java/frc/lib/io/motor/MotorIORevSim.java | 4 ++--
.../frc/lib/util/LoggedTunableNumber.java | 19 ++++---------------
.../robot/subsystems/revRotary/RevRotary.java | 3 ---
3 files changed, 6 insertions(+), 20 deletions(-)
diff --git a/src/main/java/frc/lib/io/motor/MotorIORevSim.java b/src/main/java/frc/lib/io/motor/MotorIORevSim.java
index 42552389..97a0934f 100644
--- a/src/main/java/frc/lib/io/motor/MotorIORevSim.java
+++ b/src/main/java/frc/lib/io/motor/MotorIORevSim.java
@@ -45,7 +45,7 @@
*/
public class MotorIORevSim extends MotorIORev implements MotorIOSim {
- public record RevFollowerFollower(int id, boolean opposesLeader) {
+ public record RevFollower(int id, boolean opposesLeader) {
}
public SparkBase motor;
@@ -70,7 +70,7 @@ public MotorIORevSim(
boolean isFlex,
DCMotor gearBox,
SparkBaseConfig config,
- RevFollowerFollower... followerData)
+ RevFollower... followerData)
{
super(name, id, isFlex, config);
diff --git a/src/main/java/frc/lib/util/LoggedTunableNumber.java b/src/main/java/frc/lib/util/LoggedTunableNumber.java
index 0db3a4e9..50f4526c 100644
--- a/src/main/java/frc/lib/util/LoggedTunableNumber.java
+++ b/src/main/java/frc/lib/util/LoggedTunableNumber.java
@@ -69,6 +69,9 @@ public void initDefault(double defaultValue)
if (!hasDefault) {
hasDefault = true;
this.defaultValue = defaultValue;
+ if (Constants.tuningMode) {
+ dashboardNumber = new LoggedNetworkNumber(key, defaultValue);
+ }
}
}
@@ -81,22 +84,8 @@ public double get()
{
if (!hasDefault) {
return 0.0;
- } else if (Constants.tuningMode) {
- if (dashboardNumber == null) {
- dashboardNumber = new LoggedNetworkNumber(key, defaultValue);
- }
- double value = dashboardNumber.get();
- if (key.contains("STOW")) {
- System.out.println("TunableNumber " + key + " returning dashboard value: " + value);
- }
- return value;
} else {
- if (key.contains("STOW")) {
- System.out
- .println("TunableNumber " + key + " returning default value: " + defaultValue
- + " (tuningMode=" + Constants.tuningMode + ")");
- }
- return defaultValue;
+ return Constants.tuningMode ? dashboardNumber.get() : defaultValue;
}
}
diff --git a/src/main/java/frc/robot/subsystems/revRotary/RevRotary.java b/src/main/java/frc/robot/subsystems/revRotary/RevRotary.java
index 45eae9f4..8e198a9a 100644
--- a/src/main/java/frc/robot/subsystems/revRotary/RevRotary.java
+++ b/src/main/java/frc/robot/subsystems/revRotary/RevRotary.java
@@ -14,14 +14,12 @@
import frc.lib.mechanisms.rotary.RotaryMechanism;
import frc.lib.util.LoggedTunableNumber;
import frc.lib.util.LoggerHelper;
-import frc.robot.RobotState;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
public class RevRotary extends SubsystemBase {
private final RotaryMechanism io;
- private final RobotState robotstate;
private static final LoggedTunableNumber STOW_SETPOINT =
new LoggedTunableNumber("REV STOW", 0.0);
@@ -46,7 +44,6 @@ public Angle getSetpoint()
public RevRotary(RotaryMechanism io)
{
this.io = io;
- this.robotstate = RobotState.getInstance();
setSetpoint(RevRotaryConstants.DEFAULT_SETPOINT).ignoringDisable(true).schedule();
}
From 475d9649367e518673c280bb0abc3aded84a8b48 Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Wed, 26 Nov 2025 15:58:54 -0500
Subject: [PATCH 26/29] rename folder
---
src/main/java/frc/lib/devices/ObjectDetection.java | 4 ++--
.../java/frc/lib/io/objectDetection/ObjectDetectionIO.java | 2 +-
.../io/objectDetection/ObjectDetectionIOPhotonVision.java | 2 +-
.../frc/lib/io/objectDetection/ObjectDetectionIOSim.java | 2 +-
.../frc/robot/subsystems/objectDetector/ObjectDetector.java | 2 +-
.../subsystems/objectDetector/ObjectDetectorConstants.java | 6 +++---
6 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/main/java/frc/lib/devices/ObjectDetection.java b/src/main/java/frc/lib/devices/ObjectDetection.java
index b7dd71e8..7ee0b785 100644
--- a/src/main/java/frc/lib/devices/ObjectDetection.java
+++ b/src/main/java/frc/lib/devices/ObjectDetection.java
@@ -13,9 +13,9 @@
import edu.wpi.first.math.geometry.Transform3d;
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.geometry.Translation2d;
-import frc.lib.io.objectDetection.ObjectDetectionIO;
-import frc.lib.io.objectDetection.ObjectDetectionIO.TargetObservation;
import frc.lib.io.objectDetection.ObjectDetectionIOInputsAutoLogged;
+import frc.lib.io.objectdetection.ObjectDetectionIO;
+import frc.lib.io.objectdetection.ObjectDetectionIO.TargetObservation;
/**
* Device level implementation of an object detection camera. While the IO layer is responsible for
diff --git a/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIO.java b/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIO.java
index a6c4cb6c..303685b8 100644
--- a/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIO.java
+++ b/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIO.java
@@ -13,7 +13,7 @@
* not, see .
*/
-package frc.lib.io.objectDetection;
+package frc.lib.io.objectdetection;
import org.littletonrobotics.junction.AutoLog;
diff --git a/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIOPhotonVision.java b/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIOPhotonVision.java
index 87faa2e6..924cf91f 100644
--- a/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIOPhotonVision.java
+++ b/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIOPhotonVision.java
@@ -13,7 +13,7 @@
* not, see .
*/
-package frc.lib.io.objectDetection;
+package frc.lib.io.objectdetection;
import org.photonvision.PhotonCamera;
import org.photonvision.targeting.PhotonPipelineResult;
diff --git a/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIOSim.java b/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIOSim.java
index 63454001..743c0129 100644
--- a/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIOSim.java
+++ b/src/main/java/frc/lib/io/objectDetection/ObjectDetectionIOSim.java
@@ -2,7 +2,7 @@
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
-package frc.lib.io.objectDetection;
+package frc.lib.io.objectdetection;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/frc/robot/subsystems/objectDetector/ObjectDetector.java b/src/main/java/frc/robot/subsystems/objectDetector/ObjectDetector.java
index 7421c2a9..b05114e2 100644
--- a/src/main/java/frc/robot/subsystems/objectDetector/ObjectDetector.java
+++ b/src/main/java/frc/robot/subsystems/objectDetector/ObjectDetector.java
@@ -16,7 +16,7 @@
package frc.robot.subsystems.objectDetector;
import frc.lib.devices.ObjectDetection;
-import frc.lib.io.objectDetection.ObjectDetectionIO;
+import frc.lib.io.objectdetection.ObjectDetectionIO;
import frc.robot.subsystems.drive.Drive;
import org.littletonrobotics.junction.Logger;
import edu.wpi.first.math.geometry.Translation2d;
diff --git a/src/main/java/frc/robot/subsystems/objectDetector/ObjectDetectorConstants.java b/src/main/java/frc/robot/subsystems/objectDetector/ObjectDetectorConstants.java
index a2c1d2ae..b6e0f9f2 100644
--- a/src/main/java/frc/robot/subsystems/objectDetector/ObjectDetectorConstants.java
+++ b/src/main/java/frc/robot/subsystems/objectDetector/ObjectDetectorConstants.java
@@ -13,9 +13,9 @@
import edu.wpi.first.units.measure.Angle;
import edu.wpi.first.units.Units;
import edu.wpi.first.wpilibj.Timer;
-import frc.lib.io.objectDetection.ObjectDetectionIO;
-import frc.lib.io.objectDetection.ObjectDetectionIOPhotonVision;
-import frc.lib.io.objectDetection.ObjectDetectionIOSim;
+import frc.lib.io.objectdetection.ObjectDetectionIO;
+import frc.lib.io.objectdetection.ObjectDetectionIOPhotonVision;
+import frc.lib.io.objectdetection.ObjectDetectionIOSim;
import frc.robot.Constants;
import frc.robot.subsystems.drive.Drive;
From 5eed0514fa461fa7e3ca5a5a5ff55a24d1ef1e1f Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Wed, 26 Nov 2025 16:10:33 -0500
Subject: [PATCH 27/29] add comment
---
src/main/java/frc/lib/io/motor/MotorIORev.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/main/java/frc/lib/io/motor/MotorIORev.java b/src/main/java/frc/lib/io/motor/MotorIORev.java
index ad6303ba..35c84baf 100644
--- a/src/main/java/frc/lib/io/motor/MotorIORev.java
+++ b/src/main/java/frc/lib/io/motor/MotorIORev.java
@@ -135,6 +135,7 @@ public String getName()
@Override
public void updateInputs(MotorInputs inputs)
{
+ // TODO: implement isConnected check for REV motors
inputs.connected = true;
inputs.position = Rotation.of(encoder.getPosition());
inputs.velocity = RotationsPerSecond.of(encoder.getVelocity() / 60.0);
From c0dd1312eebcf8dc3ace029c952202d554f9e208 Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Wed, 26 Nov 2025 16:16:15 -0500
Subject: [PATCH 28/29] fix import
---
src/main/java/frc/lib/devices/ObjectDetection.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/frc/lib/devices/ObjectDetection.java b/src/main/java/frc/lib/devices/ObjectDetection.java
index 7ee0b785..0a1b9733 100644
--- a/src/main/java/frc/lib/devices/ObjectDetection.java
+++ b/src/main/java/frc/lib/devices/ObjectDetection.java
@@ -13,7 +13,7 @@
import edu.wpi.first.math.geometry.Transform3d;
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.geometry.Translation2d;
-import frc.lib.io.objectDetection.ObjectDetectionIOInputsAutoLogged;
+import frc.lib.io.objectdetection.ObjectDetectionIOInputsAutoLogged;
import frc.lib.io.objectdetection.ObjectDetectionIO;
import frc.lib.io.objectdetection.ObjectDetectionIO.TargetObservation;
From 6743832ec7174abc7426505f5301ed2a35f483bb Mon Sep 17 00:00:00 2001
From: Garrett Hart
Date: Wed, 26 Nov 2025 17:57:44 -0500
Subject: [PATCH 29/29] remove debug logging
---
src/main/java/frc/lib/mechanisms/rotary/RotaryMechanism.java | 5 -----
1 file changed, 5 deletions(-)
diff --git a/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanism.java b/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanism.java
index e763ab91..a7255a03 100644
--- a/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanism.java
+++ b/src/main/java/frc/lib/mechanisms/rotary/RotaryMechanism.java
@@ -7,10 +7,8 @@
import frc.lib.io.motor.MotorIO.ControlType;
import frc.lib.io.motor.MotorInputsAutoLogged;
import frc.lib.mechanisms.Mechanism;
-import static edu.wpi.first.units.Units.Degrees;
import java.util.Optional;
import java.util.function.Supplier;
-import org.littletonrobotics.junction.Logger;
import edu.wpi.first.math.MathUtil;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Translation3d;
@@ -81,9 +79,6 @@ public void periodic()
visualizer.setTrajectoryAngle(getTrajectoryAngle());
visualizer.setGoalAngle(getGoalAngle());
- Logger.recordOutput(
- this.name + "/PositionDegrees",
- inputs.position.in(Degrees));
}
@Override