Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pidcontroller] Implement previous state recovery on startup #13003

Merged
merged 6 commits into from
Jul 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions bundles/org.openhab.automation.pidcontroller/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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`
Expand Down Expand Up @@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ class PIDController {
private double integralResult;
private double derivativeResult;
private double previousError;
private double output;

private double kp;
private double ki;
Expand All @@ -38,22 +37,38 @@ 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;
this.derivativeTimeConstantSec = derivativeTimeConstantSec;
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;
}
}

Expand Down Expand Up @@ -84,7 +99,7 @@ public PIDOutputDTO calculate(double input, double setpoint, long lastInvocation

final double derivativePart = kd * derivativeResult;

output = proportionalPart + integralPart + derivativePart;
final double output = proportionalPart + integralPart + derivativePart;

return new PIDOutputDTO(output, proportionalPart, integralPart, derivativePart, error);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,12 @@ public PIDControllerTriggerHandler(Trigger module, ItemRegistry itemRegistry, Ev
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();
Expand Down Expand Up @@ -208,6 +213,26 @@ private TriggerHandlerCallback getCallback() {
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();

Expand Down