]> git.basschouten.com Git - openhab-addons.git/commitdiff
[pidcontroller] Remove limits, make Ki dependent from the loop time, add reset comman...
authorFabian Wolter <github@fabian-wolter.de>
Tue, 12 Jan 2021 16:16:29 +0000 (17:16 +0100)
committerGitHub <noreply@github.com>
Tue, 12 Jan 2021 16:16:29 +0000 (17:16 +0100)
* [pidcontroller] Remove limits, make Ki dependent from the loop time

Also fix naming of thread and shutdown executor.

Signed-off-by: Fabian Wolter <github@fabian-wolter.de>
bundles/org.openhab.automation.pidcontroller/README.md
bundles/org.openhab.automation.pidcontroller/src/main/java/org/openhab/automation/pidcontroller/internal/PIDControllerConstants.java
bundles/org.openhab.automation.pidcontroller/src/main/java/org/openhab/automation/pidcontroller/internal/handler/PIDController.java
bundles/org.openhab.automation.pidcontroller/src/main/java/org/openhab/automation/pidcontroller/internal/handler/PIDControllerTriggerHandler.java
bundles/org.openhab.automation.pidcontroller/src/main/java/org/openhab/automation/pidcontroller/internal/type/PIDControllerTriggerType.java

index 4822a3e98112a599b16ebbf177d12dc460a6b839..1630ef814ee17dc264cbb8e2807c00434b60d5f4 100644 (file)
@@ -22,20 +22,17 @@ This module triggers whenever the `input` or the `setpoint` changes or the `loop
 Every trigger calculates the P, the I and the D part and sums them up to form the `output` value.
 This is then transferred to the action module.
 
-| Name               | Type    | Description                                                                                                                                        | Required |
-|--------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------|----------|
-| `input`            | Item    | Name of the input [Item](https://www.openhab.org/docs/configuration/items.html) (e.g. temperature sensor value)                                    | Y        |
-| `setpoint`         | Item    | Name of the setpoint Item (e.g. desired room temperature)                                                                                          | Y        |
-| `kp`               | Decimal | P: [Proportional Gain](#proportional-p-gain-parameter) Parameter                                                                                   | Y        |
-| `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        |
-| `outputLowerLimit` | Decimal | The output of the PID controller will be max this value                                                                                            | Y        |
-| `outputUpperLimit` | Decimal | The output of the PID controller will be min this value                                                                                            | Y        |
-| `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        |
-
-The purpose of the limit parameters are to keep the output value and the integral value in a reasonable range, if the regulation cannot meet its setpoint.
-E.g. the window is open and the heater doesn't manage to heat up the room.
+| Name             | Type    | Description                                                                                                                                        | Required |
+|------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------|----------|
+| `input`          | Item    | Name of the input [Item](https://www.openhab.org/docs/configuration/items.html) (e.g. temperature sensor value)                                    | Y        |
+| `setpoint`       | Item    | Name of the setpoint Item (e.g. desired room temperature)                                                                                          | Y        |
+| `kp`             | Decimal | P: [Proportional Gain](#proportional-p-gain-parameter) Parameter                                                                                   | Y        |
+| `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        |
+| `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        |
+
 
 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.
@@ -76,8 +73,8 @@ The bigger this parameter, the faster the drifting.
 
 A value of 0 disables the I part.
 
-A value of 1 adds the current setpoint deviation (error) to the output each second.
-E.g. the setpoint is 25°C and the measured value is 20°C, the output will be set to 5 after 1 sec.
+A value of 1 adds the current setpoint deviation (error) to the output each `loopTime` (in milliseconds).
+E.g. (`loopTimeMs=1000`) the setpoint is 25°C and the measured value is 20°C, the output will be set to 5 after 1 sec.
 After 2 sec the output will be 10.
 If the output is the opening of a valve in %, you might want to set this parameter to a lower value (`ki=0.1` would result in 30% after 60 sec: 5\*0.1\*60=30).
 
index 7522bc37a716a6aa129a528f8bd4014c1e18f0d0..a3f21fac4b8567fbba45a82a146e95be0ef74955 100644 (file)
@@ -26,8 +26,7 @@ public class PIDControllerConstants {
     public static final String AUTOMATION_NAME = "pidcontroller";
     public static final String CONFIG_INPUT_ITEM = "input";
     public static final String CONFIG_SETPOINT_ITEM = "setpoint";
-    public static final String CONFIG_OUTPUT_LOWER_LIMIT = "outputLowerLimit";
-    public static final String CONFIG_OUTPUT_UPPER_LIMIT = "outputUpperLimit";
+    public static final String CONFIG_COMMAND_ITEM = "commandItem";
     public static final String CONFIG_LOOP_TIME = "loopTime";
     public static final String CONFIG_KP_GAIN = "kp";
     public static final String CONFIG_KI_GAIN = "ki";
index 36e5d84b37de665d7b991930e0220af6605a987a..8ba4060bf4929c46f89cc826d554543d64b01b6d 100644 (file)
@@ -25,9 +25,6 @@ import org.openhab.automation.pidcontroller.internal.LowpassFilter;
  */
 @NonNullByDefault
 class PIDController {
-    private final double outputLowerLimit;
-    private final double outputUpperLimit;
-
     private double integralResult;
     private double derivativeResult;
     private double previousError;
@@ -38,17 +35,14 @@ class PIDController {
     private double kd;
     private double derivativeTimeConstantSec;
 
-    public PIDController(double outputLowerLimit, double outputUpperLimit, double kpAdjuster, double kiAdjuster,
-            double kdAdjuster, double derivativeTimeConstantSec) {
-        this.outputLowerLimit = outputLowerLimit;
-        this.outputUpperLimit = outputUpperLimit;
+    public PIDController(double kpAdjuster, double kiAdjuster, double kdAdjuster, double derivativeTimeConstantSec) {
         this.kp = kpAdjuster;
         this.ki = kiAdjuster;
         this.kd = kdAdjuster;
         this.derivativeTimeConstantSec = derivativeTimeConstantSec;
     }
 
-    public PIDOutputDTO calculate(double input, double setpoint, long lastInvocationMs) {
+    public PIDOutputDTO calculate(double input, double setpoint, long lastInvocationMs, int loopTimeMs) {
         final double lastInvocationSec = lastInvocationMs / 1000d;
         final double error = setpoint - input;
 
@@ -60,13 +54,7 @@ class PIDController {
         }
 
         // integral calculation
-        integralResult += error * lastInvocationSec;
-        // limit to output limits
-        if (ki != 0) {
-            final double maxIntegral = outputUpperLimit / ki;
-            final double minIntegral = outputLowerLimit / ki;
-            integralResult = Math.min(maxIntegral, Math.max(minIntegral, integralResult));
-        }
+        integralResult += error * lastInvocationMs / loopTimeMs;
 
         // calculate parts
         final double proportionalPart = kp * error;
@@ -74,9 +62,14 @@ class PIDController {
         final double derivativePart = kd * derivativeResult;
         output = proportionalPart + integralPart + derivativePart;
 
-        // limit output value
-        output = Math.min(outputUpperLimit, Math.max(outputLowerLimit, output));
-
         return new PIDOutputDTO(output, proportionalPart, integralPart, derivativePart, error);
     }
+
+    public void setIntegralResult(double integralResult) {
+        this.integralResult = integralResult;
+    }
+
+    public void setDerivativeResult(double derivativeResult) {
+        this.derivativeResult = derivativeResult;
+    }
 }
index 6806928011dc7007169f37e16d4699e12f5b740d..c3280c1229c775555ca0b89a3921c4025c7eb41c 100644 (file)
@@ -18,6 +18,7 @@ import java.math.BigDecimal;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
@@ -62,7 +63,7 @@ public class PIDControllerTriggerHandler extends BaseTriggerModuleHandler implem
     private static final Set<String> SUBSCRIBED_EVENT_TYPES = Set.of(ItemStateEvent.TYPE, ItemStateChangedEvent.TYPE);
     private final Logger logger = LoggerFactory.getLogger(PIDControllerTriggerHandler.class);
     private final ScheduledExecutorService scheduler = Executors
-            .newSingleThreadScheduledExecutor(new NamedThreadFactory("OH-automation-" + AUTOMATION_NAME, true));
+            .newSingleThreadScheduledExecutor(new NamedThreadFactory("automation-" + AUTOMATION_NAME, true));
     private final ServiceRegistration<?> eventSubscriberRegistration;
     private final PIDController controller;
     private final int loopTimeMs;
@@ -70,6 +71,7 @@ public class PIDControllerTriggerHandler extends BaseTriggerModuleHandler implem
     private long previousTimeMs = System.currentTimeMillis();
     private Item inputItem;
     private Item setpointItem;
+    private Optional<String> commandTopic;
     private EventFilter eventFilter;
 
     public PIDControllerTriggerHandler(Trigger module, ItemRegistry itemRegistry, EventPublisher eventPublisher,
@@ -93,8 +95,13 @@ public class PIDControllerTriggerHandler extends BaseTriggerModuleHandler implem
             throw new IllegalArgumentException("Configured setpoint item not found: " + setpointItemName, e);
         }
 
-        double outputLowerLimit = getDoubleFromConfig(config, CONFIG_OUTPUT_LOWER_LIMIT);
-        double outputUpperLimit = getDoubleFromConfig(config, CONFIG_OUTPUT_UPPER_LIMIT);
+        String commandItemName = (String) config.get(CONFIG_COMMAND_ITEM);
+        if (commandItemName != null) {
+            commandTopic = Optional.of("openhab/items/" + commandItemName + "/statechanged");
+        } else {
+            commandTopic = Optional.empty();
+        }
+
         double kpAdjuster = getDoubleFromConfig(config, CONFIG_KP_GAIN);
         double kiAdjuster = getDoubleFromConfig(config, CONFIG_KI_GAIN);
         double kdAdjuster = getDoubleFromConfig(config, CONFIG_KD_GAIN);
@@ -103,15 +110,15 @@ public class PIDControllerTriggerHandler extends BaseTriggerModuleHandler implem
         loopTimeMs = ((BigDecimal) requireNonNull(config.get(CONFIG_LOOP_TIME), CONFIG_LOOP_TIME + " is not set"))
                 .intValue();
 
-        controller = new PIDController(outputLowerLimit, outputUpperLimit, kpAdjuster, kiAdjuster, kdAdjuster,
-                kdTimeConstant);
+        controller = new PIDController(kpAdjuster, kiAdjuster, kdAdjuster, kdTimeConstant);
 
         eventFilter = event -> {
             String topic = event.getTopic();
 
             return topic.equals("openhab/items/" + inputItemName + "/state")
                     || topic.equals("openhab/items/" + inputItemName + "/statechanged")
-                    || topic.equals("openhab/items/" + setpointItemName + "/statechanged");
+                    || topic.equals("openhab/items/" + setpointItemName + "/statechanged")
+                    || commandTopic.map(t -> topic.equals(t)).orElse(false);
         };
 
         eventSubscriberRegistration = bundleContext.registerService(EventSubscriber.class.getName(), this, null);
@@ -152,7 +159,7 @@ public class PIDControllerTriggerHandler extends BaseTriggerModuleHandler implem
 
         long now = System.currentTimeMillis();
 
-        PIDOutputDTO output = controller.calculate(input, setpoint, now - previousTimeMs);
+        PIDOutputDTO output = controller.calculate(input, setpoint, now - previousTimeMs, loopTimeMs);
         previousTimeMs = now;
 
         Map<String, BigDecimal> outputs = new HashMap<>();
@@ -198,7 +205,17 @@ public class PIDControllerTriggerHandler extends BaseTriggerModuleHandler implem
     @Override
     public void receive(Event event) {
         if (event instanceof ItemStateChangedEvent) {
-            calculate();
+            if (event.getTopic().equals(commandTopic.get())) {
+                ItemStateChangedEvent changedEvent = (ItemStateChangedEvent) event;
+                if ("RESET".equals(changedEvent.getItemState().toString())) {
+                    controller.setIntegralResult(0);
+                    controller.setDerivativeResult(0);
+                } else {
+                    logger.warn("Unknown command: {}", changedEvent.getItemState());
+                }
+            } else {
+                calculate();
+            }
         }
     }
 
@@ -221,6 +238,8 @@ public class PIDControllerTriggerHandler extends BaseTriggerModuleHandler implem
             localControllerjob.cancel(true);
         }
 
+        scheduler.shutdown();
+
         super.dispose();
     }
 }
index 2cdd7c74b935ff086477ff8660cfbe9cbe4eecdb..856ded793f3f488719755bcd747c31d66b3a5d72 100644 (file)
@@ -44,23 +44,22 @@ public class PIDControllerTriggerType extends TriggerType {
                 .withRequired(true).withReadOnly(true).withMultiple(false).withContext("item").withLabel("Setpoint")
                 .withDescription("Targeted setpoint").build());
         configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_KP_GAIN, Type.DECIMAL).withRequired(true)
-                .withMultiple(false).withDefault("1.0").withLabel("Proportional Gain (Kp)")
+                .withMultiple(false).withDefault("1.0").withMinimum(BigDecimal.ZERO).withLabel("Proportional Gain (Kp)")
                 .withDescription("Change to output propertional to current error value.").build());
         configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_KI_GAIN, Type.DECIMAL).withRequired(true)
-                .withMultiple(false).withDefault("1.0").withLabel("Integral Gain (Ki)")
+                .withMultiple(false).withDefault("1.0").withMinimum(BigDecimal.ZERO).withLabel("Integral Gain (Ki)")
                 .withDescription("Accelerate movement towards the setpoint.").build());
         configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_KD_GAIN, Type.DECIMAL).withRequired(true)
-                .withMultiple(false).withDefault("1.0").withLabel("Derivative Gain (Kd)")
+                .withMultiple(false).withDefault("1.0").withMinimum(BigDecimal.ZERO).withLabel("Derivative Gain (Kd)")
                 .withDescription("Slows the rate of change of the output.").build());
         configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_KD_TIMECONSTANT, Type.DECIMAL)
-                .withRequired(true).withMultiple(false).withDefault("1.0").withLabel("Derivative Time Constant")
-                .withDescription("Slows the rate of change of the D Part (T1) in seconds.").withUnit("s").build());
-        configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_OUTPUT_LOWER_LIMIT, Type.DECIMAL)
-                .withRequired(true).withMultiple(false).withDefault("0").withLabel("Output Lower Limit")
-                .withDescription("The output of the PID controller will be min this value").build());
-        configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_OUTPUT_UPPER_LIMIT, Type.DECIMAL)
-                .withRequired(true).withMultiple(false).withDefault("100").withLabel("Output Upper Limit")
-                .withDescription("The output of the PID controller will be max this value").build());
+                .withRequired(true).withMultiple(false).withMinimum(BigDecimal.ZERO).withDefault("1.0")
+                .withLabel("Derivative Time Constant")
+                .withDescription("Slows the rate of change of the D part (T1) in seconds.").withUnit("s").build());
+        configDescriptions
+                .add(ConfigDescriptionParameterBuilder.create(CONFIG_COMMAND_ITEM, Type.TEXT).withRequired(false)
+                        .withReadOnly(true).withMultiple(false).withContext("item").withLabel("Command Item")
+                        .withDescription("You can send String commands to this Item like \"RESET\".").build());
         configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_LOOP_TIME, Type.DECIMAL)
                 .withRequired(true).withMultiple(false).withDefault(DEFAULT_LOOPTIME_MS).withLabel("Loop Time")
                 .withDescription("The interval the output value is updated in ms").withUnit("ms").build());