Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
85 changes: 85 additions & 0 deletions lib/src/main/java/com/team2813/lib2813/control/ControlMode.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,103 @@

import com.revrobotics.spark.SparkBase.ControlType;

/**
* Enumeration defining standardized motor control modes for the team's control system.
*
* <p>This enum provides a vendor-neutral abstraction over different motor controller control modes
* while maintaining compatibility with specific hardware implementations. Each control mode maps to
* the appropriate vendor-specific control type for seamless integration with different motor
* controller families.
*
* <p>The enum currently includes mappings for REV Robotics SPARK controllers, with each mode
* corresponding to a specific {@link ControlType} from the SPARK API. Additional vendor mappings
* can be added as needed.
*
* @author Team 2813
* @since 1.0
*/
public enum ControlMode {

/**
* Open-loop duty cycle control mode.
*
* <p>Controls the motor by setting the duty cycle (percentage of time the motor is receiving a
* signal/the signal is on) directly. The motor output is proportional to the pulse width,
* typically ranging from -1.0 (full reverse) to +1.0 (full forward). This is the most basic
* control mode and does not use feedback control.
*
* <p>Maps to {@link ControlType#kDutyCycle} for SPARK controllers.
*
* <p>Here is a visualization of the duty cycle: <hr> <br>
* <br>
* <img src="../doc-files/dutycyclediagram.jpeg" width-"100"></img>
*/
DUTY_CYCLE(ControlType.kDutyCycle),

/**
* Closed-loop velocity control mode.
*
* <p>Controls the motor to maintain a specific velocity using PID feedback control. The
* controller continuously adjusts the motor output to minimize the error between the demanded
* velocity and the actual measured velocity from the encoder. This mode is ideal for applications
* requiring consistent speed regardless of load variations.
*
* <p>Maps to {@link ControlType#kVelocity} for SPARK controllers.
*/
VELOCITY(ControlType.kVelocity),

/**
* Closed-loop position control mode with motion profiling.
*
* <p>Controls the motor to reach a specific position using advanced motion profiling algorithms.
* The controller generates smooth velocity and acceleration profiles to move the mechanism to the
* target position while respecting configured motion constraints (max velocity, max
* acceleration). This mode provides the smoothest and most controlled movement for precise
* positioning applications.
*
* <p>Maps to {@link ControlType#kPosition} for SPARK controllers.
*
* <p><b>Note:</b> Despite the name "MOTION_MAGIC," this maps to position control type for SPARK
* controllers, as the motion profiling is handled internally.
*/
MOTION_MAGIC(ControlType.kPosition),

/**
* Open-loop voltage control mode.
*
* <p>Controls the motor by applying a specific voltage directly to the motor terminals. Unlike
* duty cycle control, voltage control compensates for battery voltage variations to provide more
* consistent motor behavior. The demand value represents the desired voltage (in Volts) to apply
* to the motor.
*
* <p>Maps to {@link ControlType#kVoltage} for SPARK controllers.
*/
VOLTAGE(ControlType.kVoltage);

/** The corresponding SPARK controller control type for this mode */
private final ControlType sparkMode;

/**
* Creates a ControlMode with the specified SPARK controller mapping.
*
* @param sparkMode the corresponding {@link ControlType} for SPARK controllers
*/
ControlMode(ControlType sparkMode) {
this.sparkMode = sparkMode;
}

/**
* Gets the corresponding SPARK controller control type for this mode.
*
* <p>This method provides the mapping from the vendor-neutral ControlMode to the specific {@link
* ControlType} required by REV Robotics SPARK controllers. This abstraction allows the same
* control mode enum to be used across different motor controller implementations.
*
* <p>The last references to this method were removed a while ago.
*
* @return the corresponding {@link ControlType} for SPARK controllers
*/
@Deprecated(forRemoval = true)
public ControlType getSparkMode() {
return sparkMode;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,57 +3,119 @@
import com.team2813.lib2813.util.InputValidation;
import java.util.Optional;

/**
* Immutable value class representing the identifying information for a CAN device.
*
* <p>This class encapsulates the essential information needed to uniquely identify a device on the
* CAN bus network: the CAN ID and the specific CAN bus the device is connected to. It provides a
* standardized way to represent device identity across the team's control system architecture.
*
* <p>The class distinguishes between devices on the RoboRIO's built-in CAN bus (represented by an
* empty Optional for the canbus) and devices on named CAN buses such as CANivore or other CAN bus
* interfaces.
*
* <p>Key features:
*
* <ul>
* <li>Immutable design for thread safety and reliable identity
* <li>Input validation for CAN ID range [0, 62]
* </ul>
*
* <p>This class is commonly used as a key in device registries and for comparing device instances
* to determine if they represent the same physical hardware.
*
* @author Team 2813
* @since 1.0
*/
public final class DeviceInformation {

/** The CAN ID of the device, validated to be in range [0, 62] */
private int id;

/** The CAN bus name, empty if on the RoboRIO's default CAN bus */
private Optional<String> canbus;

/**
* Creates a DeviceInformation for a device on the RoboRIO can loop
* Creates DeviceInformation for a device on the RoboRIO's default CAN bus.
*
* @param id the can ID
* <p>This constructor is used for devices connected directly to the RoboRIO's built-in CAN bus
* interface. The canbus will be represented as an empty Optional to indicate the default bus.
*
* @param id the CAN ID of the device, must be in range [0, 62]
* @throws com.team2813.lib2813.util.InvalidCanIdException if the CAN ID is outside the valid
* range
*/
public DeviceInformation(int id) {
this(id, null);
}

/**
* Creates a DeviceInformation with a canbus string. If {@code canbus} is {@code null}, method
* acts like {@link #DeviceInformation(int)} was called
* Creates DeviceInformation with a specific CAN bus name.
*
* <p>This constructor supports devices on named CAN buses such as CANivore devices or other CAN
* bus interfaces. If {@code canbus} is {@code null}, this method behaves identically to {@link
* #DeviceInformation(int)}.
*
* @param id the CAN id
* @param canbus the canbus string
* @param id the CAN ID of the device, must be in range [0, 62]
* @param canbus the CAN bus name, or {@code null} for the RoboRIO default bus
* @throws com.team2813.lib2813.util.InvalidCanIdException if the CAN ID is outside the valid
* range
*/
public DeviceInformation(int id, String canbus) {
this.id = InputValidation.checkCanId(id);
this.canbus = Optional.ofNullable(canbus);
}

/**
* Gets the can id of this device
* Gets the CAN ID of this device.
*
* @return the can id of the device
* <p>The CAN ID is guaranteed to be in the valid range [0, 62] due to validation performed during
* construction.
*
* @return the CAN ID of the device
*/
public int id() {
return id;
}

/**
* Returns the canbus that this device is on, or {@link Optional#empty()} if it is on the RoboRIO
* can loop
* Returns the CAN bus that this device is connected to.
*
* <p>The return value interpretation:
*
* <ul>
* <li>{@link Optional#empty()} - Device is on the RoboRIO's default CAN bus
* <li>{@code Optional.of("busname")} - Device is on the named CAN bus
* </ul>
*
* @return the canbus that the device is on
* @return an Optional containing the CAN bus name, or empty if on the RoboRIO CAN bus
*/
public Optional<String> canbus() {
return canbus;
}

/**
* Determines whether this DeviceInformation is equal to another object.
*
* <p>Two DeviceInformation instances are considered equal if and only if they have the same CAN
* ID and are on the same CAN bus. This allows DeviceInformation objects to be used reliably as
* keys in hash-based collections and for device identity comparisons.
*
* @param o the object to compare against
* @return {@code true} if the objects represent the same device, {@code false} otherwise
*/
@Override
public boolean equals(Object o) {
if (!(o instanceof DeviceInformation)) return false;
DeviceInformation other = (DeviceInformation) o;
return other.id == id && other.canbus.equals(canbus);
}

/**
* Returns a hash code value for this DeviceInformation.
*
* @return a hash code value for this object
*/
@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
Loading
Loading