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 org.wpilib.drive;
006
007import static org.wpilib.util.ErrorMessages.requireNonNullParam;
008
009import java.util.function.DoubleConsumer;
010import org.wpilib.hardware.hal.HAL;
011import org.wpilib.hardware.motor.MotorController;
012import org.wpilib.math.geometry.Rotation2d;
013import org.wpilib.math.geometry.Translation2d;
014import org.wpilib.math.util.MathUtil;
015import org.wpilib.util.sendable.Sendable;
016import org.wpilib.util.sendable.SendableBuilder;
017import org.wpilib.util.sendable.SendableRegistry;
018
019/**
020 * A class for driving Mecanum drive platforms.
021 *
022 * <p>Mecanum drives are rectangular with one wheel on each corner. Each wheel has rollers toed in
023 * 45 degrees toward the front or back. When looking at the wheels from the top, the roller axles
024 * should form an X across the robot. Each drive() function provides different inverse kinematic
025 * relations for a Mecanum drive robot.
026 *
027 * <p>Drive base diagram:
028 *
029 * <pre>
030 * \\_______/
031 * \\ |   | /
032 *   |   |
033 * /_|___|_\\
034 * /       \\
035 * </pre>
036 *
037 * <p>Each drive() function provides different inverse kinematic relations for a Mecanum drive
038 * robot.
039 *
040 * <p>This library uses the NWU axes convention (North-West-Up as external reference in the world
041 * frame). The positive X axis points ahead, the positive Y axis points to the left, and the
042 * positive Z axis points up. Rotations follow the right-hand rule, so counterclockwise rotation
043 * around the Z axis is positive.
044 *
045 * <p>Inputs smaller then {@value org.wpilib.drive.RobotDriveBase#kDefaultDeadband} will be set to
046 * 0, and larger values will be scaled so that the full range is still used. This deadband value can
047 * be changed with {@link #setDeadband}.
048 *
049 * <p>{@link org.wpilib.hardware.motor.MotorSafety} is enabled by default. The driveCartesian or
050 * drivePolar methods should be called periodically to avoid Motor Safety timeouts.
051 */
052public class MecanumDrive extends RobotDriveBase implements Sendable, AutoCloseable {
053  private static int instances;
054
055  private final DoubleConsumer m_frontLeftMotor;
056  private final DoubleConsumer m_rearLeftMotor;
057  private final DoubleConsumer m_frontRightMotor;
058  private final DoubleConsumer m_rearRightMotor;
059
060  // Used for Sendable property getters
061  private double m_frontLeftOutput;
062  private double m_rearLeftOutput;
063  private double m_frontRightOutput;
064  private double m_rearRightOutput;
065
066  private boolean m_reported;
067
068  /**
069   * Wheel velocities for a mecanum drive.
070   *
071   * <p>Uses normalized voltage [-1.0..1.0].
072   */
073  @SuppressWarnings("MemberName")
074  public static class WheelVelocities {
075    /** Front-left wheel velocity. */
076    public double frontLeft;
077
078    /** Front-right wheel velocity. */
079    public double frontRight;
080
081    /** Rear-left wheel velocity. */
082    public double rearLeft;
083
084    /** Rear-right wheel velocity. */
085    public double rearRight;
086
087    /** Constructs a WheelVelocities with zeroes for all four velocities. */
088    public WheelVelocities() {}
089
090    /**
091     * Constructs a WheelVelocities.
092     *
093     * @param frontLeft The front left velocity [-1.0..1.0].
094     * @param frontRight The front right velocity [-1.0..1.0].
095     * @param rearLeft The rear left velocity [-1.0..1.0].
096     * @param rearRight The rear right velocity [-1.0..1.0].
097     */
098    public WheelVelocities(double frontLeft, double frontRight, double rearLeft, double rearRight) {
099      this.frontLeft = frontLeft;
100      this.frontRight = frontRight;
101      this.rearLeft = rearLeft;
102      this.rearRight = rearRight;
103    }
104  }
105
106  /**
107   * Construct a MecanumDrive.
108   *
109   * <p>If a motor needs to be inverted, do so before passing it in.
110   *
111   * @param frontLeftMotor The motor on the front-left corner.
112   * @param rearLeftMotor The motor on the rear-left corner.
113   * @param frontRightMotor The motor on the front-right corner.
114   * @param rearRightMotor The motor on the rear-right corner.
115   */
116  @SuppressWarnings({"removal", "this-escape"})
117  public MecanumDrive(
118      MotorController frontLeftMotor,
119      MotorController rearLeftMotor,
120      MotorController frontRightMotor,
121      MotorController rearRightMotor) {
122    this(
123        (double output) -> frontLeftMotor.setDutyCycle(output),
124        (double output) -> rearLeftMotor.setDutyCycle(output),
125        (double output) -> frontRightMotor.setDutyCycle(output),
126        (double output) -> rearRightMotor.setDutyCycle(output));
127    SendableRegistry.addChild(this, frontLeftMotor);
128    SendableRegistry.addChild(this, rearLeftMotor);
129    SendableRegistry.addChild(this, frontRightMotor);
130    SendableRegistry.addChild(this, rearRightMotor);
131  }
132
133  /**
134   * Construct a MecanumDrive.
135   *
136   * <p>If a motor needs to be inverted, do so before passing it in.
137   *
138   * @param frontLeftMotor The setter for the motor on the front-left corner.
139   * @param rearLeftMotor The setter for the motor on the rear-left corner.
140   * @param frontRightMotor The setter for the motor on the front-right corner.
141   * @param rearRightMotor The setter for the motor on the rear-right corner.
142   */
143  @SuppressWarnings("this-escape")
144  public MecanumDrive(
145      DoubleConsumer frontLeftMotor,
146      DoubleConsumer rearLeftMotor,
147      DoubleConsumer frontRightMotor,
148      DoubleConsumer rearRightMotor) {
149    requireNonNullParam(frontLeftMotor, "frontLeftMotor", "MecanumDrive");
150    requireNonNullParam(rearLeftMotor, "rearLeftMotor", "MecanumDrive");
151    requireNonNullParam(frontRightMotor, "frontRightMotor", "MecanumDrive");
152    requireNonNullParam(rearRightMotor, "rearRightMotor", "MecanumDrive");
153
154    m_frontLeftMotor = frontLeftMotor;
155    m_rearLeftMotor = rearLeftMotor;
156    m_frontRightMotor = frontRightMotor;
157    m_rearRightMotor = rearRightMotor;
158    instances++;
159    SendableRegistry.add(this, "MecanumDrive", instances);
160  }
161
162  @Override
163  public void close() {
164    SendableRegistry.remove(this);
165  }
166
167  /**
168   * Drive method for Mecanum platform.
169   *
170   * <p>Angles are measured counterclockwise from the positive X axis. The robot's velocity is
171   * independent of its angle or rotation rate.
172   *
173   * @param xVelocity The robot's velocity along the X axis [-1.0..1.0]. Forward is positive.
174   * @param yVelocity The robot's velocity along the Y axis [-1.0..1.0]. Left is positive.
175   * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Counterclockwise is
176   *     positive.
177   */
178  public void driveCartesian(double xVelocity, double yVelocity, double zRotation) {
179    driveCartesian(xVelocity, yVelocity, zRotation, Rotation2d.kZero);
180  }
181
182  /**
183   * Drive method for Mecanum platform.
184   *
185   * <p>Angles are measured counterclockwise from the positive X axis. The robot's velocity is
186   * independent of its angle or rotation rate.
187   *
188   * @param xVelocity The robot's velocity along the X axis [-1.0..1.0]. Forward is positive.
189   * @param yVelocity The robot's velocity along the Y axis [-1.0..1.0]. Left is positive.
190   * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Counterclockwise is
191   *     positive.
192   * @param gyroAngle The gyro heading around the Z axis. Use this to implement field-oriented
193   *     controls.
194   */
195  public void driveCartesian(
196      double xVelocity, double yVelocity, double zRotation, Rotation2d gyroAngle) {
197    if (!m_reported) {
198      HAL.reportUsage("RobotDrive", "MecanumCartesian");
199      m_reported = true;
200    }
201
202    xVelocity = MathUtil.applyDeadband(xVelocity, m_deadband);
203    yVelocity = MathUtil.applyDeadband(yVelocity, m_deadband);
204
205    var velocities = driveCartesianIK(xVelocity, yVelocity, zRotation, gyroAngle);
206
207    m_frontLeftOutput = velocities.frontLeft * m_maxOutput;
208    m_rearLeftOutput = velocities.rearLeft * m_maxOutput;
209    m_frontRightOutput = velocities.frontRight * m_maxOutput;
210    m_rearRightOutput = velocities.rearRight * m_maxOutput;
211
212    m_frontLeftMotor.accept(m_frontLeftOutput);
213    m_frontRightMotor.accept(m_frontRightOutput);
214    m_rearLeftMotor.accept(m_rearLeftOutput);
215    m_rearRightMotor.accept(m_rearRightOutput);
216
217    feed();
218  }
219
220  /**
221   * Drive method for Mecanum platform.
222   *
223   * <p>Angles are measured counterclockwise from straight ahead. The velocity at which the robot
224   * drives (translation) is independent of its angle or rotation rate.
225   *
226   * @param magnitude The robot's velocity at a given angle [-1.0..1.0]. Forward is positive.
227   * @param angle The gyro heading around the Z axis at which the robot drives.
228   * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Counterclockwise is
229   *     positive.
230   */
231  public void drivePolar(double magnitude, Rotation2d angle, double zRotation) {
232    if (!m_reported) {
233      HAL.reportUsage("RobotDrive", "MecanumPolar");
234      m_reported = true;
235    }
236
237    driveCartesian(
238        magnitude * angle.getCos(), magnitude * angle.getSin(), zRotation, Rotation2d.kZero);
239  }
240
241  /**
242   * Cartesian inverse kinematics for Mecanum platform.
243   *
244   * <p>Angles are measured counterclockwise from the positive X axis. The robot's velocity is
245   * independent of its angle or rotation rate.
246   *
247   * @param xVelocity The robot's velocity along the X axis [-1.0..1.0]. Forward is positive.
248   * @param yVelocity The robot's velocity along the Y axis [-1.0..1.0]. Left is positive.
249   * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Counterclockwise is
250   *     positive.
251   * @return Wheel velocities [-1.0..1.0].
252   */
253  public static WheelVelocities driveCartesianIK(
254      double xVelocity, double yVelocity, double zRotation) {
255    return driveCartesianIK(xVelocity, yVelocity, zRotation, Rotation2d.kZero);
256  }
257
258  /**
259   * Cartesian inverse kinematics for Mecanum platform.
260   *
261   * <p>Angles are measured clockwise from the positive X axis. The robot's velocity is independent
262   * of its angle or rotation rate.
263   *
264   * @param xVelocity The robot's velocity along the X axis [-1.0..1.0]. Forward is positive.
265   * @param yVelocity The robot's velocity along the Y axis [-1.0..1.0]. Left is positive.
266   * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Counterclockwise is
267   *     positive.
268   * @param gyroAngle The gyro heading around the Z axis. Use this to implement field-oriented
269   *     controls.
270   * @return Wheel velocities [-1.0..1.0].
271   */
272  public static WheelVelocities driveCartesianIK(
273      double xVelocity, double yVelocity, double zRotation, Rotation2d gyroAngle) {
274    xVelocity = Math.clamp(xVelocity, -1.0, 1.0);
275    yVelocity = Math.clamp(yVelocity, -1.0, 1.0);
276
277    // Compensate for gyro angle.
278    var input = new Translation2d(xVelocity, yVelocity).rotateBy(gyroAngle.unaryMinus());
279
280    double[] wheelVelocities = new double[4];
281    wheelVelocities[MotorType.kFrontLeft.value] = input.getX() + input.getY() + zRotation;
282    wheelVelocities[MotorType.kFrontRight.value] = input.getX() - input.getY() - zRotation;
283    wheelVelocities[MotorType.kRearLeft.value] = input.getX() - input.getY() + zRotation;
284    wheelVelocities[MotorType.kRearRight.value] = input.getX() + input.getY() - zRotation;
285
286    normalize(wheelVelocities);
287
288    return new WheelVelocities(
289        wheelVelocities[MotorType.kFrontLeft.value],
290        wheelVelocities[MotorType.kFrontRight.value],
291        wheelVelocities[MotorType.kRearLeft.value],
292        wheelVelocities[MotorType.kRearRight.value]);
293  }
294
295  @Override
296  public void stopMotor() {
297    m_frontLeftOutput = 0.0;
298    m_frontRightOutput = 0.0;
299    m_rearLeftOutput = 0.0;
300    m_rearRightOutput = 0.0;
301
302    m_frontLeftMotor.accept(0.0);
303    m_frontRightMotor.accept(0.0);
304    m_rearLeftMotor.accept(0.0);
305    m_rearRightMotor.accept(0.0);
306
307    feed();
308  }
309
310  @Override
311  public String getDescription() {
312    return "MecanumDrive";
313  }
314
315  @Override
316  public void initSendable(SendableBuilder builder) {
317    builder.setSmartDashboardType("MecanumDrive");
318    builder.setActuator(true);
319    builder.addDoubleProperty(
320        "Front Left Motor Velocity", () -> m_frontLeftOutput, m_frontLeftMotor);
321    builder.addDoubleProperty(
322        "Front Right Motor Velocity", () -> m_frontRightOutput, m_frontRightMotor);
323    builder.addDoubleProperty("Rear Left Motor Velocity", () -> m_rearLeftOutput, m_rearLeftMotor);
324    builder.addDoubleProperty(
325        "Rear Right Motor Velocity", () -> m_rearRightOutput, m_rearRightMotor);
326  }
327}