[pidcontroller] Implement previous state recovery on startup (#13003)
* [pidcontroller] Implement previous state recovery on startup This feature allows the PID controller parameters to be updated and OpenHAB to be restarted without losing the current controller state. This is especially important for systems with a long response time. For example it might take up to a day for an underfloor heating controller to stabilise after losing state and having to build up the integrator value from zero. Signed-off-by: Lenno Nagel <lenno@nagel.ee> * Change logger.info -> logger.debug Signed-off-by: Lenno Nagel <lenno@nagel.ee> * Change debug Item -> inspector Item in README Signed-off-by: Lenno Nagel <lenno@nagel.ee> * Add documentation regarding state persistence Signed-off-by: Lenno Nagel <lenno@nagel.ee> * Update bundles/org.openhab.automation.pidcontroller/README.md Signed-off-by: Fabian Wolter <github@fabian-wolter.de> * Update bundles/org.openhab.automation.pidcontroller/README.md Signed-off-by: Fabian Wolter <github@fabian-wolter.de> Co-authored-by: Fabian Wolter <github@fabian-wolter.de>
This commit is contained in:
@@ -28,7 +28,6 @@ class PIDController {
|
||||
private double integralResult;
|
||||
private double derivativeResult;
|
||||
private double previousError;
|
||||
private double output;
|
||||
|
||||
private double kp;
|
||||
private double ki;
|
||||
@@ -38,7 +37,8 @@ class PIDController {
|
||||
private double iMaxResult;
|
||||
|
||||
public PIDController(double kpAdjuster, double kiAdjuster, double kdAdjuster, double derivativeTimeConstantSec,
|
||||
double iMinValue, double iMaxValue) {
|
||||
double iMinValue, double iMaxValue, double previousIntegralPart, double previousDerivativePart,
|
||||
double previousError) {
|
||||
this.kp = kpAdjuster;
|
||||
this.ki = kiAdjuster;
|
||||
this.kd = kdAdjuster;
|
||||
@@ -46,7 +46,7 @@ class PIDController {
|
||||
this.iMinResult = Double.NaN;
|
||||
this.iMaxResult = Double.NaN;
|
||||
|
||||
// prepare min/max for the integral result accumulator
|
||||
// prepare min/max, restore previous state for the integral result accumulator
|
||||
if (Double.isFinite(kiAdjuster) && Math.abs(kiAdjuster) > 0.0) {
|
||||
if (Double.isFinite(iMinValue)) {
|
||||
this.iMinResult = iMinValue / kiAdjuster;
|
||||
@@ -54,6 +54,21 @@ class PIDController {
|
||||
if (Double.isFinite(iMaxValue)) {
|
||||
this.iMaxResult = iMaxValue / kiAdjuster;
|
||||
}
|
||||
if (Double.isFinite(previousIntegralPart)) {
|
||||
this.integralResult = previousIntegralPart / kiAdjuster;
|
||||
}
|
||||
}
|
||||
|
||||
// restore previous state for the derivative result accumulator
|
||||
if (Double.isFinite(kdAdjuster) && Math.abs(kdAdjuster) > 0.0) {
|
||||
if (Double.isFinite(previousDerivativePart)) {
|
||||
this.derivativeResult = previousDerivativePart / kdAdjuster;
|
||||
}
|
||||
}
|
||||
|
||||
// restore previous state for the previous error variable
|
||||
if (Double.isFinite(previousError)) {
|
||||
this.previousError = previousError;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +99,7 @@ class PIDController {
|
||||
|
||||
final double derivativePart = kd * derivativeResult;
|
||||
|
||||
output = proportionalPart + integralPart + derivativePart;
|
||||
final double output = proportionalPart + integralPart + derivativePart;
|
||||
|
||||
return new PIDOutputDTO(output, proportionalPart, integralPart, derivativePart, error);
|
||||
}
|
||||
|
||||
@@ -117,7 +117,12 @@ 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, iMinValue, iMaxValue);
|
||||
double previousIntegralPart = getItemNameValueAsNumberOrZero(itemRegistry, iInspector);
|
||||
double previousDerivativePart = getItemNameValueAsNumberOrZero(itemRegistry, dInspector);
|
||||
double previousError = getItemNameValueAsNumberOrZero(itemRegistry, eInspector);
|
||||
|
||||
controller = new PIDController(kpAdjuster, kiAdjuster, kdAdjuster, kdTimeConstant, iMinValue, iMaxValue,
|
||||
previousIntegralPart, previousDerivativePart, previousError);
|
||||
|
||||
eventFilter = event -> {
|
||||
String topic = event.getTopic();
|
||||
@@ -208,6 +213,26 @@ public class PIDControllerTriggerHandler extends BaseTriggerModuleHandler implem
|
||||
throw new IllegalStateException("The module callback is not set");
|
||||
}
|
||||
|
||||
private double getItemNameValueAsNumberOrZero(ItemRegistry itemRegistry, @Nullable String itemName)
|
||||
throws IllegalArgumentException {
|
||||
double value = 0.0;
|
||||
|
||||
if (itemName == null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
try {
|
||||
value = getItemValueAsNumber(itemRegistry.getItem(itemName));
|
||||
logger.debug("Item '{}' value {} recovered by PID controller", itemName, value);
|
||||
} catch (ItemNotFoundException e) {
|
||||
throw new IllegalArgumentException("Configured item not found: " + itemName, e);
|
||||
} catch (PIDException e) {
|
||||
logger.warn("Item '{}' value recovery errored: {}", itemName, e.getMessage());
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private double getItemValueAsNumber(Item item) throws PIDException {
|
||||
State setpointState = item.getState();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user