001/*----------------------------------------------------------------------------*/
002/* Copyright (c) FIRST 2008-2017. All Rights Reserved.                        */
003/* Open Source Software - may be modified and shared by FRC teams. The code   */
004/* must be accompanied by the FIRST BSD license file in the root directory of */
005/* the project.                                                               */
006/*----------------------------------------------------------------------------*/
007
008package edu.wpi.first.wpilibj;
009
010import edu.wpi.first.wpilibj.hal.FRCNetComm.tInstances;
011import edu.wpi.first.wpilibj.hal.FRCNetComm.tResourceType;
012import edu.wpi.first.wpilibj.hal.HAL;
013
014import static java.util.Objects.requireNonNull;
015
016/**
017 * Utility class for handling Robot drive based on a definition of the motor configuration. The
018 * robot drive class handles basic driving for a robot. Currently, 2 and 4 motor tank and mecanum
019 * drive trains are supported. In the future other drive types like swerve might be implemented.
020 * Motor channel numbers are supplied on creation of the class. Those are used for either the drive
021 * function (intended for hand created drive code, such as autonomous) or with the Tank/Arcade
022 * functions intended to be used for Operator Control driving.
023 */
024public class RobotDrive implements MotorSafety {
025
026  protected MotorSafetyHelper m_safetyHelper;
027
028  /**
029   * The location of a motor on the robot for the purpose of driving.
030   */
031  public enum MotorType {
032    kFrontLeft(0), kFrontRight(1), kRearLeft(2), kRearRight(3);
033
034    @SuppressWarnings("MemberName")
035    public final int value;
036
037    private MotorType(int value) {
038      this.value = value;
039    }
040  }
041
042  public static final double kDefaultExpirationTime = 0.1;
043  public static final double kDefaultSensitivity = 0.5;
044  public static final double kDefaultMaxOutput = 1.0;
045  protected static final int kMaxNumberOfMotors = 4;
046  protected double m_sensitivity;
047  protected double m_maxOutput;
048  protected SpeedController m_frontLeftMotor;
049  protected SpeedController m_frontRightMotor;
050  protected SpeedController m_rearLeftMotor;
051  protected SpeedController m_rearRightMotor;
052  protected boolean m_allocatedSpeedControllers;
053  protected static boolean kArcadeRatioCurve_Reported = false;
054  protected static boolean kTank_Reported = false;
055  protected static boolean kArcadeStandard_Reported = false;
056  protected static boolean kMecanumCartesian_Reported = false;
057  protected static boolean kMecanumPolar_Reported = false;
058
059  /**
060   * Constructor for RobotDrive with 2 motors specified with channel numbers. Set up parameters for
061   * a two wheel drive system where the left and right motor pwm channels are specified in the call.
062   * This call assumes Talons for controlling the motors.
063   *
064   * @param leftMotorChannel  The PWM channel number that drives the left motor.
065   * @param rightMotorChannel The PWM channel number that drives the right motor.
066   */
067  public RobotDrive(final int leftMotorChannel, final int rightMotorChannel) {
068    m_sensitivity = kDefaultSensitivity;
069    m_maxOutput = kDefaultMaxOutput;
070    m_frontLeftMotor = null;
071    m_rearLeftMotor = new Talon(leftMotorChannel);
072    m_frontRightMotor = null;
073    m_rearRightMotor = new Talon(rightMotorChannel);
074    m_allocatedSpeedControllers = true;
075    setupMotorSafety();
076    drive(0, 0);
077  }
078
079  /**
080   * Constructor for RobotDrive with 4 motors specified with channel numbers. Set up parameters for
081   * a four wheel drive system where all four motor pwm channels are specified in the call. This
082   * call assumes Talons for controlling the motors.
083   *
084   * @param frontLeftMotor  Front left motor channel number
085   * @param rearLeftMotor   Rear Left motor channel number
086   * @param frontRightMotor Front right motor channel number
087   * @param rearRightMotor  Rear Right motor channel number
088   */
089  public RobotDrive(final int frontLeftMotor, final int rearLeftMotor, final int frontRightMotor,
090                    final int rearRightMotor) {
091    m_sensitivity = kDefaultSensitivity;
092    m_maxOutput = kDefaultMaxOutput;
093    m_rearLeftMotor = new Talon(rearLeftMotor);
094    m_rearRightMotor = new Talon(rearRightMotor);
095    m_frontLeftMotor = new Talon(frontLeftMotor);
096    m_frontRightMotor = new Talon(frontRightMotor);
097    m_allocatedSpeedControllers = true;
098    setupMotorSafety();
099    drive(0, 0);
100  }
101
102  /**
103   * Constructor for RobotDrive with 2 motors specified as SpeedController objects. The
104   * SpeedController version of the constructor enables programs to use the RobotDrive classes with
105   * subclasses of the SpeedController objects, for example, versions with ramping or reshaping of
106   * the curve to suit motor bias or dead-band elimination.
107   *
108   * @param leftMotor  The left SpeedController object used to drive the robot.
109   * @param rightMotor the right SpeedController object used to drive the robot.
110   */
111  public RobotDrive(SpeedController leftMotor, SpeedController rightMotor) {
112    if (leftMotor == null || rightMotor == null) {
113      m_rearLeftMotor = m_rearRightMotor = null;
114      throw new NullPointerException("Null motor provided");
115    }
116    m_frontLeftMotor = null;
117    m_rearLeftMotor = leftMotor;
118    m_frontRightMotor = null;
119    m_rearRightMotor = rightMotor;
120    m_sensitivity = kDefaultSensitivity;
121    m_maxOutput = kDefaultMaxOutput;
122    m_allocatedSpeedControllers = false;
123    setupMotorSafety();
124    drive(0, 0);
125  }
126
127  /**
128   * Constructor for RobotDrive with 4 motors specified as SpeedController objects. Speed controller
129   * input version of RobotDrive (see previous comments).
130   *
131   * @param rearLeftMotor   The back left SpeedController object used to drive the robot.
132   * @param frontLeftMotor  The front left SpeedController object used to drive the robot
133   * @param rearRightMotor  The back right SpeedController object used to drive the robot.
134   * @param frontRightMotor The front right SpeedController object used to drive the robot.
135   */
136  public RobotDrive(SpeedController frontLeftMotor, SpeedController rearLeftMotor,
137                    SpeedController frontRightMotor, SpeedController rearRightMotor) {
138    m_frontLeftMotor = requireNonNull(frontLeftMotor, "frontLeftMotor cannot be null");
139    m_rearLeftMotor = requireNonNull(rearLeftMotor, "rearLeftMotor cannot be null");
140    m_frontRightMotor = requireNonNull(frontRightMotor, "frontRightMotor cannot be null");
141    m_rearRightMotor = requireNonNull(rearRightMotor, "rearRightMotor cannot be null");
142    m_sensitivity = kDefaultSensitivity;
143    m_maxOutput = kDefaultMaxOutput;
144    m_allocatedSpeedControllers = false;
145    setupMotorSafety();
146    drive(0, 0);
147  }
148
149  /**
150   * Drive the motors at "outputMagnitude" and "curve". Both outputMagnitude and curve are -1.0 to
151   * +1.0 values, where 0.0 represents stopped and not turning. {@literal curve < 0 will turn left
152   * and curve > 0} will turn right.
153   *
154   * <p>The algorithm for steering provides a constant turn radius for any normal speed range, both
155   * forward and backward. Increasing sensitivity causes sharper turns for fixed values of curve.
156   *
157   * <p>This function will most likely be used in an autonomous routine.
158   *
159   * @param outputMagnitude The speed setting for the outside wheel in a turn, forward or backwards,
160   *                        +1 to -1.
161   * @param curve           The rate of turn, constant for different forward speeds. Set {@literal
162   *                        curve < 0 for left turn or curve > 0 for right turn.} Set curve =
163   *                        e^(-r/w) to get a turn radius r for wheelbase w of your robot.
164   *                        Conversely, turn radius r = -ln(curve)*w for a given value of curve and
165   *                        wheelbase w.
166   */
167  public void drive(double outputMagnitude, double curve) {
168    final double leftOutput;
169    final double rightOutput;
170
171    if (!kArcadeRatioCurve_Reported) {
172      HAL.report(tResourceType.kResourceType_RobotDrive, getNumMotors(),
173          tInstances.kRobotDrive_ArcadeRatioCurve);
174      kArcadeRatioCurve_Reported = true;
175    }
176    if (curve < 0) {
177      double value = Math.log(-curve);
178      double ratio = (value - m_sensitivity) / (value + m_sensitivity);
179      if (ratio == 0) {
180        ratio = .0000000001;
181      }
182      leftOutput = outputMagnitude / ratio;
183      rightOutput = outputMagnitude;
184    } else if (curve > 0) {
185      double value = Math.log(curve);
186      double ratio = (value - m_sensitivity) / (value + m_sensitivity);
187      if (ratio == 0) {
188        ratio = .0000000001;
189      }
190      leftOutput = outputMagnitude;
191      rightOutput = outputMagnitude / ratio;
192    } else {
193      leftOutput = outputMagnitude;
194      rightOutput = outputMagnitude;
195    }
196    setLeftRightMotorOutputs(leftOutput, rightOutput);
197  }
198
199  /**
200   * Provide tank steering using the stored robot configuration. drive the robot using two joystick
201   * inputs. The Y-axis will be selected from each Joystick object.
202   *
203   * @param leftStick  The joystick to control the left side of the robot.
204   * @param rightStick The joystick to control the right side of the robot.
205   */
206  public void tankDrive(GenericHID leftStick, GenericHID rightStick) {
207    if (leftStick == null || rightStick == null) {
208      throw new NullPointerException("Null HID provided");
209    }
210    tankDrive(leftStick.getY(), rightStick.getY(), true);
211  }
212
213  /**
214   * Provide tank steering using the stored robot configuration. drive the robot using two joystick
215   * inputs. The Y-axis will be selected from each Joystick object.
216   *
217   * @param leftStick     The joystick to control the left side of the robot.
218   * @param rightStick    The joystick to control the right side of the robot.
219   * @param squaredInputs Setting this parameter to true decreases the sensitivity at lower speeds
220   */
221  public void tankDrive(GenericHID leftStick, GenericHID rightStick, boolean squaredInputs) {
222    if (leftStick == null || rightStick == null) {
223      throw new NullPointerException("Null HID provided");
224    }
225    tankDrive(leftStick.getY(), rightStick.getY(), squaredInputs);
226  }
227
228  /**
229   * Provide tank steering using the stored robot configuration. This function lets you pick the
230   * axis to be used on each Joystick object for the left and right sides of the robot.
231   *
232   * @param leftStick  The Joystick object to use for the left side of the robot.
233   * @param leftAxis   The axis to select on the left side Joystick object.
234   * @param rightStick The Joystick object to use for the right side of the robot.
235   * @param rightAxis  The axis to select on the right side Joystick object.
236   */
237  public void tankDrive(GenericHID leftStick, final int leftAxis, GenericHID rightStick,
238                        final int rightAxis) {
239    if (leftStick == null || rightStick == null) {
240      throw new NullPointerException("Null HID provided");
241    }
242    tankDrive(leftStick.getRawAxis(leftAxis), rightStick.getRawAxis(rightAxis), true);
243  }
244
245  /**
246   * Provide tank steering using the stored robot configuration. This function lets you pick the
247   * axis to be used on each Joystick object for the left and right sides of the robot.
248   *
249   * @param leftStick     The Joystick object to use for the left side of the robot.
250   * @param leftAxis      The axis to select on the left side Joystick object.
251   * @param rightStick    The Joystick object to use for the right side of the robot.
252   * @param rightAxis     The axis to select on the right side Joystick object.
253   * @param squaredInputs Setting this parameter to true decreases the sensitivity at lower speeds
254   */
255  public void tankDrive(GenericHID leftStick, final int leftAxis, GenericHID rightStick,
256                        final int rightAxis, boolean squaredInputs) {
257    if (leftStick == null || rightStick == null) {
258      throw new NullPointerException("Null HID provided");
259    }
260    tankDrive(leftStick.getRawAxis(leftAxis), rightStick.getRawAxis(rightAxis), squaredInputs);
261  }
262
263  /**
264   * Provide tank steering using the stored robot configuration. This function lets you directly
265   * provide joystick values from any source.
266   *
267   * @param leftValue     The value of the left stick.
268   * @param rightValue    The value of the right stick.
269   * @param squaredInputs Setting this parameter to true decreases the sensitivity at lower speeds
270   */
271  public void tankDrive(double leftValue, double rightValue, boolean squaredInputs) {
272
273    if (!kTank_Reported) {
274      HAL.report(tResourceType.kResourceType_RobotDrive, getNumMotors(),
275          tInstances.kRobotDrive_Tank);
276      kTank_Reported = true;
277    }
278
279    // square the inputs (while preserving the sign) to increase fine control
280    // while permitting full power
281    leftValue = limit(leftValue);
282    rightValue = limit(rightValue);
283    if (squaredInputs) {
284      if (leftValue >= 0.0) {
285        leftValue = leftValue * leftValue;
286      } else {
287        leftValue = -(leftValue * leftValue);
288      }
289      if (rightValue >= 0.0) {
290        rightValue = rightValue * rightValue;
291      } else {
292        rightValue = -(rightValue * rightValue);
293      }
294    }
295    setLeftRightMotorOutputs(leftValue, rightValue);
296  }
297
298  /**
299   * Provide tank steering using the stored robot configuration. This function lets you directly
300   * provide joystick values from any source.
301   *
302   * @param leftValue  The value of the left stick.
303   * @param rightValue The value of the right stick.
304   */
305  public void tankDrive(double leftValue, double rightValue) {
306    tankDrive(leftValue, rightValue, true);
307  }
308
309  /**
310   * Arcade drive implements single stick driving. Given a single Joystick, the class assumes the Y
311   * axis for the move value and the X axis for the rotate value. (Should add more information here
312   * regarding the way that arcade drive works.)
313   *
314   * @param stick         The joystick to use for Arcade single-stick driving. The Y-axis will be
315   *                      selected for forwards/backwards and the X-axis will be selected for
316   *                      rotation rate.
317   * @param squaredInputs If true, the sensitivity will be decreased for small values
318   */
319  public void arcadeDrive(GenericHID stick, boolean squaredInputs) {
320    // simply call the full-featured arcadeDrive with the appropriate values
321    arcadeDrive(stick.getY(), stick.getX(), squaredInputs);
322  }
323
324  /**
325   * Arcade drive implements single stick driving. Given a single Joystick, the class assumes the Y
326   * axis for the move value and the X axis for the rotate value. (Should add more information here
327   * regarding the way that arcade drive works.)
328   *
329   * @param stick The joystick to use for Arcade single-stick driving. The Y-axis will be selected
330   *              for forwards/backwards and the X-axis will be selected for rotation rate.
331   */
332  public void arcadeDrive(GenericHID stick) {
333    arcadeDrive(stick, true);
334  }
335
336  /**
337   * Arcade drive implements single stick driving. Given two joystick instances and two axis,
338   * compute the values to send to either two or four motors.
339   *
340   * @param moveStick     The Joystick object that represents the forward/backward direction
341   * @param moveAxis      The axis on the moveStick object to use for forwards/backwards (typically
342   *                      Y_AXIS)
343   * @param rotateStick   The Joystick object that represents the rotation value
344   * @param rotateAxis    The axis on the rotation object to use for the rotate right/left
345   *                      (typically X_AXIS)
346   * @param squaredInputs Setting this parameter to true decreases the sensitivity at lower speeds
347   */
348  public void arcadeDrive(GenericHID moveStick, final int moveAxis, GenericHID rotateStick,
349                          final int rotateAxis, boolean squaredInputs) {
350    double moveValue = moveStick.getRawAxis(moveAxis);
351    double rotateValue = rotateStick.getRawAxis(rotateAxis);
352
353    arcadeDrive(moveValue, rotateValue, squaredInputs);
354  }
355
356  /**
357   * Arcade drive implements single stick driving. Given two joystick instances and two axis,
358   * compute the values to send to either two or four motors.
359   *
360   * @param moveStick   The Joystick object that represents the forward/backward direction
361   * @param moveAxis    The axis on the moveStick object to use for forwards/backwards (typically
362   *                    Y_AXIS)
363   * @param rotateStick The Joystick object that represents the rotation value
364   * @param rotateAxis  The axis on the rotation object to use for the rotate right/left (typically
365   *                    X_AXIS)
366   */
367  public void arcadeDrive(GenericHID moveStick, final int moveAxis, GenericHID rotateStick,
368                          final int rotateAxis) {
369    arcadeDrive(moveStick, moveAxis, rotateStick, rotateAxis, true);
370  }
371
372  /**
373   * Arcade drive implements single stick driving. This function lets you directly provide
374   * joystick values from any source.
375   *
376   * @param moveValue     The value to use for forwards/backwards
377   * @param rotateValue   The value to use for the rotate right/left
378   * @param squaredInputs If set, decreases the sensitivity at low speeds
379   */
380  public void arcadeDrive(double moveValue, double rotateValue, boolean squaredInputs) {
381    // local variables to hold the computed PWM values for the motors
382    if (!kArcadeStandard_Reported) {
383      HAL.report(tResourceType.kResourceType_RobotDrive, getNumMotors(),
384          tInstances.kRobotDrive_ArcadeStandard);
385      kArcadeStandard_Reported = true;
386    }
387
388    double leftMotorSpeed;
389    double rightMotorSpeed;
390
391    moveValue = limit(moveValue);
392    rotateValue = limit(rotateValue);
393
394    if (squaredInputs) {
395      // square the inputs (while preserving the sign) to increase fine control
396      // while permitting full power
397      if (moveValue >= 0.0) {
398        moveValue = moveValue * moveValue;
399      } else {
400        moveValue = -(moveValue * moveValue);
401      }
402      if (rotateValue >= 0.0) {
403        rotateValue = rotateValue * rotateValue;
404      } else {
405        rotateValue = -(rotateValue * rotateValue);
406      }
407    }
408
409    if (moveValue > 0.0) {
410      if (rotateValue > 0.0) {
411        leftMotorSpeed = moveValue - rotateValue;
412        rightMotorSpeed = Math.max(moveValue, rotateValue);
413      } else {
414        leftMotorSpeed = Math.max(moveValue, -rotateValue);
415        rightMotorSpeed = moveValue + rotateValue;
416      }
417    } else {
418      if (rotateValue > 0.0) {
419        leftMotorSpeed = -Math.max(-moveValue, rotateValue);
420        rightMotorSpeed = moveValue + rotateValue;
421      } else {
422        leftMotorSpeed = moveValue - rotateValue;
423        rightMotorSpeed = -Math.max(-moveValue, -rotateValue);
424      }
425    }
426
427    setLeftRightMotorOutputs(leftMotorSpeed, rightMotorSpeed);
428  }
429
430  /**
431   * Arcade drive implements single stick driving. This function lets you directly provide
432   * joystick values from any source.
433   *
434   * @param moveValue   The value to use for fowards/backwards
435   * @param rotateValue The value to use for the rotate right/left
436   */
437  public void arcadeDrive(double moveValue, double rotateValue) {
438    arcadeDrive(moveValue, rotateValue, true);
439  }
440
441  /**
442   * Drive method for Mecanum wheeled robots.
443   *
444   * <p>A method for driving with Mecanum wheeled robots. There are 4 wheels on the robot, arranged
445   * so that the front and back wheels are toed in 45 degrees. When looking at the wheels from the
446   * top, the roller axles should form an X across the robot.
447   *
448   * <p>This is designed to be directly driven by joystick axes.
449   *
450   * @param x         The speed that the robot should drive in the X direction. [-1.0..1.0]
451   * @param y         The speed that the robot should drive in the Y direction. This input is
452   *                  inverted to match the forward == -1.0 that joysticks produce. [-1.0..1.0]
453   * @param rotation  The rate of rotation for the robot that is completely independent of the
454   *                  translation. [-1.0..1.0]
455   * @param gyroAngle The current angle reading from the gyro. Use this to implement field-oriented
456   *                  controls.
457   */
458  @SuppressWarnings("ParameterName")
459  public void mecanumDrive_Cartesian(double x, double y, double rotation, double gyroAngle) {
460    if (!kMecanumCartesian_Reported) {
461      HAL.report(tResourceType.kResourceType_RobotDrive, getNumMotors(),
462          tInstances.kRobotDrive_MecanumCartesian);
463      kMecanumCartesian_Reported = true;
464    }
465    @SuppressWarnings("LocalVariableName")
466    double xIn = x;
467    @SuppressWarnings("LocalVariableName")
468    double yIn = y;
469    // Negate y for the joystick.
470    yIn = -yIn;
471    // Compenstate for gyro angle.
472    double[] rotated = rotateVector(xIn, yIn, gyroAngle);
473    xIn = rotated[0];
474    yIn = rotated[1];
475
476    double[] wheelSpeeds = new double[kMaxNumberOfMotors];
477    wheelSpeeds[MotorType.kFrontLeft.value] = xIn + yIn + rotation;
478    wheelSpeeds[MotorType.kFrontRight.value] = -xIn + yIn - rotation;
479    wheelSpeeds[MotorType.kRearLeft.value] = -xIn + yIn + rotation;
480    wheelSpeeds[MotorType.kRearRight.value] = xIn + yIn - rotation;
481
482    normalize(wheelSpeeds);
483    m_frontLeftMotor.set(wheelSpeeds[MotorType.kFrontLeft.value] * m_maxOutput);
484    m_frontRightMotor.set(wheelSpeeds[MotorType.kFrontRight.value] * m_maxOutput);
485    m_rearLeftMotor.set(wheelSpeeds[MotorType.kRearLeft.value] * m_maxOutput);
486    m_rearRightMotor.set(wheelSpeeds[MotorType.kRearRight.value] * m_maxOutput);
487
488    if (m_safetyHelper != null) {
489      m_safetyHelper.feed();
490    }
491  }
492
493  /**
494   * Drive method for Mecanum wheeled robots.
495   *
496   * <p>A method for driving with Mecanum wheeled robots. There are 4 wheels on the robot, arranged
497   * so that the front and back wheels are toed in 45 degrees. When looking at the wheels from the
498   * top, the roller axles should form an X across the robot.
499   *
500   * @param magnitude The speed that the robot should drive in a given direction.
501   * @param direction The direction the robot should drive in degrees. The direction and maginitute
502   *                  are independent of the rotation rate.
503   * @param rotation  The rate of rotation for the robot that is completely independent of the
504   *                  magnitute or direction. [-1.0..1.0]
505   */
506  public void mecanumDrive_Polar(double magnitude, double direction, double rotation) {
507    if (!kMecanumPolar_Reported) {
508      HAL.report(tResourceType.kResourceType_RobotDrive, getNumMotors(),
509          tInstances.kRobotDrive_MecanumPolar);
510      kMecanumPolar_Reported = true;
511    }
512    // Normalized for full power along the Cartesian axes.
513    magnitude = limit(magnitude) * Math.sqrt(2.0);
514    // The rollers are at 45 degree angles.
515    double dirInRad = (direction + 45.0) * 3.14159 / 180.0;
516    double cosD = Math.cos(dirInRad);
517    double sinD = Math.sin(dirInRad);
518
519    double[] wheelSpeeds = new double[kMaxNumberOfMotors];
520    wheelSpeeds[MotorType.kFrontLeft.value] = (sinD * magnitude + rotation);
521    wheelSpeeds[MotorType.kFrontRight.value] = (cosD * magnitude - rotation);
522    wheelSpeeds[MotorType.kRearLeft.value] = (cosD * magnitude + rotation);
523    wheelSpeeds[MotorType.kRearRight.value] = (sinD * magnitude - rotation);
524
525    normalize(wheelSpeeds);
526
527    m_frontLeftMotor.set(wheelSpeeds[MotorType.kFrontLeft.value] * m_maxOutput);
528    m_frontRightMotor.set(wheelSpeeds[MotorType.kFrontRight.value] * m_maxOutput);
529    m_rearLeftMotor.set(wheelSpeeds[MotorType.kRearLeft.value] * m_maxOutput);
530    m_rearRightMotor.set(wheelSpeeds[MotorType.kRearRight.value] * m_maxOutput);
531
532    if (m_safetyHelper != null) {
533      m_safetyHelper.feed();
534    }
535  }
536
537  /**
538   * Holonomic Drive method for Mecanum wheeled robots.
539   *
540   * <p>This is an alias to mecanumDrive_Polar() for backward compatability
541   *
542   * @param magnitude The speed that the robot should drive in a given direction. [-1.0..1.0]
543   * @param direction The direction the robot should drive. The direction and maginitute are
544   *                  independent of the rotation rate.
545   * @param rotation  The rate of rotation for the robot that is completely independent of the
546   *                  magnitute or direction. [-1.0..1.0]
547   */
548  void holonomicDrive(double magnitude, double direction, double rotation) {
549    mecanumDrive_Polar(magnitude, direction, rotation);
550  }
551
552  /**
553   * Set the speed of the right and left motors. This is used once an appropriate drive setup
554   * function is called such as twoWheelDrive(). The motors are set to "leftSpeed" and
555   * "rightSpeed" and includes flipping the direction of one side for opposing motors.
556   *
557   * @param leftOutput  The speed to send to the left side of the robot.
558   * @param rightOutput The speed to send to the right side of the robot.
559   */
560  public void setLeftRightMotorOutputs(double leftOutput, double rightOutput) {
561    if (m_rearLeftMotor == null || m_rearRightMotor == null) {
562      throw new NullPointerException("Null motor provided");
563    }
564
565    if (m_frontLeftMotor != null) {
566      m_frontLeftMotor.set(limit(leftOutput) * m_maxOutput);
567    }
568    m_rearLeftMotor.set(limit(leftOutput) * m_maxOutput);
569
570    if (m_frontRightMotor != null) {
571      m_frontRightMotor.set(-limit(rightOutput) * m_maxOutput);
572    }
573    m_rearRightMotor.set(-limit(rightOutput) * m_maxOutput);
574
575    if (m_safetyHelper != null) {
576      m_safetyHelper.feed();
577    }
578  }
579
580  /**
581   * Limit motor values to the -1.0 to +1.0 range.
582   */
583  protected static double limit(double num) {
584    if (num > 1.0) {
585      return 1.0;
586    }
587    if (num < -1.0) {
588      return -1.0;
589    }
590    return num;
591  }
592
593  /**
594   * Normalize all wheel speeds if the magnitude of any wheel is greater than 1.0.
595   */
596  protected static void normalize(double[] wheelSpeeds) {
597    double maxMagnitude = Math.abs(wheelSpeeds[0]);
598    for (int i = 1; i < kMaxNumberOfMotors; i++) {
599      double temp = Math.abs(wheelSpeeds[i]);
600      if (maxMagnitude < temp) {
601        maxMagnitude = temp;
602      }
603    }
604    if (maxMagnitude > 1.0) {
605      for (int i = 0; i < kMaxNumberOfMotors; i++) {
606        wheelSpeeds[i] = wheelSpeeds[i] / maxMagnitude;
607      }
608    }
609  }
610
611  /**
612   * Rotate a vector in Cartesian space.
613   */
614  @SuppressWarnings("ParameterName")
615  protected static double[] rotateVector(double x, double y, double angle) {
616    double cosA = Math.cos(angle * (3.14159 / 180.0));
617    double sinA = Math.sin(angle * (3.14159 / 180.0));
618    double[] out = new double[2];
619    out[0] = x * cosA - y * sinA;
620    out[1] = x * sinA + y * cosA;
621    return out;
622  }
623
624  /**
625   * Invert a motor direction. This is used when a motor should run in the opposite direction as the
626   * drive code would normally run it. Motors that are direct drive would be inverted, the drive
627   * code assumes that the motors are geared with one reversal.
628   *
629   * @param motor      The motor index to invert.
630   * @param isInverted True if the motor should be inverted when operated.
631   */
632  public void setInvertedMotor(MotorType motor, boolean isInverted) {
633    switch (motor) {
634      case kFrontLeft:
635        m_frontLeftMotor.setInverted(isInverted);
636        break;
637      case kFrontRight:
638        m_frontRightMotor.setInverted(isInverted);
639        break;
640      case kRearLeft:
641        m_rearLeftMotor.setInverted(isInverted);
642        break;
643      case kRearRight:
644        m_rearRightMotor.setInverted(isInverted);
645        break;
646      default:
647        throw new IllegalArgumentException("Illegal motor type: " + motor);
648    }
649  }
650
651  /**
652   * Set the turning sensitivity.
653   *
654   * <p>This only impacts the drive() entry-point.
655   *
656   * @param sensitivity Effectively sets the turning sensitivity (or turn radius for a given value)
657   */
658  public void setSensitivity(double sensitivity) {
659    m_sensitivity = sensitivity;
660  }
661
662  /**
663   * Configure the scaling factor for using RobotDrive with motor controllers in a mode other than
664   * PercentVbus.
665   *
666   * @param maxOutput Multiplied with the output percentage computed by the drive functions.
667   */
668  public void setMaxOutput(double maxOutput) {
669    m_maxOutput = maxOutput;
670  }
671
672  /**
673   * Free the speed controllers if they were allocated locally.
674   */
675  public void free() {
676    if (m_allocatedSpeedControllers) {
677      if (m_frontLeftMotor != null) {
678        ((PWM) m_frontLeftMotor).free();
679      }
680      if (m_frontRightMotor != null) {
681        ((PWM) m_frontRightMotor).free();
682      }
683      if (m_rearLeftMotor != null) {
684        ((PWM) m_rearLeftMotor).free();
685      }
686      if (m_rearRightMotor != null) {
687        ((PWM) m_rearRightMotor).free();
688      }
689    }
690  }
691
692  @Override
693  public void setExpiration(double timeout) {
694    m_safetyHelper.setExpiration(timeout);
695  }
696
697  @Override
698  public double getExpiration() {
699    return m_safetyHelper.getExpiration();
700  }
701
702  @Override
703  public boolean isAlive() {
704    return m_safetyHelper.isAlive();
705  }
706
707  @Override
708  public boolean isSafetyEnabled() {
709    return m_safetyHelper.isSafetyEnabled();
710  }
711
712  @Override
713  public void setSafetyEnabled(boolean enabled) {
714    m_safetyHelper.setSafetyEnabled(enabled);
715  }
716
717  @Override
718  public String getDescription() {
719    return "Robot Drive";
720  }
721
722  @Override
723  public void stopMotor() {
724    if (m_frontLeftMotor != null) {
725      m_frontLeftMotor.stopMotor();
726    }
727    if (m_frontRightMotor != null) {
728      m_frontRightMotor.stopMotor();
729    }
730    if (m_rearLeftMotor != null) {
731      m_rearLeftMotor.stopMotor();
732    }
733    if (m_rearRightMotor != null) {
734      m_rearRightMotor.stopMotor();
735    }
736    if (m_safetyHelper != null) {
737      m_safetyHelper.feed();
738    }
739  }
740
741  private void setupMotorSafety() {
742    m_safetyHelper = new MotorSafetyHelper(this);
743    m_safetyHelper.setExpiration(kDefaultExpirationTime);
744    m_safetyHelper.setSafetyEnabled(true);
745  }
746
747  protected int getNumMotors() {
748    int motors = 0;
749    if (m_frontLeftMotor != null) {
750      motors++;
751    }
752    if (m_frontRightMotor != null) {
753      motors++;
754    }
755    if (m_rearLeftMotor != null) {
756      motors++;
757    }
758    if (m_rearRightMotor != null) {
759      motors++;
760    }
761    return motors;
762  }
763}