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   * @deprecated Use {@link #calculateWithVelocities(double, double)}.
177   */
178  @SuppressWarnings("removal")
179  @Deprecated(forRemoval = true, since = "2025")
180  public double calculate(double velocity, double acceleration) {
181    return ks * Math.signum(velocity) + kg + kv * velocity + ka * acceleration;
182  }
183
184  /**
185   * Calculates the feedforward from the gains and velocity setpoint assuming continuous control
186   * (acceleration is assumed to be zero).
187   *
188   * @param velocity The velocity setpoint.
189   * @return The computed feedforward.
190   */
191  public double calculate(double velocity) {
192    return calculate(velocity, 0);
193  }
194
195  /**
196   * Calculates the feedforward from the gains and setpoints assuming discrete control.
197   *
198   * <p>Note this method is inaccurate when the velocity crosses 0.
199   *
200   * @param currentVelocity The current velocity setpoint in meters per second.
201   * @param nextVelocity The next velocity setpoint in meters per second.
202   * @return The computed feedforward in volts.
203   */
204  public double calculateWithVelocities(double currentVelocity, double nextVelocity) {
205    // See wpimath/algorithms.md#Elevator_feedforward for derivation
206    if (ka < 1e-9) {
207      return ks * Math.signum(nextVelocity) + kg + kv * nextVelocity;
208    } else {
209      double A = -kv / ka;
210      double B = 1.0 / ka;
211      double A_d = Math.exp(A * m_dt);
212      double B_d = A > -1e-9 ? B * m_dt : 1.0 / A * (A_d - 1.0) * B;
213      return kg
214          + ks * Math.signum(currentVelocity)
215          + 1.0 / B_d * (nextVelocity - A_d * currentVelocity);
216    }
217  }
218
219  // Rearranging the main equation from the calculate() method yields the
220  // formulas for the methods below:
221
222  /**
223   * Calculates the maximum achievable velocity given a maximum voltage supply and an acceleration.
224   * Useful for ensuring that velocity and acceleration constraints for a trapezoidal profile are
225   * simultaneously achievable - enter the acceleration constraint, and this will give you a
226   * simultaneously-achievable velocity constraint.
227   *
228   * @param maxVoltage The maximum voltage that can be supplied to the elevator, in volts.
229   * @param acceleration The acceleration of the elevator, in (m/s²).
230   * @return The maximum possible velocity in (m/s) at the given acceleration.
231   */
232  public double maxAchievableVelocity(double maxVoltage, double acceleration) {
233    // Assume max velocity is positive
234    return (maxVoltage - ks - kg - acceleration * ka) / kv;
235  }
236
237  /**
238   * Calculates the minimum achievable velocity given a maximum voltage supply and an acceleration.
239   * Useful for ensuring that velocity and acceleration constraints for a trapezoidal profile are
240   * simultaneously achievable - enter the acceleration constraint, and this will give you a
241   * simultaneously-achievable velocity constraint.
242   *
243   * @param maxVoltage The maximum voltage that can be supplied to the elevator, in volts.
244   * @param acceleration The acceleration of the elevator, in (m/s²).
245   * @return The maximum possible velocity in (m/s) at the given acceleration.
246   */
247  public double minAchievableVelocity(double maxVoltage, double acceleration) {
248    // Assume min velocity is negative, ks flips sign
249    return (-maxVoltage + ks - kg - acceleration * ka) / kv;
250  }
251
252  /**
253   * Calculates the maximum achievable acceleration given a maximum voltage supply and a velocity.
254   * Useful for ensuring that velocity and acceleration constraints for a trapezoidal profile are
255   * simultaneously achievable - enter the velocity constraint, and this will give you a
256   * simultaneously-achievable acceleration constraint.
257   *
258   * @param maxVoltage The maximum voltage that can be supplied to the elevator, in volts.
259   * @param velocity The velocity of the elevator, in (m/s)
260   * @return The maximum possible acceleration in (m/s²) at the given velocity.
261   */
262  public double maxAchievableAcceleration(double maxVoltage, double velocity) {
263    return (maxVoltage - ks * Math.signum(velocity) - kg - velocity * kv) / ka;
264  }
265
266  /**
267   * Calculates the minimum achievable acceleration given a maximum voltage supply and a velocity.
268   * Useful for ensuring that velocity and acceleration constraints for a trapezoidal profile are
269   * simultaneously achievable - enter the velocity constraint, and this will give you a
270   * simultaneously-achievable acceleration constraint.
271   *
272   * @param maxVoltage The maximum voltage that can be supplied to the elevator, in volts.
273   * @param velocity The velocity of the elevator, in (m/s)
274   * @return The maximum possible acceleration in (m/s²) at the given velocity.
275   */
276  public double minAchievableAcceleration(double maxVoltage, double velocity) {
277    return maxAchievableAcceleration(-maxVoltage, velocity);
278  }
279
280  /** ElevatorFeedforward struct for serialization. */
281  public static final ElevatorFeedforwardStruct struct = new ElevatorFeedforwardStruct();
282
283  /** ElevatorFeedforward protobuf for serialization. */
284  public static final ElevatorFeedforwardProto proto = new ElevatorFeedforwardProto();
285}