| `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.
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`
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.
private double integralResult;
private double derivativeResult;
private double previousError;
- private double output;
private double kp;
private double ki;
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;
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;
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;
}
}
final double derivativePart = kd * derivativeResult;
- output = proportionalPart + integralPart + derivativePart;
+ final double output = proportionalPart + integralPart + derivativePart;
return new PIDOutputDTO(output, proportionalPart, integralPart, derivativePart, error);
}
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();
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();