001/*----------------------------------------------------------------------------*/
002/* Copyright (c) FIRST 2008-2012. All Rights Reserved.                        */
003/* Open Source Software - may be modified and shared by FRC teams. The code   */
004/* must be accompanied by the FIRST BSD license file in the root directory of */
005/* the project.                                                               */
006/*----------------------------------------------------------------------------*/
007package edu.wpi.first.wpilibj;
008
009import java.util.TimerTask;
010
011import edu.wpi.first.wpilibj.livewindow.LiveWindowSendable;
012import edu.wpi.first.wpilibj.tables.ITable;
013import edu.wpi.first.wpilibj.tables.ITableListener;
014import edu.wpi.first.wpilibj.util.BoundaryException;
015
016/**
017 * Class implements a PID Control Loop.
018 *
019 * Creates a separate thread which reads the given PIDSource and takes
020 * care of the integral calculations, as well as writing the given
021 * PIDOutput
022 */
023public class PIDController implements LiveWindowSendable, Controller {
024
025    public static final double kDefaultPeriod = .05;
026    private static int instances = 0;
027    private double m_P;     // factor for "proportional" control
028    private double m_I;     // factor for "integral" control
029    private double m_D;     // factor for "derivative" control
030    private double m_F;                 // factor for feedforward term
031    private double m_maximumOutput = 1.0; // |maximum output|
032    private double m_minimumOutput = -1.0;  // |minimum output|
033    private double m_maximumInput = 0.0;    // maximum input - limit setpoint to this
034    private double m_minimumInput = 0.0;    // minimum input - limit setpoint to this
035    private boolean m_continuous = false; // do the endpoints wrap around? eg. Absolute encoder
036    private boolean m_enabled = false;    //is the pid controller enabled
037    private double m_prevError = 0.0; // the prior sensor input (used to compute velocity)
038    private double m_totalError = 0.0; //the sum of the errors for use in the integral calc
039    private Tolerance m_tolerance;  //the tolerance object used to check if on target
040    private double m_setpoint = 0.0;
041    private double m_error = 0.0;
042    private double m_result = 0.0;
043    private double m_period = kDefaultPeriod;
044    PIDSource m_pidInput;
045    PIDOutput m_pidOutput;
046    java.util.Timer m_controlLoop;
047    private boolean m_freed = false;
048    private boolean m_usingPercentTolerance;
049
050    /**
051     * Tolerance is the type of tolerance used to specify if the PID controller is on target.
052     * The various implementations of this class such as PercentageTolerance and AbsoluteTolerance
053     * specify types of tolerance specifications to use.
054     */
055    public interface Tolerance {
056        public boolean onTarget();
057    }
058
059    public class PercentageTolerance implements Tolerance {
060        double percentage;
061
062        PercentageTolerance(double value) {
063            percentage = value;
064        }
065
066        @Override
067    public boolean onTarget() {
068            return (Math.abs(getError()) < percentage / 100
069                    * (m_maximumInput - m_minimumInput));
070        }
071    }
072
073    public class AbsoluteTolerance implements Tolerance {
074        double value;
075
076        AbsoluteTolerance(double value) {
077            this.value = value;
078        }
079
080        @Override
081    public boolean onTarget() {
082            return Math.abs(getError()) < value;
083        }
084    }
085
086    public class NullTolerance implements Tolerance {
087
088        @Override
089    public boolean onTarget() {
090            throw new RuntimeException("No tolerance value set when using PIDController.onTarget()");
091        }
092    }
093
094    private class PIDTask extends TimerTask {
095
096        private PIDController m_controller;
097
098        public PIDTask(PIDController controller) {
099            if (controller == null) {
100                throw new NullPointerException("Given PIDController was null");
101            }
102            m_controller = controller;
103        }
104
105        @Override
106    public void run() {
107            m_controller.calculate();
108        }
109    }
110
111    /**
112     * Allocate a PID object with the given constants for P, I, D, and F
113     * @param Kp the proportional coefficient
114     * @param Ki the integral coefficient
115     * @param Kd the derivative coefficient
116     * @param Kf the feed forward term
117     * @param source The PIDSource object that is used to get values
118     * @param output The PIDOutput object that is set to the output percentage
119     * @param period the loop time for doing calculations. This particularly effects calculations of the
120     * integral and differential terms. The default is 50ms.
121     */
122    public PIDController(double Kp, double Ki, double Kd, double Kf,
123                         PIDSource source, PIDOutput output,
124                         double period) {
125
126        if (source == null) {
127            throw new NullPointerException("Null PIDSource was given");
128        }
129        if (output == null) {
130            throw new NullPointerException("Null PIDOutput was given");
131        }
132
133        m_controlLoop = new java.util.Timer();
134
135
136        m_P = Kp;
137        m_I = Ki;
138        m_D = Kd;
139        m_F = Kf;
140
141        m_pidInput = source;
142        m_pidOutput = output;
143        m_period = period;
144
145        m_controlLoop.schedule(new PIDTask(this), 0L, (long) (m_period * 1000));
146
147        instances++;
148        HLUsageReporting.reportPIDController(instances);
149        m_tolerance = new NullTolerance();
150    }
151
152    /**
153     * Allocate a PID object with the given constants for P, I, D and period
154     * @param Kp the proportional coefficient
155     * @param Ki the integral coefficient
156     * @param Kd the derivative coefficient
157     * @param source the PIDSource object that is used to get values
158     * @param output the PIDOutput object that is set to the output percentage
159     * @param period the loop time for doing calculations. This particularly effects calculations of the
160     * integral and differential terms. The default is 50ms.
161     */
162    public PIDController(double Kp, double Ki, double Kd,
163                         PIDSource source, PIDOutput output,
164                         double period) {
165        this(Kp, Ki, Kd, 0.0, source, output, period);
166    }
167
168    /**
169     * Allocate a PID object with the given constants for P, I, D, using a 50ms period.
170     * @param Kp the proportional coefficient
171     * @param Ki the integral coefficient
172     * @param Kd the derivative coefficient
173     * @param source The PIDSource object that is used to get values
174     * @param output The PIDOutput object that is set to the output percentage
175     */
176    public PIDController(double Kp, double Ki, double Kd,
177                         PIDSource source, PIDOutput output) {
178        this(Kp, Ki, Kd, source, output, kDefaultPeriod);
179    }
180
181    /**
182     * Allocate a PID object with the given constants for P, I, D, using a 50ms period.
183     * @param Kp the proportional coefficient
184     * @param Ki the integral coefficient
185     * @param Kd the derivative coefficient
186     * @param Kf the feed forward term
187     * @param source The PIDSource object that is used to get values
188     * @param output The PIDOutput object that is set to the output percentage
189     */
190    public PIDController(double Kp, double Ki, double Kd, double Kf,
191                         PIDSource source, PIDOutput output) {
192        this(Kp, Ki, Kd, Kf, source, output, kDefaultPeriod);
193    }
194
195    /**
196     * Free the PID object
197     */
198    public void free() {
199      m_controlLoop.cancel();
200      synchronized (this) {
201        m_freed = true;
202        m_pidOutput = null;
203        m_pidInput = null;
204        m_controlLoop = null;
205      }
206      if(this.table!=null) table.removeTableListener(listener);
207    }
208
209    /**
210     * Read the input, calculate the output accordingly, and write to the output.
211     * This should only be called by the PIDTask
212     * and is created during initialization.
213     */
214    private void calculate() {
215        boolean enabled;
216        PIDSource pidInput;
217
218        synchronized (this) {
219            if (m_pidInput == null) {
220                return;
221            }
222            if (m_pidOutput == null) {
223                return;
224            }
225            enabled = m_enabled; // take snapshot of these values...
226            pidInput = m_pidInput;
227        }
228
229        if (enabled) {
230          double input;
231            double result;
232            PIDOutput pidOutput = null;
233            synchronized (this){
234              input = pidInput.pidGet();
235            }
236            synchronized (this) {
237                m_error = m_setpoint - input;
238                if (m_continuous) {
239                    if (Math.abs(m_error)
240                            > (m_maximumInput - m_minimumInput) / 2) {
241                        if (m_error > 0) {
242                            m_error = m_error - m_maximumInput + m_minimumInput;
243                        } else {
244                            m_error = m_error
245                                      + m_maximumInput - m_minimumInput;
246                        }
247                    }
248                }
249
250                if (m_I != 0) {
251                    double potentialIGain = (m_totalError + m_error) * m_I;
252                    if (potentialIGain < m_maximumOutput) {
253                        if (potentialIGain > m_minimumOutput) {
254                            m_totalError += m_error;
255                        } else {
256                            m_totalError = m_minimumOutput / m_I;
257                        }
258                    } else {
259                        m_totalError = m_maximumOutput / m_I;
260                    }
261                }
262
263                m_result = m_P * m_error + m_I * m_totalError + m_D * (m_error - m_prevError) + m_setpoint * m_F;
264                m_prevError = m_error;
265
266                if (m_result > m_maximumOutput) {
267                    m_result = m_maximumOutput;
268                } else if (m_result < m_minimumOutput) {
269                    m_result = m_minimumOutput;
270                }
271                pidOutput = m_pidOutput;
272                result = m_result;
273            }
274
275            pidOutput.pidWrite(result);
276        }
277    }
278
279    /**
280     * Set the PID Controller gain parameters.
281     * Set the proportional, integral, and differential coefficients.
282     * @param p Proportional coefficient
283     * @param i Integral coefficient
284     * @param d Differential coefficient
285     */
286    public synchronized void setPID(double p, double i, double d) {
287        m_P = p;
288        m_I = i;
289        m_D = d;
290
291        if (table != null) {
292            table.putNumber("p", p);
293            table.putNumber("i", i);
294            table.putNumber("d", d);
295        }
296    }
297
298    /**
299    * Set the PID Controller gain parameters.
300    * Set the proportional, integral, and differential coefficients.
301    * @param p Proportional coefficient
302    * @param i Integral coefficient
303    * @param d Differential coefficient
304    * @param f Feed forward coefficient
305    */
306    public synchronized void setPID(double p, double i, double d, double f) {
307        m_P = p;
308        m_I = i;
309        m_D = d;
310        m_F = f;
311
312        if (table != null) {
313            table.putNumber("p", p);
314            table.putNumber("i", i);
315            table.putNumber("d", d);
316            table.putNumber("f", f);
317        }
318    }
319
320    /**
321     * Get the Proportional coefficient
322     * @return proportional coefficient
323     */
324    public synchronized double getP() {
325        return m_P;
326    }
327
328    /**
329     * Get the Integral coefficient
330     * @return integral coefficient
331     */
332    public synchronized double getI() {
333        return m_I;
334    }
335
336    /**
337     * Get the Differential coefficient
338     * @return differential coefficient
339     */
340    public synchronized double getD() {
341        return m_D;
342    }
343
344    /**
345     * Get the Feed forward coefficient
346     * @return feed forward coefficient
347     */
348    public synchronized double getF() {
349        return m_F;
350    }
351
352    /**
353     * Return the current PID result
354     * This is always centered on zero and constrained the the max and min outs
355     * @return the latest calculated output
356     */
357    public synchronized double get() {
358        return m_result;
359    }
360
361    /**
362     *  Set the PID controller to consider the input to be continuous,
363     *  Rather then using the max and min in as constraints, it considers them to
364     *  be the same point and automatically calculates the shortest route to
365     *  the setpoint.
366     * @param continuous Set to true turns on continuous, false turns off continuous
367     */
368    public synchronized void setContinuous(boolean continuous) {
369        m_continuous = continuous;
370    }
371
372    /**
373     *  Set the PID controller to consider the input to be continuous,
374     *  Rather then using the max and min in as constraints, it considers them to
375     *  be the same point and automatically calculates the shortest route to
376     *  the setpoint.
377     */
378    public synchronized void setContinuous() {
379        this.setContinuous(true);
380    }
381
382    /**
383     * Sets the maximum and minimum values expected from the input and setpoint.
384     *
385     * @param minimumInput the minimum value expected from the input
386     * @param maximumInput the maximum value expected from the input
387     */
388    public synchronized void setInputRange(double minimumInput, double maximumInput) {
389        if (minimumInput > maximumInput) {
390            throw new BoundaryException("Lower bound is greater than upper bound");
391        }
392        m_minimumInput = minimumInput;
393        m_maximumInput = maximumInput;
394        setSetpoint(m_setpoint);
395    }
396
397    /**
398     * Sets the minimum and maximum values to write.
399     *
400     * @param minimumOutput the minimum percentage to write to the output
401     * @param maximumOutput the maximum percentage to write to the output
402     */
403    public synchronized void setOutputRange(double minimumOutput, double maximumOutput) {
404        if (minimumOutput > maximumOutput) {
405            throw new BoundaryException("Lower bound is greater than upper bound");
406        }
407        m_minimumOutput = minimumOutput;
408        m_maximumOutput = maximumOutput;
409    }
410
411    /**
412     * Set the setpoint for the PIDController
413     * @param setpoint the desired setpoint
414     */
415    public synchronized void setSetpoint(double setpoint) {
416        if (m_maximumInput > m_minimumInput) {
417            if (setpoint > m_maximumInput) {
418                m_setpoint = m_maximumInput;
419            } else if (setpoint < m_minimumInput) {
420                m_setpoint = m_minimumInput;
421            } else {
422                m_setpoint = setpoint;
423            }
424        } else {
425            m_setpoint = setpoint;
426        }
427
428        if (table != null)
429            table.putNumber("setpoint", m_setpoint);
430    }
431
432    /**
433     * Returns the current setpoint of the PIDController
434     * @return the current setpoint
435     */
436    public synchronized double getSetpoint() {
437        return m_setpoint;
438    }
439
440    /**
441     * Returns the current difference of the input from the setpoint
442     * @return the current error
443     */
444    public synchronized double getError() {
445        //return m_error;
446        return getSetpoint() - m_pidInput.pidGet();
447    }
448
449    /**
450     * Set the percentage error which is considered tolerable for use with
451     * OnTarget. (Input of 15.0 = 15 percent)
452     * @param percent error which is tolerable
453     * @deprecated Use {@link #setPercentTolerance(double)} or {@link #setAbsoluteTolerance(double)} instead.
454     */
455    @Deprecated
456  public synchronized void setTolerance(double percent) {
457        m_tolerance = new PercentageTolerance(percent);
458    }
459
460    /** Set the PID tolerance using a Tolerance object.
461     * Tolerance can be specified as a percentage of the range or as an absolute
462     * value. The Tolerance object encapsulates those options in an object. Use it by
463     * creating the type of tolerance that you want to use: setTolerance(new PIDController.AbsoluteTolerance(0.1))
464     * @param tolerance a tolerance object of the right type, e.g. PercentTolerance
465     * or AbsoluteTolerance
466     */
467    private synchronized void setTolerance(Tolerance tolerance) {
468        m_tolerance = tolerance;
469    }
470
471    /**
472     * Set the absolute error which is considered tolerable for use with
473     * OnTarget.
474     * @param absvalue absolute error which is tolerable in the units of the input object
475     */
476    public synchronized void setAbsoluteTolerance(double absvalue) {
477        m_tolerance = new AbsoluteTolerance(absvalue);
478    }
479
480    /**
481     * Set the percentage error which is considered tolerable for use with
482     * OnTarget. (Input of 15.0 = 15 percent)
483     * @param percentage percent error which is tolerable
484     */
485    public synchronized void setPercentTolerance(double percentage) {
486        m_tolerance = new PercentageTolerance(percentage);
487    }
488
489    /**
490     * Return true if the error is within the percentage of the total input range,
491     * determined by setTolerance. This assumes that the maximum and minimum input
492     * were set using setInput.
493     * @return true if the error is less than the tolerance
494     */
495    public synchronized boolean onTarget() {
496        return m_tolerance.onTarget();
497    }
498
499    /**
500     * Begin running the PIDController
501     */
502    @Override
503  public synchronized void enable() {
504        m_enabled = true;
505
506        if (table != null) {
507            table.putBoolean("enabled", true);
508        }
509    }
510
511    /**
512     * Stop running the PIDController, this sets the output to zero before stopping.
513     */
514    @Override
515    public synchronized void disable() {
516        m_pidOutput.pidWrite(0);
517        m_enabled = false;
518
519        if (table != null) {
520            table.putBoolean("enabled", false);
521        }
522    }
523
524    /**
525     * Return true if PIDController is enabled.
526     */
527    public synchronized boolean isEnable() {
528        return m_enabled;
529    }
530
531    /**
532     * Reset the previous error,, the integral term, and disable the controller.
533     */
534    public synchronized void reset() {
535        disable();
536        m_prevError = 0;
537        m_totalError = 0;
538        m_result = 0;
539    }
540
541    @Override
542  public String getSmartDashboardType() {
543        return "PIDController";
544    }
545
546    private final ITableListener listener = new ITableListener() {
547        @Override
548    public void valueChanged(ITable table, String key, Object value, boolean isNew) {
549            if (key.equals("p") || key.equals("i") || key.equals("d") || key.equals("f")) {
550                if (getP() != table.getNumber("p", 0.0) || getI() != table.getNumber("i", 0.0) ||
551                        getD() != table.getNumber("d", 0.0) || getF() != table.getNumber("f", 0.0))
552                    setPID(table.getNumber("p", 0.0), table.getNumber("i", 0.0), table.getNumber("d", 0.0), table.getNumber("f", 0.0));
553            } else if (key.equals("setpoint")) {
554                if (getSetpoint() != ((Double) value).doubleValue())
555                    setSetpoint(((Double) value).doubleValue());
556            } else if (key.equals("enabled")) {
557                if (isEnable() != ((Boolean) value).booleanValue()) {
558                    if (((Boolean) value).booleanValue()) {
559                        enable();
560                    } else {
561                        disable();
562                    }
563                }
564            }
565        }
566    };
567    private ITable table;
568    @Override
569  public void initTable(ITable table) {
570        if(this.table!=null)
571            this.table.removeTableListener(listener);
572        this.table = table;
573        if(table!=null) {
574            table.putNumber("p", getP());
575            table.putNumber("i", getI());
576            table.putNumber("d", getD());
577            table.putNumber("f", getF());
578            table.putNumber("setpoint", getSetpoint());
579            table.putBoolean("enabled", isEnable());
580            table.addTableListener(listener, false);
581        }
582    }
583
584    /**
585     * {@inheritDoc}
586     */
587    @Override
588  public ITable getTable() {
589        return table;
590    }
591
592    /**
593     * {@inheritDoc}
594     */
595    @Override
596  public void updateTable() {
597    }
598
599    /**
600     * {@inheritDoc}
601     */
602    @Override
603  public void startLiveWindowMode() {
604        disable();
605    }
606
607    /**
608     * {@inheritDoc}
609     */
610    @Override
611  public void stopLiveWindowMode() {
612    }
613}