418 lines
17 KiB
Java
418 lines
17 KiB
Java
package frc.robot.controls;
|
|
import static edu.wpi.first.units.Units.MetersPerSecond;
|
|
import static edu.wpi.first.units.Units.RotationsPerSecond;
|
|
import com.ctre.phoenix6.swerve.SwerveModule.DriveRequestType;
|
|
import com.ctre.phoenix6.swerve.SwerveRequest;
|
|
|
|
import edu.wpi.first.math.filter.SlewRateLimiter;
|
|
import edu.wpi.first.math.geometry.Pose2d;
|
|
import edu.wpi.first.math.geometry.Rotation2d;
|
|
import edu.wpi.first.networktables.NetworkTable;
|
|
import edu.wpi.first.networktables.NetworkTableInstance;
|
|
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
|
|
import edu.wpi.first.wpilibj2.command.Command;
|
|
import edu.wpi.first.wpilibj2.command.Commands;
|
|
import edu.wpi.first.wpilibj2.command.InstantCommand;
|
|
import edu.wpi.first.wpilibj2.command.button.CommandXboxController;
|
|
import frc.robot.Constants;
|
|
import frc.robot.Constants.DriveMode;
|
|
import frc.robot.generated.TunerConstants;
|
|
import frc.robot.subsystems.CommandSwerveDrivetrain;
|
|
import frc.robot.subsystems.Superstructure;
|
|
import frc.robot.subsystems.Superstructure.WantedState;
|
|
|
|
import java.util.Optional;
|
|
import edu.wpi.first.wpilibj.DriverStation;
|
|
import edu.wpi.first.wpilibj.DriverStation.Alliance;
|
|
import edu.wpi.first.wpilibj.Timer;
|
|
|
|
public class DriveControls {
|
|
|
|
public boolean isRedAlliance;
|
|
|
|
private final CommandSwerveDrivetrain drivetrain;
|
|
private final CommandXboxController driver;
|
|
|
|
private final double maxSpeed = TunerConstants.kSpeedAt12Volts.in(MetersPerSecond);
|
|
private final double maxAngularRate = RotationsPerSecond.of(0.75).in(edu.wpi.first.units.Units.RadiansPerSecond);
|
|
|
|
// 默认速度比例,普通驾驶使用。
|
|
private double normalDriveScale = 0.3;
|
|
private double normalTurnScale = 0.7;
|
|
|
|
// 加速模式比例,按住 RB 时使用。
|
|
private double boostDriveScale = 1.0;
|
|
private double boostTurnScale = 1.0;
|
|
// 自动瞄准时降低平移速度。
|
|
private double slowDriveScale =0.1;
|
|
|
|
private boolean boostEnabled = false;
|
|
|
|
private final SlewRateLimiter vxLimiter = new SlewRateLimiter(5.0); // m/s^2 等效加速度限制
|
|
private final SlewRateLimiter vyLimiter = new SlewRateLimiter(5.0);
|
|
private final SlewRateLimiter omegaLimiter = new SlewRateLimiter(9.0); // rad/s^2 等效角加速度限制
|
|
|
|
// 记录上一周期输出,用于只限制加速、不限制减速。
|
|
private double lastVx = 0.0;
|
|
private double lastVy = 0.0;
|
|
private double lastOmega = 0.0;
|
|
|
|
private NetworkTable autoControlNetworkTable;
|
|
|
|
private final Superstructure superstructure;
|
|
public void setBoostEnabled(boolean enabled) {
|
|
boostEnabled = enabled;
|
|
}
|
|
|
|
// 当前是否使用场地坐标系,默认启用。
|
|
private boolean fieldCentricEnabled = true;
|
|
|
|
private final SwerveRequest.FieldCentric fieldCentric =
|
|
new SwerveRequest.FieldCentric()
|
|
.withDeadband(maxSpeed * 0.05)
|
|
.withRotationalDeadband(maxAngularRate * 0.05)
|
|
.withDriveRequestType(DriveRequestType.Velocity);
|
|
|
|
private final SwerveRequest.FieldCentric fieldCentricAuto =
|
|
new SwerveRequest.FieldCentric()
|
|
.withDeadband(maxSpeed * 0.05)
|
|
.withRotationalDeadband(maxAngularRate * 0.05)
|
|
.withDriveRequestType(DriveRequestType.Velocity)
|
|
.withForwardPerspective(SwerveRequest.ForwardPerspectiveValue.BlueAlliance);
|
|
|
|
private final SwerveRequest.RobotCentric robotCentric =
|
|
new SwerveRequest.RobotCentric()
|
|
.withDeadband(maxSpeed * 0.05)
|
|
.withRotationalDeadband(maxAngularRate * 0.05)
|
|
.withDriveRequestType(DriveRequestType.Velocity);
|
|
|
|
public DriveControls(CommandSwerveDrivetrain drivetrain, CommandXboxController driver, Superstructure superstructure) {
|
|
this.drivetrain = drivetrain;
|
|
this.driver = driver;
|
|
this.superstructure = superstructure;
|
|
autoControlNetworkTable = NetworkTableInstance.getDefault().getTable("AutoControl");
|
|
}
|
|
|
|
public void setFieldCentricEnabled(boolean enabled) {
|
|
fieldCentricEnabled = enabled;
|
|
}
|
|
|
|
public boolean isFieldCentricEnabled() {
|
|
return fieldCentricEnabled;
|
|
}
|
|
|
|
/** 底盘默认驾驶命令,在 teleop 期间持续执行。 */
|
|
public Command defaultDriveCommand() {
|
|
return drivetrain.applyRequest(() -> {
|
|
WantedState wantedState = superstructure.getWantedState();
|
|
SmartDashboard.putString("wantedState", wantedState.name());
|
|
double driveScale = boostEnabled ? boostDriveScale : normalDriveScale;
|
|
double turnScale = boostEnabled ? boostTurnScale : normalTurnScale;
|
|
double vx = -driver.getLeftY() * maxSpeed * driveScale;
|
|
double vy = -driver.getLeftX() * maxSpeed * driveScale;
|
|
double vomega = -driver.getRightX() * maxAngularRate * turnScale;
|
|
double limitedVx = limitAccelOnly(vx, lastVx, vxLimiter);
|
|
double limitedVy = limitAccelOnly(vy, lastVy, vyLimiter);
|
|
double limitedOmega = limitAccelOnly(vomega, lastOmega, omegaLimiter);
|
|
|
|
lastVx = limitedVx;
|
|
lastVy = limitedVy;
|
|
lastOmega = limitedOmega;
|
|
|
|
vx = limitedVx;
|
|
vy = limitedVy;
|
|
vomega = limitedOmega;
|
|
|
|
double[] hubAim = calculateDistanceAndRotationToHub();
|
|
double[] passAim = calculateDistanceAndRotationToPassBall();
|
|
autoControlNetworkTable.getEntry("distanceToHub").setDouble(hubAim[0]);
|
|
autoControlNetworkTable.getEntry("angleDifferenceToHub").setDouble(hubAim[1]);
|
|
autoControlNetworkTable.getEntry("distanceToPassball").setDouble(passAim[0]);
|
|
autoControlNetworkTable.getEntry("angleDifferenceToPassball").setDouble(passAim[1]);
|
|
|
|
double[] activeAim = wantedState == WantedState.PASS_BALL ? passAim : hubAim;
|
|
drivetrain.setTargetHeading(
|
|
drivetrain.getRotation().plus(Rotation2d.fromDegrees(activeAim[1])));
|
|
|
|
switch (wantedState) {
|
|
case SHOOT_HUB:
|
|
case AIM_HUB:
|
|
vomega = calculateRotationSpeedFromRotationAngle(hubAim[1]);
|
|
autoControlNetworkTable.getEntry("autoRotationRate").setDouble(vomega);
|
|
vx = -driver.getLeftY() * maxSpeed * slowDriveScale;
|
|
vy = -driver.getLeftX() * maxSpeed * slowDriveScale;
|
|
break;
|
|
case PASS_BALL:
|
|
vomega = calculateRotationSpeedFromRotationAngle(passAim[1]);
|
|
autoControlNetworkTable.getEntry("autoRotationRate").setDouble(vomega);
|
|
break;
|
|
default:
|
|
autoControlNetworkTable.getEntry("autoRotationRate").setDouble(Double.NaN);
|
|
break;
|
|
}
|
|
|
|
autoControlNetworkTable.getEntry("ActualVX").setDouble(vx);
|
|
autoControlNetworkTable.getEntry("ActualVY").setDouble(vy);
|
|
autoControlNetworkTable.getEntry("ActualVOmega").setDouble(vomega);
|
|
|
|
if (fieldCentricEnabled) {
|
|
return fieldCentric
|
|
.withVelocityX(vx)
|
|
.withVelocityY(vy)
|
|
.withRotationalRate(vomega);
|
|
} else {
|
|
return robotCentric
|
|
.withVelocityX(vx)
|
|
.withVelocityY(vy)
|
|
.withRotationalRate(vomega);
|
|
}
|
|
}, () -> fieldCentricEnabled ? DriveMode.FIELD_CENTRIC : DriveMode.ROBOT_CENTRIC);
|
|
}
|
|
// 返回当前位置到两个目标点中更近目标的位移。
|
|
// 返回到更近目标的带符号位移。
|
|
public double calculateDifferenceToTwoTarget(double current, double target1, double target2){
|
|
double x1 = target1 - current;
|
|
double x2 = target2 - current;
|
|
return Math.abs(x1) < Math.abs(x2) ? x1 : x2;
|
|
}
|
|
public double[] calculateDistanceAndRotationToHub() {
|
|
Pose2d currentPose = drivetrain.getState().Pose; // 当前机器人位姿
|
|
double targetX = isRedAlliance ? Constants.Field.RedHubPositionX : Constants.Field.BlueHubPositionX;
|
|
double targetY = isRedAlliance ? Constants.Field.RedHubPositionY : Constants.Field.BlueHubPositionY;
|
|
|
|
// 计算目标角度,相对于场地坐标系。
|
|
double deltaX = targetX - currentPose.getX();
|
|
double deltaY = targetY - currentPose.getY();
|
|
double targetAngle = Math.toDegrees(Math.atan2(deltaY, deltaX)); // 转换为角度
|
|
|
|
// 当前机器人朝向。
|
|
double currentAngle = currentPose.getRotation().getDegrees();
|
|
double angleDifference = targetAngle - currentAngle;
|
|
// Normalize angle difference to the range [-180, 180]
|
|
if (angleDifference > 180) {
|
|
angleDifference -= 360;
|
|
} else if (angleDifference < -180) {
|
|
angleDifference += 360;
|
|
}
|
|
|
|
// 计算距离。
|
|
double distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
// 返回距离和旋转角度误差。
|
|
double[] distanceAndRotation = new double[]{distance, angleDifference};
|
|
|
|
// 距离单位为米,角度单位为度。
|
|
return distanceAndRotation;
|
|
}
|
|
public double[] calculateDistanceAndRotationToPassBall() {
|
|
Pose2d currentPose = drivetrain.getState().Pose; // 当前机器人位姿
|
|
double targetX = isRedAlliance ? Constants.Field.RedPassingBallPosX : Constants.Field.BluePassingBallPosX; // 传球目标 X 坐标
|
|
double targetY1 = Constants.Field.PassingBallPosY1; // 传球目标 Y 坐标
|
|
double targetY2 = Constants.Field.PassingBallPosY2; // 传球目标 Y 坐标
|
|
|
|
// 计算目标角度,相对于场地坐标系。
|
|
double deltaX = targetX - currentPose.getX();
|
|
double deltaY = calculateDifferenceToTwoTarget(currentPose.getY(), targetY1, targetY2);
|
|
double targetAngle = Math.toDegrees(Math.atan2(deltaY, deltaX)); // 转换为角度
|
|
|
|
// 当前机器人朝向。
|
|
double currentAngle = currentPose.getRotation().getDegrees();
|
|
double angleDifference = targetAngle - currentAngle;
|
|
// Normalize angle difference to the range [-180, 180]
|
|
if (angleDifference > 180) {
|
|
angleDifference -= 360;
|
|
} else if (angleDifference < -180) {
|
|
angleDifference += 360;
|
|
}
|
|
|
|
// 计算距离。
|
|
double distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
// 返回距离和旋转角度误差。
|
|
double[] distanceAndRotation = new double[]{distance, angleDifference};
|
|
|
|
// 距离单位为米,角度单位为度。
|
|
return distanceAndRotation;
|
|
}
|
|
|
|
public double calculateRotationSpeedFromRotationAngle(double angleDifference) {
|
|
double calDifference = angleDifference;
|
|
|
|
// Optional: Apply teleop offset if enabled
|
|
if (Constants.AutoPositioning.teleopOffsetEnabled) {
|
|
double teleopOffset = -driver.getRightX();
|
|
calDifference += teleopOffset * Constants.AutoPositioning.teleopOffsetkP;
|
|
}
|
|
|
|
// If angle difference is within threshold, don't rotate
|
|
if (Math.abs(calDifference) > 1.0) {
|
|
double rotationRate = calDifference * Constants.AutoPositioning.autoRotationkP;
|
|
if (Math.abs(rotationRate) < 0.24) {
|
|
return Math.copySign(0.24, rotationRate);
|
|
}
|
|
return rotationRate;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
private double calculatevomegaFromRotationAngle(double error) {
|
|
double vomega;
|
|
if (Math.abs(error) < Math.toRadians(Constants.AutoPositioning.autoPositioningAngleError)) {
|
|
vomega = 0.0;
|
|
} else {
|
|
vomega = error * Constants.AutoPositioning.TurningkP;
|
|
}
|
|
// 限制最大角速度。
|
|
vomega = Math.max(-maxAngularRate, Math.min(maxAngularRate, vomega));
|
|
return vomega;
|
|
}
|
|
|
|
/**
|
|
* 只限制加速,也就是输出幅值变大时;不限制减速。
|
|
* 这样松杆或刹车能更及时,同时 boost 仍能抑制电流冲击。
|
|
*/
|
|
private double limitAccelOnly(double target, double last, SlewRateLimiter limiter) {
|
|
// 如果正在减速,直接放行到目标值。
|
|
if (Math.abs(target) < Math.abs(last)) {
|
|
limiter.reset(target); // 同步 limiter 内部状态,防止下一次突跳。
|
|
return target;
|
|
}
|
|
// 否则是加速,使用 limiter。
|
|
return limiter.calculate(target);
|
|
}
|
|
|
|
/** 紧急停止:清零限速器,让速度输出立刻归零。 */
|
|
public void emergencyStop() {
|
|
vxLimiter.reset(0.0);
|
|
vyLimiter.reset(0.0);
|
|
omegaLimiter.reset(0.0);
|
|
|
|
lastVx = 0.0;
|
|
lastVy = 0.0;
|
|
lastOmega = 0.0;
|
|
}
|
|
|
|
public Command idleCommand() {
|
|
final var idle = new SwerveRequest.Idle();
|
|
return drivetrain.applyRequest(() -> idle).ignoringDisable(true);
|
|
}
|
|
|
|
public Command brakeWhileHeld() {
|
|
final var brake = new SwerveRequest.SwerveDriveBrake();
|
|
return drivetrain.applyRequest(() -> brake);
|
|
}
|
|
|
|
|
|
public SwerveRequest.SwerveDriveBrake brakeRequest() {
|
|
return new SwerveRequest.SwerveDriveBrake();
|
|
}
|
|
|
|
public SwerveRequest.Idle idleRequest() {
|
|
return new SwerveRequest.Idle();
|
|
}
|
|
|
|
public double getMaxSpeed() {
|
|
return maxSpeed;
|
|
}
|
|
|
|
public double getMaxAngularRate() {
|
|
return maxAngularRate;
|
|
}
|
|
public void setTeamColors() {
|
|
Optional<Alliance> ally = DriverStation.getAlliance(); // 获取联盟颜色
|
|
if (ally.isPresent()) {
|
|
// 根据联盟颜色更新自动瞄准目标。
|
|
if (ally.get() == Alliance.Red) {
|
|
isRedAlliance = true; // 红队
|
|
} else if (ally.get() == Alliance.Blue) {
|
|
isRedAlliance = false; // 蓝队
|
|
}
|
|
} else {
|
|
isRedAlliance = true;
|
|
}
|
|
if (isRedAlliance) {
|
|
System.out.println("Robot is on the Red Alliance.");
|
|
} else {
|
|
System.out.println("Robot is on the Blue Alliance.");
|
|
}
|
|
}
|
|
public Command getAllianceColorCommand() {
|
|
return Commands.runOnce(this::setTeamColors);
|
|
}
|
|
public Command autoAimCommand() {
|
|
return drivetrain.applyRequest(() -> {
|
|
double[] distanceAndRotation = calculateDistanceAndRotationToHub();
|
|
double vomega = calculateRotationSpeedFromRotationAngle(distanceAndRotation[1]);
|
|
return fieldCentric
|
|
.withVelocityX(0.0)
|
|
.withVelocityY(0.0)
|
|
.withRotationalRate(vomega);
|
|
}, () -> DriveMode.FIELD_CENTRIC)
|
|
.until(() -> Math.abs(calculateDistanceAndRotationToHub()[1]) < 1.5)
|
|
.withTimeout(1.0)
|
|
.andThen(new InstantCommand(() -> {
|
|
drivetrain.stop();
|
|
}, drivetrain));
|
|
}
|
|
public Command shakeCommand(double amplitude, double switchPeriod, double totalTime) {
|
|
Timer timer = new Timer();
|
|
|
|
return Commands.sequence(
|
|
new InstantCommand(timer::restart),
|
|
drivetrain.applyRequest(() -> {
|
|
double t = timer.get();
|
|
int phase = ((int) (t / switchPeriod)) % 4;
|
|
|
|
double vx = 0.0;
|
|
double vy = 0.0;
|
|
double[] distanceAndRotation = calculateDistanceAndRotationToHub();
|
|
double vomega = calculateRotationSpeedFromRotationAngle(distanceAndRotation[1]);
|
|
|
|
|
|
switch (phase) {
|
|
case 0:
|
|
vx = amplitude; // 前
|
|
vy = 0.0;
|
|
break;
|
|
case 1:
|
|
vx = 0.0;
|
|
vy = amplitude; // 左
|
|
break;
|
|
case 2:
|
|
vx = -amplitude; // 后
|
|
vy = 0.0;
|
|
break;
|
|
case 3:
|
|
vx = 0.0;
|
|
vy = -amplitude; // 右
|
|
break;
|
|
}
|
|
|
|
return robotCentric
|
|
.withVelocityX(vx)
|
|
.withVelocityY(vy)
|
|
.withRotationalRate(vomega);
|
|
}, () -> DriveMode.ROBOT_CENTRIC).withTimeout(totalTime),
|
|
new InstantCommand(() -> {
|
|
timer.stop();
|
|
drivetrain.stop();
|
|
}, drivetrain)
|
|
);
|
|
}
|
|
|
|
public Command shakeCommand() {
|
|
return shakeCommand(0.3, 0.15, 5.0);
|
|
}
|
|
|
|
public void pushDistanceData(){
|
|
double[] distanceAndRotation = calculateDistanceAndRotationToHub();
|
|
// 将距离数据写入 NetworkTable。
|
|
autoControlNetworkTable.getEntry("distanceToHub").setDouble(distanceAndRotation[0]);
|
|
autoControlNetworkTable.getEntry("angleDifferenceToHub").setDouble(distanceAndRotation[1]);
|
|
// 自动阶段默认驾驶命令不运行,这里同样保持目标朝向最新,
|
|
// 让自动程序里的射击门控 isAimed() 有效。
|
|
drivetrain.setTargetHeading(
|
|
drivetrain.getRotation().plus(Rotation2d.fromDegrees(distanceAndRotation[1])));
|
|
}
|
|
|
|
}
|