001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package edu.wpi.first.wpilibj2.command; 006 007import static edu.wpi.first.util.ErrorMessages.requireNonNullParam; 008 009import edu.wpi.first.math.controller.PIDController; 010import edu.wpi.first.math.controller.RamseteController; 011import edu.wpi.first.math.controller.SimpleMotorFeedforward; 012import edu.wpi.first.math.geometry.Pose2d; 013import edu.wpi.first.math.kinematics.ChassisSpeeds; 014import edu.wpi.first.math.kinematics.DifferentialDriveKinematics; 015import edu.wpi.first.math.kinematics.DifferentialDriveWheelSpeeds; 016import edu.wpi.first.math.trajectory.Trajectory; 017import edu.wpi.first.util.sendable.SendableBuilder; 018import edu.wpi.first.wpilibj.Timer; 019import java.util.function.BiConsumer; 020import java.util.function.Supplier; 021 022/** 023 * A command that uses a RAMSETE controller ({@link RamseteController}) to follow a trajectory 024 * {@link Trajectory} with a differential drive. 025 * 026 * <p>The command handles trajectory-following, PID calculations, and feedforwards internally. This 027 * is intended to be a more-or-less "complete solution" that can be used by teams without a great 028 * deal of controls expertise. 029 * 030 * <p>Advanced teams seeking more flexibility (for example, those who wish to use the onboard PID 031 * functionality of a "smart" motor controller) may use the secondary constructor that omits the PID 032 * and feedforward functionality, returning only the raw wheel speeds from the RAMSETE controller. 033 * 034 * <p>This class is provided by the NewCommands VendorDep 035 */ 036public class RamseteCommand extends CommandBase { 037 private final Timer m_timer = new Timer(); 038 private final boolean m_usePID; 039 private final Trajectory m_trajectory; 040 private final Supplier<Pose2d> m_pose; 041 private final RamseteController m_follower; 042 private final SimpleMotorFeedforward m_feedforward; 043 private final DifferentialDriveKinematics m_kinematics; 044 private final Supplier<DifferentialDriveWheelSpeeds> m_speeds; 045 private final PIDController m_leftController; 046 private final PIDController m_rightController; 047 private final BiConsumer<Double, Double> m_output; 048 private DifferentialDriveWheelSpeeds m_prevSpeeds = new DifferentialDriveWheelSpeeds(); 049 private double m_prevTime; 050 051 /** 052 * Constructs a new RamseteCommand that, when executed, will follow the provided trajectory. PID 053 * control and feedforward are handled internally, and outputs are scaled -12 to 12 representing 054 * units of volts. 055 * 056 * <p>Note: The controller will *not* set the outputVolts to zero upon completion of the path - 057 * this is left to the user, since it is not appropriate for paths with nonstationary endstates. 058 * 059 * @param trajectory The trajectory to follow. 060 * @param pose A function that supplies the robot pose - use one of the odometry classes to 061 * provide this. 062 * @param controller The RAMSETE controller used to follow the trajectory. 063 * @param feedforward The feedforward to use for the drive. 064 * @param kinematics The kinematics for the robot drivetrain. 065 * @param wheelSpeeds A function that supplies the speeds of the left and right sides of the robot 066 * drive. 067 * @param leftController The PIDController for the left side of the robot drive. 068 * @param rightController The PIDController for the right side of the robot drive. 069 * @param outputVolts A function that consumes the computed left and right outputs (in volts) for 070 * the robot drive. 071 * @param requirements The subsystems to require. 072 */ 073 public RamseteCommand( 074 Trajectory trajectory, 075 Supplier<Pose2d> pose, 076 RamseteController controller, 077 SimpleMotorFeedforward feedforward, 078 DifferentialDriveKinematics kinematics, 079 Supplier<DifferentialDriveWheelSpeeds> wheelSpeeds, 080 PIDController leftController, 081 PIDController rightController, 082 BiConsumer<Double, Double> outputVolts, 083 Subsystem... requirements) { 084 m_trajectory = requireNonNullParam(trajectory, "trajectory", "RamseteCommand"); 085 m_pose = requireNonNullParam(pose, "pose", "RamseteCommand"); 086 m_follower = requireNonNullParam(controller, "controller", "RamseteCommand"); 087 m_feedforward = feedforward; 088 m_kinematics = requireNonNullParam(kinematics, "kinematics", "RamseteCommand"); 089 m_speeds = requireNonNullParam(wheelSpeeds, "wheelSpeeds", "RamseteCommand"); 090 m_leftController = requireNonNullParam(leftController, "leftController", "RamseteCommand"); 091 m_rightController = requireNonNullParam(rightController, "rightController", "RamseteCommand"); 092 m_output = requireNonNullParam(outputVolts, "outputVolts", "RamseteCommand"); 093 094 m_usePID = true; 095 096 addRequirements(requirements); 097 } 098 099 /** 100 * Constructs a new RamseteCommand that, when executed, will follow the provided trajectory. 101 * Performs no PID control and calculates no feedforwards; outputs are the raw wheel speeds from 102 * the RAMSETE controller, and will need to be converted into a usable form by the user. 103 * 104 * @param trajectory The trajectory to follow. 105 * @param pose A function that supplies the robot pose - use one of the odometry classes to 106 * provide this. 107 * @param follower The RAMSETE follower used to follow the trajectory. 108 * @param kinematics The kinematics for the robot drivetrain. 109 * @param outputMetersPerSecond A function that consumes the computed left and right wheel speeds. 110 * @param requirements The subsystems to require. 111 */ 112 public RamseteCommand( 113 Trajectory trajectory, 114 Supplier<Pose2d> pose, 115 RamseteController follower, 116 DifferentialDriveKinematics kinematics, 117 BiConsumer<Double, Double> outputMetersPerSecond, 118 Subsystem... requirements) { 119 m_trajectory = requireNonNullParam(trajectory, "trajectory", "RamseteCommand"); 120 m_pose = requireNonNullParam(pose, "pose", "RamseteCommand"); 121 m_follower = requireNonNullParam(follower, "follower", "RamseteCommand"); 122 m_kinematics = requireNonNullParam(kinematics, "kinematics", "RamseteCommand"); 123 m_output = 124 requireNonNullParam(outputMetersPerSecond, "outputMetersPerSecond", "RamseteCommand"); 125 126 m_feedforward = null; 127 m_speeds = null; 128 m_leftController = null; 129 m_rightController = null; 130 131 m_usePID = false; 132 133 addRequirements(requirements); 134 } 135 136 @Override 137 public void initialize() { 138 m_prevTime = -1; 139 var initialState = m_trajectory.sample(0); 140 m_prevSpeeds = 141 m_kinematics.toWheelSpeeds( 142 new ChassisSpeeds( 143 initialState.velocityMetersPerSecond, 144 0, 145 initialState.curvatureRadPerMeter * initialState.velocityMetersPerSecond)); 146 m_timer.restart(); 147 if (m_usePID) { 148 m_leftController.reset(); 149 m_rightController.reset(); 150 } 151 } 152 153 @Override 154 public void execute() { 155 double curTime = m_timer.get(); 156 double dt = curTime - m_prevTime; 157 158 if (m_prevTime < 0) { 159 m_output.accept(0.0, 0.0); 160 m_prevTime = curTime; 161 return; 162 } 163 164 var targetWheelSpeeds = 165 m_kinematics.toWheelSpeeds( 166 m_follower.calculate(m_pose.get(), m_trajectory.sample(curTime))); 167 168 var leftSpeedSetpoint = targetWheelSpeeds.leftMetersPerSecond; 169 var rightSpeedSetpoint = targetWheelSpeeds.rightMetersPerSecond; 170 171 double leftOutput; 172 double rightOutput; 173 174 if (m_usePID) { 175 double leftFeedforward = 176 m_feedforward.calculate( 177 leftSpeedSetpoint, (leftSpeedSetpoint - m_prevSpeeds.leftMetersPerSecond) / dt); 178 179 double rightFeedforward = 180 m_feedforward.calculate( 181 rightSpeedSetpoint, (rightSpeedSetpoint - m_prevSpeeds.rightMetersPerSecond) / dt); 182 183 leftOutput = 184 leftFeedforward 185 + m_leftController.calculate(m_speeds.get().leftMetersPerSecond, leftSpeedSetpoint); 186 187 rightOutput = 188 rightFeedforward 189 + m_rightController.calculate( 190 m_speeds.get().rightMetersPerSecond, rightSpeedSetpoint); 191 } else { 192 leftOutput = leftSpeedSetpoint; 193 rightOutput = rightSpeedSetpoint; 194 } 195 196 m_output.accept(leftOutput, rightOutput); 197 m_prevSpeeds = targetWheelSpeeds; 198 m_prevTime = curTime; 199 } 200 201 @Override 202 public void end(boolean interrupted) { 203 m_timer.stop(); 204 205 if (interrupted) { 206 m_output.accept(0.0, 0.0); 207 } 208 } 209 210 @Override 211 public boolean isFinished() { 212 return m_timer.hasElapsed(m_trajectory.getTotalTimeSeconds()); 213 } 214 215 @Override 216 public void initSendable(SendableBuilder builder) { 217 super.initSendable(builder); 218 builder.addDoubleProperty("leftVelocity", () -> m_prevSpeeds.leftMetersPerSecond, null); 219 builder.addDoubleProperty("rightVelocity", () -> m_prevSpeeds.rightMetersPerSecond, null); 220 } 221}