diff --git a/bundles/org.openhab.automation.pidcontroller/README.md b/bundles/org.openhab.automation.pidcontroller/README.md index 35ae91eaab0..cd1aea4eada 100644 --- a/bundles/org.openhab.automation.pidcontroller/README.md +++ b/bundles/org.openhab.automation.pidcontroller/README.md @@ -35,14 +35,14 @@ This is then transferred to the action module. | `ki` | Decimal | I: [Integral Gain](#integral-i-gain-parameter) Parameter | Y | | `kd` | Decimal | D: [Derivative Gain](#derivative-d-gain-parameter) Parameter | Y | | `kdTimeConstant` | Decimal | D-T1: [Derivative Gain Time Constant](#derivative-time-constant-d-t1-parameter) in sec. | Y | -| `commandItem` | String | Send a String "RESET" to this item to reset the I- and the D-part to 0. | N | +| `commandItem` | String | Send a String "RESET" to this item to reset the I- and the D-part to 0. | N | | `loopTime` | Decimal | The interval the output value will be updated in milliseconds. Note: the output will also be updated when the input value or the setpoint changes. | Y | -| `integralMinValue` | Decimal | The I-part will be limited (min) to this value. | N | -| `integralMaxValue` | Decimal | The I-part will be limited (max) to this value. | N | -| `pInspector` | Item | Name of the debug Item for the current P-part | N | -| `iInspector` | Item | Name of the debug Item for the current I-part | N | -| `dInspector` | Item | Name of the debug Item for the current D-part | N | -| `eInspector` | Item | Name of the debug Item for the current regulation difference (error) | N | +| `integralMinValue` | Decimal | The I-part will be limited (min) to this value. | N | +| `integralMaxValue` | Decimal | The I-part will be limited (max) to this value. | N | +| `pInspector` | Item | Name of the inspector Item for the current P-part | N | +| `iInspector` | Item | Name of the inspector Item for the current I-part | N | +| `dInspector` | Item | Name of the inspector Item for the current D-part | N | +| `eInspector` | Item | Name of the inspector Item for the current regulation difference (error) | N | The `loopTime` should be max a tenth of the system response. E.g. the heating needs 10 min to heat up the room, the loop time should be max 1 min. @@ -57,6 +57,9 @@ You can view the internal P-, I- and D-parts of the controller with the inspecto These values are useful when tuning the controller. They are updated every time the output is updated. +Inspector items are also used to recover the controller's previous state during startup. This feature allows the PID +controller parameters to be updated and openHAB to be restarted without losing the current controller state. + ## Proportional (P) Gain Parameter Parameter: `kp` @@ -135,3 +138,11 @@ This can be done either by changing the setpoint (e.g. 20°C -> 25°C) or by for This process can take some time with slow responding control loops like heating systems. You will get faster results with constant lighting or PV zero export applications. + +## Persisting controller state across restarts + +Persisting controller state requires inspector items `iInspector`, `dInspector`, `eInspector` to be configured. +The PID controller uses these Items to expose internal state in order to restore it during startup or reload. + +In addition, you need to have persistence set up for these items in openHAB. Please see openHAB documentation regarding +[Persistence](https://www.openhab.org/docs/configuration/persistence.html) for more details and instructions. diff --git a/bundles/org.openhab.automation.pidcontroller/src/main/java/org/openhab/automation/pidcontroller/internal/handler/PIDController.java b/bundles/org.openhab.automation.pidcontroller/src/main/java/org/openhab/automation/pidcontroller/internal/handler/PIDController.java index a93f89af415..a78e5815168 100644 --- a/bundles/org.openhab.automation.pidcontroller/src/main/java/org/openhab/automation/pidcontroller/internal/handler/PIDController.java +++ b/bundles/org.openhab.automation.pidcontroller/src/main/java/org/openhab/automation/pidcontroller/internal/handler/PIDController.java @@ -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); } diff --git a/bundles/org.openhab.automation.pidcontroller/src/main/java/org/openhab/automation/pidcontroller/internal/handler/PIDControllerTriggerHandler.java b/bundles/org.openhab.automation.pidcontroller/src/main/java/org/openhab/automation/pidcontroller/internal/handler/PIDControllerTriggerHandler.java index cfd9f3237c6..d764d28fc98 100644 --- a/bundles/org.openhab.automation.pidcontroller/src/main/java/org/openhab/automation/pidcontroller/internal/handler/PIDControllerTriggerHandler.java +++ b/bundles/org.openhab.automation.pidcontroller/src/main/java/org/openhab/automation/pidcontroller/internal/handler/PIDControllerTriggerHandler.java @@ -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();