diff --git a/lib/src/main/java/com/team2813/lib2813/subsystems/MotorSubsystem.java b/lib/src/main/java/com/team2813/lib2813/subsystems/MotorSubsystem.java index c33a905f..d8033320 100644 --- a/lib/src/main/java/com/team2813/lib2813/subsystems/MotorSubsystem.java +++ b/lib/src/main/java/com/team2813/lib2813/subsystems/MotorSubsystem.java @@ -24,7 +24,27 @@ import java.util.function.Supplier; /** - * Defines PID control over a motor, with values specified by an encoder + * Defines a generic subsystem comprising a motor and encoder. + * + *

The MotorSybsystem supports a dual operation mode: + * + *

+ * + *

The PID Mode is enabled by calling {@link setSetpoint(T)}. The subsystem starts moving + * toward the setpoint and maintains position at the setpoint under the control of the PID + * controller. The motor system's {@link isEnabled()} returns {@code true}. + * + *

The Direct User Input Mode is activated when the user calls the {@link + * set(ControlType,double,double)} or {@link set(ControlType,double)} method, where the user + * provides direct input of type ControlType (specified via {@link + * MotorSubsystemConfiguration#controlMode(ControlType)}). The PID Mode is interrupted and + * disengaged, and {@link isEnabled()} returns {@code false}. It can be re-engaged with the {@link + * enable()} method and will resume movement toward setpoint. * * @param the type of the {@link Supplier} used to specify setpoints. */ @@ -33,6 +53,8 @@ public abstract class MotorSubsystem> extends Subsyste protected final Motor motor; protected final Encoder encoder; + // Specifies if the mechanism is currently under the control of the PID controller. + private boolean isEnabled; protected final ControlMode controlMode; protected final AngleUnit rotationUnit; protected final double acceptableError; @@ -42,11 +64,153 @@ public abstract class MotorSubsystem> extends Subsyste private final DoublePublisher appliedCurrentPublisher; private final DoublePublisher setpointPublisher; - private boolean isEnabled; + /** A configuration for a MotorSubsystem */ + public static class MotorSubsystemConfiguration { + /** The default acceptable position error. */ + public static final double DEFAULT_ERROR = 5.0; + + /** The default starting position if one is not provided. */ + public static final double DEFAULT_STARTING_POSITION = 0.0; + + // See setter methods for descriptions of these member variables. + private ControlMode controlMode; + private AngleUnit rotationUnit; + private final Motor motor; + private final Encoder encoder; + private PIDController controller; + private double acceptableError; + private double startingPosition; + private NetworkTableInstance ntInstance; + + /** + * Creates a new configuration for a MotorSubsystems. The default acceptable error is {@value + * #DEFAULT_ERROR}, the PID constants are set to 0, and the starting position is {@value + * #DEFAULT_STARTING_POSITION} + * + * @param motor the motor to control + * @param encoder the encoder providing feedback + */ + public MotorSubsystemConfiguration(Motor motor, Encoder encoder) { + this.motor = Objects.requireNonNull(motor, "motor should not be null"); + this.encoder = Objects.requireNonNull(encoder, "encoder should not be null"); + controller = new PIDController(0, 0, 0); + acceptableError = DEFAULT_ERROR; + startingPosition = DEFAULT_STARTING_POSITION; + controlMode = ControlMode.DUTY_CYCLE; + rotationUnit = Units.Rotations; + } + + /** + * Creates a new config for MotorSubsystems using a motor that has a built-in encoder. The + * default acceptable error is {@value #DEFAULT_ERROR}, the PID constants are set to 0, and the + * starting position is {@value #DEFAULT_STARTING_POSITION} + * + * @param motor the integrated motor controller + */ + public MotorSubsystemConfiguration(PIDMotor motor) { + this(motor, motor); + } + + /** + * Sets the controller that drives the motor to a user-specified setpoint. + * + * @param controller The PID controller + * @return {@code this} for chaining + */ + public MotorSubsystemConfiguration controller(PIDController controller) { + this.controller = controller; + return this; + } + + /** + * Sets the control mode to use when giving output to the motor. Defaults to {@link + * ControlMode#DUTY_CYCLE}. + * + * @param controlMode The mode to use when controlling the motor + * @return {@code this} for chaining + * @throws IllegalArgumentException If {@code controlMode} is for positional control + */ + public MotorSubsystemConfiguration controlMode(ControlMode controlMode) { + if (controlMode.isPositionalControl()) { + throw new IllegalArgumentException( + String.format( + "Control mode %s is for positional control. This is invalid! Please use a different" + + " control mode", + controlMode)); + } + this.controlMode = controlMode; + return this; + } + + /** + * Sets the PID constants for the controller. + * + * @param p the proportional + * @param i the integral + * @param d the derivative + * @return {@code this} for chaining + */ + public MotorSubsystemConfiguration PID(double p, double i, double d) { + controller.setPID(p, i, d); + return this; + } + + /** + * Sets the initial setpoint of the controller. + * + * @param startingPosition the initial setpoint + * @return {@code this} for chaining + */ + public MotorSubsystemConfiguration startingPosition(Angle startingPosition) { + this.startingPosition = startingPosition.in(this.rotationUnit); + return this; + } + + /** + * Sets the initial setpoint of the controller from the current value of a supplier. + * + *

This is provided to allow the subclass to define an {@code Enum} (that implements {@code + * Supplier}) which defines the supported positions of this subsystem. + * + * @param startingPositionSupplier supplier to use to get the initial setpoint + * @return {@code this} for chaining + */ + public MotorSubsystemConfiguration startingPosition(Supplier startingPositionSupplier) { + return startingPosition(startingPositionSupplier.get()); + } + + /** + * Sets the acceptable position error. + * + *

This is the acceptable delta between the user-specified setpoint and the actual subsystem + * position where the system is reporting to be "in position" (see {@link atPosition}). + */ + public MotorSubsystemConfiguration acceptableError(double error) { + this.acceptableError = error; + return this; + } + + /** + * Sets the unit to use for PID calculations. + * + * @param rotationUnit The angle unit to use for calculations + */ + public MotorSubsystemConfiguration rotationUnit(AngleUnit rotationUnit) { + startingPosition = rotationUnit.convertFrom(startingPosition, this.rotationUnit); + this.rotationUnit = rotationUnit; + return this; + } + + /** Sets the network table instance to report subsystem internal state to. */ + public MotorSubsystemConfiguration publishTo(NetworkTableInstance ntInstance) { + this.ntInstance = ntInstance; + return this; + } + } protected MotorSubsystem(MotorSubsystemConfiguration builder) { - this.controller = builder.controller; - this.controller.setTolerance(builder.acceptableError); + controller = builder.controller; + controller.setTolerance(builder.acceptableError); acceptableError = builder.acceptableError; motor = builder.motor; encoder = builder.encoder; @@ -65,7 +229,7 @@ protected MotorSubsystem(MotorSubsystemConfiguration builder) { setpointPublisher = null; } - this.controller.setSetpoint(builder.startingPosition); + controller.setSetpoint(builder.startingPosition); } /** @@ -101,28 +265,13 @@ public final boolean atPosition() { } /** - * {@inheritDoc} - * - *

Additionally, this method disables PID control of the subsystem - */ - @Override - public final void set(ControlMode mode, double demand, double feedForward) { - if (isEnabled()) { - disable(); - } - motor.set(mode, demand, feedForward); - } - - @Override - public final Current getAppliedCurrent() { - return motor.getAppliedCurrent(); - } - - /** - * Enables the motor + * Engages the PID Controller. * *

The motor voltage will be periodically updated to move the motor towards the current * setupoint. + * + *

If this method is called after the motor drive toward setpoint was interrupted by user + * interruption, the motor will resume its movement towards the last set setpoint. */ public final void enable() { isEnabled = true; @@ -140,26 +289,18 @@ public final void disable() { } /** - * Returns whether the controller is enabled. If this is enabled, then PID control will be used. + * Returns whether the PID controller is engaged. * - * @return Whether the controller is enabled. + *

When the PID controller is engaged, the motor system is moving toward the user-specified + * setpoint position (set via {@link setSetpoint(T)}), or, if it has already reached the setpoint, + * is maintaining that position. + * + * @return Whether the PID controller is engaged. */ public final boolean isEnabled() { return isEnabled; } - /** - * {@inheritDoc} - * - *

Additionally, this method disables PID control of the subsystem. It does not clamp - * the provided value. - */ - @Override - public final void set(ControlMode mode, double demand) { - isEnabled = false; - motor.set(mode, demand); - } - /** * Clamps the given output value and provides it to the motor. * @@ -197,47 +338,96 @@ protected double clampOutput(double output) { return output; } + // Returns the current position of the motor subsystem in the units specified by m_rotationUnit. + // This position is used by the PID controller (when it is enabled) to determine if it is already + // at position or needs to adjust the subsystem position to reach a user-specified setpoint. protected final double getMeasurement() { return encoder.getPositionMeasure().in(rotationUnit); } + // `Motor` method overrides. + // ------------------------- + + /** + * {@inheritDoc} + * + *

Additionally, this method disengages PID control of the subsystem. + */ + @Override + public final void set(ControlMode mode, double demand, double feedForward) { + if (isEnabled()) { + disable(); + } + motor.set(mode, demand, feedForward); + } + + /** + * {@inheritDoc} + * + *

Additionally, this method disables PID control of the subsystem. It does not clamp + * the provided value. + */ + @Override + public final void set(ControlMode mode, double demand) { + isEnabled = false; + motor.set(mode, demand); + } + + /** {@inheritDoc} */ + @Override + public final Current getAppliedCurrent() { + return motor.getAppliedCurrent(); + } + + // `Encoder` method overrides. + // --------------------------- + + /** {@inheritDoc} */ @Override @Deprecated(forRemoval = true) public double position() { return encoder.position(); } + /** {@inheritDoc} */ @Override public final Angle getPositionMeasure() { return encoder.getPositionMeasure(); } + /** {@inheritDoc} */ @Override @Deprecated(forRemoval = true) public void setPosition(double position) { encoder.setPosition(position); } + /** {@inheritDoc} */ @Override public final void setPosition(Angle position) { encoder.setPosition(position); } + /** {@inheritDoc} */ @Override @Deprecated(forRemoval = true) public double getVelocity() { return encoder.getVelocity(); } + /** {@inheritDoc} */ @Override public final AngularVelocity getVelocityMeasure() { return encoder.getVelocityMeasure(); } + // `Subsystem` method overrides. + // ----------------------------- + /** Applies the PID output to the motor if this subsystem is enabled. */ @Override public void periodic() { - if (isEnabled) { + if (isEnabled()) { useOutput(controller.calculate(getMeasurement())); } if (positionPublisher != null) { @@ -247,141 +437,4 @@ public void periodic() { appliedCurrentPublisher.set(getAppliedCurrent().in(Amps)); } } - - /** A configuration for a MotorSubsystem */ - public static class MotorSubsystemConfiguration { - /** The default acceptable position error. */ - public static final double DEFAULT_ERROR = 5.0; - - /** The default starting position if one is not provided. */ - public static final double DEFAULT_STARTING_POSITION = 0.0; - - private ControlMode controlMode; - private AngleUnit rotationUnit; - private final Motor motor; - private final Encoder encoder; - private PIDController controller; - private double acceptableError; - private double startingPosition; - private NetworkTableInstance ntInstance; - - /** - * Creates a new configuration for a MotorSubsystems. The default acceptable error is {@value - * #DEFAULT_ERROR}, the PID constants are set to 0, and the starting position is {@value - * #DEFAULT_STARTING_POSITION} - * - * @param motor the motor to control - * @param encoder the encoder providing feedback - */ - public MotorSubsystemConfiguration(Motor motor, Encoder encoder) { - this.motor = Objects.requireNonNull(motor, "motor should not be null"); - this.encoder = Objects.requireNonNull(encoder, "encoder should not be null"); - controller = new PIDController(0, 0, 0); - acceptableError = DEFAULT_ERROR; - startingPosition = DEFAULT_STARTING_POSITION; - controlMode = ControlMode.DUTY_CYCLE; - rotationUnit = Units.Rotations; - } - - /** - * Creates a new config for MotorSubsystems using a motor that has a built-in encoder. The - * default acceptable error is {@value #DEFAULT_ERROR}, the PID constants are set to 0, and the - * starting position is {@value #DEFAULT_STARTING_POSITION} - * - * @param motor the integrated motor controller - */ - public MotorSubsystemConfiguration(PIDMotor motor) { - this(motor, motor); - } - - /** - * Sets the controller used to calculate the next value - * - * @param controller The PID controller - * @return {@code this} for chaining - */ - public MotorSubsystemConfiguration controller(PIDController controller) { - this.controller = controller; - return this; - } - - /** - * Sets the control mode to use when giving output to the motor. Defaults to {@link - * ControlMode#DUTY_CYCLE}. - * - * @param controlMode The mode to use when controlling the motor - * @return {@code this} for chaining - * @throws IllegalArgumentException If {@code controlMode} is for positional control - */ - public MotorSubsystemConfiguration controlMode(ControlMode controlMode) { - if (controlMode.isPositionalControl()) { - throw new IllegalArgumentException( - String.format( - "Control mode %s is for positional control. This is invalid! Please use a different" - + " control mode", - controlMode)); - } - this.controlMode = controlMode; - return this; - } - - /** - * Sets the PID constants for the controller - * - * @param p the proportional - * @param i the integral - * @param d the derivative - * @return {@code this} for chaining - */ - public MotorSubsystemConfiguration PID(double p, double i, double d) { - controller.setPID(p, i, d); - return this; - } - - /** - * Sets the initial setpoint of the controller - * - * @param startingPosition the initial setpoint - * @return {@code this} for chaining - */ - public MotorSubsystemConfiguration startingPosition(Angle startingPosition) { - this.startingPosition = startingPosition.in(this.rotationUnit); - return this; - } - - /** - * Sets the initial setpoint of the controller from the current value of a supplier. - * - *

This is provided to allow the subclass to define an {@code Enum} (that implements {@code - * Supplier}) which defines the supported positions of this subsystem. - * - * @param startingPositionSupplier supplier to use to get the initial setpoint - * @return {@code this} for chaining - */ - public MotorSubsystemConfiguration startingPosition(Supplier startingPositionSupplier) { - return startingPosition(startingPositionSupplier.get()); - } - - /** Sets the acceptable position error. */ - public MotorSubsystemConfiguration acceptableError(double error) { - this.acceptableError = error; - return this; - } - - /** - * Sets the unit to use for PID calculations - * - * @param rotationUnit The angle unit to use for calculations - */ - public MotorSubsystemConfiguration rotationUnit(AngleUnit rotationUnit) { - startingPosition = rotationUnit.convertFrom(startingPosition, this.rotationUnit); - this.rotationUnit = rotationUnit; - return this; - } - - public MotorSubsystemConfiguration publishTo(NetworkTableInstance ntInstance) { - this.ntInstance = ntInstance; - return this; - } - } }