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