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