[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:
Fabian Wolter
2022-04-03 17:38:56 +02:00
committed by GitHub
parent 28ce7ebaed
commit 3520621c1b
5 changed files with 75 additions and 22 deletions

View File

@@ -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";

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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) //