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