package org.cowboycoders.pid;
import org.fluxoid.utils.TrapezoidIntegrator;
import java.util.HashSet;
import java.util.Set;
public class PidController implements PidParameterController {
private double previousError = 0;
private TrapezoidIntegrator integral = new TrapezoidIntegrator();
// last values
private double proportionalGain = 1;
private double integralGain = 0;
private double derivativeGain = 0;
private Double lastTimeStamp; //seconds
private Long startTimeOffset;
private double elapsedTime;
private ProcessVariableProvider processVariable;
private OutputController output;
private final Set<PidUpdateListener> listeners = new HashSet<PidUpdateListener>();
private GainController gainController;
public PidController(ProcessVariableProvider pv, OutputController out, GainController gc) {
this.processVariable = pv;
this.output = out;
this.gainController = gc;
}
/* (non-Javadoc)
* @see org.cowboycoders.pid.PidParameterController#getProportionalGain()
*/
@Override
public double getProportionalGain() {
return proportionalGain;
}
/* (non-Javadoc)
* @see org.cowboycoders.pid.PidParameterController#getIntegralGain()
*/
@Override
public double getIntegralGain() {
return integralGain;
}
/* (non-Javadoc)
* @see org.cowboycoders.pid.PidParameterController#getDerivativeGain()
*/
@Override
public double getDerivativeGain() {
return derivativeGain;
}
protected double getPreviousError() {
return previousError;
}
protected void setPreviousError(double previousError) {
this.previousError = previousError;
}
protected TrapezoidIntegrator getErrorIntegrator() {
return integral;
}
protected void setErrorIntegrator(TrapezoidIntegrator integral) {
this.integral = integral;
}
protected ProcessVariableProvider getProcessVariableProvider() {
return processVariable;
}
protected void setProcessVariableProvider(ProcessVariableProvider processVariable) {
this.processVariable = processVariable;
}
protected OutputController getOutputController() {
return output;
}
protected void setOutputController(OutputController output) {
this.output = output;
}
protected double getLastTimeStamp() {
return lastTimeStamp;
}
protected void setLastTimeStamp(double lastTimeStamp) {
this.lastTimeStamp = lastTimeStamp;
}
protected double getElapsedTime() {
return elapsedTime;
}
protected void setElapsedTime(double elapsedTime) {
this.elapsedTime = elapsedTime;
}
protected synchronized GainController getGainController() {
return gainController;
}
@Override
public synchronized boolean setGainController(GainController gainController) {
this.gainController = gainController;
return true;
}
protected void setProportionalGain(double proportionalGain) {
this.proportionalGain = proportionalGain;
}
protected void setIntegralGain(double integralGain) {
this.integralGain = integralGain;
}
protected void setDerivativeGain(double derivativeGain) {
this.derivativeGain = derivativeGain;
}
/**
* Calculate the control signal using a positional PID controller
*
* @param setpoint - target output, in this case power in Watts.
*/
public synchronized void adjustSetpoint(double setpoint) {
// Get the current power value (process variable)
double pv = getProcessVariableProvider().getProcessVariable();
// Calculate the difference between the current power value and
// the target power value (set point). This is the error term.
double error = setpoint - pv;
// Construct a timer (seconds)
if (startTimeOffset == null) {
startTimeOffset = System.nanoTime();
}
double timeStamp = (System.nanoTime() - startTimeOffset) / Math.pow(10, 9);
// Handle the initial case when no previous error exists
if (lastTimeStamp == null) {
lastTimeStamp = timeStamp;
setPreviousError(error);
getErrorIntegrator().add(timeStamp, error);
return;
}
OutputController outputController = getOutputController();
GainController gainController = getGainController();
// Calculate the time elapsed since the last update
double dt = timeStamp - lastTimeStamp;
// Calculate the derivative term from the previous error using
// the finite difference method
double previousError = getPreviousError();
double derivative = (error - previousError) / dt;
// Calculate the integral term
getErrorIntegrator().add(timeStamp, error);
double integral = getErrorIntegrator().getIntegral();
// Step the timer forward
double elapsedTime = getElapsedTime() + dt;
setElapsedTime(elapsedTime);
OutputControlParameters outputParameters = new OutputControlParameters(
elapsedTime, previousError, error, dt, integral, derivative, setpoint, pv
);
GainParameters gain = gainController.getGain(outputParameters);
// Get the PID coefficients
double Kp = gain.getProportionalGain();
double Ki = gain.getIntegralGain();
double Kd = gain.getDerivativeGain();
double output = Kp * error + Ki * integral + Kd * derivative;
// Update a reference to the previous values
setProportionalGain(Kp);
setDerivativeGain(Kd);
setIntegralGain(Ki);
setPreviousError(error);
// Make sure that the control signal is within set bounds
if (output > outputController.getMaxOutput()) output = outputController.getMaxOutput();
else if (output < outputController.getMinOutput()) output = outputController.getMinOutput();
synchronized (listeners) {
for (PidUpdateListener listener : listeners) {
listener.onPidUpdate(setpoint, pv, output, error);
}
}
outputController.setOutput(output);
}
/**
* {@link PidController#adjustSetpoint(double)} is supposed to polled regularly
* Should you wish to take a break, use this method before restarting.
*/
public synchronized void reset() {
startTimeOffset = null;
lastTimeStamp = null;
elapsedTime = 0;
setErrorIntegrator(new TrapezoidIntegrator());
setPreviousError(0);
}
/* (non-Javadoc)
* @see org.cowboycoders.pid.PidParameterController#registerPidUpdateLister(org.cowboycoders
* .pid.PidUpdateListener)
*/
@Override
public void registerPidUpdateLister(PidUpdateListener listener) {
synchronized (listeners) {
listeners.add(listener);
}
}
/* (non-Javadoc)
* @see org.cowboycoders.pid.PidParameterController#unregisterPidUpdateLister(org.cowboycoders.pid.PidUpdateListener)
*/
@Override
public void unregisterPidUpdateLister(PidUpdateListener listener) {
synchronized (listeners) {
listeners.remove(listener);
}
}
}