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.HolonomicDriveController;
010import edu.wpi.first.math.controller.PIDController;
011import edu.wpi.first.math.controller.ProfiledPIDController;
012import edu.wpi.first.math.controller.SimpleMotorFeedforward;
013import edu.wpi.first.math.geometry.Pose2d;
014import edu.wpi.first.math.geometry.Rotation2d;
015import edu.wpi.first.math.kinematics.ChassisSpeeds;
016import edu.wpi.first.math.kinematics.MecanumDriveKinematics;
017import edu.wpi.first.math.kinematics.MecanumDriveMotorVoltages;
018import edu.wpi.first.math.kinematics.MecanumDriveWheelSpeeds;
019import edu.wpi.first.math.trajectory.Trajectory;
020import edu.wpi.first.wpilibj.Timer;
021import java.util.function.Consumer;
022import java.util.function.Supplier;
023
024/**
025 * A command that uses two PID controllers ({@link PIDController}) and a ProfiledPIDController
026 * ({@link ProfiledPIDController}) to follow a trajectory {@link Trajectory} with a mecanum drive.
027 *
028 * <p>The command handles trajectory-following, Velocity PID calculations, and feedforwards
029 * internally. This is intended to be a more-or-less "complete solution" that can be used by teams
030 * without a great deal of controls expertise.
031 *
032 * <p>Advanced teams seeking more flexibility (for example, those who wish to use the onboard PID
033 * functionality of a "smart" motor controller) may use the secondary constructor that omits the PID
034 * and feedforward functionality, returning only the raw wheel speeds from the PID controllers.
035 *
036 * <p>The robot angle controller does not follow the angle given by the trajectory but rather goes
037 * to the angle given in the final state of the trajectory.
038 *
039 * <p>This class is provided by the NewCommands VendorDep
040 */
041@SuppressWarnings("removal")
042public class MecanumControllerCommand extends Command {
043  private final Timer m_timer = new Timer();
044  private final boolean m_usePID;
045  private final Trajectory m_trajectory;
046  private final Supplier<Pose2d> m_pose;
047  private final SimpleMotorFeedforward m_feedforward;
048  private final MecanumDriveKinematics m_kinematics;
049  private final HolonomicDriveController m_controller;
050  private final Supplier<Rotation2d> m_desiredRotation;
051  private final double m_maxWheelVelocity;
052  private final PIDController m_frontLeftController;
053  private final PIDController m_rearLeftController;
054  private final PIDController m_frontRightController;
055  private final PIDController m_rearRightController;
056  private final Supplier<MecanumDriveWheelSpeeds> m_currentWheelSpeeds;
057  private final MecanumVoltagesConsumer m_outputDriveVoltages;
058  private final Consumer<MecanumDriveWheelSpeeds> m_outputWheelSpeeds;
059  private double m_prevFrontLeftSpeedSetpoint; // m/s
060  private double m_prevRearLeftSpeedSetpoint; // m/s
061  private double m_prevFrontRightSpeedSetpoint; // m/s
062  private double m_prevRearRightSpeedSetpoint; // m/s
063
064  /**
065   * Constructs a new MecanumControllerCommand that when executed will follow the provided
066   * trajectory. PID control and feedforward are handled internally. Outputs are scaled from -12 to
067   * 12 as a voltage output to the motor.
068   *
069   * <p>Note: The controllers will *not* set the outputVolts to zero upon completion of the path
070   * this is left to the user, since it is not appropriate for paths with nonstationary endstates.
071   *
072   * @param trajectory The trajectory to follow.
073   * @param pose A function that supplies the robot pose - use one of the odometry classes to
074   *     provide this.
075   * @param feedforward The feedforward to use for the drivetrain.
076   * @param kinematics The kinematics for the robot drivetrain.
077   * @param xController The Trajectory Tracker PID controller for the robot's x position.
078   * @param yController The Trajectory Tracker PID controller for the robot's y position.
079   * @param thetaController The Trajectory Tracker PID controller for angle for the robot.
080   * @param desiredRotation The angle that the robot should be facing. This is sampled at each time
081   *     step.
082   * @param maxWheelVelocity The maximum velocity of a drivetrain wheel in m/s.
083   * @param frontLeftController The front left wheel velocity PID.
084   * @param rearLeftController The rear left wheel velocity PID.
085   * @param frontRightController The front right wheel velocity PID.
086   * @param rearRightController The rear right wheel velocity PID.
087   * @param currentWheelSpeeds A MecanumDriveWheelSpeeds object containing the current wheel speeds.
088   * @param outputDriveVoltages A MecanumVoltagesConsumer that consumes voltages of mecanum motors.
089   * @param requirements The subsystems to require.
090   */
091  @SuppressWarnings("this-escape")
092  public MecanumControllerCommand(
093      Trajectory trajectory,
094      Supplier<Pose2d> pose,
095      SimpleMotorFeedforward feedforward,
096      MecanumDriveKinematics kinematics,
097      PIDController xController,
098      PIDController yController,
099      ProfiledPIDController thetaController,
100      Supplier<Rotation2d> desiredRotation,
101      double maxWheelVelocity,
102      PIDController frontLeftController,
103      PIDController rearLeftController,
104      PIDController frontRightController,
105      PIDController rearRightController,
106      Supplier<MecanumDriveWheelSpeeds> currentWheelSpeeds,
107      MecanumVoltagesConsumer outputDriveVoltages,
108      Subsystem... requirements) {
109    m_trajectory = requireNonNullParam(trajectory, "trajectory", "MecanumControllerCommand");
110    m_pose = requireNonNullParam(pose, "pose", "MecanumControllerCommand");
111    m_feedforward = requireNonNullParam(feedforward, "feedforward", "MecanumControllerCommand");
112    m_kinematics = requireNonNullParam(kinematics, "kinematics", "MecanumControllerCommand");
113
114    m_controller =
115        new HolonomicDriveController(
116            requireNonNullParam(xController, "xController", "MecanumControllerCommand"),
117            requireNonNullParam(yController, "yController", "MecanumControllerCommand"),
118            requireNonNullParam(thetaController, "thetaController", "MecanumControllerCommand"));
119
120    m_desiredRotation =
121        requireNonNullParam(desiredRotation, "desiredRotation", "MecanumControllerCommand");
122
123    m_maxWheelVelocity = maxWheelVelocity;
124
125    m_frontLeftController =
126        requireNonNullParam(frontLeftController, "frontLeftController", "MecanumControllerCommand");
127    m_rearLeftController =
128        requireNonNullParam(rearLeftController, "rearLeftController", "MecanumControllerCommand");
129    m_frontRightController =
130        requireNonNullParam(
131            frontRightController, "frontRightController", "MecanumControllerCommand");
132    m_rearRightController =
133        requireNonNullParam(rearRightController, "rearRightController", "MecanumControllerCommand");
134
135    m_currentWheelSpeeds =
136        requireNonNullParam(currentWheelSpeeds, "currentWheelSpeeds", "MecanumControllerCommand");
137
138    m_outputDriveVoltages =
139        requireNonNullParam(outputDriveVoltages, "outputDriveVoltages", "MecanumControllerCommand");
140
141    m_outputWheelSpeeds = null;
142
143    m_usePID = true;
144
145    addRequirements(requirements);
146  }
147
148  /**
149   * Constructs a new MecanumControllerCommand that when executed will follow the provided
150   * trajectory. PID control and feedforward are handled internally. Outputs are scaled from -12 to
151   * 12 as a voltage output to the motor.
152   *
153   * <p>Note: The controllers will *not* set the outputVolts to zero upon completion of the path
154   * this is left to the user, since it is not appropriate for paths with nonstationary endstates.
155   *
156   * @param trajectory The trajectory to follow.
157   * @param pose A function that supplies the robot pose - use one of the odometry classes to
158   *     provide this.
159   * @param feedforward The feedforward to use for the drivetrain.
160   * @param kinematics The kinematics for the robot drivetrain.
161   * @param xController The Trajectory Tracker PID controller for the robot's x position.
162   * @param yController The Trajectory Tracker PID controller for the robot's y position.
163   * @param thetaController The Trajectory Tracker PID controller for angle for the robot.
164   * @param desiredRotation The angle that the robot should be facing. This is sampled at each time
165   *     step.
166   * @param maxWheelVelocityMetersPerSecond The maximum velocity of a drivetrain wheel.
167   * @param frontLeftController The front left wheel velocity PID.
168   * @param rearLeftController The rear left wheel velocity PID.
169   * @param frontRightController The front right wheel velocity PID.
170   * @param rearRightController The rear right wheel velocity PID.
171   * @param currentWheelSpeeds A MecanumDriveWheelSpeeds object containing the current wheel speeds.
172   * @param outputDriveVoltages A MecanumDriveMotorVoltages object containing the output motor
173   *     voltages.
174   * @param requirements The subsystems to require.
175   * @deprecated Use {@link MecanumVoltagesConsumer} instead of {@code
176   *     Consumer<MecanumDriveMotorVoltages}.
177   */
178  @Deprecated(since = "2025", forRemoval = true)
179  public MecanumControllerCommand(
180      Trajectory trajectory,
181      Supplier<Pose2d> pose,
182      SimpleMotorFeedforward feedforward,
183      MecanumDriveKinematics kinematics,
184      PIDController xController,
185      PIDController yController,
186      ProfiledPIDController thetaController,
187      Supplier<Rotation2d> desiredRotation,
188      double maxWheelVelocityMetersPerSecond,
189      PIDController frontLeftController,
190      PIDController rearLeftController,
191      PIDController frontRightController,
192      PIDController rearRightController,
193      Supplier<MecanumDriveWheelSpeeds> currentWheelSpeeds,
194      Consumer<MecanumDriveMotorVoltages> outputDriveVoltages,
195      Subsystem... requirements) {
196    this(
197        trajectory,
198        pose,
199        feedforward,
200        kinematics,
201        xController,
202        yController,
203        thetaController,
204        desiredRotation,
205        maxWheelVelocityMetersPerSecond,
206        frontLeftController,
207        rearLeftController,
208        frontRightController,
209        rearRightController,
210        currentWheelSpeeds,
211        (frontLeft, frontRight, rearLeft, rearRight) ->
212            outputDriveVoltages.accept(
213                new MecanumDriveMotorVoltages(frontLeft, frontRight, rearLeft, rearRight)),
214        requirements);
215  }
216
217  /**
218   * Constructs a new MecanumControllerCommand that when executed will follow the provided
219   * trajectory. PID control and feedforward are handled internally. Outputs are scaled from -12 to
220   * 12 as a voltage output to the motor.
221   *
222   * <p>Note: The controllers will *not* set the outputVolts to zero upon completion of the path
223   * this is left to the user, since it is not appropriate for paths with nonstationary endstates.
224   *
225   * <p>Note 2: The final rotation of the robot will be set to the rotation of the final pose in the
226   * trajectory. The robot will not follow the rotations from the poses at each timestep. If
227   * alternate rotation behavior is desired, the other constructor with a supplier for rotation
228   * should be used.
229   *
230   * @param trajectory The trajectory to follow.
231   * @param pose A function that supplies the robot pose - use one of the odometry classes to
232   *     provide this.
233   * @param feedforward The feedforward to use for the drivetrain.
234   * @param kinematics The kinematics for the robot drivetrain.
235   * @param xController The Trajectory Tracker PID controller for the robot's x position.
236   * @param yController The Trajectory Tracker PID controller for the robot's y position.
237   * @param thetaController The Trajectory Tracker PID controller for angle for the robot.
238   * @param maxWheelVelocityMetersPerSecond The maximum velocity of a drivetrain wheel.
239   * @param frontLeftController The front left wheel velocity PID.
240   * @param rearLeftController The rear left wheel velocity PID.
241   * @param frontRightController The front right wheel velocity PID.
242   * @param rearRightController The rear right wheel velocity PID.
243   * @param currentWheelSpeeds A MecanumDriveWheelSpeeds object containing the current wheel speeds.
244   * @param outputDriveVoltages A MecanumVoltagesConsumer that consumes voltages of mecanum motors.
245   * @param requirements The subsystems to require.
246   */
247  public MecanumControllerCommand(
248      Trajectory trajectory,
249      Supplier<Pose2d> pose,
250      SimpleMotorFeedforward feedforward,
251      MecanumDriveKinematics kinematics,
252      PIDController xController,
253      PIDController yController,
254      ProfiledPIDController thetaController,
255      double maxWheelVelocityMetersPerSecond,
256      PIDController frontLeftController,
257      PIDController rearLeftController,
258      PIDController frontRightController,
259      PIDController rearRightController,
260      Supplier<MecanumDriveWheelSpeeds> currentWheelSpeeds,
261      MecanumVoltagesConsumer outputDriveVoltages,
262      Subsystem... requirements) {
263    this(
264        trajectory,
265        pose,
266        feedforward,
267        kinematics,
268        xController,
269        yController,
270        thetaController,
271        () -> trajectory.getStates().get(trajectory.getStates().size() - 1).pose.getRotation(),
272        maxWheelVelocityMetersPerSecond,
273        frontLeftController,
274        rearLeftController,
275        frontRightController,
276        rearRightController,
277        currentWheelSpeeds,
278        outputDriveVoltages,
279        requirements);
280  }
281
282  /**
283   * Constructs a new MecanumControllerCommand that when executed will follow the provided
284   * trajectory. PID control and feedforward are handled internally. Outputs are scaled from -12 to
285   * 12 as a voltage output to the motor.
286   *
287   * <p>Note: The controllers will *not* set the outputVolts to zero upon completion of the path
288   * this is left to the user, since it is not appropriate for paths with nonstationary endstates.
289   *
290   * <p>Note 2: The final rotation of the robot will be set to the rotation of the final pose in the
291   * trajectory. The robot will not follow the rotations from the poses at each timestep. If
292   * alternate rotation behavior is desired, the other constructor with a supplier for rotation
293   * should be used.
294   *
295   * @param trajectory The trajectory to follow.
296   * @param pose A function that supplies the robot pose - use one of the odometry classes to
297   *     provide this.
298   * @param feedforward The feedforward to use for the drivetrain.
299   * @param kinematics The kinematics for the robot drivetrain.
300   * @param xController The Trajectory Tracker PID controller for the robot's x position.
301   * @param yController The Trajectory Tracker PID controller for the robot's y position.
302   * @param thetaController The Trajectory Tracker PID controller for angle for the robot.
303   * @param maxWheelVelocity The maximum velocity of a drivetrain wheel in m/s.
304   * @param frontLeftController The front left wheel velocity PID.
305   * @param rearLeftController The rear left wheel velocity PID.
306   * @param frontRightController The front right wheel velocity PID.
307   * @param rearRightController The rear right wheel velocity PID.
308   * @param currentWheelSpeeds A MecanumDriveWheelSpeeds object containing the current wheel speeds.
309   * @param outputDriveVoltages A MecanumDriveMotorVoltages object containing the output motor
310   *     voltages.
311   * @param requirements The subsystems to require.
312   * @deprecated Use {@link MecanumVoltagesConsumer} instead of {@code
313   *     Consumer<MecanumDriveMotorVoltages>}.
314   */
315  @Deprecated(since = "2025", forRemoval = true)
316  public MecanumControllerCommand(
317      Trajectory trajectory,
318      Supplier<Pose2d> pose,
319      SimpleMotorFeedforward feedforward,
320      MecanumDriveKinematics kinematics,
321      PIDController xController,
322      PIDController yController,
323      ProfiledPIDController thetaController,
324      double maxWheelVelocity,
325      PIDController frontLeftController,
326      PIDController rearLeftController,
327      PIDController frontRightController,
328      PIDController rearRightController,
329      Supplier<MecanumDriveWheelSpeeds> currentWheelSpeeds,
330      Consumer<MecanumDriveMotorVoltages> outputDriveVoltages,
331      Subsystem... requirements) {
332    this(
333        trajectory,
334        pose,
335        feedforward,
336        kinematics,
337        xController,
338        yController,
339        thetaController,
340        maxWheelVelocity,
341        frontLeftController,
342        rearLeftController,
343        frontRightController,
344        rearRightController,
345        currentWheelSpeeds,
346        (frontLeft, frontRight, rearLeft, rearRight) ->
347            outputDriveVoltages.accept(
348                new MecanumDriveMotorVoltages(frontLeft, frontRight, rearLeft, rearRight)),
349        requirements);
350  }
351
352  /**
353   * Constructs a new MecanumControllerCommand that when executed will follow the provided
354   * trajectory. The user should implement a velocity PID on the desired output wheel velocities.
355   *
356   * <p>Note: The controllers will *not* set the outputVolts to zero upon completion of the path -
357   * this is left to the user, since it is not appropriate for paths with nonstationary end-states.
358   *
359   * @param trajectory The trajectory to follow.
360   * @param pose A function that supplies the robot pose - use one of the odometry classes to
361   *     provide this.
362   * @param kinematics The kinematics for the robot drivetrain.
363   * @param xController The Trajectory Tracker PID controller for the robot's x position.
364   * @param yController The Trajectory Tracker PID controller for the robot's y position.
365   * @param thetaController The Trajectory Tracker PID controller for angle for the robot.
366   * @param desiredRotation The angle that the robot should be facing. This is sampled at each time
367   *     step.
368   * @param maxWheelVelocity The maximum velocity of a drivetrain wheel in m/s.
369   * @param outputWheelSpeeds A MecanumDriveWheelSpeeds object containing the output wheel speeds.
370   * @param requirements The subsystems to require.
371   */
372  @SuppressWarnings("this-escape")
373  public MecanumControllerCommand(
374      Trajectory trajectory,
375      Supplier<Pose2d> pose,
376      MecanumDriveKinematics kinematics,
377      PIDController xController,
378      PIDController yController,
379      ProfiledPIDController thetaController,
380      Supplier<Rotation2d> desiredRotation,
381      double maxWheelVelocity,
382      Consumer<MecanumDriveWheelSpeeds> outputWheelSpeeds,
383      Subsystem... requirements) {
384    m_trajectory = requireNonNullParam(trajectory, "trajectory", "MecanumControllerCommand");
385    m_pose = requireNonNullParam(pose, "pose", "MecanumControllerCommand");
386    m_feedforward = new SimpleMotorFeedforward(0, 0, 0);
387    m_kinematics = requireNonNullParam(kinematics, "kinematics", "MecanumControllerCommand");
388
389    m_controller =
390        new HolonomicDriveController(
391            requireNonNullParam(xController, "xController", "MecanumControllerCommand"),
392            requireNonNullParam(yController, "yController", "MecanumControllerCommand"),
393            requireNonNullParam(thetaController, "thetaController", "MecanumControllerCommand"));
394
395    m_desiredRotation =
396        requireNonNullParam(desiredRotation, "desiredRotation", "MecanumControllerCommand");
397
398    m_maxWheelVelocity = maxWheelVelocity;
399
400    m_frontLeftController = null;
401    m_rearLeftController = null;
402    m_frontRightController = null;
403    m_rearRightController = null;
404
405    m_currentWheelSpeeds = null;
406
407    m_outputWheelSpeeds =
408        requireNonNullParam(outputWheelSpeeds, "outputWheelSpeeds", "MecanumControllerCommand");
409
410    m_outputDriveVoltages = null;
411
412    m_usePID = false;
413
414    addRequirements(requirements);
415  }
416
417  /**
418   * Constructs a new MecanumControllerCommand that when executed will follow the provided
419   * trajectory. The user should implement a velocity PID on the desired output wheel velocities.
420   *
421   * <p>Note: The controllers will *not* set the outputVolts to zero upon completion of the path -
422   * this is left to the user, since it is not appropriate for paths with nonstationary end-states.
423   *
424   * <p>Note 2: The final rotation of the robot will be set to the rotation of the final pose in the
425   * trajectory. The robot will not follow the rotations from the poses at each timestep. If
426   * alternate rotation behavior is desired, the other constructor with a supplier for rotation
427   * should be used.
428   *
429   * @param trajectory The trajectory to follow.
430   * @param pose A function that supplies the robot pose - use one of the odometry classes to
431   *     provide this.
432   * @param kinematics The kinematics for the robot drivetrain.
433   * @param xController The Trajectory Tracker PID controller for the robot's x position.
434   * @param yController The Trajectory Tracker PID controller for the robot's y position.
435   * @param thetaController The Trajectory Tracker PID controller for angle for the robot.
436   * @param maxWheelVelocity The maximum velocity of a drivetrain wheel.
437   * @param outputWheelSpeeds A MecanumDriveWheelSpeeds object containing the output wheel speeds.
438   * @param requirements The subsystems to require.
439   */
440  public MecanumControllerCommand(
441      Trajectory trajectory,
442      Supplier<Pose2d> pose,
443      MecanumDriveKinematics kinematics,
444      PIDController xController,
445      PIDController yController,
446      ProfiledPIDController thetaController,
447      double maxWheelVelocity,
448      Consumer<MecanumDriveWheelSpeeds> outputWheelSpeeds,
449      Subsystem... requirements) {
450    this(
451        trajectory,
452        pose,
453        kinematics,
454        xController,
455        yController,
456        thetaController,
457        () -> trajectory.getStates().get(trajectory.getStates().size() - 1).pose.getRotation(),
458        maxWheelVelocity,
459        outputWheelSpeeds,
460        requirements);
461  }
462
463  @Override
464  public void initialize() {
465    var initialState = m_trajectory.sample(0);
466
467    var initialXVelocity = initialState.velocity * initialState.pose.getRotation().getCos();
468    var initialYVelocity = initialState.velocity * initialState.pose.getRotation().getSin();
469
470    MecanumDriveWheelSpeeds prevSpeeds =
471        m_kinematics.toWheelSpeeds(new ChassisSpeeds(initialXVelocity, initialYVelocity, 0.0));
472
473    m_prevFrontLeftSpeedSetpoint = prevSpeeds.frontLeft;
474    m_prevRearLeftSpeedSetpoint = prevSpeeds.rearLeft;
475    m_prevFrontRightSpeedSetpoint = prevSpeeds.frontRight;
476    m_prevRearRightSpeedSetpoint = prevSpeeds.rearRight;
477
478    m_timer.restart();
479  }
480
481  @Override
482  public void execute() {
483    double curTime = m_timer.get();
484
485    var desiredState = m_trajectory.sample(curTime);
486
487    var targetChassisSpeeds =
488        m_controller.calculate(m_pose.get(), desiredState, m_desiredRotation.get());
489    var targetWheelSpeeds = m_kinematics.toWheelSpeeds(targetChassisSpeeds);
490
491    targetWheelSpeeds = targetWheelSpeeds.desaturate(m_maxWheelVelocity);
492
493    double frontLeftSpeedSetpoint = targetWheelSpeeds.frontLeft;
494    double rearLeftSpeedSetpoint = targetWheelSpeeds.rearLeft;
495    double frontRightSpeedSetpoint = targetWheelSpeeds.frontRight;
496    double rearRightSpeedSetpoint = targetWheelSpeeds.rearRight;
497
498    double frontLeftOutput;
499    double rearLeftOutput;
500    double frontRightOutput;
501    double rearRightOutput;
502
503    if (m_usePID) {
504      final double frontLeftFeedforward =
505          m_feedforward.calculate(m_prevFrontLeftSpeedSetpoint, frontLeftSpeedSetpoint);
506
507      final double rearLeftFeedforward =
508          m_feedforward.calculate(m_prevRearLeftSpeedSetpoint, rearLeftSpeedSetpoint);
509
510      final double frontRightFeedforward =
511          m_feedforward.calculate(m_prevFrontRightSpeedSetpoint, frontRightSpeedSetpoint);
512
513      final double rearRightFeedforward =
514          m_feedforward.calculate(m_prevRearRightSpeedSetpoint, rearRightSpeedSetpoint);
515
516      frontLeftOutput =
517          frontLeftFeedforward
518              + m_frontLeftController.calculate(
519                  m_currentWheelSpeeds.get().frontLeft, frontLeftSpeedSetpoint);
520
521      rearLeftOutput =
522          rearLeftFeedforward
523              + m_rearLeftController.calculate(
524                  m_currentWheelSpeeds.get().rearLeft, rearLeftSpeedSetpoint);
525
526      frontRightOutput =
527          frontRightFeedforward
528              + m_frontRightController.calculate(
529                  m_currentWheelSpeeds.get().frontRight, frontRightSpeedSetpoint);
530
531      rearRightOutput =
532          rearRightFeedforward
533              + m_rearRightController.calculate(
534                  m_currentWheelSpeeds.get().rearRight, rearRightSpeedSetpoint);
535
536      m_outputDriveVoltages.accept(
537          frontLeftOutput, frontRightOutput, rearLeftOutput, rearRightOutput);
538
539    } else {
540      m_outputWheelSpeeds.accept(
541          new MecanumDriveWheelSpeeds(
542              frontLeftSpeedSetpoint,
543              frontRightSpeedSetpoint,
544              rearLeftSpeedSetpoint,
545              rearRightSpeedSetpoint));
546    }
547  }
548
549  @Override
550  public void end(boolean interrupted) {
551    m_timer.stop();
552  }
553
554  @Override
555  public boolean isFinished() {
556    return m_timer.hasElapsed(m_trajectory.getTotalTime());
557  }
558
559  /** A consumer to represent an operation on the voltages of a mecanum drive. */
560  @FunctionalInterface
561  public interface MecanumVoltagesConsumer {
562    /**
563     * Accepts the voltages to perform some operation with them.
564     *
565     * @param frontLeftVoltage The voltage of the front left motor.
566     * @param frontRightVoltage The voltage of the front right motor.
567     * @param rearLeftVoltage The voltage of the rear left motor.
568     * @param rearRightVoltage The voltage of the rear left motor.
569     */
570    void accept(
571        double frontLeftVoltage,
572        double frontRightVoltage,
573        double rearLeftVoltage,
574        double rearRightVoltage);
575  }
576}