[pidcontroller] Use framework scheduler and remove custom Action module (#10806)

* [pidcontroller] Use framework scheduler and remove custom Action module

Signed-off-by: Fabian Wolter <github@fabian-wolter.de>
This commit is contained in:
Fabian Wolter
2021-06-08 08:07:24 +02:00
committed by GitHub
parent 6141d69da3
commit cf13bb8275
9 changed files with 87 additions and 250 deletions

View File

@@ -36,5 +36,5 @@ public class PIDControllerConstants {
public static final String I_INSPECTOR = "iInspector";
public static final String D_INSPECTOR = "dInspector";
public static final String E_INSPECTOR = "eInspector";
public static final String OUTPUT = "output";
public static final String COMMAND = "command";
}

View File

@@ -17,9 +17,7 @@ import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.automation.pidcontroller.internal.handler.PIDControllerActionHandler;
import org.openhab.automation.pidcontroller.internal.handler.PIDControllerTriggerHandler;
import org.openhab.core.automation.Action;
import org.openhab.core.automation.Module;
import org.openhab.core.automation.Trigger;
import org.openhab.core.automation.handler.BaseModuleHandlerFactory;
@@ -39,8 +37,7 @@ import org.osgi.service.component.annotations.Reference;
@Component(service = ModuleHandlerFactory.class, configurationPid = "action.pidcontroller")
@NonNullByDefault
public class PIDControllerModuleHandlerFactory extends BaseModuleHandlerFactory {
private static final Collection<String> TYPES = Set.of(PIDControllerTriggerHandler.MODULE_TYPE_ID,
PIDControllerActionHandler.MODULE_TYPE_ID);
private static final Collection<String> TYPES = Set.of(PIDControllerTriggerHandler.MODULE_TYPE_ID);
private ItemRegistry itemRegistry;
private EventPublisher eventPublisher;
private BundleContext bundleContext;
@@ -63,8 +60,6 @@ public class PIDControllerModuleHandlerFactory extends BaseModuleHandlerFactory
switch (module.getTypeUID()) {
case PIDControllerTriggerHandler.MODULE_TYPE_ID:
return new PIDControllerTriggerHandler((Trigger) module, itemRegistry, eventPublisher, bundleContext);
case PIDControllerActionHandler.MODULE_TYPE_ID:
return new PIDControllerActionHandler((Action) module, itemRegistry, eventPublisher);
}
return null;

View File

@@ -1,84 +0,0 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.automation.pidcontroller.internal.handler;
import static org.openhab.automation.pidcontroller.internal.PIDControllerConstants.AUTOMATION_NAME;
import java.math.BigDecimal;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.automation.Action;
import org.openhab.core.automation.handler.ActionHandler;
import org.openhab.core.automation.handler.BaseModuleHandler;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.items.events.ItemCommandEvent;
import org.openhab.core.items.events.ItemEventFactory;
import org.openhab.core.library.types.DecimalType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Hilbrand Bouwkamp - Initial Contribution
* @author Fabian Wolter - Add PID debugging items
*/
@NonNullByDefault
public class PIDControllerActionHandler extends BaseModuleHandler<Action> implements ActionHandler {
public static final String MODULE_TYPE_ID = AUTOMATION_NAME + ".action";
private final Logger logger = LoggerFactory.getLogger(PIDControllerActionHandler.class);
private ItemRegistry itemRegistry;
private EventPublisher eventPublisher;
public PIDControllerActionHandler(Action module, ItemRegistry itemRegistry, EventPublisher eventPublisher) {
super(module);
this.itemRegistry = itemRegistry;
this.eventPublisher = eventPublisher;
}
@Override
public @Nullable Map<String, Object> execute(Map<String, Object> context) {
final Configuration configuration = module.getConfiguration();
context.forEach((k, v) -> {
// Remove triggername from key to get raw trigger param
String itemKey = k.substring(k.lastIndexOf('.') + 1);
String itemName = (String) configuration.get(itemKey);
if (itemName == null || itemName.isBlank()) {
// try original key name (<triggername>.<trigger_param>)
itemName = (String) configuration.get(k);
if (itemName == null || itemName.isBlank()) {
return;
}
}
if (v instanceof BigDecimal) {
final BigDecimal command = (BigDecimal) v;
final DecimalType outputValue = new DecimalType(command);
final ItemCommandEvent itemCommandEvent = ItemEventFactory.createCommandEvent(itemName, outputValue);
eventPublisher.post(itemCommandEvent);
} else {
logger.warn(
"Command was not posted because either the configuration was not correct or a service was missing: ItemName: {}, Command: {}, eventPublisher: {}, ItemRegistry: {}",
itemName, v, eventPublisher, itemRegistry);
}
});
return null;
}
}

View File

@@ -15,14 +15,10 @@ package org.openhab.automation.pidcontroller.internal.handler;
import static org.openhab.automation.pidcontroller.internal.PIDControllerConstants.*;
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;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -32,7 +28,6 @@ import org.openhab.core.automation.ModuleHandlerCallback;
import org.openhab.core.automation.Trigger;
import org.openhab.core.automation.handler.BaseTriggerModuleHandler;
import org.openhab.core.automation.handler.TriggerHandlerCallback;
import org.openhab.core.common.NamedThreadFactory;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.events.Event;
import org.openhab.core.events.EventFilter;
@@ -44,6 +39,7 @@ import org.openhab.core.items.ItemRegistry;
import org.openhab.core.items.events.ItemEventFactory;
import org.openhab.core.items.events.ItemStateChangedEvent;
import org.openhab.core.items.events.ItemStateEvent;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
@@ -63,18 +59,19 @@ public class PIDControllerTriggerHandler extends BaseTriggerModuleHandler implem
public static final String MODULE_TYPE_ID = AUTOMATION_NAME + ".trigger";
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("automation-" + AUTOMATION_NAME, true));
private final ServiceRegistration<?> eventSubscriberRegistration;
private final PIDController controller;
private final int loopTimeMs;
private @Nullable ScheduledFuture<?> controllerjob;
private long previousTimeMs = System.currentTimeMillis();
private Item inputItem;
private Item setpointItem;
private Optional<String> commandTopic;
private EventFilter eventFilter;
private EventPublisher eventPublisher;
private @Nullable String pInspector;
private @Nullable String iInspector;
private @Nullable String dInspector;
private @Nullable String eInspector;
public PIDControllerTriggerHandler(Trigger module, ItemRegistry itemRegistry, EventPublisher eventPublisher,
BundleContext bundleContext) {
@@ -109,6 +106,10 @@ public class PIDControllerTriggerHandler extends BaseTriggerModuleHandler implem
double kiAdjuster = getDoubleFromConfig(config, CONFIG_KI_GAIN);
double kdAdjuster = getDoubleFromConfig(config, CONFIG_KD_GAIN);
double kdTimeConstant = getDoubleFromConfig(config, CONFIG_KD_TIMECONSTANT);
pInspector = (String) config.get(P_INSPECTOR);
iInspector = (String) config.get(I_INSPECTOR);
dInspector = (String) config.get(D_INSPECTOR);
eInspector = (String) config.get(E_INSPECTOR);
loopTimeMs = ((BigDecimal) requireNonNull(config.get(CONFIG_LOOP_TIME), CONFIG_LOOP_TIME + " is not set"))
.intValue();
@@ -118,17 +119,21 @@ public class PIDControllerTriggerHandler extends BaseTriggerModuleHandler implem
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")
return ("openhab/items/" + inputItemName + "/state").equals(topic)
|| ("openhab/items/" + inputItemName + "/statechanged").equals(topic)
|| ("openhab/items/" + setpointItemName + "/statechanged").equals(topic)
|| commandTopic.map(t -> topic.equals(t)).orElse(false);
};
eventSubscriberRegistration = bundleContext.registerService(EventSubscriber.class.getName(), this, null);
eventPublisher.post(ItemEventFactory.createCommandEvent(inputItemName, RefreshType.REFRESH));
}
controllerjob = scheduler.scheduleWithFixedDelay(this::calculate, 0, loopTimeMs, TimeUnit.MILLISECONDS);
@Override
public void setCallback(ModuleHandlerCallback callback) {
super.setCallback(callback);
getCallback().getScheduler().scheduleWithFixedDelay(this::calculate, 0, loopTimeMs, TimeUnit.MILLISECONDS);
}
private <T> T requireNonNull(T obj, String message) {
@@ -165,24 +170,27 @@ public class PIDControllerTriggerHandler extends BaseTriggerModuleHandler implem
PIDOutputDTO output = controller.calculate(input, setpoint, now - previousTimeMs, loopTimeMs);
previousTimeMs = now;
Map<String, BigDecimal> outputs = new HashMap<>();
updateItem(pInspector, output.getProportionalPart());
updateItem(iInspector, output.getIntegralPart());
updateItem(dInspector, output.getDerivativePart());
updateItem(eInspector, output.getError());
putBigDecimal(outputs, OUTPUT, output.getOutput());
putBigDecimal(outputs, P_INSPECTOR, output.getProportionalPart());
putBigDecimal(outputs, I_INSPECTOR, output.getIntegralPart());
putBigDecimal(outputs, D_INSPECTOR, output.getDerivativePart());
putBigDecimal(outputs, E_INSPECTOR, output.getError());
getCallback().triggered(module, Map.of(COMMAND, new DecimalType(output.getOutput())));
}
ModuleHandlerCallback localCallback = callback;
if (localCallback != null && localCallback instanceof TriggerHandlerCallback) {
((TriggerHandlerCallback) localCallback).triggered(module, outputs);
} else {
logger.warn("No callback set");
private void updateItem(@Nullable String itemName, double value) {
if (itemName != null) {
eventPublisher.post(ItemEventFactory.createCommandEvent(itemName, new DecimalType(value)));
}
}
private void putBigDecimal(Map<String, BigDecimal> map, String key, double value) {
map.put(key, BigDecimal.valueOf(value));
private TriggerHandlerCallback getCallback() {
ModuleHandlerCallback localCallback = callback;
if (localCallback != null && localCallback instanceof TriggerHandlerCallback) {
return (TriggerHandlerCallback) localCallback;
}
throw new IllegalStateException("The module callback is not set");
}
private double getItemValueAsNumber(Item item) throws PIDException {
@@ -191,7 +199,7 @@ public class PIDControllerTriggerHandler extends BaseTriggerModuleHandler implem
if (setpointState instanceof Number) {
double doubleValue = ((Number) setpointState).doubleValue();
if (Double.isFinite(doubleValue)) {
if (Double.isFinite(doubleValue) && !Double.isNaN(doubleValue)) {
return doubleValue;
}
} else if (setpointState instanceof StringType) {
@@ -237,13 +245,6 @@ public class PIDControllerTriggerHandler extends BaseTriggerModuleHandler implem
public void dispose() {
eventSubscriberRegistration.unregister();
ScheduledFuture<?> localControllerjob = controllerjob;
if (localControllerjob != null) {
localControllerjob.cancel(true);
}
scheduler.shutdown();
super.dispose();
}
}

View File

@@ -14,15 +14,11 @@ package org.openhab.automation.pidcontroller.internal.template;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.automation.pidcontroller.internal.PIDControllerConstants;
import org.openhab.automation.pidcontroller.internal.handler.PIDControllerActionHandler;
import org.openhab.automation.pidcontroller.internal.handler.PIDControllerTriggerHandler;
import org.openhab.automation.pidcontroller.internal.type.PIDControllerActionType;
import org.openhab.core.automation.Action;
import org.openhab.core.automation.Condition;
import org.openhab.core.automation.Trigger;
@@ -45,15 +41,8 @@ public class PIDControllerRuleTemplate extends RuleTemplate {
final List<Trigger> triggers = List.of(ModuleBuilder.createTrigger().withId(triggerId)
.withTypeUID(PIDControllerTriggerHandler.MODULE_TYPE_ID).withLabel("PID Controller Trigger").build());
final Map<String, String> actionInputs = Map.of(PIDControllerActionType.INPUT,
triggerId + "." + PIDControllerConstants.OUTPUT);
final List<Action> actions = List.of(ModuleBuilder.createAction().withId(UUID.randomUUID().toString())
.withTypeUID(PIDControllerActionHandler.MODULE_TYPE_ID).withLabel("PID Controller Action")
.withInputs(actionInputs).build());
return new PIDControllerRuleTemplate(Set.of("PID Controller"), triggers, Collections.emptyList(), actions,
Collections.emptyList());
return new PIDControllerRuleTemplate(Set.of("PID Controller"), triggers, Collections.emptyList(),
Collections.emptyList(), Collections.emptyList());
}
public PIDControllerRuleTemplate(Set<String> tags, List<Trigger> triggers, List<Condition> conditions,

View File

@@ -1,71 +0,0 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.automation.pidcontroller.internal.type;
import static org.openhab.automation.pidcontroller.internal.PIDControllerConstants.*;
import java.math.BigDecimal;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.automation.pidcontroller.internal.handler.PIDControllerActionHandler;
import org.openhab.core.automation.Visibility;
import org.openhab.core.automation.type.ActionType;
import org.openhab.core.automation.type.Input;
import org.openhab.core.config.core.ConfigDescriptionParameter;
import org.openhab.core.config.core.ConfigDescriptionParameter.Type;
import org.openhab.core.config.core.ConfigDescriptionParameterBuilder;
/**
*
* @author Hilbrand Bouwkamp - Initial Contribution
*/
@NonNullByDefault
public class PIDControllerActionType extends ActionType {
public static final String INPUT = "input";
public static PIDControllerActionType initialize() {
final ConfigDescriptionParameter outputItem = ConfigDescriptionParameterBuilder.create(OUTPUT, Type.TEXT)
.withRequired(true).withMultiple(false).withContext("item").withLabel("Output Item")
.withDescription("Item to send output").build();
final ConfigDescriptionParameter pInspectorItem = ConfigDescriptionParameterBuilder
.create(P_INSPECTOR, Type.TEXT).withRequired(false).withMultiple(false).withContext("item")
.withLabel("P Inspector Item").withDescription("Item for debugging the P part").build();
final ConfigDescriptionParameter iInspectorItem = ConfigDescriptionParameterBuilder
.create(I_INSPECTOR, Type.TEXT).withRequired(false).withMultiple(false).withContext("item")
.withLabel("I Inspector Item").withDescription("Item for debugging the I part").build();
final ConfigDescriptionParameter dInspectorItem = ConfigDescriptionParameterBuilder
.create(D_INSPECTOR, Type.TEXT).withRequired(false).withMultiple(false).withContext("item")
.withLabel("D Inspector Item").withDescription("Item for debugging the D part").build();
final ConfigDescriptionParameter eInspectorItem = ConfigDescriptionParameterBuilder
.create(E_INSPECTOR, Type.TEXT).withRequired(false).withMultiple(false).withContext("item")
.withLabel("Error Inspector Item").withDescription("Item for debugging the error value").build();
List<ConfigDescriptionParameter> config = List.of(outputItem, pInspectorItem, iInspectorItem, dInspectorItem,
eInspectorItem);
List<Input> inputs = List.of(createInput(INPUT), createInput(P_INSPECTOR), createInput(I_INSPECTOR),
createInput(D_INSPECTOR), createInput(E_INSPECTOR));
return new PIDControllerActionType(config, inputs);
}
private static Input createInput(String name) {
return new Input(name, BigDecimal.class.getName());
}
public PIDControllerActionType(List<ConfigDescriptionParameter> configDescriptions, List<Input> inputs) {
super(PIDControllerActionHandler.MODULE_TYPE_ID, configDescriptions, "calculate PID output", null, null,
Visibility.VISIBLE, inputs, null);
}
}

View File

@@ -19,7 +19,6 @@ import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.automation.pidcontroller.internal.handler.PIDControllerActionHandler;
import org.openhab.automation.pidcontroller.internal.handler.PIDControllerTriggerHandler;
import org.openhab.core.automation.type.ModuleType;
import org.openhab.core.automation.type.ModuleTypeProvider;
@@ -33,9 +32,8 @@ import org.osgi.service.component.annotations.Component;
@Component
@NonNullByDefault
public class PIDControllerModuleTypeProvider implements ModuleTypeProvider {
private static final Map<String, ModuleType> PROVIDED_MODULE_TYPES = Map.of(
PIDControllerActionHandler.MODULE_TYPE_ID, PIDControllerActionType.initialize(),
PIDControllerTriggerHandler.MODULE_TYPE_ID, PIDControllerTriggerType.initialize());
private static final Map<String, ModuleType> PROVIDED_MODULE_TYPES = Map
.of(PIDControllerTriggerHandler.MODULE_TYPE_ID, PIDControllerTriggerType.initialize());
@SuppressWarnings("unchecked")
@Override

View File

@@ -17,6 +17,7 @@ import static org.openhab.automation.pidcontroller.internal.PIDControllerConstan
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.automation.pidcontroller.internal.handler.PIDControllerTriggerHandler;
@@ -30,24 +31,26 @@ import org.openhab.core.config.core.ConfigDescriptionParameterBuilder;
/**
*
* @author Hilbrand Bouwkamp - Initial Contribution
* @author Fabian Wolter - Add inspector Items for debugging
*/
@NonNullByDefault
public class PIDControllerTriggerType extends TriggerType {
private static final String DEFAULT_LOOPTIME_MS = "1000";
private static final String ITEM = "item";
public static PIDControllerTriggerType initialize() {
List<ConfigDescriptionParameter> configDescriptions = new ArrayList<>();
configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_INPUT_ITEM, Type.TEXT) //
.withRequired(true) //
.withMultiple(false) //
.withContext("item") //
.withContext(ITEM) //
.withLabel("Input Item") //
.withDescription("Item to monitor") //
.build());
configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_SETPOINT_ITEM, Type.TEXT) //
.withRequired(true) //
.withMultiple(false) //
.withContext("item") //
.withContext(ITEM) //
.withLabel("Setpoint") //
.withDescription("Targeted setpoint") //
.build());
@@ -83,13 +86,6 @@ public class PIDControllerTriggerType extends TriggerType {
.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) //
.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) //
@@ -98,20 +94,37 @@ public class PIDControllerTriggerType extends TriggerType {
.withDescription("The interval the output value is updated in ms") //
.withUnit("ms") //
.build());
Output output = new Output(OUTPUT, BigDecimal.class.getName(), "Output", "Output value of the PID Controller",
null, null, null);
Output pInspector = new Output(P_INSPECTOR, BigDecimal.class.getName(), "P Inspector",
"Current P value of the pid controller", null, null, null);
Output iInspector = new Output(I_INSPECTOR, BigDecimal.class.getName(), "I Inspector",
"Current I value of the pid controller", null, null, null);
Output dInspector = new Output(D_INSPECTOR, BigDecimal.class.getName(), "D Inspector",
"Current D value of the pid controller", null, null, null);
Output eInspector = new Output(E_INSPECTOR, BigDecimal.class.getName(), "Error Value Inspector",
"Current error value of the pid controller", null, null, null);
configDescriptions.add(ConfigDescriptionParameterBuilder.create(P_INSPECTOR, Type.TEXT) //
.withRequired(false) //
.withMultiple(false) //
.withContext(ITEM) //
.withLabel("P Inspector Item") //
.withDescription("Item for debugging the P part") //
.build());
configDescriptions.add(ConfigDescriptionParameterBuilder.create(I_INSPECTOR, Type.TEXT) //
.withRequired(false) //
.withMultiple(false) //
.withContext(ITEM) //
.withLabel("I Inspector Item") //
.withDescription("Item for debugging the I part") //
.build());
configDescriptions.add(ConfigDescriptionParameterBuilder.create(D_INSPECTOR, Type.TEXT) //
.withRequired(false).withMultiple(false) //
.withContext(ITEM) //
.withLabel("D Inspector Item") //
.withDescription("Item for debugging the D part") //
.build());
configDescriptions.add(ConfigDescriptionParameterBuilder.create(E_INSPECTOR, Type.TEXT) //
.withRequired(false).withMultiple(false) //
.withContext(ITEM) //
.withLabel("Error Inspector Item") //
.withDescription("Item for debugging the error value") //
.build());
List<Output> outputs = List.of(output, pInspector, iInspector, dInspector, eInspector);
Output output = new Output(COMMAND, BigDecimal.class.getName(), "Output", "Output value of the PID Controller",
Set.of("command"), null, null);
return new PIDControllerTriggerType(configDescriptions, outputs);
return new PIDControllerTriggerType(configDescriptions, List.of(output));
}
public PIDControllerTriggerType(List<ConfigDescriptionParameter> configDescriptions, List<Output> outputs) {