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