295 lines
13 KiB
Java
295 lines
13 KiB
Java
package frc.robot.subsystems;
|
|
|
|
import static edu.wpi.first.units.Units.*;
|
|
|
|
import java.util.function.Supplier;
|
|
|
|
import com.ctre.phoenix6.SignalLogger;
|
|
import com.ctre.phoenix6.Utils;
|
|
import com.ctre.phoenix6.swerve.SwerveDrivetrainConstants;
|
|
import com.ctre.phoenix6.swerve.SwerveModuleConstants;
|
|
import com.ctre.phoenix6.swerve.SwerveRequest;
|
|
|
|
import com.pathplanner.lib.auto.AutoBuilder;
|
|
import com.pathplanner.lib.config.PIDConstants;
|
|
import com.pathplanner.lib.config.RobotConfig;
|
|
import com.pathplanner.lib.controllers.PPHolonomicDriveController;
|
|
|
|
import edu.wpi.first.math.Matrix;
|
|
import edu.wpi.first.math.geometry.Rotation2d;
|
|
import edu.wpi.first.math.numbers.N1;
|
|
import edu.wpi.first.math.numbers.N3;
|
|
import edu.wpi.first.wpilibj.DriverStation;
|
|
import edu.wpi.first.wpilibj.DriverStation.Alliance;
|
|
import edu.wpi.first.wpilibj.Notifier;
|
|
import edu.wpi.first.wpilibj.RobotController;
|
|
import edu.wpi.first.wpilibj2.command.Command;
|
|
import edu.wpi.first.wpilibj2.command.Subsystem;
|
|
import edu.wpi.first.wpilibj2.command.sysid.SysIdRoutine;
|
|
import frc.robot.TunerConstants.TunerConstants.TunerSwerveDrivetrain;
|
|
|
|
/**
|
|
* Class that extends the Phoenix 6 SwerveDrivetrain class and implements
|
|
* Subsystem so it can easily be used in command-based projects.
|
|
*/
|
|
public class CommandSwerveDrivetrain extends TunerSwerveDrivetrain implements Subsystem {
|
|
|
|
private static final double kSimLoopPeriod = 0.005; // 5 ms
|
|
private Notifier m_simNotifier = null;
|
|
private double m_lastSimTime;
|
|
|
|
/* Blue alliance sees forward as 0 degrees (toward red alliance wall) */
|
|
private static final Rotation2d kBlueAlliancePerspectiveRotation = Rotation2d.kZero;
|
|
/* Red alliance sees forward as 180 degrees (toward blue alliance wall) */
|
|
private static final Rotation2d kRedAlliancePerspectiveRotation = Rotation2d.k180deg;
|
|
/* Keep track if we've ever applied the operator perspective before or not */
|
|
private boolean m_hasAppliedOperatorPerspective = false;
|
|
|
|
private final SwerveRequest.ApplyRobotSpeeds m_pathApplyRobotSpeeds = new SwerveRequest.ApplyRobotSpeeds();
|
|
|
|
/* Swerve requests to apply during SysId characterization */
|
|
private final SwerveRequest.SysIdSwerveTranslation m_translationCharacterization = new SwerveRequest.SysIdSwerveTranslation();
|
|
private final SwerveRequest.SysIdSwerveSteerGains m_steerCharacterization = new SwerveRequest.SysIdSwerveSteerGains();
|
|
private final SwerveRequest.SysIdSwerveRotation m_rotationCharacterization = new SwerveRequest.SysIdSwerveRotation();
|
|
|
|
/* SysId routine for characterizing translation. This is used to find PID gains for the drive motors. */
|
|
private final SysIdRoutine m_sysIdRoutineTranslation = new SysIdRoutine(
|
|
new SysIdRoutine.Config(
|
|
null, // Use default ramp rate (1 V/s)
|
|
Volts.of(4), // Reduce dynamic step voltage to 4 V to prevent brownout
|
|
null, // Use default timeout (10 s)
|
|
// Log state with SignalLogger class
|
|
state -> SignalLogger.writeString("SysIdTranslation_State", state.toString())
|
|
),
|
|
new SysIdRoutine.Mechanism(
|
|
output -> setControl(m_translationCharacterization.withVolts(output)),
|
|
null,
|
|
this
|
|
)
|
|
);
|
|
|
|
/* SysId routine for characterizing steer. This is used to find PID gains for the steer motors. */
|
|
private final SysIdRoutine m_sysIdRoutineSteer = new SysIdRoutine(
|
|
new SysIdRoutine.Config(
|
|
null, // Use default ramp rate (1 V/s)
|
|
Volts.of(7), // Use dynamic voltage of 7 V
|
|
null, // Use default timeout (10 s)
|
|
// Log state with SignalLogger class
|
|
state -> SignalLogger.writeString("SysIdSteer_State", state.toString())
|
|
),
|
|
new SysIdRoutine.Mechanism(
|
|
volts -> setControl(m_steerCharacterization.withVolts(volts)),
|
|
null,
|
|
this
|
|
)
|
|
);
|
|
|
|
/*
|
|
* SysId routine for characterizing rotation.
|
|
* This is used to find PID gains for the FieldCentricFacingAngle HeadingController.
|
|
* See the documentation of SwerveRequest.SysIdSwerveRotation for info on importing the log to SysId.
|
|
*/
|
|
private final SysIdRoutine m_sysIdRoutineRotation = new SysIdRoutine(
|
|
new SysIdRoutine.Config(
|
|
/* This is in radians per second², but SysId only supports "volts per second" */
|
|
Volts.of(Math.PI / 6).per(Second),
|
|
/* This is in radians per second, but SysId only supports "volts" */
|
|
Volts.of(Math.PI),
|
|
null, // Use default timeout (10 s)
|
|
// Log state with SignalLogger class
|
|
state -> SignalLogger.writeString("SysIdRotation_State", state.toString())
|
|
),
|
|
new SysIdRoutine.Mechanism(
|
|
output -> {
|
|
/* output is actually radians per second, but SysId only supports "volts" */
|
|
setControl(m_rotationCharacterization.withRotationalRate(output.in(Volts)));
|
|
/* also log the requested output for SysId */
|
|
SignalLogger.writeDouble("Rotational_Rate", output.in(Volts));
|
|
},
|
|
null,
|
|
this
|
|
)
|
|
);
|
|
|
|
private void configureAutoBuilder() {
|
|
try {
|
|
var config = RobotConfig.fromGUISettings();
|
|
AutoBuilder.configure(
|
|
() -> getState().Pose, // Supplier of current robot pose
|
|
this::resetPose, // Consumer for seeding pose against auto
|
|
() -> getState().Speeds, // Supplier of current robot speeds
|
|
// Consumer of ChassisSpeeds and feedforwards to drive the robot
|
|
(speeds, feedforwards) -> setControl(
|
|
m_pathApplyRobotSpeeds.withSpeeds(speeds)
|
|
.withWheelForceFeedforwardsX(feedforwards.robotRelativeForcesXNewtons())
|
|
.withWheelForceFeedforwardsY(feedforwards.robotRelativeForcesYNewtons())
|
|
),
|
|
new PPHolonomicDriveController(
|
|
// PID constants for translation
|
|
new PIDConstants(10, 0, 0),
|
|
// PID constants for rotation
|
|
new PIDConstants(7, 0, 0)
|
|
),
|
|
config,
|
|
// Assume the path needs to be flipped for Red vs Blue, this is normally the case
|
|
() -> DriverStation.getAlliance().orElse(Alliance.Blue) == Alliance.Red,
|
|
this // Subsystem for requirements
|
|
);
|
|
} catch (Exception ex) {
|
|
DriverStation.reportError("Failed to load PathPlanner config and configure AutoBuilder", ex.getStackTrace());
|
|
}
|
|
}
|
|
|
|
/* The SysId routine to test */
|
|
private SysIdRoutine m_sysIdRoutineToApply = m_sysIdRoutineTranslation;
|
|
|
|
/**
|
|
* Constructs a CTRE SwerveDrivetrain using the specified constants.
|
|
* <p>
|
|
* This constructs the underlying hardware devices, so users should not construct
|
|
* the devices themselves. If they need the devices, they can access them through
|
|
* getters in the classes.
|
|
*
|
|
* @param drivetrainConstants Drivetrain-wide constants for the swerve drive
|
|
* @param modules Constants for each specific module
|
|
*/
|
|
public CommandSwerveDrivetrain(
|
|
SwerveDrivetrainConstants drivetrainConstants,
|
|
SwerveModuleConstants<?, ?, ?>... modules
|
|
) {
|
|
super(drivetrainConstants, modules);
|
|
if (Utils.isSimulation()) {
|
|
startSimThread();
|
|
}
|
|
configureAutoBuilder();
|
|
}
|
|
|
|
/**
|
|
* Constructs a CTRE SwerveDrivetrain using the specified constants.
|
|
* <p>
|
|
* This constructs the underlying hardware devices, so users should not construct
|
|
* the devices themselves. If they need the devices, they can access them through
|
|
* getters in the classes.
|
|
*
|
|
* @param drivetrainConstants Drivetrain-wide constants for the swerve drive
|
|
* @param odometryUpdateFrequency The frequency to run the odometry loop. If
|
|
* unspecified or set to 0 Hz, this is 250 Hz on
|
|
* CAN FD, and 100 Hz on CAN 2.0.
|
|
* @param modules Constants for each specific module
|
|
*/
|
|
public CommandSwerveDrivetrain(
|
|
SwerveDrivetrainConstants drivetrainConstants,
|
|
double odometryUpdateFrequency,
|
|
SwerveModuleConstants<?, ?, ?>... modules
|
|
) {
|
|
super(drivetrainConstants, odometryUpdateFrequency, modules);
|
|
if (Utils.isSimulation()) {
|
|
startSimThread();
|
|
}
|
|
configureAutoBuilder();
|
|
}
|
|
|
|
/**
|
|
* Constructs a CTRE SwerveDrivetrain using the specified constants.
|
|
* <p>
|
|
* This constructs the underlying hardware devices, so users should not construct
|
|
* the devices themselves. If they need the devices, they can access them through
|
|
* getters in the classes.
|
|
*
|
|
* @param drivetrainConstants Drivetrain-wide constants for the swerve drive
|
|
* @param odometryUpdateFrequency The frequency to run the odometry loop. If
|
|
* unspecified or set to 0 Hz, this is 250 Hz on
|
|
* CAN FD, and 100 Hz on CAN 2.0.
|
|
* @param odometryStandardDeviation The standard deviation for odometry calculation
|
|
* in the form [x, y, theta]ᵀ, with units in meters
|
|
* and radians
|
|
* @param visionStandardDeviation The standard deviation for vision calculation
|
|
* in the form [x, y, theta]ᵀ, with units in meters
|
|
* and radians
|
|
* @param modules Constants for each specific module
|
|
*/
|
|
public CommandSwerveDrivetrain(
|
|
SwerveDrivetrainConstants drivetrainConstants,
|
|
double odometryUpdateFrequency,
|
|
Matrix<N3, N1> odometryStandardDeviation,
|
|
Matrix<N3, N1> visionStandardDeviation,
|
|
SwerveModuleConstants<?, ?, ?>... modules
|
|
) {
|
|
super(drivetrainConstants, odometryUpdateFrequency, odometryStandardDeviation, visionStandardDeviation, modules);
|
|
if (Utils.isSimulation()) {
|
|
startSimThread();
|
|
}
|
|
configureAutoBuilder();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Returns a command that applies the specified control request to this swerve drivetrain.
|
|
*
|
|
* @param request Function returning the request to apply
|
|
* @return Command to run
|
|
*/
|
|
public Command applyRequest(Supplier<SwerveRequest> requestSupplier) {
|
|
return run(() -> this.setControl(requestSupplier.get()));
|
|
}
|
|
|
|
/**
|
|
* Runs the SysId Quasistatic test in the given direction for the routine
|
|
* specified by {@link #m_sysIdRoutineToApply}.
|
|
*
|
|
* @param direction Direction of the SysId Quasistatic test
|
|
* @return Command to run
|
|
*/
|
|
public Command sysIdQuasistatic(SysIdRoutine.Direction direction) {
|
|
return m_sysIdRoutineToApply.quasistatic(direction);
|
|
}
|
|
|
|
/**
|
|
* Runs the SysId Dynamic test in the given direction for the routine
|
|
* specified by {@link #m_sysIdRoutineToApply}.
|
|
*
|
|
* @param direction Direction of the SysId Dynamic test
|
|
* @return Command to run
|
|
*/
|
|
public Command sysIdDynamic(SysIdRoutine.Direction direction) {
|
|
return m_sysIdRoutineToApply.dynamic(direction);
|
|
}
|
|
|
|
@Override
|
|
public void periodic() {
|
|
/*
|
|
* Periodically try to apply the operator perspective.
|
|
* If we haven't applied the operator perspective before, then we should apply it regardless of DS state.
|
|
* This allows us to correct the perspective in case the robot code restarts mid-match.
|
|
* Otherwise, only check and apply the operator perspective if the DS is disabled.
|
|
* This ensures driving behavior doesn't change until an explicit disable event occurs during testing.
|
|
*/
|
|
if (!m_hasAppliedOperatorPerspective || DriverStation.isDisabled()) {
|
|
DriverStation.getAlliance().ifPresent(allianceColor -> {
|
|
setOperatorPerspectiveForward(
|
|
allianceColor == Alliance.Red
|
|
? kRedAlliancePerspectiveRotation
|
|
: kBlueAlliancePerspectiveRotation
|
|
);
|
|
m_hasAppliedOperatorPerspective = true;
|
|
});
|
|
}
|
|
}
|
|
|
|
private void startSimThread() {
|
|
m_lastSimTime = Utils.getCurrentTimeSeconds();
|
|
|
|
/* Run simulation at a faster rate so PID gains behave more reasonably */
|
|
m_simNotifier = new Notifier(() -> {
|
|
final double currentTime = Utils.getCurrentTimeSeconds();
|
|
double deltaTime = currentTime - m_lastSimTime;
|
|
m_lastSimTime = currentTime;
|
|
|
|
/* use the measured time delta, get battery voltage from WPILib */
|
|
updateSimState(deltaTime, RobotController.getBatteryVoltage());
|
|
});
|
|
m_simNotifier.startPeriodic(kSimLoopPeriod);
|
|
}
|
|
}
|