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.controller.proto.ElevatorFeedforwardProto;
008import edu.wpi.first.math.controller.struct.ElevatorFeedforwardStruct;
009import edu.wpi.first.util.protobuf.ProtobufSerializable;
010import edu.wpi.first.util.struct.StructSerializable;
011
012/**
013 * A helper class that computes feedforward outputs for a simple elevator (modeled as a motor acting
014 * against the force of gravity).
015 */
016public class ElevatorFeedforward implements ProtobufSerializable, StructSerializable {
017  /** The static gain, in volts. */
018  private double ks;
019
020  /** The gravity gain, in volts. */
021  private double kg;
022
023  /** The velocity gain, in V/(m/s). */
024  private double kv;
025
026  /** The acceleration gain, in V/(m/s²). */
027  private double ka;
028
029  /** The period, in seconds. */
030  private final double m_dt;
031
032  /**
033   * Creates a new ElevatorFeedforward with the specified gains and period.
034   *
035   * @param ks The static gain in volts.
036   * @param kg The gravity gain in volts.
037   * @param kv The velocity gain in V/(m/s).
038   * @param ka The acceleration gain in V/(m/s²).
039   * @param dtSeconds The period in seconds.
040   * @throws IllegalArgumentException for kv < zero.
041   * @throws IllegalArgumentException for ka < zero.
042   * @throws IllegalArgumentException for period ≤ zero.
043   */
044  public ElevatorFeedforward(double ks, double kg, double kv, double ka, double dtSeconds) {
045    this.ks = ks;
046    this.kg = kg;
047    this.kv = kv;
048    this.ka = ka;
049    if (kv < 0.0) {
050      throw new IllegalArgumentException("kv must be a non-negative number, got " + kv + "!");
051    }
052    if (ka < 0.0) {
053      throw new IllegalArgumentException("ka must be a non-negative number, got " + ka + "!");
054    }
055    if (dtSeconds <= 0.0) {
056      throw new IllegalArgumentException(
057          "period must be a positive number, got " + dtSeconds + "!");
058    }
059    m_dt = dtSeconds;
060  }
061
062  /**
063   * Creates a new ElevatorFeedforward with the specified gains. The period is defaulted to 20 ms.
064   *
065   * @param ks The static gain in volts.
066   * @param kg The gravity gain in volts.
067   * @param kv The velocity gain in V/(m/s).
068   * @param ka The acceleration gain in V/(m/s²).
069   * @throws IllegalArgumentException for kv &lt; zero.
070   * @throws IllegalArgumentException for ka &lt; zero.
071   */
072  public ElevatorFeedforward(double ks, double kg, double kv, double ka) {
073    this(ks, kg, kv, ka, 0.020);
074  }
075
076  /**
077   * Creates a new ElevatorFeedforward with the specified gains. Acceleration gain is defaulted to
078   * zero. The period is defaulted to 20 ms.
079   *
080   * @param ks The static gain in volts.
081   * @param kg The gravity gain in volts.
082   * @param kv The velocity gain in V/(m/s).
083   * @throws IllegalArgumentException for kv &lt; zero.
084   */
085  public ElevatorFeedforward(double ks, double kg, double kv) {
086    this(ks, kg, kv, 0);
087  }
088
089  /**
090   * Sets the static gain.
091   *
092   * @param ks The static gain in volts.
093   */
094  public void setKs(double ks) {
095    this.ks = ks;
096  }
097
098  /**
099   * Sets the gravity gain.
100   *
101   * @param kg The gravity gain in volts.
102   */
103  public void setKg(double kg) {
104    this.kg = kg;
105  }
106
107  /**
108   * Sets the velocity gain.
109   *
110   * @param kv The velocity gain in V/(m/s).
111   */
112  public void setKv(double kv) {
113    this.kv = kv;
114  }
115
116  /**
117   * Sets the acceleration gain.
118   *
119   * @param ka The acceleration gain in V/(m/s²).
120   */
121  public void setKa(double ka) {
122    this.ka = ka;
123  }
124
125  /**
126   * Returns the static gain in volts.
127   *
128   * @return The static gain in volts.
129   */
130  public double getKs() {
131    return ks;
132  }
133
134  /**
135   * Returns the gravity gain in volts.
136   *
137   * @return The gravity gain in volts.
138   */
139  public double getKg() {
140    return kg;
141  }
142
143  /**
144   * Returns the velocity gain in V/(m/s).
145   *
146   * @return The velocity gain.
147   */
148  public double getKv() {
149    return kv;
150  }
151
152  /**
153   * Returns the acceleration gain in V/(m/s²).
154   *
155   * @return The acceleration gain.
156   */
157  public double getKa() {
158    return ka;
159  }
160
161  /**
162   * Returns the period in seconds.
163   *
164   * @return The period in seconds.
165   */
166  public double getDt() {
167    return m_dt;
168  }
169
170  /**
171   * Calculates the feedforward from the gains and setpoints assuming continuous control.
172   *
173   * @param velocity The velocity setpoint.
174   * @param acceleration The acceleration setpoint.
175   * @return The computed feedforward.
176   */
177  @SuppressWarnings("removal")
178  @Deprecated(forRemoval = true, since = "2025")
179  public double calculate(double velocity, double acceleration) {
180    return ks * Math.signum(velocity) + kg + kv * velocity + ka * acceleration;
181  }
182
183  /**
184   * Calculates the feedforward from the gains and velocity setpoint assuming continuous control
185   * (acceleration is assumed to be zero).
186   *
187   * @param velocity The velocity setpoint.
188   * @return The computed feedforward.
189   */
190  public double calculate(double velocity) {
191    return calculate(velocity, 0);
192  }
193
194  /**
195   * Calculates the feedforward from the gains and setpoints assuming discrete control.
196   *
197   * <p>Note this method is inaccurate when the velocity crosses 0.
198   *
199   * @param currentVelocity The current velocity setpoint in meters per second.
200   * @param nextVelocity The next velocity setpoint in meters per second.
201   * @return The computed feedforward in volts.
202   */
203  public double calculateWithVelocities(double currentVelocity, double nextVelocity) {
204    // See wpimath/algorithms.md#Elevator_feedforward for derivation
205    if (ka == 0.0) {
206      return ks * Math.signum(nextVelocity) + kg + kv * nextVelocity;
207    } else {
208      double A = -kv / ka;
209      double B = 1.0 / ka;
210      double A_d = Math.exp(A * m_dt);
211      double B_d = 1.0 / A * (A_d - 1.0) * B;
212      return kg
213          + ks * Math.signum(currentVelocity)
214          + 1.0 / B_d * (nextVelocity - A_d * currentVelocity);
215    }
216  }
217
218  // Rearranging the main equation from the calculate() method yields the
219  // formulas for the methods below:
220
221  /**
222   * Calculates the maximum achievable velocity given a maximum voltage supply and an acceleration.
223   * Useful for ensuring that velocity and acceleration constraints for a trapezoidal profile are
224   * simultaneously achievable - enter the acceleration constraint, and this will give you a
225   * simultaneously-achievable velocity constraint.
226   *
227   * @param maxVoltage The maximum voltage that can be supplied to the elevator, in volts.
228   * @param acceleration The acceleration of the elevator, in (m/s²).
229   * @return The maximum possible velocity in (m/s) at the given acceleration.
230   */
231  public double maxAchievableVelocity(double maxVoltage, double acceleration) {
232    // Assume max velocity is positive
233    return (maxVoltage - ks - kg - acceleration * ka) / kv;
234  }
235
236  /**
237   * Calculates the minimum achievable velocity given a maximum voltage supply and an acceleration.
238   * Useful for ensuring that velocity and acceleration constraints for a trapezoidal profile are
239   * simultaneously achievable - enter the acceleration constraint, and this will give you a
240   * simultaneously-achievable velocity constraint.
241   *
242   * @param maxVoltage The maximum voltage that can be supplied to the elevator, in volts.
243   * @param acceleration The acceleration of the elevator, in (m/s²).
244   * @return The maximum possible velocity in (m/s) at the given acceleration.
245   */
246  public double minAchievableVelocity(double maxVoltage, double acceleration) {
247    // Assume min velocity is negative, ks flips sign
248    return (-maxVoltage + ks - kg - acceleration * ka) / kv;
249  }
250
251  /**
252   * Calculates the maximum achievable acceleration given a maximum voltage supply and a velocity.
253   * Useful for ensuring that velocity and acceleration constraints for a trapezoidal profile are
254   * simultaneously achievable - enter the velocity constraint, and this will give you a
255   * simultaneously-achievable acceleration constraint.
256   *
257   * @param maxVoltage The maximum voltage that can be supplied to the elevator, in volts.
258   * @param velocity The velocity of the elevator, in (m/s)
259   * @return The maximum possible acceleration in (m/s²) at the given velocity.
260   */
261  public double maxAchievableAcceleration(double maxVoltage, double velocity) {
262    return (maxVoltage - ks * Math.signum(velocity) - kg - velocity * kv) / ka;
263  }
264
265  /**
266   * Calculates the minimum achievable acceleration given a maximum voltage supply and a velocity.
267   * Useful for ensuring that velocity and acceleration constraints for a trapezoidal profile are
268   * simultaneously achievable - enter the velocity constraint, and this will give you a
269   * simultaneously-achievable acceleration constraint.
270   *
271   * @param maxVoltage The maximum voltage that can be supplied to the elevator, in volts.
272   * @param velocity The velocity of the elevator, in (m/s)
273   * @return The maximum possible acceleration in (m/s²) at the given velocity.
274   */
275  public double minAchievableAcceleration(double maxVoltage, double velocity) {
276    return maxAchievableAcceleration(-maxVoltage, velocity);
277  }
278
279  /** ElevatorFeedforward struct for serialization. */
280  public static final ElevatorFeedforwardStruct struct = new ElevatorFeedforwardStruct();
281
282  /** ElevatorFeedforward protobuf for serialization. */
283  public static final ElevatorFeedforwardProto proto = new ElevatorFeedforwardProto();
284}