Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions lib/src/main/java/com/team2813/lib2813/control/ControlMode.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,42 @@
package com.team2813.lib2813.control;

/**
* Defines the control algorithms available for motor controllers.
*
* <p>The positional control flag is used to determine if a control mode requires position feedback
* for proper operation. This distinction is important for:
*
* <ul>
* <li>Validating that position sensors are configured before use
* <li>Determining if setpoint wrapping/limits should apply
* <li>Selecting appropriate feed-forward models
* </ul>
*/
public enum ControlMode {
/** Open-loop output as percentage of max [-1.0, 1.0] */
DUTY_CYCLE(false),

/** Closed-loop velocity control (requires velocity feedback) */
VELOCITY(false),

/** Trapezoidal motion profile with velocity/acceleration limits (requires position feedback) */
MOTION_MAGIC(true),

/** Open-loop voltage output [-12V, 12V] */
VOLTAGE(false);

private final boolean isPositionalControl;

/**
* @param isPositionalControl true if this mode requires position feedback
*/
ControlMode(boolean isPositionalControl) {
this.isPositionalControl = isPositionalControl;
}

/**
* @return true if this control mode requires position sensing
*/
public boolean isPositionalControl() {
return isPositionalControl;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
import com.team2813.lib2813.util.InputValidation;
import java.util.Optional;

/**
* Immutable container for CAN device addressing information.
*
* <p>CAN devices can exist on either the RoboRIO's built-in CAN bus or on external CAN buses (e.g.,
* CANivore). This class encapsulates both the device ID and optional bus name for proper device
* identification.
*/
public final class DeviceInformation {
private int id;
private Optional<String> canbus;
Expand Down Expand Up @@ -47,13 +54,24 @@ public Optional<String> canbus() {
return canbus;
}

/**
* Compares based on both ID and CAN bus.
*
* @param o object to compare
* @return true if both ID and bus match
*/
@Override
public boolean equals(Object o) {
if (!(o instanceof DeviceInformation)) return false;
DeviceInformation other = (DeviceInformation) o;
return other.id == id && other.canbus.equals(canbus);
}

/**
* Hash combines ID and bus for proper hash-based collection behavior.
*
* @return combined hash of id and canbus
*/
@Override
public int hashCode() {
return id * 31 + canbus.hashCode();
Expand Down
98 changes: 84 additions & 14 deletions lib/src/main/java/com/team2813/lib2813/control/Encoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,123 @@
import edu.wpi.first.units.measure.Angle;
import edu.wpi.first.units.measure.AngularVelocity;

/** Specifies a device that can perceive rotational positions. */
/**
* Interface specifying a device that can perceive rotational positions and velocities.
*
* <p>This interface defines the contract for encoder devices that provide angular position and
* velocity feedback. It supports both legacy double-based methods (deprecated) and modern type-safe
* unit-based methods using WPILib's units system.
*
* <p>The interface provides a migration path from unsafe raw double values to type-safe {@link
* Angle} and {@link AngularVelocity} measurements. New implementations should focus on the
* unit-safe methods, while legacy methods are maintained for backward compatibility but marked for
* removal.
*
* <p>Common implementations include absolute encoders (CANcoder), relative encoders (integrated
* motor encoders), and other rotational position sensing devices.
*
* @author Team 2813
* @since 1.0
*/
public interface Encoder {

/**
* Gets the position of the encoder
* Gets the current position of the encoder as a raw double value.
*
* @return the position of the encoder
* @return the position of the encoder as an unspecified double value
* @deprecated This method does not specify position in a specific measurement, so it is not safe
* to use. Use {@link #getPositionMeasure()} instead
* to use. Use {@link #getPositionMeasure()} instead for type safety
*/
@Deprecated(forRemoval = true)
double position();

/**
* Gets the position of the encoder
* Gets the current position of the encoder using type-safe units.
*
* <p>This method returns the encoder position as an {@link Angle} measurement, providing type
* safety and explicit unit handling. The returned angle can be easily converted to any angular
* unit (degrees, radians, rotations) using the WPILib units system.
*
* <p>Example usage:
*
* <pre>{@code
* Angle position = encoder.getPositionMeasure();
* double degrees = position.in(Units.Degrees);
* double rotations = position.in(Units.Rotations);
* }</pre>
*
* @return the position of the encoder as a measure
* @return the current position of the encoder as an {@link Angle} measurement
*/
Angle getPositionMeasure();

/**
* Sets the position of the encoder
* Sets the encoder position to the specified raw double value.
*
* @param position the position of the encoder
* <p><b>Warning:</b> This method accepts position without specifying units, making it unsafe and
* ambiguous. The interpretation of the position value depends on the specific encoder
* implementation and configuration.
*
* @param position the new position value as an unspecified double
* @deprecated This method does not specify a unit, so it is not safe to use. Use {@link
* #setPosition(Angle)} instead.
* #setPosition(Angle)} instead for type safety
*/
@Deprecated(forRemoval = true)
void setPosition(double position);

/**
* Sets the encoder position using type-safe units.
*
* <p>This method accepts any {@link Angle} measurement and converts it to the encoder's native
* units for setting the position. The type-safe approach eliminates unit confusion and provides
* clear, readable code.
*
* <p>The default implementation converts the angle to radians and calls the legacy {@link
* #setPosition(double)} method. Implementations should override this method to provide direct
* unit-safe position setting when possible.
*
* <p>Example usage:
*
* <pre>{@code
* encoder.setPosition(Units.Degrees.of(90));
* encoder.setPosition(Units.Rotations.of(0.25));
* }</pre>
*
* @param position the new position as an {@link Angle} measurement
*/
default void setPosition(Angle position) {
setPosition(position.in(Units.Radians));
}

/**
* Gets the velocity of the encoder
* Gets the current velocity of the encoder as a raw double value.
*
* @return the velocity that the encoder perceives
* @return the velocity that the encoder perceives as an unspecified double value
* @deprecated This method does not specify velocity in a specific measurement, so it is not safe
* to use. Use {@link #getVelocityMeasure()} instead
* to use. Use {@link #getVelocityMeasure()} instead for type safety
*/
@Deprecated(forRemoval = true)
double getVelocity();

/**
* Gets the velocity of the encoder
* Gets the current velocity of the encoder using type-safe units.
*
* <p>This method returns the encoder velocity as an {@link AngularVelocity} measurement,
* providing type safety and explicit unit handling. The returned velocity can be easily converted
* to any angular velocity unit using the WPILib units system.
*
* <p>The default implementation assumes the legacy {@link #getVelocity()} method returns radians
* per second and wraps it in a type-safe measurement. Implementations should override this method
* to provide the correct units for their specific hardware.
*
* <p>Example usage:
*
* <pre>{@code
* AngularVelocity velocity = encoder.getVelocityMeasure();
* double rpm = velocity.in(Units.RPM);
* double radPerSec = velocity.in(Units.RadiansPerSecond);
* }</pre>
*
* @return The velocity as a measure
* @return the current velocity as an {@link AngularVelocity} measurement
*/
default AngularVelocity getVelocityMeasure() {
return Units.RadiansPerSecond.of(getVelocity());
Expand Down
44 changes: 41 additions & 3 deletions lib/src/main/java/com/team2813/lib2813/control/InvertType.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@
import java.util.*;
import java.util.stream.Stream;

/**
* Unified inversion semantics across different motor controller families.
*
* <p>This enum provides two distinct inversion modes:
*
* <ul>
* <li><b>Absolute direction:</b> CLOCKWISE/COUNTER_CLOCKWISE define motor direction independent
* of leader state. Used for standalone motors or when precise control over follower direction
* is needed.
* <li><b>Relative direction:</b> FOLLOW_MASTER/OPPOSE_MASTER define direction relative to the
* leader motor. Simplifies configuration when followers should mirror or oppose leader
* behavior regardless of leader inversion.
* </ul>
*
* <p>The lazy-initialized Maps class uses a holder pattern to defer reverse-mapping construction
* until first use, avoiding unnecessary computation if conversions are never needed.
*/
public enum InvertType {
CLOCKWISE(InvertedValue.Clockwise_Positive, true),
COUNTER_CLOCKWISE(InvertedValue.CounterClockwise_Positive, false),
Expand All @@ -22,11 +39,18 @@ public enum InvertType {
private final Optional<InvertedValue> phoenixInvert;
private final Optional<Boolean> sparkMaxInvert;

/** Constructor for relative inversion types (no hardware mapping). */
InvertType() {
phoenixInvert = Optional.empty();
sparkMaxInvert = Optional.empty();
}

/**
* Constructor for absolute direction types.
*
* @param phoenixInvert CTRE Phoenix 6 inversion value
* @param sparkMaxInvert REV Spark Max inversion boolean
*/
InvertType(InvertedValue phoenixInvert, boolean sparkMaxInvert) {
this.phoenixInvert = Optional.of(phoenixInvert);
this.sparkMaxInvert = Optional.of(sparkMaxInvert);
Expand Down Expand Up @@ -54,26 +78,40 @@ public static Optional<InvertType> fromSparkMaxInvert(boolean v) {
return Optional.of(Maps.sparkMaxMap.get(v));
}

/**
* @return Phoenix inversion value if this is an absolute direction type
*/
public Optional<InvertedValue> phoenixInvert() {
return phoenixInvert;
}

/**
* @return Phoenix inversion, throwing if not present (internal use only)
*/
private InvertedValue forcePhoenixInvert() {
return phoenixInvert.orElseThrow();
}

/**
* @return Spark Max inversion value if this is an absolute direction type
*/
public Optional<Boolean> sparkMaxInvert() {
return sparkMaxInvert;
}

/**
* @return Spark Max inversion, throwing if not present (internal use only)
*/
private boolean forceSparkMaxInvert() {
return sparkMaxInvert.orElseThrow();
}

/**
* Contains the maps for {@link InvertType#fromPhoenixInvert(InvertedValue)} and {@link
* InvertType#fromSparkMaxInvert(boolean)}. In a static class so that they will only be
* initialized if they are needed.
* Lazy-initialized reverse lookup maps using the holder pattern.
*
* <p>Maps are constructed only when first accessed, avoiding overhead if
* fromPhoenixInvert/fromSparkMaxInvert are never called. The merge function (a, b) -> null
* handles the impossible case of duplicate keys, which cannot occur given the enum definition.
*/
private static final class Maps {
private static final Map<InvertedValue, InvertType> phoenixMap =
Expand Down
3 changes: 1 addition & 2 deletions lib/src/main/java/com/team2813/lib2813/control/Motor.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

import edu.wpi.first.units.measure.Current;

/** Basic motor control interface for command-based control and telemetry. */
public interface Motor {
// motor control

/**
* Sets the motor to run with a specified mode of control.
*
Expand Down
38 changes: 38 additions & 0 deletions lib/src/main/java/com/team2813/lib2813/control/PIDMotor.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,49 @@
package com.team2813.lib2813.control;

/**
* A motor with integrated PID control and encoder feedback.
*
* <p>Combines motor control, position sensing, and closed-loop control configuration. Some motor
* controllers support multiple PID slots for switching between different tuning parameters.
*/
public interface PIDMotor extends Motor, Encoder {
/**
* Configures PIDF constants for a specific slot.
*
* @param slot the PID slot index (hardware-dependent, typically 0-3)
* @param p the proportional gain
* @param i the integral gain
* @param d the derivative gain
* @param f the feedforward gain
*/
void configPIDF(int slot, double p, double i, double d, double f);

/**
* Configures PIDF constants for the default slot (typically slot 0).
*
* @param p the proportional gain
* @param i the integral gain
* @param d the derivative gain
* @param f the feedforward gain
*/
void configPIDF(double p, double i, double d, double f);

/**
* Configures PID constants for a specific slot with zero feedforward.
*
* @param slot the PID slot index (hardware-dependent, typically 0-3)
* @param p the proportional gain
* @param i the integral gain
* @param d the derivative gain
*/
void configPID(int slot, double p, double i, double d);

/**
* Configures PID constants for the default slot (typically slot 0) with zero feedforward.
*
* @param p the proportional gain
* @param i the integral gain
* @param d the derivative gain
*/
void configPID(double p, double i, double d);
}
Loading
Loading