001/*
002 * Copyright (C) Cross The Road Electronics.  All rights reserved.
003 * License information can be found in CTRE_LICENSE.txt
004 * For support and suggestions contact support@ctr-electronics.com or file
005 * an issue tracker at https://github.com/CrossTheRoadElec/Phoenix-Releases
006 */
007package com.ctre.phoenix6.swerve.utility;
008
009import org.wpilib.math.util.MathUtil;
010
011/**
012 * Phoenix-centric PID controller taken from WPI's {@link org.wpilib.math.controller.PIDController} class.
013 * <p>
014 * This class differs from the WPI implementation by using explicit timestamps for
015 * integral/derivative calculations. Ideally, these timestamps come from the {@link com.ctre.phoenix6.StatusSignal}.
016 */
017public class PhoenixPIDController {
018  // Factor for "proportional" control
019  private double m_kp;
020
021  // Factor for "integral" control
022  private double m_ki;
023
024  // Factor for "derivative" control
025  private double m_kd;
026
027  // The error range where "integral" control applies
028  private double m_iZone = Double.POSITIVE_INFINITY;
029
030  private double m_maximumIntegral = 1.0;
031
032  private double m_minimumIntegral = -1.0;
033
034  private double m_maximumInput;
035
036  private double m_minimumInput;
037
038  // Do the endpoints wrap around? e.g. Absolute encoder
039  private boolean m_continuous;
040
041  // The error at the time of the most recent call to calculate()
042  private double m_positionError;
043  private double m_velocityError;
044
045  // The error at the time of the second-most-recent call to calculate() (used to compute velocity)
046  private double m_prevError;
047
048  // The sum of the errors for use in the integral calc
049  private double m_totalError;
050
051  // The error that is considered at setpoint.
052  private double m_positionTolerance = 0.05;
053  private double m_velocityTolerance = Double.POSITIVE_INFINITY;
054
055  private double m_setpoint;
056  private double m_measurement;
057
058  private boolean m_haveMeasurement;
059  private boolean m_haveSetpoint;
060
061  private double m_lastAppliedOutput;
062
063  // The last timestamp acquired when performing a calculation
064  private double m_lastTimestamp;
065
066  /**
067   * Allocates a PIDController with the given constants for kp, ki, and kd.
068   *
069   * @param kp The proportional coefficient.
070   * @param ki The integral coefficient.
071   * @param kd The derivative coefficient.
072   */
073  @SuppressWarnings("this-escape")
074  public PhoenixPIDController(double kp, double ki, double kd) {
075    m_kp = kp;
076    m_ki = ki;
077    m_kd = kd;
078  }
079
080  /**
081   * Sets the PID Controller gain parameters.
082   *
083   * <p>Set the proportional, integral, and differential coefficients.
084   *
085   * @param kp The proportional coefficient.
086   * @param ki The integral coefficient.
087   * @param kd The derivative coefficient.
088   */
089  public void setPID(double kp, double ki, double kd) {
090    m_kp = kp;
091    m_ki = ki;
092    m_kd = kd;
093  }
094
095  /**
096   * Sets the Proportional coefficient of the PID controller gain.
097   *
098   * @param kp proportional coefficient
099   */
100  public void setP(double kp) {
101    m_kp = kp;
102  }
103
104  /**
105   * Sets the Integral coefficient of the PID controller gain.
106   *
107   * @param ki integral coefficient
108   */
109  public void setI(double ki) {
110    m_ki = ki;
111  }
112
113  /**
114   * Sets the Differential coefficient of the PID controller gain.
115   *
116   * @param kd differential coefficient
117   */
118  public void setD(double kd) {
119    m_kd = kd;
120  }
121
122  /**
123   * Sets the IZone range. When the absolute value of the position error is greater than IZone, the
124   * total accumulated error will reset to zero, disabling integral gain until the absolute value of
125   * the position error is less than IZone. This is used to prevent integral windup. Must be
126   * non-negative. Passing a value of zero will effectively disable integral gain. Passing a value
127   * of {@link Double#POSITIVE_INFINITY} disables IZone functionality.
128   *
129   * @param iZone Maximum magnitude of error to allow integral control.
130   * @throws IllegalArgumentException if iZone &lt; 0
131   */
132  public void setIZone(double iZone) {
133    if (iZone < 0) {
134      throw new IllegalArgumentException("IZone must be a non-negative number!");
135    }
136    m_iZone = iZone;
137  }
138
139  /**
140   * Get the Proportional coefficient.
141   *
142   * @return proportional coefficient
143   */
144  public double getP() {
145    return m_kp;
146  }
147
148  /**
149   * Get the Integral coefficient.
150   *
151   * @return integral coefficient
152   */
153  public double getI() {
154    return m_ki;
155  }
156
157  /**
158   * Get the Differential coefficient.
159   *
160   * @return differential coefficient
161   */
162  public double getD() {
163    return m_kd;
164  }
165
166  /**
167   * Get the IZone range.
168   *
169   * @return Maximum magnitude of error to allow integral control.
170   */
171  public double getIZone() {
172    return m_iZone;
173  }
174
175  /**
176   * Returns the position tolerance of this controller.
177   *
178   * @return the position tolerance of the controller.
179   */
180  public double getPositionTolerance() {
181    return m_positionTolerance;
182  }
183
184  /**
185   * Returns the velocity tolerance of this controller.
186   *
187   * @return the velocity tolerance of the controller.
188   */
189  public double getVelocityTolerance() {
190    return m_velocityTolerance;
191  }
192
193  /**
194   * Returns the current setpoint of the PIDController.
195   *
196   * @return The current setpoint.
197   */
198  public double getSetpoint() {
199    return m_setpoint;
200  }
201
202  /**
203   * Returns true if the error is within the tolerance of the setpoint.
204   *
205   * <p>This will return false until at least one input value has been computed.
206   *
207   * @return Whether the error is within the acceptable bounds.
208   */
209  public boolean atSetpoint() {
210    return m_haveMeasurement
211        && m_haveSetpoint
212        && Math.abs(m_positionError) < m_positionTolerance
213        && Math.abs(m_velocityError) < m_velocityTolerance;
214  }
215
216  /**
217   * Enables continuous input.
218   *
219   * <p>Rather then using the max and min input range as constraints, it considers them to be the
220   * same point and automatically calculates the shortest route to the setpoint.
221   *
222   * @param minimumInput The minimum value expected from the input.
223   * @param maximumInput The maximum value expected from the input.
224   */
225  public void enableContinuousInput(double minimumInput, double maximumInput) {
226    m_continuous = true;
227    m_minimumInput = minimumInput;
228    m_maximumInput = maximumInput;
229  }
230
231  /** Disables continuous input. */
232  public void disableContinuousInput() {
233    m_continuous = false;
234  }
235
236  /**
237   * Returns true if continuous input is enabled.
238   *
239   * @return True if continuous input is enabled.
240   */
241  public boolean isContinuousInputEnabled() {
242    return m_continuous;
243  }
244
245  /**
246   * Sets the minimum and maximum values for the integrator.
247   *
248   * <p>When the cap is reached, the integrator value is added to the controller output rather than
249   * the integrator value times the integral gain.
250   *
251   * @param minimumIntegral The minimum value of the integrator.
252   * @param maximumIntegral The maximum value of the integrator.
253   */
254  public void setIntegratorRange(double minimumIntegral, double maximumIntegral) {
255    m_minimumIntegral = minimumIntegral;
256    m_maximumIntegral = maximumIntegral;
257  }
258
259  /**
260   * Sets the error which is considered tolerable for use with atSetpoint().
261   *
262   * @param positionTolerance Position error which is tolerable.
263   */
264  public void setTolerance(double positionTolerance) {
265    setTolerance(positionTolerance, Double.POSITIVE_INFINITY);
266  }
267
268  /**
269   * Sets the error which is considered tolerable for use with atSetpoint().
270   *
271   * @param positionTolerance Position error which is tolerable.
272   * @param velocityTolerance Velocity error which is tolerable.
273   */
274  public void setTolerance(double positionTolerance, double velocityTolerance) {
275    m_positionTolerance = positionTolerance;
276    m_velocityTolerance = velocityTolerance;
277  }
278
279  /**
280   * Returns the difference between the setpoint and the measurement.
281   *
282   * @return The error.
283   */
284  public double getPositionError() {
285    return m_positionError;
286  }
287
288  /**
289   * Returns the velocity error.
290   *
291   * @return The velocity error.
292   */
293  public double getVelocityError() {
294    return m_velocityError;
295  }
296
297  /**
298   * Returns the next output of the PID controller.
299   *
300   * @param measurement The current measurement of the process variable.
301   * @param setpoint The setpoint to target
302   * @param currentTimestamp The current timestamp to use for calculating integral/derivative error
303   * @return The next controller output.
304   */
305  public double calculate(double measurement, double setpoint, double currentTimestamp) {
306    m_setpoint = setpoint;
307    m_haveSetpoint = true;
308    m_measurement = measurement;
309    m_haveMeasurement = true;
310    m_prevError = m_positionError;
311
312    double thisPeriod = currentTimestamp - m_lastTimestamp;
313    m_lastTimestamp = currentTimestamp;
314
315    if (m_continuous) {
316      double errorBound = (m_maximumInput - m_minimumInput) / 2.0;
317      m_positionError = MathUtil.inputModulus(m_setpoint - m_measurement, -errorBound, errorBound);
318    } else {
319      m_positionError = m_setpoint - m_measurement;
320    }
321
322    m_velocityError = (m_positionError - m_prevError) / thisPeriod;
323
324    // If the absolute value of the position error is greater than IZone, reset the total error
325    if (Math.abs(m_positionError) > m_iZone) {
326      m_totalError = 0;
327    } else if (m_ki != 0) {
328      m_totalError =
329          Math.clamp(
330              m_totalError + m_positionError * thisPeriod,
331              m_minimumIntegral / m_ki,
332              m_maximumIntegral / m_ki);
333    }
334
335    m_lastAppliedOutput = m_kp * m_positionError + m_ki * m_totalError + m_kd * m_velocityError;
336    return m_lastAppliedOutput;
337  }
338
339  /** Returns the last applied output from this PID controller. */
340  public double getLastAppliedOutput() {
341    return m_lastAppliedOutput;
342  }
343
344  /** Resets the previous error and the integral term. */
345  public void reset() {
346    m_positionError = 0;
347    m_prevError = 0;
348    m_totalError = 0;
349    m_velocityError = 0;
350    m_haveMeasurement = false;
351  }
352}