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}