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.math.controller;
006
007import edu.wpi.first.math.WPIMathJNI;
008import edu.wpi.first.math.controller.proto.ArmFeedforwardProto;
009import edu.wpi.first.math.controller.struct.ArmFeedforwardStruct;
010import edu.wpi.first.util.protobuf.ProtobufSerializable;
011import edu.wpi.first.util.struct.StructSerializable;
012
013/**
014 * A helper class that computes feedforward outputs for a simple arm (modeled as a motor acting
015 * against the force of gravity on a beam suspended at an angle).
016 */
017public class ArmFeedforward implements ProtobufSerializable, StructSerializable {
018  /** The static gain, in volts. */
019  public final double ks;
020
021  /** The gravity gain, in volts. */
022  public final double kg;
023
024  /** The velocity gain, in volt seconds per radian. */
025  public final double kv;
026
027  /** The acceleration gain, in volt secondsĀ² per radian. */
028  public final double ka;
029
030  /** Arm feedforward protobuf for serialization. */
031  public static final ArmFeedforwardProto proto = new ArmFeedforwardProto();
032
033  /** Arm feedforward struct for serialization. */
034  public static final ArmFeedforwardStruct struct = new ArmFeedforwardStruct();
035
036  /**
037   * Creates a new ArmFeedforward with the specified gains. Units of the gain values will dictate
038   * units of the computed feedforward.
039   *
040   * @param ks The static gain.
041   * @param kg The gravity gain.
042   * @param kv The velocity gain.
043   * @param ka The acceleration gain.
044   * @throws IllegalArgumentException for kv < zero.
045   * @throws IllegalArgumentException for ka < zero.
046   */
047  public ArmFeedforward(double ks, double kg, double kv, double ka) {
048    this.ks = ks;
049    this.kg = kg;
050    this.kv = kv;
051    this.ka = ka;
052    if (kv < 0.0) {
053      throw new IllegalArgumentException("kv must be a non-negative number, got " + kv + "!");
054    }
055    if (ka < 0.0) {
056      throw new IllegalArgumentException("ka must be a non-negative number, got " + ka + "!");
057    }
058  }
059
060  /**
061   * Creates a new ArmFeedforward with the specified gains. Acceleration gain is defaulted to zero.
062   * Units of the gain values will dictate units of the computed feedforward.
063   *
064   * @param ks The static gain.
065   * @param kg The gravity gain.
066   * @param kv The velocity gain.
067   */
068  public ArmFeedforward(double ks, double kg, double kv) {
069    this(ks, kg, kv, 0);
070  }
071
072  /**
073   * Calculates the feedforward from the gains and setpoints.
074   *
075   * @param positionRadians The position (angle) setpoint. This angle should be measured from the
076   *     horizontal (i.e. if the provided angle is 0, the arm should be parallel with the floor). If
077   *     your encoder does not follow this convention, an offset should be added.
078   * @param velocityRadPerSec The velocity setpoint.
079   * @param accelRadPerSecSquared The acceleration setpoint.
080   * @return The computed feedforward.
081   */
082  public double calculate(
083      double positionRadians, double velocityRadPerSec, double accelRadPerSecSquared) {
084    return ks * Math.signum(velocityRadPerSec)
085        + kg * Math.cos(positionRadians)
086        + kv * velocityRadPerSec
087        + ka * accelRadPerSecSquared;
088  }
089
090  /**
091   * Calculates the feedforward from the gains and velocity setpoint (acceleration is assumed to be
092   * zero).
093   *
094   * @param positionRadians The position (angle) setpoint. This angle should be measured from the
095   *     horizontal (i.e. if the provided angle is 0, the arm should be parallel with the floor). If
096   *     your encoder does not follow this convention, an offset should be added.
097   * @param velocity The velocity setpoint.
098   * @return The computed feedforward.
099   */
100  public double calculate(double positionRadians, double velocity) {
101    return calculate(positionRadians, velocity, 0);
102  }
103
104  /**
105   * Calculates the feedforward from the gains and setpoints.
106   *
107   * @param currentAngle The current angle in radians. This angle should be measured from the
108   *     horizontal (i.e. if the provided angle is 0, the arm should be parallel to the floor). If
109   *     your encoder does not follow this convention, an offset should be added.
110   * @param currentVelocity The current velocity setpoint in radians per second.
111   * @param nextVelocity The next velocity setpoint in radians per second.
112   * @param dt Time between velocity setpoints in seconds.
113   * @return The computed feedforward in volts.
114   */
115  public double calculate(
116      double currentAngle, double currentVelocity, double nextVelocity, double dt) {
117    return WPIMathJNI.calculate(ks, kv, ka, kg, currentAngle, currentVelocity, nextVelocity, dt);
118  }
119
120  // Rearranging the main equation from the calculate() method yields the
121  // formulas for the methods below:
122
123  /**
124   * Calculates the maximum achievable velocity given a maximum voltage supply, a position, and an
125   * acceleration. Useful for ensuring that velocity and acceleration constraints for a trapezoidal
126   * profile are simultaneously achievable - enter the acceleration constraint, and this will give
127   * you a simultaneously-achievable velocity constraint.
128   *
129   * @param maxVoltage The maximum voltage that can be supplied to the arm.
130   * @param angle The angle of the arm. This angle should be measured from the horizontal (i.e. if
131   *     the provided angle is 0, the arm should be parallel with the floor). If your encoder does
132   *     not follow this convention, an offset should be added.
133   * @param acceleration The acceleration of the arm.
134   * @return The maximum possible velocity at the given acceleration and angle.
135   */
136  public double maxAchievableVelocity(double maxVoltage, double angle, double acceleration) {
137    // Assume max velocity is positive
138    return (maxVoltage - ks - Math.cos(angle) * kg - acceleration * ka) / kv;
139  }
140
141  /**
142   * Calculates the minimum achievable velocity given a maximum voltage supply, a position, and an
143   * acceleration. Useful for ensuring that velocity and acceleration constraints for a trapezoidal
144   * profile are simultaneously achievable - enter the acceleration constraint, and this will give
145   * you a simultaneously-achievable velocity constraint.
146   *
147   * @param maxVoltage The maximum voltage that can be supplied to the arm.
148   * @param angle The angle of the arm. This angle should be measured from the horizontal (i.e. if
149   *     the provided angle is 0, the arm should be parallel with the floor). If your encoder does
150   *     not follow this convention, an offset should be added.
151   * @param acceleration The acceleration of the arm.
152   * @return The minimum possible velocity at the given acceleration and angle.
153   */
154  public double minAchievableVelocity(double maxVoltage, double angle, double acceleration) {
155    // Assume min velocity is negative, ks flips sign
156    return (-maxVoltage + ks - Math.cos(angle) * kg - acceleration * ka) / kv;
157  }
158
159  /**
160   * Calculates the maximum achievable acceleration given a maximum voltage supply, a position, and
161   * a velocity. Useful for ensuring that velocity and acceleration constraints for a trapezoidal
162   * profile are simultaneously achievable - enter the velocity constraint, and this will give you a
163   * simultaneously-achievable acceleration constraint.
164   *
165   * @param maxVoltage The maximum voltage that can be supplied to the arm.
166   * @param angle The angle of the arm. This angle should be measured from the horizontal (i.e. if
167   *     the provided angle is 0, the arm should be parallel with the floor). If your encoder does
168   *     not follow this convention, an offset should be added.
169   * @param velocity The velocity of the arm.
170   * @return The maximum possible acceleration at the given velocity.
171   */
172  public double maxAchievableAcceleration(double maxVoltage, double angle, double velocity) {
173    return (maxVoltage - ks * Math.signum(velocity) - Math.cos(angle) * kg - velocity * kv) / ka;
174  }
175
176  /**
177   * Calculates the minimum achievable acceleration given a maximum voltage supply, a position, and
178   * a velocity. Useful for ensuring that velocity and acceleration constraints for a trapezoidal
179   * profile are simultaneously achievable - enter the velocity constraint, and this will give you a
180   * simultaneously-achievable acceleration constraint.
181   *
182   * @param maxVoltage The maximum voltage that can be supplied to the arm.
183   * @param angle The angle of the arm. This angle should be measured from the horizontal (i.e. if
184   *     the provided angle is 0, the arm should be parallel with the floor). If your encoder does
185   *     not follow this convention, an offset should be added.
186   * @param velocity The velocity of the arm.
187   * @return The minimum possible acceleration at the given velocity.
188   */
189  public double minAchievableAcceleration(double maxVoltage, double angle, double velocity) {
190    return maxAchievableAcceleration(-maxVoltage, angle, velocity);
191  }
192}