]> git.basschouten.com Git - openhab-addons.git/blob
cfeb52b1eff0f7708b8339abaf9fa48cfca9fcd2
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.automation.pidcontroller.internal.handler;
14
15 import static org.openhab.automation.pidcontroller.internal.PIDControllerConstants.*;
16
17 import java.math.BigDecimal;
18 import java.util.HashMap;
19 import java.util.Map;
20 import java.util.Objects;
21 import java.util.Set;
22 import java.util.concurrent.Executors;
23 import java.util.concurrent.ScheduledExecutorService;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.automation.pidcontroller.internal.PIDException;
30 import org.openhab.core.automation.ModuleHandlerCallback;
31 import org.openhab.core.automation.Trigger;
32 import org.openhab.core.automation.handler.BaseTriggerModuleHandler;
33 import org.openhab.core.automation.handler.TriggerHandlerCallback;
34 import org.openhab.core.common.NamedThreadFactory;
35 import org.openhab.core.config.core.Configuration;
36 import org.openhab.core.events.Event;
37 import org.openhab.core.events.EventFilter;
38 import org.openhab.core.events.EventPublisher;
39 import org.openhab.core.events.EventSubscriber;
40 import org.openhab.core.items.Item;
41 import org.openhab.core.items.ItemNotFoundException;
42 import org.openhab.core.items.ItemRegistry;
43 import org.openhab.core.items.events.ItemEventFactory;
44 import org.openhab.core.items.events.ItemStateChangedEvent;
45 import org.openhab.core.items.events.ItemStateEvent;
46 import org.openhab.core.library.types.StringType;
47 import org.openhab.core.types.RefreshType;
48 import org.openhab.core.types.State;
49 import org.osgi.framework.BundleContext;
50 import org.osgi.framework.ServiceRegistration;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 /**
55  *
56  * @author Hilbrand Bouwkamp - Initial Contribution
57  * @author Fabian Wolter - Add PID debug output values
58  */
59 @NonNullByDefault
60 public class PIDControllerTriggerHandler extends BaseTriggerModuleHandler implements EventSubscriber {
61     public static final String MODULE_TYPE_ID = AUTOMATION_NAME + ".trigger";
62     private static final Set<String> SUBSCRIBED_EVENT_TYPES = Set.of(ItemStateEvent.TYPE, ItemStateChangedEvent.TYPE);
63     private final Logger logger = LoggerFactory.getLogger(PIDControllerTriggerHandler.class);
64     private final ScheduledExecutorService scheduler = Executors
65             .newSingleThreadScheduledExecutor(new NamedThreadFactory("OH-automation-" + AUTOMATION_NAME, true));
66     private final ServiceRegistration<?> eventSubscriberRegistration;
67     private final PIDController controller;
68     private final int loopTimeMs;
69     private @Nullable ScheduledFuture<?> controllerjob;
70     private long previousTimeMs = System.currentTimeMillis();
71     private Item inputItem;
72     private Item setpointItem;
73     private EventFilter eventFilter;
74
75     public PIDControllerTriggerHandler(Trigger module, ItemRegistry itemRegistry, EventPublisher eventPublisher,
76             BundleContext bundleContext) {
77         super(module);
78
79         Configuration config = module.getConfiguration();
80
81         String inputItemName = (String) requireNonNull(config.get(CONFIG_INPUT_ITEM), "Input item is not set");
82         String setpointItemName = (String) requireNonNull(config.get(CONFIG_SETPOINT_ITEM), "Setpoint item is not set");
83
84         try {
85             inputItem = itemRegistry.getItem(inputItemName);
86         } catch (ItemNotFoundException e) {
87             throw new IllegalArgumentException("Configured input item not found: " + inputItemName, e);
88         }
89
90         try {
91             setpointItem = itemRegistry.getItem(setpointItemName);
92         } catch (ItemNotFoundException e) {
93             throw new IllegalArgumentException("Configured setpoint item not found: " + setpointItemName, e);
94         }
95
96         double outputLowerLimit = getDoubleFromConfig(config, CONFIG_OUTPUT_LOWER_LIMIT);
97         double outputUpperLimit = getDoubleFromConfig(config, CONFIG_OUTPUT_UPPER_LIMIT);
98         double kpAdjuster = getDoubleFromConfig(config, CONFIG_KP_GAIN);
99         double kiAdjuster = getDoubleFromConfig(config, CONFIG_KI_GAIN);
100         double kdAdjuster = getDoubleFromConfig(config, CONFIG_KD_GAIN);
101         double kdTimeConstant = getDoubleFromConfig(config, CONFIG_KD_TIMECONSTANT);
102
103         loopTimeMs = ((BigDecimal) requireNonNull(config.get(CONFIG_LOOP_TIME), CONFIG_LOOP_TIME + " is not set"))
104                 .intValue();
105
106         controller = new PIDController(outputLowerLimit, outputUpperLimit, kpAdjuster, kiAdjuster, kdAdjuster,
107                 kdTimeConstant);
108
109         eventFilter = event -> {
110             String topic = event.getTopic();
111
112             return topic.equals("openhab/items/" + inputItemName + "/state")
113                     || topic.equals("openhab/items/" + inputItemName + "/statechanged")
114                     || topic.equals("openhab/items/" + setpointItemName + "/statechanged");
115         };
116
117         eventSubscriberRegistration = bundleContext.registerService(EventSubscriber.class.getName(), this, null);
118
119         eventPublisher.post(ItemEventFactory.createCommandEvent(inputItemName, RefreshType.REFRESH));
120
121         controllerjob = scheduler.scheduleWithFixedDelay(this::calculate, 0, loopTimeMs, TimeUnit.MILLISECONDS);
122     }
123
124     private <T> T requireNonNull(T obj, String message) {
125         if (obj == null) {
126             throw new IllegalArgumentException(message);
127         }
128         return obj;
129     }
130
131     private double getDoubleFromConfig(Configuration config, String key) {
132         return ((BigDecimal) Objects.requireNonNull(config.get(key), key + " is not set")).doubleValue();
133     }
134
135     private void calculate() {
136         double input;
137         double setpoint;
138
139         try {
140             input = getItemValueAsNumber(inputItem);
141         } catch (PIDException e) {
142             logger.warn("Input item: {}", e.getMessage());
143             return;
144         }
145
146         try {
147             setpoint = getItemValueAsNumber(setpointItem);
148         } catch (PIDException e) {
149             logger.warn("Setpoint item: {}", e.getMessage());
150             return;
151         }
152
153         long now = System.currentTimeMillis();
154
155         PIDOutputDTO output = controller.calculate(input, setpoint, now - previousTimeMs);
156         previousTimeMs = now;
157
158         Map<String, BigDecimal> outputs = new HashMap<>();
159
160         putBigDecimal(outputs, OUTPUT, output.getOutput());
161         putBigDecimal(outputs, P_INSPECTOR, output.getProportionalPart());
162         putBigDecimal(outputs, I_INSPECTOR, output.getIntegralPart());
163         putBigDecimal(outputs, D_INSPECTOR, output.getDerivativePart());
164         putBigDecimal(outputs, E_INSPECTOR, output.getError());
165
166         ModuleHandlerCallback localCallback = callback;
167         if (localCallback != null && localCallback instanceof TriggerHandlerCallback) {
168             ((TriggerHandlerCallback) localCallback).triggered(module, outputs);
169         } else {
170             logger.warn("No callback set");
171         }
172     }
173
174     private void putBigDecimal(Map<String, BigDecimal> map, String key, double value) {
175         map.put(key, BigDecimal.valueOf(value));
176     }
177
178     private double getItemValueAsNumber(Item item) throws PIDException {
179         State setpointState = item.getState();
180
181         if (setpointState instanceof Number) {
182             double doubleValue = ((Number) setpointState).doubleValue();
183
184             if (Double.isFinite(doubleValue)) {
185                 return doubleValue;
186             }
187         } else if (setpointState instanceof StringType) {
188             try {
189                 return Double.parseDouble(setpointState.toString());
190             } catch (NumberFormatException e) {
191                 // nothing
192             }
193         }
194         throw new PIDException(
195                 "Item type is not a number: " + setpointState.getClass().getSimpleName() + ": " + setpointState);
196     }
197
198     @Override
199     public void receive(Event event) {
200         if (event instanceof ItemStateChangedEvent) {
201             calculate();
202         }
203     }
204
205     @Override
206     public Set<String> getSubscribedEventTypes() {
207         return SUBSCRIBED_EVENT_TYPES;
208     }
209
210     @Override
211     public @Nullable EventFilter getEventFilter() {
212         return eventFilter;
213     }
214
215     @Override
216     public void dispose() {
217         eventSubscriberRegistration.unregister();
218
219         ScheduledFuture<?> localControllerjob = controllerjob;
220         if (localControllerjob != null) {
221             localControllerjob.cancel(true);
222         }
223
224         super.dispose();
225     }
226 }