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}