[pidcontroller] Remove limits, make Ki dependent from the loop time, add reset command (#9759)
* [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>
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user