]> git.basschouten.com Git - openhab-addons.git/blob
335d9641d21ac7e9e7a51992342d4a52b316b83a
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.pwm.internal.handler;
14
15 import static org.openhab.automation.pwm.internal.PWMConstants.*;
16
17 import java.math.BigDecimal;
18 import java.util.Collections;
19 import java.util.Objects;
20 import java.util.Optional;
21 import java.util.Set;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.automation.pwm.internal.PWMException;
28 import org.openhab.automation.pwm.internal.handler.state.StateMachine;
29 import org.openhab.core.automation.ModuleHandlerCallback;
30 import org.openhab.core.automation.Trigger;
31 import org.openhab.core.automation.handler.BaseTriggerModuleHandler;
32 import org.openhab.core.automation.handler.TriggerHandlerCallback;
33 import org.openhab.core.config.core.Configuration;
34 import org.openhab.core.events.Event;
35 import org.openhab.core.events.EventFilter;
36 import org.openhab.core.events.EventSubscriber;
37 import org.openhab.core.items.Item;
38 import org.openhab.core.items.ItemNotFoundException;
39 import org.openhab.core.items.ItemRegistry;
40 import org.openhab.core.items.events.ItemStateEvent;
41 import org.openhab.core.library.types.DecimalType;
42 import org.openhab.core.library.types.OnOffType;
43 import org.openhab.core.library.types.StringType;
44 import org.openhab.core.types.State;
45 import org.openhab.core.types.UnDefType;
46 import org.osgi.framework.BundleContext;
47 import org.osgi.framework.ServiceRegistration;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 /**
52  * Represents a Trigger module in the rules engine.
53  *
54  * @author Fabian Wolter - Initial Contribution
55  */
56 @NonNullByDefault
57 public class PWMTriggerHandler extends BaseTriggerModuleHandler implements EventSubscriber {
58     public static final String MODULE_TYPE_ID = AUTOMATION_NAME + ".trigger";
59     private static final Set<String> SUBSCRIBED_EVENT_TYPES = Set.of(ItemStateEvent.TYPE);
60     private final Logger logger = LoggerFactory.getLogger(PWMTriggerHandler.class);
61     private final BundleContext bundleContext;
62     private final EventFilter eventFilter;
63     private final Optional<Double> minDutyCycle;
64     private final Optional<Double> maxDutyCycle;
65     private final boolean isEquateMinToZero;
66     private final boolean isEquateMaxToHundred;
67     private final Optional<Double> deadManSwitchTimeoutMs;
68     private final Item dutyCycleItem;
69     private @Nullable ServiceRegistration<?> eventSubscriberRegistration;
70     private @Nullable ScheduledFuture<?> deadMeanSwitchTimer;
71     private @Nullable StateMachine stateMachine;
72     private String ruleUID;
73
74     public PWMTriggerHandler(Trigger module, ItemRegistry itemRegistry, BundleContext bundleContext, String ruleUID) {
75         super(module);
76         this.bundleContext = bundleContext;
77         this.ruleUID = ruleUID;
78
79         Configuration config = module.getConfiguration();
80
81         String dutycycleItemName = (String) Objects.requireNonNull(config.get(CONFIG_DUTY_CYCLE_ITEM),
82                 "DutyCycle item is not set");
83
84         minDutyCycle = getOptionalDoubleFromConfig(config, CONFIG_MIN_DUTYCYCLE);
85         isEquateMinToZero = getBooleanFromConfig(config, CONFIG_EQUATE_MIN_TO_ZERO);
86         maxDutyCycle = getOptionalDoubleFromConfig(config, CONFIG_MAX_DUTYCYCLE);
87         isEquateMaxToHundred = getBooleanFromConfig(config, CONFIG_EQUATE_MAX_TO_HUNDRED);
88         deadManSwitchTimeoutMs = getOptionalDoubleFromConfig(config, CONFIG_DEAD_MAN_SWITCH);
89
90         try {
91             dutyCycleItem = itemRegistry.getItem(dutycycleItemName);
92         } catch (ItemNotFoundException e) {
93             throw new IllegalArgumentException("Dutycycle item not found: " + dutycycleItemName, e);
94         }
95
96         eventFilter = event -> event.getTopic().equals("openhab/items/" + dutycycleItemName + "/state");
97     }
98
99     @Override
100     public void setCallback(ModuleHandlerCallback callback) {
101         super.setCallback(callback);
102
103         double periodSec = getDoubleFromConfig(module.getConfiguration(), CONFIG_PERIOD);
104         stateMachine = new StateMachine(getCallback().getScheduler(), this::setOutput, (long) (periodSec * 1000),
105                 ruleUID);
106
107         eventSubscriberRegistration = bundleContext.registerService(EventSubscriber.class.getName(), this, null);
108     }
109
110     private double getDoubleFromConfig(Configuration config, String key) {
111         return ((BigDecimal) Objects.requireNonNull(config.get(key), ruleUID + ": " + key + " is not set"))
112                 .doubleValue();
113     }
114
115     private Optional<Double> getOptionalDoubleFromConfig(Configuration config, String key) {
116         Object o = config.get(key);
117
118         if (o instanceof BigDecimal) {
119             return Optional.of(((BigDecimal) o).doubleValue());
120         }
121
122         return Optional.empty();
123     }
124
125     private boolean getBooleanFromConfig(Configuration config, String key) {
126         return ((Boolean) config.get(key)).booleanValue();
127     }
128
129     @Override
130     public void receive(Event event) {
131         if (!(event instanceof ItemStateEvent)) {
132             return;
133         }
134
135         ItemStateEvent changedEvent = (ItemStateEvent) event;
136         synchronized (this) {
137             try {
138                 double newDutycycle = getDutyCycleValueInPercent(changedEvent.getItemState());
139                 double newDutycycleBeforeLimit = newDutycycle;
140
141                 restartDeadManSwitchTimer();
142
143                 // set duty cycle to 0% if it is 0% or it is smaller than min duty cycle and equateMinToZero is true
144                 // set duty cycle to min duty cycle if it is smaller than min duty cycle
145                 final double newDutyCycleFinal1 = newDutycycle;
146                 newDutycycle = minDutyCycle.map(minDutycycle -> {
147                     long dutycycleRounded1 = Math.round(newDutyCycleFinal1);
148                     if (dutycycleRounded1 <= 0 || (dutycycleRounded1 <= minDutycycle && isEquateMinToZero)) {
149                         return 0d;
150                     } else {
151                         return Math.max(minDutycycle, newDutyCycleFinal1);
152                     }
153                 }).orElse(newDutycycle);
154
155                 // set duty cycle to 100% if it is 100% or it is larger then max duty cycle and equateMaxToHundred is
156                 // true
157                 // set duty cycle to max duty cycle if it is larger then max duty cycle
158                 final double newDutyCycleFinal2 = newDutycycle;
159                 newDutycycle = maxDutyCycle.map(maxDutycycle -> {
160                     long dutycycleRounded2 = Math.round(newDutyCycleFinal2);
161                     if (dutycycleRounded2 >= 100 || (dutycycleRounded2 >= maxDutycycle && isEquateMaxToHundred)) {
162                         return 100d;
163                     } else {
164                         return Math.min(maxDutycycle, newDutyCycleFinal2);
165                     }
166                 }).orElse(newDutycycle);
167
168                 logger.debug("{}: Received new duty cycle: {} {}", ruleUID, newDutycycleBeforeLimit,
169                         newDutycycle != newDutycycleBeforeLimit ? "Limited to: " + newDutycycle : "");
170
171                 StateMachine localStateMachine = stateMachine;
172                 if (localStateMachine != null) {
173                     localStateMachine.setDutycycle(newDutycycle);
174                 } else {
175                     logger.debug("{}: Initialization not finished", ruleUID);
176                 }
177             } catch (PWMException e) {
178                 logger.warn("{}: {}", ruleUID, e.getMessage());
179             }
180         }
181     }
182
183     private void restartDeadManSwitchTimer() {
184         ScheduledFuture<?> timer = deadMeanSwitchTimer;
185         if (timer != null) {
186             timer.cancel(true);
187         }
188
189         deadManSwitchTimeoutMs.ifPresent(timeout -> {
190             deadMeanSwitchTimer = getCallback().getScheduler().schedule(this::activateDeadManSwitch,
191                     timeout.longValue(), TimeUnit.MILLISECONDS);
192         });
193     }
194
195     private void activateDeadManSwitch() {
196         logger.warn("{}: Dead-man switch activated. Disabling output", ruleUID);
197
198         StateMachine localStateMachine = stateMachine;
199         if (localStateMachine != null) {
200             localStateMachine.stop();
201         }
202     }
203
204     private void setOutput(boolean enable) {
205         getCallback().triggered(module, Collections.singletonMap(OUTPUT, OnOffType.from(enable)));
206     }
207
208     private TriggerHandlerCallback getCallback() {
209         ModuleHandlerCallback localCallback = callback;
210         if (localCallback != null && localCallback instanceof TriggerHandlerCallback) {
211             return (TriggerHandlerCallback) localCallback;
212         }
213
214         throw new IllegalStateException();
215     }
216
217     private double getDutyCycleValueInPercent(State state) throws PWMException {
218         if (state instanceof DecimalType) {
219             return ((DecimalType) state).doubleValue();
220         } else if (state instanceof StringType) {
221             try {
222                 return Integer.parseInt(state.toString());
223             } catch (NumberFormatException e) {
224                 // nothing
225             }
226         } else if (state instanceof UnDefType) {
227             throw new PWMException(ruleUID + ": Duty cycle item '" + dutyCycleItem.getName() + "' has no valid value");
228         }
229         throw new PWMException(
230                 ruleUID + ": Duty cycle item not of type DecimalType: " + state.getClass().getSimpleName());
231     }
232
233     @Override
234     public Set<String> getSubscribedEventTypes() {
235         return SUBSCRIBED_EVENT_TYPES;
236     }
237
238     @Override
239     public @Nullable EventFilter getEventFilter() {
240         return eventFilter;
241     }
242
243     @Override
244     public void dispose() {
245         ServiceRegistration<?> localEventSubscriberRegistration = eventSubscriberRegistration;
246         if (localEventSubscriberRegistration != null) {
247             localEventSubscriberRegistration.unregister();
248         }
249
250         super.dispose();
251     }
252 }