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.MathSharedStore;
008import edu.wpi.first.math.MathUsageId;
009import edu.wpi.first.math.MathUtil;
010import edu.wpi.first.math.trajectory.TrapezoidProfile;
011import edu.wpi.first.util.sendable.Sendable;
012import edu.wpi.first.util.sendable.SendableBuilder;
013import edu.wpi.first.util.sendable.SendableRegistry;
014
015/**
016 * Implements a PID control loop whose setpoint is constrained by a trapezoid profile. Users should
017 * call reset() when they first start running the controller to avoid unwanted behavior.
018 */
019public class ProfiledPIDController implements Sendable {
020  private static int instances;
021
022  private PIDController m_controller;
023  private double m_minimumInput;
024  private double m_maximumInput;
025  private TrapezoidProfile.State m_goal = new TrapezoidProfile.State();
026  private TrapezoidProfile.State m_setpoint = new TrapezoidProfile.State();
027  private TrapezoidProfile.Constraints m_constraints;
028
029  /**
030   * Allocates a ProfiledPIDController with the given constants for Kp, Ki, and Kd.
031   *
032   * @param Kp The proportional coefficient.
033   * @param Ki The integral coefficient.
034   * @param Kd The derivative coefficient.
035   * @param constraints Velocity and acceleration constraints for goal.
036   */
037  public ProfiledPIDController(
038      double Kp, double Ki, double Kd, TrapezoidProfile.Constraints constraints) {
039    this(Kp, Ki, Kd, constraints, 0.02);
040  }
041
042  /**
043   * Allocates a ProfiledPIDController with the given constants for Kp, Ki, and Kd.
044   *
045   * @param Kp The proportional coefficient.
046   * @param Ki The integral coefficient.
047   * @param Kd The derivative coefficient.
048   * @param constraints Velocity and acceleration constraints for goal.
049   * @param period The period between controller updates in seconds. The default is 0.02 seconds.
050   */
051  public ProfiledPIDController(
052      double Kp, double Ki, double Kd, TrapezoidProfile.Constraints constraints, double period) {
053    m_controller = new PIDController(Kp, Ki, Kd, period);
054    m_constraints = constraints;
055    instances++;
056
057    SendableRegistry.add(this, "ProfiledPIDController", instances);
058    MathSharedStore.reportUsage(MathUsageId.kController_ProfiledPIDController, instances);
059  }
060
061  /**
062   * Sets the PID Controller gain parameters.
063   *
064   * <p>Sets the proportional, integral, and differential coefficients.
065   *
066   * @param Kp Proportional coefficient
067   * @param Ki Integral coefficient
068   * @param Kd Differential coefficient
069   */
070  public void setPID(double Kp, double Ki, double Kd) {
071    m_controller.setPID(Kp, Ki, Kd);
072  }
073
074  /**
075   * Sets the proportional coefficient of the PID controller gain.
076   *
077   * @param Kp proportional coefficient
078   */
079  public void setP(double Kp) {
080    m_controller.setP(Kp);
081  }
082
083  /**
084   * Sets the integral coefficient of the PID controller gain.
085   *
086   * @param Ki integral coefficient
087   */
088  public void setI(double Ki) {
089    m_controller.setI(Ki);
090  }
091
092  /**
093   * Sets the differential coefficient of the PID controller gain.
094   *
095   * @param Kd differential coefficient
096   */
097  public void setD(double Kd) {
098    m_controller.setD(Kd);
099  }
100
101  /**
102   * Sets the IZone range. When the absolute value of the position error is greater than IZone, the
103   * total accumulated error will reset to zero, disabling integral gain until the absolute value of
104   * the position error is less than IZone. This is used to prevent integral windup. Must be
105   * non-negative. Passing a value of zero will effectively disable integral gain. Passing a value
106   * of {@link Double#POSITIVE_INFINITY} disables IZone functionality.
107   *
108   * @param iZone Maximum magnitude of error to allow integral control.
109   */
110  public void setIZone(double iZone) {
111    m_controller.setIZone(iZone);
112  }
113
114  /**
115   * Gets the proportional coefficient.
116   *
117   * @return proportional coefficient
118   */
119  public double getP() {
120    return m_controller.getP();
121  }
122
123  /**
124   * Gets the integral coefficient.
125   *
126   * @return integral coefficient
127   */
128  public double getI() {
129    return m_controller.getI();
130  }
131
132  /**
133   * Gets the differential coefficient.
134   *
135   * @return differential coefficient
136   */
137  public double getD() {
138    return m_controller.getD();
139  }
140
141  /**
142   * Get the IZone range.
143   *
144   * @return Maximum magnitude of error to allow integral control.
145   */
146  public double getIZone() {
147    return m_controller.getIZone();
148  }
149
150  /**
151   * Gets the period of this controller.
152   *
153   * @return The period of the controller.
154   */
155  public double getPeriod() {
156    return m_controller.getPeriod();
157  }
158
159  /**
160   * Returns the position tolerance of this controller.
161   *
162   * @return the position tolerance of the controller.
163   */
164  public double getPositionTolerance() {
165    return m_controller.getPositionTolerance();
166  }
167
168  /**
169   * Returns the velocity tolerance of this controller.
170   *
171   * @return the velocity tolerance of the controller.
172   */
173  public double getVelocityTolerance() {
174    return m_controller.getVelocityTolerance();
175  }
176
177  /**
178   * Sets the goal for the ProfiledPIDController.
179   *
180   * @param goal The desired goal state.
181   */
182  public void setGoal(TrapezoidProfile.State goal) {
183    m_goal = goal;
184  }
185
186  /**
187   * Sets the goal for the ProfiledPIDController.
188   *
189   * @param goal The desired goal position.
190   */
191  public void setGoal(double goal) {
192    m_goal = new TrapezoidProfile.State(goal, 0);
193  }
194
195  /**
196   * Gets the goal for the ProfiledPIDController.
197   *
198   * @return The goal.
199   */
200  public TrapezoidProfile.State getGoal() {
201    return m_goal;
202  }
203
204  /**
205   * Returns true if the error is within the tolerance of the error.
206   *
207   * <p>This will return false until at least one input value has been computed.
208   *
209   * @return True if the error is within the tolerance of the error.
210   */
211  public boolean atGoal() {
212    return atSetpoint() && m_goal.equals(m_setpoint);
213  }
214
215  /**
216   * Set velocity and acceleration constraints for goal.
217   *
218   * @param constraints Velocity and acceleration constraints for goal.
219   */
220  public void setConstraints(TrapezoidProfile.Constraints constraints) {
221    m_constraints = constraints;
222  }
223
224  /**
225   * Get the velocity and acceleration constraints for this controller.
226   *
227   * @return Velocity and acceleration constraints.
228   */
229  public TrapezoidProfile.Constraints getConstraints() {
230    return m_constraints;
231  }
232
233  /**
234   * Returns the current setpoint of the ProfiledPIDController.
235   *
236   * @return The current setpoint.
237   */
238  public TrapezoidProfile.State getSetpoint() {
239    return m_setpoint;
240  }
241
242  /**
243   * Returns true if the error is within the tolerance of the error.
244   *
245   * <p>This will return false until at least one input value has been computed.
246   *
247   * @return True if the error is within the tolerance of the error.
248   */
249  public boolean atSetpoint() {
250    return m_controller.atSetpoint();
251  }
252
253  /**
254   * Enables continuous input.
255   *
256   * <p>Rather then using the max and min input range as constraints, it considers them to be the
257   * same point and automatically calculates the shortest route to the setpoint.
258   *
259   * @param minimumInput The minimum value expected from the input.
260   * @param maximumInput The maximum value expected from the input.
261   */
262  public void enableContinuousInput(double minimumInput, double maximumInput) {
263    m_controller.enableContinuousInput(minimumInput, maximumInput);
264    m_minimumInput = minimumInput;
265    m_maximumInput = maximumInput;
266  }
267
268  /** Disables continuous input. */
269  public void disableContinuousInput() {
270    m_controller.disableContinuousInput();
271  }
272
273  /**
274   * Sets the minimum and maximum values for the integrator.
275   *
276   * <p>When the cap is reached, the integrator value is added to the controller output rather than
277   * the integrator value times the integral gain.
278   *
279   * @param minimumIntegral The minimum value of the integrator.
280   * @param maximumIntegral The maximum value of the integrator.
281   */
282  public void setIntegratorRange(double minimumIntegral, double maximumIntegral) {
283    m_controller.setIntegratorRange(minimumIntegral, maximumIntegral);
284  }
285
286  /**
287   * Sets the error which is considered tolerable for use with atSetpoint().
288   *
289   * @param positionTolerance Position error which is tolerable.
290   */
291  public void setTolerance(double positionTolerance) {
292    setTolerance(positionTolerance, Double.POSITIVE_INFINITY);
293  }
294
295  /**
296   * Sets the error which is considered tolerable for use with atSetpoint().
297   *
298   * @param positionTolerance Position error which is tolerable.
299   * @param velocityTolerance Velocity error which is tolerable.
300   */
301  public void setTolerance(double positionTolerance, double velocityTolerance) {
302    m_controller.setTolerance(positionTolerance, velocityTolerance);
303  }
304
305  /**
306   * Returns the difference between the setpoint and the measurement.
307   *
308   * @return The error.
309   */
310  public double getPositionError() {
311    return m_controller.getPositionError();
312  }
313
314  /**
315   * Returns the change in error per second.
316   *
317   * @return The change in error per second.
318   */
319  public double getVelocityError() {
320    return m_controller.getVelocityError();
321  }
322
323  /**
324   * Returns the next output of the PID controller.
325   *
326   * @param measurement The current measurement of the process variable.
327   * @return The controller's next output.
328   */
329  public double calculate(double measurement) {
330    if (m_controller.isContinuousInputEnabled()) {
331      // Get error which is the smallest distance between goal and measurement
332      double errorBound = (m_maximumInput - m_minimumInput) / 2.0;
333      double goalMinDistance =
334          MathUtil.inputModulus(m_goal.position - measurement, -errorBound, errorBound);
335      double setpointMinDistance =
336          MathUtil.inputModulus(m_setpoint.position - measurement, -errorBound, errorBound);
337
338      // Recompute the profile goal with the smallest error, thus giving the shortest path. The goal
339      // may be outside the input range after this operation, but that's OK because the controller
340      // will still go there and report an error of zero. In other words, the setpoint only needs to
341      // be offset from the measurement by the input range modulus; they don't need to be equal.
342      m_goal.position = goalMinDistance + measurement;
343      m_setpoint.position = setpointMinDistance + measurement;
344    }
345
346    var profile = new TrapezoidProfile(m_constraints, m_goal, m_setpoint);
347    m_setpoint = profile.calculate(getPeriod());
348    return m_controller.calculate(measurement, m_setpoint.position);
349  }
350
351  /**
352   * Returns the next output of the PID controller.
353   *
354   * @param measurement The current measurement of the process variable.
355   * @param goal The new goal of the controller.
356   * @return The controller's next output.
357   */
358  public double calculate(double measurement, TrapezoidProfile.State goal) {
359    setGoal(goal);
360    return calculate(measurement);
361  }
362
363  /**
364   * Returns the next output of the PIDController.
365   *
366   * @param measurement The current measurement of the process variable.
367   * @param goal The new goal of the controller.
368   * @return The controller's next output.
369   */
370  public double calculate(double measurement, double goal) {
371    setGoal(goal);
372    return calculate(measurement);
373  }
374
375  /**
376   * Returns the next output of the PID controller.
377   *
378   * @param measurement The current measurement of the process variable.
379   * @param goal The new goal of the controller.
380   * @param constraints Velocity and acceleration constraints for goal.
381   * @return The controller's next output.
382   */
383  public double calculate(
384      double measurement, TrapezoidProfile.State goal, TrapezoidProfile.Constraints constraints) {
385    setConstraints(constraints);
386    return calculate(measurement, goal);
387  }
388
389  /**
390   * Reset the previous error and the integral term.
391   *
392   * @param measurement The current measured State of the system.
393   */
394  public void reset(TrapezoidProfile.State measurement) {
395    m_controller.reset();
396    m_setpoint = measurement;
397  }
398
399  /**
400   * Reset the previous error and the integral term.
401   *
402   * @param measuredPosition The current measured position of the system.
403   * @param measuredVelocity The current measured velocity of the system.
404   */
405  public void reset(double measuredPosition, double measuredVelocity) {
406    reset(new TrapezoidProfile.State(measuredPosition, measuredVelocity));
407  }
408
409  /**
410   * Reset the previous error and the integral term.
411   *
412   * @param measuredPosition The current measured position of the system. The velocity is assumed to
413   *     be zero.
414   */
415  public void reset(double measuredPosition) {
416    reset(measuredPosition, 0.0);
417  }
418
419  @Override
420  public void initSendable(SendableBuilder builder) {
421    builder.setSmartDashboardType("ProfiledPIDController");
422    builder.addDoubleProperty("p", this::getP, this::setP);
423    builder.addDoubleProperty("i", this::getI, this::setI);
424    builder.addDoubleProperty("d", this::getD, this::setD);
425    builder.addDoubleProperty("izone", this::getIZone, this::setIZone);
426    builder.addDoubleProperty("goal", () -> getGoal().position, this::setGoal);
427  }
428}