[pidcontroller] Add ability to limit the I-part (#12565)
* [pidcontroller] Add ability to limit the I-part * Apply iMinValue & iMaxValue to the integral result accumulator Signed-off-by: Fabian Wolter <github@fabian-wolter.de> * Set iMinResult, iMaxResult default value to NaN Co-authored-by: Lenno Nagel <lenno@nagel.ee>
This commit is contained in:
@@ -32,6 +32,8 @@ public class PIDControllerConstants {
|
||||
public static final String CONFIG_KI_GAIN = "ki";
|
||||
public static final String CONFIG_KD_GAIN = "kd";
|
||||
public static final String CONFIG_KD_TIMECONSTANT = "kdTimeConstant";
|
||||
public static final String CONFIG_I_MAX = "integralMaxValue";
|
||||
public static final String CONFIG_I_MIN = "integralMinValue";
|
||||
public static final String P_INSPECTOR = "pInspector";
|
||||
public static final String I_INSPECTOR = "iInspector";
|
||||
public static final String D_INSPECTOR = "dInspector";
|
||||
|
||||
@@ -34,12 +34,27 @@ class PIDController {
|
||||
private double ki;
|
||||
private double kd;
|
||||
private double derivativeTimeConstantSec;
|
||||
private double iMinResult;
|
||||
private double iMaxResult;
|
||||
|
||||
public PIDController(double kpAdjuster, double kiAdjuster, double kdAdjuster, double derivativeTimeConstantSec) {
|
||||
public PIDController(double kpAdjuster, double kiAdjuster, double kdAdjuster, double derivativeTimeConstantSec,
|
||||
double iMinValue, double iMaxValue) {
|
||||
this.kp = kpAdjuster;
|
||||
this.ki = kiAdjuster;
|
||||
this.kd = kdAdjuster;
|
||||
this.derivativeTimeConstantSec = derivativeTimeConstantSec;
|
||||
this.iMinResult = Double.NaN;
|
||||
this.iMaxResult = Double.NaN;
|
||||
|
||||
// prepare min/max for the integral result accumulator
|
||||
if (Double.isFinite(kiAdjuster) && Math.abs(kiAdjuster) > 0.0) {
|
||||
if (Double.isFinite(iMinValue)) {
|
||||
this.iMinResult = iMinValue / kiAdjuster;
|
||||
}
|
||||
if (Double.isFinite(iMaxValue)) {
|
||||
this.iMaxResult = iMaxValue / kiAdjuster;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PIDOutputDTO calculate(double input, double setpoint, long lastInvocationMs, int loopTimeMs) {
|
||||
@@ -55,11 +70,20 @@ class PIDController {
|
||||
|
||||
// integral calculation
|
||||
integralResult += error * lastInvocationMs / loopTimeMs;
|
||||
if (Double.isFinite(iMinResult)) {
|
||||
integralResult = Math.max(integralResult, iMinResult);
|
||||
}
|
||||
if (Double.isFinite(iMaxResult)) {
|
||||
integralResult = Math.min(integralResult, iMaxResult);
|
||||
}
|
||||
|
||||
// calculate parts
|
||||
final double proportionalPart = kp * error;
|
||||
final double integralPart = ki * integralResult;
|
||||
|
||||
double integralPart = ki * integralResult;
|
||||
|
||||
final double derivativePart = kd * derivativeResult;
|
||||
|
||||
output = proportionalPart + integralPart + derivativePart;
|
||||
|
||||
return new PIDOutputDTO(output, proportionalPart, integralPart, derivativePart, error);
|
||||
|
||||
@@ -16,7 +16,6 @@ import static org.openhab.automation.pidcontroller.internal.PIDControllerConstan
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -108,6 +107,8 @@ public class PIDControllerTriggerHandler extends BaseTriggerModuleHandler implem
|
||||
double kiAdjuster = getDoubleFromConfig(config, CONFIG_KI_GAIN);
|
||||
double kdAdjuster = getDoubleFromConfig(config, CONFIG_KD_GAIN);
|
||||
double kdTimeConstant = getDoubleFromConfig(config, CONFIG_KD_TIMECONSTANT);
|
||||
double iMinValue = getDoubleFromConfig(config, CONFIG_I_MIN);
|
||||
double iMaxValue = getDoubleFromConfig(config, CONFIG_I_MAX);
|
||||
pInspector = (String) config.get(P_INSPECTOR);
|
||||
iInspector = (String) config.get(I_INSPECTOR);
|
||||
dInspector = (String) config.get(D_INSPECTOR);
|
||||
@@ -116,7 +117,7 @@ public class PIDControllerTriggerHandler extends BaseTriggerModuleHandler implem
|
||||
loopTimeMs = ((BigDecimal) requireNonNull(config.get(CONFIG_LOOP_TIME), CONFIG_LOOP_TIME + " is not set"))
|
||||
.intValue();
|
||||
|
||||
controller = new PIDController(kpAdjuster, kiAdjuster, kdAdjuster, kdTimeConstant);
|
||||
controller = new PIDController(kpAdjuster, kiAdjuster, kdAdjuster, kdTimeConstant, iMinValue, iMaxValue);
|
||||
|
||||
eventFilter = event -> {
|
||||
String topic = event.getTopic();
|
||||
@@ -146,7 +147,13 @@ public class PIDControllerTriggerHandler extends BaseTriggerModuleHandler implem
|
||||
}
|
||||
|
||||
private double getDoubleFromConfig(Configuration config, String key) {
|
||||
return ((BigDecimal) Objects.requireNonNull(config.get(key), key + " is not set")).doubleValue();
|
||||
Object rawValue = config.get(key);
|
||||
|
||||
if (rawValue == null) {
|
||||
return Double.NaN;
|
||||
}
|
||||
|
||||
return ((BigDecimal) rawValue).doubleValue();
|
||||
}
|
||||
|
||||
private void calculate() {
|
||||
@@ -184,7 +191,8 @@ public class PIDControllerTriggerHandler extends BaseTriggerModuleHandler implem
|
||||
if (itemName != null) {
|
||||
try {
|
||||
itemRegistry.getItem(itemName);
|
||||
eventPublisher.post(ItemEventFactory.createCommandEvent(itemName, new DecimalType(value)));
|
||||
eventPublisher.post(ItemEventFactory.createStateEvent(itemName,
|
||||
Double.isFinite(value) ? new DecimalType(value) : UnDefType.UNDEF));
|
||||
} catch (ItemNotFoundException e) {
|
||||
logger.warn("Item doesn't exist: {}", itemName);
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ public class PIDControllerTriggerType extends TriggerType {
|
||||
.withMinimum(BigDecimal.ZERO) //
|
||||
.withDefault("1.0") //
|
||||
.withLabel("Derivative Time Constant") //
|
||||
.withDescription("Slows the rate of change of the D part (T1) in seconds.") //
|
||||
.withDescription("Slows the rate of change of the D-part (T1) in seconds.") //
|
||||
.withUnit("s") //
|
||||
.build());
|
||||
configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_LOOP_TIME, Type.DECIMAL) //
|
||||
@@ -94,6 +94,18 @@ public class PIDControllerTriggerType extends TriggerType {
|
||||
.withDescription("The interval the output value is updated in ms") //
|
||||
.withUnit("ms") //
|
||||
.build());
|
||||
configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_I_MIN, Type.DECIMAL) //
|
||||
.withRequired(false) //
|
||||
.withMultiple(false) //
|
||||
.withLabel("I-part Lower Limit") //
|
||||
.withDescription("The I-part will be min this value. Can be left empty for no limit.") //
|
||||
.build());
|
||||
configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_I_MAX, Type.DECIMAL) //
|
||||
.withRequired(false) //
|
||||
.withMultiple(false) //
|
||||
.withLabel("I-part Upper Limit") //
|
||||
.withDescription("The I-part will be max this value. Can be left empty for no limit.") //
|
||||
.build());
|
||||
configDescriptions.add(ConfigDescriptionParameterBuilder.create(P_INSPECTOR, Type.TEXT) //
|
||||
.withRequired(false) //
|
||||
.withMultiple(false) //
|
||||
|
||||
Reference in New Issue
Block a user