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#DEFAULT_DEADBAND} 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 public static class WheelVelocities { 074 /** Front-left wheel velocity. */ 075 public double frontLeft; 076 077 /** Front-right wheel velocity. */ 078 public double frontRight; 079 080 /** Rear-left wheel velocity. */ 081 public double rearLeft; 082 083 /** Rear-right wheel velocity. */ 084 public double rearRight; 085 086 /** Constructs a WheelVelocities with zeroes for all four velocities. */ 087 public WheelVelocities() {} 088 089 /** 090 * Constructs a WheelVelocities. 091 * 092 * @param frontLeft The front left velocity [-1.0..1.0]. 093 * @param frontRight The front right velocity [-1.0..1.0]. 094 * @param rearLeft The rear left velocity [-1.0..1.0]. 095 * @param rearRight The rear right velocity [-1.0..1.0]. 096 */ 097 public WheelVelocities(double frontLeft, double frontRight, double rearLeft, double rearRight) { 098 this.frontLeft = frontLeft; 099 this.frontRight = frontRight; 100 this.rearLeft = rearLeft; 101 this.rearRight = rearRight; 102 } 103 } 104 105 /** 106 * Construct a MecanumDrive. 107 * 108 * <p>If a motor needs to be inverted, do so before passing it in. 109 * 110 * @param frontLeftMotor The motor on the front-left corner. 111 * @param rearLeftMotor The motor on the rear-left corner. 112 * @param frontRightMotor The motor on the front-right corner. 113 * @param rearRightMotor The motor on the rear-right corner. 114 */ 115 @SuppressWarnings({"removal", "this-escape"}) 116 public MecanumDrive( 117 MotorController frontLeftMotor, 118 MotorController rearLeftMotor, 119 MotorController frontRightMotor, 120 MotorController rearRightMotor) { 121 this( 122 (double output) -> frontLeftMotor.setThrottle(output), 123 (double output) -> rearLeftMotor.setThrottle(output), 124 (double output) -> frontRightMotor.setThrottle(output), 125 (double output) -> rearRightMotor.setThrottle(output)); 126 SendableRegistry.addChild(this, frontLeftMotor); 127 SendableRegistry.addChild(this, rearLeftMotor); 128 SendableRegistry.addChild(this, frontRightMotor); 129 SendableRegistry.addChild(this, rearRightMotor); 130 } 131 132 /** 133 * Construct a MecanumDrive. 134 * 135 * <p>If a motor needs to be inverted, do so before passing it in. 136 * 137 * @param frontLeftMotor The setter for the motor on the front-left corner. 138 * @param rearLeftMotor The setter for the motor on the rear-left corner. 139 * @param frontRightMotor The setter for the motor on the front-right corner. 140 * @param rearRightMotor The setter for the motor on the rear-right corner. 141 */ 142 @SuppressWarnings("this-escape") 143 public MecanumDrive( 144 DoubleConsumer frontLeftMotor, 145 DoubleConsumer rearLeftMotor, 146 DoubleConsumer frontRightMotor, 147 DoubleConsumer rearRightMotor) { 148 requireNonNullParam(frontLeftMotor, "frontLeftMotor", "MecanumDrive"); 149 requireNonNullParam(rearLeftMotor, "rearLeftMotor", "MecanumDrive"); 150 requireNonNullParam(frontRightMotor, "frontRightMotor", "MecanumDrive"); 151 requireNonNullParam(rearRightMotor, "rearRightMotor", "MecanumDrive"); 152 153 m_frontLeftMotor = frontLeftMotor; 154 m_rearLeftMotor = rearLeftMotor; 155 m_frontRightMotor = frontRightMotor; 156 m_rearRightMotor = rearRightMotor; 157 instances++; 158 SendableRegistry.add(this, "MecanumDrive", instances); 159 } 160 161 @Override 162 public void close() { 163 SendableRegistry.remove(this); 164 } 165 166 /** 167 * Drive method for Mecanum platform. 168 * 169 * <p>Angles are measured counterclockwise from the positive X axis. The robot's velocity is 170 * independent of its angle or rotation rate. 171 * 172 * @param xVelocity The robot's velocity along the X axis [-1.0..1.0]. Forward is positive. 173 * @param yVelocity The robot's velocity along the Y axis [-1.0..1.0]. Left is positive. 174 * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Counterclockwise is 175 * positive. 176 */ 177 public void driveCartesian(double xVelocity, double yVelocity, double zRotation) { 178 driveCartesian(xVelocity, yVelocity, zRotation, Rotation2d.kZero); 179 } 180 181 /** 182 * Drive method for Mecanum platform. 183 * 184 * <p>Angles are measured counterclockwise from the positive X axis. The robot's velocity is 185 * independent of its angle or rotation rate. 186 * 187 * @param xVelocity The robot's velocity along the X axis [-1.0..1.0]. Forward is positive. 188 * @param yVelocity The robot's velocity along the Y axis [-1.0..1.0]. Left is positive. 189 * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Counterclockwise is 190 * positive. 191 * @param gyroAngle The gyro heading around the Z axis. Use this to implement field-oriented 192 * controls. 193 */ 194 public void driveCartesian( 195 double xVelocity, double yVelocity, double zRotation, Rotation2d gyroAngle) { 196 if (!m_reported) { 197 HAL.reportUsage("RobotDrive", "MecanumCartesian"); 198 m_reported = true; 199 } 200 201 xVelocity = MathUtil.applyDeadband(xVelocity, m_deadband); 202 yVelocity = MathUtil.applyDeadband(yVelocity, m_deadband); 203 204 var velocities = driveCartesianIK(xVelocity, yVelocity, zRotation, gyroAngle); 205 206 m_frontLeftOutput = velocities.frontLeft * m_maxOutput; 207 m_rearLeftOutput = velocities.rearLeft * m_maxOutput; 208 m_frontRightOutput = velocities.frontRight * m_maxOutput; 209 m_rearRightOutput = velocities.rearRight * m_maxOutput; 210 211 m_frontLeftMotor.accept(m_frontLeftOutput); 212 m_frontRightMotor.accept(m_frontRightOutput); 213 m_rearLeftMotor.accept(m_rearLeftOutput); 214 m_rearRightMotor.accept(m_rearRightOutput); 215 216 feed(); 217 } 218 219 /** 220 * Drive method for Mecanum platform. 221 * 222 * <p>Angles are measured counterclockwise from straight ahead. The velocity at which the robot 223 * drives (translation) is independent of its angle or rotation rate. 224 * 225 * @param magnitude The robot's velocity at a given angle [-1.0..1.0]. Forward is positive. 226 * @param angle The gyro heading around the Z axis at which the robot drives. 227 * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Counterclockwise is 228 * positive. 229 */ 230 public void drivePolar(double magnitude, Rotation2d angle, double zRotation) { 231 if (!m_reported) { 232 HAL.reportUsage("RobotDrive", "MecanumPolar"); 233 m_reported = true; 234 } 235 236 driveCartesian( 237 magnitude * angle.getCos(), magnitude * angle.getSin(), zRotation, Rotation2d.kZero); 238 } 239 240 /** 241 * Cartesian inverse kinematics for Mecanum platform. 242 * 243 * <p>Angles are measured counterclockwise from the positive X axis. The robot's velocity is 244 * independent of its angle or rotation rate. 245 * 246 * @param xVelocity The robot's velocity along the X axis [-1.0..1.0]. Forward is positive. 247 * @param yVelocity The robot's velocity along the Y axis [-1.0..1.0]. Left is positive. 248 * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Counterclockwise is 249 * positive. 250 * @return Wheel velocities [-1.0..1.0]. 251 */ 252 public static WheelVelocities driveCartesianIK( 253 double xVelocity, double yVelocity, double zRotation) { 254 return driveCartesianIK(xVelocity, yVelocity, zRotation, Rotation2d.kZero); 255 } 256 257 /** 258 * Cartesian inverse kinematics for Mecanum platform. 259 * 260 * <p>Angles are measured clockwise from the positive X axis. The robot's velocity is independent 261 * of its angle or rotation rate. 262 * 263 * @param xVelocity The robot's velocity along the X axis [-1.0..1.0]. Forward is positive. 264 * @param yVelocity The robot's velocity along the Y axis [-1.0..1.0]. Left is positive. 265 * @param zRotation The robot's rotation rate around the Z axis [-1.0..1.0]. Counterclockwise is 266 * positive. 267 * @param gyroAngle The gyro heading around the Z axis. Use this to implement field-oriented 268 * controls. 269 * @return Wheel velocities [-1.0..1.0]. 270 */ 271 public static WheelVelocities driveCartesianIK( 272 double xVelocity, double yVelocity, double zRotation, Rotation2d gyroAngle) { 273 xVelocity = Math.clamp(xVelocity, -1.0, 1.0); 274 yVelocity = Math.clamp(yVelocity, -1.0, 1.0); 275 276 // Compensate for gyro angle. 277 var input = new Translation2d(xVelocity, yVelocity).rotateBy(gyroAngle.unaryMinus()); 278 279 double[] wheelVelocities = new double[4]; 280 wheelVelocities[MotorType.FRONT_LEFT.value] = input.getX() + input.getY() + zRotation; 281 wheelVelocities[MotorType.FRONT_RIGHT.value] = input.getX() - input.getY() - zRotation; 282 wheelVelocities[MotorType.REAR_LEFT.value] = input.getX() - input.getY() + zRotation; 283 wheelVelocities[MotorType.REAR_RIGHT.value] = input.getX() + input.getY() - zRotation; 284 285 normalize(wheelVelocities); 286 287 return new WheelVelocities( 288 wheelVelocities[MotorType.FRONT_LEFT.value], 289 wheelVelocities[MotorType.FRONT_RIGHT.value], 290 wheelVelocities[MotorType.REAR_LEFT.value], 291 wheelVelocities[MotorType.REAR_RIGHT.value]); 292 } 293 294 @Override 295 public void stopMotor() { 296 m_frontLeftOutput = 0.0; 297 m_frontRightOutput = 0.0; 298 m_rearLeftOutput = 0.0; 299 m_rearRightOutput = 0.0; 300 301 m_frontLeftMotor.accept(0.0); 302 m_frontRightMotor.accept(0.0); 303 m_rearLeftMotor.accept(0.0); 304 m_rearRightMotor.accept(0.0); 305 306 feed(); 307 } 308 309 @Override 310 public String getDescription() { 311 return "MecanumDrive"; 312 } 313 314 @Override 315 public void initSendable(SendableBuilder builder) { 316 builder.setSmartDashboardType("MecanumDrive"); 317 builder.setActuator(true); 318 builder.addDoubleProperty( 319 "Front Left Motor Velocity", () -> m_frontLeftOutput, m_frontLeftMotor); 320 builder.addDoubleProperty( 321 "Front Right Motor Velocity", () -> m_frontRightOutput, m_frontRightMotor); 322 builder.addDoubleProperty("Rear Left Motor Velocity", () -> m_rearLeftOutput, m_rearLeftMotor); 323 builder.addDoubleProperty( 324 "Rear Right Motor Velocity", () -> m_rearRightOutput, m_rearRightMotor); 325 } 326}